1bd06a3ecSMike Barcroft /*- 21de7b4b8SPedro F. Giffuni * SPDX-License-Identifier: BSD-3-Clause 31de7b4b8SPedro F. Giffuni * 4bd06a3ecSMike Barcroft * Copyright (c) 1999 Berkeley Software Design, Inc. All rights reserved. 5bd06a3ecSMike Barcroft * 6bd06a3ecSMike Barcroft * Redistribution and use in source and binary forms, with or without 7bd06a3ecSMike Barcroft * modification, are permitted provided that the following conditions 8bd06a3ecSMike Barcroft * are met: 9bd06a3ecSMike Barcroft * 1. Redistributions of source code must retain the above copyright 10bd06a3ecSMike Barcroft * notice, this list of conditions and the following disclaimer. 11bd06a3ecSMike Barcroft * 2. Redistributions in binary form must reproduce the above copyright 12bd06a3ecSMike Barcroft * notice, this list of conditions and the following disclaimer in the 13bd06a3ecSMike Barcroft * documentation and/or other materials provided with the distribution. 14bd06a3ecSMike Barcroft * 3. Berkeley Software Design Inc's name may not be used to endorse or 15bd06a3ecSMike Barcroft * promote products derived from this software without specific prior 16bd06a3ecSMike Barcroft * written permission. 17bd06a3ecSMike Barcroft * 18bd06a3ecSMike Barcroft * THIS SOFTWARE IS PROVIDED BY BERKELEY SOFTWARE DESIGN INC ``AS IS'' AND 19bd06a3ecSMike Barcroft * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20bd06a3ecSMike Barcroft * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21bd06a3ecSMike Barcroft * ARE DISCLAIMED. IN NO EVENT SHALL BERKELEY SOFTWARE DESIGN INC BE LIABLE 22bd06a3ecSMike Barcroft * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23bd06a3ecSMike Barcroft * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24bd06a3ecSMike Barcroft * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25bd06a3ecSMike Barcroft * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26bd06a3ecSMike Barcroft * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27bd06a3ecSMike Barcroft * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28bd06a3ecSMike Barcroft * SUCH DAMAGE. 29bd06a3ecSMike Barcroft * 30bd06a3ecSMike Barcroft * From BSDI: daemon.c,v 1.2 1996/08/15 01:11:09 jch Exp 31bd06a3ecSMike Barcroft */ 32bd06a3ecSMike Barcroft 3354ede02dSPhilippe Charnier #include <sys/cdefs.h> 3454ede02dSPhilippe Charnier __FBSDID("$FreeBSD$"); 3554ede02dSPhilippe Charnier 36c6262cb6SPawel Jakub Dawidek #include <sys/param.h> 3753c49998SMikolaj Golub #include <sys/mman.h> 382ad43027SMikolaj Golub #include <sys/wait.h> 39bd06a3ecSMike Barcroft 4053d49b37SJilles Tjoelker #include <fcntl.h> 41bd06a3ecSMike Barcroft #include <err.h> 42846be7bdSPoul-Henning Kamp #include <errno.h> 430a402ad2SIhor Antonov #include <getopt.h> 44c6262cb6SPawel Jakub Dawidek #include <libutil.h> 45e6d4b388STom Rhodes #include <login_cap.h> 466b3ad1d7SMaxim Sobolev #include <paths.h> 47195fc497SMikolaj Golub #include <pwd.h> 48195fc497SMikolaj Golub #include <signal.h> 49bd06a3ecSMike Barcroft #include <stdio.h> 50bd06a3ecSMike Barcroft #include <stdlib.h> 51bd06a3ecSMike Barcroft #include <unistd.h> 5253d49b37SJilles Tjoelker #include <string.h> 5353d49b37SJilles Tjoelker #include <strings.h> 5453d49b37SJilles Tjoelker #define SYSLOG_NAMES 5553d49b37SJilles Tjoelker #include <syslog.h> 5653d49b37SJilles Tjoelker #include <time.h> 5753d49b37SJilles Tjoelker #include <assert.h> 58bd06a3ecSMike Barcroft 5953d49b37SJilles Tjoelker #define LBUF_SIZE 4096 6053d49b37SJilles Tjoelker 6153d49b37SJilles Tjoelker struct log_params { 6253d49b37SJilles Tjoelker int dosyslog; 6353d49b37SJilles Tjoelker int logpri; 6453d49b37SJilles Tjoelker int noclose; 6553d49b37SJilles Tjoelker int outfd; 664cd407ecSMaxim Sobolev const char *outfn; 6753d49b37SJilles Tjoelker }; 6853d49b37SJilles Tjoelker 69e6d4b388STom Rhodes static void restrict_process(const char *); 7053d49b37SJilles Tjoelker static void handle_term(int); 7153d49b37SJilles Tjoelker static void handle_chld(int); 724cd407ecSMaxim Sobolev static void handle_hup(int); 734cd407ecSMaxim Sobolev static int open_log(const char *); 744cd407ecSMaxim Sobolev static void reopen_log(struct log_params *); 7553d49b37SJilles Tjoelker static int listen_child(int, struct log_params *); 7653d49b37SJilles Tjoelker static int get_log_mapping(const char *, const CODE *); 7753d49b37SJilles Tjoelker static void open_pid_files(const char *, const char *, struct pidfh **, 7853d49b37SJilles Tjoelker struct pidfh **); 7953d49b37SJilles Tjoelker static void do_output(const unsigned char *, size_t, struct log_params *); 8053d49b37SJilles Tjoelker static void daemon_sleep(time_t, long); 81bd06a3ecSMike Barcroft 824cd407ecSMaxim Sobolev static volatile sig_atomic_t terminate = 0, child_gone = 0, pid = 0, 834cd407ecSMaxim Sobolev do_log_reopen = 0; 8453d49b37SJilles Tjoelker 850a402ad2SIhor Antonov static const char shortopts[] = "+cfHSp:P:ru:o:s:l:t:m:R:T:h"; 860a402ad2SIhor Antonov 870a402ad2SIhor Antonov static const struct option longopts[] = { 880a402ad2SIhor Antonov { "change-dir", no_argument, NULL, 'c' }, 890a402ad2SIhor Antonov { "close-fds", no_argument, NULL, 'f' }, 900a402ad2SIhor Antonov { "sighup", no_argument, NULL, 'H' }, 910a402ad2SIhor Antonov { "syslog", no_argument, NULL, 'S' }, 920a402ad2SIhor Antonov { "output-file", required_argument, NULL, 'o' }, 930a402ad2SIhor Antonov { "output-mask", required_argument, NULL, 'm' }, 940a402ad2SIhor Antonov { "child-pidfile", required_argument, NULL, 'p' }, 950a402ad2SIhor Antonov { "supervisor-pidfile", required_argument, NULL, 'P' }, 960a402ad2SIhor Antonov { "restart", no_argument, NULL, 'r' }, 970a402ad2SIhor Antonov { "restart-delay", required_argument, NULL, 'R' }, 980a402ad2SIhor Antonov { "title", required_argument, NULL, 't' }, 990a402ad2SIhor Antonov { "user", required_argument, NULL, 'u' }, 1000a402ad2SIhor Antonov { "syslog-priority", required_argument, NULL, 's' }, 1010a402ad2SIhor Antonov { "syslog-facility", required_argument, NULL, 'l' }, 1020a402ad2SIhor Antonov { "syslog-tag", required_argument, NULL, 'T' }, 1030a402ad2SIhor Antonov { "help", no_argument, NULL, 'h' }, 1040a402ad2SIhor Antonov { NULL, 0, NULL, 0 } 1050a402ad2SIhor Antonov }; 1060a402ad2SIhor Antonov 1070a402ad2SIhor Antonov static _Noreturn void 1080a402ad2SIhor Antonov usage(int exitcode) 1090a402ad2SIhor Antonov { 1100a402ad2SIhor Antonov (void)fprintf(stderr, 1110a402ad2SIhor Antonov "usage: daemon [-cfHrS] [-p child_pidfile] [-P supervisor_pidfile]\n" 1120a402ad2SIhor Antonov " [-u user] [-o output_file] [-t title]\n" 1130a402ad2SIhor Antonov " [-l syslog_facility] [-s syslog_priority]\n" 1140a402ad2SIhor Antonov " [-T syslog_tag] [-m output_mask] [-R restart_delay_secs]\n" 1150a402ad2SIhor Antonov "command arguments ...\n"); 1160a402ad2SIhor Antonov 1170a402ad2SIhor Antonov (void)fprintf(stderr, 1180a402ad2SIhor Antonov " --change-dir -c Change the current working directory to root\n" 1190a402ad2SIhor Antonov " --close-fds -f Set stdin, stdout, stderr to /dev/null\n" 1200a402ad2SIhor Antonov " --sighup -H Close and re-open output file on SIGHUP\n" 1210a402ad2SIhor Antonov " --syslog -S Send output to syslog\n" 1220a402ad2SIhor Antonov " --output-file -o <file> Append output of the child process to file\n" 1230a402ad2SIhor Antonov " --output-mask -m <mask> What to send to syslog/file\n" 1240a402ad2SIhor Antonov " 1=stdout, 2=stderr, 3=both\n" 1250a402ad2SIhor Antonov " --child-pidfile -p <file> Write PID of the child process to file\n" 1260a402ad2SIhor Antonov " --supervisor-pidfile -P <file> Write PID of the supervisor process to file\n" 1270a402ad2SIhor Antonov " --restart -r Restart child if it terminates (1 sec delay)\n" 1280a402ad2SIhor Antonov " --restart-delay -R <N> Restart child if it terminates after N sec\n" 1290a402ad2SIhor Antonov " --title -t <title> Set the title of the supervisor process\n" 1300a402ad2SIhor Antonov " --user -u <user> Drop privileges, run as given user\n" 1310a402ad2SIhor Antonov " --syslog-priority -s <prio> Set syslog priority\n" 1320a402ad2SIhor Antonov " --syslog-facility -l <flty> Set syslog facility\n" 1330a402ad2SIhor Antonov " --syslog-tag -T <tag> Set syslog tag\n" 1340a402ad2SIhor Antonov " --help -h Show this help\n"); 1350a402ad2SIhor Antonov 1360a402ad2SIhor Antonov exit(exitcode); 1370a402ad2SIhor Antonov } 1380a402ad2SIhor Antonov 139bd06a3ecSMike Barcroft int 140bd06a3ecSMike Barcroft main(int argc, char *argv[]) 141bd06a3ecSMike Barcroft { 14253d49b37SJilles Tjoelker const char *pidfile, *ppidfile, *title, *user, *outfn, *logtag; 14353d49b37SJilles Tjoelker int ch, nochdir, noclose, restart, dosyslog, child_eof; 14453d49b37SJilles Tjoelker sigset_t mask_susp, mask_orig, mask_read, mask_term; 14553d49b37SJilles Tjoelker struct log_params logpar; 14653d49b37SJilles Tjoelker int pfd[2] = { -1, -1 }, outfd = -1; 1474cd407ecSMaxim Sobolev int stdmask, logpri, logfac, log_reopen; 14832b17786SJohn-Mark Gurney struct pidfh *ppfh, *pfh; 14953d49b37SJilles Tjoelker char *p; 150bd06a3ecSMike Barcroft 15153d49b37SJilles Tjoelker memset(&logpar, 0, sizeof(logpar)); 15253d49b37SJilles Tjoelker stdmask = STDOUT_FILENO | STDERR_FILENO; 15353d49b37SJilles Tjoelker ppidfile = pidfile = user = NULL; 154bd06a3ecSMike Barcroft nochdir = noclose = 1; 15553d49b37SJilles Tjoelker logpri = LOG_NOTICE; 15653d49b37SJilles Tjoelker logfac = LOG_DAEMON; 15753d49b37SJilles Tjoelker logtag = "daemon"; 158b6193c24SMikolaj Golub restart = 0; 15953d49b37SJilles Tjoelker dosyslog = 0; 1604cd407ecSMaxim Sobolev log_reopen = 0; 16153d49b37SJilles Tjoelker outfn = NULL; 16253d49b37SJilles Tjoelker title = NULL; 1630a402ad2SIhor Antonov while ((ch = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) { 164bd06a3ecSMike Barcroft switch (ch) { 165bd06a3ecSMike Barcroft case 'c': 166bd06a3ecSMike Barcroft nochdir = 0; 167bd06a3ecSMike Barcroft break; 168bd06a3ecSMike Barcroft case 'f': 169bd06a3ecSMike Barcroft noclose = 0; 170bd06a3ecSMike Barcroft break; 1714cd407ecSMaxim Sobolev case 'H': 1724cd407ecSMaxim Sobolev log_reopen = 1; 1734cd407ecSMaxim Sobolev break; 17453d49b37SJilles Tjoelker case 'l': 17553d49b37SJilles Tjoelker logfac = get_log_mapping(optarg, facilitynames); 176*6b4ef4b1SIhor Antonov if (logfac == -1) { 17753d49b37SJilles Tjoelker errx(5, "unrecognized syslog facility"); 178*6b4ef4b1SIhor Antonov } 17953d49b37SJilles Tjoelker dosyslog = 1; 18053d49b37SJilles Tjoelker break; 18153d49b37SJilles Tjoelker case 'm': 18253d49b37SJilles Tjoelker stdmask = strtol(optarg, &p, 10); 183*6b4ef4b1SIhor Antonov if (p == optarg || stdmask < 0 || stdmask > 3) { 18453d49b37SJilles Tjoelker errx(6, "unrecognized listening mask"); 185*6b4ef4b1SIhor Antonov } 18653d49b37SJilles Tjoelker break; 18753d49b37SJilles Tjoelker case 'o': 18853d49b37SJilles Tjoelker outfn = optarg; 18953d49b37SJilles Tjoelker break; 190846be7bdSPoul-Henning Kamp case 'p': 191846be7bdSPoul-Henning Kamp pidfile = optarg; 192846be7bdSPoul-Henning Kamp break; 19332b17786SJohn-Mark Gurney case 'P': 19432b17786SJohn-Mark Gurney ppidfile = optarg; 19532b17786SJohn-Mark Gurney break; 196b6193c24SMikolaj Golub case 'r': 197b6193c24SMikolaj Golub restart = 1; 198b6193c24SMikolaj Golub break; 19937820b87SIan Lepore case 'R': 20037820b87SIan Lepore restart = strtol(optarg, &p, 0); 201*6b4ef4b1SIhor Antonov if (p == optarg || restart < 1) { 20237820b87SIan Lepore errx(6, "invalid restart delay"); 203*6b4ef4b1SIhor Antonov } 20437820b87SIan Lepore break; 20553d49b37SJilles Tjoelker case 's': 20653d49b37SJilles Tjoelker logpri = get_log_mapping(optarg, prioritynames); 207*6b4ef4b1SIhor Antonov if (logpri == -1) { 20853d49b37SJilles Tjoelker errx(4, "unrecognized syslog priority"); 209*6b4ef4b1SIhor Antonov } 21053d49b37SJilles Tjoelker dosyslog = 1; 21153d49b37SJilles Tjoelker break; 21253d49b37SJilles Tjoelker case 'S': 21353d49b37SJilles Tjoelker dosyslog = 1; 21453d49b37SJilles Tjoelker break; 215112bfcf5SConrad Meyer case 't': 216112bfcf5SConrad Meyer title = optarg; 217112bfcf5SConrad Meyer break; 21853d49b37SJilles Tjoelker case 'T': 21953d49b37SJilles Tjoelker logtag = optarg; 22053d49b37SJilles Tjoelker dosyslog = 1; 22153d49b37SJilles Tjoelker break; 222e6d4b388STom Rhodes case 'u': 223e6d4b388STom Rhodes user = optarg; 224e6d4b388STom Rhodes break; 2250a402ad2SIhor Antonov case 'h': 2260a402ad2SIhor Antonov usage(0); 2270a402ad2SIhor Antonov __builtin_unreachable(); 228bd06a3ecSMike Barcroft default: 2290a402ad2SIhor Antonov usage(1); 230bd06a3ecSMike Barcroft } 231bd06a3ecSMike Barcroft } 232bd06a3ecSMike Barcroft argc -= optind; 233bd06a3ecSMike Barcroft argv += optind; 234bd06a3ecSMike Barcroft 235*6b4ef4b1SIhor Antonov if (argc == 0) { 2360a402ad2SIhor Antonov usage(1); 237*6b4ef4b1SIhor Antonov } 23812d7249eSTom Rhodes 239*6b4ef4b1SIhor Antonov if (!title) { 24053d49b37SJilles Tjoelker title = argv[0]; 241*6b4ef4b1SIhor Antonov } 24253d49b37SJilles Tjoelker 24353d49b37SJilles Tjoelker if (outfn) { 2444cd407ecSMaxim Sobolev outfd = open_log(outfn); 245*6b4ef4b1SIhor Antonov if (outfd == -1) { 24653d49b37SJilles Tjoelker err(7, "open"); 24753d49b37SJilles Tjoelker } 248*6b4ef4b1SIhor Antonov } 24953d49b37SJilles Tjoelker 250*6b4ef4b1SIhor Antonov if (dosyslog) { 25153d49b37SJilles Tjoelker openlog(logtag, LOG_PID | LOG_NDELAY, logfac); 252*6b4ef4b1SIhor Antonov } 25353d49b37SJilles Tjoelker 25432b17786SJohn-Mark Gurney ppfh = pfh = NULL; 255846be7bdSPoul-Henning Kamp /* 256846be7bdSPoul-Henning Kamp * Try to open the pidfile before calling daemon(3), 257846be7bdSPoul-Henning Kamp * to be able to report the error intelligently 258846be7bdSPoul-Henning Kamp */ 25953d49b37SJilles Tjoelker open_pid_files(pidfile, ppidfile, &pfh, &ppfh); 2609da0ef13SMikolaj Golub if (daemon(nochdir, noclose) == -1) { 2619da0ef13SMikolaj Golub warn("daemon"); 2629da0ef13SMikolaj Golub goto exit; 2639da0ef13SMikolaj Golub } 2649da0ef13SMikolaj Golub /* Write out parent pidfile if needed. */ 2659da0ef13SMikolaj Golub pidfile_write(ppfh); 266195fc497SMikolaj Golub /* 267b6193c24SMikolaj Golub * If the pidfile or restart option is specified the daemon 268b6193c24SMikolaj Golub * executes the command in a forked process and wait on child 269b6193c24SMikolaj Golub * exit to remove the pidfile or restart the command. Normally 270b6193c24SMikolaj Golub * we don't want the monitoring daemon to be terminated 271b6193c24SMikolaj Golub * leaving the running process and the stale pidfile, so we 272b6193c24SMikolaj Golub * catch SIGTERM and forward it to the children expecting to 27353d49b37SJilles Tjoelker * get SIGCHLD eventually. We also must fork() to obtain a 27453d49b37SJilles Tjoelker * readable pipe with the child for writing to a log file 27553d49b37SJilles Tjoelker * and syslog. 276195fc497SMikolaj Golub */ 277195fc497SMikolaj Golub pid = -1; 27853d49b37SJilles Tjoelker if (pidfile || ppidfile || restart || outfd != -1 || dosyslog) { 2794cd407ecSMaxim Sobolev struct sigaction act_term, act_chld, act_hup; 28053d49b37SJilles Tjoelker 28153d49b37SJilles Tjoelker /* Avoid PID racing with SIGCHLD and SIGTERM. */ 28253d49b37SJilles Tjoelker memset(&act_term, 0, sizeof(act_term)); 28353d49b37SJilles Tjoelker act_term.sa_handler = handle_term; 28453d49b37SJilles Tjoelker sigemptyset(&act_term.sa_mask); 28553d49b37SJilles Tjoelker sigaddset(&act_term.sa_mask, SIGCHLD); 28653d49b37SJilles Tjoelker 28753d49b37SJilles Tjoelker memset(&act_chld, 0, sizeof(act_chld)); 28853d49b37SJilles Tjoelker act_chld.sa_handler = handle_chld; 28953d49b37SJilles Tjoelker sigemptyset(&act_chld.sa_mask); 29053d49b37SJilles Tjoelker sigaddset(&act_chld.sa_mask, SIGTERM); 29153d49b37SJilles Tjoelker 2924cd407ecSMaxim Sobolev memset(&act_hup, 0, sizeof(act_hup)); 2934cd407ecSMaxim Sobolev act_hup.sa_handler = handle_hup; 2944cd407ecSMaxim Sobolev sigemptyset(&act_hup.sa_mask); 2954cd407ecSMaxim Sobolev 29653d49b37SJilles Tjoelker /* Block these when avoiding racing before sigsuspend(). */ 29753d49b37SJilles Tjoelker sigemptyset(&mask_susp); 29853d49b37SJilles Tjoelker sigaddset(&mask_susp, SIGTERM); 29953d49b37SJilles Tjoelker sigaddset(&mask_susp, SIGCHLD); 30053d49b37SJilles Tjoelker /* Block SIGTERM when we lack a valid child PID. */ 30153d49b37SJilles Tjoelker sigemptyset(&mask_term); 30253d49b37SJilles Tjoelker sigaddset(&mask_term, SIGTERM); 3032ad43027SMikolaj Golub /* 30453d49b37SJilles Tjoelker * When reading, we wish to avoid SIGCHLD. SIGTERM 30553d49b37SJilles Tjoelker * has to be caught, otherwise we'll be stuck until 30653d49b37SJilles Tjoelker * the read() returns - if it returns. 307195fc497SMikolaj Golub */ 30853d49b37SJilles Tjoelker sigemptyset(&mask_read); 30953d49b37SJilles Tjoelker sigaddset(&mask_read, SIGCHLD); 31053d49b37SJilles Tjoelker /* Block SIGTERM to avoid racing until we have forked. */ 31153d49b37SJilles Tjoelker if (sigprocmask(SIG_BLOCK, &mask_term, &mask_orig)) { 3129da0ef13SMikolaj Golub warn("sigprocmask"); 3139da0ef13SMikolaj Golub goto exit; 3149da0ef13SMikolaj Golub } 31553d49b37SJilles Tjoelker if (sigaction(SIGTERM, &act_term, NULL) == -1) { 31653d49b37SJilles Tjoelker warn("sigaction"); 31753d49b37SJilles Tjoelker goto exit; 31853d49b37SJilles Tjoelker } 31953d49b37SJilles Tjoelker if (sigaction(SIGCHLD, &act_chld, NULL) == -1) { 32053d49b37SJilles Tjoelker warn("sigaction"); 32153d49b37SJilles Tjoelker goto exit; 32253d49b37SJilles Tjoelker } 32353c49998SMikolaj Golub /* 32453c49998SMikolaj Golub * Try to protect against pageout kill. Ignore the 32553c49998SMikolaj Golub * error, madvise(2) will fail only if a process does 32653c49998SMikolaj Golub * not have superuser privileges. 32753c49998SMikolaj Golub */ 32853c49998SMikolaj Golub (void)madvise(NULL, 0, MADV_PROTECT); 32953d49b37SJilles Tjoelker logpar.outfd = outfd; 33053d49b37SJilles Tjoelker logpar.dosyslog = dosyslog; 33153d49b37SJilles Tjoelker logpar.logpri = logpri; 33253d49b37SJilles Tjoelker logpar.noclose = noclose; 3334cd407ecSMaxim Sobolev logpar.outfn = outfn; 3344cd407ecSMaxim Sobolev if (log_reopen && outfd >= 0 && 3354cd407ecSMaxim Sobolev sigaction(SIGHUP, &act_hup, NULL) == -1) { 3364cd407ecSMaxim Sobolev warn("sigaction"); 3374cd407ecSMaxim Sobolev goto exit; 3384cd407ecSMaxim Sobolev } 339b6193c24SMikolaj Golub restart: 340*6b4ef4b1SIhor Antonov if (pipe(pfd)) { 34153d49b37SJilles Tjoelker err(1, "pipe"); 342*6b4ef4b1SIhor Antonov } 343195fc497SMikolaj Golub /* 34453d49b37SJilles Tjoelker * Spawn a child to exec the command. 3452ad43027SMikolaj Golub */ 34653d49b37SJilles Tjoelker child_gone = 0; 3472ad43027SMikolaj Golub pid = fork(); 3482ad43027SMikolaj Golub if (pid == -1) { 3499da0ef13SMikolaj Golub warn("fork"); 3509da0ef13SMikolaj Golub goto exit; 35153d49b37SJilles Tjoelker } else if (pid > 0) { 35253d49b37SJilles Tjoelker /* 35353d49b37SJilles Tjoelker * Unblock SIGTERM after we know we have a valid 35453d49b37SJilles Tjoelker * child PID to signal. 35553d49b37SJilles Tjoelker */ 35653d49b37SJilles Tjoelker if (sigprocmask(SIG_UNBLOCK, &mask_term, NULL)) { 35753d49b37SJilles Tjoelker warn("sigprocmask"); 35853d49b37SJilles Tjoelker goto exit; 35953d49b37SJilles Tjoelker } 36053d49b37SJilles Tjoelker close(pfd[1]); 36153d49b37SJilles Tjoelker pfd[1] = -1; 3622ad43027SMikolaj Golub } 3632ad43027SMikolaj Golub } 364195fc497SMikolaj Golub if (pid <= 0) { 3652ad43027SMikolaj Golub /* Now that we are the child, write out the pid. */ 366c6262cb6SPawel Jakub Dawidek pidfile_write(pfh); 367846be7bdSPoul-Henning Kamp 368*6b4ef4b1SIhor Antonov if (user != NULL) { 3692ad43027SMikolaj Golub restrict_process(user); 370*6b4ef4b1SIhor Antonov } 37153d49b37SJilles Tjoelker /* 37253d49b37SJilles Tjoelker * When forking, the child gets the original sigmask, 37353d49b37SJilles Tjoelker * and dup'd pipes. 37453d49b37SJilles Tjoelker */ 37553d49b37SJilles Tjoelker if (pid == 0) { 37653d49b37SJilles Tjoelker close(pfd[0]); 377*6b4ef4b1SIhor Antonov if (sigprocmask(SIG_SETMASK, &mask_orig, NULL)) { 37853d49b37SJilles Tjoelker err(1, "sigprogmask"); 379*6b4ef4b1SIhor Antonov } 38053d49b37SJilles Tjoelker if (stdmask & STDERR_FILENO) { 381*6b4ef4b1SIhor Antonov if (dup2(pfd[1], STDERR_FILENO) == -1) { 38253d49b37SJilles Tjoelker err(1, "dup2"); 38353d49b37SJilles Tjoelker } 384*6b4ef4b1SIhor Antonov } 38553d49b37SJilles Tjoelker if (stdmask & STDOUT_FILENO) { 386*6b4ef4b1SIhor Antonov if (dup2(pfd[1], STDOUT_FILENO) == -1) { 38753d49b37SJilles Tjoelker err(1, "dup2"); 38853d49b37SJilles Tjoelker } 389*6b4ef4b1SIhor Antonov } 39053d49b37SJilles Tjoelker if (pfd[1] != STDERR_FILENO && 391*6b4ef4b1SIhor Antonov pfd[1] != STDOUT_FILENO) { 39253d49b37SJilles Tjoelker close(pfd[1]); 39353d49b37SJilles Tjoelker } 394*6b4ef4b1SIhor Antonov } 395bd06a3ecSMike Barcroft execvp(argv[0], argv); 396846be7bdSPoul-Henning Kamp /* 3972ad43027SMikolaj Golub * execvp() failed -- report the error. The child is 3982ad43027SMikolaj Golub * now running, so the exit status doesn't matter. 399846be7bdSPoul-Henning Kamp */ 4002ad43027SMikolaj Golub err(1, "%s", argv[0]); 4012ad43027SMikolaj Golub } 40253d49b37SJilles Tjoelker setproctitle("%s[%d]", title, (int)pid); 40353d49b37SJilles Tjoelker /* 40453d49b37SJilles Tjoelker * As we have closed the write end of pipe for parent process, 40553d49b37SJilles Tjoelker * we might detect the child's exit by reading EOF. The child 40653d49b37SJilles Tjoelker * might have closed its stdout and stderr, so we must wait for 40753d49b37SJilles Tjoelker * the SIGCHLD to ensure that the process is actually gone. 40853d49b37SJilles Tjoelker */ 40953d49b37SJilles Tjoelker child_eof = 0; 41053d49b37SJilles Tjoelker for (;;) { 41153d49b37SJilles Tjoelker /* 41253d49b37SJilles Tjoelker * We block SIGCHLD when listening, but SIGTERM we accept 41353d49b37SJilles Tjoelker * so the read() won't block if we wish to depart. 41453d49b37SJilles Tjoelker * 41553d49b37SJilles Tjoelker * Upon receiving SIGTERM, we have several options after 41653d49b37SJilles Tjoelker * sending the SIGTERM to our child: 41753d49b37SJilles Tjoelker * - read until EOF 41853d49b37SJilles Tjoelker * - read until EOF but only for a while 41953d49b37SJilles Tjoelker * - bail immediately 42053d49b37SJilles Tjoelker * 42153d49b37SJilles Tjoelker * We go for the third, as otherwise we have no guarantee 42253d49b37SJilles Tjoelker * that we won't block indefinitely if the child refuses 42353d49b37SJilles Tjoelker * to depart. To handle the second option, a different 42453d49b37SJilles Tjoelker * approach would be needed (procctl()?) 42553d49b37SJilles Tjoelker */ 42653d49b37SJilles Tjoelker if (child_gone && child_eof) { 42753d49b37SJilles Tjoelker break; 42853d49b37SJilles Tjoelker } else if (terminate) { 42953d49b37SJilles Tjoelker goto exit; 43053d49b37SJilles Tjoelker } else if (!child_eof) { 43153d49b37SJilles Tjoelker if (sigprocmask(SIG_BLOCK, &mask_read, NULL)) { 43253d49b37SJilles Tjoelker warn("sigprocmask"); 43353d49b37SJilles Tjoelker goto exit; 43453d49b37SJilles Tjoelker } 43553d49b37SJilles Tjoelker child_eof = !listen_child(pfd[0], &logpar); 43653d49b37SJilles Tjoelker if (sigprocmask(SIG_UNBLOCK, &mask_read, NULL)) { 43753d49b37SJilles Tjoelker warn("sigprocmask"); 43853d49b37SJilles Tjoelker goto exit; 43953d49b37SJilles Tjoelker } 44053d49b37SJilles Tjoelker } else { 44153d49b37SJilles Tjoelker if (sigprocmask(SIG_BLOCK, &mask_susp, NULL)) { 44253d49b37SJilles Tjoelker warn("sigprocmask"); 44353d49b37SJilles Tjoelker goto exit; 44453d49b37SJilles Tjoelker } 44553d49b37SJilles Tjoelker while (!terminate && !child_gone) 44653d49b37SJilles Tjoelker sigsuspend(&mask_orig); 44753d49b37SJilles Tjoelker if (sigprocmask(SIG_UNBLOCK, &mask_susp, NULL)) { 44853d49b37SJilles Tjoelker warn("sigprocmask"); 44953d49b37SJilles Tjoelker goto exit; 45053d49b37SJilles Tjoelker } 45153d49b37SJilles Tjoelker } 45253d49b37SJilles Tjoelker } 453*6b4ef4b1SIhor Antonov if (restart && !terminate) { 45409a3675dSConrad Meyer daemon_sleep(restart, 0); 455*6b4ef4b1SIhor Antonov } 45653d49b37SJilles Tjoelker if (sigprocmask(SIG_BLOCK, &mask_term, NULL)) { 45753d49b37SJilles Tjoelker warn("sigprocmask"); 45853d49b37SJilles Tjoelker goto exit; 45953d49b37SJilles Tjoelker } 46053d49b37SJilles Tjoelker if (restart && !terminate) { 46153d49b37SJilles Tjoelker close(pfd[0]); 46253d49b37SJilles Tjoelker pfd[0] = -1; 463b6193c24SMikolaj Golub goto restart; 464b6193c24SMikolaj Golub } 4659da0ef13SMikolaj Golub exit: 46653d49b37SJilles Tjoelker close(outfd); 46753d49b37SJilles Tjoelker close(pfd[0]); 46853d49b37SJilles Tjoelker close(pfd[1]); 469*6b4ef4b1SIhor Antonov if (dosyslog) { 47053d49b37SJilles Tjoelker closelog(); 471*6b4ef4b1SIhor Antonov } 472c6262cb6SPawel Jakub Dawidek pidfile_remove(pfh); 47332b17786SJohn-Mark Gurney pidfile_remove(ppfh); 4749da0ef13SMikolaj Golub exit(1); /* If daemon(3) succeeded exit status does not matter. */ 475bd06a3ecSMike Barcroft } 476bd06a3ecSMike Barcroft 477bd06a3ecSMike Barcroft static void 47853d49b37SJilles Tjoelker daemon_sleep(time_t secs, long nsecs) 479195fc497SMikolaj Golub { 48053d49b37SJilles Tjoelker struct timespec ts = { secs, nsecs }; 48109a3675dSConrad Meyer 48209a3675dSConrad Meyer while (!terminate && nanosleep(&ts, &ts) == -1) { 483*6b4ef4b1SIhor Antonov if (errno != EINTR) { 48453d49b37SJilles Tjoelker err(1, "nanosleep"); 48553d49b37SJilles Tjoelker } 48653d49b37SJilles Tjoelker } 487*6b4ef4b1SIhor Antonov } 48853d49b37SJilles Tjoelker 48953d49b37SJilles Tjoelker static void 49053d49b37SJilles Tjoelker open_pid_files(const char *pidfile, const char *ppidfile, 49153d49b37SJilles Tjoelker struct pidfh **pfh, struct pidfh **ppfh) 49253d49b37SJilles Tjoelker { 49353d49b37SJilles Tjoelker pid_t fpid; 49453d49b37SJilles Tjoelker int serrno; 49553d49b37SJilles Tjoelker 49653d49b37SJilles Tjoelker if (pidfile) { 49753d49b37SJilles Tjoelker *pfh = pidfile_open(pidfile, 0600, &fpid); 49853d49b37SJilles Tjoelker if (*pfh == NULL) { 49953d49b37SJilles Tjoelker if (errno == EEXIST) { 50053d49b37SJilles Tjoelker errx(3, "process already running, pid: %d", 50153d49b37SJilles Tjoelker fpid); 50253d49b37SJilles Tjoelker } 50353d49b37SJilles Tjoelker err(2, "pidfile ``%s''", pidfile); 50453d49b37SJilles Tjoelker } 50553d49b37SJilles Tjoelker } 50653d49b37SJilles Tjoelker /* Do the same for the actual daemon process. */ 50753d49b37SJilles Tjoelker if (ppidfile) { 50853d49b37SJilles Tjoelker *ppfh = pidfile_open(ppidfile, 0600, &fpid); 50953d49b37SJilles Tjoelker if (*ppfh == NULL) { 51053d49b37SJilles Tjoelker serrno = errno; 51153d49b37SJilles Tjoelker pidfile_remove(*pfh); 51253d49b37SJilles Tjoelker errno = serrno; 51353d49b37SJilles Tjoelker if (errno == EEXIST) { 51453d49b37SJilles Tjoelker errx(3, "process already running, pid: %d", 51553d49b37SJilles Tjoelker fpid); 51653d49b37SJilles Tjoelker } 51753d49b37SJilles Tjoelker err(2, "ppidfile ``%s''", ppidfile); 51853d49b37SJilles Tjoelker } 51953d49b37SJilles Tjoelker } 52053d49b37SJilles Tjoelker } 52153d49b37SJilles Tjoelker 52253d49b37SJilles Tjoelker static int 52353d49b37SJilles Tjoelker get_log_mapping(const char *str, const CODE *c) 52453d49b37SJilles Tjoelker { 52553d49b37SJilles Tjoelker const CODE *cp; 52653d49b37SJilles Tjoelker for (cp = c; cp->c_name; cp++) 527*6b4ef4b1SIhor Antonov if (strcmp(cp->c_name, str) == 0) { 52853d49b37SJilles Tjoelker return cp->c_val; 529*6b4ef4b1SIhor Antonov } 53053d49b37SJilles Tjoelker return -1; 531195fc497SMikolaj Golub } 532195fc497SMikolaj Golub 533195fc497SMikolaj Golub static void 534e6d4b388STom Rhodes restrict_process(const char *user) 53512d7249eSTom Rhodes { 53612d7249eSTom Rhodes struct passwd *pw = NULL; 53712d7249eSTom Rhodes 538e6d4b388STom Rhodes pw = getpwnam(user); 539*6b4ef4b1SIhor Antonov if (pw == NULL) { 540e6d4b388STom Rhodes errx(1, "unknown user: %s", user); 541*6b4ef4b1SIhor Antonov } 54212d7249eSTom Rhodes 543*6b4ef4b1SIhor Antonov if (setusercontext(NULL, pw, pw->pw_uid, LOGIN_SETALL) != 0) { 544e6d4b388STom Rhodes errx(1, "failed to set user environment"); 545*6b4ef4b1SIhor Antonov } 5466b3ad1d7SMaxim Sobolev 5476b3ad1d7SMaxim Sobolev setenv("USER", pw->pw_name, 1); 5486b3ad1d7SMaxim Sobolev setenv("HOME", pw->pw_dir, 1); 5496b3ad1d7SMaxim Sobolev setenv("SHELL", *pw->pw_shell ? pw->pw_shell : _PATH_BSHELL, 1); 55012d7249eSTom Rhodes } 55112d7249eSTom Rhodes 55253d49b37SJilles Tjoelker /* 55353d49b37SJilles Tjoelker * We try to collect whole lines terminated by '\n'. Otherwise we collect a 55453d49b37SJilles Tjoelker * full buffer, and then output it. 55553d49b37SJilles Tjoelker * 55653d49b37SJilles Tjoelker * Return value of 0 is assumed to mean EOF or error, and 1 indicates to 55753d49b37SJilles Tjoelker * continue reading. 55853d49b37SJilles Tjoelker */ 559b6193c24SMikolaj Golub static int 56053d49b37SJilles Tjoelker listen_child(int fd, struct log_params *logpar) 5612ad43027SMikolaj Golub { 56253d49b37SJilles Tjoelker static unsigned char buf[LBUF_SIZE]; 56353d49b37SJilles Tjoelker static size_t bytes_read = 0; 56453d49b37SJilles Tjoelker int rv; 5652ad43027SMikolaj Golub 56653d49b37SJilles Tjoelker assert(logpar); 56753d49b37SJilles Tjoelker assert(bytes_read < LBUF_SIZE - 1); 56853d49b37SJilles Tjoelker 569*6b4ef4b1SIhor Antonov if (do_log_reopen) { 5704cd407ecSMaxim Sobolev reopen_log(logpar); 571*6b4ef4b1SIhor Antonov } 57253d49b37SJilles Tjoelker rv = read(fd, buf + bytes_read, LBUF_SIZE - bytes_read - 1); 57353d49b37SJilles Tjoelker if (rv > 0) { 57453d49b37SJilles Tjoelker unsigned char *cp; 57553d49b37SJilles Tjoelker 57653d49b37SJilles Tjoelker bytes_read += rv; 57753d49b37SJilles Tjoelker assert(bytes_read <= LBUF_SIZE - 1); 57853d49b37SJilles Tjoelker /* Always NUL-terminate just in case. */ 57953d49b37SJilles Tjoelker buf[LBUF_SIZE - 1] = '\0'; 58053d49b37SJilles Tjoelker /* 58153d49b37SJilles Tjoelker * Chomp line by line until we run out of buffer. 58253d49b37SJilles Tjoelker * This does not take NUL characters into account. 58353d49b37SJilles Tjoelker */ 58453d49b37SJilles Tjoelker while ((cp = memchr(buf, '\n', bytes_read)) != NULL) { 58553d49b37SJilles Tjoelker size_t bytes_line = cp - buf + 1; 58653d49b37SJilles Tjoelker assert(bytes_line <= bytes_read); 58753d49b37SJilles Tjoelker do_output(buf, bytes_line, logpar); 58853d49b37SJilles Tjoelker bytes_read -= bytes_line; 58953d49b37SJilles Tjoelker memmove(buf, cp + 1, bytes_read); 590195fc497SMikolaj Golub } 59153d49b37SJilles Tjoelker /* Wait until the buffer is full. */ 592*6b4ef4b1SIhor Antonov if (bytes_read < LBUF_SIZE - 1) { 59353d49b37SJilles Tjoelker return 1; 594*6b4ef4b1SIhor Antonov } 59553d49b37SJilles Tjoelker do_output(buf, bytes_read, logpar); 59653d49b37SJilles Tjoelker bytes_read = 0; 59753d49b37SJilles Tjoelker return 1; 59853d49b37SJilles Tjoelker } else if (rv == -1) { 59953d49b37SJilles Tjoelker /* EINTR should trigger another read. */ 60053d49b37SJilles Tjoelker if (errno == EINTR) { 60153d49b37SJilles Tjoelker return 1; 60253d49b37SJilles Tjoelker } else { 60353d49b37SJilles Tjoelker warn("read"); 60453d49b37SJilles Tjoelker return 0; 605c60d51f9SMikolaj Golub } 60653d49b37SJilles Tjoelker } 60753d49b37SJilles Tjoelker /* Upon EOF, we have to flush what's left of the buffer. */ 60853d49b37SJilles Tjoelker if (bytes_read > 0) { 60953d49b37SJilles Tjoelker do_output(buf, bytes_read, logpar); 61053d49b37SJilles Tjoelker bytes_read = 0; 61153d49b37SJilles Tjoelker } 61253d49b37SJilles Tjoelker return 0; 61353d49b37SJilles Tjoelker } 61453d49b37SJilles Tjoelker 61553d49b37SJilles Tjoelker /* 61653d49b37SJilles Tjoelker * The default behavior is to stay silent if the user wants to redirect 61753d49b37SJilles Tjoelker * output to a file and/or syslog. If neither are provided, then we bounce 61853d49b37SJilles Tjoelker * everything back to parent's stdout. 61953d49b37SJilles Tjoelker */ 62053d49b37SJilles Tjoelker static void 62153d49b37SJilles Tjoelker do_output(const unsigned char *buf, size_t len, struct log_params *logpar) 62253d49b37SJilles Tjoelker { 62353d49b37SJilles Tjoelker assert(len <= LBUF_SIZE); 62453d49b37SJilles Tjoelker assert(logpar); 62553d49b37SJilles Tjoelker 626*6b4ef4b1SIhor Antonov if (len < 1) { 62753d49b37SJilles Tjoelker return; 628*6b4ef4b1SIhor Antonov } 629*6b4ef4b1SIhor Antonov if (logpar->dosyslog) { 63053d49b37SJilles Tjoelker syslog(logpar->logpri, "%.*s", (int)len, buf); 631*6b4ef4b1SIhor Antonov } 63253d49b37SJilles Tjoelker if (logpar->outfd != -1) { 63353d49b37SJilles Tjoelker if (write(logpar->outfd, buf, len) == -1) 63453d49b37SJilles Tjoelker warn("write"); 63553d49b37SJilles Tjoelker } 636*6b4ef4b1SIhor Antonov if (logpar->noclose && !logpar->dosyslog && logpar->outfd == -1) { 63753d49b37SJilles Tjoelker printf("%.*s", (int)len, buf); 63853d49b37SJilles Tjoelker } 639*6b4ef4b1SIhor Antonov } 64053d49b37SJilles Tjoelker 64153d49b37SJilles Tjoelker /* 64253d49b37SJilles Tjoelker * We use the global PID acquired directly from fork. If there is no valid 64353d49b37SJilles Tjoelker * child pid, the handler should be blocked and/or child_gone == 1. 64453d49b37SJilles Tjoelker */ 64553d49b37SJilles Tjoelker static void 64653d49b37SJilles Tjoelker handle_term(int signo) 64753d49b37SJilles Tjoelker { 648*6b4ef4b1SIhor Antonov if (pid > 0 && !child_gone) { 64953d49b37SJilles Tjoelker kill(pid, signo); 650*6b4ef4b1SIhor Antonov } 651b6193c24SMikolaj Golub terminate = 1; 652195fc497SMikolaj Golub } 65353d49b37SJilles Tjoelker 65453d49b37SJilles Tjoelker static void 6554cd407ecSMaxim Sobolev handle_chld(int signo __unused) 65653d49b37SJilles Tjoelker { 6574cd407ecSMaxim Sobolev 65853d49b37SJilles Tjoelker for (;;) { 65953d49b37SJilles Tjoelker int rv = waitpid(-1, NULL, WNOHANG); 66053d49b37SJilles Tjoelker if (pid == rv) { 66153d49b37SJilles Tjoelker child_gone = 1; 66253d49b37SJilles Tjoelker break; 66353d49b37SJilles Tjoelker } else if (rv == -1 && errno != EINTR) { 66453d49b37SJilles Tjoelker warn("waitpid"); 66553d49b37SJilles Tjoelker return; 6662ad43027SMikolaj Golub } 6672ad43027SMikolaj Golub } 6682ad43027SMikolaj Golub } 6692ad43027SMikolaj Golub 6702ad43027SMikolaj Golub static void 6714cd407ecSMaxim Sobolev handle_hup(int signo __unused) 6724cd407ecSMaxim Sobolev { 6734cd407ecSMaxim Sobolev 6744cd407ecSMaxim Sobolev do_log_reopen = 1; 6754cd407ecSMaxim Sobolev } 6764cd407ecSMaxim Sobolev 6774cd407ecSMaxim Sobolev static int 6784cd407ecSMaxim Sobolev open_log(const char *outfn) 6794cd407ecSMaxim Sobolev { 6804cd407ecSMaxim Sobolev 6814cd407ecSMaxim Sobolev return open(outfn, O_CREAT | O_WRONLY | O_APPEND | O_CLOEXEC, 0600); 6824cd407ecSMaxim Sobolev } 6834cd407ecSMaxim Sobolev 6844cd407ecSMaxim Sobolev static void 6854cd407ecSMaxim Sobolev reopen_log(struct log_params *lpp) 6864cd407ecSMaxim Sobolev { 6874cd407ecSMaxim Sobolev int outfd; 6884cd407ecSMaxim Sobolev 6894cd407ecSMaxim Sobolev do_log_reopen = 0; 6904cd407ecSMaxim Sobolev outfd = open_log(lpp->outfn); 691*6b4ef4b1SIhor Antonov if (lpp->outfd >= 0) { 6924cd407ecSMaxim Sobolev close(lpp->outfd); 693*6b4ef4b1SIhor Antonov } 6944cd407ecSMaxim Sobolev lpp->outfd = outfd; 6954cd407ecSMaxim Sobolev } 6964cd407ecSMaxim Sobolev 697