2 * Copyright (c) 2014 The DragonFly Project. All rights reserved.
4 * This code is derived from software contributed to The DragonFly Project
5 * by Matthew Dillon <dillon@backplane.com>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in
15 * the documentation and/or other materials provided with the
17 * 3. Neither the name of The DragonFly Project nor the names of its
18 * contributors may be used to endorse or promote products derived
19 * from this software without specific, prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * Handle remote listen/connect and parsing operations.
40 pthread_mutex_t serial_mtx;
41 time_t LastStart; /* uptime */
42 time_t LastStop; /* uptime */
44 runstate_t RunState = RS_STOPPED;
48 static void *logger_thread(void *arg);
49 static void setstate_stopped(command_t *cmd, struct timespec *ts);
50 static int escapewrite(FILE *fp, char *buf, int n, int *statep);
53 execute_init(command_t *cmd)
57 pid_t stoppingpid = -1;
60 int lfd; /* unix domain listen socket */
61 int pfd; /* pid file */
67 if (cmd->label == NULL || cmd->ext_ac == 0) {
68 fprintf(cmd->fp, "init requires a label and command\n");
71 fprintf(cmd->fp, "initializing new service: %s\n", cmd->label);
73 if ((xfd = open("/dev/null", O_RDWR)) < 0) {
74 fprintf(cmd->fp, "Unable to open /dev/null: %s\n",
80 * Setup pidfile and unix domain listen socket and lock the
83 rc = setup_pid_and_socket(cmd, &lfd, &pfd);
90 if (cmd->foreground) {
99 fprintf(cmd->fp, "Unable to create pipe: %s\n",
103 remove_pid_and_socket(cmd, cmd->label);
115 fprintf(cmd->fp, "fork failed: %s\n", strerror(errno));
120 remove_pid_and_socket(cmd, cmd->label);
124 * Fill-in pfd before returning.
126 snprintf(buf, sizeof(buf), "%d\n", (int)pid);
127 write(pfd, buf, strlen(buf));
133 * Wait for child to completely detach from the tty
143 * Forked child is now the service demon.
145 * Detach from terminal, scrap tty.
147 if (cmd->foreground == 0) {
152 if (xfd != 0) /* scrap tty inputs */
154 if (cmd->foreground == 0) {
157 if (xfd != 1) /* scrap tty outputs */
162 if ((tfd = open("/dev/tty", O_RDWR)) >= 0) {
163 ioctl(tfd, TIOCNOTTY, 0); /* no controlling tty */
166 setsid(); /* new session */
170 * Setup log file. The log file must not use descriptors 0, 1, or 2.
172 if (cmd->logfile && strcmp(cmd->logfile, "/dev/null") == 0)
174 else if (cmd->logfile)
175 cmd->logfd = open(cmd->logfile, O_WRONLY|O_CREAT|O_APPEND, 0640);
176 else if (cmd->foreground)
182 * Signal parent that we are completely detached now.
185 if (cmd->foreground == 0) {
186 write(fds[1], &c, 1);
193 * Setup log pipe. The logger thread copies the pipe to a buffer
194 * for the 'log' directive and also writes it to logfd.
197 if (cmd->fp != stdout)
199 cmd->fp = fdopen(cmd->logfds[1], "w");
206 pthread_cond_init(&cmd->logcond, NULL);
209 * Start accept thread for unix domain listen socket.
211 pthread_mutex_lock(&serial_mtx);
212 pthread_create(&logtd, NULL, logger_thread, cmd);
213 remote_listener(cmd, lfd);
216 * Become the reaper for all children recursively.
218 if (procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL) < 0) {
219 fprintf(cmd->fp, "svc is unable to become the "
220 "reaper for its children\n");
225 * Initial service start
230 * Main loop is the reaper
233 union reaper_info info;
240 * If we are running just block doing normal reaping,
241 * if we are stopping we have to poll for reaping while
242 * we handle stopping.
245 if (RunState == RS_STARTED) {
246 pthread_mutex_unlock(&serial_mtx);
247 pid = wait3(&status, 0, NULL);
248 pthread_mutex_lock(&serial_mtx);
250 pid = wait3(&status, WNOHANG, NULL);
252 clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
255 if (pid == DirectPid) {
257 "svc %s: lost direct child %d\n",
261 if (cmd->restart_some) {
262 setstate_stopped(cmd, &ts);
263 } /* else still considered normal run state */
264 } else if (cmd->debug) {
266 * Reap random disconnected child, but don't
267 * spew to the log unless debugging is
271 "svc %s: reap indirect child %d\n",
278 * Calculate the pid to potentially act on and/or
279 * determine if any children still exist.
281 if (DirectPid >= 0) {
283 } else if (procctl(P_PID, getpid(),
284 PROC_REAP_STATUS, &info) == 0) {
285 usepid = info.status.pid_head;
290 fprintf(stderr, "svc %s: usepid %d\n",
296 * If stoppingpid changes we have to reset the TERM->KILL
300 setstate_stopped(cmd, &ts);
301 } else if (stoppingpid != usepid &&
302 (RunState == RS_STOPPING2 ||
303 RunState == RS_STOPPING3)) {
304 RunState = RS_STOPPING1;
306 stoppingpid = usepid;
314 setstate_stopped(cmd, &ts);
317 dt = (int)(ts.tv_sec - LastStop);
319 if (cmd->exit_mode) {
321 * Service demon was told to exit on service
322 * stop (-x passed to init).
325 "svc %s: service demon exiting\n",
327 remove_pid_and_socket(cmd, cmd->label);
329 } else if (cmd->manual_stop) {
331 * Service demon was told to stop via
332 * commanded (not automatic) action. We
333 * do not auto-restart the service in
336 pthread_mutex_unlock(&serial_mtx);
337 if (dt < 0 || dt > 60)
341 pthread_mutex_lock(&serial_mtx);
342 } else if (cmd->restart_some || cmd->restart_all) {
344 * Handle automatic restarts
346 if (dt > cmd->restart_timo) {
349 pthread_mutex_unlock(&serial_mtx);
351 pthread_mutex_lock(&serial_mtx);
355 * No automatic restart was configured,
356 * wait for commanded action.
358 pthread_mutex_unlock(&serial_mtx);
359 if (dt < 0 || dt > 60)
363 pthread_mutex_lock(&serial_mtx);
368 * Reset TERM->KILL timer
370 nextstop = ts.tv_sec;
371 RunState = RS_STOPPING2;
374 if (cmd->termkill_timo == 0) {
375 nextstop = ts.tv_sec - 1;
377 kill(stoppingpid, SIGTERM);
378 fprintf(cmd->fp, "svc %s: sigterm %d\n",
379 cmd->label, stoppingpid);
382 RunState = RS_STOPPING3;
385 dt = (int)(ts.tv_sec - nextstop);
386 if (dt > cmd->termkill_timo) {
387 fprintf(cmd->fp, "svc %s: sigkill %d\n",
388 cmd->label, stoppingpid);
389 kill(stoppingpid, SIGKILL);
395 pthread_mutex_unlock(&serial_mtx);
397 /* does not return */
401 execute_start(command_t *cmd)
406 while (RunState == RS_STOPPING1 ||
407 RunState == RS_STOPPING2 ||
408 RunState == RS_STOPPING3) {
410 "svc %s: Waiting for previous action to complete\n",
413 pthread_mutex_unlock(&serial_mtx);
415 pthread_mutex_lock(&serial_mtx);
416 if (--maxwait == 0) {
418 "svc %s: Giving up waiting for action\n",
424 if (RunState == RS_STARTED) {
425 fprintf(cmd->fp, "svc %s: Already started pid %d\n",
426 cmd->label, DirectPid);
431 clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
432 if ((DirectPid = fork()) == 0) {
434 /* leave stdin /dev/null */
435 dup2(fileno(InitCmd->fp), 1); /* setup stdout */
436 dup2(fileno(InitCmd->fp), 2); /* setup stderr */
438 execvp(InitCmd->ext_av[0], InitCmd->ext_av);
441 if (DirectPid >= 0) {
442 RunState = RS_STARTED;
443 LastStart = ts.tv_sec;
445 setstate_stopped(InitCmd, &ts);
447 InitCmd->manual_stop = 0;
448 fprintf(cmd->fp, "svc %s: Starting pid %d\n", cmd->label, DirectPid);
455 execute_restart(command_t *cmd)
459 rc = execute_stop(cmd) + execute_start(cmd);
464 execute_stop(command_t *cmd)
466 union reaper_info info;
468 int save_restart_some;
469 int save_restart_all;
472 save_restart_some = InitCmd->restart_some;
473 save_restart_all = InitCmd->restart_all;
475 InitCmd->manual_stop = 1;
476 if (cmd->commanded && (cmd->restart_some || cmd->restart_all)) {
477 InitCmd->restart_some = cmd->restart_some;
478 InitCmd->restart_all = cmd->restart_all;
480 fprintf(cmd->fp, "svc %s: Stopping\n", cmd->label);
484 * Start the kill chain going so the master loop's wait3 wakes up.
486 if (DirectPid >= 0) {
487 kill(DirectPid, SIGTERM);
489 if (procctl(P_PID, getpid(), PROC_REAP_STATUS, &info) == 0 &&
490 info.status.pid_head > 0) {
491 kill(info.status.pid_head, SIGTERM);
495 clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
496 LastStop = ts.tv_sec;
497 RunState = RS_STOPPING1;
500 * If commanded (verses automatic), we are running remote in our
501 * own thread and we need to wait for the action to complete.
503 if (cmd->commanded) {
504 while (RunState == RS_STOPPING1 ||
505 RunState == RS_STOPPING2 ||
506 RunState == RS_STOPPING3) {
508 "svc %s: Waiting for service to stop\n",
511 pthread_mutex_unlock(&serial_mtx);
513 pthread_mutex_lock(&serial_mtx);
514 if (--maxwait == 0) {
516 "svc %s: Giving up waiting for stop\n",
522 if (cmd->restart_some || cmd->restart_all) {
523 InitCmd->restart_some = save_restart_some;
524 InitCmd->restart_all = save_restart_all;
532 execute_exit(command_t *cmd)
534 if (cmd->commanded) {
535 InitCmd->restart_some = 0;
536 InitCmd->restart_all = 1; /* kill all children */
537 InitCmd->exit_mode = 1; /* exit after stop */
539 fprintf(cmd->fp, "svc %s: Stopping and Exiting\n", cmd->label);
546 execute_list(command_t *cmd)
548 fprintf(cmd->fp, "%-16s\n", cmd->label);
554 execute_status(command_t *cmd)
560 if (InitCmd && InitCmd->exit_mode)
561 state = "stopped (exiting)";
562 else if (InitCmd && InitCmd->manual_stop)
563 state = "stopped (manual)";
580 fprintf(cmd->fp, "%-16s %s\n", cmd->label, state);
586 execute_log(command_t *cmd)
588 int lbsize = (int)sizeof(cmd->logbuf);
589 int lbmask = lbsize - 1;
599 * mode 0 - Dump everything then exit
600 * mode 1 - Dump everything then block/loop
601 * mode 2 - Skeep to end then block/loop
603 if (cmd->tail_mode == 2)
604 windex = InitCmd->logwindex;
606 windex = InitCmd->logwindex - InitCmd->logcount;
608 dotstate = 0; /* 0=start-of-line 1=middle-of-line 2=dot */
612 * Calculate the amount of data we missed and determine
613 * if some data was lost.
615 n = InitCmd->logwindex - windex;
616 if (n < 0 || n > InitCmd->logcount) {
617 windex = InitCmd->logwindex - InitCmd->logcount;
618 pthread_mutex_unlock(&serial_mtx);
619 fprintf(cmd->fp, "\n(LOG DATA LOST)\n");
620 pthread_mutex_lock(&serial_mtx);
625 * Circular buffer and copy size limitations. If no
626 * data ready, wait for some.
628 if (n > lbsize - (windex & lbmask))
629 n = lbsize - (windex & lbmask);
633 if (cmd->tail_mode == 0)
635 pthread_cond_wait(&InitCmd->logcond, &serial_mtx);
638 bcopy(InitCmd->logbuf + (windex & lbmask), buf, n);
641 * Dump log output, escape any '.' on a line by itself.
643 pthread_mutex_unlock(&serial_mtx);
644 n = escapewrite(cmd->fp, buf, n, &dotstate);
647 lastnl = (buf[n-1] == '\n');
648 pthread_mutex_lock(&serial_mtx);
655 pthread_mutex_unlock(&serial_mtx);
656 fprintf(cmd->fp, "\n");
657 pthread_mutex_lock(&serial_mtx);
663 * Change or reopen logfile.
666 execute_logfile(command_t *cmd)
674 logfile = cmd->logfile;
675 if (cmd->ext_av && cmd->ext_av[0])
676 logfile = cmd->ext_av[0];
678 logfile = InitCmd->logfile;
682 if (InitCmd->logfile &&
683 strcmp(InitCmd->logfile, logfile) == 0) {
684 fprintf(cmd->fp, "svc %s: Reopen logfile %s\n",
685 cmd->label, logfile);
687 fprintf(cmd->fp, "svc %s: Change logfile to %s\n",
688 cmd->label, logfile);
690 if (InitCmd->logfd >= 0) {
691 close(InitCmd->logfd);
694 if (strcmp(logfile, "/dev/null") == 0) {
695 sreplace(&InitCmd->logfile, logfile);
697 fd = open(logfile, O_WRONLY|O_CREAT|O_APPEND, 0640);
700 sreplace(&InitCmd->logfile, logfile);
703 "svc %s: Unable to open/create "
706 logfile, strerror(errno));
715 execute_help(command_t *cmd)
718 "svc [options] directive [label [additional_args]]\n"
720 "Directives: init start stop stopall restart exit\n"
721 " kill list status log logf tailf logfile\n"
729 logger_thread(void *arg)
731 command_t *cmd = arg;
732 int lbsize = (int)sizeof(cmd->logbuf);
733 int lbmask = lbsize - 1;
737 pthread_detach(pthread_self());
738 pthread_mutex_lock(&serial_mtx);
741 * slip circular buffer to make room for new data.
743 n = cmd->logcount - (lbsize - LOGCHUNK);
748 windex = cmd->logwindex & lbmask;
752 pthread_mutex_unlock(&serial_mtx);
753 n = read(cmd->logfds[0], cmd->logbuf + windex, n);
754 pthread_mutex_lock(&serial_mtx);
757 write(cmd->logfd, cmd->logbuf + windex, n);
760 pthread_cond_signal(&cmd->logcond);
762 if (n == 0 || (n < 0 && errno != EINTR))
765 pthread_mutex_unlock(&serial_mtx);
771 setstate_stopped(command_t *cmd, struct timespec *ts)
773 if (RunState != RS_STOPPED) {
774 RunState = RS_STOPPED;
775 LastStop = ts->tv_sec;
776 if (cmd->sync_mode) /* support -s option */
783 escapewrite(FILE *fp, char *buf, int n, int *statep)
793 for (i = b; i < n; ++i) {
815 * dot was output at beginning of line
825 if (*statep == 3) /* flush with escape */
829 n = fwrite(buf, 1, i - b, fp);
835 if (*statep == 3) { /* added escape */
836 n = fwrite(".", 1, 1, fp);
837 /* escapes not counted in r */