summaryrefslogtreecommitdiff
path: root/init/halt.c
blob: 2070eaa4df6fe9016d4c00c40da5bea4b59fb780 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
/* vi: set sw=4 ts=4: */
/*
 * Poweroff reboot and halt, oh my.
 *
 * Copyright 2006 by Rob Landley <rob@landley.net>
 *
 * Licensed under GPLv2, see file LICENSE in this source tree.
 */
//config:config HALT
//config:	bool "halt (4 kb)"
//config:	default y
//config:	help
//config:	Stop all processes and halt the system.
//config:
//config:config POWEROFF
//config:	bool "poweroff (4 kb)"
//config:	default y
//config:	help
//config:	Stop all processes and power off the system.
//config:
//config:config REBOOT
//config:	bool "reboot (4 kb)"
//config:	default y
//config:	help
//config:	Stop all processes and reboot the system.
//config:
//config:config FEATURE_WAIT_FOR_INIT
//config:	bool "Before signaling init, make sure it is ready for it"
//config:	default y
//config:	depends on HALT || POWEROFF || REBOOT
//config:	help
//config:	In rare cases, poweroff may be commanded by firmware to OS
//config:	even before init process exists. On Linux, this spawns
//config:	"/sbin/poweroff" very early. This option adds code
//config:	which checks that init is ready to receive poweroff
//config:	commands. Code size increase of ~80 bytes.
//config:
//config:config FEATURE_CALL_TELINIT
//config:	bool "Call telinit on shutdown and reboot"
//config:	default y
//config:	depends on (HALT || POWEROFF || REBOOT) && !INIT
//config:	help
//config:	Call an external program (normally telinit) to facilitate
//config:	a switch to a proper runlevel.
//config:
//config:	This option is only available if you selected halt and friends,
//config:	but did not select init.
//config:
//config:config TELINIT_PATH
//config:	string "Path to telinit executable"
//config:	default "/sbin/telinit"
//config:	depends on FEATURE_CALL_TELINIT
//config:	help
//config:	When busybox halt and friends have to call external telinit
//config:	to facilitate proper shutdown, this path is to be used when
//config:	locating telinit executable.

//applet:IF_HALT(APPLET(halt, BB_DIR_SBIN, BB_SUID_DROP))
//                   APPLET_ODDNAME:name      main  location     suid_type     help
//applet:IF_POWEROFF(APPLET_ODDNAME(poweroff, halt, BB_DIR_SBIN, BB_SUID_DROP, poweroff))
//applet:IF_REBOOT(  APPLET_ODDNAME(reboot,   halt, BB_DIR_SBIN, BB_SUID_DROP, reboot))

//kbuild:lib-$(CONFIG_HALT) += halt.o
//kbuild:lib-$(CONFIG_POWEROFF) += halt.o
//kbuild:lib-$(CONFIG_REBOOT) += halt.o

//usage:#define halt_trivial_usage
//usage:       "[-d DELAY] [-n] [-f]" IF_FEATURE_WTMP(" [-w]")
//usage:#define halt_full_usage "\n\n"
//usage:       "Halt the system\n"
//usage:     "\n	-d SEC	Delay interval"
//usage:     "\n	-n	Do not sync"
//usage:     "\n	-f	Force (don't go through init)"
//usage:	IF_FEATURE_WTMP(
//usage:     "\n	-w	Only write a wtmp record"
//usage:	)
//usage:
//usage:#define poweroff_trivial_usage
//usage:       "[-d DELAY] [-n] [-f]"
//usage:#define poweroff_full_usage "\n\n"
//usage:       "Halt and shut off power\n"
//usage:     "\n	-d SEC	Delay interval"
//usage:     "\n	-n	Do not sync"
//usage:     "\n	-f	Force (don't go through init)"
//usage:
//usage:#define reboot_trivial_usage
//usage:       "[-d DELAY] [-n] [-f]"
//usage:#define reboot_full_usage "\n\n"
//usage:       "Reboot the system\n"
//usage:     "\n	-d SEC	Delay interval"
//usage:     "\n	-n	Do not sync"
//usage:     "\n	-f	Force (don't go through init)"

#include "libbb.h"
#include "reboot.h"

#if ENABLE_FEATURE_WTMP
#include <sys/utsname.h>

static void write_wtmp(void)
{
	struct utmpx utmp;
	struct utsname uts;
	/* "man utmp" says wtmp file should *not* be created automagically */
	/*if (access(bb_path_wtmp_file, R_OK|W_OK) == -1) {
		close(creat(bb_path_wtmp_file, 0664));
	}*/
	memset(&utmp, 0, sizeof(utmp));
	utmp.ut_tv.tv_sec = time(NULL);
	strcpy(utmp.ut_user, "shutdown"); /* it is wide enough */
	utmp.ut_type = RUN_LVL;
	utmp.ut_id[0] = '~'; utmp.ut_id[1] = '~'; /* = strcpy(utmp.ut_id, "~~"); */
	utmp.ut_line[0] = '~'; utmp.ut_line[1] = '~'; /* = strcpy(utmp.ut_line, "~~"); */
	uname(&uts);
	safe_strncpy(utmp.ut_host, uts.release, sizeof(utmp.ut_host));
	updwtmpx(bb_path_wtmp_file, &utmp);
}
#else
#define write_wtmp() ((void)0)
#endif

#if ENABLE_FEATURE_WAIT_FOR_INIT
/* In Linux, "poweroff" may be spawned even before init.
 * For example, with ACPI:
 * linux/drivers/acpi/bus.c:
 *  static void sb_notify_work(struct work_struct *dummy)
 *      orderly_poweroff(true);
 * linux/kernel/reboot.c:
 *  static int run_cmd(const char *cmd)
 *      ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
 *  poweroff_cmd[] = "/sbin/poweroff";
 *  static int __orderly_poweroff(bool force)
 *      ret = run_cmd(poweroff_cmd);
 *
 * We want to make sure init exists and listens to signals.
 */
static int init_was_not_there(void)
{
	enum { initial = 5 }; /* 5 seconds should be plenty for timeout */
	int cnt = initial - 1;

	/* Just existence of PID 1 does not mean it installed
	 * the handlers already.
	 */
#if 0
	while (kill(1, 0) != 0 && --cnt >= 0)
		sleep1();
#endif
	/* ... so let's wait for some evidence a usual startup event,
	 * mounting of /proc, happened. By that time init should be ready
	 * for signals.
	 */
	while (access("/proc/meminfo", F_OK) != 0 && --cnt >= 0)
		sleep1();

	/* Does it look like init wasn't there? */
	return (cnt != initial - 1);
}
#else
  /* Assume it's always there */
# define init_was_not_there() 0
#endif

int halt_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int halt_main(int argc UNUSED_PARAM, char **argv)
{
	static const int magic[] = {
		RB_HALT_SYSTEM,
		RB_POWER_OFF,
		RB_AUTOBOOT
	};
	static const smallint signals[] = { SIGUSR1, SIGUSR2, SIGTERM };

	int delay = 0;
	int which, flags, rc;

	/* Figure out which applet we're running */
	if (ENABLE_HALT && !ENABLE_POWEROFF && !ENABLE_REBOOT)
		which = 0;
	else
	if (!ENABLE_HALT && ENABLE_POWEROFF && !ENABLE_REBOOT)
		which = 1;
	else
	if (!ENABLE_HALT && !ENABLE_POWEROFF && ENABLE_REBOOT)
		which = 2;
	else
	for (which = 0; "hpr"[which] != applet_name[0]; which++)
		continue;

	/* Parse and handle arguments */
	/* We support -w even if !ENABLE_FEATURE_WTMP,
	 * in order to not break scripts.
	 * -i (shut down network interfaces) is ignored.
	 */
	flags = getopt32(argv, "d:+nfwi", &delay);

	sleep(delay);

	write_wtmp();

	if (flags & 8) /* -w */
		return EXIT_SUCCESS;

	if (!(flags & 2)) /* no -n */
		sync();

	/* Perform action. */
	rc = 1;
	if (!(flags & 4)) { /* no -f */
//TODO: I tend to think that signalling linuxrc is wrong
// pity original author didn't comment on it...
		if (ENABLE_LINUXRC) {
			/* talk to linuxrc */
			/* bbox init/linuxrc assumed */
			pid_t *pidlist = find_pid_by_name("linuxrc");
			if (pidlist[0] > 0)
				rc = kill(pidlist[0], signals[which]);
			if (ENABLE_FEATURE_CLEAN_UP)
				free(pidlist);
		}
		if (rc) {
			/* talk to init */
			if (!ENABLE_FEATURE_CALL_TELINIT) {
				/* bbox init assumed */
				rc = kill(1, signals[which]);
				if (init_was_not_there())
					rc = kill(1, signals[which]);
			} else {
				/* SysV style init assumed */
				/* runlevels:
				 * 0 == shutdown
				 * 6 == reboot */
				execlp(CONFIG_TELINIT_PATH,
						CONFIG_TELINIT_PATH,
						which == 2 ? "6" : "0",
						(char *)NULL
				);
				bb_perror_msg_and_die("can't execute '%s'",
						CONFIG_TELINIT_PATH);
			}
		}
	} else {
		rc = reboot(magic[which]);
	}

	if (rc)
		bb_perror_nomsg_and_die();
	return rc;
}