/* This file is part of genrc Copyryght (C) 2018 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 void xpipe(int p[2]) { if (pipe(p)) { system_error(errno, "pipe"); exit(1); } } #define max(a,b) ((a) > (b) ? (a) : (b)) static void write_pid_file(pid_t pid) { if (genrc_create_pidfile) { FILE *fp; fp = fopen(genrc_create_pidfile, "w"); fprintf(fp, "%lu\n", (unsigned long)pid); fclose(fp); } } #define LOGBUFSIZE 1024 struct log_buffer { char buf[LOGBUFSIZE]; size_t pos; int prio; }; static void log_buffer_init(struct log_buffer *lb, int prio) { lb->buf[sizeof(lb->buf) - 1] = 0; lb->pos = 0; lb->prio = prio; } static void log_buffer_read(int fd, struct log_buffer *lb) { int rc; rc = read(fd, lb->buf + lb->pos, 1); if (rc == -1) { syslog(LOG_ERR, "read: %m"); } else if (rc == 1) { if (lb->pos == sizeof(lb->buf)-1) { syslog(lb->prio, "%s", lb->buf); lb->pos = 0; } else if (lb->buf[lb->pos] == '\n') { lb->buf[lb->pos] = 0; syslog(lb->prio, "%s", lb->buf); lb->pos = 0; } else lb->pos++; } } void wait_loop(pid_t child, int out, int err) { fd_set rdset; int nfd = (out > err ? out : err) + 1; struct log_buffer obuf, ebuf; openlog(genrc_program, LOG_PID, LOG_DAEMON); log_buffer_init(&obuf, LOG_INFO); log_buffer_init(&ebuf, LOG_ERR); while (1) { int rc, status; if (waitpid(child, &status, WNOHANG) == child) { if (genrc_create_pidfile) unlink(genrc_create_pidfile); if (WIFEXITED(status)) { syslog(LOG_INFO, "%s exited with status %d", genrc_program, WEXITSTATUS(status)); _exit(WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { char const *coremsg = ""; #ifdef WCOREDUMP if (WCOREDUMP(status)) coremsg = " (core dumped)"; #endif syslog(LOG_INFO, "%s terminated on signal %d%s", genrc_program, WTERMSIG(status), coremsg); } else if (WIFSTOPPED(status)) { syslog(LOG_INFO, "%s stopped on signal %d", genrc_program, WSTOPSIG(status)); } else { syslog(LOG_INFO, "%s terminated; status %d", genrc_program, rc); } break; } FD_ZERO(&rdset); FD_SET(out, &rdset); FD_SET(err, &rdset); rc = select(nfd, &rdset, NULL, NULL, NULL); if (rc == -1) { if (errno == EINTR) continue; syslog(LOG_CRIT, "select: %m"); syslog(LOG_CRIT, "killing %lu and terminating", (unsigned long) child); kill(child, SIGKILL); break; } if (FD_ISSET(out, &rdset)) { log_buffer_read(out, &obuf); } if (FD_ISSET(err, &rdset)) { log_buffer_read(err, &ebuf); } } _exit(1); } pid_t start_command(int p[]) { int errpipe[2], outpipe[2]; pid_t pid; xpipe(errpipe); xpipe(outpipe); pid = fork(); if (pid == -1) { system_error(errno, "pipe"); return -1; } if (pid == 0) { char *argv[] = { SHELL, "-c", NULL, NULL }; int i; runas(); close(0); close(1); close(2); open("/dev/null", O_RDWR); dup(outpipe[1]); dup(errpipe[1]); i = max(max(outpipe[0],outpipe[1]), max(errpipe[0],errpipe[1])); while (--i > 2) close(i); argv[2] = genrc_command; execvp(SHELL, argv); system_error(errno, "failed to exec %s", genrc_program); exit(127); } write_pid_file(pid); close(outpipe[1]); close(errpipe[1]); p[0] = outpipe[0]; p[1] = errpipe[0]; return pid; } int sentinel(void) { pid_t pid; int p[2]; /* Detach from the controlling terminal */ pid = fork(); if (pid == -1) { system_error(errno, "fork"); return -1; } if (pid) /* master */ return 0; /* Run as a session leader */ setsid(); pid = fork(); if (pid == -1) { system_error(errno, "fork"); exit(1); } if (pid) _exit(0); /* Grand-child */ pid = start_command(p); if (pid == -1) _exit(127); wait_loop(pid, p[0], p[1]); _exit(1); }