xref: /freebsd/sbin/reboot/reboot.c (revision e2257b31)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1980, 1986, 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 #include <sys/types.h>
33 #include <sys/boottrace.h>
34 #include <sys/mount.h>
35 #include <sys/reboot.h>
36 #include <sys/stat.h>
37 #include <sys/sysctl.h>
38 #include <sys/time.h>
39 
40 #include <err.h>
41 #include <errno.h>
42 #include <fcntl.h>
43 #include <pwd.h>
44 #include <signal.h>
45 #include <stdbool.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <syslog.h>
50 #include <unistd.h>
51 #include <utmpx.h>
52 
53 #define PATH_NEXTBOOT "/boot/nextboot.conf"
54 
55 static void usage(void) __dead2;
56 static uint64_t get_pageins(void);
57 
58 static bool dohalt;
59 static bool donextboot;
60 
61 #define E(...) do {				\
62 		if (force) {			\
63 			warn( __VA_ARGS__ );	\
64 			return;			\
65 		}				\
66 		err(1, __VA_ARGS__);		\
67 	} while (0)				\
68 
69 static void
70 zfsbootcfg(const char *pool, bool force)
71 {
72 	char *k;
73 	int rv;
74 
75 	asprintf(&k,
76 	    "zfsbootcfg -z %s -n freebsd:nvstore -k nextboot_enable -v YES",
77 	    pool);
78 	if (k == NULL)
79 		E("No memory for zfsbootcfg");
80 
81 	rv = system(k);
82 	if (rv == 0)
83 		return;
84 	if (rv == -1)
85 		E("system zfsbootcfg");
86 	if (rv == 127)
87 		E("zfsbootcfg not found in path");
88 	E("zfsbootcfg returned %d", rv);
89 }
90 
91 static void
92 write_nextboot(const char *fn, const char *env, bool force)
93 {
94 	FILE *fp;
95 	struct statfs sfs;
96 	bool supported = false;
97 	bool zfs = false;
98 
99 	if (statfs("/boot", &sfs) != 0)
100 		err(1, "statfs /boot");
101 	if (strcmp(sfs.f_fstypename, "ufs") == 0) {
102 		/*
103 		 * Only UFS supports the full nextboot protocol.
104 		 */
105 		supported = true;
106 	} else if (strcmp(sfs.f_fstypename, "zfs") == 0) {
107 		zfs = true;
108 	}
109 
110 	if (zfs) {
111 		zfsbootcfg(sfs.f_mntfromname, force);
112 	}
113 
114 	fp = fopen(fn, "w");
115 	if (fp == NULL)
116 		E("Can't create %s", fn);
117 
118 	if (fprintf(fp,"%s%s",
119 	    supported ? "nextboot_enable=\"YES\"\n" : "",
120 	    env != NULL ? env : "") < 0) {
121 		int e;
122 
123 		e = errno;
124 		fclose(fp);
125 		if (unlink(fn))
126 			warn("unlink %s", fn);
127 		errno = e;
128 		E("Can't write %s", fn);
129 	}
130 	fclose(fp);
131 }
132 
133 static char *
134 split_kv(char *raw)
135 {
136 	char *eq;
137 	int len;
138 
139 	eq = strchr(raw, '=');
140 	if (eq == NULL)
141 		errx(1, "No = in environment string %s", raw);
142 	*eq++ = '\0';
143 	len = strlen(eq);
144 	if (len == 0)
145 		errx(1, "Invalid null value %s=", raw);
146 	if (eq[0] == '"') {
147 		if (len < 2 || eq[len - 1] != '"')
148 			errx(1, "Invalid string '%s'", eq);
149 		eq[len - 1] = '\0';
150 		return (eq + 1);
151 	}
152 	return (eq);
153 }
154 
155 static void
156 add_env(char **env, const char *key, const char *value)
157 {
158 	char *oldenv;
159 
160 	oldenv = *env;
161 	asprintf(env, "%s%s=\"%s\"\n", oldenv != NULL ? oldenv : "", key, value);
162 	if (env == NULL)
163 		errx(1, "No memory to build env array");
164 	free(oldenv);
165 }
166 
167 /*
168  * Different options are valid for different programs.
169  */
170 #define GETOPT_REBOOT "cDde:k:lNno:pqr"
171 #define GETOPT_NEXTBOOT "De:k:o:"
172 
173 int
174 main(int argc, char *argv[])
175 {
176 	struct utmpx utx;
177 	const struct passwd *pw;
178 	int ch, howto, i, sverrno;
179 	bool Dflag, fflag, lflag, Nflag, nflag, qflag;
180 	uint64_t pageins;
181 	const char *user, *kernel = NULL, *getopts = GETOPT_REBOOT;
182 	char *env = NULL, *v;
183 
184 	if (strstr(getprogname(), "halt") != NULL) {
185 		dohalt = true;
186 		howto = RB_HALT;
187 	} else if (strcmp(getprogname(), "nextboot") == 0) {
188 		donextboot = true;
189 		getopts = GETOPT_NEXTBOOT; /* Note: reboot's extra opts return '?' */
190 	} else {
191 		howto = 0;
192 	}
193 	Dflag = fflag = lflag = Nflag = nflag = qflag = false;
194 	while ((ch = getopt(argc, argv, getopts)) != -1) {
195 		switch(ch) {
196 		case 'c':
197 			howto |= RB_POWERCYCLE;
198 			break;
199 		case 'D':
200 			Dflag = true;
201 			break;
202 		case 'd':
203 			howto |= RB_DUMP;
204 			break;
205 		case 'e':
206 			v = split_kv(optarg);
207 			add_env(&env, optarg, v);
208 			break;
209 		case 'f':
210 			fflag = true;
211 			break;
212 		case 'k':
213 			kernel = optarg;
214 			break;
215 		case 'l':
216 			lflag = true;
217 			break;
218 		case 'n':
219 			nflag = true;
220 			howto |= RB_NOSYNC;
221 			break;
222 		case 'N':
223 			nflag = true;
224 			Nflag = true;
225 			break;
226 		case 'o':
227 			add_env(&env, "kernel_options", optarg);
228 			break;
229 		case 'p':
230 			howto |= RB_POWEROFF;
231 			break;
232 		case 'q':
233 			qflag = true;
234 			break;
235 		case 'r':
236 			howto |= RB_REROOT;
237 			break;
238 		case '?':
239 		default:
240 			usage();
241 		}
242 	}
243 
244 	argc -= optind;
245 	argv += optind;
246 	if (argc != 0)
247 		usage();
248 
249 	if (Dflag && ((howto & ~RB_HALT) != 0  || kernel != NULL))
250 		errx(1, "cannot delete existing nextboot config and do anything else");
251 	if ((howto & (RB_DUMP | RB_HALT)) == (RB_DUMP | RB_HALT))
252 		errx(1, "cannot dump (-d) when halting; must reboot instead");
253 	if (Nflag && (howto & RB_NOSYNC) != 0)
254 		errx(1, "-N cannot be used with -n");
255 	if ((howto & RB_POWEROFF) && (howto & RB_POWERCYCLE))
256 		errx(1, "-c and -p cannot be used together");
257 	if ((howto & RB_REROOT) != 0 && howto != RB_REROOT)
258 		errx(1, "-r cannot be used with -c, -d, -n, or -p");
259 	if ((howto & RB_REROOT) != 0 && kernel != NULL)
260 		errx(1, "-r and -k cannot be used together, there is no next kernel");
261 
262 	if (Dflag) {
263 		if (unlink(PATH_NEXTBOOT) != 0)
264 			err(1, "unlink %s", PATH_NEXTBOOT);
265 		exit(0);
266 	}
267 
268 	if (!donextboot && geteuid() != 0) {
269 		errno = EPERM;
270 		err(1, NULL);
271 	}
272 
273 	if (qflag) {
274 		reboot(howto);
275 		err(1, NULL);
276 	}
277 
278 	if (kernel != NULL) {
279 		if (!fflag) {
280 			char *k;
281 			struct stat sb;
282 
283 			asprintf(&k, "/boot/%s/kernel", kernel);
284 			if (k == NULL)
285 				errx(1, "No memory to check %s", kernel);
286 			if (stat(k, &sb) != 0)
287 				err(1, "stat %s", k);
288 			if (!S_ISREG(sb.st_mode))
289 				errx(1, "%s is not a file", k);
290 			free(k);
291 		}
292 		add_env(&env, "kernel", kernel);
293 	}
294 
295 	if (env != NULL)
296 		write_nextboot(PATH_NEXTBOOT, env, fflag);
297 	if (donextboot)
298 		exit (0);
299 
300 	/* Log the reboot. */
301 	if (!lflag)  {
302 		if ((user = getlogin()) == NULL)
303 			user = (pw = getpwuid(getuid())) ?
304 			    pw->pw_name : "???";
305 		if (dohalt) {
306 			openlog("halt", 0, LOG_AUTH | LOG_CONS);
307 			syslog(LOG_CRIT, "halted by %s", user);
308 		} else if (howto & RB_REROOT) {
309 			openlog("reroot", 0, LOG_AUTH | LOG_CONS);
310 			syslog(LOG_CRIT, "rerooted by %s", user);
311 		} else if (howto & RB_POWEROFF) {
312 			openlog("reboot", 0, LOG_AUTH | LOG_CONS);
313 			syslog(LOG_CRIT, "powered off by %s", user);
314 		} else if (howto & RB_POWERCYCLE) {
315 			openlog("reboot", 0, LOG_AUTH | LOG_CONS);
316 			syslog(LOG_CRIT, "power cycled by %s", user);
317 		} else {
318 			openlog("reboot", 0, LOG_AUTH | LOG_CONS);
319 			syslog(LOG_CRIT, "rebooted by %s", user);
320 		}
321 	}
322 	utx.ut_type = SHUTDOWN_TIME;
323 	gettimeofday(&utx.ut_tv, NULL);
324 	pututxline(&utx);
325 
326 	/*
327 	 * Do a sync early on, so disks start transfers while we're off
328 	 * killing processes.  Don't worry about writes done before the
329 	 * processes die, the reboot system call syncs the disks.
330 	 */
331 	if (!nflag)
332 		sync();
333 
334 	/*
335 	 * Ignore signals that we can get as a result of killing
336 	 * parents, group leaders, etc.
337 	 */
338 	(void)signal(SIGHUP,  SIG_IGN);
339 	(void)signal(SIGINT,  SIG_IGN);
340 	(void)signal(SIGQUIT, SIG_IGN);
341 	(void)signal(SIGTERM, SIG_IGN);
342 	(void)signal(SIGTSTP, SIG_IGN);
343 
344 	/*
345 	 * If we're running in a pipeline, we don't want to die
346 	 * after killing whatever we're writing to.
347 	 */
348 	(void)signal(SIGPIPE, SIG_IGN);
349 
350 	/*
351 	 * Only init(8) can perform rerooting.
352 	 */
353 	if (howto & RB_REROOT) {
354 		if (kill(1, SIGEMT) == -1)
355 			err(1, "SIGEMT init");
356 
357 		return (0);
358 	}
359 
360 	/* Just stop init -- if we fail, we'll restart it. */
361 	BOOTTRACE("SIGTSTP to init(8)...");
362 	if (kill(1, SIGTSTP) == -1)
363 		err(1, "SIGTSTP init");
364 
365 	/* Send a SIGTERM first, a chance to save the buffers. */
366 	BOOTTRACE("SIGTERM to all other processes...");
367 	if (kill(-1, SIGTERM) == -1 && errno != ESRCH)
368 		err(1, "SIGTERM processes");
369 
370 	/*
371 	 * After the processes receive the signal, start the rest of the
372 	 * buffers on their way.  Wait 5 seconds between the SIGTERM and
373 	 * the SIGKILL to give everybody a chance. If there is a lot of
374 	 * paging activity then wait longer, up to a maximum of approx
375 	 * 60 seconds.
376 	 */
377 	sleep(2);
378 	for (i = 0; i < 20; i++) {
379 		pageins = get_pageins();
380 		if (!nflag)
381 			sync();
382 		sleep(3);
383 		if (get_pageins() == pageins)
384 			break;
385 	}
386 
387 	for (i = 1;; ++i) {
388 		BOOTTRACE("SIGKILL to all other processes(%d)...", i);
389 		if (kill(-1, SIGKILL) == -1) {
390 			if (errno == ESRCH)
391 				break;
392 			goto restart;
393 		}
394 		if (i > 5) {
395 			(void)fprintf(stderr,
396 			    "WARNING: some process(es) wouldn't die\n");
397 			break;
398 		}
399 		(void)sleep(2 * i);
400 	}
401 
402 	reboot(howto);
403 	/* FALLTHROUGH */
404 
405 restart:
406 	BOOTTRACE("SIGHUP to init(8)...");
407 	sverrno = errno;
408 	errx(1, "%s%s", kill(1, SIGHUP) == -1 ? "(can't restart init): " : "",
409 	    strerror(sverrno));
410 	/* NOTREACHED */
411 }
412 
413 static void
414 usage(void)
415 {
416 
417 	(void)fprintf(stderr, dohalt ?
418 	    "usage: halt [-clNnpq] [-k kernel]\n" :
419 	    "usage: reboot [-cdlNnpqr] [-k kernel]\n");
420 	exit(1);
421 }
422 
423 static uint64_t
424 get_pageins(void)
425 {
426 	uint64_t pageins;
427 	size_t len;
428 
429 	len = sizeof(pageins);
430 	if (sysctlbyname("vm.stats.vm.v_swappgsin", &pageins, &len, NULL, 0)
431 	    != 0) {
432 		warn("v_swappgsin");
433 		return (0);
434 	}
435 	return (pageins);
436 }
437