svc - Initial commit, preliminary service manager
[dragonfly.git] / sbin / svc / execute.c
1 /*
2  * Copyright (c) 2014 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
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.
20  *
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.
33  */
34 /*
35  * Handle remote listen/connect and parsing operations.
36  */
37
38 #include "svc.h"
39
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;
47
48 int
49 execute_init(command_t *cmd)
50 {
51         char buf[32];
52         pid_t pid;
53         pid_t stoppingpid = -1;
54         time_t nextstop = 0;
55         int lfd;        /* unix domain listen socket */
56         int pfd;        /* pid file */
57         int xfd;
58         int rc;
59         int fds[2];
60         char c;
61
62         if (cmd->label == NULL || cmd->ext_ac == 0) {
63                 fprintf(cmd->fp, "init requires a label and command\n");
64                 return 1;
65         }
66         fprintf(cmd->fp, "initializing new service: %s\n", cmd->label);
67
68         if (pipe(fds) < 0) {
69                 fprintf(cmd->fp, "Unable to create pipe: %s\n",
70                         strerror(errno));
71                 return 1;
72         }
73         if ((xfd = open("/dev/null", O_RDWR)) < 0) {
74                 fprintf(cmd->fp, "Unable to open /dev/null: %s\n",
75                         strerror(errno));
76                 return 1;
77         }
78
79         /*
80          * Setup pidfile and unix domain listen socket and lock the
81          * pidfile.
82          */
83         rc = setup_pid_and_socket(cmd, &lfd, &pfd);
84         if (rc) {
85                 close(fds[0]);
86                 close(fds[1]);
87                 return rc;
88         }
89
90         /*
91          * Detach the service
92          */
93         if ((pid = fork()) != 0) {
94                 /*
95                  * Parent
96                  */
97                 close(fds[1]);
98                 if (pid < 0) {
99                         fprintf(cmd->fp, "fork failed: %s\n", strerror(errno));
100                 } else {
101                         /*
102                          * Fill-in pfd before returning.
103                          */
104                         snprintf(buf, sizeof(buf), "%d\n", (int)pid);
105                         write(pfd, buf, strlen(buf));
106                 }
107                 close(lfd);
108                 close(pfd);
109
110                 /*
111                  * Wait for child to completely detach from the tty
112                  * before returning.
113                  */
114                 read(fds[0], &c, 1);
115                 close(fds[0]);
116
117                 return 0;
118         }
119
120         /*
121          * Service demon is now running.
122          *
123          * Detach from terminal, setup logfile.
124          */
125         close(fds[0]);
126         fclose(cmd->fp);
127         if (cmd->logfile)
128                 cmd->fp = fopen(cmd->logfile, "a");
129         else
130                 cmd->fp = fdopen(dup(xfd), "a");
131         if (xfd != 0) {
132                 dup2(xfd, 0);
133                 close(xfd);
134         }
135         dup2(fileno(cmd->fp), 1);
136         dup2(fileno(cmd->fp), 2);
137         if ((xfd = open("/dev/tty", O_RDWR)) >= 0) {
138                 ioctl(xfd, TIOCNOTTY, 0);       /* no controlling tty */
139                 close(xfd);
140         }
141         setsid();                               /* new session */
142
143         /*
144          * Signal parent that we are completely detached now.
145          */
146         c = 1;
147         write(fds[1], &c, 1);
148         close(fds[1]);
149         InitCmd = cmd;
150
151         /*
152          * Start accept thread for unix domain listen socket.
153          * Start service
154          */
155         remote_listener(cmd, lfd);
156         pthread_mutex_lock(&serial_mtx);
157         execute_start(cmd);
158
159         /*
160          * Become the reaper for all children recursively.
161          */
162         if (procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL) < 0) {
163                 fprintf(cmd->fp, "svc is unable to become the "
164                                  "reaper for its children\n");
165                 fflush(cmd->fp);
166         }
167
168         /*
169          * Main loop is the reaper
170          */
171         for (;;) {
172                 union reaper_info info;
173                 struct timespec ts;
174                 int status;
175                 int dt;
176                 pid_t usepid;
177
178                 /*
179                  * If we are running just block doing normal reaping,
180                  * if we are stopping we have to poll for reaping while
181                  * we handle stopping.
182                  */
183                 if (RunState == RS_STARTED) {
184                         pthread_mutex_unlock(&serial_mtx);
185                         pid = wait3(&status, 0, NULL);
186                         pthread_mutex_lock(&serial_mtx);
187                 } else {
188                         pid = wait3(&status, WNOHANG, NULL);
189                 }
190                 clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
191
192                 if (pid > 0) {
193                         if (pid == DirectPid) {
194                                 fprintf(cmd->fp,
195                                         "svc %s: lost direct child %d\n",
196                                         cmd->label, pid);
197                                 fflush(cmd->fp);
198                                 DirectPid = -1;
199                                 if (cmd->restart_some) {
200                                         RunState = RS_STOPPED;
201                                         LastStop = ts.tv_sec;
202                                 } /* else still considered normal run state */
203                         } else {
204                                 /* reap random disconnected child */
205                                 fprintf(cmd->fp,
206                                         "svc %s: reap indirect child %d\n",
207                                         cmd->label,
208                                         (int)pid);
209                         }
210                 }
211
212                 /*
213                  * Calculate the pid to potentially act on and/or
214                  * determine if any children still exist.
215                  */
216                 if (DirectPid >= 0) {
217                         usepid = DirectPid;
218                 } else if (procctl(P_PID, getpid(),
219                                    PROC_REAP_STATUS, &info) == 0) {
220                         usepid = info.status.pid_head;
221                 } else {
222                         usepid = -1;
223                 }
224
225                 /*
226                  * If stoppingpid changes we have to reset the TERM->KILL
227                  * timer.
228                  */
229                 if (usepid < 0) {
230                         if (RunState != RS_STOPPED) {
231                                 RunState = RS_STOPPED;
232                                 LastStop = ts.tv_sec;
233                         }
234                 } else if (stoppingpid != usepid &&
235                            (RunState == RS_STOPPING2 ||
236                             RunState == RS_STOPPING3)) {
237                         RunState = RS_STOPPING1;
238                 }
239                 stoppingpid = usepid;
240
241                 /*
242                  * State machine
243                  */
244                 switch(RunState) {
245                 case RS_STARTED:
246                         if (usepid < 0) {
247                                 RunState = RS_STOPPED;
248                                 LastStop = ts.tv_sec;
249                         }
250                         break;
251                 case RS_STOPPED:
252                         dt = (int)(ts.tv_sec - LastStop);
253
254                         if (cmd->exit_mode) {
255                                 /*
256                                  * Service demon was told to exit on service
257                                  * stop (-x passed to init).
258                                  */
259                                 fprintf(cmd->fp,
260                                         "svc %s: service demon exiting\n",
261                                         cmd->label);
262                                 exit(0);
263                         } else if (cmd->manual_stop) {
264                                 /*
265                                  * Service demon was told to stop via
266                                  * commanded (not automatic) action.  We
267                                  * do not auto-restart the service in
268                                  * this situation.
269                                  */
270                                 pthread_mutex_unlock(&serial_mtx);
271                                 if (dt < 0 || dt > 60)
272                                         sleep(60);
273                                 else
274                                         sleep(1);
275                                 pthread_mutex_lock(&serial_mtx);
276                         } else if (cmd->restart_some || cmd->restart_all) {
277                                 /*
278                                  * Handle automatic restarts
279                                  */
280                                 if (dt > cmd->restart_timo) {
281                                         execute_start(cmd);
282                                 } else {
283                                         pthread_mutex_unlock(&serial_mtx);
284                                         sleep(1);
285                                         pthread_mutex_lock(&serial_mtx);
286                                 }
287                         } else {
288                                 /*
289                                  * No automatic restart was configured,
290                                  * wait for commanded action.
291                                  */
292                                 pthread_mutex_unlock(&serial_mtx);
293                                 if (dt < 0 || dt > 60)
294                                         sleep(60);
295                                 else
296                                         sleep(1);
297                                 pthread_mutex_lock(&serial_mtx);
298                         }
299                         break;
300                 case RS_STOPPING1:
301                         /*
302                          * Reset TERM->KILL timer
303                          */
304                         nextstop = ts.tv_sec;
305                         RunState = RS_STOPPING2;
306                         /* fall through */
307                 case RS_STOPPING2:
308                         if (cmd->termkill_timo == 0) {
309                                 nextstop = ts.tv_sec - 1;
310                         } else {
311                                 kill(stoppingpid, SIGTERM);
312                                 fprintf(cmd->fp, "svc %s: sigterm %d\n",
313                                         cmd->label, stoppingpid);
314                                 sleep(1);
315                         }
316                         RunState = RS_STOPPING3;
317                         /* fall through */
318                 case RS_STOPPING3:
319                         dt = (int)(ts.tv_sec - nextstop);
320                         if (dt > cmd->termkill_timo) {
321                                 fprintf(cmd->fp, "svc %s: sigkill %d\n",
322                                         cmd->label, stoppingpid);
323                                 kill(stoppingpid, SIGKILL);
324                         }
325                         sleep(1);
326                         break;
327                 }
328         }
329         pthread_mutex_unlock(&serial_mtx);
330         exit(0);
331         /* does not return */
332 }
333
334 int
335 execute_start(command_t *cmd)
336 {
337         struct timespec ts;
338         int maxwait = 60;
339
340         while (RunState == RS_STOPPING1 ||
341                RunState == RS_STOPPING2 ||
342                RunState == RS_STOPPING3) {
343                 fprintf(cmd->fp,
344                         "svc %s: Waiting for previous action to complete\n",
345                         cmd->label);
346                 fflush(cmd->fp);
347                 pthread_mutex_unlock(&serial_mtx);
348                 sleep(1);
349                 pthread_mutex_lock(&serial_mtx);
350                 if (--maxwait == 0) {
351                         fprintf(cmd->fp,
352                                 "svc %s: Giving up waiting for action\n",
353                                 cmd->label);
354                         fflush(cmd->fp);
355                         break;
356                 }
357         }
358         if (RunState == RS_STARTED) {
359                 fprintf(cmd->fp, "svc %s: Already started pid %d\n",
360                         cmd->label, DirectPid);
361                 fflush(cmd->fp);
362                 return 0;
363         }
364
365         clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
366         if ((DirectPid = fork()) == 0) {
367                 closefrom(3);
368                 execvp(InitCmd->ext_av[0], InitCmd->ext_av);
369                 exit(99);
370         }
371         if (DirectPid >= 0) {
372                 RunState = RS_STARTED;
373                 LastStart = ts.tv_sec;
374         } else {
375                 RunState = RS_STOPPED;
376                 LastStop = ts.tv_sec;
377         }
378         InitCmd->manual_stop = 0;
379         fprintf(cmd->fp, "svc %s: Starting pid %d\n", cmd->label, DirectPid);
380         fflush(cmd->fp);
381
382         return 0;
383 }
384
385 int
386 execute_restart(command_t *cmd)
387 {
388         int rc;
389
390         rc = execute_stop(cmd) + execute_start(cmd);
391         return rc;
392 }
393
394 int
395 execute_stop(command_t *cmd)
396 {
397         struct timespec ts;
398         int save_restart_some;
399         int save_restart_all;
400         int maxwait = 60;
401
402         save_restart_some = InitCmd->restart_some;
403         save_restart_all = InitCmd->restart_all;
404         if (cmd->commanded)
405                 InitCmd->manual_stop = 1;
406         if (cmd->commanded && (cmd->restart_some || cmd->restart_all)) {
407                 InitCmd->restart_some = cmd->restart_some;
408                 InitCmd->restart_all = cmd->restart_all;
409         }
410         fprintf(cmd->fp, "svc %s: Stopping\n", cmd->label);
411         fflush(cmd->fp);
412
413         if (DirectPid >= 0) {
414                 kill(DirectPid, SIGTERM);
415         }
416
417         clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
418         LastStop = ts.tv_sec;
419         RunState = RS_STOPPING1;
420
421         /*
422          * If commanded (verses automatic), we are running remote in our
423          * own thread and we need to wait for the action to complete.
424          */
425         if (cmd->commanded) {
426                 while (RunState == RS_STOPPING1 ||
427                        RunState == RS_STOPPING2 ||
428                        RunState == RS_STOPPING3) {
429                         fprintf(cmd->fp,
430                                 "svc %s: Waiting for service to stop\n",
431                                 cmd->label);
432                         fflush(cmd->fp);
433                         pthread_mutex_unlock(&serial_mtx);
434                         sleep(1);
435                         pthread_mutex_lock(&serial_mtx);
436                         if (--maxwait == 0) {
437                                 fprintf(cmd->fp,
438                                         "svc %s: Giving up waiting for stop\n",
439                                         cmd->label);
440                                 fflush(cmd->fp);
441                                 break;
442                         }
443                 }
444                 if (cmd->restart_some || cmd->restart_all) {
445                         InitCmd->restart_some = save_restart_some;
446                         InitCmd->restart_all = save_restart_all;
447                 }
448         }
449
450         return 0;
451 }
452
453 int
454 execute_exit(command_t *cmd)
455 {
456         execute_stop(cmd);
457         fprintf(cmd->fp, "svc %s: Exiting\n", cmd->label);
458
459         exit(0);
460 }
461
462 int
463 execute_list(command_t *cmd)
464 {
465         fprintf(cmd->fp, "%-16s\n", cmd->label);
466
467         return 0;
468 }
469
470 int
471 execute_status(command_t *cmd)
472 {
473         const char *state;
474
475         switch(RunState) {
476         case RS_STOPPED:
477                 if (InitCmd && InitCmd->manual_stop)
478                         state = "stopped (manual)";
479                 else
480                         state = "stopped";
481                 break;
482         case RS_STARTED:
483                 state = "running";
484                 break;
485         case RS_STOPPING1:
486         case RS_STOPPING2:
487         case RS_STOPPING3:
488                 state = "killing";
489                 break;
490         default:
491                 state = "unknown";
492                 break;
493         }
494
495         fprintf(cmd->fp, "%-16s %s\n", cmd->label, state);
496
497         return 0;
498 }
499
500 int
501 execute_log(command_t *cmd __unused)
502 {
503         return 0;
504 }
505
506 int
507 execute_logfile(command_t *cmd)
508 {
509         char *logfile;
510         int fd;
511         int rc;
512
513         logfile = cmd->logfile;
514         if (cmd->ext_av && cmd->ext_av[0])
515                 logfile = cmd->ext_av[0];
516         if (logfile == NULL && InitCmd)
517                 logfile = InitCmd->logfile;
518
519         rc = 0;
520         if (InitCmd && logfile) {
521                 if (InitCmd->logfile &&
522                     strcmp(InitCmd->logfile, logfile) == 0) {
523                         fprintf(cmd->fp, "svc %s: Reopen logfile %s\n",
524                                 cmd->label, logfile);
525                 } else {
526                         fprintf(cmd->fp, "svc %s: Change logfile to %s\n",
527                                 cmd->label, logfile);
528                 }
529                 fd = open(logfile, O_WRONLY|O_CREAT|O_APPEND, 0640);
530                 if (fd >= 0) {
531                         fflush(stdout);
532                         fflush(stderr);
533                         dup2(fd, fileno(stdout));
534                         dup2(fd, fileno(stderr));
535                         sreplace(&InitCmd->logfile, logfile);
536                         close(fd);
537                 } else {
538                         fprintf(cmd->fp,
539                                 "svc %s: Unable to open/create \"%s\": %s\n",
540                                 cmd->label, logfile, strerror(errno));
541                         rc = 1;
542                 }
543         }
544         return rc;
545 }