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
, "Unknown 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
, ",");