summaryrefslogtreecommitdiff
path: root/hyprpm/src/helpers/Sys.cpp
blob: 868ad9a9533ca4964f78f0a30716fefe4144f507 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include "Sys.hpp"
#include "Die.hpp"
#include "StringUtils.hpp"

#include <pwd.h>
#include <unistd.h>
#include <print>
#include <filesystem>
#include <optional>

#include <hyprutils/os/Process.hpp>
#include <hyprutils/string/VarList.hpp>

using namespace Hyprutils::OS;
using namespace Hyprutils::String;

inline constexpr std::array<std::string_view, 3> SUPERUSER_BINARIES = {
    "sudo",
    "doas",
    "run0",
};

static std::string fetchSuperuserBins() {
    std::ostringstream oss;
    auto               it = SUPERUSER_BINARIES.begin();
    if (it != SUPERUSER_BINARIES.end()) {
        oss << *it++;
        for (; it != SUPERUSER_BINARIES.end(); ++it)
            oss << ", " << *it;
    }

    return oss.str();
}

static bool executableExistsInPath(const std::string& exe) {
    const char* PATHENV = std::getenv("PATH");
    if (!PATHENV)
        return false;

    CVarList        paths(PATHENV, 0, ':', true);
    std::error_code ec;

    for (const auto& PATH : paths) {
        std::filesystem::path candidate = std::filesystem::path(PATH) / exe;
        if (!std::filesystem::exists(candidate, ec) || ec)
            continue;
        if (!std::filesystem::is_regular_file(candidate, ec) || ec)
            continue;
        auto perms = std::filesystem::status(candidate, ec).permissions();
        if (ec)
            continue;
        if ((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none)
            return true;
    }

    return false;
}

static std::optional<std::pair<std::string, int>> execAndGet(std::string_view cmd, bool noRedirect = false) {
    std::string command = std::string{cmd};
    if (!noRedirect)
        command += " 2>&1";

    CProcess proc("/bin/sh", {"-c", command});
    if (!proc.runSync())
        // optional handles nullopt gracefully
        return std::nullopt;

    return {{proc.stdOut(), proc.exitCode()}};
}

int NSys::getUID() {
    const auto UID   = getuid();
    const auto PWUID = getpwuid(UID);
    return PWUID ? PWUID->pw_uid : UID;
}

int NSys::getEUID() {
    const auto UID   = geteuid();
    const auto PWUID = getpwuid(UID);
    return PWUID ? PWUID->pw_uid : UID;
}

bool NSys::isSuperuser() {
    return getuid() != geteuid() || geteuid() == 0;
}

std::string NSys::runAsSuperuser(const std::string& cmd) {
    for (const auto& BIN : SUPERUSER_BINARIES) {
        if (!executableExistsInPath(std::string{BIN}))
            continue;

        const auto result = execAndGet(std::string{BIN} + " /bin/sh -c \"" + cmd + "\"", true);
        if (!result.has_value() || result->second != 0)
            Debug::die("Failed to run a command as sudo. This could be due to an invalid password, or a hyprpm bug.");

        return result->first;
    }

    Debug::die("{} {}", "Failed to find a superuser binary. Supported: ", fetchSuperuserBins());
    return "";
}

void NSys::cacheSudo() {
    // "caches" the sudo so that the prompt later doesn't pop up in a weird spot
    // sudo will not ask us again for a moment
    runAsSuperuser("echo e > /dev/null");
}

void NSys::dropSudo() {
    for (const auto& BIN : SUPERUSER_BINARIES) {
        if (!executableExistsInPath(std::string{BIN}))
            continue;

        if (BIN == "sudo")
            execAndGet("sudo -k");
        else {
            // note the superuser binary that is being dropped
            std::println("{}", infoString("Don't know how to drop timestamp for '{}', ignoring.", BIN));
        }
        return;
    }
}