1 /* $NetBSD: shutdown.c,v 1.60 2026/05/11 23:21:07 kre Exp $ */ 2 3 /* 4 * Copyright (c) 1988, 1990, 1993 5 * The Regents of the University of California. All rights reserved. 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 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 #ifndef lint 34 __COPYRIGHT("@(#) Copyright (c) 1988, 1990, 1993\ 35 The Regents of the University of California. All rights reserved."); 36 #endif /* not lint */ 37 38 #ifndef lint 39 #if 0 40 static char sccsid[] = "@(#)shutdown.c 8.4 (Berkeley) 4/28/95"; 41 #else 42 __RCSID("$NetBSD: shutdown.c,v 1.60 2026/05/11 23:21:07 kre Exp $"); 43 #endif 44 #endif /* not lint */ 45 46 #include <sys/param.h> 47 #include <sys/time.h> 48 #include <sys/resource.h> 49 #include <sys/syslog.h> 50 51 #include <ctype.h> 52 #include <err.h> 53 #include <fcntl.h> 54 #include <pwd.h> 55 #include <setjmp.h> 56 #include <signal.h> 57 #include <stdio.h> 58 #include <stdlib.h> 59 #include <string.h> 60 #include <time.h> 61 #include <tzfile.h> 62 #include <unistd.h> 63 #include <errno.h> 64 65 #include "pathnames.h" 66 67 #ifdef DEBUG 68 #undef _PATH_NOLOGIN 69 #define _PATH_NOLOGIN "./nologin" 70 #undef _PATH_FASTBOOT 71 #define _PATH_FASTBOOT "./fastboot" 72 #endif 73 74 #define H *60*60 75 #define M *60 76 #define S *1 77 #define NOLOG_TIME 5*60 78 static const struct interval { 79 time_t timeleft, timetowait; 80 } tlist[] = { 81 { 10 H, 5 H }, { 5 H, 3 H }, { 2 H, 1 H }, { 1 H, 30 M }, 82 { 30 M, 10 M }, { 20 M, 10 M }, { 10 M, 5 M }, { 5 M, 3 M }, 83 { 2 M, 1 M }, { 1 M, 30 S }, { 30 S, 30 S }, 84 { 0, 0 } 85 }; 86 #undef H 87 #undef M 88 #undef S 89 90 static time_t offset, shuttime; 91 static int dofast, dohalt, doreboot, kidding, nofork, nosync, dodump; 92 static size_t mbuflen; 93 static int dopowerdown; 94 static int dodebug, dosilent, doverbose; 95 static const char *whom; 96 static char mbuf[BUFSIZ]; 97 static char *bootstr; 98 99 static void badtime(void) __dead; 100 static void die_you_gravy_sucking_pig_dog(void) __dead; 101 static void doitfast(void); 102 static void dorcshutdown(const char *what); 103 static void finish(int) __dead; 104 static void getoffset(char *); 105 static void loop(void) __dead; 106 static void nolog(void); 107 static void timeout(int) __dead; 108 static void timewarn(time_t); 109 static void usage(void) __dead; 110 111 int 112 main(int argc, char *argv[]) 113 { 114 char *p, *endp; 115 struct passwd *pw; 116 size_t arglen, len; 117 int ch; 118 119 (void)setprogname(argv[0]); 120 #ifndef DEBUG 121 if (geteuid()) 122 errx(1, "%s: Not super-user", strerror(EPERM)); 123 #endif 124 while ((ch = getopt(argc, argv, "b:Ddfhknprvxz")) != -1) 125 switch (ch) { 126 case 'b': 127 bootstr = optarg; 128 break; 129 case 'd': 130 dodump = 1; 131 break; 132 case 'D': 133 nofork = 1; 134 break; 135 case 'f': 136 dofast = 1; 137 break; 138 case 'p': 139 dopowerdown = 1; 140 /* FALLTHROUGH */ 141 case 'h': 142 dohalt = 1; 143 break; 144 case 'k': 145 kidding = 1; 146 break; 147 case 'n': 148 nosync = 1; 149 break; 150 case 'r': 151 doreboot = 1; 152 break; 153 case 'v': 154 doverbose = 1; 155 break; 156 case 'x': 157 dodebug = 1; 158 break; 159 case 'z': 160 dosilent = 1; 161 break; 162 case '?': 163 default: 164 usage(); 165 } 166 argc -= optind; 167 argv += optind; 168 169 if (argc < 1) 170 usage(); 171 172 if (dodump && !dohalt && !doreboot) 173 doreboot = 1; 174 175 if (dofast && nosync) { 176 warnx("Incompatible options -f and -n"); 177 usage(); 178 } 179 if (dohalt && doreboot) { 180 const char *which_flag = dopowerdown ? "p" : "h"; 181 182 warnx("Incompatible options -%s and -r", which_flag); 183 usage(); 184 } 185 186 getoffset(*argv++); 187 188 if (argv[0]) { 189 if (strcmp(argv[0], "-") || argv[1]) { 190 for (p = mbuf, len = sizeof(mbuf); *argv; ++argv) { 191 arglen = strlen(*argv); 192 if ((len -= arglen) <= 2) 193 break; 194 if (p != mbuf) 195 *p++ = ' '; 196 (void)memmove(p, *argv, arglen); 197 p += arglen; 198 } 199 *p = '\n'; 200 *++p = '\0'; 201 } else { 202 p = mbuf; 203 endp = mbuf + sizeof(mbuf) - 2; 204 for (;;) { 205 if (!fgets(p, endp - p + 1, stdin)) 206 break; 207 for (; *p && p < endp; ++p); 208 if (p == endp) { 209 *p = '\n'; 210 *++p = '\0'; 211 break; 212 } 213 } 214 } 215 } 216 mbuflen = strlen(mbuf); 217 218 if (offset) 219 (void)printf("Shutdown at %.24s.\n", ctime(&shuttime)); 220 else 221 (void)printf("Shutdown NOW!\n"); 222 223 if (!(whom = getlogin())) 224 whom = (pw = getpwuid(getuid())) ? pw->pw_name : "???"; 225 226 #ifdef DEBUG 227 (void)putc('\n', stdout); 228 #else 229 (void)setpriority(PRIO_PROCESS, 0, PRIO_MIN); 230 if (nofork == 0) { 231 int forkpid; 232 233 forkpid = fork(); 234 if (forkpid == -1) { 235 perror("shutdown: fork"); 236 exit(1); 237 } 238 if (forkpid) { 239 (void)printf("shutdown: [pid %d]\n", forkpid); 240 exit(0); 241 } 242 (void)setsid(); 243 } 244 #endif 245 openlog("shutdown", LOG_CONS, LOG_AUTH); 246 loop(); 247 /* NOTREACHED */ 248 #ifdef __GNUC__ 249 return 1; 250 #endif 251 } 252 253 static void 254 loop(void) 255 { 256 const struct interval *tp; 257 u_int sltime; 258 int logged; 259 260 if (offset <= NOLOG_TIME) { 261 logged = 1; 262 nolog(); 263 } 264 else 265 logged = 0; 266 tp = tlist; 267 if (tp->timeleft < offset) 268 (void)sleep((u_int)(offset - tp->timeleft)); 269 else { 270 while (offset < tp->timeleft) 271 ++tp; 272 /* 273 * Warn now, if going to sleep more than a fifth of 274 * the next wait time. 275 */ 276 if ((sltime = offset - tp->timeleft) != 0) { 277 if (sltime > tp->timetowait / 5) 278 timewarn(offset); 279 (void)sleep(sltime); 280 } 281 } 282 for (;; ++tp) { 283 timewarn(tp->timeleft); 284 if (!logged && tp->timeleft <= NOLOG_TIME) { 285 logged = 1; 286 nolog(); 287 } 288 (void)sleep((u_int)tp->timetowait); 289 if (!tp->timeleft) 290 break; 291 } 292 die_you_gravy_sucking_pig_dog(); 293 } 294 295 static jmp_buf alarmbuf; 296 297 #define plural(n) ((n) == 1 ? "" : "s") 298 299 static void 300 timewarn(time_t timeleft) 301 { 302 static int first; 303 static char hostname[MAXHOSTNAMELEN + 1]; 304 FILE *pf; 305 char wcmd[MAXPATHLEN + 4]; 306 307 if (!first++) { 308 (void)gethostname(hostname, sizeof(hostname)); 309 hostname[sizeof(hostname) - 1] = '\0'; 310 } 311 312 /* undoc -n option to wall suppresses normal wall banner */ 313 (void)snprintf(wcmd, sizeof wcmd, "%s -n", _PATH_WALL); 314 if ((pf = popen(wcmd, "w")) == NULL) { 315 syslog(LOG_ERR, "%s: Can't find `%s' (%m)", getprogname(), 316 _PATH_WALL); 317 return; 318 } 319 320 (void)fprintf(pf, 321 "\007*** %sSystem shutdown message from %s@%s ***\007\n", 322 timeleft ? "": "FINAL ", whom, hostname); 323 324 if (timeleft > 10*60) 325 (void)fprintf(pf, "System going down at %5.5s\n\n", 326 ctime(&shuttime) + 11); 327 else if (timeleft > 59) 328 (void)fprintf(pf, "System going down in %ld minute%s\n\n", 329 (long)timeleft / 60, plural((long)timeleft / 60)); 330 else if (timeleft) 331 (void)fprintf(pf, "System going down in ~30 seconds\n\n"); 332 else 333 (void)fprintf(pf, "System going down IMMEDIATELY\n\n"); 334 335 if (mbuflen) 336 (void)fwrite(mbuf, 1, mbuflen, pf); 337 338 /* 339 * play some games, just in case wall doesn't come back 340 * probably unnecessary, given that wall is careful. 341 */ 342 if (!setjmp(alarmbuf)) { 343 (void)signal(SIGALRM, timeout); 344 (void)alarm((u_int)30); 345 (void)pclose(pf); 346 (void)alarm((u_int)0); 347 (void)signal(SIGALRM, SIG_DFL); 348 } 349 } 350 351 static void 352 /*ARGSUSED*/ 353 timeout(int signo) 354 { 355 longjmp(alarmbuf, 1); 356 } 357 358 static void 359 die_you_gravy_sucking_pig_dog(void) 360 { 361 const char *what; 362 363 if (doreboot) { 364 what = "reboot"; 365 } else if (dohalt && dopowerdown) { 366 what = "poweroff"; 367 } else if (dohalt) { 368 what = "halt"; 369 } else { 370 what = "shutdown"; 371 } 372 373 syslog(LOG_NOTICE, "%s by %s: %s", what, whom, mbuf); 374 (void)sleep(2); 375 376 (void)printf("\r\nSystem shutdown time has arrived\007\007\r\n"); 377 if (kidding) { 378 (void)printf("\rbut you'll have to do it yourself\r\n"); 379 finish(0); 380 } 381 if (dofast) 382 doitfast(); 383 dorcshutdown(what); 384 if (doreboot || dohalt) { 385 const char *args[20]; 386 const char **arg, *path; 387 #ifndef DEBUG 388 int serrno; 389 #endif 390 391 arg = &args[0]; 392 if (doreboot) { 393 path = _PATH_REBOOT; 394 *arg++ = "reboot"; 395 } else { 396 path = _PATH_HALT; 397 *arg++ = "halt"; 398 } 399 if (doverbose) 400 *arg++ = "-v"; 401 if (dodebug) 402 *arg++ = "-x"; 403 if (dosilent) 404 *arg++ = "-z"; 405 if (dodump) 406 *arg++ = "-d"; 407 if (nosync) 408 *arg++ = "-n"; 409 if (dopowerdown) 410 *arg++ = "-p"; 411 *arg++ = "-l"; 412 if (bootstr) 413 *arg++ = bootstr; 414 *arg++ = NULL; 415 #ifndef DEBUG 416 (void)unlink(_PATH_NOLOGIN); 417 (void)execve(path, __UNCONST(args), NULL); 418 serrno = errno; 419 syslog(LOG_ERR, "Can't exec `%s' (%m)", path); 420 errno = serrno; 421 warn("Can't exec `%s'", path); 422 #else 423 printf("%s", path); 424 for (arg = &args[0]; *arg; arg++) 425 printf(" %s", *arg); 426 printf("\n"); 427 #endif 428 } else { 429 #ifndef DEBUG 430 (void)kill(1, SIGTERM); /* to single user */ 431 #else 432 printf("kill 1\n"); 433 #endif 434 } 435 finish(0); 436 } 437 438 #define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0')) 439 440 static void 441 getoffset(char *timearg) 442 { 443 struct tm *lt; 444 char *p; 445 time_t now; 446 int yearset; 447 448 (void)time(&now); 449 if (!strcasecmp(timearg, "now")) { /* now */ 450 offset = 0; 451 shuttime = now; 452 return; 453 } 454 455 if (*timearg == '+') { /* +minutes */ 456 if (!isdigit((unsigned char)*++timearg)) 457 badtime(); 458 offset = atoi(timearg) * 60; 459 shuttime = now + offset; 460 return; 461 } 462 463 /* handle hh:mm by getting rid of the colon */ 464 for (p = timearg; *p; ++p) 465 if (!isascii(*p) || !isdigit((unsigned char)*p)) { 466 if (*p == ':' && strlen(p) == 3) { 467 p[0] = p[1]; 468 p[1] = p[2]; 469 p[2] = '\0'; 470 } 471 else 472 badtime(); 473 } 474 475 (void)unsetenv("TZ"); /* OUR timezone */ 476 lt = localtime(&now); /* current time val */ 477 478 lt->tm_sec = 0; 479 480 yearset = 0; 481 switch (strlen(timearg)) { 482 case 12: 483 lt->tm_year = ATOI2(timearg) * 100 - TM_YEAR_BASE; 484 yearset = 1; 485 /* FALLTHROUGH */ 486 case 10: 487 if (yearset) { 488 lt->tm_year += ATOI2(timearg); 489 } else { 490 yearset = ATOI2(timearg); 491 if (yearset < 20) 492 lt->tm_year = yearset + 2100 - TM_YEAR_BASE; 493 else 494 lt->tm_year = yearset + 2000 - TM_YEAR_BASE; 495 } 496 /* FALLTHROUGH */ 497 case 8: 498 lt->tm_mon = ATOI2(timearg); 499 if (lt->tm_mon < 1 || lt->tm_mon > 12) 500 badtime(); 501 --lt->tm_mon; 502 /* FALLTHROUGH */ 503 case 6: 504 lt->tm_mday = ATOI2(timearg); 505 if (lt->tm_mday < 1 || lt->tm_mday > 31) 506 badtime(); 507 /* FALLTHROUGH */ 508 case 4: 509 lt->tm_hour = ATOI2(timearg); 510 if (lt->tm_hour < 0 || lt->tm_hour > 23) 511 badtime(); 512 /* FALLTHROUGH */ 513 case 2: 514 lt->tm_min = ATOI2(timearg); 515 /* ignore possibility of a leap second */ 516 if (lt->tm_min < 0 || lt->tm_min > 59) 517 badtime(); 518 break; 519 default: 520 badtime(); 521 } 522 523 if ((shuttime = mktime(lt)) == -1) 524 badtime(); 525 if ((offset = shuttime - now) < 0) 526 errx(1, "time is already past"); 527 } 528 529 static void 530 dorcshutdown(const char *arg) 531 { 532 static char cmd[sizeof(_PATH_RCSHUTDOWN) + 64]; 533 534 if (arg) { 535 snprintf(cmd, sizeof(cmd), "set -- %s && . " _PATH_RCSHUTDOWN, 536 arg); 537 cmd[sizeof(cmd)-1] = 0; 538 arg = cmd; 539 } else { 540 arg = ". " _PATH_RCSHUTDOWN; 541 } 542 543 (void)printf("\r\nAbout to run shutdown hooks...\r\n"); 544 #ifndef DEBUG 545 (void)setuid(0); 546 (void)system(arg); 547 #endif 548 (void)sleep(5); /* Give operator a chance to abort this. */ 549 (void)printf("\r\nDone running shutdown hooks.\r\n"); 550 } 551 552 #define FSMSG "fastboot file for fsck\n" 553 static void 554 doitfast(void) 555 { 556 int fastfd; 557 558 if ((fastfd = open(_PATH_FASTBOOT, O_WRONLY|O_CREAT|O_TRUNC, 559 0664)) >= 0) { 560 (void)write(fastfd, FSMSG, sizeof(FSMSG) - 1); 561 (void)close(fastfd); 562 } 563 } 564 565 #define NOMSG "\n\nNO LOGINS: System going down at " 566 static void 567 nolog(void) 568 { 569 int logfd; 570 char *ct; 571 572 if (kidding) 573 return; 574 575 (void)unlink(_PATH_NOLOGIN); /* in case linked to another file */ 576 (void)signal(SIGINT, finish); 577 (void)signal(SIGHUP, finish); 578 (void)signal(SIGQUIT, finish); 579 (void)signal(SIGTERM, finish); 580 if ((logfd = open(_PATH_NOLOGIN, O_WRONLY|O_CREAT|O_TRUNC, 581 0664)) >= 0) { 582 (void)write(logfd, NOMSG, sizeof(NOMSG) - 1); 583 ct = ctime(&shuttime); 584 (void)write(logfd, ct + 11, 5); 585 (void)write(logfd, "\n\n", 2); 586 (void)write(logfd, mbuf, strlen(mbuf)); 587 (void)close(logfd); 588 } 589 } 590 591 static void 592 /*ARGSUSED*/ 593 finish(int signo) 594 { 595 596 if (!kidding) 597 (void)unlink(_PATH_NOLOGIN); 598 exit(0); 599 } 600 601 static void 602 badtime(void) 603 { 604 605 warnx("illegal time format"); 606 usage(); 607 } 608 609 static void 610 usage(void) 611 { 612 613 (void)fprintf(stderr, 614 "Usage: %s [-Ddfhknprvxz] [-b bootstr] time [message ... | -]\n", 615 getprogname()); 616 exit(1); 617 } 618