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, "Uknown 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