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