shutdown.c revision 140797
1160996Ssam/*
2160996Ssam * Copyright (c) 1988, 1990, 1993
3160996Ssam *	The Regents of the University of California.  All rights reserved.
4160996Ssam *
5160996Ssam * Redistribution and use in source and binary forms, with or without
6160996Ssam * modification, are permitted provided that the following conditions
7160996Ssam * are met:
8160996Ssam * 1. Redistributions of source code must retain the above copyright
9160996Ssam *    notice, this list of conditions and the following disclaimer.
10160996Ssam * 2. Redistributions in binary form must reproduce the above copyright
11160996Ssam *    notice, this list of conditions and the following disclaimer in the
12160996Ssam *    documentation and/or other materials provided with the distribution.
13160996Ssam * 4. Neither the name of the University nor the names of its contributors
14160996Ssam *    may be used to endorse or promote products derived from this software
15160996Ssam *    without specific prior written permission.
16160996Ssam *
17160996Ssam * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18160996Ssam * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19160996Ssam * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20160996Ssam * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21160996Ssam * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22160996Ssam * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23160996Ssam * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24160996Ssam * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25160996Ssam * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26160996Ssam * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27160996Ssam * SUCH DAMAGE.
28160996Ssam */
29160996Ssam
30160996Ssam#if 0
31160996Ssam#ifndef lint
32160996Ssamstatic const char copyright[] =
33160996Ssam"@(#) Copyright (c) 1988, 1990, 1993\n\
34160996Ssam	The Regents of the University of California.  All rights reserved.\n";
35160996Ssam#endif /* not lint */
36160996Ssam
37160996Ssam#ifndef lint
38160996Ssamstatic char sccsid[] = "@(#)shutdown.c	8.4 (Berkeley) 4/28/95";
39160996Ssam#endif /* not lint */
40160996Ssam#endif
41160996Ssam#include <sys/cdefs.h>
42160996Ssam__FBSDID("$FreeBSD: head/sbin/shutdown/shutdown.c 140797 2005-01-25 08:40:51Z delphij $");
43160996Ssam
44160996Ssam#include <sys/param.h>
45160996Ssam#include <sys/time.h>
46160996Ssam#include <sys/resource.h>
47160996Ssam#include <sys/syslog.h>
48160996Ssam
49160996Ssam#include <ctype.h>
50160996Ssam#include <err.h>
51160996Ssam#include <fcntl.h>
52160996Ssam#include <paths.h>
53160996Ssam#include <pwd.h>
54160996Ssam#include <setjmp.h>
55160996Ssam#include <signal.h>
56160996Ssam#include <stdio.h>
57160996Ssam#include <stdlib.h>
58160996Ssam#include <string.h>
59160996Ssam#include <unistd.h>
60160996Ssam
61160996Ssam#ifdef DEBUG
62160996Ssam#undef _PATH_NOLOGIN
63160996Ssam#define	_PATH_NOLOGIN	"./nologin"
64160996Ssam#endif
65160996Ssam
66160996Ssam#define	H		*60*60
67160996Ssam#define	M		*60
68160996Ssam#define	S		*1
69160996Ssam#define	NOLOG_TIME	5*60
70160996Ssamstruct interval {
71160996Ssam	int timeleft, timetowait;
72160996Ssam} tlist[] = {
73160996Ssam	{ 10 H,  5 H },
74160996Ssam	{  5 H,  3 H },
75160996Ssam	{  2 H,  1 H },
76160996Ssam	{  1 H, 30 M },
77160996Ssam	{ 30 M, 10 M },
78160996Ssam	{ 20 M, 10 M },
79160996Ssam	{ 10 M,  5 M },
80160996Ssam	{  5 M,  3 M },
81160996Ssam	{  2 M,  1 M },
82160996Ssam	{  1 M, 30 S },
83160996Ssam	{ 30 S, 30 S },
84160996Ssam	{  0  ,  0   }
85160996Ssam};
86160996Ssam#undef H
87160996Ssam#undef M
88160996Ssam#undef S
89160996Ssam
90160996Ssamstatic time_t offset, shuttime;
91160996Ssamstatic int dohalt, dopower, doreboot, killflg, mbuflen, oflag;
92160996Ssamstatic char mbuf[BUFSIZ];
93160996Ssamstatic const char *nosync, *whom;
94160996Ssam
95160996Ssamvoid badtime(void);
96160996Ssamvoid die_you_gravy_sucking_pig_dog(void);
97160996Ssamvoid finish(int);
98160996Ssamvoid getoffset(char *);
99160996Ssamvoid loop(void);
100160996Ssamvoid nolog(void);
101160996Ssamvoid timeout(int);
102160996Ssamvoid timewarn(int);
103160996Ssamvoid usage(const char *);
104160996Ssam
105160996Ssamextern const char **environ;
106160996Ssam
107160996Ssamint
108160996Ssammain(int argc, char **argv)
109160996Ssam{
110160996Ssam	char *p, *endp;
111160996Ssam	struct passwd *pw;
112160996Ssam	int arglen, ch, len, readstdin;
113160996Ssam
114160996Ssam#ifndef DEBUG
115160996Ssam	if (geteuid())
116160996Ssam		errx(1, "NOT super-user");
117160996Ssam#endif
118160996Ssam	nosync = NULL;
119160996Ssam	readstdin = 0;
120160996Ssam	while ((ch = getopt(argc, argv, "-hknopr")) != -1)
121160996Ssam		switch (ch) {
122160996Ssam		case '-':
123160996Ssam			readstdin = 1;
124160996Ssam			break;
125160996Ssam		case 'h':
126160996Ssam			dohalt = 1;
127160996Ssam			break;
128160996Ssam		case 'k':
129160996Ssam			killflg = 1;
130160996Ssam			break;
131160996Ssam		case 'n':
132160996Ssam			nosync = "-n";
133160996Ssam			break;
134160996Ssam		case 'o':
135160996Ssam			oflag = 1;
136160996Ssam			break;
137160996Ssam		case 'p':
138160996Ssam			dopower = 1;
139160996Ssam			break;
140160996Ssam		case 'r':
141160996Ssam			doreboot = 1;
142160996Ssam			break;
143160996Ssam		case '?':
144160996Ssam		default:
145160996Ssam			usage((char *)NULL);
146160996Ssam		}
147160996Ssam	argc -= optind;
148160996Ssam	argv += optind;
149160996Ssam
150160996Ssam	if (argc < 1)
151160996Ssam		usage((char *)NULL);
152160996Ssam
153160996Ssam	if (killflg + doreboot + dohalt + dopower > 1)
154160996Ssam		usage("incompatible switches -h, -k, -p and -r");
155160996Ssam
156160996Ssam	if (oflag && !(dohalt || dopower || doreboot))
157160996Ssam		usage("-o requires -h, -p or -r");
158160996Ssam
159160996Ssam	if (nosync != NULL && !oflag)
160160996Ssam		usage("-n requires -o");
161160996Ssam
162160996Ssam	getoffset(*argv++);
163160996Ssam
164160996Ssam	if (*argv) {
165160996Ssam		for (p = mbuf, len = sizeof(mbuf); *argv; ++argv) {
166160996Ssam			arglen = strlen(*argv);
167160996Ssam			if ((len -= arglen) <= 2)
168160996Ssam				break;
169160996Ssam			if (p != mbuf)
170160996Ssam				*p++ = ' ';
171160996Ssam			memmove(p, *argv, arglen);
172160996Ssam			p += arglen;
173160996Ssam		}
174160996Ssam		*p = '\n';
175160996Ssam		*++p = '\0';
176160996Ssam	}
177160996Ssam
178160996Ssam	if (readstdin) {
179160996Ssam		p = mbuf;
180160996Ssam		endp = mbuf + sizeof(mbuf) - 2;
181160996Ssam		for (;;) {
182160996Ssam			if (!fgets(p, endp - p + 1, stdin))
183160996Ssam				break;
184160996Ssam			for (; *p &&  p < endp; ++p);
185160996Ssam			if (p == endp) {
186160996Ssam				*p = '\n';
187160996Ssam				*++p = '\0';
188160996Ssam				break;
189160996Ssam			}
190160996Ssam		}
191160996Ssam	}
192160996Ssam	mbuflen = strlen(mbuf);
193160996Ssam
194160996Ssam	if (offset)
195160996Ssam		(void)printf("Shutdown at %.24s.\n", ctime(&shuttime));
196160996Ssam	else
197160996Ssam		(void)printf("Shutdown NOW!\n");
198160996Ssam
199160996Ssam	if (!(whom = getlogin()))
200160996Ssam		whom = (pw = getpwuid(getuid())) ? pw->pw_name : "???";
201160996Ssam
202160996Ssam#ifdef DEBUG
203160996Ssam	(void)putc('\n', stdout);
204160996Ssam#else
205160996Ssam	(void)setpriority(PRIO_PROCESS, 0, PRIO_MIN);
206160996Ssam	{
207160996Ssam		int forkpid;
208160996Ssam
209160996Ssam		forkpid = fork();
210160996Ssam		if (forkpid == -1)
211160996Ssam			err(1, "fork");
212160996Ssam		if (forkpid)
213160996Ssam			errx(0, "[pid %d]", forkpid);
214160996Ssam	}
215160996Ssam	setsid();
216160996Ssam#endif
217160996Ssam	openlog("shutdown", LOG_CONS, LOG_AUTH);
218160996Ssam	loop();
219160996Ssam	return(0);
220160996Ssam}
221160996Ssam
222160996Ssamvoid
223160996Ssamloop()
224160996Ssam{
225160996Ssam	struct interval *tp;
226160996Ssam	u_int sltime;
227160996Ssam	int logged;
228160996Ssam
229160996Ssam	if (offset <= NOLOG_TIME) {
230160996Ssam		logged = 1;
231160996Ssam		nolog();
232160996Ssam	}
233160996Ssam	else
234160996Ssam		logged = 0;
235160996Ssam	tp = tlist;
236160996Ssam	if (tp->timeleft < offset)
237160996Ssam		(void)sleep((u_int)(offset - tp->timeleft));
238160996Ssam	else {
239160996Ssam		while (tp->timeleft && offset < tp->timeleft)
240160996Ssam			++tp;
241160996Ssam		/*
242160996Ssam		 * Warn now, if going to sleep more than a fifth of
243160996Ssam		 * the next wait time.
244160996Ssam		 */
245160996Ssam		if ((sltime = offset - tp->timeleft)) {
246160996Ssam			if (sltime > (u_int)(tp->timetowait / 5))
247160996Ssam				timewarn(offset);
248160996Ssam			(void)sleep(sltime);
249160996Ssam		}
250160996Ssam	}
251160996Ssam	for (;; ++tp) {
252160996Ssam		timewarn(tp->timeleft);
253160996Ssam		if (!logged && tp->timeleft <= NOLOG_TIME) {
254160996Ssam			logged = 1;
255160996Ssam			nolog();
256160996Ssam		}
257160996Ssam		(void)sleep((u_int)tp->timetowait);
258160996Ssam		if (!tp->timeleft)
259160996Ssam			break;
260160996Ssam	}
261160996Ssam	die_you_gravy_sucking_pig_dog();
262160996Ssam}
263160996Ssam
264160996Ssamstatic jmp_buf alarmbuf;
265160996Ssam
266160996Ssamstatic const char *restricted_environ[] = {
267160996Ssam	"PATH=" _PATH_STDPATH,
268160996Ssam	NULL
269160996Ssam};
270160996Ssam
271160996Ssamvoid
272160996Ssamtimewarn(int timeleft)
273160996Ssam{
274160996Ssam	static int first;
275160996Ssam	static char hostname[MAXHOSTNAMELEN + 1];
276160996Ssam	FILE *pf;
277160996Ssam	char wcmd[MAXPATHLEN + 4];
278160996Ssam
279160996Ssam	if (!first++)
280160996Ssam		(void)gethostname(hostname, sizeof(hostname));
281160996Ssam
282160996Ssam	/* undoc -n option to wall suppresses normal wall banner */
283160996Ssam	(void)snprintf(wcmd, sizeof(wcmd), "%s -n", _PATH_WALL);
284160996Ssam	environ = restricted_environ;
285160996Ssam	if (!(pf = popen(wcmd, "w"))) {
286160996Ssam		syslog(LOG_ERR, "shutdown: can't find %s: %m", _PATH_WALL);
287160996Ssam		return;
288160996Ssam	}
289160996Ssam
290160996Ssam	(void)fprintf(pf,
291160996Ssam	    "\007*** %sSystem shutdown message from %s@%s ***\007\n",
292160996Ssam	    timeleft ? "": "FINAL ", whom, hostname);
293160996Ssam
294160996Ssam	if (timeleft > 10*60)
295160996Ssam		(void)fprintf(pf, "System going down at %5.5s\n\n",
296160996Ssam		    ctime(&shuttime) + 11);
297160996Ssam	else if (timeleft > 59)
298160996Ssam		(void)fprintf(pf, "System going down in %d minute%s\n\n",
299160996Ssam		    timeleft / 60, (timeleft > 60) ? "s" : "");
300160996Ssam	else if (timeleft)
301160996Ssam		(void)fprintf(pf, "System going down in 30 seconds\n\n");
302160996Ssam	else
303160996Ssam		(void)fprintf(pf, "System going down IMMEDIATELY\n\n");
304160996Ssam
305160996Ssam	if (mbuflen)
306160996Ssam		(void)fwrite(mbuf, sizeof(*mbuf), mbuflen, pf);
307160996Ssam
308160996Ssam	/*
309160996Ssam	 * play some games, just in case wall doesn't come back
310160996Ssam	 * probably unnecessary, given that wall is careful.
311160996Ssam	 */
312160996Ssam	if (!setjmp(alarmbuf)) {
313160996Ssam		(void)signal(SIGALRM, timeout);
314160996Ssam		(void)alarm((u_int)30);
315160996Ssam		(void)pclose(pf);
316160996Ssam		(void)alarm((u_int)0);
317160996Ssam		(void)signal(SIGALRM, SIG_DFL);
318160996Ssam	}
319160996Ssam}
320160996Ssam
321160996Ssamvoid
322160996Ssamtimeout(int signo __unused)
323160996Ssam{
324160996Ssam	longjmp(alarmbuf, 1);
325160996Ssam}
326160996Ssam
327160996Ssamvoid
328160996Ssamdie_you_gravy_sucking_pig_dog()
329160996Ssam{
330160996Ssam	char *empty_environ[] = { NULL };
331160996Ssam
332160996Ssam	syslog(LOG_NOTICE, "%s by %s: %s",
333160996Ssam	    doreboot ? "reboot" : dohalt ? "halt" : dopower ? "power-down" :
334160996Ssam	    "shutdown", whom, mbuf);
335160996Ssam	(void)sleep(2);
336160996Ssam
337160996Ssam	(void)printf("\r\nSystem shutdown time has arrived\007\007\r\n");
338160996Ssam	if (killflg) {
339160996Ssam		(void)printf("\rbut you'll have to do it yourself\r\n");
340160996Ssam		exit(0);
341160996Ssam	}
342160996Ssam#ifdef DEBUG
343160996Ssam	if (doreboot)
344160996Ssam		(void)printf("reboot");
345160996Ssam	else if (dohalt)
346160996Ssam		(void)printf("halt");
347160996Ssam	else if (dopower)
348160996Ssam		(void)printf("power-down");
349160996Ssam	if (nosync != NULL)
350160996Ssam		(void)printf(" no sync");
351160996Ssam	(void)printf("\nkill -HUP 1\n");
352160996Ssam#else
353160996Ssam	if (!oflag) {
354160996Ssam		(void)kill(1, doreboot ? SIGINT :	/* reboot */
355160996Ssam			      dohalt ? SIGUSR1 :	/* halt */
356160996Ssam			      dopower ? SIGUSR2 :	/* power-down */
357160996Ssam			      SIGTERM);			/* single-user */
358160996Ssam	} else {
359160996Ssam		if (doreboot) {
360160996Ssam			execle(_PATH_REBOOT, "reboot", "-l", nosync,
361160996Ssam				(char *)NULL, empty_environ);
362160996Ssam			syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
363160996Ssam				_PATH_REBOOT);
364160996Ssam			warn(_PATH_REBOOT);
365160996Ssam		}
366160996Ssam		else if (dohalt) {
367160996Ssam			execle(_PATH_HALT, "halt", "-l", nosync,
368160996Ssam				(char *)NULL, empty_environ);
369160996Ssam			syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
370160996Ssam				_PATH_HALT);
371160996Ssam			warn(_PATH_HALT);
372160996Ssam		}
373160996Ssam		else if (dopower) {
374160996Ssam			execle(_PATH_HALT, "halt", "-l", "-p", nosync,
375160996Ssam				(char *)NULL, empty_environ);
376160996Ssam			syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
377160996Ssam				_PATH_HALT);
378160996Ssam			warn(_PATH_HALT);
379160996Ssam		}
380160996Ssam		(void)kill(1, SIGTERM);		/* to single-user */
381160996Ssam	}
382160996Ssam#endif
383160996Ssam	finish(0);
384160996Ssam}
385160996Ssam
386160996Ssam#define	ATOI2(p)	(p[0] - '0') * 10 + (p[1] - '0'); p += 2;
387160996Ssam
388160996Ssamvoid
389160996Ssamgetoffset(char *timearg)
390160996Ssam{
391160996Ssam	struct tm *lt;
392160996Ssam	char *p;
393160996Ssam	time_t now;
394160996Ssam	int this_year;
395160996Ssam
396160996Ssam	(void)time(&now);
397160996Ssam
398160996Ssam	if (!strcasecmp(timearg, "now")) {		/* now */
399160996Ssam		offset = 0;
400160996Ssam		shuttime = now;
401160996Ssam		return;
402160996Ssam	}
403160996Ssam
404160996Ssam	if (*timearg == '+') {				/* +minutes */
405160996Ssam		if (!isdigit(*++timearg))
406160996Ssam			badtime();
407160996Ssam		if ((offset = atoi(timearg) * 60) < 0)
408160996Ssam			badtime();
409160996Ssam		shuttime = now + offset;
410160996Ssam		return;
411160996Ssam	}
412160996Ssam
413160996Ssam	/* handle hh:mm by getting rid of the colon */
414160996Ssam	for (p = timearg; *p; ++p)
415160996Ssam		if (!isascii(*p) || !isdigit(*p)) {
416160996Ssam			if (*p == ':' && strlen(p) == 3) {
417160996Ssam				p[0] = p[1];
418160996Ssam				p[1] = p[2];
419160996Ssam				p[2] = '\0';
420160996Ssam			}
421160996Ssam			else
422160996Ssam				badtime();
423160996Ssam		}
424195848Ssam
425160996Ssam	unsetenv("TZ");					/* OUR timezone */
426160996Ssam	lt = localtime(&now);				/* current time val */
427160996Ssam
428160996Ssam	switch(strlen(timearg)) {
429160996Ssam	case 10:
430160996Ssam		this_year = lt->tm_year;
431160996Ssam		lt->tm_year = ATOI2(timearg);
432160996Ssam		/*
433160996Ssam		 * check if the specified year is in the next century.
434160996Ssam		 * allow for one year of user error as many people will
435160996Ssam		 * enter n - 1 at the start of year n.
436160996Ssam		 */
437160996Ssam		if (lt->tm_year < (this_year % 100) - 1)
438160996Ssam			lt->tm_year += 100;
439160996Ssam		/* adjust for the year 2000 and beyond */
440160996Ssam		lt->tm_year += (this_year - (this_year % 100));
441160996Ssam		/* FALLTHROUGH */
442160996Ssam	case 8:
443160996Ssam		lt->tm_mon = ATOI2(timearg);
444160996Ssam		if (--lt->tm_mon < 0 || lt->tm_mon > 11)
445160996Ssam			badtime();
446160996Ssam		/* FALLTHROUGH */
447160996Ssam	case 6:
448160996Ssam		lt->tm_mday = ATOI2(timearg);
449160996Ssam		if (lt->tm_mday < 1 || lt->tm_mday > 31)
450160996Ssam			badtime();
451160996Ssam		/* FALLTHROUGH */
452160996Ssam	case 4:
453160996Ssam		lt->tm_hour = ATOI2(timearg);
454160996Ssam		if (lt->tm_hour < 0 || lt->tm_hour > 23)
455160996Ssam			badtime();
456160996Ssam		lt->tm_min = ATOI2(timearg);
457160996Ssam		if (lt->tm_min < 0 || lt->tm_min > 59)
458160996Ssam			badtime();
459160996Ssam		lt->tm_sec = 0;
460160996Ssam		if ((shuttime = mktime(lt)) == -1)
461160996Ssam			badtime();
462160996Ssam		if ((offset = shuttime - now) < 0)
463160996Ssam			errx(1, "that time is already past.");
464160996Ssam		break;
465160996Ssam	default:
466160996Ssam		badtime();
467160996Ssam	}
468160996Ssam}
469
470#define	NOMSG	"\n\nNO LOGINS: System going down at "
471void
472nolog()
473{
474	int logfd;
475	char *ct;
476
477	(void)unlink(_PATH_NOLOGIN);	/* in case linked to another file */
478	(void)signal(SIGINT, finish);
479	(void)signal(SIGHUP, finish);
480	(void)signal(SIGQUIT, finish);
481	(void)signal(SIGTERM, finish);
482	if ((logfd = open(_PATH_NOLOGIN, O_WRONLY|O_CREAT|O_TRUNC,
483	    0664)) >= 0) {
484		(void)write(logfd, NOMSG, sizeof(NOMSG) - 1);
485		ct = ctime(&shuttime);
486		(void)write(logfd, ct + 11, 5);
487		(void)write(logfd, "\n\n", 2);
488		(void)write(logfd, mbuf, strlen(mbuf));
489		(void)close(logfd);
490	}
491}
492
493void
494finish(int signo __unused)
495{
496	if (!killflg)
497		(void)unlink(_PATH_NOLOGIN);
498	exit(0);
499}
500
501void
502badtime()
503{
504	errx(1, "bad time format");
505}
506
507void
508usage(const char *cp)
509{
510	if (cp != NULL)
511		warnx("%s", cp);
512	(void)fprintf(stderr,
513	    "usage: shutdown [-] [-h | -p | -r | -k] [-o [-n]]"
514	    " time [warning-message ...]\n");
515	exit(1);
516}
517