drm/linux: Add list_for_each_entry_safe_reverse()
[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 = "dfhp:r:R:xst:u:g:G:l:c:mj: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         cmd->logfd = -1;
80         sreplace(&cmd->piddir, "/var/run");     /* must not be NULL */
81         cmd->termkill_timo = -1;                /* will use default value */
82         cmd->orig_ac = ac;
83         cmd->orig_av = av;
84         cmd->empty_label = 1;
85
86         optind = 1;
87         opterr = 1;
88         optreset = 1;
89
90         while ((ch = getopt(ac, av, optstr)) != -1) {
91                 switch(ch) {
92                 case 'd':
93                         cmd->debug = 1;
94                         cmd->foreground = 1;
95                         break;
96                 case 'f':
97                         cmd->foreground = 1;
98                         break;
99                 case 'h':
100                         execute_help(cmd);
101                         exit(0);
102                         break;
103                 case 'p':
104                         sreplace(&cmd->piddir, optarg);
105                         break;
106                 case 'r':
107                         cmd->restart_some = 1;
108                         cmd->restart_all = 0;
109                         cmd->restart_timo = strtol(optarg, NULL, 0);
110                         break;
111                 case 'R':
112                         cmd->restart_some = 0;
113                         cmd->restart_all = 1;
114                         cmd->restart_timo = strtol(optarg, NULL, 0);
115                         break;
116                 case 'x':
117                         cmd->exit_mode = 1;
118                         break;
119                 case 's':
120                         cmd->sync_mode = 1;
121                         break;
122                 case 't':
123                         cmd->termkill_timo = strtoul(optarg, NULL, 0);
124                         break;
125                 case 'u':
126                         if (isdigit(optarg[0])) {
127                                 pwent = getpwuid(strtol(optarg, NULL, 0));
128                         } else {
129                                 pwent = getpwnam(optarg);
130                         }
131                         if (pwent == NULL) {
132                                 fprintf(fp, "Cannot find user %s: %s\n",
133                                         optarg,
134                                         strerror(errno));
135                                 goto failed;
136                         }
137                         cmd->uid_mode = 1;
138                         sfree(&cmd->pwent.pw_name);
139                         sfree(&cmd->pwent.pw_passwd);
140                         sfree(&cmd->pwent.pw_class);
141                         sfree(&cmd->pwent.pw_gecos);
142                         sfree(&cmd->pwent.pw_dir);
143                         sfree(&cmd->pwent.pw_shell);
144                         cmd->pwent = *pwent;
145                         sdup(&cmd->pwent.pw_name);
146                         sdup(&cmd->pwent.pw_passwd);
147                         sdup(&cmd->pwent.pw_class);
148                         sdup(&cmd->pwent.pw_gecos);
149                         sdup(&cmd->pwent.pw_dir);
150                         sdup(&cmd->pwent.pw_shell);
151                         break;
152                 case 'g':
153                         setgroupent(1);
154                         if (isdigit(optarg[0])) {
155                                 grent = getgrgid(strtol(optarg, NULL, 0));
156                         } else {
157                                 grent = getgrnam(optarg);
158                         }
159                         if (grent == NULL) {
160                                 fprintf(fp, "Cannot find group %s: %s\n",
161                                         optarg,
162                                         strerror(errno));
163                                 goto failed;
164                         }
165                         cmd->gid_mode = 1;
166                         sfree(&cmd->grent.gr_name);
167                         sfree(&cmd->grent.gr_passwd);
168                         afree(&cmd->grent.gr_mem);
169                         cmd->grent = *grent;
170                         sdup(&cmd->grent.gr_name);
171                         sdup(&cmd->grent.gr_passwd);
172                         adup(&cmd->grent.gr_mem);
173                         break;
174                 case 'G':
175                         setgroupent(1);
176                         cpy = strdup(optarg);
177                         sub = strtok(cpy, ",");
178                         i = 0;
179                         while (sub) {
180                                 if (isdigit(sub[0])) {
181                                         grent = getgrgid(strtol(sub, NULL, 0));
182                                 } else {
183                                         grent = getgrnam(sub);
184                                 }
185                                 if (grent == NULL) {
186                                         fprintf(fp,
187                                                 "Cannot find group %s: %s\n",
188                                                 sub, strerror(errno));
189                                         i = -1;
190                                 }
191                                 if (i == NGROUPS) {
192                                         fprintf(fp,
193                                                 "Too many groups specified, "
194                                                 "max %d\n", NGROUPS);
195                                         i = -1;
196                                 }
197                                 if (i >= 0)
198                                         cmd->groups[i++] = grent->gr_gid;
199                                 sub = strtok(NULL, ",");
200                         }
201                         free(cpy);
202                         if (i < 0)
203                                 goto failed;
204                         cmd->ngroups = i;
205                         break;
206                 case 'l':
207                         sreplace(&cmd->logfile, optarg);
208                         break;
209                 case 'c':
210                         sreplace(&cmd->rootdir, optarg);
211                         break;
212                 case 'm':
213                         cmd->mountdev = 1;
214                         break;
215                 case 'j':
216                         sreplace(&cmd->jaildir, optarg);
217                         break;
218                 case 'k':
219                         rc = process_jailspec(cmd, optarg);
220                         if (rc)
221                                 goto failed;
222                         break;
223                 case 'T':
224                         sreplace(&cmd->proctitle, optarg);
225                         break;
226                 case 'F':
227                         cmd->restart_per = 60;
228                         if (sscanf(optarg, "%d:%d",
229                                    &cmd->restart_count,
230                                    &cmd->restart_per) < 1) {
231                                 fprintf(fp, "bad restart specification: %s\n",
232                                         optarg);
233                                 goto failed;
234                         }
235                         break;
236                 default:
237                         fprintf(fp, "Unknown option %c\n", ch);
238                         goto failed;
239                 }
240         }
241
242         /*
243          * directive [label] [...additional args]
244          *
245          * If 'all' is specified the label field is left NULL (ensure that
246          * it is NULL), and empty_label is still cleared so safety code works.
247          */
248         i = optind;
249         if (av[i]) {
250                 cmd->directive = strdup(av[i]);
251                 ++i;
252                 if (av[i]) {
253                         cmd->empty_label = 0;
254                         if (strcmp(av[i], "all") == 0)
255                                 sfree(&cmd->label);
256                         else
257                                 cmd->label = strdup(av[i]);
258                         ++i;
259                         cmd->ext_av = av + i;
260                         cmd->ext_ac = ac - i;
261                         adup(&cmd->ext_av);
262                 }
263         } else {
264                 fprintf(fp, "No directive specified\n");
265                 goto failed;
266         }
267         rc = 0;
268 failed:
269         endgrent();
270         endpwent();
271
272         return rc;
273 }
274
275 int
276 execute_cmd(command_t *cmd)
277 {
278         const char *directive;
279         int rc;
280
281         directive = cmd->directive;
282
283         /*
284          * Safely, require a label for directives that do not match
285          * this list, or 'all'.  Do not default to all if no label
286          * is specified.  e.g. things like 'kill' or 'exit' could
287          * blow up the system.
288          */
289         if (cmd->empty_label) {
290                 if (strcmp(directive, "status") != 0 &&
291                     strcmp(directive, "list") != 0 &&
292                     strcmp(directive, "log") != 0 &&
293                     strcmp(directive, "logf") != 0 &&
294                     strcmp(directive, "help") != 0 &&
295                     strcmp(directive, "tailf") != 0)  {
296                         fprintf(cmd->fp,
297                                 "Directive requires a label or 'all': %s\n",
298                                 directive);
299                         rc = 1;
300                         return rc;
301                 }
302         }
303
304         /*
305          * Process directives.  If we are on the remote already the
306          * execute_remote() function will simply chain to the passed-in
307          * function.
308          */
309         if (strcmp(directive, "init") == 0) {
310                 rc = execute_init(cmd);
311         } else if (strcmp(directive, "help") == 0) {
312                 rc = execute_help(cmd);
313         } else if (strcmp(directive, "start") == 0) {
314                 rc = execute_remote(cmd, execute_start);
315         } else if (strcmp(directive, "stop") == 0) {
316                 rc = execute_remote(cmd, execute_stop);
317         } else if (strcmp(directive, "stopall") == 0) {
318                 cmd->restart_some = 0;
319                 cmd->restart_all = 1;
320                 rc = execute_remote(cmd, execute_stop);
321         } else if (strcmp(directive, "restart") == 0) {
322                 rc = execute_remote(cmd, execute_restart);
323         } else if (strcmp(directive, "exit") == 0) {
324                 cmd->restart_some = 0;
325                 cmd->restart_all = 1;           /* stop everything */
326                 cmd->force_remove_files = 1;
327                 rc = execute_remote(cmd, execute_exit);
328         } else if (strcmp(directive, "kill") == 0) {
329                 cmd->restart_some = 0;
330                 cmd->restart_all = 1;           /* stop everything */
331                 cmd->termkill_timo = 0;         /* force immediate SIGKILL */
332                 cmd->force_remove_files = 1;
333                 rc = execute_remote(cmd, execute_exit);
334         } else if (strcmp(directive, "list") == 0) {
335                 rc = execute_remote(cmd, execute_list);
336         } else if (strcmp(directive, "status") == 0) {
337                 rc = execute_remote(cmd, execute_status);
338         } else if (strcmp(directive, "log") == 0) {
339                 rc = execute_remote(cmd, execute_log);
340         } else if (strcmp(directive, "logf") == 0) {
341                 cmd->tail_mode = 1;
342                 rc = execute_remote(cmd, execute_log);
343         } else if (strcmp(directive, "tailf") == 0) {
344                 cmd->tail_mode = 2;
345                 rc = execute_remote(cmd, execute_log);
346         } else if (strcmp(directive, "logfile") == 0) {
347                 rc = execute_remote(cmd, execute_logfile);
348         } else {
349                 fprintf(cmd->fp, "Uknown directive: %s\n", directive);
350                 rc = 1;
351         }
352         return rc;
353 }
354
355 static
356 int
357 execute_remote(command_t *cmd, int (*func)(command_t *cmd))
358 {
359         DIR *dir;
360         struct dirent *den;
361         const char *p1;
362         const char *p2;
363         char *plab;
364         size_t cmdlen;
365         size_t len;
366         int rc;
367
368         /*
369          * If already on the remote service just execute the operation
370          * as requested.
371          */
372         if (cmd->cmdline == 0) {
373                 return (func(cmd));
374         }
375
376         /*
377          * Look for label(s).  If no exact match or label is NULL, scan
378          * piddir for matches.
379          */
380         if ((dir = opendir(cmd->piddir)) == NULL) {
381                 fprintf(cmd->fp, "Unable to scan \"%s\"\n", cmd->piddir);
382                 return 1;
383         }
384
385         rc = 0;
386         cmdlen = (cmd->label ? strlen(cmd->label) : 0);
387
388         while ((den = readdir(dir)) != NULL) {
389                 /*
390                  * service. prefix.
391                  */
392                 if (strncmp(den->d_name, "service.", 8) != 0)
393                         continue;
394
395                 /*
396                  * .sk suffix
397                  */
398                 p1 = den->d_name + 8;
399                 p2 = strrchr(p1, '.');
400                 if (p2 == NULL || p2 < p1 || strcmp(p2, ".sk") != 0)
401                         continue;
402
403                 /*
404                  * Extract the label from the service.<label>.sk name.
405                  */
406                 len = p2 - p1;
407                 plab = strdup(p1);
408                 *strrchr(plab, '.') = 0;
409
410                 /*
411                  * Start remote execution (in parallel) for all matching
412                  * labels.  This will generally create some asynchronous
413                  * threads.
414                  */
415                 if (cmdlen == 0 ||
416                     (cmdlen <= len && strncmp(cmd->label, plab, cmdlen) == 0)) {
417                         remote_execute(cmd, plab);
418                 }
419                 free(plab);
420         }
421         closedir(dir);
422
423         /*
424          * Wait for completion of remote commands and dump output.
425          */
426         rc = remote_wait();
427
428         return rc;
429 }
430
431 void
432 free_cmd(command_t *cmd)
433 {
434         sfree(&cmd->piddir);
435
436         sfree(&cmd->pwent.pw_name);
437         sfree(&cmd->pwent.pw_passwd);
438         sfree(&cmd->pwent.pw_class);
439         sfree(&cmd->pwent.pw_gecos);
440         sfree(&cmd->pwent.pw_dir);
441         sfree(&cmd->pwent.pw_shell);
442
443         sfree(&cmd->grent.gr_name);
444         sfree(&cmd->grent.gr_passwd);
445         afree(&cmd->grent.gr_mem);
446
447         sfree(&cmd->logfile);
448         sfree(&cmd->rootdir);
449         sfree(&cmd->jaildir);
450         sfree(&cmd->proctitle);
451         sfree(&cmd->directive);
452         sfree(&cmd->label);
453         afree(&cmd->ext_av);
454
455         if (cmd->logfd >= 0) {
456                 close(cmd->logfd);
457                 cmd->logfd = -1;
458         }
459
460         bzero(cmd, sizeof(*cmd));
461 }
462
463 static
464 int
465 process_jailspec(command_t *cmd, const char *spec)
466 {
467         char *cpy = strdup(spec);
468         char *ptr;
469         int rc = 0;
470
471         ptr = strtok(cpy, ",");
472         while (ptr) {
473                 if (strcmp(ptr, "clean") == 0) {
474                         cmd->jail_clean = 1;
475                 } else if (strncmp(ptr, "ip=", 3) == 0) {
476                         assert(0); /* XXX TODO */
477                 } else {
478                         fprintf(cmd->fp, "jail-spec '%s' not understood\n",
479                                 ptr);
480                         rc = 1;
481                 }
482                 ptr = strtok(NULL, ",");
483         }
484         free(cpy);
485
486         return rc;
487 }