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