/** * 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)); } }
/** * 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; }
/** * 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)); } }
/** * 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; }
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; } }
/** * 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; }
/** * 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)); }
/** * 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)); } }
/** * 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)); } }
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 }
/** * 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; }
/** * 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)); } }
/** * 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(); }
/** * 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); }