hammer2 - Allow @LABEL to be omitted
[dragonfly.git] / sbin / svc / execute.c
blobe724653a402e6c4a236540073770d94addf20c99
1 /*
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
9 * are met:
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
16 * distribution.
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
32 * SUCH DAMAGE.
35 * Handle remote listen/connect and parsing operations.
38 #include "svc.h"
40 pthread_mutex_t serial_mtx;
41 time_t LastStart; /* uptime */
42 time_t LastStop; /* uptime */
43 pid_t DirectPid = -1;
44 runstate_t RunState = RS_STOPPED;
45 command_t *InitCmd;
46 int RestartCounter;
48 static void *logger_thread(void *arg);
49 static void setstate_stopped(command_t *cmd, struct timespec *ts);
50 static int setup_gid(command_t *cmd);
51 static int setup_uid(command_t *cmd);
52 static int setup_jail(command_t *cmd);
53 static int setup_chroot(command_t *cmd);
54 static int setup_devfs(command_t *cmd, const char *dir, int domount);
55 static int escapewrite(FILE *fp, char *buf, int n, int *statep);
57 int
58 execute_init(command_t *cmd)
60 char buf[32];
61 pid_t pid;
62 pid_t stoppingpid = -1;
63 pthread_t logtd;
64 time_t nextstop = 0;
65 int lfd; /* unix domain listen socket */
66 int pfd; /* pid file */
67 int xfd;
68 int rc;
69 int fds[2];
70 char c;
72 if (cmd->label == NULL || cmd->ext_ac == 0) {
73 fprintf(cmd->fp, "init requires a label and command\n");
74 return 1;
76 fprintf(cmd->fp, "initializing new service: %s\n", cmd->label);
78 if ((xfd = open("/dev/null", O_RDWR)) < 0) {
79 fprintf(cmd->fp, "Unable to open /dev/null: %s\n",
80 strerror(errno));
81 return 1;
85 * Setup pidfile and unix domain listen socket and lock the
86 * pidfile.
88 rc = setup_pid_and_socket(cmd, &lfd, &pfd);
89 if (rc)
90 return rc;
93 * Detach the service
95 if (cmd->foreground) {
97 * Stay in foreground.
99 fds[0] = -1;
100 fds[1] = -1;
101 pid = 0;
102 } else {
103 if (pipe(fds) < 0) {
104 fprintf(cmd->fp, "Unable to create pipe: %s\n",
105 strerror(errno));
106 close(lfd);
107 close(pfd);
108 remove_pid_and_socket(cmd, cmd->label);
109 return 1;
111 pid = fork();
114 if (pid != 0) {
116 * Parent
118 close(fds[1]);
119 if (pid < 0) {
120 fprintf(cmd->fp, "fork failed: %s\n", strerror(errno));
121 close(lfd);
122 close(pfd);
123 close(fds[0]);
124 close(fds[1]);
125 remove_pid_and_socket(cmd, cmd->label);
126 return 1;
127 } else {
129 * Fill-in pfd before returning.
131 snprintf(buf, sizeof(buf), "%d\n", (int)pid);
132 write(pfd, buf, strlen(buf));
134 close(lfd);
135 close(pfd);
138 * Wait for child to completely detach from the tty
139 * before returning.
141 read(fds[0], &c, 1);
142 close(fds[0]);
144 return 0;
148 * Forked child is now the service demon.
150 * Detach from terminal, scrap tty, set process title.
152 if (cmd->proctitle) {
153 setproctitle("%s - %s", cmd->label, cmd->proctitle);
154 } else {
155 setproctitle("%s", cmd->label);
157 if (cmd->mountdev) {
158 if (cmd->jaildir)
159 setup_devfs(cmd, cmd->jaildir, 1);
160 else if (cmd->rootdir)
161 setup_devfs(cmd, cmd->rootdir, 1);
164 if (cmd->foreground == 0) {
165 close(fds[0]);
166 fds[0] = -1;
169 if (xfd != 0) /* scrap tty inputs */
170 dup2(xfd, 0);
171 if (cmd->foreground == 0) {
172 int tfd;
174 if (xfd != 1) /* scrap tty outputs */
175 dup2(xfd, 1);
176 if (xfd != 2)
177 dup2(xfd, 2);
179 if ((tfd = open("/dev/tty", O_RDWR)) >= 0) {
180 ioctl(tfd, TIOCNOTTY, 0); /* no controlling tty */
181 close(tfd);
183 setsid(); /* new session */
187 * Setup log file. The log file must not use descriptors 0, 1, or 2.
189 if (cmd->logfile && strcmp(cmd->logfile, "/dev/null") == 0)
190 cmd->logfd = -1;
191 else if (cmd->logfile)
192 cmd->logfd = open(cmd->logfile, O_WRONLY|O_CREAT|O_APPEND, 0640);
193 else if (cmd->foreground)
194 cmd->logfd = dup(1);
195 else
196 cmd->logfd = -1;
199 * Signal parent that we are completely detached now.
201 c = 1;
202 if (cmd->foreground == 0) {
203 write(fds[1], &c, 1);
204 close(fds[1]);
205 fds[1] = -1;
207 InitCmd = cmd;
210 * Setup log pipe. The logger thread copies the pipe to a buffer
211 * for the 'log' directive and also writes it to logfd.
213 pipe(cmd->logfds);
214 if (cmd->fp != stdout)
215 fclose(cmd->fp);
216 cmd->fp = fdopen(cmd->logfds[1], "w");
218 if (xfd > 2) {
219 close(xfd);
220 xfd = -1;
223 pthread_cond_init(&cmd->logcond, NULL);
226 * Start accept thread for unix domain listen socket.
228 pthread_mutex_lock(&serial_mtx);
229 pthread_create(&logtd, NULL, logger_thread, cmd);
230 remote_listener(cmd, lfd);
233 * Become the reaper for all children recursively.
235 if (procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL) < 0) {
236 fprintf(cmd->fp, "svc is unable to become the "
237 "reaper for its children\n");
238 fflush(cmd->fp);
242 * Initial service start
244 execute_start(cmd);
247 * Main loop is the reaper
249 for (;;) {
250 union reaper_info info;
251 struct timespec ts;
252 int status;
253 int dt;
254 pid_t usepid;
257 * If we are running just block doing normal reaping,
258 * if we are stopping we have to poll for reaping while
259 * we handle stopping.
261 fflush(cmd->fp);
262 if (RunState == RS_STARTED) {
263 pthread_mutex_unlock(&serial_mtx);
264 pid = wait3(&status, 0, NULL);
265 pthread_mutex_lock(&serial_mtx);
266 } else {
267 pid = wait3(&status, WNOHANG, NULL);
269 clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
271 if (pid > 0) {
272 if (pid == DirectPid) {
273 fprintf(cmd->fp,
274 "svc %s: lost direct child %d\n",
275 cmd->label, pid);
276 fflush(cmd->fp);
277 DirectPid = -1;
278 if (cmd->restart_some) {
279 setstate_stopped(cmd, &ts);
280 } /* else still considered normal run state */
281 } else if (cmd->debug) {
283 * Reap random disconnected child, but don't
284 * spew to the log unless debugging is
285 * enabled.
287 fprintf(cmd->fp,
288 "svc %s: reap indirect child %d\n",
289 cmd->label,
290 (int)pid);
295 * Calculate the pid to potentially act on and/or
296 * determine if any children still exist.
298 if (DirectPid >= 0) {
299 usepid = DirectPid;
300 } else if (procctl(P_PID, getpid(),
301 PROC_REAP_STATUS, &info) == 0) {
302 usepid = info.status.pid_head;
303 } else {
304 usepid = -1;
306 if (cmd->debug) {
307 fprintf(stderr, "svc %s: usepid %d\n",
308 cmd->label, usepid);
309 fflush(stderr);
313 * If stoppingpid changes we have to reset the TERM->KILL
314 * timer.
316 if (usepid < 0) {
317 setstate_stopped(cmd, &ts);
318 } else if (stoppingpid != usepid &&
319 (RunState == RS_STOPPING2 ||
320 RunState == RS_STOPPING3)) {
321 RunState = RS_STOPPING1;
323 stoppingpid = usepid;
326 * State machine
328 switch(RunState) {
329 case RS_STARTED:
330 if (usepid < 0)
331 setstate_stopped(cmd, &ts);
332 break;
333 case RS_STOPPED:
334 dt = (int)(ts.tv_sec - LastStop);
336 if (cmd->exit_mode) {
338 * Service demon was told to exit on service
339 * stop (-x passed to init).
341 fprintf(cmd->fp,
342 "svc %s: service demon exiting\n",
343 cmd->label);
344 remove_pid_and_socket(cmd, cmd->label);
345 goto exitloop;
346 } else if (cmd->manual_stop) {
348 * Service demon was told to stop via
349 * commanded (not automatic) action. We
350 * do not auto-restart the service in
351 * this situation.
353 pthread_mutex_unlock(&serial_mtx);
354 if (dt < 0 || dt > 60)
355 sleep(60);
356 else
357 sleep(1);
358 pthread_mutex_lock(&serial_mtx);
359 } else if (cmd->restart_some || cmd->restart_all) {
361 * Handle automatic restarts
363 if (dt > cmd->restart_timo) {
364 execute_start(cmd);
365 } else {
366 pthread_mutex_unlock(&serial_mtx);
367 sleep(1);
368 pthread_mutex_lock(&serial_mtx);
370 } else {
372 * No automatic restart was configured,
373 * wait for commanded action.
375 pthread_mutex_unlock(&serial_mtx);
376 if (dt < 0 || dt > 60)
377 sleep(60);
378 else
379 sleep(1);
380 pthread_mutex_lock(&serial_mtx);
382 break;
383 case RS_STOPPING1:
385 * Reset TERM->KILL timer
387 nextstop = ts.tv_sec;
388 RunState = RS_STOPPING2;
389 /* fall through */
390 case RS_STOPPING2:
391 if (cmd->termkill_timo == 0) {
392 nextstop = ts.tv_sec - 1;
393 } else {
394 kill(stoppingpid, SIGTERM);
395 fprintf(cmd->fp, "svc %s: sigterm %d\n",
396 cmd->label, stoppingpid);
397 sleep(1);
399 RunState = RS_STOPPING3;
400 /* fall through */
401 case RS_STOPPING3:
402 dt = (int)(ts.tv_sec - nextstop);
403 if (dt > cmd->termkill_timo) {
404 fprintf(cmd->fp, "svc %s: sigkill %d\n",
405 cmd->label, stoppingpid);
406 kill(stoppingpid, SIGKILL);
408 sleep(1);
409 break;
412 exitloop:
413 pthread_mutex_unlock(&serial_mtx);
414 if (cmd->mountdev) {
415 if (cmd->jaildir)
416 setup_devfs(cmd, cmd->jaildir, 0);
417 else if (cmd->rootdir)
418 setup_devfs(cmd, cmd->rootdir, 0);
420 exit(0);
421 /* does not return */
425 execute_start(command_t *cmd)
427 struct timespec ts;
428 int maxwait = 60;
430 while (RunState == RS_STOPPING1 ||
431 RunState == RS_STOPPING2 ||
432 RunState == RS_STOPPING3) {
433 fprintf(cmd->fp,
434 "svc %s: Waiting for previous action to complete\n",
435 cmd->label);
436 fflush(cmd->fp);
437 pthread_mutex_unlock(&serial_mtx);
438 sleep(1);
439 pthread_mutex_lock(&serial_mtx);
440 if (--maxwait == 0) {
441 fprintf(cmd->fp,
442 "svc %s: Giving up waiting for action\n",
443 cmd->label);
444 fflush(cmd->fp);
445 break;
448 if (RunState == RS_STARTED) {
449 fprintf(cmd->fp, "svc %s: Already started pid %d\n",
450 cmd->label, DirectPid);
451 fflush(cmd->fp);
452 return 0;
455 clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
456 if ((DirectPid = fork()) == 0) {
457 fflush(InitCmd->fp);
458 /* leave stdin /dev/null */
459 dup2(fileno(InitCmd->fp), 1); /* setup stdout */
460 dup2(fileno(InitCmd->fp), 2); /* setup stderr */
461 closefrom(3);
463 if (cmd->jaildir) /* jail or chroot */
464 setup_jail(cmd);
465 else if (cmd->rootdir)
466 setup_chroot(cmd);
468 setup_gid(cmd);
469 setup_uid(cmd);
470 execvp(InitCmd->ext_av[0], InitCmd->ext_av);
471 exit(99);
473 if (DirectPid >= 0) {
474 RunState = RS_STARTED;
475 LastStart = ts.tv_sec;
476 } else {
477 setstate_stopped(InitCmd, &ts);
479 InitCmd->manual_stop = 0;
480 fprintf(cmd->fp, "svc %s: Starting pid %d\n", cmd->label, DirectPid);
481 fflush(cmd->fp);
483 return 0;
487 execute_restart(command_t *cmd)
489 int rc;
491 rc = execute_stop(cmd) + execute_start(cmd);
492 return rc;
496 execute_stop(command_t *cmd)
498 union reaper_info info;
499 struct timespec ts;
500 int save_restart_some;
501 int save_restart_all;
502 int maxwait = 60;
504 save_restart_some = InitCmd->restart_some;
505 save_restart_all = InitCmd->restart_all;
506 if (cmd->commanded)
507 InitCmd->manual_stop = 1;
508 if (cmd->commanded && (cmd->restart_some || cmd->restart_all)) {
509 InitCmd->restart_some = cmd->restart_some;
510 InitCmd->restart_all = cmd->restart_all;
512 fprintf(cmd->fp, "svc %s: Stopping\n", cmd->label);
513 fflush(cmd->fp);
516 * Start the kill chain going so the master loop's wait3 wakes up.
518 if (DirectPid >= 0) {
519 kill(DirectPid, SIGTERM);
520 } else {
521 if (procctl(P_PID, getpid(), PROC_REAP_STATUS, &info) == 0 &&
522 info.status.pid_head > 0) {
523 kill(info.status.pid_head, SIGTERM);
527 clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
528 LastStop = ts.tv_sec;
529 RunState = RS_STOPPING1;
532 * If commanded (verses automatic), we are running remote in our
533 * own thread and we need to wait for the action to complete.
535 if (cmd->commanded) {
536 while (RunState == RS_STOPPING1 ||
537 RunState == RS_STOPPING2 ||
538 RunState == RS_STOPPING3) {
539 fprintf(cmd->fp,
540 "svc %s: Waiting for service to stop\n",
541 cmd->label);
542 fflush(cmd->fp);
543 pthread_mutex_unlock(&serial_mtx);
544 sleep(1);
545 pthread_mutex_lock(&serial_mtx);
546 if (--maxwait == 0) {
547 fprintf(cmd->fp,
548 "svc %s: Giving up waiting for stop\n",
549 cmd->label);
550 fflush(cmd->fp);
551 break;
554 if (cmd->restart_some || cmd->restart_all) {
555 InitCmd->restart_some = save_restart_some;
556 InitCmd->restart_all = save_restart_all;
560 return 0;
564 execute_exit(command_t *cmd)
566 if (cmd->commanded) {
567 InitCmd->restart_some = 0;
568 InitCmd->restart_all = 1; /* kill all children */
569 InitCmd->exit_mode = 1; /* exit after stop */
570 } else {
571 cmd->exit_mode = 1;
573 fprintf(cmd->fp, "svc %s: Stopping and Exiting\n", cmd->label);
574 execute_stop(cmd);
576 return 0;
580 execute_list(command_t *cmd)
582 fprintf(cmd->fp, "%-16s\n", cmd->label);
584 return 0;
588 execute_status(command_t *cmd)
590 const char *state;
592 switch(RunState) {
593 case RS_STOPPED:
594 if (InitCmd && InitCmd->exit_mode)
595 state = "stopped (exiting)";
596 else if (InitCmd && InitCmd->manual_stop)
597 state = "stopped (manual)";
598 else
599 state = "stopped";
600 break;
601 case RS_STARTED:
602 state = "running";
603 break;
604 case RS_STOPPING1:
605 case RS_STOPPING2:
606 case RS_STOPPING3:
607 state = "killing";
608 break;
609 default:
610 state = "unknown";
611 break;
614 fprintf(cmd->fp, "%-16s %s\n", cmd->label, state);
616 return 0;
620 execute_log(command_t *cmd)
622 int lbsize = (int)sizeof(cmd->logbuf);
623 int lbmask = lbsize - 1;
624 int windex;
625 int n;
626 int lastnl;
627 int dotstate;
628 char buf[LOGCHUNK];
630 assert(InitCmd);
633 * mode 0 - Dump everything then exit
634 * mode 1 - Dump everything then block/loop
635 * mode 2 - Skeep to end then block/loop
637 if (cmd->tail_mode == 2)
638 windex = InitCmd->logwindex;
639 else
640 windex = InitCmd->logwindex - InitCmd->logcount;
641 lastnl = 1;
642 dotstate = 0; /* 0=start-of-line 1=middle-of-line 2=dot */
644 for (;;) {
646 * Calculate the amount of data we missed and determine
647 * if some data was lost.
649 n = InitCmd->logwindex - windex;
650 if (n < 0 || n > InitCmd->logcount) {
651 windex = InitCmd->logwindex - InitCmd->logcount;
652 pthread_mutex_unlock(&serial_mtx);
653 fprintf(cmd->fp, "\n(LOG DATA LOST)\n");
654 pthread_mutex_lock(&serial_mtx);
655 continue;
659 * Circular buffer and copy size limitations. If no
660 * data ready, wait for some.
662 if (n > lbsize - (windex & lbmask))
663 n = lbsize - (windex & lbmask);
664 if (n > LOGCHUNK)
665 n = LOGCHUNK;
666 if (n == 0) {
667 if (cmd->tail_mode == 0)
668 break;
669 pthread_cond_wait(&InitCmd->logcond, &serial_mtx);
670 continue;
672 bcopy(InitCmd->logbuf + (windex & lbmask), buf, n);
675 * Dump log output, escape any '.' on a line by itself.
677 pthread_mutex_unlock(&serial_mtx);
678 n = escapewrite(cmd->fp, buf, n, &dotstate);
679 fflush(cmd->fp);
680 if (n > 0)
681 lastnl = (buf[n-1] == '\n');
682 pthread_mutex_lock(&serial_mtx);
684 if (n < 0)
685 break;
686 windex += n;
688 if (lastnl == 0) {
689 pthread_mutex_unlock(&serial_mtx);
690 fprintf(cmd->fp, "\n");
691 pthread_mutex_lock(&serial_mtx);
693 return 0;
697 * Change or reopen logfile.
700 execute_logfile(command_t *cmd)
702 char *logfile;
703 int fd;
704 int rc;
706 assert(InitCmd);
708 logfile = cmd->logfile;
709 if (cmd->ext_av && cmd->ext_av[0])
710 logfile = cmd->ext_av[0];
711 if (logfile == NULL)
712 logfile = InitCmd->logfile;
714 rc = 0;
715 if (logfile) {
716 if (InitCmd->logfile &&
717 strcmp(InitCmd->logfile, logfile) == 0) {
718 fprintf(cmd->fp, "svc %s: Reopen logfile %s\n",
719 cmd->label, logfile);
720 } else {
721 fprintf(cmd->fp, "svc %s: Change logfile to %s\n",
722 cmd->label, logfile);
724 if (InitCmd->logfd >= 0) {
725 close(InitCmd->logfd);
726 InitCmd->logfd = -1;
728 if (strcmp(logfile, "/dev/null") == 0) {
729 sreplace(&InitCmd->logfile, logfile);
730 } else {
731 fd = open(logfile, O_WRONLY|O_CREAT|O_APPEND, 0640);
732 if (fd >= 0) {
733 InitCmd->logfd = fd;
734 sreplace(&InitCmd->logfile, logfile);
735 } else {
736 fprintf(cmd->fp,
737 "svc %s: Unable to open/create "
738 "\"%s\": %s\n",
739 cmd->label,
740 logfile, strerror(errno));
741 rc = 1;
745 return rc;
749 execute_help(command_t *cmd)
751 fprintf(cmd->fp,
752 "svc [options] directive [label [additional_args]]\n"
753 "\n"
754 "Directives: init start stop stopall restart exit\n"
755 " kill list status log logf tailf logfile\n"
756 " help\n"
758 return 0;
761 static
762 void *
763 logger_thread(void *arg)
765 command_t *cmd = arg;
766 int lbsize = (int)sizeof(cmd->logbuf);
767 int lbmask = lbsize - 1;
768 int windex;
769 int n;
771 pthread_detach(pthread_self());
772 pthread_mutex_lock(&serial_mtx);
773 for (;;) {
775 * slip circular buffer to make room for new data.
777 n = cmd->logcount - (lbsize - LOGCHUNK);
778 if (n > 0) {
779 cmd->logcount -= n;
780 cmd->logwindex += n;
782 windex = cmd->logwindex & lbmask;
783 n = lbsize - windex;
784 if (n > LOGCHUNK)
785 n = LOGCHUNK;
786 pthread_mutex_unlock(&serial_mtx);
787 n = read(cmd->logfds[0], cmd->logbuf + windex, n);
788 pthread_mutex_lock(&serial_mtx);
789 if (n > 0) {
790 if (cmd->logfd >= 0)
791 write(cmd->logfd, cmd->logbuf + windex, n);
792 cmd->logcount += n;
793 cmd->logwindex += n;
794 pthread_cond_signal(&cmd->logcond);
796 if (n == 0 || (n < 0 && errno != EINTR))
797 break;
799 pthread_mutex_unlock(&serial_mtx);
800 return NULL;
804 * Put us in the STOPPED state if we are not already there, and
805 * handle post-stop options (aka sync).
807 static
808 void
809 setstate_stopped(command_t *cmd, struct timespec *ts)
811 if (RunState != RS_STOPPED) {
812 RunState = RS_STOPPED;
813 LastStop = ts->tv_sec;
814 if (cmd->sync_mode) /* support -s option */
815 sync();
819 static
821 setup_gid(command_t *cmd)
823 int i;
825 if (cmd->gid_mode &&
826 setgid(cmd->grent.gr_gid) < 0) {
827 fprintf(cmd->fp, "unable to setgid to \"%s\": %s\n",
828 cmd->grent.gr_name, strerror(errno));
829 return 1;
833 * -G overrides all group ids.
835 if (cmd->ngroups) {
836 if (setgroups(cmd->ngroups, cmd->groups) < 0) {
837 fprintf(cmd->fp, "unable to setgroups to (");
838 for (i = 0; i < cmd->ngroups; ++i) {
839 if (i)
840 fprintf(cmd->fp, ", ");
841 fprintf(cmd->fp, "%d", cmd->groups[i]);
843 fprintf(cmd->fp, "): %s\n", strerror(errno));
844 return 1;
847 return 0;
850 static
852 setup_uid(command_t *cmd)
854 fprintf(stderr, "UIDMODE %d %d\n", cmd->uid_mode, cmd->pwent.pw_uid);
855 if (cmd->uid_mode &&
856 cmd->gid_mode == 0 &&
857 cmd->ngroups == 0 &&
858 setgid(cmd->pwent.pw_gid) < 0) {
859 fprintf(cmd->fp, "unable to setgid for user \"%s\": %s\n",
860 cmd->pwent.pw_name,
861 strerror(errno));
862 return 1;
864 if (cmd->uid_mode &&
865 setuid(cmd->pwent.pw_uid) < 0) {
866 fprintf(cmd->fp, "unable to setuid for user \"%s\": %s\n",
867 cmd->pwent.pw_name,
868 strerror(errno));
869 return 1;
871 return 0;
874 static
876 setup_jail(command_t *cmd)
878 struct jail info;
879 char hostbuf[256];
881 if (gethostname(hostbuf, sizeof(hostbuf) - 1) < 0) {
882 fprintf(cmd->fp, "gethostname() failed: %s\n", strerror(errno));
883 return 1;
885 /* make sure it is zero terminated */
886 hostbuf[sizeof(hostbuf) -1] = 0;
888 bzero(&info, sizeof(info));
889 info.version = 1;
890 info.path = cmd->jaildir;
891 info.hostname = hostbuf;
892 /* info.n_ips, sockaddr_storage ips[] */
894 if (jail(&info) < 0) {
895 fprintf(cmd->fp, "unable to create jail \"%s\": %s\n",
896 cmd->rootdir,
897 strerror(errno));
898 return 1;
900 return 0;
903 static
905 setup_chroot(command_t *cmd)
907 if (chroot(cmd->rootdir) < 0) {
908 fprintf(cmd->fp, "unable to chroot to \"%s\": %s\n",
909 cmd->rootdir,
910 strerror(errno));
911 return 1;
913 return 0;
916 static
918 setup_devfs(command_t *cmd, const char *dir, int domount)
920 struct devfs_mount_info info;
921 struct statfs fs;
922 int rc = 0;
923 char *path;
925 bzero(&info, sizeof(info));
926 info.flags = 0;
927 asprintf(&path, "%s/dev", dir);
929 if (domount) {
930 if (statfs(path, &fs) == 0 &&
931 strcmp(fs.f_fstypename, "devfs") == 0) {
932 fprintf(cmd->fp, "devfs already mounted\n");
933 } else
934 if (mount("devfs", path, MNT_NOEXEC|MNT_NOSUID, &info) < 0) {
935 fprintf(cmd->fp, "cannot mount devfs on %s: %s\n",
936 path, strerror(errno));
937 rc = 1;
939 } else {
940 if (statfs(path, &fs) < 0 ||
941 strcmp(fs.f_fstypename, "devfs") != 0) {
942 fprintf(cmd->fp, "devfs already unmounted\n");
943 } else
944 if (unmount(path, 0) < 0) {
945 fprintf(cmd->fp, "cannot unmount devfs from %s: %s\n",
946 path, strerror(errno));
947 rc = 1;
950 free(path);
951 return rc;
955 * Escape writes. A '.' on a line by itself must be escaped to '..'.
957 static
959 escapewrite(FILE *fp, char *buf, int n, int *statep)
961 int b;
962 int i;
963 int r;
964 char c;
966 b = 0;
967 r = 0;
968 i = 0;
969 while (i < n) {
970 for (i = b; i < n; ++i) {
971 c = buf[i];
973 switch(*statep) {
974 case 0:
976 * beginning of line
978 if (c == '.')
979 *statep = 2;
980 else if (c != '\n')
981 *statep = 1;
982 break;
983 case 1:
985 * middle of line
987 if (c == '\n')
988 *statep = 0;
989 break;
990 case 2:
992 * dot was output at beginning of line
994 if (c == '\n')
995 *statep = 3;
996 else
997 *statep = 1;
998 break;
999 default:
1000 break;
1002 if (*statep == 3) /* flush with escape */
1003 break;
1005 if (i != b) {
1006 n = fwrite(buf, 1, i - b, fp);
1007 if (n > 0)
1008 r += n;
1009 if (n < 0)
1010 r = -1;
1012 if (*statep == 3) { /* added escape */
1013 n = fwrite(".", 1, 1, fp);
1014 /* escapes not counted in r */
1015 *statep = 1;
1016 if (n < 0)
1017 r = -1;
1019 if (r < 0)
1020 break;
1022 return r;