shutdown.c revision 1.22
1/*	$NetBSD: shutdown.c,v 1.22 1998/01/20 23:24:47 mycroft Exp $	*/
2
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. All advertising materials mentioning features or use of this software
16 *    must display the following acknowledgement:
17 *	This product includes software developed by the University of
18 *	California, Berkeley and its contributors.
19 * 4. Neither the name of the University nor the names of its contributors
20 *    may be used to endorse or promote products derived from this software
21 *    without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36#include <sys/cdefs.h>
37#ifndef lint
38__COPYRIGHT("@(#) Copyright (c) 1988, 1990, 1993\n\
39	The Regents of the University of California.  All rights reserved.\n");
40#endif /* not lint */
41
42#ifndef lint
43#if 0
44static char sccsid[] = "@(#)shutdown.c	8.4 (Berkeley) 4/28/95";
45#else
46__RCSID("$NetBSD: shutdown.c,v 1.22 1998/01/20 23:24:47 mycroft Exp $");
47#endif
48#endif /* not lint */
49
50#include <sys/param.h>
51#include <sys/time.h>
52#include <sys/resource.h>
53#include <sys/syslog.h>
54
55#include <ctype.h>
56#include <err.h>
57#include <fcntl.h>
58#include <pwd.h>
59#include <setjmp.h>
60#include <signal.h>
61#include <stdio.h>
62#include <stdlib.h>
63#include <string.h>
64#include <tzfile.h>
65#include <unistd.h>
66
67#include "pathnames.h"
68
69#ifdef DEBUG
70#undef _PATH_NOLOGIN
71#define	_PATH_NOLOGIN	"./nologin"
72#undef _PATH_FASTBOOT
73#define	_PATH_FASTBOOT	"./fastboot"
74#endif
75
76#define	H		*60*60
77#define	M		*60
78#define	S		*1
79#define	NOLOG_TIME	5*60
80struct interval {
81	int timeleft, timetowait;
82} tlist[] = {
83	{ 10 H,  5 H },	{  5 H,  3 H },	{  2 H,  1 H },	{ 1 H, 30 M },
84	{ 30 M, 10 M },	{ 20 M, 10 M },	{ 10 M,  5 M },	{ 5 M,  3 M },
85	{  2 M,  1 M },	{  1 M, 30 S },	{ 30 S, 30 S },
86	{  0, 0 }
87};
88#undef H
89#undef M
90#undef S
91
92static time_t offset, shuttime;
93static int dofast, dohalt, doreboot, killflg, mbuflen, nosync, dodump;
94static char *whom, mbuf[BUFSIZ];
95
96void badtime __P((void));
97void die_you_gravy_sucking_pig_dog __P((void));
98void doitfast __P((void));
99void finish __P((int));
100void getoffset __P((char *));
101void loop __P((void));
102int main __P((int, char *[]));
103void nolog __P((void));
104void timeout __P((int));
105void timewarn __P((int));
106void usage __P((void));
107
108int
109main(argc, argv)
110	int argc;
111	char *argv[];
112{
113	char *p, *endp;
114	struct passwd *pw;
115	int arglen, ch, len;
116
117#ifndef DEBUG
118	if (geteuid())
119		errx(1, "NOT super-user");
120#endif
121	while ((ch = getopt(argc, argv, "dfhknr")) != -1)
122		switch (ch) {
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 'r':
139			doreboot = 1;
140			break;
141		case '?':
142		default:
143			usage();
144		}
145	argc -= optind;
146	argv += optind;
147
148	if (argc < 1)
149		usage();
150
151	if (dodump && !dohalt && !doreboot)
152		doreboot = 1;
153
154	if (dofast && nosync) {
155		warnx("incompatible switches -f and -n");
156		usage();
157	}
158	if (dohalt && doreboot) {
159		warnx("incompatible switches -h and -r");
160		usage();
161	}
162
163	getoffset(*argv++);
164
165	if (argv[0])
166		if (strcmp(argv[0], "-") || argv[1]) {
167			for (p = mbuf, len = sizeof(mbuf); *argv; ++argv) {
168				arglen = strlen(*argv);
169				if ((len -= arglen) <= 2)
170					break;
171				if (p != mbuf)
172					*p++ = ' ';
173				memmove(p, *argv, arglen);
174				p += arglen;
175			}
176			*p = '\n';
177			*++p = '\0';
178		} else {
179			p = mbuf;
180			endp = mbuf + sizeof(mbuf) - 2;
181			for (;;) {
182				if (!fgets(p, endp - p + 1, stdin))
183					break;
184				for (; *p &&  p < endp; ++p);
185				if (p == endp) {
186					*p = '\n';
187					*++p = '\0';
188					break;
189				}
190			}
191		}
192	mbuflen = strlen(mbuf);
193
194	if (offset)
195		(void)printf("Shutdown at %.24s.\n", ctime(&shuttime));
196	else
197		(void)printf("Shutdown NOW!\n");
198
199	if (!(whom = getlogin()))
200		whom = (pw = getpwuid(getuid())) ? pw->pw_name : "???";
201
202#ifdef DEBUG
203	(void)putc('\n', stdout);
204#else
205	(void)setpriority(PRIO_PROCESS, 0, PRIO_MIN);
206	{
207		int forkpid;
208
209		forkpid = fork();
210		if (forkpid == -1) {
211			perror("shutdown: fork");
212			exit(1);
213		}
214		if (forkpid) {
215			(void)printf("shutdown: [pid %d]\n", forkpid);
216			exit(0);
217		}
218	}
219#endif
220	openlog("shutdown", LOG_CONS, LOG_AUTH);
221	loop();
222	/* NOTREACHED */
223#ifdef __GNUC__
224	return 1;
225#endif
226}
227
228void
229loop()
230{
231	struct interval *tp;
232	u_int sltime;
233	int logged;
234
235	if (offset <= NOLOG_TIME) {
236		logged = 1;
237		nolog();
238	}
239	else
240		logged = 0;
241	tp = tlist;
242	if (tp->timeleft < offset)
243		(void)sleep((u_int)(offset - tp->timeleft));
244	else {
245		while (offset < tp->timeleft)
246			++tp;
247		/*
248		 * Warn now, if going to sleep more than a fifth of
249		 * the next wait time.
250		 */
251		if ((sltime = offset - tp->timeleft) != 0) {
252			if (sltime > tp->timetowait / 5)
253				timewarn(offset);
254			(void)sleep(sltime);
255		}
256	}
257	for (;; ++tp) {
258		timewarn(tp->timeleft);
259		if (!logged && tp->timeleft <= NOLOG_TIME) {
260			logged = 1;
261			nolog();
262		}
263		(void)sleep((u_int)tp->timetowait);
264		if (!tp->timeleft)
265			break;
266	}
267	die_you_gravy_sucking_pig_dog();
268}
269
270static jmp_buf alarmbuf;
271
272void
273timewarn(timeleft)
274	int timeleft;
275{
276	static int first;
277	static char hostname[MAXHOSTNAMELEN + 1];
278	FILE *pf;
279	char wcmd[MAXPATHLEN + 4];
280
281	if (!first++)
282		(void)gethostname(hostname, sizeof(hostname));
283
284	/* undoc -n option to wall suppresses normal wall banner */
285	(void)snprintf(wcmd, sizeof(wcmd), "%s -n", _PATH_WALL);
286	if (!(pf = popen(wcmd, "w"))) {
287		syslog(LOG_ERR, "shutdown: can't find %s: %m", _PATH_WALL);
288		return;
289	}
290
291	(void)fprintf(pf,
292	    "\007*** %sSystem shutdown message from %s@%s ***\007\n",
293	    timeleft ? "": "FINAL ", whom, hostname);
294
295	if (timeleft > 10*60)
296		(void)fprintf(pf, "System going down at %5.5s\n\n",
297		    ctime(&shuttime) + 11);
298	else if (timeleft > 59)
299		(void)fprintf(pf, "System going down in %d minute%s\n\n",
300		    timeleft / 60, (timeleft > 60) ? "s" : "");
301	else if (timeleft)
302		(void)fprintf(pf, "System going down in 30 seconds\n\n");
303	else
304		(void)fprintf(pf, "System going down IMMEDIATELY\n\n");
305
306	if (mbuflen)
307		(void)fwrite(mbuf, sizeof(*mbuf), mbuflen, pf);
308
309	/*
310	 * play some games, just in case wall doesn't come back
311	 * probably unecessary, given that wall is careful.
312	 */
313	if (!setjmp(alarmbuf)) {
314		(void)signal(SIGALRM, timeout);
315		(void)alarm((u_int)30);
316		(void)pclose(pf);
317		(void)alarm((u_int)0);
318		(void)signal(SIGALRM, SIG_DFL);
319	}
320}
321
322void
323timeout(signo)
324	int signo;
325{
326	longjmp(alarmbuf, 1);
327}
328
329void
330die_you_gravy_sucking_pig_dog()
331{
332
333	syslog(LOG_NOTICE, "%s by %s: %s",
334	    doreboot ? "reboot" : dohalt ? "halt" : "shutdown", whom, mbuf);
335	(void)sleep(2);
336
337	(void)printf("\r\nSystem shutdown time has arrived\007\007\r\n");
338	if (killflg) {
339		(void)printf("\rbut you'll have to do it yourself\r\n");
340		finish(0);
341	}
342	if (dofast)
343		doitfast();
344	if (doreboot || dohalt) {
345		char *args[8], **arg, *path;
346
347		arg = &args[0];
348		if (doreboot) {
349			path = _PATH_REBOOT;
350			*arg++ = "reboot";
351		} else {
352			path = _PATH_HALT;
353			*arg++ = "halt";
354		}
355		if (dodump)
356			*arg++ = "-d";
357		if (nosync)
358			*arg++ = "-n";
359		*arg++ = "-l";
360		*arg++ = 0;
361#ifndef DEBUG
362		exect(path, args, (char **)0);
363		syslog(LOG_ERR, "shutdown: can't exec %s: %m", path);
364		perror("shutdown");
365#else
366		printf("%s", path);
367		for (arg = &args[0]; *arg; arg++)
368			printf(" %s", *arg);
369		printf("\n");
370#endif
371	} else {
372#ifndef DEBUG
373		(void)kill(1, SIGTERM);		/* to single user */
374#else
375		printf("kill 1\n");
376#endif
377	}
378	finish(0);
379}
380
381#define	ATOI2(p)	(p[0] - '0') * 10 + (p[1] - '0'); p += 2;
382
383void
384getoffset(timearg)
385	char *timearg;
386{
387	struct tm *lt;
388	char *p;
389	time_t now;
390	int yearset;
391
392	if (!strcasecmp(timearg, "now")) {		/* now */
393		offset = 0;
394		return;
395	}
396
397	(void)time(&now);
398	if (*timearg == '+') {				/* +minutes */
399		if (!isdigit(*++timearg))
400			badtime();
401		offset = atoi(timearg) * 60;
402		shuttime = now + offset;
403		return;
404	}
405
406	/* handle hh:mm by getting rid of the colon */
407	for (p = timearg; *p; ++p)
408		if (!isascii(*p) || !isdigit(*p))
409			if (*p == ':' && strlen(p) == 3) {
410				p[0] = p[1];
411				p[1] = p[2];
412				p[2] = '\0';
413			}
414			else
415				badtime();
416
417	unsetenv("TZ");					/* OUR timezone */
418	lt = localtime(&now);				/* current time val */
419
420	lt->tm_sec = 0;
421
422	yearset = 0;
423	switch (strlen(timearg)) {
424	case 12:
425		lt->tm_year = ATOI2(timearg);
426		lt->tm_year *= 100;
427		yearset = 1;
428		/* FALLTHROUGH */
429	case 10:
430		if (yearset) {
431			yearset = ATOI2(timearg);
432			lt->tm_year += yearset;
433		} else {
434			yearset = ATOI2(timearg);
435			if (yearset < 69)
436				lt->tm_year = yearset + 2000;
437			else
438				lt->tm_year = yearset + 1900;
439		}
440		lt->tm_year -= TM_YEAR_BASE;
441		/* FALLTHROUGH */
442	case 8:
443		lt->tm_mon = ATOI2(timearg);
444		--lt->tm_mon;
445		/* FALLTHROUGH */
446	case 6:
447		lt->tm_mday = ATOI2(timearg);
448		/* FALLTHROUGH */
449	case 4:
450		lt->tm_hour = ATOI2(timearg);
451		/* FALLTHROUGH */
452	case 2:
453		lt->tm_min = ATOI2(timearg);
454		break;
455	default:
456		badtime();
457	}
458
459	if ((shuttime = mktime(lt)) == -1)
460		badtime();
461	if ((offset = shuttime - now) < 0)
462		errx(1, "time is already past");
463}
464
465#define	FSMSG	"fastboot file for fsck\n"
466void
467doitfast()
468{
469	int fastfd;
470
471	if ((fastfd = open(_PATH_FASTBOOT, O_WRONLY|O_CREAT|O_TRUNC,
472	    0664)) >= 0) {
473		(void)write(fastfd, FSMSG, sizeof(FSMSG) - 1);
474		(void)close(fastfd);
475	}
476}
477
478#define	NOMSG	"\n\nNO LOGINS: System going down at "
479void
480nolog()
481{
482	int logfd;
483	char *ct;
484
485	(void)unlink(_PATH_NOLOGIN);	/* in case linked to another file */
486	(void)signal(SIGINT, finish);
487	(void)signal(SIGHUP, finish);
488	(void)signal(SIGQUIT, finish);
489	(void)signal(SIGTERM, finish);
490	if ((logfd = open(_PATH_NOLOGIN, O_WRONLY|O_CREAT|O_TRUNC,
491	    0664)) >= 0) {
492		(void)write(logfd, NOMSG, sizeof(NOMSG) - 1);
493		ct = ctime(&shuttime);
494		(void)write(logfd, ct + 11, 5);
495		(void)write(logfd, "\n\n", 2);
496		(void)write(logfd, mbuf, strlen(mbuf));
497		(void)close(logfd);
498	}
499}
500
501void
502finish(signo)
503	int signo;
504{
505	if (!killflg)
506		(void)unlink(_PATH_NOLOGIN);
507	exit(0);
508}
509
510void
511badtime()
512{
513	warnx("illegal time format");
514	usage();
515}
516
517void
518usage()
519{
520	(void)fprintf(stderr,
521	    "usage: shutdown [-dfhknr] time [message ... | -]\n");
522	exit(1);
523}
524