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