/* This file is part of genrc Copyryght (C) 2018, 2019 Sergey Poznyakoff License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. */ #include "genrc.h" #include #include #include static int expand_command(char **ret, const char *str, size_t len, char **argv, void *closure) { struct runcap rc; int res; rc.rc_argv = argv; rc.rc_cap[RUNCAP_STDERR].sc_size = 0; /* Disable stderr capturing */ res = runcap(&rc, 0); if (res == -1) { if (errno == ENOMEM) return WRDSE_NOSPACE; else { size_t size = 0; *ret = NULL; if (grecs_asprintf(ret, &size, "can't run %s: %s", argv[0], strerror(errno))) return WRDSE_NOSPACE; else return WRDSE_USERERR; } } if (rc.rc_cap[RUNCAP_STDOUT].sc_leng) { char *p = malloc(rc.rc_cap[RUNCAP_STDOUT].sc_leng + 1); size_t i; if (!p) { runcap_free(&rc); return WRDSE_NOSPACE; } *ret = p; for (i = 0; i < rc.rc_cap[RUNCAP_STDOUT].sc_leng; i++) { res = runcap_getc(&rc, RUNCAP_STDOUT, p); if (res == -1) { size_t size = 0; free(*ret); *ret = NULL; runcap_free(&rc); if (grecs_asprintf(ret, &size, "error capturing output " "from %s: %s", argv[0], strerror(errno))) return WRDSE_NOSPACE; else return WRDSE_USERERR; } if (res == 0) break; if (*p == '\n') *p = ' '; p++; } *p = 0; } runcap_free(&rc); return WRDSE_OK; } extern char **environ; /* Spawn genrc_command. If genrc_shell is not NULL, start it via "genrc_shell -c". Otherwise, do a plain exec(3). If P is not null, close all file descriptors, open /dev/null as standard input, use P[0] as standard output (sic!) and P[1] as standard error. The function never returns. */ void spawn(int *p) { char **argv; if (genrc_shell) { argv = xcalloc(4, sizeof(argv[0])); argv[0] = genrc_shell; argv[1] = "-c"; argv[2] = genrc_command; argv[3] = NULL; } else { struct wordsplit ws; int wsflags = WRDSF_ENV | WRDSF_ENOMEMABRT | WRDSF_SHOWERR | WRDSF_ERROR ; ws.ws_error = genrc_error; ws.ws_command = expand_command; ws.ws_env = (const char **)environ; if (wordsplit(genrc_command, &ws, wsflags)) exit(127); argv = ws.ws_wordv; } runas(); if (p) { int i; close(0); close(1); close(2); open("/dev/null", O_RDWR); dup(p[0]); dup(p[1]); i = sysconf(_SC_OPEN_MAX); while (--i > 2) close(i); } execvp(argv[0], argv); if (p) { openlog(genrc_program, LOG_PID, LOG_DAEMON); syslog(LOG_CRIT, "failed to exec: %m"); } else system_error(errno, "failed to exec %s", genrc_program); exit(127); } void report_exec_error(int rc, char const *program) { if (WIFEXITED(rc)) { if (WEXITSTATUS(rc)) { genrc_error("%s exited with status %d", program, WEXITSTATUS(rc)); } } else if (WIFSIGNALED(rc)) { char const *coremsg = ""; #ifdef WCOREDUMP if (WCOREDUMP(rc)) coremsg = " (core dumped)"; #endif genrc_error("%s terminated on signal %d%s", program, WTERMSIG(rc), coremsg); } else if (WIFSTOPPED(rc)) { genrc_error("%s stopped on signal %d", program, WSTOPSIG(rc)); } else { genrc_error("%s terminated with unrecognized status: %d", program, rc); } } typedef void (*SIGHANDLER)(int); void sigchld(int sig) { } int timedwaitpid(pid_t pid, int *status) { struct timeval now, stoptime, ttw; int rc = -1; struct sigaction act, oldact; act.sa_handler = sigchld; act.sa_flags = 0; sigemptyset(&act.sa_mask); sigaction(SIGCHLD, &act, &oldact); gettimeofday(&stoptime, NULL); stoptime.tv_sec += genrc_timeout; while (1) { pid_t p; p = waitpid(pid, status, WNOHANG); if (p == pid) { rc = 0; break; } if (p < 0 && errno != EINTR) { system_error(errno, "waitpid"); break; } gettimeofday(&now, NULL); if (timercmp(&now, &stoptime, >=)) break; timersub(&stoptime, &now, &ttw); if (select(0, NULL, NULL, NULL, &ttw) < 0) { if (errno != EINTR) { system_error(errno, "select"); break; } } } sigaction(SIGCHLD, &oldact, NULL); if (rc) { kill(pid, SIGKILL); } return rc; } int com_start(void) { pid_t pid; int status; PIDLIST pids; char *p; pidlist_init(&pids); if (get_pid_list(genrc_pid_closure, &pids) == 0) { int running = pids.pidc > 0; pidlist_free(&pids); if (running) { genrc_error("%s is already running", genrc_program); return 1; } } if (genrc_verbose) printf("Starting %s\n", genrc_program); if ((p = getenv("GENRC_SENTINEL")) && *p == '1') return sentinel(); pid = fork(); if (pid == -1) { system_error(errno, "fork"); return 1; } if (pid == 0) { spawn(NULL); } if (timedwaitpid(pid, &status)) { genrc_error("timed out waiting for %s to return", genrc_program); return 1; } if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) { report_exec_error(status, genrc_program); return 1; } return 0; }