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

launchtool.cc

#define _GNU_SOURCE           // for isblank
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <Logger.h>
#include <Config.h>
#include <ChildProcess.h>
#include <Exec.h>

#include "LaunchtoolCfg.h"
#include "LaunchtoolOutput.h"
#include "MultiReader.h"

#include <Exception.h>

#include <Pidfile.h>

#include <popt.h>

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>           // free
#include <time.h>       // time
#include <sys/time.h>   // gettimeofday
#include <sys/types.h>  // kill, getpid
#include <sys/resource.h>     // struct rusage
#include <unistd.h>           // getpid
#include <signal.h>           // kill
#include <ctype.h>
#include <errno.h>            // errno
#include <execinfo.h>
#include <sys/wait.h>   // Exit status check functions
#include <ctype.h>

#include <string>
#include <vector>

#undef LOGTAG
#define LOGTAG "launchtool"

#define APPNAME "launchtool"

#define CONFIG_DIR "/etc/launchtool"

using namespace std;
using namespace stringf;

extern char **environ;


// Singleton running status
class Status
{
public:
      // PID of the child process
      pid_t pid;

      // true if launchtool has been interrupted (e.g. by a signal)
      bool interrupted;

      // Signal that interrupted the launchtool (if interrupted==true)
      int int_signal;

      // The index for scanning the wait_times array
      int next_delay;

protected:
      static Status* instance;

public:     
      Status() throw () : pid(1), interrupted(false), int_signal(0), next_delay(0) {}

      static Status& get()
      {
            if (!instance)
                  instance = new Status();

            return *instance;
      }
};
Status* Status::instance = 0;

// Structure holding informations about the execution of a process
struct cmd_status
{
      // Process pid
      pid_t pid;
      // Return value
      int status;
      // Command used to start the process
      string cmd;
      // Running time of the process
      struct timeval running_time;
      // Informations on resource usage
      struct rusage ru;
      // Process stdout + stderr
      string output;

      // Return a string with a printable representation of the contents of the
      // structure
      string beautified_status() const throw () { return Process::formatStatus(status); }
};

// Run the command `cmd', storing informations on the execution in `st'
int run_command(struct cmd_status* st)
      throw (SystemException);
      
// Write a report in `report' about the execution of a program, using the
// status informations in `st'
void output_execution_report(const cmd_status& st);

// Handle reception of a signal to be forwarded
void forward_signal(int signum)
{
      try {
            if (Status::get().pid)
                  if (kill(Status::get().pid, signum) == -1)
                        throw SystemException(errno, "sending signal " + fmt(signum) +
                                                                        " to child " + fmt(Status::get().pid));
      } catch (Exception& e) {
            Output::lauerr(string(e.type()) + ": " + e.desc());
      }
}

// Handle reception of a signal that terminates the running launchtool
void term_signal(int signum)
{
      Status::get().interrupted = true;
      Status::get().int_signal = signum;
}

// Handle reception of a critical signal
void crit_signal(int signum)
{
      string signame = sys_siglist[signum];

      // Log a stack trace
      const int trace_size = 50;
      void *addrs[trace_size];
      size_t size = backtrace(addrs, trace_size);
      char **strings = backtrace_symbols(addrs, size);
      Output::lauerr(signame + " received. " + fmt(size) + " stack frames unwound:");
      for (size_t i = 0; i < size; i++)
            Output::lauerr(string("   ") + strings[i]);
      free(strings);

      if (Status::get().pid)
      {
            Output::lauerr("Sending the child process the TERM signal...");
            if (kill(Status::get().pid, SIGTERM) == -1)
                  throw SystemException(errno, "sending signal " + fmt(SIGTERM) +
                                                                  " to child " + fmt(Status::get().pid));
            sleep(2);
            Output::lauerr("Sending the child process the KILL signal...");
            if (kill(Status::get().pid, SIGKILL) == -1)
                  throw SystemException(errno, "sending signal " + fmt(SIGKILL) +
                                                                  " to child " + fmt(Status::get().pid));
      }

      Output::lauerr("Exiting");
      _exit(1);
}

// Setup signal masks and handlers
void setup_signals()
{
      // Block signals in `blocked_signals'
      sigset_t block_set;
      sigemptyset(&block_set);
      for (unsigned int i = 0; i < Cfg::get().blocked_signals.size(); i++)
            sigaddset(&block_set, Cfg::get().blocked_signals[i]);
      sigprocmask(SIG_BLOCK, &block_set, 0);

      struct sigaction sa;
      sigemptyset(&sa.sa_mask);
      sa.sa_flags = 0;

      // Setup the terminating function for normal termination signals
      sa.sa_handler = term_signal;
      sigaction(SIGINT, &sa, 0);
      sigaction(SIGQUIT, &sa, 0);
      sigaction(SIGTERM, &sa, 0);

      // Setup the critical terminating function for critical signals
      sa.sa_handler = crit_signal;
      sigaction(SIGFPE, &sa, 0);
      sigaction(SIGILL, &sa, 0);
      sigaction(SIGSEGV, &sa, 0);
      sigaction(SIGBUS, &sa, 0);
      sigaction(SIGABRT, &sa, 0);
      sigaction(SIGIOT, &sa, 0);
      sigaction(SIGTRAP, &sa, 0);
      sigaction(SIGSYS, &sa, 0);

      // Setup the forwarder function for signals in `forwarded_signals',
      // possibly overwriting `terminating' and `critical' handler setups
      sa.sa_handler = forward_signal;
      for (unsigned int i = 0; i < Cfg::get().forwarded_signals.size(); i++)
            sigaction(Cfg::get().forwarded_signals[i], &sa, 0);
}

class LaunchtoolMain : public Process
{
public:
      virtual int main() throw ();
};

int LaunchtoolMain::main() throw ()
{
      try {
            if (Cfg::get().verbose)
                  if (Cfg::get().detach)
                        log_info(Cfg::get().visible_tag + " going to background with pid " + fmt(getpid()));
                  else
                        log_info(Cfg::get().visible_tag + " started (pid: " + fmt(getpid()) + ")");

            // Detach from TTY
            if (Cfg::get().detach)
            {
                  chdir("/");
                  detachFromTTY();
            }

            // Setup and create the pid file
            Pidfile pidfile(Cfg::get().visible_tag, Cfg::get().piddir);
            if (Cfg::get().pidfile)
                  pidfile.takeover();

            // Main loop
            setup_signals();

            string rep_prefix = Cfg::get().visible_tag + " (" +
                                          Cfg::get().command + ") ";

            if (Cfg::get().verbose)
                  Output::lauout(rep_prefix + "starting session");
            
            // Run and re-run the process
            while (! Status::get().interrupted)
            {
                  string report;
                  cmd_status status;

                  run_command(&status);
      
                  // Check if we should do a silent restart
                  if (Cfg::get().enable_silent_restart &&
                              WIFEXITED(status.status) && WEXITSTATUS(status.status) ==
                              Cfg::get().silent_restart_status)
                  {
                        // Silent restart
                        sleep(Cfg::get().silent_restart_time);
                  } else {
                        // Log execution stats if requested
                        if (Cfg::get().stats)
                              output_execution_report(status);

                        // Check for successful termination
                        if (WIFEXITED(status.status) && WEXITSTATUS(status.status) == 0)
                        {
                              // Successful termination
                              if (Cfg::get().verbose)
                                    Output::lauout(rep_prefix + "terminated successfully");
                              return 0;
                        }
                        else
                        {
                              // Unsuccessful termination

                              // Just return the exit status if we should not restart the
                              // command
                              if (!Cfg::get().wait_times.size())
                                    return WEXITSTATUS(status.status);

                              unsigned int wait_time;

                              // Compute how much time we should wait
                              if (status.running_time.tv_sec < Cfg::get().good_running_time)
                              {
                                    // good_running_time not reached
                                    wait_time =
                                          Cfg::get().wait_times[Status::get().next_delay];

                                    if (!Cfg::get().infinite_runs ||
                                                Status::get().next_delay <
                                                Cfg::get().wait_times.size() - 1)
                                          Status::get().next_delay++;
                              } else {
                                    wait_time = Cfg::get().wait_times[0];
                                    Status::get().next_delay = 0;
                              }
                                    
                              if (Status::get().next_delay >=
                                          Cfg::get().wait_times.size())
                              {
                                    // Retry limit reached
                                    Output::lauerr(rep_prefix +
                                                "restarting too often: giving up");
                                    return WEXITSTATUS(status.status);
                              } else if (!Status::get().interrupted) {
                                    // Program exited and needs re-running
                                    Output::lauerr(rep_prefix + status.beautified_status() +
                                                ", restart in " + fmt(wait_time) + " seconds");
                                    sleep(wait_time);
                              }
                        }
                  }

                  if (Status::get().interrupted)
                  {
                        // Interrupted by a signal
                        if (WIFSIGNALED(status.status))
                        {
                              // The signal killed the child process
                              Output::lauerr(rep_prefix + "interrupted by signal "
                                          + fmt(Status::get().int_signal) +
                                          " forwarded by the launchtool");
                        } else {
                              // The signal killed just the launchtool while it was sleeping
                              Output::lauerr(rep_prefix + 
                                          "launchtool interrupted by signal "
                                          + fmt(Status::get().int_signal) +
                                          " while sleeping");
                        }
                  }
            }
            // Terminated by signal
            Output::lauerr(rep_prefix + "launchtool terminated by signal " +
                        fmt(Status::get().int_signal));
      } catch (Exception& e) {
            Output::lauerr(Cfg::get().visible_tag + " (" + Cfg::get().command +
                        ") caught exception: " + e.type() + ": " + e.desc());
      }

      if (Cfg::get().verbose)
            Output::lauout(Cfg::get().visible_tag + " (" + Cfg::get().command +
                        ") quit");
      return 1;
}

static bool cimatch(const string& str1, const string& str2) throw ()
{
      if (str1.size() != str2.size())
            return false;
      return strncasecmp(str1.data(), str2.data(), str1.size()) == 0;
}

static bool parseBool(const string& str) throw (ConsistencyCheckException)
{
      if (str == "0" || cimatch(str, "no") || cimatch(str, "false"))
            return false;
      if (str == "1" || cimatch(str, "yes") || cimatch(str, "true"))
            return true;
      throw ConsistencyCheckException("Bad true/false falue: " + str);
}

vector<unsigned int> parseIntList(const string& str) throw (ConsistencyCheckException)
{
      const char* s = str.c_str();
      const char* e = s + str.size();

      vector<unsigned int> res;
      while (s < e)
      {
            res.push_back((unsigned int)strtol(s, (char**)&s, 10));

            while (s < e && (*s == ',' || isblank(*s)))
                  s++;
      }

      return res;
}

vector<unsigned int> parseSigList(const string& str) throw (ConsistencyCheckException)
{
      const char* s = str.data();
      const char* e = str.data() + str.size();

      static struct {
            unsigned int num;
            const char* name;
      } sigs[] = {
            {  1, "HUP" },    {  2, "INT" },  {  3, "QUIT" },  {  4, "ILL" },
            {  5, "TRAP" },   {  6, "ABRT" }, {  7, "BUS" },   {  8, "FPE" },
            {  9, "KILL" },   { 10, "USR1" }, { 11, "SEGV" },  { 12, "USR2" },
            { 13, "PIPE" },   { 14, "ALRM" }, { 15, "TERM" },  { 17, "CHLD" },
            { 18, "CONT" },   { 19, "STOP" }, { 20, "TSTP" },  { 21, "TTIN" },
            { 22, "TTOU" },   { 23, "URG" },  { 24, "XCPU" },  { 25, "XFSZ" },
            { 26, "VTALRM" }, { 27, "PROF" }, { 28, "WINCH" }, { 29, "IO" },
            { 30, "PWR" },    { 31, "SYS" },  {  0, 0 } };

      vector<unsigned int> res;
      while (s < e)
      {
            if (isdigit(*s))
                  res.push_back(strtol(s, (char**)&s, 10));
            else
            {
                  string v;
                  while (s < e && (*s != ',' && !isblank(*s)))
                        v += *s++;
                  for (unsigned int i = 0; sigs[i].name; i++)
                        if (v == sigs[i].name ||
                                    (v.substr(0, 3) == "SIG" &&
                                          v.substr(3) == sigs[i].name))
                        {
                              res.push_back(sigs[i].num);
                              v = string();
                              break;
                        }
                  if (v.size())
                        throw ConsistencyCheckException("Unknown signal name: " + v); 
            }

            while (s < e && (*s == ',' || isblank(*s)))
                  s++;
      }

      return res;
}

vector<string> parseStringList(const string& str) throw (ConsistencyCheckException)
{
      const char* s = str.data();
      const char* e = str.data() + str.size();

      vector<string> res;
      while (s < e)
      {
            string v;

            while (s < e && (*s != ',' && !isblank(*s)))
                  v += *s;
            res.push_back(v);

            while (s < e && (*s == ',' || isblank(*s)))
                  s++;
      }

      return res;
}

int main(int argc, const char* argv[])
{
      // Initialize command line parsing data

      // Special actions
      int op_version = 0;
      int op_check = 0;
      int op_showcfg = 0;
      int op_kill = -1;

      // Verbosity
      int op_verbose = -1;
      int op_no_verbose = -1;
      int op_debug = -1;
      int op_no_debug = -1;

      // Operating parameters
      char* op_config_file = 0;
      char* op_visible_tag = 0;
      char* op_root_dir = 0;
      char* op_start_dir = 0;
      int op_umask = -1;
      int op_infinite_runs = -1;
      int op_no_infinite_runs = -1;
      char* op_wait_times = 0;
      int op_good_running_time = -1;
      char* op_forwarded_signals = 0;
      char* op_blocked_signals = 0;
      int op_detach = -1;
      int op_no_detach = -1;
      int op_pidfile = -1;
      char* op_piddir = 0;
      int op_no_pidfile = -1;
      char* op_command = 0;
      char* op_tag = 0;
      int op_limit_cpu = -1;
      int op_limit_file_size = -1;
      int op_limit_data_memory = -1;
      int op_limit_process_count = -1;
      int op_limit_open_files = -1;
      int op_limit_core_size = -1;
      char* op_user = 0;
      char* op_group = 0;
      int op_restrict_environment = -1;
      int op_no_restrict_environment = -1;
      char* op_allowed_env_vars = 0;
      char* op_log_launchtool_out = 0;
      char* op_log_launchtool_err = 0;
      char* op_log_child_stdout = 0;
      char* op_log_child_stderr = 0;
      int op_silent_restart_status = -1;
      int op_silent_restart_time = -1;
      int op_stats = -1;
      int op_no_stats = -1;
      
      char* help =
                  "Run a command supervising its execution.\n";
      struct poptOption emptyTable[] = { POPT_TABLEEND };
      struct poptOption optionsTable[] = {
            // Special actions
            { "kill", 'k', POPT_ARG_INT | POPT_ARGFLAG_OPTIONAL, &op_kill, 0, "kill a running daemon with the specified signal (15 by default) and exit", "signal" },
            { "check", 0, POPT_ARG_NONE, &op_check, 0, "check if another " APPNAME " is running and exit", 0 },
            { "showcfg", 0, POPT_ARG_NONE, &op_showcfg, 0, "process config files and commandline, show the resulting configuration and exit", 0 },
            { "version", 'V', POPT_ARG_NONE, &op_version, 0, "print version and exit", 0 },

            // Verbosity
            { "verbose", 'v', POPT_ARG_NONE, &op_verbose, 0, "enable verbose output", 0 },
            { "no-verbose", 0, POPT_ARG_NONE, &op_no_verbose, 0, "disable verbose output", 0 },
            { "debug", 0, POPT_ARG_NONE, &op_debug, 0, "enable debug output (includes --verbose output)", 0 },
            { "no-debug", 0, POPT_ARG_NONE, &op_no_debug, 0, "disable debug output", 0 },
            
            // Operating parameters
            { "config", 'C', POPT_ARG_STRING, &op_config_file, 0, "file with configuration data (default: " CONFIG_DIR "/<tag>.conf)", "file" },
            { "tag", 't', POPT_ARG_STRING, &op_tag, 0, "tag used to identify the session", "tag" },
            { "command", 'c', POPT_ARG_STRING, &op_command, 0, "command to execute", "cmd" },
            { "visible-tag", 0, POPT_ARG_STRING, &op_visible_tag, 0, "tag to use for pidfiles and logfiles instead of \"launchtool-<tag>\"", "tag" },

            { "daemon", 'd', POPT_ARG_NONE, &op_detach, 0, "fork to background, becoming a daemon", 0 },
            { "no-daemon", 'n', POPT_ARG_NONE, &op_no_detach, 0, "don't fork to background", 0 },

            { "pidfile", 0, POPT_ARG_NONE, &op_pidfile, 0, "create a pidfile (default when --daemon is used)", 0 },
            { "no-pidfile", 0, POPT_ARG_NONE, &op_no_pidfile, 0, "don't create a pidfile (default when --daemon is not used)", 0 },
            { "piddir", 0, POPT_ARG_STRING, &op_piddir, 0, "directory where pidfiles are stored (default to " PID_DIR ")", "dir" },

            { "chroot", 0, POPT_ARG_STRING, &op_root_dir, 0, "chroot to this directory before running the command", "dir" },
            { "chdir", 0, POPT_ARG_STRING, &op_start_dir, 0, "chdir to this directory before running the command (default to '.' or '/' if --daemon is present)", "dir" },
            { "user", 'u', POPT_ARG_STRING, &op_user, 0, "user privileges to run the command with", "user" },
            { "group", 'g', POPT_ARG_STRING, &op_group, 0, "group privileges to run the command with", "group" },

            { "umask", 0, POPT_ARG_STRING, &op_umask, 0, "set this umask before running the command", "mask" },

            { "infinite-runs", 'L', POPT_ARG_NONE, &op_infinite_runs, 0, "never give up restarting the command if it fails", 0 },
            { "no-infinite-runs", 0, POPT_ARG_NONE, &op_no_infinite_runs, 0, "give up restarting the command after a certain number of failures", 0 },

            { "wait-times", 0, POPT_ARG_STRING, &op_wait_times, 0, "list of times (in seconds) to wait after a program failure before restarting it.  If not specified, failed commands will not be restarted.", "t1,t2,..." },
            { "good-running-time", 0, POPT_ARG_INT, &op_good_running_time, 0, "minimum running time needed to restart for the first wait time", "seconds" },

            { "forwarded-signals", 0, POPT_ARG_STRING, &op_forwarded_signals, 0, "list of signals (in name or in number) to be forwarded to the command", "sig1,sig2,..." },
            { "blocked-signals", 0, POPT_ARG_STRING, &op_blocked_signals, 0, "list of signals (in name or in number) to be blocked before running the command", "sig1,sig2,..." },

            { "limit-cpu", 0, POPT_ARG_INT, &op_limit_cpu, 0, "CPU time limit for the command (see setrlimit(2))", "seconds" },
            { "limit-file-size", 0, POPT_ARG_INT, &op_limit_file_size, 0, "File size limit for the command (see setrlimit(2))", "1024b-blocks" },
            { "limit-data-memory", 0, POPT_ARG_INT, &op_limit_data_memory, 0, "Data memory size limit for the command (see setrlimit(2))", "1024b-blocks" },
            { "limit-process-count", 0, POPT_ARG_INT, &op_limit_process_count, 0, "Process count limit for the command (see setrlimit(2))", "count" },
            { "limit-open-files", 0, POPT_ARG_INT, &op_limit_open_files, 0, "Open files limit for the command (see setrlimit(2))", "count" },
            { "limit-core-size", 0, POPT_ARG_INT, &op_limit_core_size, 0, "Core file size limit for the command (see setrlimit(2))", "1024b-blocks" },
            
            { "restrict-environment", 0, POPT_ARG_NONE, &op_restrict_environment, 0, "restrict the child environment", 0 },
            { "no-restrict-environment", 0, POPT_ARG_NONE, &op_no_restrict_environment, 0, "copy all environment variables to the child environment", 0 },

            { "allowed-env-vars", 0, POPT_ARG_STRING, &op_allowed_env_vars, 0, "list of environment variables to be copied to the child when the environment is restricted", "var1,var2,..." },

            { "log-launchtool-output", 0, POPT_ARG_STRING, &op_log_launchtool_out, 0, "target of the launchtool output (ignore, stdout, stderr, file:filename or syslog:identity,facility,level)", "target" },
            { "log-launchtool-errors", 0, POPT_ARG_STRING, &op_log_launchtool_err, 0, "target of the launchtool error messages (ignore, stdout, stderr, file:filename or syslog:identity,facility,level)", "target" },
            { "log-child-output", 0, POPT_ARG_STRING, &op_log_child_stdout, 0, "target of the child output (ignore, stdout, stderr, file:filename or syslog:identity,facility,level)", "target" },
            { "log-child-errors", 0, POPT_ARG_STRING, &op_log_child_stderr, 0, "target of the child error messages (ignore, stdout, stderr, file:filename or syslog:identity,facility,level)", "target" },

            { "silent-restart-status", 0, POPT_ARG_INT, &op_silent_restart_status, 0, "Return value used by the child to explicitly request a restart (feature disabled if not specified)", "value" },
            { "silent-restart-time", 0, POPT_ARG_INT, &op_silent_restart_time, 0, "Time to wait before restarting the child after an explicit restart request", "seconds" },

            { "stats", 0, POPT_ARG_NONE, &op_stats, 0, "Produce some statistics when the command terminates (implied by --verbose)", 0 },
            { "no-stats", 0, POPT_ARG_NONE, &op_no_stats, 0, "Do not produce statistics when the command terminates", 0 },

            POPT_AUTOHELP
            { NULL, 0, POPT_ARG_INCLUDE_TABLE, emptyTable, 0, help, 0 },
            POPT_TABLEEND
      };
      poptContext optCon = poptGetContext(NULL, argc, argv, optionsTable, 0);
      poptSetOtherOptionHelp(optCon, "[options]");

      set_unexpected(DefaultUnexpected);

      if (argc < 2)
      {
            poptPrintHelp(optCon, stderr, 0);
            return 1;
      }

      // Process commandline
      int res = poptGetNextOpt(optCon);
      if (res != -1)
      {
            fprintf(stderr, "%s: %s\n\n",
                  poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
                  poptStrerror(res));
            poptPrintUsage(optCon, stderr, 0);
            return 1;
      }

      if (op_version)
      {
            printf("%s ver." VERSION "\n", argv[0]);
            return 0;
      }

      Output::set_lauout(new StdoutOutput());
      Output::set_lauerr(new StderrOutput());
      Output::set_cldout(new StdoutOutput());
      Output::set_clderr(new StderrOutput());

      try {
            LaunchtoolLogEnvironment logEnv();

            // Read configuration files
            Config cfg;
            if (op_config_file)
                  cfg = Config::parse(op_config_file);
            else
            {
                  if (op_tag)
                  {
                        string file = string(CONFIG_DIR "/") + op_tag + ".conf";
                        if (access(file.c_str(), R_OK) == 0)
                              cfg = Config::parse(file);
                  }
            }

            // Use the private configuration section, if it exists
            try {
                  if (op_tag)
                        cfg = cfg.getSection(op_tag);
            } catch (ConfigKeyNotFoundException& e) {}


            string cand_tag = op_tag ? op_tag : cfg.getString("tag", "");
            if (cand_tag == "")
            {
                  fprintf(stderr, "A tag must be provided, with the -t/--tag switch or in a config file\n");
                  return 1;
            }
            Cfg::init(cand_tag);

#define GETSTRING(parm, cfgparm) \
            Cfg::get().parm = op_##parm ? op_##parm \
                  : cfg.getString(cfgparm, Cfg::get().parm)

#define GETINT(parm, cfgparm) \
            Cfg::get().parm = op_##parm != -1 ? op_##parm \
                  : cfg.getInt(cfgparm, Cfg::get().parm)

#define GETBOOL(parm, cfgparm) \
            Cfg::get().parm = op_##parm != -1 ? 1 \
                  : parseBool(cfg.getString(cfgparm, Cfg::get().parm ? "yes" : "no")); \
            Cfg::get().parm = op_no_##parm != -1 ? 0 : Cfg::get().parm

            // Setup configuration data
            GETSTRING(visible_tag, "visible tag");

            GETSTRING(command, "command");
      
            GETBOOL(detach, "daemon");
            if (Cfg::get().detach)
                  Cfg::get().pidfile = true;
            GETBOOL(pidfile, "pidfile");

            GETSTRING(piddir, "piddir");

            Cfg::get().start_dir = Cfg::get().detach ? "/" : ".";
            GETSTRING(start_dir, "start dir");
            GETSTRING(root_dir, "root dir");

            GETINT(umask, "umask");

            GETBOOL(infinite_runs, "infinite runs");

            string cand_wait_times = op_wait_times ? op_wait_times :
                  cfg.getString("wait times", "");
            if (cand_wait_times.size())
                  Cfg::get().wait_times = parseIntList(cand_wait_times);

            GETINT(good_running_time, "good running time");

            Cfg::get().forwarded_signals = parseSigList(op_forwarded_signals ? op_forwarded_signals
                  : cfg.getString("forwarded signals", ""));

            Cfg::get().blocked_signals = parseSigList(op_blocked_signals ? op_blocked_signals
                  : cfg.getString("blocked signals", ""));

            GETINT(limit_cpu, "cpu limit");
            GETINT(limit_file_size, "file size limit");
            GETINT(limit_data_memory, "data memory limit");
            GETINT(limit_process_count, "process count limit");
            GETINT(limit_open_files, "open files limit");
            GETINT(limit_core_size, "core size limit");
            
            GETSTRING(user, "user");
            GETSTRING(group, "group");

            GETBOOL(restrict_environment, "restrict environment");

            vector<string> strlist = parseStringList(op_allowed_env_vars ?
                        op_allowed_env_vars : cfg.getString("allowed env vars", ""));
            Cfg::get().allowed_env_vars.clear();
            for (vector<string>::const_iterator i = strlist.begin();
                        i != strlist.end(); i++)
                  Cfg::get().allowed_env_vars.insert(*i);

            GETSTRING(log_launchtool_out, "launchtool output");
            GETSTRING(log_launchtool_err, "launchtool errors");
            GETSTRING(log_child_stdout, "command output");
            GETSTRING(log_child_stderr, "command errors");

            GETINT(silent_restart_status, "silent restart status");
            if (Cfg::get().silent_restart_status != -1)
                  Cfg::get().enable_silent_restart = true;
            GETINT(silent_restart_time, "silent restart time");

            GETBOOL(verbose, "verbose");
            GETBOOL(debug, "debug");
            if (Cfg::get().verbose)
                  Cfg::get().stats = true;
            GETBOOL(stats, "stats");

            // Initialize output system
            Output::set_lauout(Output::getMethod(Cfg::get().log_launchtool_out));
            Output::set_lauerr(Output::getMethod(Cfg::get().log_launchtool_err));
            Output::set_cldout(Output::getMethod(Cfg::get().log_child_stdout));
            Output::set_clderr(Output::getMethod(Cfg::get().log_child_stderr));

            // Concatenate remaining parameters to the command
            while (poptPeekArg(optCon))
            {
                  if (Cfg::get().command.size())
                        Cfg::get().command += " ";
                  Cfg::get().command += poptGetArg(optCon);
            }

            // Do special actions, if requested
            if (op_showcfg)
            {
                  Cfg::get().print();
                  return 0;
            }

            if (op_kill != -1)
            {
                  if (op_kill == 0)
                        op_kill = 15;
                  Pidfile pidfile(Cfg::get().visible_tag, Cfg::get().piddir);
                  if (pidfile.kill(op_kill))
                        return 0;
                  else {
                        Output::lauerr(Cfg::get().visible_tag + " is not running");
                        return 1;
                  }
            }

            if (op_check)
            {
                  Pidfile pidfile(Cfg::get().visible_tag, Cfg::get().piddir);
                  if (pidfile.is_active())
                  {
                        if (isatty(1)) printf("%.*s is running with pid %d\n",
                                    PFSTR(Cfg::get().visible_tag),
                                    pidfile.read());
                        return 0;
                  } else {
                        if (isatty(1)) printf("%.*s is not running\n",
                                    PFSTR(Cfg::get().visible_tag));
                        return 1;
                  }
            }

            // Check for another existing such launchtool, if needed
            Pidfile pidfile(Cfg::get().visible_tag, Cfg::get().piddir);
            if (!op_no_pidfile && pidfile.is_active())
            {
                  Output::lauerr(Cfg::get().visible_tag + " already running as pid " + fmt(pidfile.read()));
                  return 1;
            }

            // Main initialization
            LaunchtoolMain* launchtool = new LaunchtoolMain();
            if (! Cfg::get().detach)
                  return launchtool->main();
            else
            {
                  ChildProcess daemon(launchtool);
                  daemon.fork();
                  return 0;
            }
      } catch (Exception& e) {
            if (Cfg::initialized())
                  Output::lauerr(Cfg::get().visible_tag + " " + e.type() + ": " + e.desc());
            else
                  Output::lauerr(string(e.type()) + ": " + e.desc());
            return 1;
      }
      return 0;
};

class SubCommand : public Process
{
public:
      virtual int main() throw ();
};

int SubCommand::main() throw ()
{
      try {
            // Change root directory
            if (Cfg::get().root_dir.size())
                  Process::chroot(Cfg::get().root_dir);

            // Change working directory
            if (Cfg::get().start_dir.size())
                  Process::chdir(Cfg::get().start_dir);

            // Set umask
            if (Cfg::get().umask != (mode_t)-1)
                  Process::umask(Cfg::get().umask);

            // Set resource limits
            if (Cfg::get().limit_cpu != -1)
                  Process::setCPUTimeLimit(Cfg::get().limit_cpu);
            if (Cfg::get().limit_file_size != -1)
                  Process::setFileSizeLimit(Cfg::get().limit_file_size);
            if (Cfg::get().limit_data_memory != -1) 
                  Process::setDataMemoryLimit(Cfg::get().limit_data_memory);
            if (Cfg::get().limit_process_count != -1)
                  Process::setChildrenLimit(Cfg::get().limit_process_count);
            if (Cfg::get().limit_open_files != -1)
                  Process::setOpenFilesLimit(Cfg::get().limit_open_files);
            if (Cfg::get().limit_core_size != -1)
                  Process::setCoreSizeLimit(Cfg::get().limit_core_size);

            // Set user and group permissions
            if (Cfg::get().user.size())
                  if (Cfg::get().group.size())
                        Process::setPerms(Cfg::get().user, Cfg::get().group);
                  else
                        Process::setPerms(Cfg::get().user);

            Exec prog("/bin/sh");
            prog.addArg("/bin/sh");
            prog.addArg("-c");
            prog.addArg(Cfg::get().command);

            // Build environment, with optional filtering
            if (!Cfg::get().restrict_environment)
                  for (char** s = environ; *s; s++)
                        prog.addEnv(*s);
            else
            {
                  if (Cfg::get().allowed_env_vars.size())
                        for (char** s = environ; *s; s++)
                        {
                              string name = *s;
                              size_t pos = name.find('=');
                              if (pos != string::npos)
                                    name.resize(pos);
                              if (Cfg::get().allowed_env_vars.find(name) !=
                                          Cfg::get().allowed_env_vars.end())
                                    prog.addEnv(*s);
                        }
            }
            prog.addEnv("UNDER_LAUNCHTOOL=1");

            // Execute the program
            prog.exec();
            // Unreachable, but makes g++ stop complaining
            return 1;
      } catch (Exception& e) {
            Output::lauerr(string(e.type()) + ": " + e.desc());
            return 1;
      }
}

class StdoutListener : public LineMultiReader::Listener
{
public:
      virtual void haveLine(int fd, const string& line) throw ()
      {
            Output::cldout(line);
      }
};

class StderrListener : public LineMultiReader::Listener
{
public:
      virtual void haveLine(int fd, const string& line) throw ()
      {
            Output::clderr(line);
      }
};

// Run the child command logging its output, filling up `st' and returning the
// child exit status
int run_command(struct cmd_status* st)
      throw (SystemException)
{
      struct timeval starttime;
      int cld_stdout, cld_stderr;
      gettimeofday(&starttime, 0);

      ChildProcess child(new SubCommand());

      // Run the command as a child process, catching stdout and stderr
      child.fork(0, &cld_stdout, &cld_stderr);

      // Fill in the global childPid variable used by the signal handlers
      Status::get().pid = child.pid();

      // Fill-in the status structure info
      st->cmd = Cfg::get().command;
      st->pid = child.pid();

      // Read the child output
      StdoutListener sol;
      StderrListener sel;
      LineMultiReader oreader;
      oreader.addFD(cld_stdout, &sol);
      oreader.addFD(cld_stderr, &sel);

      while (1)
      {
            try {
                  oreader.readLoop();
                  break;
            } catch (InterruptedException& e) {
                  Output::lauerr("Wait interrupted " + e.desc());
                  if (Status::get().interrupted)
                        break;
            }
      }

      close(cld_stdout);
      close(cld_stderr);

      // Read the child exit status and resource informations
      bool done = false;
      while (!done)
      {
            try {
                  st->status = child.wait(&st->ru);
                  done = true;
            } catch (InterruptedException) {}
      }
      Status::get().pid = 0;

      // Calculate the total running time
      gettimeofday(&st->running_time, 0);
      if (st->running_time.tv_usec < starttime.tv_usec)
      {
            st->running_time.tv_usec += 1000000;
            st->running_time.tv_sec -= 1;
      }
      st->running_time.tv_usec -= starttime.tv_usec;
      st->running_time.tv_sec -= starttime.tv_sec;

      return st->status;
}


void output_execution_report(const cmd_status& st)
{
      Output::lauout(" * Process execution details");
      Output::lauout("   Command: " + st.cmd + " " + st.beautified_status());
      Output::lauout(fmt("   Time: running: %02li:%02li:%02li"
                                    " user: %li.%06lis"
                                    " system: %li.%06lis",
                                    st.running_time.tv_sec / 3600,
                                    st.running_time.tv_sec / 60 % 60,
                                    st.running_time.tv_sec % 60,
                                    st.ru.ru_utime.tv_sec, st.ru.ru_utime.tv_usec,
                                    st.ru.ru_stime.tv_sec, st.ru.ru_stime.tv_usec));

      if (st.ru.ru_maxrss)
            Output::lauout(fmt("   Max resident set size: %lib", st.ru.ru_maxrss));
      if (st.ru.ru_ixrss)
            Output::lauout(fmt("   Integral shared memory size: %lib", st.ru.ru_ixrss));
      if (st.ru.ru_idrss)
            Output::lauout(fmt("   Integral unshared data size: %lib", st.ru.ru_idrss));
      if (st.ru.ru_isrss)
            Output::lauout(fmt("   Integral unshared stack size: %lib", st.ru.ru_isrss));
      if (st.ru.ru_minflt)
      Output::lauout(fmt("   Page reclaims: %li", st.ru.ru_minflt));
      if (st.ru.ru_majflt)
            Output::lauout(fmt("   Page faults: %li", st.ru.ru_majflt));
      if (st.ru.ru_nswap)
            Output::lauout(fmt("   Swaps: %li", st.ru.ru_nswap));
      if (st.ru.ru_inblock)
            Output::lauout(fmt("   Block input operations: %li", st.ru.ru_inblock));
      if (st.ru.ru_oublock)
            Output::lauout(fmt("   Block output operations: %li", st.ru.ru_oublock));
      if (st.ru.ru_msgsnd)
            Output::lauout(fmt("   Messages sent: %li", st.ru.ru_msgsnd));
      if (st.ru.ru_msgrcv)
            Output::lauout(fmt("   Messages received: %li", st.ru.ru_msgrcv));
      if (st.ru.ru_nsignals)
            Output::lauout(fmt("   Signals received: %li", st.ru.ru_nsignals));
      if (st.ru.ru_nvcsw)
            Output::lauout(fmt("   Voluntary context switches: %li", st.ru.ru_nvcsw));
      if (st.ru.ru_nivcsw)
            Output::lauout(fmt("   Involuntary context switches: %li", st.ru.ru_nivcsw));
}

// vim:set ts=4 sw=4:

Generated by  Doxygen 1.6.0   Back to index