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