periodic(8): Sync with FreeBSD current
[dragonfly.git] / sbin / svc / svc.c
bloba0107d25550eecf81a87f2f2825c28f249bd7a70
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 * SERVICE MANAGER
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.
42 #include "svc.h"
44 static int execute_remote(command_t *cmd, int (*func)(command_t *cmd));
45 static int process_jailspec(command_t *cmd, const char *spec);
47 int
48 main(int ac, char **av)
50 command_t cmd;
51 int rc;
53 signal(SIGPIPE, SIG_IGN);
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);
62 return rc;
65 int
66 process_cmd(command_t *cmd, FILE *fp, int ac, char **av)
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;
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;
86 optind = 1;
87 opterr = 1;
88 optreset = 1;
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);
131 if (pwent == NULL) {
132 fprintf(fp, "Cannot find user %s: %s\n",
133 optarg,
134 strerror(errno));
135 goto failed;
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);
159 if (grent == NULL) {
160 fprintf(fp, "Cannot find group %s: %s\n",
161 optarg,
162 strerror(errno));
163 goto failed;
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);
185 if (grent == NULL) {
186 fprintf(fp,
187 "Cannot find group %s: %s\n",
188 sub, strerror(errno));
189 i = -1;
191 if (i == NGROUPS) {
192 fprintf(fp,
193 "Too many groups specified, "
194 "max %d\n", NGROUPS);
195 i = -1;
197 if (i >= 0)
198 cmd->groups[i++] = grent->gr_gid;
199 sub = strtok(NULL, ",");
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;
235 break;
236 default:
237 fprintf(fp, "Unknown option %c\n", ch);
238 goto failed;
243 * directive [label] [...additional args]
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.
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);
263 } else {
264 fprintf(fp, "No directive specified\n");
265 goto failed;
267 rc = 0;
268 failed:
269 endgrent();
270 endpwent();
272 return rc;
276 execute_cmd(command_t *cmd)
278 const char *directive;
279 int rc;
281 directive = cmd->directive;
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.
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;
305 * Process directives. If we are on the remote already the
306 * execute_remote() function will simply chain to the passed-in
307 * function.
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;
352 return rc;
355 static
357 execute_remote(command_t *cmd, int (*func)(command_t *cmd))
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;
369 * If already on the remote service just execute the operation
370 * as requested.
372 if (cmd->cmdline == 0) {
373 return (func(cmd));
377 * Look for label(s). If no exact match or label is NULL, scan
378 * piddir for matches.
380 if ((dir = opendir(cmd->piddir)) == NULL) {
381 fprintf(cmd->fp, "Unable to scan \"%s\"\n", cmd->piddir);
382 return 1;
385 rc = 0;
386 cmdlen = (cmd->label ? strlen(cmd->label) : 0);
388 while ((den = readdir(dir)) != NULL) {
390 * service. prefix.
392 if (strncmp(den->d_name, "service.", 8) != 0)
393 continue;
396 * .sk suffix
398 p1 = den->d_name + 8;
399 p2 = strrchr(p1, '.');
400 if (p2 == NULL || p2 < p1 || strcmp(p2, ".sk") != 0)
401 continue;
404 * Extract the label from the service.<label>.sk name.
406 len = p2 - p1;
407 plab = strdup(p1);
408 *strrchr(plab, '.') = 0;
411 * Start remote execution (in parallel) for all matching
412 * labels. This will generally create some asynchronous
413 * threads.
415 if (cmdlen == 0 ||
416 (cmdlen <= len && strncmp(cmd->label, plab, cmdlen) == 0)) {
417 remote_execute(cmd, plab);
419 free(plab);
421 closedir(dir);
424 * Wait for completion of remote commands and dump output.
426 rc = remote_wait();
428 return rc;
431 void
432 free_cmd(command_t *cmd)
434 sfree(&cmd->piddir);
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);
443 sfree(&cmd->grent.gr_name);
444 sfree(&cmd->grent.gr_passwd);
445 afree(&cmd->grent.gr_mem);
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);
455 if (cmd->logfd >= 0) {
456 close(cmd->logfd);
457 cmd->logfd = -1;
460 bzero(cmd, sizeof(*cmd));
463 static
465 process_jailspec(command_t *cmd, const char *spec)
467 char *cpy = strdup(spec);
468 char *ptr;
469 int rc = 0;
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;
482 ptr = strtok(NULL, ",");
484 free(cpy);
486 return rc;