Exemple #1
0
/**
 * g_process_change_dir:
 *
 * Change the current working directory to the value specified by the user
 * and verify that the daemon would be able to dump core to that directory
 * if that is requested.
 **/
static void
g_process_change_dir(void)
{
  const gchar *cwd = NULL;

  if (process_opts.mode != G_PM_FOREGROUND)
    {
      if (process_opts.cwd)
        cwd = process_opts.cwd;
      else if (process_opts.pidfile_dir)
        cwd = process_opts.pidfile_dir;
      if (!cwd)
        cwd = get_installation_path_for(SYSLOG_NG_PATH_PIDFILEDIR);

      if (cwd)
        if (chdir(cwd))
          g_process_message("Error changing to directory=%s, errcode=%d", cwd, errno);
    }

  /* this check is here to avoid having to change directory early in the startup process */
  if ((process_opts.core) && access(".", W_OK) < 0)
    {
      gchar buf[256];

      if (!getcwd(buf, sizeof(buf)))
        strncpy(buf, "unable-to-query", sizeof(buf));
      g_process_message("Unable to write to current directory, core dumps will not be generated; dir='%s', error='%s'", buf,
                        g_strerror(errno));
    }

}
Exemple #2
0
/**
 * g_process_change_caps:
 *
 * Change the current capset to the value specified by the user.  causes the
 * startup process to fail if this function returns FALSE, but we only do
 * this if the capset cannot be parsed, otherwise a failure changing the
 * capabilities will not result in failure
 *
 * Returns: TRUE to indicate success
 **/
static gboolean
g_process_change_caps(void)
{
  if (g_process_is_cap_enabled())
    {
      cap_t cap = cap_from_text(process_opts.caps);

      if (cap == NULL)
        {
          g_process_message("Error parsing capabilities: %s", process_opts.caps);
          process_opts.caps = NULL;
          process_opts.enable_caps = FALSE;
          return FALSE;
        }
      else
        {
          if (cap_set_proc(cap) == -1)
            {
              g_process_message("Error setting capabilities, capability management disabled; error='%s'", g_strerror(errno));
              process_opts.caps = NULL;
              process_opts.enable_caps = FALSE;

            }
          cap_free(cap);
        }
    }
  return TRUE;
}
Exemple #3
0
/**
 * g_process_enable_core:
 *
 * Enable core file dumping by setting PR_DUMPABLE and changing the core
 * file limit to infinity.
 **/
static void
g_process_enable_core(void)
{
  struct rlimit limit;

  if (process_opts.core)
    {
#if SYSLOG_NG_ENABLE_LINUX_CAPS
      if (!prctl(PR_GET_DUMPABLE, 0, 0, 0, 0))
        {
          gint rc;

          rc = prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);

          if (rc < 0)
            g_process_message("Cannot set process to be dumpable; error='%s'", g_strerror(errno));
        }
#endif

      limit.rlim_cur = limit.rlim_max = RLIM_INFINITY;
      if (setrlimit(RLIMIT_CORE, &limit) < 0)
        g_process_message("Error setting core limit to infinity; error='%s'", g_strerror(errno));
      
    }
}
Exemple #4
0
/**
 * g_process_change_user:
 *
 * Change the current user/group/groups to the value specified by the user.
 * causes the startup process to fail if this function returns FALSE. (e.g.
 * the user requested the uid/gid to change we could not change to that uid)
 *
 * Returns: TRUE to indicate success
 **/
static gboolean
g_process_change_user(void)
{
  g_process_keep_caps();

  if (process_opts.gid >= 0)
    {
      if (setgid((gid_t) process_opts.gid) < 0)
        {
          g_process_message("Error in setgid(); group='%s', gid='%d', error='%s'", process_opts.group, process_opts.gid,
                            g_strerror(errno));
          if (getuid() == 0)
            return FALSE;
        }
      if (process_opts.user && initgroups(process_opts.user, (gid_t) process_opts.gid) < 0)
        {
          g_process_message("Error in initgroups(); user='******', error='%s'", process_opts.user, g_strerror(errno));
          if (getuid() == 0)
            return FALSE;
        }
    }

  if (process_opts.uid >= 0)
    {
      if (setuid((uid_t) process_opts.uid) < 0)
        {
          g_process_message("Error in setuid(); user='******', uid='%d', error='%s'", process_opts.user, process_opts.uid,
                            g_strerror(errno));
          if (getuid() == 0)
            return FALSE;
        }
    }

  return TRUE;
}
Exemple #5
0
static void
g_process_resolve_names(void)
{
  if (process_opts.user && !resolve_user(process_opts.user, &process_opts.uid))
    {
      g_process_message("Error resolving user; user='******'", process_opts.user);
      process_opts.uid = -1;
    }
  if (process_opts.group && !resolve_group(process_opts.group, &process_opts.gid))
    {
      g_process_message("Error resolving group; group='%s'", process_opts.group);
      process_opts.gid = -1;
    }
}
Exemple #6
0
/**
 * g_process_change_root:
 *
 * Change the current root to the value specified by the user, causes the
 * startup process to fail if this function returns FALSE. (e.g. the user
 * specified a chroot but we could not change to that directory)
 *
 * Returns: TRUE to indicate success
 **/
static gboolean
g_process_change_root(void)
{
  if (process_opts.chroot_dir)
    {
      if (chroot(process_opts.chroot_dir) < 0)
        {
          g_process_message("Error in chroot(); chroot='%s', error='%s'\n", process_opts.chroot_dir, g_strerror(errno));
          return FALSE;
        }
      if (chdir("/") < 0)
        {
          g_process_message("Error in chdir() after chroot; chroot='%s', error='%s'\n", process_opts.chroot_dir, g_strerror(errno));
          return FALSE;
        }
    }
  return TRUE;
}
Exemple #7
0
/**
 * g_process_change_limits:
 *
 * Set fd limit.
 *
 **/
static void
g_process_change_limits(void)
{
  struct rlimit limit;

  limit.rlim_cur = limit.rlim_max = process_opts.fd_limit_min;
  
  if (setrlimit(RLIMIT_NOFILE, &limit) < 0)
    g_process_message("Error setting file number limit; limit='%d'; error='%s'", process_opts.fd_limit_min, g_strerror(errno));
}
Exemple #8
0
/**
 * g_process_remove_pidfile:
 *
 * Remove the pidfile.
 **/
static void
g_process_remove_pidfile(void)
{
  gchar buf[256];
  const gchar *pidfile;

  pidfile = g_process_format_pidfile_name(buf, sizeof(buf));

  if (unlink(pidfile) < 0)
    {
      g_process_message("Error removing pid file; file='%s', error='%s'", pidfile, g_strerror(errno));
    }
}
Exemple #9
0
/**
 * g_process_enable_core:
 *
 * Enable core file dumping by setting PR_DUMPABLE and changing the core
 * file limit to infinity.
 **/
static void
g_process_enable_core(void)
{
  struct rlimit limit;

  if (process_opts.core)
    {
      g_process_set_dumpable();

      limit.rlim_cur = limit.rlim_max = RLIM_INFINITY;
      if (setrlimit(RLIMIT_CORE, &limit) < 0)
        g_process_message("Error setting core limit to infinity; error='%s'", g_strerror(errno));

    }
}
Exemple #10
0
static void
g_process_set_dumpable(void)
{
#if SYSLOG_NG_ENABLE_LINUX_CAPS
  if (!prctl(PR_GET_DUMPABLE, 0, 0, 0, 0))
    {
      gint rc;

      rc = prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);

      if (rc < 0)
        g_process_message("Cannot set process to be dumpable; error='%s'", g_strerror(errno));
    }
#endif
}
Exemple #11
0
/**
 * g_process_change_user:
 *
 * Change the current user/group/groups to the value specified by the user.
 * causes the startup process to fail if this function returns FALSE. (e.g.
 * the user requested the uid/gid to change we could not change to that uid)
 *
 * Returns: TRUE to indicate success
 **/
static gboolean
g_process_change_user(void)
{
#if SYSLOG_NG_ENABLE_LINUX_CAPS
  if (process_opts.caps)
    prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
#endif

  if (process_opts.gid >= 0)
    {
      if (setgid((gid_t) process_opts.gid) < 0)
        {
          g_process_message("Error in setgid(); group='%s', gid='%d', error='%s'", process_opts.group, process_opts.gid, g_strerror(errno));
          if (getuid() == 0)
            return FALSE;
        }
      if (process_opts.user && initgroups(process_opts.user, (gid_t) process_opts.gid) < 0)
        {
          g_process_message("Error in initgroups(); user='******', error='%s'", process_opts.user, g_strerror(errno));
          if (getuid() == 0)
            return FALSE;
        }
    }

  if (process_opts.uid >= 0)
    {
      if (setuid((uid_t) process_opts.uid) < 0)
        {
          g_process_message("Error in setuid(); user='******', uid='%d', error='%s'", process_opts.user, process_opts.uid, g_strerror(errno));
          if (getuid() == 0)
            return FALSE;
        }
    }
  
  return TRUE;
}
Exemple #12
0
/**
 * g_process_write_pidfile:
 * @pid: pid to write into the pidfile
 *
 * Write the pid to the pidfile.
 **/
static void
g_process_write_pidfile(pid_t pid)
{
  gchar buf[256];
  const gchar *pidfile;
  FILE *fd;

  pidfile = g_process_format_pidfile_name(buf, sizeof(buf));
  fd = fopen(pidfile, "w");
  if (fd != NULL)
    {
      fprintf(fd, "%d\n", (int) pid);
      fclose(fd);
    }
  else
    {
      g_process_message("Error creating pid file; file='%s', error='%s'", pidfile, g_strerror(errno));
    }

}
Exemple #13
0
/**
 * g_process_start:
 *
 * Start the process as directed by the options set by various
 * g_process_set_*() functions.
 **/
void
g_process_start(void)
{
  pid_t pid;

  g_process_detach_tty();
  g_process_change_limits();
  g_process_resolve_names();

  if (process_opts.mode == G_PM_BACKGROUND)
    {
      /* no supervisor, sends result to startup process directly */
      if (pipe(init_result_pipe) != 0)
        {
          g_process_message("Error daemonizing process, cannot open pipe; error='%s'", g_strerror(errno));
          exit(1);
        }

      if ((pid = fork()) < 0)
        {
          g_process_message("Error forking child process; error='%s'", g_strerror(errno));
          exit(1);
        }
      else if (pid != 0)
        {
          /* shut down init_result_pipe write side */

          close(init_result_pipe[1]);

          /* connect startup_result_pipe with init_result_pipe */
          startup_result_pipe[0] = init_result_pipe[0];
          init_result_pipe[0] = -1;

          g_process_perform_startup();
          /* NOTE: never returns */
          g_assert_not_reached();
        }
      process_kind = G_PK_DAEMON;

      /* shut down init_result_pipe read side */
      close(init_result_pipe[0]);
      init_result_pipe[0] = -1;

      /* update systemd socket activation pid */
      inherit_systemd_activation();
    }
  else if (process_opts.mode == G_PM_SAFE_BACKGROUND)
    {
      /* full blown startup/supervisor/daemon */
      if (pipe(startup_result_pipe) != 0)
        {
          g_process_message("Error daemonizing process, cannot open pipe; error='%s'", g_strerror(errno));
          exit(1);
        }
      /* first fork off supervisor process */
      if ((pid = fork()) < 0)
        {
          g_process_message("Error forking child process; error='%s'", g_strerror(errno));
          exit(1);
        }
      else if (pid != 0)
        {
          /* this is the startup process */

          /* shut down startup_result_pipe write side */
          close(startup_result_pipe[1]);
          startup_result_pipe[1] = -1;

          /* NOTE: never returns */
          g_process_perform_startup();
          g_assert_not_reached();
        }
      /* this is the supervisor process */

      /* shut down startup_result_pipe read side */
      close(startup_result_pipe[0]);
      startup_result_pipe[0] = -1;

      /* update systemd socket activation pid */
      inherit_systemd_activation();

      process_kind = G_PK_SUPERVISOR;
      g_process_perform_supervise();
      /* we only return in the daamon process here */
    }
  else if (process_opts.mode == G_PM_FOREGROUND)
    {
      process_kind = G_PK_DAEMON;
    }
  else
    {
      g_assert_not_reached();
    }

  /* daemon process, we should return to the caller to perform work */
  /* Only call setsid() for backgrounded processes. */
  if (process_opts.mode != G_PM_FOREGROUND)
    {
      setsid();
    }

  /* NOTE: we need to signal the parent in case of errors from this point.
   * This is accomplished by writing the appropriate exit code to
   * init_result_pipe, the easiest way doing so is calling g_process_startup_failed.
   * */

  if (!g_process_change_root() ||
      !g_process_change_user() ||
      !g_process_change_caps())
    {
      g_process_startup_failed(1, TRUE);
    }
  g_process_enable_core();
  g_process_change_dir();
}
Exemple #14
0
/**
 * g_process_perform_supervise:
 *
 * Supervise process, returns only in the context of the daemon process, the
 * supervisor process exits here.
 **/
static void
g_process_perform_supervise(void)
{
  pid_t pid;
  gboolean first = TRUE, exited = FALSE;
  gchar proc_title[PROC_TITLE_SPACE];
  struct sigaction sa;

  g_snprintf(proc_title, PROC_TITLE_SPACE, "supervising %s", process_opts.name);
  g_process_setproctitle(proc_title);

  memset(&sa, 0, sizeof(sa));
  sa.sa_handler = SIG_IGN;
  sigaction(SIGHUP, &sa, NULL);

  while (1)
    {
      if (pipe(init_result_pipe) != 0)
        {
          g_process_message("Error daemonizing process, cannot open pipe; error='%s'", g_strerror(errno));
          g_process_startup_failed(1, TRUE);
        }

      /* fork off a child process */
      if ((pid = fork()) < 0)
        {
          g_process_message("Error forking child process; error='%s'", g_strerror(errno));
          g_process_startup_failed(1, TRUE);
        }
      else if (pid != 0)
        {
          gint rc;
          gboolean deadlock = FALSE;

          /* this is the supervisor process */

          /* shut down init_result_pipe write side */
          close(init_result_pipe[1]);
          init_result_pipe[1] = -1;

          rc = g_process_recv_result();
          if (first)
            {
              /* first time encounter, we have a chance to report back, do it */
              g_process_send_result(rc);
              if (rc != 0)
                break;
              g_process_detach_stdio();
            }
          first = FALSE;
          if (rc != 0)
            {
              gint i = 0;
              /* initialization failed in daemon, it will probably exit soon, wait and restart */

              while (i < 6 && waitpid(pid, &rc, WNOHANG) == 0)
                {
                  if (i > 3)
                    kill(pid, i > 4 ? SIGKILL : SIGTERM);
                  sleep(1);
                  i++;
                }
              if (i == 6)
                g_process_message("Initialization failed but the daemon did not exit, even when forced to, trying to recover; pid='%d'",
                                  pid);
              continue;
            }

          if (process_opts.check_fn && (process_opts.check_period >= 0))
            {
              gint i = 1;
              while (!(exited = waitpid(pid, &rc, WNOHANG)))
                {
                  if (i >= process_opts.check_period)
                    {
                      if (!process_opts.check_fn())
                        break;
                      i = 0;
                    }
                  sleep(1);
                  i++;
                }

              if (!exited)
                {
                  gint j = 0;
                  g_process_message("Daemon deadlock detected, killing process;");
                  deadlock = TRUE;

                  while (j < 6 && waitpid(pid, &rc, WNOHANG) == 0)
                    {
                      if (j > 3)
                        kill(pid, j > 4 ? SIGKILL : SIGABRT);
                      sleep(1);
                      j++;
                    }
                  if (j == 6)
                    g_process_message("The daemon did not exit after deadlock, even when forced to, trying to recover; pid='%d'", pid);
                }
            }
          else
            {
              waitpid(pid, &rc, 0);
            }

          if (deadlock || WIFSIGNALED(rc) || (WIFEXITED(rc) && WEXITSTATUS(rc) != 0))
            {
              gchar argbuf[64];

              if (!access(G_PROCESS_FAILURE_NOTIFICATION, R_OK | X_OK))
                {
                  const gchar *notify_reason;
                  pid_t npid = fork();
                  gint nrc;
                  switch (npid)
                    {
                    case -1:
                      g_process_message("Could not fork for external notification; reason='%s'", strerror(errno));
                      break;

                    case 0:
                      switch(fork())
                        {
                        case -1:
                          g_process_message("Could not fork for external notification; reason='%s'", strerror(errno));
                          exit(1);
                          break;
                        case 0:
                          if (deadlock)
                            {
                              notify_reason = "deadlock detected";
                              argbuf[0] = 0;
                            }
                          else
                            {
                              snprintf(argbuf, sizeof(argbuf), "%d", WIFSIGNALED(rc) ? WTERMSIG(rc) : WEXITSTATUS(rc));
                              if (WIFSIGNALED(rc))
                                notify_reason = "signalled";
                              else
                                notify_reason = "non-zero exit code";
                            }
                          execlp(G_PROCESS_FAILURE_NOTIFICATION, G_PROCESS_FAILURE_NOTIFICATION,
                                 SAFE_STRING(process_opts.name),
                                 SAFE_STRING(process_opts.chroot_dir),
                                 SAFE_STRING(process_opts.pidfile_dir),
                                 SAFE_STRING(process_opts.pidfile),
                                 SAFE_STRING(process_opts.cwd),
                                 SAFE_STRING(process_opts.caps),
                                 notify_reason,
                                 argbuf,
                                 (deadlock || !WIFSIGNALED(rc) || WTERMSIG(rc) != SIGKILL) ? "restarting" : "not-restarting",
                                 (gchar *) NULL);
                          g_process_message("Could not execute external notification; reason='%s'", strerror(errno));
                          break;

                        default:
                          exit(0);
                          break;
                        } /* child process */
                    default:
                      waitpid(npid, &nrc, 0);
                      break;
                    }
                }
              if (deadlock || !WIFSIGNALED(rc) || WTERMSIG(rc) != SIGKILL)
                {
                  g_process_message("Daemon exited due to a deadlock/signal/failure, restarting; exitcode='%d'", rc);
                  sleep(1);
                }
              else
                {
                  g_process_message("Daemon was killed, not restarting; exitcode='%d'", rc);
                  break;
                }
            }
          else
            {
              g_process_message("Daemon exited gracefully, not restarting; exitcode='%d'", rc);
              break;
            }
        }
      else
        {
          /* this is the daemon process, thus we should return to the caller of g_process_start() */
          /* shut down init_result_pipe read side */
          process_kind = G_PK_DAEMON;
          close(init_result_pipe[0]);
          init_result_pipe[0] = -1;

          /* update systemd socket activation pid */
          inherit_systemd_activation();

          memcpy(process_opts.argv_start, process_opts.argv_orig, process_opts.argv_env_len);
          return;
        }
    }
  exit(0);
}