[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/pearsonca/rstudio/master/src/cpp/core/system/PosixSystem.cpp [Back]  [Original]

/*
 * PosixSystem.cpp
 *
 * Copyright (C) 2009-19 by RStudio, PBC
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, 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 

#ifdef __APPLE__
#include 
#include 
#include 
#include 
#endif

#ifndef __APPLE__
#include 
#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 "config.h"

namespace rstudio {
namespace core {
namespace system {

namespace {

Error ignoreSig(int signal)
{
   struct sigaction sa;
   ::memset(&sa, 0, sizeof sa);
   sa.sa_handler = SIG_IGN;
   int result = ::sigaction(signal, &sa, nullptr);
   if (result != 0)
   {
      Error error = systemError(result, ERROR_LOCATION);
      error.addProperty("signal", signal);
      return error;
   }
   else
   {
      return Success();
   }
}
   
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()
{
}

// 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, options));
      s_programIdentity = programIdentity;

      Error error = initLog();
      if (error)
         return error;
   }
   END_LOCK_MUTEX

   if (enableConfigReload)
      initializeLogConfigReload();

   return Success();
}

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");
         }
      }
   }
}

} // 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));
}
   
Error ignoreTerminalSignals()
{
   ExecBlock ignoreBlock;
   ignoreBlock.addFunctions()
      (boost::bind(ignoreSig, SIGHUP))
      (boost::bind(ignoreSig, SIGTSTP))
      (boost::bind(ignoreSig, SIGTTOU))
      (boost::bind(ignoreSig, 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 ignoreSig(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 perserved 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 = posixCall(boost::bind(::write,
                                                    pipeFd,
                                                    &fd,
                                                    4),
                                        ERROR_LOCATION,
                                        &written);

         if (error)
         {
            return error;
         }
      }
   }
   else
      LOG_ERROR(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 = 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);
         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)
{
   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;
   }

   // get the username
   core::system::User user;
   error = User::getUserFromIdentifier(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;
   pInfo->username = user.getUsername();
   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)
{
   // 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("Exepcted 10 items from ps output but received: " + safe_convert::numberToString(lineInfo.size()));
         continue;
      }

      ProcessInfo procInfo;
      procInfo.username = lineInfo[0];
      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();
}
#endif

std::ostream& operatorifa_addr,
                        (family == AF_INET) ? sizeof(struct sockaddr_in) :
                                              sizeof(struct sockaddr_in6),
                        host, NI_MAXHOST,
                        nullptr, 0, NI_NUMERICHOST) != 0)
      {
         LOG_ERROR(systemError(errno, ERROR_LOCATION));
         continue;
      }

      struct IpAddress addr;
      addr.name = pAddr->ifa_name;
      addr.addr = host;
      pAddresses->push_back(addr);
   }

   // free them and return success
   ::freeifaddrs(pAddrs);
   return Success();
}


Error restrictCoreDumps()
{
   // set allowed size of core dumps to 0 bytes
   Error error = setResourceLimit(core::system::CoreLimit, 0);
   if (error)
      return error;

   // no ptrace core dumps permitted
#ifndef __APPLE__
   int res = ::prctl(PR_SET_DUMPABLE, 0);
   if (res == -1)
      return systemError(errno, ERROR_LOCATION);
#endif

   return Success();
}

Error enableCoreDumps()
{
#ifndef __APPLE__
   int res = ::prctl(PR_SET_DUMPABLE, 1);
   if (res == -1)
      return systemError(errno, ERROR_LOCATION);
#endif

   return Success();
}

void printCoreDumpable(const std::string& context)
{
   std::ostringstream ostr;

   ostr pw_name, pPrivPasswd->pw_gid) < 0)
      return systemError(errno, ERROR_LOCATION);

   // set effective group
   if (::setegid(pPrivPasswd->pw_gid) < 0)
      return systemError(errno, ERROR_LOCATION);
   // verify
   if (::getegid() != pPrivPasswd->pw_gid)
      return systemError(EACCES, ERROR_LOCATION);

   // success
   return Success();
}

} // anonymous namespace

// privilege manipulation for systems that support setresuid/getresuid
#if defined(HAVE_SETRESUID)

Error temporarilyDropPriv(const std::string& newUsername)
{
   // clear error state
   errno = 0;

   // get user info
   User user;
   Error error = User::getUserFromIdentifier(newUsername, user);
   if (error)
      return error;

   // init supplemental group list
   // NOTE: if porting to CYGWIN may need to call getgroups/setgroups
   // after initgroups -- more research required to confirm
   if (::initgroups(user.getUsername().c_str(), user.getGroupId()) < 0)
      return systemError(errno, ERROR_LOCATION);

   // set group and verify
   if (::setresgid(-1, user.getGroupId(), ::getegid()) < 0)
      return systemError(errno, ERROR_LOCATION);
   if (::getegid() != user.getGroupId())
      return systemError(EACCES, ERROR_LOCATION);

   // set user and verify
   if (::setresuid(-1, user.getUserId(), ::geteuid()) < 0)
      return systemError(errno, ERROR_LOCATION);
   if (::geteuid() != user.getUserId())
      return systemError(EACCES, ERROR_LOCATION);

   // success
   return Success();
}

Error permanentlyDropPriv(const std::string& newUsername)
{
   // clear error state
   errno = 0;

   // get user info
   User user;
   Error error = User::getUserFromIdentifier(newUsername, user);
   if (error)
      return error;

   // supplemental group list
   if (::initgroups(user.getUsername().c_str(), user.getGroupId()) < 0)
      return systemError(errno, ERROR_LOCATION);

   // set group
   if (::setresgid(user.getGroupId(), user.getGroupId(), user.getGroupId()) < 0)
      return systemError(errno, ERROR_LOCATION);
   // verify
   gid_t rgid, egid, sgid;
   if (::getresgid(&rgid, &egid, &sgid) < 0)
      return systemError(errno, ERROR_LOCATION);
   if (rgid != user.getGroupId() || egid != user.getGroupId() || sgid != user.getGroupId())
      return systemError(EACCES, ERROR_LOCATION);

   // set user
   if (::setresuid(user.getUserId(), user.getUserId(), user.getUserId()) < 0)
      return systemError(errno, ERROR_LOCATION);
   // verify
   uid_t ruid, euid, suid;
   if (::getresuid(&ruid, &euid, &suid) < 0)
      return systemError(errno, ERROR_LOCATION);
   if (ruid != user.getUserId() || euid != user.getUserId() || suid != user.getUserId())
      return systemError(EACCES, ERROR_LOCATION);

   // success
   return Success();
}

Error restorePriv()
{
   // reset error state
   errno = 0;

   // set user
   uid_t ruid, euid, suid;
   if (::getresuid(&ruid, &euid, &suid) < 0)
      return systemError(errno, ERROR_LOCATION);
   if (::setresuid(-1, suid, -1) < 0)
      return systemError(errno, ERROR_LOCATION);
   // verify
   if (::geteuid() != suid)
      return systemError(EACCES, ERROR_LOCATION);

   // get saved user info to use in group calls
   struct passwd* pPrivPasswd = ::getpwuid(suid);
   if (pPrivPasswd == nullptr)
      return systemError(errno, ERROR_LOCATION);

   // supplemental groups
   if (::initgroups(pPrivPasswd->pw_name, pPrivPasswd->pw_gid) < 0)
      return systemError(errno, ERROR_LOCATION);

   // set group
   gid_t rgid, egid, sgid;
   if (::getresgid(&rgid, &egid, &sgid) < 0)
      return systemError(errno, ERROR_LOCATION);
   if (::setresgid(-1, sgid, -1) < 0)
      return systemError(errno, ERROR_LOCATION);
   // verify
   if (::getegid() != sgid)
      return systemError(EACCES, ERROR_LOCATION);

   // success
   return Success();
}

// privilege manipulation for systems that don't support setresuid/getresuid
#else

namespace {
   uid_t s_privUid;
}

Error temporarilyDropPriv(const std::string& newUsername)
{
   // clear error state
   errno = 0;

   // get user info
   User user;
   Error error = User::getUserFromIdentifier(newUsername, user);
   if (error)
      return error;

   // init supplemental group list
   if (::initgroups(user.getUsername().c_str(), user.getGroupId()) < 0)
      return systemError(errno, ERROR_LOCATION);

   // set group

   // save old EGUID
   gid_t oldEGUID = ::getegid();

   // copy EGUID to SGID
   if (::setregid(::getgid(), oldEGUID) < 0)
      return systemError(errno, ERROR_LOCATION);

   // set new EGID
   if (::setegid(user.getGroupId()) < 0)
      return systemError(errno, ERROR_LOCATION);

   // verify
   if (::getegid() != user.getGroupId())
      return systemError(EACCES, ERROR_LOCATION);


   // set user

   // save old EUID
   uid_t oldEUID = ::geteuid();

   // copy EUID to SUID
   if (::setreuid(::getuid(), oldEUID) < 0)
      return systemError(errno, ERROR_LOCATION);

   // set new EUID
   if (::seteuid(user.getUserId()) < 0)
      return systemError(errno, ERROR_LOCATION);

   // verify
   if (::geteuid() != user.getUserId())
      return systemError(EACCES, ERROR_LOCATION);

   // save privilleged user id
   s_privUid = oldEUID;

   // success
   return Success();
}

Error permanentlyDropPriv(const std::string& newUsername)
{
   // clear error state
   errno = 0;

   // get user info
   User user;
   Error error = User::getUserFromIdentifier(newUsername, user);
   if (error)
      return error;

   // supplemental group list
   if (::initgroups(user.getUsername().c_str(), user.getGroupId()) < 0)
      return systemError(errno, ERROR_LOCATION);

   // set group
   if (::setregid(user.getGroupId(), user.getGroupId()) < 0)
      return systemError(errno, ERROR_LOCATION);
   // verify
   if (::getgid() != user.getGroupId() || ::getegid() != user.getGroupId())
      return systemError(EACCES, ERROR_LOCATION);

   // set user
   if (::setreuid(user.getUserId(), user.getUserId()) < 0)
      return systemError(errno, ERROR_LOCATION);
   // verify
   if (::getuid() != user.getUserId() || ::geteuid() != user.getUserId())
      return systemError(EACCES, ERROR_LOCATION);

   // success
   return Success();
}

Error restorePriv()
{
   return restorePrivImpl(s_privUid);
}

#endif

Error restoreRoot()
{
   return restorePrivImpl(0);
}

} // namespace system
} // namespace core
} // namespace rstudio


Web Proxy Viewer  |  New URL  |  Original Page