svc - Implement more features
[dragonfly.git] / sbin / svc / svc.c
blob1746821ea81919e07f6a360e2d460b4c7086c80a
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: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;
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 = getpwnam(optarg);
128 } else {
129 pwent = getpwuid(strtol(optarg, NULL, 0));
131 if (pwent == NULL) {
132 fprintf(fp, "Cannot find user %s: %s\n",
133 optarg,
134 strerror(errno));
135 goto failed;
137 sfree(&cmd->pwent.pw_name);
138 sfree(&cmd->pwent.pw_passwd);
139 sfree(&cmd->pwent.pw_class);
140 sfree(&cmd->pwent.pw_gecos);
141 sfree(&cmd->pwent.pw_dir);
142 sfree(&cmd->pwent.pw_shell);
143 cmd->pwent = *pwent;
144 sdup(&cmd->pwent.pw_name);
145 sdup(&cmd->pwent.pw_passwd);
146 sdup(&cmd->pwent.pw_class);
147 sdup(&cmd->pwent.pw_gecos);
148 sdup(&cmd->pwent.pw_dir);
149 sdup(&cmd->pwent.pw_shell);
150 break;
151 case 'g':
152 setgroupent(1);
153 if (isdigit(optarg[0])) {
154 grent = getgrnam(optarg);
155 } else {
156 grent = getgrgid(strtol(optarg, NULL, 0));
158 if (grent == NULL) {
159 fprintf(fp, "Cannot find group %s: %s\n",
160 optarg,
161 strerror(errno));
162 goto failed;
164 sfree(&cmd->grent.gr_name);
165 sfree(&cmd->grent.gr_passwd);
166 afree(&cmd->grent.gr_mem);
167 cmd->grent = *grent;
168 sdup(&cmd->grent.gr_name);
169 sdup(&cmd->grent.gr_passwd);
170 adup(&cmd->grent.gr_mem);
171 break;
172 case 'G':
173 setgroupent(1);
174 cpy = strdup(optarg);
175 sub = strtok(cpy, ",");
176 i = 0;
177 while (sub) {
178 if (isdigit(optarg[0])) {
179 grent = getgrnam(optarg);
180 } else {
181 grent = getgrgid(strtol(optarg,
182 NULL, 0));
184 if (grent == NULL) {
185 fprintf(fp,
186 "Cannot find group %s: %s\n",
187 optarg,
188 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 cmd->mountdev = 1;
211 /* fall through */
212 case 'c':
213 sreplace(&cmd->rootdir, optarg);
214 break;
215 case 'J':
216 cmd->mountdev = 1;
217 /* fall through */
218 case 'j':
219 sreplace(&cmd->jaildir, optarg);
220 break;
221 case 'k':
222 rc = process_jailspec(cmd, optarg);
223 if (rc)
224 goto failed;
225 break;
226 case 'T':
227 sreplace(&cmd->proctitle, optarg);
228 break;
229 case 'F':
230 cmd->restart_per = 60;
231 if (sscanf(optarg, "%d:%d",
232 &cmd->restart_count,
233 &cmd->restart_per) < 1) {
234 fprintf(fp, "bad restart specification: %s\n",
235 optarg);
236 goto failed;
238 break;
239 default:
240 fprintf(fp, "Unknown option %c\n", ch);
241 goto failed;
246 * directive [label] [...additional args]
248 * If 'all' is specified the label field is left NULL (ensure that
249 * it is NULL), and empty_label is still cleared so safety code works.
251 i = optind;
252 if (av[i]) {
253 cmd->directive = strdup(av[i]);
254 ++i;
255 if (av[i]) {
256 cmd->empty_label = 0;
257 if (strcmp(av[i], "all") == 0)
258 sfree(&cmd->label);
259 else
260 cmd->label = strdup(av[i]);
261 ++i;
262 cmd->ext_av = av + i;
263 cmd->ext_ac = ac - i;
264 adup(&cmd->ext_av);
266 } else {
267 fprintf(fp, "No directive specified\n");
268 goto failed;
270 rc = 0;
271 failed:
272 endgrent();
273 endpwent();
275 return rc;
279 execute_cmd(command_t *cmd)
281 const char *directive;
282 int rc;
284 directive = cmd->directive;
287 * Safely, require a label for directives that do not match
288 * this list, or 'all'. Do not default to all if no label
289 * is specified. e.g. things like 'kill' or 'exit' could
290 * blow up the system.
292 if (cmd->empty_label) {
293 if (strcmp(directive, "status") != 0 &&
294 strcmp(directive, "list") != 0 &&
295 strcmp(directive, "log") != 0 &&
296 strcmp(directive, "logf") != 0 &&
297 strcmp(directive, "help") != 0 &&
298 strcmp(directive, "tailf") != 0) {
299 fprintf(cmd->fp,
300 "Directive requires a label or 'all': %s\n",
301 directive);
302 rc = 1;
303 return rc;
308 * Process directives. If we are on the remote already the
309 * execute_remote() function will simply chain to the passed-in
310 * function.
312 if (strcmp(directive, "init") == 0) {
313 rc = execute_init(cmd);
314 } else if (strcmp(directive, "help") == 0) {
315 rc = execute_help(cmd);
316 } else if (strcmp(directive, "start") == 0) {
317 rc = execute_remote(cmd, execute_start);
318 } else if (strcmp(directive, "stop") == 0) {
319 rc = execute_remote(cmd, execute_stop);
320 } else if (strcmp(directive, "stopall") == 0) {
321 cmd->restart_some = 0;
322 cmd->restart_all = 1;
323 rc = execute_remote(cmd, execute_stop);
324 } else if (strcmp(directive, "restart") == 0) {
325 rc = execute_remote(cmd, execute_restart);
326 } else if (strcmp(directive, "exit") == 0) {
327 cmd->restart_some = 0;
328 cmd->restart_all = 1; /* stop everything */
329 cmd->force_remove_files = 1;
330 rc = execute_remote(cmd, execute_exit);
331 } else if (strcmp(directive, "kill") == 0) {
332 cmd->restart_some = 0;
333 cmd->restart_all = 1; /* stop everything */
334 cmd->termkill_timo = 0; /* force immediate SIGKILL */
335 cmd->force_remove_files = 1;
336 rc = execute_remote(cmd, execute_exit);
337 } else if (strcmp(directive, "list") == 0) {
338 rc = execute_remote(cmd, execute_list);
339 } else if (strcmp(directive, "status") == 0) {
340 rc = execute_remote(cmd, execute_status);
341 } else if (strcmp(directive, "log") == 0) {
342 rc = execute_remote(cmd, execute_log);
343 } else if (strcmp(directive, "logf") == 0) {
344 cmd->tail_mode = 1;
345 rc = execute_remote(cmd, execute_log);
346 } else if (strcmp(directive, "tailf") == 0) {
347 cmd->tail_mode = 2;
348 rc = execute_remote(cmd, execute_log);
349 } else if (strcmp(directive, "logfile") == 0) {
350 rc = execute_remote(cmd, execute_logfile);
351 } else {
352 fprintf(cmd->fp, "Uknown directive: %s\n", directive);
353 rc = 1;
355 return rc;
358 static
360 execute_remote(command_t *cmd, int (*func)(command_t *cmd))
362 DIR *dir;
363 struct dirent *den;
364 const char *p1;
365 const char *p2;
366 char *plab;
367 size_t cmdlen;
368 size_t len;
369 int rc;
372 * If already on the remote service just execute the operation
373 * as requested.
375 if (cmd->cmdline == 0) {
376 return (func(cmd));
380 * Look for label(s). If no exact match or label is NULL, scan
381 * piddir for matches.
383 if ((dir = opendir(cmd->piddir)) == NULL) {
384 fprintf(cmd->fp, "Unable to scan \"%s\"\n", cmd->piddir);
385 return 1;
388 rc = 0;
389 cmdlen = (cmd->label ? strlen(cmd->label) : 0);
391 while ((den = readdir(dir)) != NULL) {
393 * service. prefix.
395 if (strncmp(den->d_name, "service.", 8) != 0)
396 continue;
399 * .sk suffix
401 p1 = den->d_name + 8;
402 p2 = strrchr(p1, '.');
403 if (p2 == NULL || p2 < p1 || strcmp(p2, ".sk") != 0)
404 continue;
407 * Extract the label from the service.<label>.sk name.
409 len = p2 - p1;
410 plab = strdup(p1);
411 *strrchr(plab, '.') = 0;
414 * Start remote execution (in parallel) for all matching
415 * labels. This will generally create some asynchronous
416 * threads.
418 if (cmdlen == 0 ||
419 (cmdlen <= len && strncmp(cmd->label, plab, cmdlen) == 0)) {
420 remote_execute(cmd, plab);
422 free(plab);
424 closedir(dir);
427 * Wait for completion of remote commands and dump output.
429 rc = remote_wait();
431 return rc;
434 void
435 free_cmd(command_t *cmd)
437 sfree(&cmd->piddir);
439 sfree(&cmd->pwent.pw_name);
440 sfree(&cmd->pwent.pw_passwd);
441 sfree(&cmd->pwent.pw_class);
442 sfree(&cmd->pwent.pw_gecos);
443 sfree(&cmd->pwent.pw_dir);
444 sfree(&cmd->pwent.pw_shell);
446 sfree(&cmd->grent.gr_name);
447 sfree(&cmd->grent.gr_passwd);
448 afree(&cmd->grent.gr_mem);
450 sfree(&cmd->logfile);
451 sfree(&cmd->rootdir);
452 sfree(&cmd->jaildir);
453 sfree(&cmd->proctitle);
454 sfree(&cmd->directive);
455 sfree(&cmd->label);
456 afree(&cmd->ext_av);
458 if (cmd->logfd >= 0) {
459 close(cmd->logfd);
460 cmd->logfd = -1;
463 bzero(cmd, sizeof(*cmd));
466 static
468 process_jailspec(command_t *cmd, const char *spec)
470 char *cpy = strdup(spec);
471 char *ptr;
472 int rc = 0;
474 ptr = strtok(cpy, ",");
475 while (ptr) {
476 if (strcmp(ptr, "clean") == 0) {
477 cmd->jail_clean = 1;
478 } else if (strncmp(ptr, "ip=", 3) == 0) {
479 assert(0); /* XXX TODO */
480 } else {
481 fprintf(cmd->fp, "jail-spec '%s' not understood\n",
482 ptr);
483 rc = 1;
485 ptr = strtok(NULL, ",");
487 free(cpy);
489 return rc;