// Copyright (c) 2022 Robin Davies // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "SysExec.hpp" #include #include #include #include #include #include #include #include #include // for PATH_MAX #include #include #include "ss.hpp" #include #include using namespace pipedal; using namespace std; #pragma GCC diagnostic ignored "-Wunused-result" // find on path, but ONLY /usr/bin and /usr/sbin static std::filesystem::path findOnSystemPath(const std::string &command) { if (command.length() != 0 && command[0] == '/') { return command; } std::string path = "/usr/bin:/usr/sbin"; std::vector paths; size_t t = 0; while (t < path.length()) { size_t pos = path.find(':', t); if (pos == string::npos) { pos = path.length(); } std::string thisPath = path.substr(t, pos - t); std::filesystem::path path = std::filesystem::path(thisPath) / command; if (std::filesystem::exists(path)) { return path; } t = pos + 1; } std::stringstream s; s << "'" << command << "' is not installed."; throw std::runtime_error(s.str()); } int pipedal::silentSysExec(const char *szCommand) { std::stringstream s; s << szCommand << " 2>&1"; FILE *output = popen(s.str().c_str(), "r"); char buffer[512]; if (output) { while (!feof(output)) { fgets(buffer, sizeof(buffer), output); } return pclose(output); } return -1; } int pipedal::sysExec(const char *szCommand) { char *args = strdup(szCommand); int argc; std::vector argv; char *p = args; while (*p) { argv.push_back(p); while (*p && *p != ' ') { ++p; } if (*p) { *p++ = '\0'; while (*p && *p == ' ') { ++p; } } } argv.push_back(nullptr); std::filesystem::path execPath = argv[0]; if (execPath.is_relative()) { execPath = findOnSystemPath(execPath); } if (!std::filesystem::exists(execPath)) { free((void *)args); throw std::runtime_error(SS("Path does not exist: " << execPath << ".")); } argv[0] = (char *)(execPath.c_str()); char **rawArgv = &argv[0]; int pbPid; int returnValue = 0; if ((pbPid = fork()) == 0) { execv(argv[0], rawArgv); exit(-1); } else { free((void *)args); waitpid(pbPid, &returnValue, 0); int exitStatus = WEXITSTATUS(returnValue); return exitStatus; } } std::string pipedal::getSelfExePath() { char result[PATH_MAX+1]; ssize_t count = readlink("/proc/self/exe", result, PATH_MAX); if (count < 0) throw std::runtime_error("Can't find EXE path."); result[count] = 0; return result; } ProcessId pipedal::sysExecAsync(const std::string&command) { char *args = strdup(command.c_str()); int argc; std::vector argv; char *p = args; while (*p) { argv.push_back(p); while (*p && *p != ' ') { ++p; } if (*p) { *p++ = '\0'; while (*p && *p == ' ') { ++p; } } } argv.push_back(nullptr); std::filesystem::path execPath = argv[0]; if (execPath.is_relative()) { execPath = findOnSystemPath(execPath); } if (!std::filesystem::exists(execPath)) { free((void *)args); throw std::runtime_error(SS("Path does not exist: " << execPath << ".")); } argv[0] = (char *)(execPath.c_str()); char **rawArgv = &argv[0]; int pbPid; if ((pbPid = fork()) == 0) { // redirect output to /dev/null int null_fd = open("/dev/null",O_WRONLY); if (null_fd == -1) { std::cerr << "Failed to open /dev/null" << std::endl; exit(EXIT_FAILURE); } dup2(null_fd,STDOUT_FILENO); dup2(null_fd,STDERR_FILENO); close(null_fd); return execv(argv[0], rawArgv); } else { free((void *)args); return pbPid; } } void pipedal::sysExecTerminate(ProcessId pid_,int termTimeoutMs,int killTimeoutMs ) { pid_t pid = (pid_t)pid_; if (pid == -1) return; if (kill(pid,SIGTERM) == -1) { throw std::runtime_error("Failed to send SIGTERM"); } auto start = std::chrono::steady_clock::now(); int status; do { pid_t result; result = waitpid(pid,&status,WNOHANG); if (result > 0) { return; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } while ( std::chrono::duration_cast( std::chrono::steady_clock::now()-start).count() < termTimeoutMs ); start = std::chrono::steady_clock::now(); if (kill(pid,SIGKILL) == -1) // some odd async effect? { return; } do { pid_t result; result = waitpid(pid,&status,WNOHANG); if (result > 0) { return; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } while ( std::chrono::duration_cast( std::chrono::steady_clock::now()-start).count() < killTimeoutMs ); throw std::runtime_error("Failed to terminate process."); } int pipedal::sysExecWait(ProcessId pid_) { pid_t pid = (pid_t)pid_; int returnValue; waitpid(pid, &returnValue, 0); int exitStatus = WEXITSTATUS(returnValue); return exitStatus; } SysExecOutput pipedal::sysExecForOutput(const std::string &program, const std::string &args) { namespace fs = std::filesystem; fs::path fullPath = findOnSystemPath(program); if (!fs::exists(fullPath)) { throw std::runtime_error(SS("Path does not exist. " << fullPath)); } std::stringstream s; s << fullPath.c_str() << " " << args << " 2>&1"; std::string fullCommand = s.str(); FILE *output = popen(fullCommand.c_str(), "r"); if (output) { char buffer[512]; std::stringstream ssOutput; while (!feof(output)) { size_t nRead = fread(buffer, sizeof(char),sizeof(buffer), output); ssOutput.write(buffer,nRead); if (nRead == 0) break; } int rc = pclose(output); SysExecOutput result { .exitCode = rc, .output = ssOutput.str() }; return result; } else { throw std::runtime_error(SS("Failed to execute command. " << fullCommand )); } } int pipedal::SudoExec(int argc, char **argv) { // re-execute with SUDO in order to prompt for SUDO credentials once only. std::vector args; std::string pkexec = "/usr/bin/sudo"; // staged because "ISO C++ forbids converting a string constant to std::vector::value_type"(!) args.push_back((char *)(pkexec.c_str())); std::string sPath = getSelfExePath(); args.push_back(const_cast(sPath.c_str())); for (int arg = 1; arg < argc; ++arg) { args.push_back(const_cast(argv[arg])); } args.push_back(nullptr); char **newArgs = &args[0]; int pbPid; int returnValue = 0; if ((pbPid = fork()) == 0) { return execv(newArgs[0], newArgs); } else { waitpid(pbPid, &returnValue, 0); int exitStatus = WEXITSTATUS(returnValue); return exitStatus; } }