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