1 /* $NetBSD: shutdown.c,v 1.52 2010/06/09 04:51:53 riz 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.52 2010/06/09 04:51:53 riz 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, killflg, nofork, nosync, dodump; 92 static size_t mbuflen; 93 static int dopowerdown; 94 static const char *whom; 95 static char mbuf[BUFSIZ]; 96 static char *bootstr; 97 98 static void badtime(void) __dead; 99 static void die_you_gravy_sucking_pig_dog(void) __dead; 100 static void doitfast(void); 101 void dorcshutdown(void); 102 static void finish(int) __dead; 103 static void getoffset(char *); 104 static void loop(void); 105 static void nolog(void); 106 static void timeout(int); 107 static void timewarn(time_t); 108 static void usage(void) __dead; 109 110 int 111 main(int argc, char *argv[]) 112 { 113 char *p, *endp; 114 struct passwd *pw; 115 size_t arglen, len; 116 int ch; 117 118 (void)setprogname(argv[0]); 119 #ifndef DEBUG 120 if (geteuid()) 121 errx(1, "%s: Not super-user", strerror(EPERM)); 122 #endif 123 while ((ch = getopt(argc, argv, "b:Ddfhknpr")) != -1) 124 switch (ch) { 125 case 'b': 126 bootstr = optarg; 127 break; 128 case 'd': 129 dodump = 1; 130 break; 131 case 'D': 132 nofork = 1; 133 break; 134 case 'f': 135 dofast = 1; 136 break; 137 case 'p': 138 dopowerdown = 1; 139 /* FALLTHROUGH */ 140 case 'h': 141 dohalt = 1; 142 break; 143 case 'k': 144 killflg = 1; 145 break; 146 case 'n': 147 nosync = 1; 148 break; 149 case 'r': 150 doreboot = 1; 151 break; 152 case '?': 153 default: 154 usage(); 155 } 156 argc -= optind; 157 argv += optind; 158 159 if (argc < 1) 160 usage(); 161 162 if (dodump && !dohalt && !doreboot) 163 doreboot = 1; 164 165 if (dofast && nosync) { 166 warnx("Incompatible options -f and -n"); 167 usage(); 168 } 169 if (dohalt && doreboot) { 170 const char *which_flag = dopowerdown ? "p" : "h"; 171 172 warnx("Incompatible options -%s and -r", which_flag); 173 usage(); 174 } 175 176 getoffset(*argv++); 177 178 if (argv[0]) { 179 if (strcmp(argv[0], "-") || argv[1]) { 180 for (p = mbuf, len = sizeof(mbuf); *argv; ++argv) { 181 arglen = strlen(*argv); 182 if ((len -= arglen) <= 2) 183 break; 184 if (p != mbuf) 185 *p++ = ' '; 186 (void)memmove(p, *argv, arglen); 187 p += arglen; 188 } 189 *p = '\n'; 190 *++p = '\0'; 191 } else { 192 p = mbuf; 193 endp = mbuf + sizeof(mbuf) - 2; 194 for (;;) { 195 if (!fgets(p, endp - p + 1, stdin)) 196 break; 197 for (; *p && p < endp; ++p); 198 if (p == endp) { 199 *p = '\n'; 200 *++p = '\0'; 201 break; 202 } 203 } 204 } 205 } 206 mbuflen = strlen(mbuf); 207 208 if (offset) 209 (void)printf("Shutdown at %.24s.\n", ctime(&shuttime)); 210 else 211 (void)printf("Shutdown NOW!\n"); 212 213 if (!(whom = getlogin())) 214 whom = (pw = getpwuid(getuid())) ? pw->pw_name : "???"; 215 216 #ifdef DEBUG 217 (void)putc('\n', stdout); 218 #else 219 (void)setpriority(PRIO_PROCESS, 0, PRIO_MIN); 220 if (nofork == 0) { 221 int forkpid; 222 223 forkpid = fork(); 224 if (forkpid == -1) { 225 perror("shutdown: fork"); 226 exit(1); 227 } 228 if (forkpid) { 229 (void)printf("shutdown: [pid %d]\n", forkpid); 230 exit(0); 231 } 232 (void)setsid(); 233 } 234 #endif 235 openlog("shutdown", LOG_CONS, LOG_AUTH); 236 loop(); 237 /* NOTREACHED */ 238 #ifdef __GNUC__ 239 return 1; 240 #endif 241 } 242 243 void 244 loop(void) 245 { 246 const struct interval *tp; 247 u_int sltime; 248 int logged; 249 250 if (offset <= NOLOG_TIME) { 251 logged = 1; 252 nolog(); 253 } 254 else 255 logged = 0; 256 tp = tlist; 257 if (tp->timeleft < offset) 258 (void)sleep((u_int)(offset - tp->timeleft)); 259 else { 260 while (offset < tp->timeleft) 261 ++tp; 262 /* 263 * Warn now, if going to sleep more than a fifth of 264 * the next wait time. 265 */ 266 if ((sltime = offset - tp->timeleft) != 0) { 267 if (sltime > tp->timetowait / 5) 268 timewarn(offset); 269 (void)sleep(sltime); 270 } 271 } 272 for (;; ++tp) { 273 timewarn(tp->timeleft); 274 if (!logged && tp->timeleft <= NOLOG_TIME) { 275 logged = 1; 276 nolog(); 277 } 278 (void)sleep((u_int)tp->timetowait); 279 if (!tp->timeleft) 280 break; 281 } 282 die_you_gravy_sucking_pig_dog(); 283 } 284 285 static jmp_buf alarmbuf; 286 287 void 288 timewarn(time_t timeleft) 289 { 290 static int first; 291 static char hostname[MAXHOSTNAMELEN + 1]; 292 FILE *pf; 293 char wcmd[MAXPATHLEN + 4]; 294 295 if (!first++) { 296 (void)gethostname(hostname, sizeof(hostname)); 297 hostname[sizeof(hostname) - 1] = '\0'; 298 } 299 300 /* undoc -n option to wall suppresses normal wall banner */ 301 (void)snprintf(wcmd, sizeof wcmd, "%s -n", _PATH_WALL); 302 if ((pf = popen(wcmd, "w")) == NULL) { 303 syslog(LOG_ERR, "%s: Can't find `%s' (%m)", getprogname(), 304 _PATH_WALL); 305 return; 306 } 307 308 (void)fprintf(pf, 309 "\007*** %sSystem shutdown message from %s@%s ***\007\n", 310 timeleft ? "": "FINAL ", whom, hostname); 311 312 if (timeleft > 10*60) 313 (void)fprintf(pf, "System going down at %5.5s\n\n", 314 ctime(&shuttime) + 11); 315 else if (timeleft > 59) 316 (void)fprintf(pf, "System going down in %ld minute%s\n\n", 317 (long)timeleft / 60, (timeleft > 60) ? "s" : ""); 318 else if (timeleft) 319 (void)fprintf(pf, "System going down in 30 seconds\n\n"); 320 else 321 (void)fprintf(pf, "System going down IMMEDIATELY\n\n"); 322 323 if (mbuflen) 324 (void)fwrite(mbuf, sizeof(*mbuf), mbuflen, pf); 325 326 /* 327 * play some games, just in case wall doesn't come back 328 * probably unnecessary, given that wall is careful. 329 */ 330 if (!setjmp(alarmbuf)) { 331 (void)signal(SIGALRM, timeout); 332 (void)alarm((u_int)30); 333 (void)pclose(pf); 334 (void)alarm((u_int)0); 335 (void)signal(SIGALRM, SIG_DFL); 336 } 337 } 338 339 static void 340 /*ARGSUSED*/ 341 timeout(int signo) 342 { 343 longjmp(alarmbuf, 1); 344 } 345 346 static void 347 die_you_gravy_sucking_pig_dog(void) 348 { 349 const char *what; 350 351 if (doreboot) { 352 what = "reboot"; 353 } else if (dohalt && dopowerdown) { 354 what = "poweroff"; 355 } else if (dohalt) { 356 what = "halt"; 357 } else { 358 what = "shutdown"; 359 } 360 361 syslog(LOG_NOTICE, "%s by %s: %s", what, whom, mbuf); 362 (void)sleep(2); 363 364 (void)printf("\r\nSystem shutdown time has arrived\007\007\r\n"); 365 if (killflg) { 366 (void)printf("\rbut you'll have to do it yourself\r\n"); 367 finish(0); 368 } 369 if (dofast) 370 doitfast(); 371 dorcshutdown(); 372 if (doreboot || dohalt) { 373 const char *args[16]; 374 const char **arg, *path; 375 #ifndef DEBUG 376 int serrno; 377 #endif 378 379 arg = &args[0]; 380 if (doreboot) { 381 path = _PATH_REBOOT; 382 *arg++ = "reboot"; 383 } else { 384 path = _PATH_HALT; 385 *arg++ = "halt"; 386 } 387 if (dodump) 388 *arg++ = "-d"; 389 if (nosync) 390 *arg++ = "-n"; 391 if (dopowerdown) 392 *arg++ = "-p"; 393 *arg++ = "-l"; 394 if (bootstr) 395 *arg++ = bootstr; 396 *arg++ = 0; 397 #ifndef DEBUG 398 (void)unlink(_PATH_NOLOGIN); 399 (void)execve(path, __UNCONST(args), NULL); 400 serrno = errno; 401 syslog(LOG_ERR, "Can't exec `%s' (%m)", path); 402 errno = serrno; 403 warn("Can't exec `%s'", path); 404 #else 405 printf("%s", path); 406 for (arg = &args[0]; *arg; arg++) 407 printf(" %s", *arg); 408 printf("\n"); 409 #endif 410 } else { 411 #ifndef DEBUG 412 (void)kill(1, SIGTERM); /* to single user */ 413 #else 414 printf("kill 1\n"); 415 #endif 416 } 417 finish(0); 418 } 419 420 #define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0')) 421 422 void 423 getoffset(char *timearg) 424 { 425 struct tm *lt; 426 char *p; 427 time_t now; 428 int yearset; 429 430 (void)time(&now); 431 if (!strcasecmp(timearg, "now")) { /* now */ 432 offset = 0; 433 shuttime = now; 434 return; 435 } 436 437 if (*timearg == '+') { /* +minutes */ 438 if (!isdigit((unsigned char)*++timearg)) 439 badtime(); 440 offset = atoi(timearg) * 60; 441 shuttime = now + offset; 442 return; 443 } 444 445 /* handle hh:mm by getting rid of the colon */ 446 for (p = timearg; *p; ++p) 447 if (!isascii(*p) || !isdigit((unsigned char)*p)) { 448 if (*p == ':' && strlen(p) == 3) { 449 p[0] = p[1]; 450 p[1] = p[2]; 451 p[2] = '\0'; 452 } 453 else 454 badtime(); 455 } 456 457 (void)unsetenv("TZ"); /* OUR timezone */ 458 lt = localtime(&now); /* current time val */ 459 460 lt->tm_sec = 0; 461 462 yearset = 0; 463 switch (strlen(timearg)) { 464 case 12: 465 lt->tm_year = ATOI2(timearg) * 100 - TM_YEAR_BASE; 466 yearset = 1; 467 /* FALLTHROUGH */ 468 case 10: 469 if (yearset) { 470 lt->tm_year += ATOI2(timearg); 471 } else { 472 yearset = ATOI2(timearg); 473 if (yearset < 69) 474 lt->tm_year = yearset + 2000 - TM_YEAR_BASE; 475 else 476 lt->tm_year = yearset + 1900 - TM_YEAR_BASE; 477 } 478 /* FALLTHROUGH */ 479 case 8: 480 lt->tm_mon = ATOI2(timearg); 481 --lt->tm_mon; 482 /* FALLTHROUGH */ 483 case 6: 484 lt->tm_mday = ATOI2(timearg); 485 /* FALLTHROUGH */ 486 case 4: 487 lt->tm_hour = ATOI2(timearg); 488 /* FALLTHROUGH */ 489 case 2: 490 lt->tm_min = ATOI2(timearg); 491 break; 492 default: 493 badtime(); 494 } 495 496 if ((shuttime = mktime(lt)) == -1) 497 badtime(); 498 if ((offset = shuttime - now) < 0) 499 errx(1, "time is already past"); 500 } 501 502 void 503 dorcshutdown(void) 504 { 505 (void)printf("\r\nAbout to run shutdown hooks...\r\n"); 506 #ifndef DEBUG 507 (void)setuid(0); 508 (void)system(". " _PATH_RCSHUTDOWN); 509 #endif 510 (void)sleep(5); /* Give operator a chance to abort this. */ 511 (void)printf("\r\nDone running shutdown hooks.\r\n"); 512 } 513 514 #define FSMSG "fastboot file for fsck\n" 515 void 516 doitfast(void) 517 { 518 int fastfd; 519 520 if ((fastfd = open(_PATH_FASTBOOT, O_WRONLY|O_CREAT|O_TRUNC, 521 0664)) >= 0) { 522 (void)write(fastfd, FSMSG, sizeof(FSMSG) - 1); 523 (void)close(fastfd); 524 } 525 } 526 527 #define NOMSG "\n\nNO LOGINS: System going down at " 528 void 529 nolog(void) 530 { 531 int logfd; 532 char *ct; 533 534 (void)unlink(_PATH_NOLOGIN); /* in case linked to another file */ 535 (void)signal(SIGINT, finish); 536 (void)signal(SIGHUP, finish); 537 (void)signal(SIGQUIT, finish); 538 (void)signal(SIGTERM, finish); 539 if ((logfd = open(_PATH_NOLOGIN, O_WRONLY|O_CREAT|O_TRUNC, 540 0664)) >= 0) { 541 (void)write(logfd, NOMSG, sizeof(NOMSG) - 1); 542 ct = ctime(&shuttime); 543 (void)write(logfd, ct + 11, 5); 544 (void)write(logfd, "\n\n", 2); 545 (void)write(logfd, mbuf, strlen(mbuf)); 546 (void)close(logfd); 547 } 548 } 549 550 static void 551 /*ARGSUSED*/ 552 finish(int signo) 553 { 554 555 if (!killflg) 556 (void)unlink(_PATH_NOLOGIN); 557 exit(0); 558 } 559 560 static void 561 badtime(void) 562 { 563 564 warnx("illegal time format"); 565 usage(); 566 } 567 568 static void 569 usage(void) 570 { 571 572 (void)fprintf(stderr, 573 "Usage: %s [-Ddfhknpr] time [message ... | -]\n", 574 getprogname()); 575 exit(1); 576 } 577