xref: /dragonfly/sbin/svc/svc.c (revision 03517d4e)
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, "Unknown 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 }
488