svc - Initial commit, preliminary service manager
[dragonfly.git] / sbin / svc / svc.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  * SERVICE MANAGER
36  *
37  * This program builds an environment to run a service in and provides
38  * numerous options for naming, tracking, and management.  It uses
39  * reapctl(2) to corral the processes under management.
40  */
41
42 #include "svc.h"
43
44 static int execute_remote(command_t *cmd, int (*func)(command_t *cmd));
45 static int process_jailspec(command_t *cmd, const char *spec);
46
47 int
48 main(int ac, char **av)
49 {
50         command_t cmd;
51         int rc;
52
53         signal(SIGPIPE, SIG_IGN);
54
55         rc = process_cmd(&cmd, stdout, ac, av);
56         cmd.cmdline = 1;        /* commanded from front-end */
57         cmd.commanded = 1;      /* commanded action (vs automatic) */
58         if (rc == 0)
59                 rc = execute_cmd(&cmd);
60         free_cmd(&cmd);
61
62         return rc;
63 }
64
65 int
66 process_cmd(command_t *cmd, FILE *fp, int ac, char **av)
67 {
68         const char *optstr = "dfp:r:R:xst:u:g:G:l:c:C:j:J:k:T:F:";
69         struct group *grent;
70         struct passwd *pwent;
71         char *sub;
72         char *cpy;
73         int rc = 1;
74         int ch;
75         int i;
76
77         bzero(cmd, sizeof(*cmd));
78         cmd->fp = fp;                           /* error and output reporting */
79         sreplace(&cmd->piddir, "/var/run");     /* must not be NULL */
80         cmd->termkill_timo = -1;                /* will use default value */
81         cmd->orig_ac = ac;
82         cmd->orig_av = av;
83
84         optind = 1;
85         opterr = 1;
86         optreset = 1;
87
88         while ((ch = getopt(ac, av, optstr)) != -1) {
89                 switch(ch) {
90                 case 'd':
91                         cmd->debug = 1;
92                         cmd->foreground = 1;
93                         break;
94                 case 'f':
95                         cmd->foreground = 1;
96                         break;
97                 case 'p':
98                         sreplace(&cmd->piddir, optarg);
99                         break;
100                 case 'r':
101                         cmd->restart_some = 1;
102                         cmd->restart_all = 0;
103                         cmd->restart_timo = strtol(optarg, NULL, 0);
104                         break;
105                 case 'R':
106                         cmd->restart_some = 0;
107                         cmd->restart_all = 1;
108                         cmd->restart_timo = strtol(optarg, NULL, 0);
109                         break;
110                 case 'x':
111                         cmd->exit_mode = 1;
112                         break;
113                 case 's':
114                         cmd->sync_mode = 1;
115                         break;
116                 case 't':
117                         cmd->termkill_timo = strtoul(optarg, NULL, 0);
118                         break;
119                 case 'u':
120                         if (isdigit(optarg[0])) {
121                                 pwent = getpwnam(optarg);
122                         } else {
123                                 pwent = getpwuid(strtol(optarg, NULL, 0));
124                         }
125                         if (pwent == NULL) {
126                                 fprintf(fp, "Cannot find user %s: %s\n",
127                                         optarg,
128                                         strerror(errno));
129                                 goto failed;
130                         }
131                         sfree(&cmd->pwent.pw_name);
132                         sfree(&cmd->pwent.pw_passwd);
133                         sfree(&cmd->pwent.pw_class);
134                         sfree(&cmd->pwent.pw_gecos);
135                         sfree(&cmd->pwent.pw_dir);
136                         sfree(&cmd->pwent.pw_shell);
137                         cmd->pwent = *pwent;
138                         sdup(&cmd->pwent.pw_name);
139                         sdup(&cmd->pwent.pw_passwd);
140                         sdup(&cmd->pwent.pw_class);
141                         sdup(&cmd->pwent.pw_gecos);
142                         sdup(&cmd->pwent.pw_dir);
143                         sdup(&cmd->pwent.pw_shell);
144                         break;
145                 case 'g':
146                         setgroupent(1);
147                         if (isdigit(optarg[0])) {
148                                 grent = getgrnam(optarg);
149                         } else {
150                                 grent = getgrgid(strtol(optarg, NULL, 0));
151                         }
152                         if (grent == NULL) {
153                                 fprintf(fp, "Cannot find group %s: %s\n",
154                                         optarg,
155                                         strerror(errno));
156                                 goto failed;
157                         }
158                         sfree(&cmd->grent.gr_name);
159                         sfree(&cmd->grent.gr_passwd);
160                         afree(&cmd->grent.gr_mem);
161                         cmd->grent = *grent;
162                         sdup(&cmd->grent.gr_name);
163                         sdup(&cmd->grent.gr_passwd);
164                         adup(&cmd->grent.gr_mem);
165                         break;
166                 case 'G':
167                         setgroupent(1);
168                         cpy = strdup(optarg);
169                         sub = strtok(cpy, ",");
170                         i = 0;
171                         while (sub) {
172                                 if (isdigit(optarg[0])) {
173                                         grent = getgrnam(optarg);
174                                 } else {
175                                         grent = getgrgid(strtol(optarg,
176                                                                 NULL, 0));
177                                 }
178                                 if (grent == NULL) {
179                                         fprintf(fp,
180                                                 "Cannot find group %s: %s\n",
181                                                 optarg,
182                                                 strerror(errno));
183                                         i = -1;
184                                 }
185                                 if (i == NGROUPS) {
186                                         fprintf(fp,
187                                                 "Too many groups specified, "
188                                                 "max %d\n", NGROUPS);
189                                         i = -1;
190                                 }
191                                 if (i >= 0)
192                                         cmd->groups[i++] = grent->gr_gid;
193                                 sub = strtok(NULL, ",");
194                         }
195                         free(cpy);
196                         if (i < 0)
197                                 goto failed;
198                         cmd->ngroups = i;
199                         break;
200                 case 'l':
201                         sreplace(&cmd->logfile, optarg);
202                         break;
203                 case 'C':
204                         cmd->mountdev = 1;
205                         /* fall through */
206                 case 'c':
207                         sreplace(&cmd->rootdir, optarg);
208                         break;
209                 case 'J':
210                         cmd->mountdev = 1;
211                         /* fall through */
212                 case 'j':
213                         sreplace(&cmd->jaildir, optarg);
214                         break;
215                 case 'k':
216                         rc = process_jailspec(cmd, optarg);
217                         if (rc)
218                                 goto failed;
219                         break;
220                 case 'T':
221                         sreplace(&cmd->proctitle, optarg);
222                         break;
223                 case 'F':
224                         cmd->restart_per = 60;
225                         if (sscanf(optarg, "%d:%d",
226                                    &cmd->restart_count,
227                                    &cmd->restart_per) < 1) {
228                                 fprintf(fp, "bad restart specification: %s\n",
229                                         optarg);
230                                 goto failed;
231                         }
232                         break;
233                 default:
234                         fprintf(fp, "Unknown option %c\n", ch);
235                         goto failed;
236                 }
237         }
238
239         /*
240          * directive [label] [...additional args]
241          */
242         i = optind;
243         if (av[i]) {
244                 cmd->directive = strdup(av[i]);
245                 ++i;
246                 if (av[i]) {
247                         cmd->label = strdup(av[i]);
248                         ++i;
249                         cmd->ext_av = av + i;
250                         cmd->ext_ac = ac - i;
251                         adup(&cmd->ext_av);
252                 }
253         } else {
254                 fprintf(fp, "No directive specified\n");
255                 goto failed;
256         }
257         rc = 0;
258 failed:
259         endgrent();
260         endpwent();
261
262         return rc;
263 }
264
265 int
266 execute_cmd(command_t *cmd)
267 {
268         const char *directive;
269         int rc;
270
271         directive = cmd->directive;
272
273         if (strcmp(directive, "init") == 0) {
274                 rc = execute_init(cmd);
275         } else if (strcmp(directive, "start") == 0) {
276                 rc = execute_remote(cmd, execute_start);
277         } else if (strcmp(directive, "stop") == 0) {
278                 rc = execute_remote(cmd, execute_stop);
279         } else if (strcmp(directive, "stopall") == 0) {
280                 cmd->restart_some = 0;
281                 cmd->restart_all = 1;
282                 rc = execute_remote(cmd, execute_stop);
283         } else if (strcmp(directive, "restart") == 0) {
284                 rc = execute_remote(cmd, execute_restart);
285         } else if (strcmp(directive, "exit") == 0) {
286                 cmd->restart_some = 0;
287                 cmd->restart_all = 1;   /* stop everything */
288                 rc = execute_remote(cmd, execute_exit);
289         } else if (strcmp(directive, "kill") == 0) {
290                 cmd->restart_some = 0;
291                 cmd->restart_all = 1;   /* stop everything */
292                 cmd->termkill_timo = 0; /* force immediate SIGKILL */
293                 rc = execute_remote(cmd, execute_exit);
294         } else if (strcmp(directive, "list") == 0) {
295                 rc = execute_remote(cmd, execute_list);
296         } else if (strcmp(directive, "status") == 0) {
297                 rc = execute_remote(cmd, execute_status);
298         } else if (strcmp(directive, "log") == 0) {
299                 rc = execute_remote(cmd, execute_log);
300         } else if (strcmp(directive, "logf") == 0) {
301                 cmd->tail_mode = 1;
302                 rc = execute_remote(cmd, execute_log);
303         } else if (strcmp(directive, "tailf") == 0) {
304                 cmd->tail_mode = 2;
305                 rc = execute_remote(cmd, execute_log);
306         } else if (strcmp(directive, "logfile") == 0) {
307                 rc = execute_remote(cmd, execute_logfile);
308         } else {
309                 fprintf(cmd->fp, "Uknown directive: %s\n", directive);
310                 rc = 1;
311         }
312         return rc;
313 }
314
315 static
316 int
317 execute_remote(command_t *cmd, int (*func)(command_t *cmd))
318 {
319         DIR *dir;
320         struct dirent *den;
321         const char *p1;
322         const char *p2;
323         char *plab;
324         size_t cmdlen;
325         size_t len;
326         int rc;
327
328         /*
329          * If already on the remote service just execute the operation
330          * as requested.
331          */
332         if (cmd->cmdline == 0) {
333                 return (func(cmd));
334         }
335
336         /*
337          * Look for label(s).  If no exact match or label is NULL, scan
338          * piddir for matches.
339          */
340         if ((dir = opendir(cmd->piddir)) == NULL) {
341                 fprintf(cmd->fp, "Unable to scan \"%s\"\n", cmd->piddir);
342                 return 1;
343         }
344
345         rc = 0;
346         cmdlen = (cmd->label ? strlen(cmd->label) : 0);
347
348         while ((den = readdir(dir)) != NULL) {
349                 /*
350                  * service. prefix.
351                  */
352                 if (strncmp(den->d_name, "service.", 8) != 0)
353                         continue;
354
355                 /*
356                  * .sk suffix
357                  */
358                 p1 = den->d_name + 8;
359                 p2 = strrchr(p1, '.');
360                 if (p2 == NULL || p2 < p1 || strcmp(p2, ".sk") != 0)
361                         continue;
362
363                 /*
364                  * Extract the label from the service.<label>.sk name.
365                  */
366                 len = p2 - p1 - 4;
367                 plab = strdup(p1);
368                 *strrchr(plab, '.') = 0;
369
370                 /*
371                  * Start remote execution (in parallel) for all matching
372                  * labels.  This will generally create some asynchronous
373                  * threads.
374                  */
375                 if (cmdlen == 0 ||
376                     (cmdlen <= len && strncmp(cmd->label, plab, cmdlen) == 0)) {
377                         remote_execute(cmd, plab);
378                 }
379                 free(plab);
380         }
381         closedir(dir);
382
383         /*
384          * Wait for completion of remote commands and dump output.
385          */
386         rc = remote_wait();
387
388         return rc;
389 }
390
391 void
392 free_cmd(command_t *cmd)
393 {
394         sfree(&cmd->piddir);
395
396         sfree(&cmd->pwent.pw_name);
397         sfree(&cmd->pwent.pw_passwd);
398         sfree(&cmd->pwent.pw_class);
399         sfree(&cmd->pwent.pw_gecos);
400         sfree(&cmd->pwent.pw_dir);
401         sfree(&cmd->pwent.pw_shell);
402
403         sfree(&cmd->grent.gr_name);
404         sfree(&cmd->grent.gr_passwd);
405         afree(&cmd->grent.gr_mem);
406
407         sfree(&cmd->logfile);
408         sfree(&cmd->rootdir);
409         sfree(&cmd->jaildir);
410         sfree(&cmd->proctitle);
411         sfree(&cmd->directive);
412         sfree(&cmd->label);
413         afree(&cmd->ext_av);
414
415         bzero(cmd, sizeof(*cmd));
416 }
417
418 static
419 int
420 process_jailspec(command_t *cmd, const char *spec)
421 {
422         char *cpy = strdup(spec);
423         char *ptr;
424         int rc = 0;
425
426         ptr = strtok(cpy, ",");
427         while (ptr) {
428                 if (strcmp(ptr, "clean") == 0) {
429                         cmd->jail_clean = 1;
430                 } else if (strncmp(ptr, "ip=", 3) == 0) {
431                         assert(0); /* XXX TODO */
432                 } else {
433                         fprintf(cmd->fp, "jail-spec '%s' not understood\n",
434                                 ptr);
435                         rc = 1;
436                 }
437                 ptr = strtok(NULL, ",");
438         }
439         free(cpy);
440
441         return rc;
442 }