shutdown.c revision 1.48
1/* $OpenBSD: shutdown.c,v 1.48 2018/02/24 20:00:07 cheloha 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#include <sys/types.h> 34#include <sys/resource.h> 35#include <sys/syslog.h> 36#include <sys/types.h> 37#include <sys/wait.h> 38 39#include <ctype.h> 40#include <fcntl.h> 41#include <sys/termios.h> 42#include <pwd.h> 43#include <signal.h> 44#include <stdio.h> 45#include <stdlib.h> 46#include <string.h> 47#include <time.h> 48#include <unistd.h> 49#include <limits.h> 50#include <errno.h> 51#include <err.h> 52 53#include "pathnames.h" 54 55#ifdef DEBUG 56#undef _PATH_NOLOGIN 57#define _PATH_NOLOGIN "./nologin" 58#undef _PATH_FASTBOOT 59#define _PATH_FASTBOOT "./fastboot" 60#endif 61 62#define H *60*60 63#define M *60 64#define S *1 65#define NOLOG_TIME 5*60 66struct interval { 67 int timeleft, timetowait; 68} tlist[] = { 69 { 10 H, 5 H }, 70 { 5 H, 3 H }, 71 { 2 H, 1 H }, 72 { 1 H, 30 M }, 73 { 30 M, 10 M }, 74 { 20 M, 10 M }, 75 { 10 M, 5 M }, 76 { 5 M, 3 M }, 77 { 2 M, 1 M }, 78 { 1 M, 30 S }, 79 { 30 S, 30 S }, 80 { 0, 0 } 81}; 82#undef H 83#undef M 84#undef S 85 86static time_t offset, shuttime; 87static int dofast, dohalt, doreboot, dopower, dodump, mbuflen, nosync; 88static sig_atomic_t killflg, timed_out; 89static char *whom, mbuf[BUFSIZ]; 90 91void badtime(void); 92void __dead die_you_gravy_sucking_pig_dog(void); 93void doitfast(void); 94void __dead finish(int); 95void getoffset(char *); 96void __dead loop(void); 97void nolog(void); 98void timeout(int); 99void timewarn(int); 100void usage(void); 101 102int 103main(int argc, char *argv[]) 104{ 105 int arglen, ch, len, readstdin = 0; 106 struct passwd *pw; 107 char *p, *endp; 108 pid_t forkpid; 109 110 if (pledge("stdio rpath wpath cpath getpw tty id proc exec", NULL) == -1) 111 err(1, "pledge"); 112 113#ifndef DEBUG 114 if (geteuid()) 115 errx(1, "NOT super-user"); 116#endif 117 while ((ch = getopt(argc, argv, "dfhknpr-")) != -1) 118 switch (ch) { 119 case '-': 120 readstdin = 1; 121 break; 122 case 'd': 123 dodump = 1; 124 break; 125 case 'f': 126 dofast = 1; 127 break; 128 case 'h': 129 dohalt = 1; 130 break; 131 case 'k': 132 killflg = 1; 133 break; 134 case 'n': 135 nosync = 1; 136 break; 137 case 'p': 138 dopower = 1; 139 break; 140 case 'r': 141 doreboot = 1; 142 break; 143 default: 144 usage(); 145 } 146 argc -= optind; 147 argv += optind; 148 149 if (argc < 1) 150 usage(); 151 152 if (dofast && nosync) { 153 warnx("incompatible switches -f and -n."); 154 usage(); 155 } 156 if (doreboot && dohalt) { 157 warnx("incompatible switches -h and -r."); 158 usage(); 159 } 160 if (doreboot && dopower) { 161 warnx("incompatible switches -p and -r."); 162 usage(); 163 } 164 getoffset(*argv++); 165 166 if (*argv) { 167 for (p = mbuf, len = sizeof(mbuf); *argv; ++argv) { 168 arglen = strlen(*argv); 169 if ((len -= arglen) <= 2) 170 break; 171 if (p != mbuf) 172 *p++ = ' '; 173 memcpy(p, *argv, arglen); 174 p += arglen; 175 } 176 *p = '\n'; 177 *++p = '\0'; 178 } 179 180 if (readstdin) { 181 p = mbuf; 182 endp = mbuf + sizeof(mbuf) - 2; 183 for (;;) { 184 if (!fgets(p, endp - p + 1, stdin)) 185 break; 186 for (; *p && p < endp; ++p) 187 ; 188 if (p == endp) { 189 *p = '\n'; 190 *++p = '\0'; 191 break; 192 } 193 } 194 } 195 mbuflen = strlen(mbuf); 196 197 if (offset) { 198 char *ct = ctime(&shuttime); 199 200 if (ct) 201 printf("Shutdown at %.24s.\n", ct); 202 else 203 printf("Shutdown soon.\n"); 204 } else 205 (void)printf("Shutdown NOW!\n"); 206 207 if (!(whom = getlogin())) 208 whom = (pw = getpwuid(getuid())) ? pw->pw_name : "???"; 209 210#ifdef DEBUG 211 (void)putc('\n', stdout); 212#else 213 (void)setpriority(PRIO_PROCESS, 0, PRIO_MIN); 214 215 forkpid = fork(); 216 if (forkpid == -1) 217 err(1, "fork"); 218 if (forkpid) { 219 (void)printf("shutdown: [pid %ld]\n", (long)forkpid); 220 exit(0); 221 } 222 setsid(); 223#endif 224 openlog("shutdown", LOG_CONS, LOG_AUTH); 225 loop(); 226 /* NOTREACHED */ 227} 228 229void 230loop(void) 231{ 232 struct interval *tp; 233 u_int sltime; 234 int logged; 235 236 if (offset <= NOLOG_TIME) { 237 logged = 1; 238 nolog(); 239 } else 240 logged = 0; 241 tp = tlist; 242 if (tp->timeleft < offset) 243 (void)sleep((u_int)(offset - tp->timeleft)); 244 else { 245 while (offset < tp->timeleft) 246 ++tp; 247 /* 248 * Warn now, if going to sleep more than a fifth of 249 * the next wait time. 250 */ 251 if ((sltime = offset - tp->timeleft)) { 252 if (sltime > tp->timetowait / 5) 253 timewarn(offset); 254 (void)sleep(sltime); 255 } 256 } 257 for (;; ++tp) { 258 timewarn(tp->timeleft); 259 if (!logged && tp->timeleft <= NOLOG_TIME) { 260 logged = 1; 261 nolog(); 262 } 263 (void)sleep((u_int)tp->timetowait); 264 if (!tp->timeleft) 265 break; 266 } 267 die_you_gravy_sucking_pig_dog(); 268} 269 270static char *restricted_environ[] = { 271 "PATH=" _PATH_STDPATH, 272 NULL 273}; 274 275void 276timewarn(int timeleft) 277{ 278 static char hostname[HOST_NAME_MAX+1]; 279 static int first; 280 int fd[2]; 281 pid_t pid, wpid; 282 283 if (!first++) 284 (void)gethostname(hostname, sizeof(hostname)); 285 286 if (pipe(fd) == -1) { 287 syslog(LOG_ERR, "pipe: %m"); 288 return; 289 } 290 switch (pid = fork()) { 291 case -1: 292 syslog(LOG_ERR, "fork: %m"); 293 close(fd[0]); 294 close(fd[1]); 295 return; 296 case 0: 297 if (dup2(fd[0], STDIN_FILENO) == -1) { 298 syslog(LOG_ERR, "dup2: %m"); 299 _exit(1); 300 } 301 if (fd[0] != STDIN_FILENO) 302 close(fd[0]); 303 close(fd[1]); 304 /* wall(1)'s undocumented '-n' flag suppresses its banner. */ 305 execle(_PATH_WALL, _PATH_WALL, "-n", (char *)NULL, 306 restricted_environ); 307 syslog(LOG_ERR, "%s: %m", _PATH_WALL); 308 _exit(1); 309 default: 310 close(fd[0]); 311 } 312 313 dprintf(fd[1], 314 "\007*** %sSystem shutdown message from %s@%s ***\007\n", 315 timeleft ? "": "FINAL ", whom, hostname); 316 317 if (timeleft > 10*60) { 318 struct tm *tm = localtime(&shuttime); 319 320 dprintf(fd[1], "System going down at %d:%02d\n\n", 321 tm->tm_hour, tm->tm_min); 322 } else if (timeleft > 59) 323 dprintf(fd[1], "System going down in %d minute%s\n\n", 324 timeleft / 60, (timeleft > 60) ? "s" : ""); 325 else if (timeleft) 326 dprintf(fd[1], "System going down in 30 seconds\n\n"); 327 else 328 dprintf(fd[1], "System going down IMMEDIATELY\n\n"); 329 330 if (mbuflen) 331 (void)write(fd[1], mbuf, mbuflen); 332 close(fd[1]); 333 334 /* 335 * If we wait longer than 30 seconds for wall(1) to exit we'll 336 * throw off our schedule. 337 */ 338 signal(SIGALRM, timeout); 339 siginterrupt(SIGALRM, 1); 340 alarm(30); 341 while ((wpid = wait(NULL)) != pid && !timed_out) 342 continue; 343 alarm(0); 344 signal(SIGALRM, SIG_DFL); 345 if (timed_out) { 346 syslog(LOG_NOTICE, 347 "wall[%ld] is taking too long to exit; continuing", 348 (long)pid); 349 timed_out = 0; 350 } 351} 352 353void 354timeout(int signo) 355{ 356 timed_out = 1; 357} 358 359void 360die_you_gravy_sucking_pig_dog(void) 361{ 362 363 syslog(LOG_NOTICE, "%s by %s: %s", 364 doreboot ? "reboot" : dopower ? "power-down" : dohalt ? "halt" : 365 "shutdown", whom, mbuf); 366 (void)sleep(2); 367 368 (void)printf("\r\nSystem shutdown time has arrived\007\007\r\n"); 369 if (killflg) { 370 (void)printf("\rbut you'll have to do it yourself\r\n"); 371 finish(0); 372 } 373 if (dofast) 374 doitfast(); 375 376 if (pledge("stdio rpath wpath cpath tty id proc exec", NULL) == -1) 377 err(1, "pledge"); 378 379#ifdef DEBUG 380 if (doreboot) 381 (void)printf("reboot"); 382 else if (dopower) 383 (void)printf("power-down"); 384 else if (dohalt) 385 (void)printf("halt"); 386 if (nosync) 387 (void)printf(" no sync"); 388 if (dofast) 389 (void)printf(" no fsck"); 390 if (dodump) 391 (void)printf(" with dump"); 392 (void)printf("\nkill -HUP 1\n"); 393#else 394 if (dohalt || dopower || doreboot) { 395 char *args[10]; 396 char **arg, *path; 397 398 if (pledge("stdio exec", NULL) == -1) 399 err(1, "pledge"); 400 401 arg = &args[0]; 402 if (doreboot) { 403 path = _PATH_REBOOT; 404 *arg++ = "reboot"; 405 } else { 406 path = _PATH_HALT; 407 *arg++ = "halt"; 408 } 409 *arg++ = "-l"; 410 if (dopower) 411 *arg++ = "-p"; 412 if (nosync) 413 *arg++ = "-n"; 414 if (dodump) 415 *arg++ = "-d"; 416 *arg++ = NULL; 417 execve(path, args, NULL); 418 syslog(LOG_ERR, "shutdown: can't exec %s: %m.", path); 419 warn("%s", path); 420 } 421 if (access(_PATH_RC, R_OK) != -1) { 422 pid_t pid; 423 struct termios t; 424 int fd; 425 426 switch ((pid = fork())) { 427 case -1: 428 break; 429 case 0: 430 if (revoke(_PATH_CONSOLE) == -1) 431 perror("revoke"); 432 if (setsid() == -1) 433 perror("setsid"); 434 fd = open(_PATH_CONSOLE, O_RDWR); 435 if (fd == -1) 436 perror("open"); 437 dup2(fd, 0); 438 dup2(fd, 1); 439 dup2(fd, 2); 440 if (fd > 2) 441 close(fd); 442 443 /* At a minimum... */ 444 tcgetattr(0, &t); 445 t.c_oflag |= (ONLCR | OPOST); 446 tcsetattr(0, TCSANOW, &t); 447 448 execl(_PATH_BSHELL, "sh", _PATH_RC, "shutdown", (char *)NULL); 449 _exit(1); 450 default: 451 waitpid(pid, NULL, 0); 452 } 453 } 454 (void)kill(1, SIGTERM); /* to single user */ 455#endif 456 finish(0); 457} 458 459#define ATOI2(p) (p[0] - '0') * 10 + (p[1] - '0'); p += 2; 460 461void 462getoffset(char *timearg) 463{ 464 struct tm *lt; 465 int this_year; 466 time_t now; 467 char *p; 468 469 if (!strcasecmp(timearg, "now")) { /* now */ 470 offset = 0; 471 return; 472 } 473 474 (void)time(&now); 475 if (*timearg == '+') { /* +minutes */ 476 const char *errstr; 477 478 offset = strtonum(++timearg, 0, INT_MAX, &errstr); 479 if (errstr) 480 badtime(); 481 offset *= 60; 482 shuttime = now + offset; 483 return; 484 } 485 486 /* handle hh:mm by getting rid of the colon */ 487 for (p = timearg; *p; ++p) { 488 if (!isascii((unsigned char)*p) || !isdigit((unsigned char)*p)) { 489 if (*p == ':' && strlen(p) == 3) { 490 p[0] = p[1]; 491 p[1] = p[2]; 492 p[2] = '\0'; 493 } else 494 badtime(); 495 } 496 } 497 498 unsetenv("TZ"); /* OUR timezone */ 499 lt = localtime(&now); /* current time val */ 500 501 switch (strlen(timearg)) { 502 case 10: 503 this_year = lt->tm_year; 504 lt->tm_year = ATOI2(timearg); 505 /* 506 * check if the specified year is in the next century. 507 * allow for one year of user error as many people will 508 * enter n - 1 at the start of year n. 509 */ 510 if (lt->tm_year < (this_year % 100) - 1) 511 lt->tm_year += 100; 512 /* adjust for the year 2000 and beyond */ 513 lt->tm_year += (this_year - (this_year % 100)); 514 /* FALLTHROUGH */ 515 case 8: 516 lt->tm_mon = ATOI2(timearg); 517 if (--lt->tm_mon < 0 || lt->tm_mon > 11) 518 badtime(); 519 /* FALLTHROUGH */ 520 case 6: 521 lt->tm_mday = ATOI2(timearg); 522 if (lt->tm_mday < 1 || lt->tm_mday > 31) 523 badtime(); 524 /* FALLTHROUGH */ 525 case 4: 526 lt->tm_hour = ATOI2(timearg); 527 if (lt->tm_hour < 0 || lt->tm_hour > 23) 528 badtime(); 529 lt->tm_min = ATOI2(timearg); 530 if (lt->tm_min < 0 || lt->tm_min > 59) 531 badtime(); 532 lt->tm_sec = 0; 533 if ((shuttime = mktime(lt)) == -1) 534 badtime(); 535 if ((offset = shuttime - now) < 0) 536 errx(1, "that time is already past."); 537 break; 538 default: 539 badtime(); 540 } 541} 542 543void 544doitfast(void) 545{ 546 int fastfd; 547 548 if ((fastfd = open(_PATH_FASTBOOT, O_WRONLY|O_CREAT|O_TRUNC, 549 0664)) >= 0) { 550 dprintf(fastfd, "fastboot file for fsck\n"); 551 close(fastfd); 552 } 553} 554 555void 556nolog(void) 557{ 558 int logfd; 559 struct tm *tm; 560 561 (void)unlink(_PATH_NOLOGIN); /* in case linked to another file */ 562 (void)signal(SIGINT, finish); 563 (void)signal(SIGHUP, finish); 564 (void)signal(SIGQUIT, finish); 565 (void)signal(SIGTERM, finish); 566 tm = localtime(&shuttime); 567 if (tm && (logfd = open(_PATH_NOLOGIN, O_WRONLY|O_CREAT|O_TRUNC, 568 0664)) >= 0) { 569 dprintf(logfd, 570 "\n\nNO LOGINS: System going down at %d:%02d\n\n", 571 tm->tm_hour, tm->tm_min); 572 close(logfd); 573 } 574} 575 576void 577finish(int signo) 578{ 579 if (!killflg) 580 (void)unlink(_PATH_NOLOGIN); 581 if (signo == 0) 582 exit(0); 583 else 584 _exit(0); 585} 586 587void 588badtime(void) 589{ 590 errx(1, "bad time format."); 591} 592 593void 594usage(void) 595{ 596 fprintf(stderr, 597 "usage: shutdown [-] [-dfhknpr] time [warning-message ...]\n"); 598 exit(1); 599} 600