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
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
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
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.
44 static int execute_remote(command_t *cmd, int (*func)(command_t *cmd));
45 static int process_jailspec(command_t *cmd, const char *spec);
48 main(int ac, char **av)
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) */
59 rc = execute_cmd(&cmd);
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:";
77 bzero(cmd, sizeof(*cmd));
78 cmd->fp = fp; /* error and output reporting */
80 sreplace(&cmd->piddir, "/var/run"); /* must not be NULL */
81 cmd->termkill_timo = -1; /* will use default value */
90 while ((ch = getopt(ac, av, optstr)) != -1) {
104 sreplace(&cmd->piddir, optarg);
107 cmd->restart_some = 1;
108 cmd->restart_all = 0;
109 cmd->restart_timo = strtol(optarg, NULL, 0);
112 cmd->restart_some = 0;
113 cmd->restart_all = 1;
114 cmd->restart_timo = strtol(optarg, NULL, 0);
123 cmd->termkill_timo = strtoul(optarg, NULL, 0);
126 if (isdigit(optarg[0])) {
127 pwent = getpwuid(strtol(optarg, NULL, 0));
129 pwent = getpwnam(optarg);
132 fprintf(fp, "Cannot find user %s: %s\n",
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);
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);
154 if (isdigit(optarg[0])) {
155 grent = getgrgid(strtol(optarg, NULL, 0));
157 grent = getgrnam(optarg);
160 fprintf(fp, "Cannot find group %s: %s\n",
166 sfree(&cmd->grent.gr_name);
167 sfree(&cmd->grent.gr_passwd);
168 afree(&cmd->grent.gr_mem);
170 sdup(&cmd->grent.gr_name);
171 sdup(&cmd->grent.gr_passwd);
172 adup(&cmd->grent.gr_mem);
176 cpy = strdup(optarg);
177 sub = strtok(cpy, ",");
180 if (isdigit(sub[0])) {
181 grent = getgrgid(strtol(sub, NULL, 0));
183 grent = getgrnam(sub);
187 "Cannot find group %s: %s\n",
188 sub, strerror(errno));
193 "Too many groups specified, "
194 "max %d\n", NGROUPS);
198 cmd->groups[i++] = grent->gr_gid;
199 sub = strtok(NULL, ",");
207 sreplace(&cmd->logfile, optarg);
210 sreplace(&cmd->rootdir, optarg);
216 sreplace(&cmd->jaildir, optarg);
219 rc = process_jailspec(cmd, optarg);
224 sreplace(&cmd->proctitle, optarg);
227 cmd->restart_per = 60;
228 if (sscanf(optarg, "%d:%d",
230 &cmd->restart_per) < 1) {
231 fprintf(fp, "bad restart specification: %s\n",
237 fprintf(fp, "Unknown option %c\n", ch);
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.
250 cmd->directive = strdup(av[i]);
253 cmd->empty_label = 0;
254 if (strcmp(av[i], "all") == 0)
257 cmd->label = strdup(av[i]);
259 cmd->ext_av = av + i;
260 cmd->ext_ac = ac - i;
264 fprintf(fp, "No directive specified\n");
276 execute_cmd(command_t *cmd)
278 const char *directive;
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) {
297 "Directive requires a label or 'all': %s\n",
305 * Process directives. If we are on the remote already the
306 * execute_remote() function will simply chain to the passed-in
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) {
342 rc = execute_remote(cmd, execute_log);
343 } else if (strcmp(directive, "tailf") == 0) {
345 rc = execute_remote(cmd, execute_log);
346 } else if (strcmp(directive, "logfile") == 0) {
347 rc = execute_remote(cmd, execute_logfile);
349 fprintf(cmd->fp, "Uknown directive: %s\n", directive);
357 execute_remote(command_t *cmd, int (*func)(command_t *cmd))
369 * If already on the remote service just execute the operation
372 if (cmd->cmdline == 0) {
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);
386 cmdlen = (cmd->label ? strlen(cmd->label) : 0);
388 while ((den = readdir(dir)) != NULL) {
392 if (strncmp(den->d_name, "service.", 8) != 0)
398 p1 = den->d_name + 8;
399 p2 = strrchr(p1, '.');
400 if (p2 == NULL || p2 < p1 || strcmp(p2, ".sk") != 0)
404 * Extract the label from the service.<label>.sk name.
408 *strrchr(plab, '.') = 0;
411 * Start remote execution (in parallel) for all matching
412 * labels. This will generally create some asynchronous
416 (cmdlen <= len && strncmp(cmd->label, plab, cmdlen) == 0)) {
417 remote_execute(cmd, plab);
424 * Wait for completion of remote commands and dump output.
432 free_cmd(command_t *cmd)
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);
455 if (cmd->logfd >= 0) {
460 bzero(cmd, sizeof(*cmd));
465 process_jailspec(command_t *cmd, const char *spec)
467 char *cpy = strdup(spec);
471 ptr = strtok(cpy, ",");
473 if (strcmp(ptr, "clean") == 0) {
475 } else if (strncmp(ptr, "ip=", 3) == 0) {
476 assert(0); /* XXX TODO */
478 fprintf(cmd->fp, "jail-spec '%s' not understood\n",
482 ptr = strtok(NULL, ",");