FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

GitHub Viewer

/* * PosixSystem.cpp * * Copyright (C) 2022 by Posit Software, PBC * * Unless you have received this program directly from Posit Software pursuant * to the terms of a commercial license agreement with Posit Software, then * this program is licensed to you under the terms of version 3 of the * GNU Affero General Public License. This program is distributed WITHOUT * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __APPLE__ #include #include #include #include #endif #ifdef __linux__ #include #include #include // Some data structures here don't compile on rhel8 due to 'private' being used as a symbol??? // so defining them again here //#include #define KEYCTL_JOIN_SESSION_KEYRING 1 /* join or start named session keyring */ #define KEYCTL_LINK 8 /* link a key into a keyring */ #define KEY_SPEC_SESSION_KEYRING -3 /* - key ID for session-specific keyring */ #define KEY_SPEC_USER_KEYRING -4 /* - key ID for UID-specific keyring */ #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" using namespace boost::placeholders; namespace rstudio { namespace core { namespace system { namespace { int signalForType(SignalType type) { switch(type) { case SigInt: return SIGINT; case SigHup: return SIGHUP; case SigAbrt: return SIGABRT; case SigSegv: return SIGSEGV; case SigIll: return SIGILL; case SigUsr1: return SIGUSR1; case SigUsr2: return SIGUSR2; case SigPipe: return SIGPIPE; case SigChld: return SIGCHLD; case SigTerm: return SIGTERM; default: return -1; } } } // anonymous namespace Error realPath(const FilePath& filePath, FilePath* pRealPath) { std::string path = string_utils::utf8ToSystem(filePath.getAbsolutePath()); char buffer[PATH_MAX*2]; char* realPath = ::realpath(path.c_str(), buffer); if (realPath == nullptr) { Error error = systemError(errno, ERROR_LOCATION); error.addProperty("path", filePath); return error; } *pRealPath = FilePath(string_utils::systemToUtf8(realPath)); return Success(); } Error realPath(const std::string& path, FilePath* pRealPath) { char buffer[PATH_MAX * 2]; char* realPath = ::realpath(path.c_str(), buffer); if (realPath == nullptr) { Error error = systemError(errno, ERROR_LOCATION); error.addProperty("path", path); return error; } *pRealPath = FilePath(realPath); return Success(); } void initHook() { } Error findProgramOnPath(const std::string& program, core::FilePath* pProgramPath) { std::string path = core::system::getenv("PATH"); auto paths = core::algorithm::split(path, ":"); for (auto&& path : paths) { // get file descriptor for path entry int fd = ::open(path.c_str(), 0); if (fd == -1) continue; // clean up when we're done BOOST_SCOPE_EXIT( (&fd) ) { ::close(fd); } BOOST_SCOPE_EXIT_END; // confirm that it's a regular file struct stat sb; int status = ::fstatat(fd, program.c_str(), &sb, 0); if (status == -1) continue; bool isRegularFile = S_ISREG(sb.st_mode); if (!isRegularFile) continue; // confirm that it's executable // note that we use AT_EACCESS to ensure checks are done using // the effective user id status = ::faccessat(fd, program.c_str(), X_OK, AT_EACCESS); if (status == -1) continue; // all checks passed; return full path *pProgramPath = FilePath(path).completeChildPath(program); return Success(); } return fileNotFoundError(program, ERROR_LOCATION); } // statics defined in System.cpp extern boost::shared_ptr s_logOptions; extern boost::recursive_mutex s_loggingMutex; extern std::string s_programIdentity; Error initializeSystemLog(const std::string& programIdentity, log::LogLevel logLevel, bool enableConfigReload) { RECURSIVE_LOCK_MUTEX(s_loggingMutex) { // create default syslog logger options log::SysLogOptions options; s_logOptions.reset(new log::LogOptions(programIdentity, logLevel, log::LoggerType::kSysLog, log::LogMessageFormatType::PRETTY, options)); s_programIdentity = programIdentity; Error error = initLog(); if (error) return error; } END_LOCK_MUTEX if (enableConfigReload) initializeLogConfigReload(); return Success(); } boost::function s_sighupHandler; namespace { void logConfigReloadThreadFunc(sigset_t waitMask) { for(;;) { // wait for SIGHUP int sig = 0; int result = ::sigwait(&waitMask, &sig); if (result != 0) return; if (sig == SIGHUP) { LOG_INFO_MESSAGE("Reloading logging configuration..."); Error error = reinitLog(); if (error) { LOG_ERROR(error); LOG_ERROR_MESSAGE("Failed to reload logging configuration"); } else { LOG_INFO_MESSAGE("Successfully reloaded logging configuration"); } // call previously registered SIGHUP handlder if (s_sighupHandler) s_sighupHandler(); } } } } // anonymous namespace void initializeLogConfigReload() { // block the SIGHUP signal sigset_t waitMask; sigemptyset(&waitMask); sigaddset(&waitMask, SIGHUP); int result = ::pthread_sigmask(SIG_BLOCK, &waitMask, nullptr); if (result != 0) return; // start a thread to handle the SIGHUP signal boost::thread thread(boost::bind(logConfigReloadThreadFunc, waitMask)); } void registerSighupHandler(const boost::function& sighupHandler) { s_sighupHandler = sighupHandler; } Error ignoreTerminalSignals() { ExecBlock ignoreBlock; ignoreBlock.addFunctions() (boost::bind(posix::ignoreSignal, SIGHUP)) (boost::bind(posix::ignoreSignal, SIGTSTP)) (boost::bind(posix::ignoreSignal, SIGTTOU)) (boost::bind(posix::ignoreSignal, SIGTTIN)); return ignoreBlock.execute(); } Error ignoreChildExits() { #if defined(HAVE_SA_NOCLDWAIT) // POSIX compliant struct sigaction reapchildren; ::memset( &reapchildren, 0, sizeof reapchildren ); reapchildren.sa_flags = SA_NOCLDWAIT; int result = ::sigaction( SIGCHLD, &reapchildren, 0); if (result != 0) return systemError(result, ERROR_LOCATION); #else // other systems if (::signal(SIGCHLD, SIG_IGN) == SIG_ERR) return systemError(errno, ERROR_LOCATION); #endif // NO_CLDWAIT return Success(); } namespace { void handleSIGCHLD(int sig) { int stat; while (::waitpid (-1, &stat, WNOHANG) > 0) { } } } Error reapChildren() { // setup signal handler struct sigaction sa; ::memset(&sa, 0, sizeof sa); sa.sa_handler = handleSIGCHLD; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; // install it int result = ::sigaction(SIGCHLD, &sa, nullptr); if (result != 0) return systemError(errno, ERROR_LOCATION); else return Success(); } // SignalBlocker -- block signals in a scope // // NOTE: boost has a portable signal_blocker class however we don't use it b/c: // 1) it is in the detail namespace (which isn't a public api) // 2) it doesn't check or fail on errors // // NOTE: blocking signals in threads and then handling them on either the // main thread or a dedicated thread will only work properly on Linux kernel // v2.6 or higher (because it supports the Native POSIX Thread Library and // thus delivers signals to multi-threaded programs in a POSIX compliant way) struct SignalBlocker::Impl { Impl() : blocked(false) {} bool blocked; sigset_t oldMask; Error block(sigset_t* pBlockMask) { // install mask int result = ::pthread_sigmask(SIG_BLOCK, pBlockMask, &oldMask); if (result != 0) return systemError(result, ERROR_LOCATION); // set restore bit and return success blocked = true; return Success(); } }; SignalBlocker::SignalBlocker() : pImpl_(new Impl()) { } Error SignalBlocker::block(SignalType signal) { // get signal int sig = signalForType(signal); if (sig < 0) return systemError(EINVAL, ERROR_LOCATION); // create a mask for blocking the signal sigset_t blockMask; sigemptyset(&blockMask); sigaddset(&blockMask, sig); // block return pImpl_->block(&blockMask); } Error SignalBlocker::blockAll() { // create mask to block all signals sigset_t blockMask; sigfillset(&blockMask); // block return pImpl_->block(&blockMask); } SignalBlocker::~SignalBlocker() { try { if (pImpl_->blocked) { // restore old mask int result = ::pthread_sigmask(SIG_SETMASK, &(pImpl_->oldMask), nullptr); if (result != 0) LOG_ERROR(systemError(result, ERROR_LOCATION)); } } catch(...) { } } Error clearSignalMask() { int result = signal_safe::clearSignalMask(); if (result != 0) return systemError(result, ERROR_LOCATION); else return Success(); } Error handleSignal(SignalType signal, void (*handler)(int)) { int sig = signalForType(signal); if (sig < 0) return systemError(EINVAL, ERROR_LOCATION); ::signal(sig, handler); return Success(); } core::Error ignoreSignal(SignalType signal) { int sig = signalForType(signal); if (sig < 0) return systemError(EINVAL, ERROR_LOCATION); return posix::ignoreSignal(sig); } Error useDefaultSignalHandler(SignalType signal) { int sig = signalForType(signal); if (sig < 0) return systemError(EINVAL, ERROR_LOCATION); struct sigaction sa; ::memset(&sa, 0, sizeof sa); sa.sa_handler = SIG_DFL; int result = ::sigaction(sig, &sa, nullptr); if (result != 0) { Error error = systemError(result, ERROR_LOCATION); return error; } else { return Success(); } } void sendSignalToSelf(SignalType signal) { ::kill(::getpid(), signalForType(signal)); } std::string username() { return system::getenv("USER"); } unsigned int effectiveUserId() { return ::geteuid(); } FilePath userHomePath(std::string envOverride) { return User::getUserHomePath(envOverride); } FilePath userSettingsPath(const FilePath& userHomeDirectory, const std::string& appName, bool ensureDirectory) { std::string lower = appName; boost::to_lower(lower); FilePath path = userHomeDirectory.completeChildPath("." + lower); if (ensureDirectory) { Error error = path.ensureDirectory(); if (error) { LOG_ERROR(error); } } return path; } bool currentUserIsPrivilleged(unsigned int minimumUserId) { return ::geteuid() < minimumUserId; } namespace { // NOTE: this function is duplicated between here and core::system // Did this to prevent the "system" interface from allowing Posix // constructs with Win32 no-ops to creep in (since this is used on // Posix for forking and has no purpose on Win32) // There is no fully reliable and cross-platform way to do this, see: // // Various potential mechanisms include: // // - closefrom // - fcntl(0, F_MAXFD) // - sysconf(_SC_OPEN_MAX) // - getrlimit(RLIMIT_NOFILE, &rl) // - gettdtablesize // - read from /proc/self/fd, /proc//fd, or /dev/fd // // Note that the above functions may return either -1 or MAX_INT, in // which case substituting/truncating to an appropriate number (1024?) // is still required #if !defined(__APPLE__) && !defined(HAVE_PROCSELF) // worst case scenario - close all file descriptors possible // this can be EXTREMELY slow when max fd is set to a high value // note: this conditional should actually never be true // as all linux systems have /proc/self - this is preserved in the codebase // as a remainder of what was being done in the recent past Error closeFileDescriptorsFrom(int fdStart) { // get limit struct rlimit rl; if (::getrlimit(RLIMIT_NOFILE, &rl) < 0) return systemError(errno, ERROR_LOCATION); if (rl.rlim_max == RLIM_INFINITY) rl.rlim_max = 1024; // default on linux // close file descriptors for (int i=fdStart; i< (int)rl.rlim_max; i++) { if (::close(i) < 0 && errno != EBADF) return systemError(errno, ERROR_LOCATION); } return Success(); } #else // read the file descriptors from a virtual directory listing, // iterate over them and close - much faster than the above method Error closeFileDescriptorsFrom(int fdStart) { std::vector fds; Error error = getOpenFds(&fds); if (error) return error; for (int fd : fds) { if (fd >= fdStart) { if (::close(fd) < 0 && errno != EBADF) return systemError(errno, ERROR_LOCATION); } } return Success(); } #endif } // anonymous namespace Error getOpenFds(std::vector* pFds) { return getOpenFds(getpid(), pFds); } #ifndef __APPLE__ Error getOpenFds(pid_t pid, std::vector* pFds) { std::string pidStr = safe_convert::numberToString(pid); boost::format fmt("/proc/%1%/fd"); FilePath filePath(boost::str(fmt % pidStr)); // note: we use a FileScanner to list the pids instead of using boost // (FilePath class), because there is a bug in boost filesystem where // directory iterators can segfault under heavy load while reading the /proc filesystem // there aren't many details on this, but see https://svn.boost.org/trac10/ticket/10450 core::system::FileScannerOptions options; options.recursive = false; tree subDirs; Error error = core::system::scanFiles(core::toFileInfo(filePath), options, &subDirs); if (error) return error; for (const FileInfo& info : subDirs) { FilePath path(info.absolutePath()); boost::optional fd = safe_convert::stringTo(path.getFilename()); if (fd) { pFds->push_back(fd.get()); } } return Success(); } #else Error getOpenFds(pid_t pid, std::vector *pFds) { // get size of the buffer needed to hold the list of fds int bufferSize = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, 0, 0); if (bufferSize == -1) return systemError(errno, ERROR_LOCATION); // get the list of open fds struct proc_fdinfo* procFdInfo = static_cast(malloc(bufferSize)); if (!procFdInfo) return systemError(boost::system::errc::not_enough_memory, ERROR_LOCATION); int filledSize = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, procFdInfo, bufferSize); int numFds = filledSize / PROC_PIDLISTFD_SIZE; for (int i = 0; i < numFds; ++i) { pFds->push_back(procFdInfo[i].proc_fd); } free(procFdInfo); return Success(); } #endif Error closeAllFileDescriptors() { return closeFileDescriptorsFrom(0); } Error closeNonStdFileDescriptors() { return closeFileDescriptorsFrom(STDERR_FILENO+1); } Error closeChildFileDescriptorsFrom(pid_t childPid, int pipeFd, uint32_t fdStart) { std::size_t written; std::vector fds; Error error = getOpenFds(childPid, &fds); if (!error) { for (uint32_t fd : fds) { error = posix::posixCall( boost::bind( ::write, pipeFd, &fd, 4), ERROR_LOCATION, &written); if (error) { return error; } } } else { // we simply log the error instead of returning it because this is generally benign and can // happen in certain normal scenarios, such as if /proc/x/fd is only readable by root // (if core dumps are turned off) core::log::logErrorAsDebug(error); } // write message close (-1) even if we failed to retrieve pids above // this prevents the child from being stuck in limbo or interpreting its // actual stdin as fds int close = -1; Error closeError = posix::posixCall( boost::bind( ::write, pipeFd, &close, 4), ERROR_LOCATION, &written); if (closeError) { return error; } return closeError; } namespace signal_safe { namespace { void safeClose(uint32_t fd) { while (::close(fd) == -1) { // keep trying the close operation if it was interrupted // otherwise, the file descriptor is not open, so return if (errno != EINTR) break; } } // worst case scenario - close all file descriptors possible // this can be EXTREMELY slow when max fd is set to a high value void closeFileDescriptorsFromSafe(uint32_t fdStart, rlim_t fdLimit) { // safe function is best effort - swallow all errors // this is necessary when invoked in a signal handler or // during a fork in multithreaded processes to prevent hangs if (fdLimit == RLIM_INFINITY) fdLimit = 1024; // default on linux // close file descriptors for (uint32_t i = fdStart; i < fdLimit; ++i) { safeClose(i); } } } // anonymous namespace void closeNonStdFileDescriptors(rlim_t fdLimit) { closeFileDescriptorsFromSafe(STDERR_FILENO+1, fdLimit); } void closeFileDescriptorsFromParent(int pipeFd, uint32_t fdStart, rlim_t fdLimit) { // read fds that we own from parent process pipe until we've read them all // the parent must give us this list because we cannot fetch it ourselves // in a signal-safe way, but we can read from the pipe safely bool error = false; bool fdsRead = false; int32_t buff; while (true) { ssize_t bytesRead = ::read(pipeFd, &buff, 4); // check for error if (bytesRead == -1 || bytesRead == 0) { if (errno != EINTR && errno != EAGAIN) { error = true; break; } continue; } // determine which fd was just read from the parent if (buff == -1) { break; // indicates no more fds are open by the process } fdsRead = true; uint32_t fd = static_cast(buff); // close the reported fd if it is in range if (fd >= fdStart && fd < fdLimit && buff != pipeFd) safeClose(fd); } // if no descriptors could be read from the parent for whatever reason, // or there was an error reading from the pipe, // fall back to the slow close method detailed above if (error || !fdsRead) { closeFileDescriptorsFromSafe(fdStart, pipeFd); closeFileDescriptorsFromSafe(pipeFd + 1, fdLimit); } } int permanentlyDropPriv(UidType newUid) { return ::setuid(newUid); } int restoreRoot() { return ::setuid(0); } int clearSignalMask() { sigset_t blockNoneMask; sigemptyset(&blockNoneMask); return ::pthread_sigmask(SIG_SETMASK, &blockNoneMask, nullptr); } } // namespace signal_safe void closeStdFileDescriptors() { ::close(STDIN_FILENO); ::close(STDOUT_FILENO); ::close(STDERR_FILENO); } void attachStdFileDescriptorsToDevNull() { int fd0 = ::open("/dev/null", O_RDWR); if (fd0 == -1) LOG_ERROR(systemError(errno, ERROR_LOCATION)); int fd1 = ::dup(fd0); if (fd1 == -1) LOG_ERROR(systemError(errno, ERROR_LOCATION)); int fd2 = ::dup(fd0); if (fd2 == -1) LOG_ERROR(systemError(errno, ERROR_LOCATION)); } void setStandardStreamsToDevNull() { core::system::closeStdFileDescriptors(); core::system::attachStdFileDescriptorsToDevNull(); std::ios::sync_with_stdio(); } bool isHiddenFile(const FilePath& filePath) { std::string filename = filePath.getFilename(); return (!filename.empty() && (filename[0] == '.')); } bool isHiddenFile(const FileInfo& fileInfo) { return isHiddenFile(FilePath(fileInfo.absolutePath())); } bool isReadOnly(const FilePath& filePath) { if (::access(filePath.getAbsolutePath().c_str(), W_OK) == -1) { if (errno == EACCES) { return true; } else { Error error = systemError(errno, ERROR_LOCATION); error.addProperty("path", filePath); LOG_ERROR(error); return false; } } else { return false; } } bool stderrIsTerminal() { return ::isatty(STDERR_FILENO) == 1; } bool stdoutIsTerminal() { return ::isatty(STDOUT_FILENO) == 1; } std::string generateUuid(bool includeDashes) { // generaate the uuid and convert it to a strting uuid_t uuid; ::uuid_generate_random(uuid); char uuidBuffer[40]; ::uuid_unparse_lower(uuid, uuidBuffer); std::string uuidStr(uuidBuffer); // remove dashes if requested if (!includeDashes) boost::algorithm::replace_all(uuidStr, "-", ""); return uuidStr; } PidType currentProcessId() { return ::getpid(); } Error executablePath(int argc, char * const argv[], FilePath* pExecutablePath) { return executablePath(argv[0], pExecutablePath); } Error executablePath(const char * argv0, FilePath* pExecutablePath) { std::string executablePath; #if defined(__APPLE__) // get path to current executable uint32_t buffSize = 2048; std::vector buffer(buffSize); if (_NSGetExecutablePath(&(buffer[0]), &buffSize) == -1) { buffer.resize(buffSize); _NSGetExecutablePath(&(buffer[0]), &buffSize); } // set it executablePath = std::string(&(buffer[0])); #elif defined(HAVE_PROCSELF) executablePath = std::string("/proc/self/exe"); #else // Note that this technique will NOT work if the executable was located // via a search of the PATH. To make this fallback fully robust we would // need to also search the PATH for the exe name in argv[0] // // use argv[0] and initial path FilePath initialPath = FilePath::initialPath(); executablePath = initialPath.completePath(argv0).getAbsolutePath(); #endif // return realPath of executable path return realPath(executablePath, pExecutablePath); } // installation path Error installPath(const std::string& relativeToExecutable, const char * argv0, FilePath* pInstallPath) { // get executable path FilePath executablePath; Error error = system::executablePath(argv0, &executablePath); if (error) return error; // fully resolve installation path relative to executable FilePath installPath = executablePath.getParent().completePath(relativeToExecutable); return realPath(installPath.getAbsolutePath(), pInstallPath); } void fixupExecutablePath(FilePath* pExePath) { // do nothing on posix } void abort() { ::abort(); } Error terminateProcess(PidType pid) { return killProcess(pid, SIGTERM); } Error killProcess(PidType pid, int signal) { if (::kill(pid, signal)) return systemError(errno, ERROR_LOCATION); else return Success(); } std::vector getSubprocessesViaPgrep(PidType pid) { std::vector subprocs; // pgrep -P ppid -l returns 0 if there are matches, non-zero // otherwise; output is one line per direct child process, // for example: // // 23432 sleep // 23433 mycommand shell_utils::ShellCommand cmd("pgrep"); cmd d_name, -1), &info, populateUsername); if (error) { // only log the error if we were not told otherwise // in the vast majority of cases, these errors indicate // transient process issues (like processes exiting) or // not having access to privileged processes and are benign // and not worthy of logging if (!suppressErrors) LOG_ERROR(error); continue; } // check if this is the process we are filtering on if (process.empty() || info.exe == process) { if (!filter || filter(info)) { pInfo->push_back(info); } } } } CATCH_UNEXPECTED_EXCEPTION if (pDir != nullptr) ::closedir(pDir); return Success(); } Error processInfo(pid_t pid, ProcessInfo* pInfo, bool populateUsername) { std::string pidStr = safe_convert::numberToString(pid); // confirm the cmdline file exists for this pid boost::format fmt("/proc/%1%/cmdline"); FilePath cmdlineFile = FilePath(boost::str(fmt % pidStr)); if (!cmdlineFile.exists()) return systemError(boost::system::errc::no_such_file_or_directory, ERROR_LOCATION); // read the cmdline std::string cmdline; Error error = core::readStringFromFile(cmdlineFile, &cmdline); if (error) return error; boost::algorithm::trim(cmdline); // confirm we have a command line if (cmdline.empty()) return systemError(boost::system::errc::protocol_error, ERROR_LOCATION); std::vector commandVector; boost::algorithm::split(commandVector, cmdline, boost::is_any_of(boost::as_array("\0"))); if (commandVector.size() == 0) return systemError(boost::system::errc::protocol_error, ERROR_LOCATION); cmdline = commandVector.front(); // remove the first element from the command vector (the actual command) // and simply keep the arguments as the command is stored in its own variable commandVector.erase(commandVector.begin()); // confirm the stat file exists for this pid boost::format statFmt("/proc/%1%/stat"); FilePath statFile = FilePath(boost::str(statFmt % pidStr)); if (!statFile.exists()) return systemError(boost::system::errc::no_such_file_or_directory, ERROR_LOCATION); // stat the file to determine it's owner struct stat st; if (::stat(cmdlineFile.getAbsolutePath().c_str(), &st) == -1) { Error error = systemError(errno, ERROR_LOCATION); error.addProperty("path", cmdlineFile); return error; } core::system::User user; // get the username if (populateUsername) { error = getUserFromUserId(st.st_uid, user); if (error) return error; } // read the stat fields for other relevant process info std::string statStr; error = core::readStringFromFile(statFile, &statStr); if (error) return error; std::vector statFields; boost::algorithm::split(statFields, statStr, boost::is_any_of(" "), boost::algorithm::token_compress_on); if (statFields.size() < 5) { return systemError(boost::system::errc::protocol_error, "Expected at least 5 stat fields but read: " + safe_convert::numberToString(statFields.size()), ERROR_LOCATION); } // get the process state std::string state = statFields[2]; // get process parent id std::string ppidStr = statFields[3]; pid_t ppid = safe_convert::stringTo(ppidStr, -1); // get process group id std::string pgrpStr = statFields[4]; pid_t pgrp = safe_convert::stringTo(pgrpStr, -1); // set process info fields pInfo->pid = pid; pInfo->ppid = ppid; pInfo->pgrp = pgrp; if (populateUsername) pInfo->username = user.getUsername(); pInfo->uid_ = st.st_uid; pInfo->uidSet_ = true; pInfo->exe = FilePath(cmdline).getFilename(); pInfo->state = state; pInfo->arguments = commandVector; return Success(); } bool isProcessRunning(pid_t pid) { // the posix standard way of checking if a process // is running is to send the 0 signal to it // requires root privilege if process is owned by another user int result = kill(pid, 0); return result == 0; } namespace { Error readStatFields(const FilePath& statFilePath, std::size_t numRequiredFields, std::vector* pFields) { if (!statFilePath.exists()) return core::fileNotFoundError(statFilePath, ERROR_LOCATION); std::string str; Error error = core::readStringFromFile(statFilePath, &str); if (error) return error; boost::algorithm::split(*pFields, str, boost::is_any_of(" "), boost::algorithm::token_compress_on); if (pFields->size() < numRequiredFields) { Error error = systemError(boost::system::errc::protocol_error, ERROR_LOCATION); error.addProperty("stat-fields", str); return error; } return Success(); } } // anonymous namespace Error ProcessInfo::creationTime(boost::posix_time::ptime* pCreationTime) const { // get clock ticks (bail if we can't) double clockTicks = ::sysconf(_SC_CLK_TCK); if (clockTicks == -1) return systemError(errno, ERROR_LOCATION); // get boot time double bootTime = 0.0; std::vector lines; Error error = core::readStringVectorFromFile(FilePath("/proc/stat"), &lines); if (error) return error; for (const std::string& line : lines) { if (boost::algorithm::starts_with(line, "btime")) { std::vector fields; boost::algorithm::split(fields, line, boost::algorithm::is_any_of(" \t"), boost::algorithm::token_compress_on); if (fields.size() > 1) { bootTime = safe_convert::stringTo(fields[1], 0); break; } } } if (bootTime == 0.0) { return systemError(boost::system::errc::protocol_error, "Unable to find btime in /proc/stat", ERROR_LOCATION); } // read the stat fields boost::format fmt("/proc/%1%"); std::string dir = boost::str(fmt % pid); FilePath procDir(dir); std::vector fields; error = readStatFields(procDir.completeChildPath("stat"), 22, &fields); if (error) return error; // get the creation time and return success double startTicks = safe_convert::stringTo(fields[21], 0); double startSecs = (startTicks / clockTicks) + bootTime; *pCreationTime = date_time::timeFromSecondsSinceEpoch(startSecs); return Success(); } #else core::Error pidof(const std::string& process, std::vector* pPids) { // use ps to capture pids std::string cmd = "ps acx | awk \"{if (\\$5==\\\"" + process + "\\\") print \\$1}\""; core::system::ProcessResult result; Error error = core::system::runCommand(cmd, core::system::ProcessOptions(), &result); if (error) return error; // parse into pids std::vector lines; boost::algorithm::split(lines, result.stdOut, boost::algorithm::is_any_of("\n")); toPids(lines, pPids); return Success(); } Error processInfo(const std::string& process, std::vector* pInfo, bool suppressErrors, ProcessFilter filter, bool populateUsername) { // use ps to capture process info // output format // USER:PID:PPID:PGID:::STATE:::PROCNAME:ARG1:ARG2:...:ARGN // we use a colon as the separator as it is not a valid path character in OSX std::string cmd = process.empty() ? "ps axj | awk '{OFS=\":\"; $5=\"\"; $6=\"\"; $8=\"\"; $9=\"\"; print}'" : "ps axj | awk '{OFS=\":\"; if ($10==\"" + process + "\"){ $5=\"\"; $6=\"\"; $8=\"\"; $9=\"\"; print} }'"; core::system::ProcessResult result; Error error = core::system::runCommand(cmd, core::system::ProcessOptions(), &result); if (error) return error; // parse into ProcessInfo std::vector lines; boost::algorithm::split(lines, result.stdOut, boost::algorithm::is_any_of("\n")); for (const std::string& line : lines) { if (line.empty()) continue; std::vector lineInfo; boost::algorithm::split(lineInfo, line, boost::algorithm::is_any_of(":")); if (lineInfo.size() < 10) { LOG_WARNING_MESSAGE("Expected 10 items from ps output but received: " + safe_convert::numberToString(lineInfo.size())); continue; } ProcessInfo procInfo; procInfo.username = lineInfo[0]; //procInfo.uid_ = only used to get the username and not available here procInfo.uidSet_ = false; procInfo.pid = safe_convert::stringTo(lineInfo[1], 0); procInfo.ppid = safe_convert::stringTo(lineInfo[2], 0); procInfo.pgrp = safe_convert::stringTo(lineInfo[3], 0); procInfo.state = lineInfo[6]; // parse process name and arguments procInfo.exe = lineInfo[9]; if (lineInfo.size() > 10) procInfo.arguments = std::vector(lineInfo.begin() + 10, lineInfo.end()); // check to see if this process info passes the filter criteria if (!filter || filter(procInfo)) { pInfo->push_back(procInfo); } } return Success(); } Error ProcessInfo::creationTime(boost::posix_time::ptime* pCreationTime) const { return systemError(boost::system::errc::not_supported, ERROR_LOCATION); } #endif std::string ProcessInfo::getUsername() const { if (username.empty()) { if (uidSet_) { // get the username core::system::User user; Error error = getUserFromUserId(uid_, user); if (error) { LOG_DEBUG_MESSAGE("Error resolving process owner: " + std::to_string(uid_) + " error: " + error.asString()); return "__user_" + std::to_string(uid_); } else return user.getUsername(); } else LOG_ERROR_MESSAGE("No uid or username for ProcessInfo"); } return username; } std::ostream& operator

Back | FazBrowse Home | New Git URL