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