#include "svc.h"
pthread_mutex_t serial_mtx;
time_t LastStart;
time_t LastStop;
pid_t DirectPid = -1;
runstate_t RunState = RS_STOPPED;
command_t *InitCmd;
int RestartCounter;
static void *logger_thread(void *arg);
static void setstate_stopped(command_t *cmd, struct timespec *ts);
static int setup_gid(command_t *cmd);
static int setup_uid(command_t *cmd);
static int setup_jail(command_t *cmd);
static int setup_chroot(command_t *cmd);
static int setup_devfs(command_t *cmd, const char *dir, int domount);
static int escapewrite(FILE *fp, char *buf, int n, int *statep);
int
execute_init(command_t *cmd)
{
char buf[32];
pid_t pid;
pid_t stoppingpid = -1;
pthread_t logtd;
time_t nextstop = 0;
int lfd;
int pfd;
int xfd;
int rc;
int fds[2];
char c;
if (cmd->label == NULL || cmd->ext_ac == 0) {
fprintf(cmd->fp, "init requires a label and command\n");
return 1;
}
fprintf(cmd->fp, "initializing new service: %s\n", cmd->label);
if ((xfd = open("/dev/null", O_RDWR)) < 0) {
fprintf(cmd->fp, "Unable to open /dev/null: %s\n",
strerror(errno));
return 1;
}
rc = setup_pid_and_socket(cmd, &lfd, &pfd);
if (rc)
return rc;
if (cmd->foreground) {
fds[0] = -1;
fds[1] = -1;
pid = 0;
} else {
if (pipe(fds) < 0) {
fprintf(cmd->fp, "Unable to create pipe: %s\n",
strerror(errno));
close(lfd);
close(pfd);
remove_pid_and_socket(cmd, cmd->label);
return 1;
}
pid = fork();
}
if (pid != 0) {
close(fds[1]);
if (pid < 0) {
fprintf(cmd->fp, "fork failed: %s\n", strerror(errno));
close(lfd);
close(pfd);
close(fds[0]);
close(fds[1]);
remove_pid_and_socket(cmd, cmd->label);
return 1;
} else {
snprintf(buf, sizeof(buf), "%d\n", (int)pid);
write(pfd, buf, strlen(buf));
}
close(lfd);
close(pfd);
read(fds[0], &c, 1);
close(fds[0]);
return 0;
}
if (cmd->proctitle) {
setproctitle("%s - %s", cmd->label, cmd->proctitle);
} else {
setproctitle("%s", cmd->label);
}
if (cmd->mountdev) {
if (cmd->jaildir)
setup_devfs(cmd, cmd->jaildir, 1);
else if (cmd->rootdir)
setup_devfs(cmd, cmd->rootdir, 1);
}
if (cmd->foreground == 0) {
close(fds[0]);
fds[0] = -1;
}
if (xfd != 0)
dup2(xfd, 0);
if (cmd->foreground == 0) {
int tfd;
if (xfd != 1)
dup2(xfd, 1);
if (xfd != 2)
dup2(xfd, 2);
if ((tfd = open("/dev/tty", O_RDWR)) >= 0) {
ioctl(tfd, TIOCNOTTY, 0);
close(tfd);
}
setsid();
}
if (cmd->logfile && strcmp(cmd->logfile, "/dev/null") == 0)
cmd->logfd = -1;
else if (cmd->logfile)
cmd->logfd = open(cmd->logfile, O_WRONLY|O_CREAT|O_APPEND, 0640);
else if (cmd->foreground)
cmd->logfd = dup(1);
else
cmd->logfd = -1;
c = 1;
if (cmd->foreground == 0) {
write(fds[1], &c, 1);
close(fds[1]);
fds[1] = -1;
}
InitCmd = cmd;
pipe(cmd->logfds);
if (cmd->fp != stdout)
fclose(cmd->fp);
cmd->fp = fdopen(cmd->logfds[1], "w");
if (xfd > 2) {
close(xfd);
xfd = -1;
}
pthread_cond_init(&cmd->logcond, NULL);
pthread_mutex_lock(&serial_mtx);
pthread_create(&logtd, NULL, logger_thread, cmd);
remote_listener(cmd, lfd);
if (procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL) < 0) {
fprintf(cmd->fp, "svc is unable to become the "
"reaper for its children\n");
fflush(cmd->fp);
}
execute_start(cmd);
for (;;) {
union reaper_info info;
struct timespec ts;
int status;
int dt;
pid_t usepid;
fflush(cmd->fp);
if (RunState == RS_STARTED) {
pthread_mutex_unlock(&serial_mtx);
pid = wait3(&status, 0, NULL);
pthread_mutex_lock(&serial_mtx);
} else {
pid = wait3(&status, WNOHANG, NULL);
}
clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
if (pid > 0) {
if (pid == DirectPid) {
fprintf(cmd->fp,
"svc %s: lost direct child %d\n",
cmd->label, pid);
fflush(cmd->fp);
DirectPid = -1;
if (cmd->restart_some) {
setstate_stopped(cmd, &ts);
}
} else if (cmd->debug) {
fprintf(cmd->fp,
"svc %s: reap indirect child %d\n",
cmd->label,
(int)pid);
}
}
if (DirectPid >= 0) {
usepid = DirectPid;
} else if (procctl(P_PID, getpid(),
PROC_REAP_STATUS, &info) == 0) {
usepid = info.status.pid_head;
} else {
usepid = -1;
}
if (cmd->debug) {
fprintf(stderr, "svc %s: usepid %d\n",
cmd->label, usepid);
fflush(stderr);
}
if (usepid < 0) {
setstate_stopped(cmd, &ts);
} else if (stoppingpid != usepid &&
(RunState == RS_STOPPING2 ||
RunState == RS_STOPPING3)) {
RunState = RS_STOPPING1;
}
stoppingpid = usepid;
switch(RunState) {
case RS_STARTED:
if (usepid < 0)
setstate_stopped(cmd, &ts);
break;
case RS_STOPPED:
dt = (int)(ts.tv_sec - LastStop);
if (cmd->exit_mode) {
fprintf(cmd->fp,
"svc %s: service demon exiting\n",
cmd->label);
remove_pid_and_socket(cmd, cmd->label);
goto exitloop;
} else if (cmd->manual_stop) {
pthread_mutex_unlock(&serial_mtx);
if (dt < 0 || dt > 60)
sleep(60);
else
sleep(1);
pthread_mutex_lock(&serial_mtx);
} else if (cmd->restart_some || cmd->restart_all) {
if (dt > cmd->restart_timo) {
execute_start(cmd);
} else {
pthread_mutex_unlock(&serial_mtx);
sleep(1);
pthread_mutex_lock(&serial_mtx);
}
} else {
pthread_mutex_unlock(&serial_mtx);
if (dt < 0 || dt > 60)
sleep(60);
else
sleep(1);
pthread_mutex_lock(&serial_mtx);
}
break;
case RS_STOPPING1:
nextstop = ts.tv_sec;
RunState = RS_STOPPING2;
case RS_STOPPING2:
if (cmd->termkill_timo == 0) {
nextstop = ts.tv_sec - 1;
} else {
kill(stoppingpid, SIGTERM);
fprintf(cmd->fp, "svc %s: sigterm %d\n",
cmd->label, stoppingpid);
sleep(1);
}
RunState = RS_STOPPING3;
case RS_STOPPING3:
dt = (int)(ts.tv_sec - nextstop);
if (dt > cmd->termkill_timo) {
fprintf(cmd->fp, "svc %s: sigkill %d\n",
cmd->label, stoppingpid);
kill(stoppingpid, SIGKILL);
}
sleep(1);
break;
}
}
exitloop:
pthread_mutex_unlock(&serial_mtx);
if (cmd->mountdev) {
if (cmd->jaildir)
setup_devfs(cmd, cmd->jaildir, 0);
else if (cmd->rootdir)
setup_devfs(cmd, cmd->rootdir, 0);
}
exit(0);
}
int
execute_start(command_t *cmd)
{
struct timespec ts;
int maxwait = 60;
while (RunState == RS_STOPPING1 ||
RunState == RS_STOPPING2 ||
RunState == RS_STOPPING3) {
fprintf(cmd->fp,
"svc %s: Waiting for previous action to complete\n",
cmd->label);
fflush(cmd->fp);
pthread_mutex_unlock(&serial_mtx);
sleep(1);
pthread_mutex_lock(&serial_mtx);
if (--maxwait == 0) {
fprintf(cmd->fp,
"svc %s: Giving up waiting for action\n",
cmd->label);
fflush(cmd->fp);
break;
}
}
if (RunState == RS_STARTED) {
fprintf(cmd->fp, "svc %s: Already started pid %d\n",
cmd->label, DirectPid);
fflush(cmd->fp);
return 0;
}
clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
if ((DirectPid = fork()) == 0) {
fflush(InitCmd->fp);
dup2(fileno(InitCmd->fp), 1);
dup2(fileno(InitCmd->fp), 2);
closefrom(3);
if (cmd->jaildir)
setup_jail(cmd);
else if (cmd->rootdir)
setup_chroot(cmd);
setup_gid(cmd);
setup_uid(cmd);
execvp(InitCmd->ext_av[0], InitCmd->ext_av);
exit(99);
}
if (DirectPid >= 0) {
RunState = RS_STARTED;
LastStart = ts.tv_sec;
} else {
setstate_stopped(InitCmd, &ts);
}
InitCmd->manual_stop = 0;
fprintf(cmd->fp, "svc %s: Starting pid %d\n", cmd->label, DirectPid);
fflush(cmd->fp);
return 0;
}
int
execute_restart(command_t *cmd)
{
int rc;
rc = execute_stop(cmd) + execute_start(cmd);
return rc;
}
int
execute_stop(command_t *cmd)
{
union reaper_info info;
struct timespec ts;
int save_restart_some;
int save_restart_all;
int maxwait = 60;
save_restart_some = InitCmd->restart_some;
save_restart_all = InitCmd->restart_all;
if (cmd->commanded)
InitCmd->manual_stop = 1;
if (cmd->commanded && (cmd->restart_some || cmd->restart_all)) {
InitCmd->restart_some = cmd->restart_some;
InitCmd->restart_all = cmd->restart_all;
}
fprintf(cmd->fp, "svc %s: Stopping\n", cmd->label);
fflush(cmd->fp);
if (DirectPid >= 0) {
kill(DirectPid, SIGTERM);
} else {
if (procctl(P_PID, getpid(), PROC_REAP_STATUS, &info) == 0 &&
info.status.pid_head > 0) {
kill(info.status.pid_head, SIGTERM);
}
}
clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
LastStop = ts.tv_sec;
RunState = RS_STOPPING1;
if (cmd->commanded) {
while (RunState == RS_STOPPING1 ||
RunState == RS_STOPPING2 ||
RunState == RS_STOPPING3) {
fprintf(cmd->fp,
"svc %s: Waiting for service to stop\n",
cmd->label);
fflush(cmd->fp);
pthread_mutex_unlock(&serial_mtx);
sleep(1);
pthread_mutex_lock(&serial_mtx);
if (--maxwait == 0) {
fprintf(cmd->fp,
"svc %s: Giving up waiting for stop\n",
cmd->label);
fflush(cmd->fp);
break;
}
}
if (cmd->restart_some || cmd->restart_all) {
InitCmd->restart_some = save_restart_some;
InitCmd->restart_all = save_restart_all;
}
}
return 0;
}
int
execute_exit(command_t *cmd)
{
if (cmd->commanded) {
InitCmd->restart_some = 0;
InitCmd->restart_all = 1;
InitCmd->exit_mode = 1;
} else {
cmd->exit_mode = 1;
}
fprintf(cmd->fp, "svc %s: Stopping and Exiting\n", cmd->label);
execute_stop(cmd);
return 0;
}
int
execute_list(command_t *cmd)
{
fprintf(cmd->fp, "%-16s\n", cmd->label);
return 0;
}
int
execute_status(command_t *cmd)
{
const char *state;
switch(RunState) {
case RS_STOPPED:
if (InitCmd && InitCmd->exit_mode)
state = "stopped (exiting)";
else if (InitCmd && InitCmd->manual_stop)
state = "stopped (manual)";
else
state = "stopped";
break;
case RS_STARTED:
state = "running";
break;
case RS_STOPPING1:
case RS_STOPPING2:
case RS_STOPPING3:
state = "killing";
break;
default:
state = "unknown";
break;
}
fprintf(cmd->fp, "%-16s %s\n", cmd->label, state);
return 0;
}
int
execute_log(command_t *cmd)
{
int lbsize = (int)sizeof(cmd->logbuf);
int lbmask = lbsize - 1;
int windex;
int n;
int lastnl;
int dotstate;
char buf[LOGCHUNK];
assert(InitCmd);
if (cmd->tail_mode == 2)
windex = InitCmd->logwindex;
else
windex = InitCmd->logwindex - InitCmd->logcount;
lastnl = 1;
dotstate = 0;
for (;;) {
n = InitCmd->logwindex - windex;
if (n < 0 || n > InitCmd->logcount) {
windex = InitCmd->logwindex - InitCmd->logcount;
pthread_mutex_unlock(&serial_mtx);
fprintf(cmd->fp, "\n(LOG DATA LOST)\n");
pthread_mutex_lock(&serial_mtx);
continue;
}
if (n > lbsize - (windex & lbmask))
n = lbsize - (windex & lbmask);
if (n > LOGCHUNK)
n = LOGCHUNK;
if (n == 0) {
if (cmd->tail_mode == 0)
break;
pthread_cond_wait(&InitCmd->logcond, &serial_mtx);
continue;
}
bcopy(InitCmd->logbuf + (windex & lbmask), buf, n);
pthread_mutex_unlock(&serial_mtx);
n = escapewrite(cmd->fp, buf, n, &dotstate);
fflush(cmd->fp);
if (n > 0)
lastnl = (buf[n-1] == '\n');
pthread_mutex_lock(&serial_mtx);
if (n < 0)
break;
windex += n;
}
if (lastnl == 0) {
pthread_mutex_unlock(&serial_mtx);
fprintf(cmd->fp, "\n");
pthread_mutex_lock(&serial_mtx);
}
return 0;
}
int
execute_logfile(command_t *cmd)
{
char *logfile;
int fd;
int rc;
assert(InitCmd);
logfile = cmd->logfile;
if (cmd->ext_av && cmd->ext_av[0])
logfile = cmd->ext_av[0];
if (logfile == NULL)
logfile = InitCmd->logfile;
rc = 0;
if (logfile) {
if (InitCmd->logfile &&
strcmp(InitCmd->logfile, logfile) == 0) {
fprintf(cmd->fp, "svc %s: Reopen logfile %s\n",
cmd->label, logfile);
} else {
fprintf(cmd->fp, "svc %s: Change logfile to %s\n",
cmd->label, logfile);
}
if (InitCmd->logfd >= 0) {
close(InitCmd->logfd);
InitCmd->logfd = -1;
}
if (strcmp(logfile, "/dev/null") == 0) {
sreplace(&InitCmd->logfile, logfile);
} else {
fd = open(logfile, O_WRONLY|O_CREAT|O_APPEND, 0640);
if (fd >= 0) {
InitCmd->logfd = fd;
sreplace(&InitCmd->logfile, logfile);
} else {
fprintf(cmd->fp,
"svc %s: Unable to open/create "
"\"%s\": %s\n",
cmd->label,
logfile, strerror(errno));
rc = 1;
}
}
}
return rc;
}
int
execute_help(command_t *cmd)
{
fprintf(cmd->fp,
"svc [options] directive [label [additional_args]]\n"
"\n"
"Directives: init start stop stopall restart exit\n"
" kill list status log logf tailf logfile\n"
" help\n"
);
return 0;
}
static
void *
logger_thread(void *arg)
{
command_t *cmd = arg;
int lbsize = (int)sizeof(cmd->logbuf);
int lbmask = lbsize - 1;
int windex;
int n;
pthread_detach(pthread_self());
pthread_mutex_lock(&serial_mtx);
for (;;) {
n = cmd->logcount - (lbsize - LOGCHUNK);
if (n > 0) {
cmd->logcount -= n;
cmd->logwindex += n;
}
windex = cmd->logwindex & lbmask;
n = lbsize - windex;
if (n > LOGCHUNK)
n = LOGCHUNK;
pthread_mutex_unlock(&serial_mtx);
n = read(cmd->logfds[0], cmd->logbuf + windex, n);
pthread_mutex_lock(&serial_mtx);
if (n > 0) {
if (cmd->logfd >= 0)
write(cmd->logfd, cmd->logbuf + windex, n);
cmd->logcount += n;
cmd->logwindex += n;
pthread_cond_signal(&cmd->logcond);
}
if (n == 0 || (n < 0 && errno != EINTR))
break;
}
pthread_mutex_unlock(&serial_mtx);
return NULL;
}
static
void
setstate_stopped(command_t *cmd, struct timespec *ts)
{
if (RunState != RS_STOPPED) {
RunState = RS_STOPPED;
LastStop = ts->tv_sec;
if (cmd->sync_mode)
sync();
}
}
static
int
setup_gid(command_t *cmd)
{
int i;
if (cmd->gid_mode &&
setgid(cmd->grent.gr_gid) < 0) {
fprintf(cmd->fp, "unable to setgid to \"%s\": %s\n",
cmd->grent.gr_name, strerror(errno));
return 1;
}
if (cmd->ngroups) {
if (setgroups(cmd->ngroups, cmd->groups) < 0) {
fprintf(cmd->fp, "unable to setgroups to (");
for (i = 0; i < cmd->ngroups; ++i) {
if (i)
fprintf(cmd->fp, ", ");
fprintf(cmd->fp, "%d", cmd->groups[i]);
}
fprintf(cmd->fp, "): %s\n", strerror(errno));
return 1;
}
}
return 0;
}
static
int
setup_uid(command_t *cmd)
{
fprintf(stderr, "UIDMODE %d %d\n", cmd->uid_mode, cmd->pwent.pw_uid);
if (cmd->uid_mode &&
cmd->gid_mode == 0 &&
cmd->ngroups == 0 &&
setgid(cmd->pwent.pw_gid) < 0) {
fprintf(cmd->fp, "unable to setgid for user \"%s\": %s\n",
cmd->pwent.pw_name,
strerror(errno));
return 1;
}
if (cmd->uid_mode &&
setuid(cmd->pwent.pw_uid) < 0) {
fprintf(cmd->fp, "unable to setuid for user \"%s\": %s\n",
cmd->pwent.pw_name,
strerror(errno));
return 1;
}
return 0;
}
static
int
setup_jail(command_t *cmd)
{
struct jail info;
char hostbuf[256];
if (gethostname(hostbuf, sizeof(hostbuf) - 1) < 0) {
fprintf(cmd->fp, "gethostname() failed: %s\n", strerror(errno));
return 1;
}
hostbuf[sizeof(hostbuf) -1] = 0;
bzero(&info, sizeof(info));
info.version = 1;
info.path = cmd->jaildir;
info.hostname = hostbuf;
if (jail(&info) < 0) {
fprintf(cmd->fp, "unable to create jail \"%s\": %s\n",
cmd->rootdir,
strerror(errno));
return 1;
}
return 0;
}
static
int
setup_chroot(command_t *cmd)
{
if (chroot(cmd->rootdir) < 0) {
fprintf(cmd->fp, "unable to chroot to \"%s\": %s\n",
cmd->rootdir,
strerror(errno));
return 1;
}
return 0;
}
static
int
setup_devfs(command_t *cmd, const char *dir, int domount)
{
struct devfs_mount_info info;
struct statfs fs;
int rc = 0;
char *path;
bzero(&info, sizeof(info));
info.flags = 0;
asprintf(&path, "%s/dev", dir);
if (domount) {
if (statfs(path, &fs) == 0 &&
strcmp(fs.f_fstypename, "devfs") == 0) {
fprintf(cmd->fp, "devfs already mounted\n");
} else
if (mount("devfs", path, MNT_NOEXEC|MNT_NOSUID, &info) < 0) {
fprintf(cmd->fp, "cannot mount devfs on %s: %s\n",
path, strerror(errno));
rc = 1;
}
} else {
if (statfs(path, &fs) < 0 ||
strcmp(fs.f_fstypename, "devfs") != 0) {
fprintf(cmd->fp, "devfs already unmounted\n");
} else
if (unmount(path, 0) < 0) {
fprintf(cmd->fp, "cannot unmount devfs from %s: %s\n",
path, strerror(errno));
rc = 1;
}
}
free(path);
return rc;
}
static
int
escapewrite(FILE *fp, char *buf, int n, int *statep)
{
int b;
int i;
int r;
char c;
b = 0;
r = 0;
i = 0;
while (i < n) {
for (i = b; i < n; ++i) {
c = buf[i];
switch(*statep) {
case 0:
if (c == '.')
*statep = 2;
else if (c != '\n')
*statep = 1;
break;
case 1:
if (c == '\n')
*statep = 0;
break;
case 2:
if (c == '\n')
*statep = 3;
else
*statep = 1;
break;
default:
break;
}
if (*statep == 3)
break;
}
if (i != b) {
n = fwrite(buf, 1, i - b, fp);
if (n > 0)
r += n;
if (n < 0)
r = -1;
}
if (*statep == 3) {
n = fwrite(".", 1, 1, fp);
*statep = 1;
if (n < 0)
r = -1;
}
if (r < 0)
break;
}
return r;
}