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