shutdown.c revision 79749
140460Sken/*
257207Sgibbs * Copyright (c) 1988, 1990, 1993
340460Sken *	The Regents of the University of California.  All rights reserved.
440460Sken *
540460Sken * Redistribution and use in source and binary forms, with or without
640460Sken * modification, are permitted provided that the following conditions
740460Sken * are met:
840460Sken * 1. Redistributions of source code must retain the above copyright
940460Sken *    notice, this list of conditions and the following disclaimer.
1040460Sken * 2. Redistributions in binary form must reproduce the above copyright
1182229Sdd *    notice, this list of conditions and the following disclaimer in the
1240460Sken *    documentation and/or other materials provided with the distribution.
1340460Sken * 3. All advertising materials mentioning features or use of this software
1440460Sken *    must display the following acknowledgement:
1540460Sken *	This product includes software developed by the University of
1640460Sken *	California, Berkeley and its contributors.
1740460Sken * 4. Neither the name of the University nor the names of its contributors
1840460Sken *    may be used to endorse or promote products derived from this software
1940460Sken *    without specific prior written permission.
2040460Sken *
2140460Sken * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2240460Sken * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2340460Sken * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2450476Speter * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2540460Sken * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26132165Ssimon * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2772762Sru * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2879538Sru * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2940460Sken * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3040460Sken * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3140460Sken * SUCH DAMAGE.
3240460Sken */
33159719Sbrueffer
34159719Sbrueffer#ifndef lint
35159719Sbruefferstatic const char copyright[] =
36159719Sbrueffer"@(#) Copyright (c) 1988, 1990, 1993\n\
37159719Sbrueffer	The Regents of the University of California.  All rights reserved.\n";
38159719Sbrueffer#endif /* not lint */
39159719Sbrueffer
40162404Sru#ifndef lint
4140460Sken#if 0
4240460Skenstatic char sccsid[] = "@(#)shutdown.c	8.4 (Berkeley) 4/28/95";
4340460Sken#endif
4440460Skenstatic const char rcsid[] =
4571895Sru  "$FreeBSD: head/sbin/shutdown/shutdown.c 79749 2001-07-15 05:46:07Z dd $";
4657207Sgibbs#endif /* not lint */
4771895Sru
4857207Sgibbs#include <sys/param.h>
4957207Sgibbs#include <sys/time.h>
5081251Sru#include <sys/resource.h>
5182327Sdd#include <sys/syslog.h>
5240460Sken
5340460Sken#include <ctype.h>
5479727Sschweikh#include <err.h>
5540460Sken#include <fcntl.h>
5640460Sken#include <pwd.h>
5740460Sken#include <setjmp.h>
5840460Sken#include <signal.h>
5940460Sken#include <stdio.h>
6082327Sdd#include <stdlib.h>
6140460Sken#include <string.h>
6240460Sken#include <unistd.h>
6340460Sken
6440460Sken#include "pathnames.h"
65132165Ssimon
66132165Ssimon#ifdef DEBUG
67132165Ssimon#undef _PATH_NOLOGIN
68132165Ssimon#define	_PATH_NOLOGIN	"./nologin"
69132165Ssimon#endif
70132165Ssimon
71132165Ssimon#define	H		*60*60
72132165Ssimon#define	M		*60
73132165Ssimon#define	S		*1
74132165Ssimon#define	NOLOG_TIME	5*60
75132165Ssimonstruct interval {
76132165Ssimon	int timeleft, timetowait;
77132165Ssimon} tlist[] = {
78132165Ssimon	{ 10 H,  5 H },
79132165Ssimon	{  5 H,  3 H },
80132165Ssimon	{  2 H,  1 H },
81132165Ssimon	{  1 H, 30 M },
8240460Sken	{ 30 M, 10 M },
8340460Sken	{ 20 M, 10 M },
8440460Sken	{ 10 M,  5 M },
8540460Sken	{  5 M,  3 M },
8649831Smpp	{  2 M,  1 M },
8749831Smpp	{  1 M, 30 S },
88140561Sru	{ 30 S, 30 S },
89140561Sru	{  0  ,  0   }
90140561Sru};
91140561Sru#undef H
92140561Sru#undef M
9340460Sken#undef S
9469027Sru
9540460Skenstatic time_t offset, shuttime;
9640460Skenstatic int dohalt, dopower, doreboot, killflg, mbuflen, oflag;
9740460Skenstatic char mbuf[BUFSIZ];
9840460Skenstatic const char *nosync, *whom;
9940460Sken
10040460Skenvoid badtime __P((void));
10157207Sgibbsvoid die_you_gravy_sucking_pig_dog __P((void));
10257207Sgibbsvoid finish __P((int));
10368716Sruvoid getoffset __P((char *));
10468716Sruvoid loop __P((void));
105void nolog __P((void));
106void timeout __P((int));
107void timewarn __P((int));
108void usage __P((const char *));
109
110int
111main(argc, argv)
112	int argc;
113	char *argv[];
114{
115	char *p, *endp;
116	struct passwd *pw;
117	int arglen, ch, len, readstdin;
118
119#ifndef DEBUG
120	if (geteuid())
121		errx(1, "NOT super-user");
122#endif
123	nosync = NULL;
124	readstdin = 0;
125	while ((ch = getopt(argc, argv, "-hknopr")) != -1)
126		switch (ch) {
127		case '-':
128			readstdin = 1;
129			break;
130		case 'h':
131			dohalt = 1;
132			break;
133		case 'k':
134			killflg = 1;
135			break;
136		case 'n':
137			nosync = "-n";
138			break;
139		case 'o':
140			oflag = 1;
141			break;
142		case 'p':
143			dopower = 1;
144			break;
145		case 'r':
146			doreboot = 1;
147			break;
148		case '?':
149		default:
150			usage((char *)NULL);
151		}
152	argc -= optind;
153	argv += optind;
154
155	if (argc < 1)
156		usage((char *)NULL);
157
158	if (killflg + doreboot + dohalt + dopower > 1)
159		usage("incompatible switches -h, -k, -p and -r");
160
161	if (oflag && !(dohalt || dopower || doreboot))
162		usage("-o requires -h, -p or -r");
163
164	if (nosync != NULL && !oflag)
165		usage("-n requires -o");
166
167	getoffset(*argv++);
168
169	if (*argv) {
170		for (p = mbuf, len = sizeof(mbuf); *argv; ++argv) {
171			arglen = strlen(*argv);
172			if ((len -= arglen) <= 2)
173				break;
174			if (p != mbuf)
175				*p++ = ' ';
176			memmove(p, *argv, arglen);
177			p += arglen;
178		}
179		*p = '\n';
180		*++p = '\0';
181	}
182
183	if (readstdin) {
184		p = mbuf;
185		endp = mbuf + sizeof(mbuf) - 2;
186		for (;;) {
187			if (!fgets(p, endp - p + 1, stdin))
188				break;
189			for (; *p &&  p < endp; ++p);
190			if (p == endp) {
191				*p = '\n';
192				*++p = '\0';
193				break;
194			}
195		}
196	}
197	mbuflen = strlen(mbuf);
198
199	if (offset)
200		(void)printf("Shutdown at %.24s.\n", ctime(&shuttime));
201	else
202		(void)printf("Shutdown NOW!\n");
203
204	if (!(whom = getlogin()))
205		whom = (pw = getpwuid(getuid())) ? pw->pw_name : "???";
206
207#ifdef DEBUG
208	(void)putc('\n', stdout);
209#else
210	(void)setpriority(PRIO_PROCESS, 0, PRIO_MIN);
211	{
212		int forkpid;
213
214		forkpid = fork();
215		if (forkpid == -1)
216			err(1, "fork");
217		if (forkpid)
218			errx(0, "[pid %d]", forkpid);
219	}
220	setsid();
221#endif
222	openlog("shutdown", LOG_CONS, LOG_AUTH);
223	loop();
224	return(0);
225}
226
227void
228loop()
229{
230	struct interval *tp;
231	u_int sltime;
232	int logged;
233
234	if (offset <= NOLOG_TIME) {
235		logged = 1;
236		nolog();
237	}
238	else
239		logged = 0;
240	tp = tlist;
241	if (tp->timeleft < offset)
242		(void)sleep((u_int)(offset - tp->timeleft));
243	else {
244		while (tp->timeleft && offset < tp->timeleft)
245			++tp;
246		/*
247		 * Warn now, if going to sleep more than a fifth of
248		 * the next wait time.
249		 */
250		if ((sltime = offset - tp->timeleft)) {
251			if (sltime > (u_int)(tp->timetowait / 5))
252				timewarn(offset);
253			(void)sleep(sltime);
254		}
255	}
256	for (;; ++tp) {
257		timewarn(tp->timeleft);
258		if (!logged && tp->timeleft <= NOLOG_TIME) {
259			logged = 1;
260			nolog();
261		}
262		(void)sleep((u_int)tp->timetowait);
263		if (!tp->timeleft)
264			break;
265	}
266	die_you_gravy_sucking_pig_dog();
267}
268
269static jmp_buf alarmbuf;
270
271static const char *restricted_environ[] = {
272	"PATH=" _PATH_STDPATH,
273	NULL
274};
275
276void
277timewarn(timeleft)
278	int timeleft;
279{
280	static int first;
281	static char hostname[MAXHOSTNAMELEN + 1];
282	FILE *pf;
283	char wcmd[MAXPATHLEN + 4];
284	extern const char **environ;
285
286	if (!first++)
287		(void)gethostname(hostname, sizeof(hostname));
288
289	/* undoc -n option to wall suppresses normal wall banner */
290	(void)snprintf(wcmd, sizeof(wcmd), "%s -n", _PATH_WALL);
291	environ = restricted_environ;
292	if (!(pf = popen(wcmd, "w"))) {
293		syslog(LOG_ERR, "shutdown: can't find %s: %m", _PATH_WALL);
294		return;
295	}
296
297	(void)fprintf(pf,
298	    "\007*** %sSystem shutdown message from %s@%s ***\007\n",
299	    timeleft ? "": "FINAL ", whom, hostname);
300
301	if (timeleft > 10*60)
302		(void)fprintf(pf, "System going down at %5.5s\n\n",
303		    ctime(&shuttime) + 11);
304	else if (timeleft > 59)
305		(void)fprintf(pf, "System going down in %d minute%s\n\n",
306		    timeleft / 60, (timeleft > 60) ? "s" : "");
307	else if (timeleft)
308		(void)fprintf(pf, "System going down in 30 seconds\n\n");
309	else
310		(void)fprintf(pf, "System going down IMMEDIATELY\n\n");
311
312	if (mbuflen)
313		(void)fwrite(mbuf, sizeof(*mbuf), mbuflen, pf);
314
315	/*
316	 * play some games, just in case wall doesn't come back
317	 * probably unnecessary, given that wall is careful.
318	 */
319	if (!setjmp(alarmbuf)) {
320		(void)signal(SIGALRM, timeout);
321		(void)alarm((u_int)30);
322		(void)pclose(pf);
323		(void)alarm((u_int)0);
324		(void)signal(SIGALRM, SIG_DFL);
325	}
326}
327
328void
329timeout(signo)
330	int signo __unused;
331{
332	longjmp(alarmbuf, 1);
333}
334
335void
336die_you_gravy_sucking_pig_dog()
337{
338	char *empty_environ[] = { NULL };
339
340	syslog(LOG_NOTICE, "%s by %s: %s",
341	    doreboot ? "reboot" : dohalt ? "halt" : dopower ? "power-down" :
342	    "shutdown", whom, mbuf);
343	(void)sleep(2);
344
345	(void)printf("\r\nSystem shutdown time has arrived\007\007\r\n");
346	if (killflg) {
347		(void)printf("\rbut you'll have to do it yourself\r\n");
348		exit(0);
349	}
350#ifdef DEBUG
351	if (doreboot)
352		(void)printf("reboot");
353	else if (dohalt)
354		(void)printf("halt");
355	else if (dopower)
356		(void)printf("power-down");
357	if (nosync != NULL)
358		(void)printf(" no sync");
359	(void)printf("\nkill -HUP 1\n");
360#else
361	if (!oflag) {
362		(void)kill(1, doreboot ? SIGINT :	/* reboot */
363			      dohalt ? SIGUSR1 :	/* halt */
364			      dopower ? SIGUSR2 :	/* power-down */
365			      SIGTERM);			/* single-user */
366	} else {
367		if (doreboot) {
368			execle(_PATH_REBOOT, "reboot", "-l", nosync,
369				(char *)NULL, empty_environ);
370			syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
371				_PATH_REBOOT);
372			warn(_PATH_REBOOT);
373		}
374		else if (dohalt) {
375			execle(_PATH_HALT, "halt", "-l", nosync,
376				(char *)NULL, empty_environ);
377			syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
378				_PATH_HALT);
379			warn(_PATH_HALT);
380		}
381		else if (dopower) {
382			execle(_PATH_HALT, "halt", "-l", "-p", nosync,
383				(char *)NULL, empty_environ);
384			syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
385				_PATH_HALT);
386			warn(_PATH_HALT);
387		}
388		(void)kill(1, SIGTERM);		/* to single-user */
389	}
390#endif
391	finish(0);
392}
393
394#define	ATOI2(p)	(p[0] - '0') * 10 + (p[1] - '0'); p += 2;
395
396void
397getoffset(timearg)
398	char *timearg;
399{
400	struct tm *lt;
401	char *p;
402	time_t now;
403	int this_year;
404
405	(void)time(&now);
406
407	if (!strcasecmp(timearg, "now")) {		/* now */
408		offset = 0;
409		shuttime = now;
410		return;
411	}
412
413	if (*timearg == '+') {				/* +minutes */
414		if (!isdigit(*++timearg))
415			badtime();
416		if ((offset = atoi(timearg) * 60) < 0)
417			badtime();
418		shuttime = now + offset;
419		return;
420	}
421
422	/* handle hh:mm by getting rid of the colon */
423	for (p = timearg; *p; ++p)
424		if (!isascii(*p) || !isdigit(*p)) {
425			if (*p == ':' && strlen(p) == 3) {
426				p[0] = p[1];
427				p[1] = p[2];
428				p[2] = '\0';
429			}
430			else
431				badtime();
432		}
433
434	unsetenv("TZ");					/* OUR timezone */
435	lt = localtime(&now);				/* current time val */
436
437	switch(strlen(timearg)) {
438	case 10:
439		this_year = lt->tm_year;
440		lt->tm_year = ATOI2(timearg);
441		/*
442		 * check if the specified year is in the next century.
443		 * allow for one year of user error as many people will
444		 * enter n - 1 at the start of year n.
445		 */
446		if (lt->tm_year < (this_year % 100) - 1)
447			lt->tm_year += 100;
448		/* adjust for the year 2000 and beyond */
449		lt->tm_year += (this_year - (this_year % 100));
450		/* FALLTHROUGH */
451	case 8:
452		lt->tm_mon = ATOI2(timearg);
453		if (--lt->tm_mon < 0 || lt->tm_mon > 11)
454			badtime();
455		/* FALLTHROUGH */
456	case 6:
457		lt->tm_mday = ATOI2(timearg);
458		if (lt->tm_mday < 1 || lt->tm_mday > 31)
459			badtime();
460		/* FALLTHROUGH */
461	case 4:
462		lt->tm_hour = ATOI2(timearg);
463		if (lt->tm_hour < 0 || lt->tm_hour > 23)
464			badtime();
465		lt->tm_min = ATOI2(timearg);
466		if (lt->tm_min < 0 || lt->tm_min > 59)
467			badtime();
468		lt->tm_sec = 0;
469		if ((shuttime = mktime(lt)) == -1)
470			badtime();
471		if ((offset = shuttime - now) < 0)
472			errx(1, "that time is already past.");
473		break;
474	default:
475		badtime();
476	}
477}
478
479#define	NOMSG	"\n\nNO LOGINS: System going down at "
480void
481nolog()
482{
483	int logfd;
484	char *ct;
485
486	(void)unlink(_PATH_NOLOGIN);	/* in case linked to another file */
487	(void)signal(SIGINT, finish);
488	(void)signal(SIGHUP, finish);
489	(void)signal(SIGQUIT, finish);
490	(void)signal(SIGTERM, finish);
491	if ((logfd = open(_PATH_NOLOGIN, O_WRONLY|O_CREAT|O_TRUNC,
492	    0664)) >= 0) {
493		(void)write(logfd, NOMSG, sizeof(NOMSG) - 1);
494		ct = ctime(&shuttime);
495		(void)write(logfd, ct + 11, 5);
496		(void)write(logfd, "\n\n", 2);
497		(void)write(logfd, mbuf, strlen(mbuf));
498		(void)close(logfd);
499	}
500}
501
502void
503finish(signo)
504	int signo __unused;
505{
506	if (!killflg)
507		(void)unlink(_PATH_NOLOGIN);
508	exit(0);
509}
510
511void
512badtime()
513{
514	errx(1, "bad time format");
515}
516
517void
518usage(cp)
519	const char *cp;
520{
521	if (cp != NULL)
522		warnx("%s", cp);
523	(void)fprintf(stderr,
524	    "usage: shutdown [-] [-h | -p | -r | -k] [-o [-n]]"
525	    " time [warning-message ...]\n");
526	exit(1);
527}
528