1/* 2Copyright (c) 2001-2006, Gerrit Pape 3All rights reserved. 4 5Redistribution and use in source and binary forms, with or without 6modification, are permitted provided that the following conditions are met: 7 8 1. Redistributions of source code must retain the above copyright notice, 9 this list of conditions and the following disclaimer. 10 2. Redistributions in binary form must reproduce the above copyright 11 notice, this list of conditions and the following disclaimer in the 12 documentation and/or other materials provided with the distribution. 13 3. The name of the author may not be used to endorse or promote products 14 derived from this software without specific prior written permission. 15 16THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26*/ 27 28/* Taken from http://smarden.sunsite.dk/runit/sv.8.html: 29 30sv - control and manage services monitored by runsv 31 32sv [-v] [-w sec] command services 33/etc/init.d/service [-w sec] command 34 35The sv program reports the current status and controls the state of services 36monitored by the runsv(8) supervisor. 37 38services consists of one or more arguments, each argument naming a directory 39service used by runsv(8). If service doesn't start with a dot or slash, 40it is searched in the default services directory /var/service/, otherwise 41relative to the current directory. 42 43command is one of up, down, status, once, pause, cont, hup, alarm, interrupt, 441, 2, term, kill, or exit, or start, stop, restart, shutdown, force-stop, 45force-reload, force-restart, force-shutdown. 46 47The sv program can be sym-linked to /etc/init.d/ to provide an LSB init 48script interface. The service to be controlled then is specified by the 49base name of the "init script". 50 51status 52 Report the current status of the service, and the appendant log service 53 if available, to standard output. 54up 55 If the service is not running, start it. If the service stops, restart it. 56down 57 If the service is running, send it the TERM signal, and the CONT signal. 58 If ./run exits, start ./finish if it exists. After it stops, do not 59 restart service. 60once 61 If the service is not running, start it. Do not restart it if it stops. 62pause cont hup alarm interrupt quit 1 2 term kill 63 If the service is running, send it the STOP, CONT, HUP, ALRM, INT, QUIT, 64 USR1, USR2, TERM, or KILL signal respectively. 65exit 66 If the service is running, send it the TERM signal, and the CONT signal. 67 Do not restart the service. If the service is down, and no log service 68 exists, runsv(8) exits. If the service is down and a log service exists, 69 send the TERM signal to the log service. If the log service is down, 70 runsv(8) exits. This command is ignored if it is given to an appendant 71 log service. 72 73sv actually looks only at the first character of above commands. 74 75Commands compatible to LSB init script actions: 76 77status 78 Same as status. 79start 80 Same as up, but wait up to 7 seconds for the command to take effect. 81 Then report the status or timeout. If the script ./check exists in 82 the service directory, sv runs this script to check whether the service 83 is up and available; it's considered to be available if ./check exits 84 with 0. 85stop 86 Same as down, but wait up to 7 seconds for the service to become down. 87 Then report the status or timeout. 88restart 89 Send the commands term, cont, and up to the service, and wait up to 90 7 seconds for the service to restart. Then report the status or timeout. 91 If the script ./check exists in the service directory, sv runs this script 92 to check whether the service is up and available again; it's considered 93 to be available if ./check exits with 0. 94shutdown 95 Same as exit, but wait up to 7 seconds for the runsv(8) process 96 to terminate. Then report the status or timeout. 97force-stop 98 Same as down, but wait up to 7 seconds for the service to become down. 99 Then report the status, and on timeout send the service the kill command. 100force-reload 101 Send the service the term and cont commands, and wait up to 102 7 seconds for the service to restart. Then report the status, 103 and on timeout send the service the kill command. 104force-restart 105 Send the service the term, cont and up commands, and wait up to 106 7 seconds for the service to restart. Then report the status, and 107 on timeout send the service the kill command. If the script ./check 108 exists in the service directory, sv runs this script to check whether 109 the service is up and available again; it's considered to be available 110 if ./check exits with 0. 111force-shutdown 112 Same as exit, but wait up to 7 seconds for the runsv(8) process to 113 terminate. Then report the status, and on timeout send the service 114 the kill command. 115 116Additional Commands 117 118check 119 Check for the service to be in the state that's been requested. Wait up to 120 7 seconds for the service to reach the requested state, then report 121 the status or timeout. If the requested state of the service is up, 122 and the script ./check exists in the service directory, sv runs 123 this script to check whether the service is up and running; 124 it's considered to be up if ./check exits with 0. 125 126Options 127 128-v 129 wait up to 7 seconds for the command to take effect. 130 Then report the status or timeout. 131-w sec 132 Override the default timeout of 7 seconds with sec seconds. Implies -v. 133 134Environment 135 136SVDIR 137 The environment variable $SVDIR overrides the default services directory 138 /var/service. 139SVWAIT 140 The environment variable $SVWAIT overrides the default 7 seconds to wait 141 for a command to take effect. It is overridden by the -w option. 142 143Exit Codes 144 sv exits 0, if the command was successfully sent to all services, and, 145 if it was told to wait, the command has taken effect to all services. 146 147 For each service that caused an error (e.g. the directory is not 148 controlled by a runsv(8) process, or sv timed out while waiting), 149 sv increases the exit code by one and exits non zero. The maximum 150 is 99. sv exits 100 on error. 151*/ 152 153/* Busyboxed by Denis Vlasenko <vda.linux@googlemail.com> */ 154/* TODO: depends on runit_lib.c - review and reduce/eliminate */ 155 156#include <sys/poll.h> 157#include <sys/file.h> 158#include "libbb.h" 159#include "runit_lib.h" 160 161static const char *acts; 162static char **service; 163static unsigned rc; 164/* "Bernstein" time format: unix + 0x400000000000000aULL */ 165static uint64_t tstart, tnow; 166svstatus_t svstatus; 167 168 169static void fatal_cannot(const char *m1) ATTRIBUTE_NORETURN; 170static void fatal_cannot(const char *m1) 171{ 172 bb_perror_msg("fatal: cannot %s", m1); 173 _exit(151); 174} 175 176static void out(const char *p, const char *m1) 177{ 178 printf("%s%s: %s", p, *service, m1); 179 if (errno) { 180 printf(": %s", strerror(errno)); 181 } 182 puts(""); /* will also flush the output */ 183} 184 185#define WARN "warning: " 186#define OK "ok: " 187 188static void fail(const char *m1) 189{ 190 ++rc; 191 out("fail: ", m1); 192} 193static void failx(const char *m1) 194{ 195 errno = 0; 196 fail(m1); 197} 198static void warn(const char *m1) 199{ 200 ++rc; 201 /* "warning: <service>: <m1>\n" */ 202 out("warning: ", m1); 203} 204static void ok(const char *m1) 205{ 206 errno = 0; 207 out(OK, m1); 208} 209 210static int svstatus_get(void) 211{ 212 int fd, r; 213 214 fd = open_write("supervise/ok"); 215 if (fd == -1) { 216 if (errno == ENODEV) { 217 *acts == 'x' ? ok("runsv not running") 218 : failx("runsv not running"); 219 return 0; 220 } 221 warn("cannot open supervise/ok"); 222 return -1; 223 } 224 close(fd); 225 fd = open_read("supervise/status"); 226 if (fd == -1) { 227 warn("cannot open supervise/status"); 228 return -1; 229 } 230 r = read(fd, &svstatus, 20); 231 close(fd); 232 switch (r) { 233 case 20: 234 break; 235 case -1: 236 warn("cannot read supervise/status"); 237 return -1; 238 default: 239 errno = 0; 240 warn("cannot read supervise/status: bad format"); 241 return -1; 242 } 243 return 1; 244} 245 246static unsigned svstatus_print(const char *m) 247{ 248 int diff; 249 int pid; 250 int normallyup = 0; 251 struct stat s; 252 uint64_t timestamp; 253 254 if (stat("down", &s) == -1) { 255 if (errno != ENOENT) { 256 bb_perror_msg(WARN"cannot stat %s/down", *service); 257 return 0; 258 } 259 normallyup = 1; 260 } 261 pid = SWAP_LE32(svstatus.pid_le32); 262 timestamp = SWAP_BE64(svstatus.time_be64); 263 if (pid) { 264 switch (svstatus.run_or_finish) { 265 case 1: printf("run: "); break; 266 case 2: printf("finish: "); break; 267 } 268 printf("%s: (pid %d) ", m, pid); 269 } else { 270 printf("down: %s: ", m); 271 } 272 diff = tnow - timestamp; 273 printf("%us", (diff < 0 ? 0 : diff)); 274 if (pid) { 275 if (!normallyup) printf(", normally down"); 276 if (svstatus.paused) printf(", paused"); 277 if (svstatus.want == 'd') printf(", want down"); 278 if (svstatus.got_term) printf(", got TERM"); 279 } else { 280 if (normallyup) printf(", normally up"); 281 if (svstatus.want == 'u') printf(", want up"); 282 } 283 return pid ? 1 : 2; 284} 285 286static int status(const char *unused) 287{ 288 int r; 289 290 r = svstatus_get(); 291 switch (r) { case -1: case 0: return 0; } 292 293 r = svstatus_print(*service); 294 if (chdir("log") == -1) { 295 if (errno != ENOENT) { 296 printf("; log: "WARN"cannot change to log service directory: %s", 297 strerror(errno)); 298 } 299 } else if (svstatus_get()) { 300 printf("; "); 301 svstatus_print("log"); 302 } 303 puts(""); /* will also flush the output */ 304 return r; 305} 306 307static int checkscript(void) 308{ 309 char *prog[2]; 310 struct stat s; 311 int pid, w; 312 313 if (stat("check", &s) == -1) { 314 if (errno == ENOENT) return 1; 315 bb_perror_msg(WARN"cannot stat %s/check", *service); 316 return 0; 317 } 318 /* if (!(s.st_mode & S_IXUSR)) return 1; */ 319 prog[0] = (char*)"./check"; 320 prog[1] = NULL; 321 pid = spawn(prog); 322 if (pid <= 0) { 323 bb_perror_msg(WARN"cannot %s child %s/check", "run", *service); 324 return 0; 325 } 326 while (wait_pid(&w, pid) == -1) { 327 if (errno == EINTR) continue; 328 bb_perror_msg(WARN"cannot %s child %s/check", "wait for", *service); 329 return 0; 330 } 331 return !wait_exitcode(w); 332} 333 334static int check(const char *a) 335{ 336 int r; 337 unsigned pid; 338 uint64_t timestamp; 339 340 r = svstatus_get(); 341 if (r == -1) 342 return -1; 343 if (r == 0) { 344 if (*a == 'x') 345 return 1; 346 return -1; 347 } 348 pid = SWAP_LE32(svstatus.pid_le32); 349 switch (*a) { 350 case 'x': 351 return 0; 352 case 'u': 353 if (!pid || svstatus.run_or_finish != 1) return 0; 354 if (!checkscript()) return 0; 355 break; 356 case 'd': 357 if (pid) return 0; 358 break; 359 case 'c': 360 if (pid && !checkscript()) return 0; 361 break; 362 case 't': 363 if (!pid && svstatus.want == 'd') break; 364 timestamp = SWAP_BE64(svstatus.time_be64); 365 if ((tstart > timestamp) || !pid || svstatus.got_term || !checkscript()) 366 return 0; 367 break; 368 case 'o': 369 timestamp = SWAP_BE64(svstatus.time_be64); 370 if ((!pid && tstart > timestamp) || (pid && svstatus.want != 'd')) 371 return 0; 372 } 373 printf(OK); 374 svstatus_print(*service); 375 puts(""); /* will also flush the output */ 376 return 1; 377} 378 379static int control(const char *a) 380{ 381 int fd, r; 382 383 if (svstatus_get() <= 0) 384 return -1; 385 if (svstatus.want == *a) 386 return 0; 387 fd = open_write("supervise/control"); 388 if (fd == -1) { 389 if (errno != ENODEV) 390 warn("cannot open supervise/control"); 391 else 392 *a == 'x' ? ok("runsv not running") : failx("runsv not running"); 393 return -1; 394 } 395 r = write(fd, a, strlen(a)); 396 close(fd); 397 if (r != strlen(a)) { 398 warn("cannot write to supervise/control"); 399 return -1; 400 } 401 return 1; 402} 403 404int sv_main(int argc, char **argv); 405int sv_main(int argc, char **argv) 406{ 407 unsigned opt; 408 unsigned i, want_exit; 409 char *x; 410 char *action; 411 const char *varservice = "/var/service/"; 412 unsigned services; 413 char **servicex; 414 unsigned waitsec = 7; 415 smallint kll = 0; 416 smallint verbose = 0; 417 int (*act)(const char*); 418 int (*cbk)(const char*); 419 int curdir; 420 421 xfunc_error_retval = 100; 422 423 x = getenv("SVDIR"); 424 if (x) varservice = x; 425 x = getenv("SVWAIT"); 426 if (x) waitsec = xatou(x); 427 428 opt = getopt32(argv, "w:v", &x); 429 if (opt & 1) waitsec = xatou(x); // -w 430 if (opt & 2) verbose = 1; // -v 431 argc -= optind; 432 argv += optind; 433 action = *argv++; 434 if (!action || !*argv) bb_show_usage(); 435 service = argv; 436 services = argc - 1; 437 438 tnow = time(0) + 0x400000000000000aULL; 439 tstart = tnow; 440 curdir = open_read("."); 441 if (curdir == -1) 442 fatal_cannot("open current directory"); 443 444 act = &control; 445 acts = "s"; 446 cbk = ✓ 447 448 switch (*action) { 449 case 'x': 450 case 'e': 451 acts = "x"; 452 if (!verbose) cbk = NULL; 453 break; 454 case 'X': 455 case 'E': 456 acts = "x"; 457 kll = 1; 458 break; 459 case 'D': 460 acts = "d"; 461 kll = 1; 462 break; 463 case 'T': 464 acts = "tc"; 465 kll = 1; 466 break; 467 case 'c': 468 if (str_equal(action, "check")) { 469 act = NULL; 470 acts = "c"; 471 break; 472 } 473 case 'u': case 'd': case 'o': case 't': case 'p': case 'h': 474 case 'a': case 'i': case 'k': case 'q': case '1': case '2': 475 action[1] = '\0'; 476 acts = action; 477 if (!verbose) cbk = NULL; 478 break; 479 case 's': 480 if (str_equal(action, "shutdown")) { 481 acts = "x"; 482 break; 483 } 484 if (str_equal(action, "start")) { 485 acts = "u"; 486 break; 487 } 488 if (str_equal(action, "stop")) { 489 acts = "d"; 490 break; 491 } 492 /* "status" */ 493 act = &status; 494 cbk = NULL; 495 break; 496 case 'r': 497 if (str_equal(action, "restart")) { 498 acts = "tcu"; 499 break; 500 } 501 bb_show_usage(); 502 case 'f': 503 if (str_equal(action, "force-reload")) { 504 acts = "tc"; 505 kll = 1; 506 break; 507 } 508 if (str_equal(action, "force-restart")) { 509 acts = "tcu"; 510 kll = 1; 511 break; 512 } 513 if (str_equal(action, "force-shutdown")) { 514 acts = "x"; 515 kll = 1; 516 break; 517 } 518 if (str_equal(action, "force-stop")) { 519 acts = "d"; 520 kll = 1; 521 break; 522 } 523 default: 524 bb_show_usage(); 525 } 526 527 servicex = service; 528 for (i = 0; i < services; ++i) { 529 if ((**service != '/') && (**service != '.')) { 530 if (chdir(varservice) == -1) 531 goto chdir_failed_0; 532 } 533 if (chdir(*service) == -1) { 534 chdir_failed_0: 535 fail("cannot change to service directory"); 536 goto nullify_service_0; 537 } 538 if (act && (act(acts) == -1)) { 539 nullify_service_0: 540 *service = NULL; 541 } 542 if (fchdir(curdir) == -1) 543 fatal_cannot("change to original directory"); 544 service++; 545 } 546 547 if (cbk) while (1) { 548 int diff; 549 550 diff = tnow - tstart; 551 service = servicex; 552 want_exit = 1; 553 for (i = 0; i < services; ++i, ++service) { 554 if (!*service) 555 continue; 556 if ((**service != '/') && (**service != '.')) { 557 if (chdir(varservice) == -1) 558 goto chdir_failed; 559 } 560 if (chdir(*service) == -1) { 561 chdir_failed: 562 fail("cannot change to service directory"); 563 goto nullify_service; 564 } 565 if (cbk(acts) != 0) 566 goto nullify_service; 567 want_exit = 0; 568 if (diff >= waitsec) { 569 printf(kll ? "kill: " : "timeout: "); 570 if (svstatus_get() > 0) { 571 svstatus_print(*service); 572 ++rc; 573 } 574 puts(""); /* will also flush the output */ 575 if (kll) 576 control("k"); 577 nullify_service: 578 *service = NULL; 579 } 580 if (fchdir(curdir) == -1) 581 fatal_cannot("change to original directory"); 582 } 583 if (want_exit) break; 584 usleep(420000); 585 tnow = time(0) + 0x400000000000000aULL; 586 } 587 return rc > 99 ? 99 : rc; 588} 589