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