Logo Search packages:      
Sourcecode: launchtool version File versions  Download package

ChildProcess.cc

#include "ChildProcess.h"

#include <Logger.h>

#include <sys/types.h>        // fork, waitpid, kill, open, getpw*, getgr*, initgroups
#include <sys/stat.h>         // open
#include <sys/resource.h>     // getrlimit, setrlimit
#include <unistd.h>                 // fork, dup2, pipe, close, setsid, _exit, chdir
#include <fcntl.h>                  // open
#include <sys/wait.h>         // waitpid
#include <signal.h>                 // kill
#include <stdio.h>                  // flockfile, funlockfile
#include <ctype.h>                  // is*
#include <pwd.h>              // getpw*
#include <grp.h>              // getgr*, initgroups
#include <errno.h>

#undef LOGTAG
#define LOGTAG "ChildProcess"

using namespace std;
using namespace stringf;

void Process::detachFromTTY() throw (SystemException)
{
      int devnull = open("/dev/null", O_RDWR);
      if (devnull == -1) throw FileException(errno, "opening /dev/null for read and write access");
      if (dup2(devnull, 0) == -1) throw SystemException(errno, "redirecting stdin to /dev/null");
      if (dup2(devnull, 1) == -1) throw SystemException(errno, "redirecting stdout to /dev/null");
      if (setsid() == -1) throw SystemException(errno, "trying to become session leader");
      if (dup2(devnull, 2) == -1) throw SystemException(errno, "redirecting stderr to /dev/null");
      close(devnull);
}

string Process::formatStatus(int status) throw ()
{
      string b_status;

      bool exited_normally = WIFEXITED(status);
      int exit_code = exited_normally ? WEXITSTATUS(status) : -1;
      bool dumped_core = status & 128;
      bool signaled = WIFSIGNALED(status);
      int signal = signaled ? WTERMSIG(status) : 0;

      if (exited_normally)
            if (exit_code == 0)
                  b_status += "terminated successfully";
            else
                  addf(b_status, "exited with code %d", exit_code);
      else
      {
            b_status += "was interrupted";
            addf(b_status, ", killed by signal %d", signal);
            if (dumped_core) b_status += " (core dumped)";
      }

      return b_status;
}

void Process::chdir(const string& dir) throw (SystemException)
{
      if (::chdir(dir.c_str()) == -1)
            throw SystemException(errno, "changing working directory to " + dir);
}

void Process::chroot(const string& dir) throw (SystemException)
{
      if (::chroot(dir.c_str()) == -1)
            throw SystemException(errno, "changing root directory to " + dir);
}

mode_t Process::umask(mode_t mask) throw ()
{
      return ::umask(mask);
}

static struct passwd* getUserInfo(const string& user)
{
      if (isdigit(user[0]))
            return getpwuid(atoi(user.c_str()));
      else
            return getpwnam(user.c_str());
}

static struct group* getGroupInfo(const string& group)
{
      if (isdigit(group[0]))
            return getgrgid(atoi(group.c_str()));
      else
            return getgrnam(group.c_str());
}

static void initGroups(const string& name, gid_t gid) throw (SystemException)
{
      if (::initgroups(name.c_str(), gid) == -1)
            throw SystemException(errno, "initializing group access list for user "
                        + name + " with additional group " + fmt(gid));
}

static void set_perms(const string& user, uid_t uid, const string& group, gid_t gid)
      throw (SystemException)
{
      initGroups(user, gid);

      if (setgid(gid) == -1)
            throw SystemException(errno, "setting group id to " + fmt(gid) + " (" + group + ")");

      if (setegid(gid) == -1)
            throw SystemException(errno, "setting effective group id to " + fmt(gid) + " (" + group + ")");

      if (setuid(uid) == -1)
            throw SystemException(errno, "setting user id to " + fmt(uid) + " (" + user + ")");

      if (seteuid(uid) == -1)
            throw SystemException(errno, "setting effective user id to " + fmt(uid) + " (" + user + ")");
}

void Process::setPerms(const string& user)
      throw (ConsistencyCheckException, SystemException)
{
      struct passwd* pw = getUserInfo(user);
      if (!pw)
            throw ConsistencyCheckException("User " + user + " does not exist on this system");
      struct group* gr = getgrgid(pw->pw_gid);
      if (!gr)
            throw ConsistencyCheckException("Group " + fmt(pw->pw_gid) +
                        " (primary group of user " + user + ") does not exist on this system");

      ::set_perms(user, pw->pw_uid, gr->gr_name, gr->gr_gid);
}

void Process::setPerms(const string& user, const string& group)
        throw (ConsistencyCheckException, SystemException)
{
      struct passwd* pw = getUserInfo(user);
      if (!pw)
            throw ConsistencyCheckException("User " + user + " does not exist on this system");
      struct group* gr = getGroupInfo(group);
      if (!gr)
            throw ConsistencyCheckException("Group " + group + " does not exist on this system");

      ::set_perms(user, pw->pw_uid, group, gr->gr_gid);
}

void Process::setPerms(uid_t user)
        throw (ConsistencyCheckException, SystemException)
{
      struct passwd* pw = getpwuid(user);
      if (!pw)
            throw ConsistencyCheckException("User " + fmt(user) + " does not exist on this system");
      struct group* gr = getgrgid(pw->pw_gid);
      if (!gr)
            throw ConsistencyCheckException("Group " + fmt(pw->pw_gid) +
                        " (primary group of user " + fmt(user) + ") does not exist on this system");

      ::set_perms(pw->pw_name, pw->pw_uid, gr->gr_name, gr->gr_gid);
}

void Process::setPerms(uid_t user, gid_t group)
        throw (ConsistencyCheckException, SystemException)
{
      struct passwd* pw = getpwuid(user);
      if (!pw)
            throw ConsistencyCheckException("User " + fmt(user) + " does not exist on this system");
      struct group* gr = getgrgid(group);
      if (!gr)
            throw ConsistencyCheckException("Group " + fmt(group) + " does not exist on this system");

      ::set_perms(pw->pw_name, pw->pw_uid, gr->gr_name, gr->gr_gid);
}


static string describe_rlimit_res_t(__rlimit_resource_t rlim)
{
      switch (rlim)
      {
            case RLIMIT_CPU: return "CPU time in seconds";
            case RLIMIT_FSIZE: return "Maximum filesize";
            case RLIMIT_DATA: return "max data size";
            case RLIMIT_STACK: return "max stack size";
            case RLIMIT_CORE: return "max core file size";
            case RLIMIT_RSS: return "max resident set size";
            case RLIMIT_NPROC: return "max number of processes";
            case RLIMIT_NOFILE: return "max number of open files";
            case RLIMIT_MEMLOCK: return "max locked-in-memory address spac";
            case RLIMIT_AS: return "address space (virtual memory) limit";
            default: return "unknown";
      }
}

static void setLimit(__rlimit_resource_t rlim, int val) throw (SystemException)
{
      struct rlimit lim;
      if (getrlimit(rlim, &lim) == -1)
            throw SystemException(errno, "Getting " + describe_rlimit_res_t(rlim) + " limit");
      lim.rlim_cur = val;
      if (setrlimit(rlim, &lim) == -1)
            throw SystemException(errno, "Setting " + describe_rlimit_res_t(rlim) +
                        " limit to " + fmt(val));
}

static int getLimit(__rlimit_resource_t rlim, int* max = 0) throw (SystemException)
{
      struct rlimit lim;
      if (getrlimit(rlim, &lim) == -1)
            throw SystemException(errno, "Getting " + describe_rlimit_res_t(rlim) + " limit");
      if (max)
            *max = lim.rlim_max;
      return lim.rlim_cur;
}

int Process::getCPUTimeLimit(int* max) throw (SystemException) { return getLimit(RLIMIT_CPU, max); }
int Process::getFileSizeLimit(int* max) throw (SystemException) { return getLimit(RLIMIT_FSIZE, max); }
int Process::getDataMemoryLimit(int* max) throw (SystemException) { return getLimit(RLIMIT_DATA, max); }
int Process::getCoreSizeLimit(int* max) throw (SystemException) { return getLimit(RLIMIT_CORE, max); }
int Process::getChildrenLimit(int* max) throw (SystemException) { return getLimit(RLIMIT_NPROC, max); }
int Process::getOpenFilesLimit(int* max) throw (SystemException) { return getLimit(RLIMIT_NOFILE, max); }

void Process::setCPUTimeLimit(int value) throw (SystemException) { setLimit(RLIMIT_CPU, value); }
void Process::setFileSizeLimit(int value) throw (SystemException) { setLimit(RLIMIT_FSIZE, value); }
void Process::setDataMemoryLimit(int value) throw (SystemException) { setLimit(RLIMIT_DATA, value); }
void Process::setCoreSizeLimit(int value) throw (SystemException) { setLimit(RLIMIT_CORE, value); }
void Process::setChildrenLimit(int value) throw (SystemException) { setLimit(RLIMIT_NPROC, value); }
void Process::setOpenFilesLimit(int value) throw (SystemException) { setLimit(RLIMIT_NOFILE, value); }


pid_t ChildProcess::fork(int* stdinfd, int* stdoutfd, int* stderrfd) throw (SystemException)
{
      int pipes[3][2];

      if (stdinfd)
      {
            if (pipe(pipes[0]) == -1)
                  throw SystemException(errno, "trying to create the pipe to connect to child standard input");
            *stdinfd = pipes[0][1];
      }
      if (stdoutfd)
      {
            if (pipe(pipes[1]) == -1)
                  throw SystemException(errno, "trying to create the pipe to connect to child standard output");
            *stdoutfd = pipes[1][0];
            if (stderrfd == stdoutfd)
                  *stderrfd = pipes[1][0];
      }
      
      if (stderrfd && stderrfd != stdoutfd)
      {
            if (pipe(pipes[2]) == -1)
                  throw SystemException(errno, "trying to create the pipe to connect to child standard error");
            *stderrfd = pipes[2][0];
      }

      flockfile(stdin);
      flockfile(stdout);
      flockfile(stderr);

      pid_t pid;
      if ((pid = ::fork()) == 0)
      {
            // Tell the logging system we're in a new process
            Log::Logger::instance()->setupForkedChild();

            // Child process
            try {
                  if (stdinfd)
                  {
                        // Redirect input from the parent to stdin
                        if (close(pipes[0][1]) == -1)
                              throw SystemException(errno, "closing write end of parent stdin pipe");
                        if (dup2(pipes[0][0], 0) == -1)
                              throw SystemException(errno, "dup2-ing parent stdin pipe to stdin");
                        if (close(pipes[0][0]) == -1)
                              throw SystemException(errno, "closing original read end of parent stdin pipe");
                  }

                  if (stdoutfd)
                  {
                        // Redirect output to the parent stdout fd
                        if (close(pipes[1][0]) == -1)
                              throw SystemException(errno, "closing read end of parent stdout pipe");
                        if (dup2(pipes[1][1], 1) == -1)
                              throw SystemException(errno, "dup2-ing stdout to parent stdout pipe");
                        if (stderrfd == stdoutfd)
                              if (dup2(pipes[1][1], 2) == -1)
                                    throw SystemException(errno, "dup2-ing stderr to parent stdout/stderr pipe");
                        if (close(pipes[1][1]) == -1)
                              throw SystemException(errno, "closing original write end of parent stdout pipe");
                  }

                  if (stderrfd && stderrfd != stdoutfd)
                  {
                        // Redirect all output to the parent
                        if (close(pipes[2][0]) == -1)
                              throw SystemException(errno, "closing read end of parent stderr pipe");
                        if (dup2(pipes[2][1], 2) == -1)
                              throw SystemException(errno, "dup2-ing stderr to parent stderr pipe");
                        if (close(pipes[2][1]) == -1)
                              throw SystemException(errno, "closing original write end of parent stderr pipe");
                  }

                  // Call the process main function
                  int res = _proc->main();
                  // Allow the child to do its cleanup
                  delete _proc;
                  // Return the exit status
                  _exit(res);
            } catch (Exception& e) {
                  log_err(string(e.type()) + ": " + e.desc());
            }
            _exit(EXIT_FAILURE);
      } else if (pid < 0) {
            funlockfile(stdin);
            funlockfile(stderr);
            funlockfile(stdout);
            if (stdinfd)
            {
                  close(pipes[0][0]);
                  close(pipes[0][1]);
            }
            if (stdoutfd)
            {
                  close(pipes[1][0]);
                  close(pipes[1][1]);
            }
            if (stderrfd && stderrfd != stdoutfd)
            {
                  close(pipes[2][0]);
                  close(pipes[2][1]);
            }
            throw SystemException(errno, "trying to fork the child process to run action script");
      } else {
            funlockfile(stdin);
            funlockfile(stderr);
            funlockfile(stdout);

            // Parent process
            _pid = pid;
            try {
                  if (stdinfd)
                        if (close(pipes[0][0]) == -1)
                              throw SystemException(errno, "closing read end of stdin child pipe");
                  if (stdoutfd)
                        if (close(pipes[1][1]) == -1)
                              throw SystemException(errno, "closing write end of stdout child pipe");
                  if (stderrfd && stderrfd != stdoutfd)
                        if (close(pipes[2][1]) == -1)
                              throw SystemException(errno, "closing write end of stderr child pipe");
                  return pid;
            } catch (SystemException& e) {
                  // Try to kill the child process if any errors occurs here
                  ::kill(pid, 15);
                  throw e;
            }
      }
}

int ChildProcess::wait() throw (SystemException, InterruptedException)
{
      if (_pid == -1)
      {
            log_debug("Child already finished");
            return -1;        // FIXME: for lack of better ideas
      }

      int status;
      if (waitpid(_pid, &status, 0) == -1)
            if (errno == EINTR)
                  throw InterruptedException("waiting for child termination");
            else
                  throw SystemException(errno, "waiting for child termination");
      _pid = -1;
      return status;
}

int ChildProcess::wait(struct rusage* ru) throw (SystemException, InterruptedException)
{
      if (_pid == -1)
      {
            log_debug("Child already finished");
            return -1;        // FIXME: for lack of better ideas
      }

      int status;
      if (wait4(_pid, &status, 0, ru) == -1)
            if (errno == EINTR)
                  throw InterruptedException("waiting for child termination");
            else
                  throw SystemException(errno, "waiting for child termination");
      _pid = -1;
      return status;
}


#if 0
#define _GNU_SOURCE

#include "runner.h"

#include <sys/types.h>        // fork, waitpid, kill, getpid, open
#include <sys/stat.h>         // open
#include <unistd.h>                 // fork, dup2, execve, pipe, close, read, getpid
#include <fcntl.h>                  // open
#include <sys/wait.h>         // waitpid
#include <signal.h>                 // kill
#include <stdlib.h>                 // malloc, free, realloc
#include <string.h>                 // strndup
#include <stdio.h>                  // asprintf
#include <ctype.h>                  // is*
#include <errno.h>

#include <Logger.h>
#include "userdb.h"

#undef LOGTAG
#define LOGTAG "runner"

using namespace stringf;

extern char **environ;

///// RunnerException

RunnerException::RunnerException(const string& context) throw ()
      : ContextException(context)
{
      pid = getpid();
}

string RunnerException::desc() const throw ()
{
      return _context + " in process " + fmt(pid);
}


///// ChildProcess

static void set_perms(const char* user, uid_t uid, const char* group, gid_t gid)
{
      if (geteuid() == 0)
      {
            userdb.initgroups(user, gid);

            if (setgid(gid) == -1)
                  throw SystemException(errno, fmt("setting group id to %d (%s)", gid, group));

            if (setegid(gid) == -1)
                  throw SystemException(errno, fmt("setting effective group id to %d (%s)", gid, group));

            if (setuid(uid) == -1)
                  throw SystemException(errno, fmt("setting user id to %d (%s)", uid, user));

            if (seteuid(uid) == -1)
                  throw SystemException(errno, fmt("setting effective user id to %d (%s)", uid, user));
      } else
            throw RunnerException("missing the necessary privileges to change permissions");
}
      
void ChildProcess::set_perms(const char* usr)
{
      string user;
      string group;
      uid_t uid = (uid_t)-1;
      gid_t gid = (gid_t)-1;

      try {
            if (isdigit(usr[0]))
            {
                  uid = atoi(usr);
                  user = userdb.user_name(uid);
            } else {
                  uid = userdb.user_id(usr);
                  user = usr;
            }

            gid = userdb.user_group(uid);
            string group = userdb.group_name(gid);

      } catch (UIDNotFoundException& e) {
            throw RunnerException(fmt("user `%s' not found on system", usr));
      } catch (UserNotFoundException& e) {
            throw RunnerException(fmt("user `%s' not found on system", usr));
      } catch (GIDNotFoundException& e) {
            throw RunnerException(fmt("group `%d' (default group for user"
                                                         " %d (%s)) not found on system",
                                                         gid, uid, usr));
      }
      ::set_perms(user.c_str(), uid, group.c_str(), gid);
}

void ChildProcess::set_perms(const char* usr, const char* grp)
{
      string user;
      string group;
      uid_t uid;
      gid_t gid;

      try {
            if (isdigit(usr[0]))
            {
                  uid = atoi(usr);
                  user = userdb.user_name(uid);
            } else {
                  uid = userdb.user_id(usr);
                  user = usr;
            }

            if (isdigit(grp[0]))
            {
                  gid = atoi(grp);
                  group = userdb.group_name(gid);
            } else {
                  gid = userdb.group_id(grp);
                  group = grp;
            }
      } catch (UIDNotFoundException& e) {
            throw RunnerException(fmt("user `%s' not found on system", usr));
      } catch (UserNotFoundException& e) {
            throw RunnerException(fmt("user `%s' not found on system", usr));
      } catch (GIDNotFoundException& e) {
            throw RunnerException(fmt("group `%s' not found on system", grp));
      } catch (GroupNotFoundException& e) {
            throw RunnerException(fmt("group `%s' not found on system", grp));
      }

      ::set_perms(user.c_str(), uid, group.c_str(), gid);
}


pid_t ChildProcess::run()
{
      int pstdin[2];
      int poutput[2];

      if (_opt_want_child_stdin)
            if (pipe(pstdin) == -1)
                  throw SystemException(errno, "trying to create the pipe to connect to child standard input");

      if (_opt_want_child_output)
            if (pipe(poutput) == -1)
                  throw SystemException(errno, "trying to create the pipe to read the script output");
      flockfile(stdout);
      flockfile(stderr);

      fflush(stdout);
      fflush(stderr);

      UserDBLock udblock(userdb);

      if ((pid = fork()) == 0)
      {
            // Child process
            try {
                  udblock.reset();
                  Logger::configure(*this);

                  set_permissions();

                  if (_opt_want_child_stdin)
                  {
                        // Redirect input from the parent to stdin
                        if (close(pstdin[1]) == -1)
                              throw SystemException(errno, "closing write end of parent pipe");
                        if (dup2(pstdin[0], 0) == -1)
                              throw SystemException(errno, "dup2-ing parent pipe to stdin");
                  }

                  if (_opt_want_child_output)
                  {
                        // Redirect all output to the parent
                        if (close(poutput[0]) == -1)
                              throw SystemException(errno, "closing read end of parent pipe");
                        if (dup2(poutput[1], 1) == -1)
                              throw SystemException(errno, "dup2-ing stdout to parent pipe");
                        if (dup2(poutput[1], 2) == -1)
                              throw SystemException(errno, "dup2-ing stderr to parent pipe");
                  }

                  _exit(main());
            } catch (Exception& e) {
                  log_err(string(e.type()) + ": " + e.desc());
            }
            _exit(EXIT_FAILURE);
      } else if (pid < 0) {
            funlockfile(stderr);
            funlockfile(stdout);
            throw SystemException(errno, "trying to fork the child process to run action script");
      } else {
            funlockfile(stderr);
            funlockfile(stdout);

            // Parent process
            try {
                  if (_opt_want_child_stdin)
                  {
                        if (close(pstdin[0]) == -1)
                              throw SystemException(errno, "closing read end of stdin child pipe");
                        child_stdin = pstdin[1];
                  }

                  if (_opt_want_child_output)
                  {
                        if (close(poutput[1]) == -1)
                              throw SystemException(errno, "closing write end of output child pipe");
                        child_output = poutput[0];
                  }

                  return pid;
            } catch (Exception& e) {
                  // Try to kill the child process if any errors occurr here
                  ::kill(pid, 15);
                  throw e;
            }
      }
}

// Read the child output
vector<string> ChildProcess::read_output(int max_output_size = 0)
{
      if (pid == 0)
            throw RunnerException("read_output was called but the child was not started");

      if (!_opt_want_child_output)
            return vector<string>();

      // Get the child output
      // Don't read more than about 64Kb of output, to avoid
      // filling up memory with scripts that write too much
      vector<string> output;
      FILE* in = fdopen(child_output, "r");
      if (!in)
            throw SystemException(errno, "calling fdopen on child output file descriptor");
      
      string line;
      int byte_count = 0;
      int c;
      while ((c = getc(in)) != EOF)
            if (max_output_size == 0 || byte_count < max_output_size)
            {
                  byte_count++;
                  if (c != '\n')
                        line += c;
                  else
                  {
                        output.push_back(line);
                        line = "";
                  }
            } else if (max_output_size != 0 && byte_count == max_output_size) {
                  output.push_back("Output too long: truncated here.");
                  byte_count++;
            }
      if (fclose(in) == EOF)
            throw SystemException(errno, "closing read end of output child pipe");

      return output;
}

// Wait for child termination
int ChildProcess::wait()
{
      if (pid == 0)
            throw RunnerException("wait was called but the child was not started");

      int status;
      if (waitpid(pid, &status, 0) == -1)
            throw SystemException(errno, "waiting for child termination");
      pid = 0;
      return status;
}

void ChildProcess::kill(int sig)
{
      if (pid == 0)
            throw RunnerException("kill was called but the child was not started");

      if (::kill(pid, sig) == -1)
            throw SystemException(errno, fmt("killing process %d with signal %d", pid, sig));
}


///// ExecChildProcess::strlist

void ExecChildProcess::strlist::expand()
{
      size *= 2;
      ptr = (char**) realloc(ptr, size * sizeof(char*));
}

ExecChildProcess::strlist::strlist(int start_size) : cur(0), size(start_size)
{
      ptr = (char**) malloc(size * sizeof(char*));
}

ExecChildProcess::strlist::~strlist()
{
      for (int i = 0; i < cur; i++)
            if (ptr[i])
                  free(ptr[i]);
      free(ptr);
}

void ExecChildProcess::strlist::addf(const char* fmt, ...) ATTR_PRINTF(1, 2)
{
      if (cur >= size)
            expand();
      va_list ap;
      va_start(ap, fmt);
      vasprintf(&ptr[cur++], fmt, ap);
      va_end(ap);
}
void ExecChildProcess::strlist::add(const char* str)
{
      if (cur >= size)
            expand();
      ptr[cur++] = strdup(str);
}
void ExecChildProcess::strlist::add(const string& str)
{
      if (cur >= size)
            expand();
      ptr[cur++] = strndup(str.data(), str.size());
}


///// ExecChildProcess

void ExecChildProcess::copy_environ(strlist& env)
{
      for (int i = 0; environ[i]; i++)
            env.add(environ[i]);
}

int ExecChildProcess::main()
{
      strlist argv;
      build_argv(argv);

      strlist env;
      build_env(env);

      // Execute the command
      if (execve(argv0(), argv.get(), env.get()) == -1)
            throw SystemException(errno, string("Executing ") + argv0());

      return EXIT_FAILURE;
}
#endif
// vim:set ts=4 sw=4:

Generated by  Doxygen 1.6.0   Back to index