diff options
author | Denis Vlasenko <vda.linux@googlemail.com> | 2009-04-26 01:08:51 +0000 |
---|---|---|
committer | Denis Vlasenko <vda.linux@googlemail.com> | 2009-04-26 01:08:51 +0000 |
commit | 71c165780ac83b0562a7cbc3b57e87807f02055b (patch) | |
tree | ac33a1f5952103df64846103301c672e32b38606 | |
parent | 5bf59c104af94e918ce879352f27b40099bf51be (diff) | |
download | busybox-w32-71c165780ac83b0562a7cbc3b57e87807f02055b.tar.gz busybox-w32-71c165780ac83b0562a7cbc3b57e87807f02055b.tar.bz2 busybox-w32-71c165780ac83b0562a7cbc3b57e87807f02055b.zip |
ifplugd: new applet by Maksym Kryzhanovskyy (xmaks AT email.cz)
+3k code and 0.5k in messages. Most of the bloat due to compat :(
-rw-r--r-- | include/applets.h | 1 | ||||
-rw-r--r-- | include/usage.h | 57 | ||||
-rw-r--r-- | networking/Config.in | 6 | ||||
-rw-r--r-- | networking/Kbuild | 1 | ||||
-rw-r--r-- | networking/ifplugd.c | 810 |
5 files changed, 859 insertions, 16 deletions
diff --git a/include/applets.h b/include/applets.h index 820aaffe2..a41f75e31 100644 --- a/include/applets.h +++ b/include/applets.h | |||
@@ -189,6 +189,7 @@ IF_ID(APPLET(id, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) | |||
189 | IF_IFCONFIG(APPLET(ifconfig, _BB_DIR_SBIN, _BB_SUID_NEVER)) | 189 | IF_IFCONFIG(APPLET(ifconfig, _BB_DIR_SBIN, _BB_SUID_NEVER)) |
190 | IF_IFUPDOWN(APPLET_ODDNAME(ifdown, ifupdown, _BB_DIR_SBIN, _BB_SUID_NEVER, ifdown)) | 190 | IF_IFUPDOWN(APPLET_ODDNAME(ifdown, ifupdown, _BB_DIR_SBIN, _BB_SUID_NEVER, ifdown)) |
191 | IF_IFENSLAVE(APPLET(ifenslave, _BB_DIR_SBIN, _BB_SUID_NEVER)) | 191 | IF_IFENSLAVE(APPLET(ifenslave, _BB_DIR_SBIN, _BB_SUID_NEVER)) |
192 | IF_IFPLUGD(APPLET(ifplugd, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) | ||
192 | IF_IFUPDOWN(APPLET_ODDNAME(ifup, ifupdown, _BB_DIR_SBIN, _BB_SUID_NEVER, ifup)) | 193 | IF_IFUPDOWN(APPLET_ODDNAME(ifup, ifupdown, _BB_DIR_SBIN, _BB_SUID_NEVER, ifup)) |
193 | IF_INETD(APPLET(inetd, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) | 194 | IF_INETD(APPLET(inetd, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) |
194 | IF_INIT(APPLET(init, _BB_DIR_SBIN, _BB_SUID_NEVER)) | 195 | IF_INIT(APPLET(init, _BB_DIR_SBIN, _BB_SUID_NEVER)) |
diff --git a/include/usage.h b/include/usage.h index 7b88a2acc..3fb996126 100644 --- a/include/usage.h +++ b/include/usage.h | |||
@@ -1727,21 +1727,46 @@ | |||
1727 | /* "\n -r, --receive-slave Create a receive-only slave" */ | 1727 | /* "\n -r, --receive-slave Create a receive-only slave" */ |
1728 | 1728 | ||
1729 | #define ifenslave_example_usage \ | 1729 | #define ifenslave_example_usage \ |
1730 | "To create a bond device, simply follow these three steps :\n" \ | 1730 | "To create a bond device, simply follow these three steps:\n" \ |
1731 | "- ensure that the required drivers are properly loaded :\n" \ | 1731 | "- ensure that the required drivers are properly loaded:\n" \ |
1732 | " # modprobe bonding ; modprobe <3c59x|eepro100|pcnet32|tulip|...>\n" \ | 1732 | " # modprobe bonding ; modprobe <3c59x|eepro100|pcnet32|tulip|...>\n" \ |
1733 | "- assign an IP address to the bond device :\n" \ | 1733 | "- assign an IP address to the bond device:\n" \ |
1734 | " # ifconfig bond0 <addr> netmask <mask> broadcast <bcast>\n" \ | 1734 | " # ifconfig bond0 <addr> netmask <mask> broadcast <bcast>\n" \ |
1735 | "- attach all the interfaces you need to the bond device :\n" \ | 1735 | "- attach all the interfaces you need to the bond device:\n" \ |
1736 | " # ifenslave bond0 eth0 eth1 eth2\n" \ | 1736 | " # ifenslave bond0 eth0 eth1 eth2\n" \ |
1737 | " If bond0 didn't have a MAC address, it will take eth0's. Then, all\n" \ | 1737 | " If bond0 didn't have a MAC address, it will take eth0's. Then, all\n" \ |
1738 | " interfaces attached AFTER this assignment will get the same MAC addr.\n\n" \ | 1738 | " interfaces attached AFTER this assignment will get the same MAC addr.\n\n" \ |
1739 | " To detach a dead interface without setting the bond device down :\n" \ | 1739 | " To detach a dead interface without setting the bond device down:\n" \ |
1740 | " # ifenslave -d bond0 eth1\n\n" \ | 1740 | " # ifenslave -d bond0 eth1\n\n" \ |
1741 | " To set the bond device down and automatically release all the slaves :\n" \ | 1741 | " To set the bond device down and automatically release all the slaves:\n" \ |
1742 | " # ifconfig bond0 down\n\n" \ | 1742 | " # ifconfig bond0 down\n\n" \ |
1743 | " To change active slave :\n" \ | 1743 | " To change active slave:\n" \ |
1744 | " # ifenslave -c bond0 eth0\n" \ | 1744 | " # ifenslave -c bond0 eth0\n" \ |
1745 | |||
1746 | #define ifplugd_trivial_usage \ | ||
1747 | "[options]" | ||
1748 | #define ifplugd_full_usage "\n\n" \ | ||
1749 | "Network interface plug detection daemon.\n\n" \ | ||
1750 | "Options:\n" \ | ||
1751 | "\n -n Do not daemonize" \ | ||
1752 | "\n -s Do not log to syslog" \ | ||
1753 | "\n -i IFACE Interface" \ | ||
1754 | "\n -f/-F Treat link detection error as link down/link up" \ | ||
1755 | "\n (otherwise exit on error)" \ | ||
1756 | "\n -a Do not up interface automatically" \ | ||
1757 | "\n -M Monitor creation/destruction of interface" \ | ||
1758 | "\n (otherwise it must exist)" \ | ||
1759 | "\n -r PROG Script to run" \ | ||
1760 | "\n -x ARG Extra argument for script" \ | ||
1761 | "\n -I Don't exit on nonzero exit code from script" \ | ||
1762 | "\n -p Don't run script on daemon startup" \ | ||
1763 | "\n -q Don't run script on daemon quit" \ | ||
1764 | "\n -l Run script on startup even if no cable is detected" \ | ||
1765 | "\n -t SECS Poll time in seconds" \ | ||
1766 | "\n -u SECS Delay before running script after link up" \ | ||
1767 | "\n -d SECS Delay after link down" \ | ||
1768 | "\n -m MODE API mode (mii, priv, ethtool, wlan, auto)" \ | ||
1769 | "\n -k Kill running daemon" \ | ||
1745 | 1770 | ||
1746 | #define ifup_trivial_usage \ | 1771 | #define ifup_trivial_usage \ |
1747 | "[-ain"IF_FEATURE_IFUPDOWN_MAPPING("m")"vf] ifaces..." | 1772 | "[-ain"IF_FEATURE_IFUPDOWN_MAPPING("m")"vf] ifaces..." |
@@ -4346,17 +4371,17 @@ | |||
4346 | "CMD: {add|del|change|replace|show}\n" \ | 4371 | "CMD: {add|del|change|replace|show}\n" \ |
4347 | "\n" \ | 4372 | "\n" \ |
4348 | "qdisc [ handle QHANDLE ] [ root |"IF_FEATURE_TC_INGRESS(" ingress |")" parent CLASSID ]\n" \ | 4373 | "qdisc [ handle QHANDLE ] [ root |"IF_FEATURE_TC_INGRESS(" ingress |")" parent CLASSID ]\n" \ |
4349 | /* "\t[ estimator INTERVAL TIME_CONSTANT ]\n" */ \ | 4374 | /* "[ estimator INTERVAL TIME_CONSTANT ]\n" */ \ |
4350 | "\t[ [ QDISC_KIND ] [ help | OPTIONS ] ]\n" \ | 4375 | " [ [ QDISC_KIND ] [ help | OPTIONS ] ]\n" \ |
4351 | "\tQDISC_KIND := { [p|b]fifo | tbf | prio | cbq | red | etc. }\n" \ | 4376 | " QDISC_KIND := { [p|b]fifo | tbf | prio | cbq | red | etc. }\n" \ |
4352 | "qdisc show [ dev STRING ]"IF_FEATURE_TC_INGRESS(" [ingress]")"\n" \ | 4377 | "qdisc show [ dev STRING ]"IF_FEATURE_TC_INGRESS(" [ingress]")"\n" \ |
4353 | "class [ classid CLASSID ] [ root | parent CLASSID ]\n" \ | 4378 | "class [ classid CLASSID ] [ root | parent CLASSID ]\n" \ |
4354 | "\t[ [ QDISC_KIND ] [ help | OPTIONS ] ]\n" \ | 4379 | " [ [ QDISC_KIND ] [ help | OPTIONS ] ]\n" \ |
4355 | "class show [ dev STRING ] [ root | parent CLASSID ]\n" \ | 4380 | "class show [ dev STRING ] [ root | parent CLASSID ]\n" \ |
4356 | "filter [ pref PRIO ] [ protocol PROTO ]\n" \ | 4381 | "filter [ pref PRIO ] [ protocol PROTO ]\n" \ |
4357 | /* "\t[ estimator INTERVAL TIME_CONSTANT ]\n" */ \ | 4382 | /* "\t[ estimator INTERVAL TIME_CONSTANT ]\n" */ \ |
4358 | "\t[ root | classid CLASSID ] [ handle FILTERID ]\n" \ | 4383 | " [ root | classid CLASSID ] [ handle FILTERID ]\n" \ |
4359 | "\t[ [ FILTER_TYPE ] [ help | OPTIONS ] ]\n" \ | 4384 | " [ [ FILTER_TYPE ] [ help | OPTIONS ] ]\n" \ |
4360 | "filter show [ dev STRING ] [ root | parent CLASSID ]" | 4385 | "filter show [ dev STRING ] [ root | parent CLASSID ]" |
4361 | 4386 | ||
4362 | #define tcpsvd_trivial_usage \ | 4387 | #define tcpsvd_trivial_usage \ |
diff --git a/networking/Config.in b/networking/Config.in index 62a21f738..9907ac907 100644 --- a/networking/Config.in +++ b/networking/Config.in | |||
@@ -303,6 +303,12 @@ config IFENSLAVE | |||
303 | Userspace application to bind several interfaces | 303 | Userspace application to bind several interfaces |
304 | to a logical interface (use with kernel bonding driver). | 304 | to a logical interface (use with kernel bonding driver). |
305 | 305 | ||
306 | config IFPLUGD | ||
307 | bool "ifplugd" | ||
308 | default n | ||
309 | help | ||
310 | Network interface plug detection daemon. | ||
311 | |||
306 | config IFUPDOWN | 312 | config IFUPDOWN |
307 | bool "ifupdown" | 313 | bool "ifupdown" |
308 | default n | 314 | default n |
diff --git a/networking/Kbuild b/networking/Kbuild index d632774ff..866d42f6b 100644 --- a/networking/Kbuild +++ b/networking/Kbuild | |||
@@ -18,6 +18,7 @@ lib-$(CONFIG_HOSTNAME) += hostname.o | |||
18 | lib-$(CONFIG_HTTPD) += httpd.o | 18 | lib-$(CONFIG_HTTPD) += httpd.o |
19 | lib-$(CONFIG_IFCONFIG) += ifconfig.o interface.o | 19 | lib-$(CONFIG_IFCONFIG) += ifconfig.o interface.o |
20 | lib-$(CONFIG_IFENSLAVE) += ifenslave.o interface.o | 20 | lib-$(CONFIG_IFENSLAVE) += ifenslave.o interface.o |
21 | lib-$(CONFIG_IFPLUGD) += ifplugd.o | ||
21 | lib-$(CONFIG_IFUPDOWN) += ifupdown.o | 22 | lib-$(CONFIG_IFUPDOWN) += ifupdown.o |
22 | lib-$(CONFIG_INETD) += inetd.o | 23 | lib-$(CONFIG_INETD) += inetd.o |
23 | lib-$(CONFIG_IP) += ip.o | 24 | lib-$(CONFIG_IP) += ip.o |
diff --git a/networking/ifplugd.c b/networking/ifplugd.c new file mode 100644 index 000000000..5a389fdf9 --- /dev/null +++ b/networking/ifplugd.c | |||
@@ -0,0 +1,810 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * ifplugd for busybox | ||
4 | * | ||
5 | * Copyright (C) 2009 | ||
6 | * | ||
7 | * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. | ||
8 | */ | ||
9 | #include "libbb.h" | ||
10 | |||
11 | #include <linux/if.h> | ||
12 | #include <linux/ethtool.h> | ||
13 | #include <net/ethernet.h> | ||
14 | #include <linux/netlink.h> | ||
15 | #include <linux/rtnetlink.h> | ||
16 | #include <linux/sockios.h> | ||
17 | #include <syslog.h> | ||
18 | |||
19 | #define __user | ||
20 | #include <linux/wireless.h> | ||
21 | |||
22 | #define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS" | ||
23 | #define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT" | ||
24 | |||
25 | enum { | ||
26 | FLAG_NO_AUTO = 1 << 0, // -a, Do not enable interface automatically | ||
27 | FLAG_NO_DAEMON = 1 << 1, // -n, Do not daemonize | ||
28 | FLAG_NO_SYSLOG = 1 << 2, // -s, Do not use syslog, use stderr instead | ||
29 | FLAG_IGNORE_FAIL = 1 << 3, // -f, Ignore detection failure, retry instead (failure is treated as DOWN) | ||
30 | FLAG_IGNORE_FAIL_POSITIVE = 1 << 4, // -F, Ignore detection failure, retry instead (failure is treated as UP) | ||
31 | FLAG_IFACE = 1 << 5, // -i, Specify ethernet interface | ||
32 | FLAG_RUN = 1 << 6, // -r, Specify program to execute | ||
33 | FLAG_IGNORE_RETVAL = 1 << 7, // -I, Don't exit on nonzero return value of program executed | ||
34 | FLAG_POLL_TIME = 1 << 8, // -t, Specify poll time in seconds | ||
35 | FLAG_DELAY_UP = 1 << 9, // -u, Specify delay for configuring interface | ||
36 | FLAG_DELAY_DOWN = 1 << 10, // -d, Specify delay for deconfiguring interface | ||
37 | FLAG_API_MODE = 1 << 11, // -m, Force API mode (mii, priv, ethtool, wlan, auto) | ||
38 | FLAG_NO_STARTUP = 1 << 12, // -p, Don't run script on daemon startup | ||
39 | FLAG_NO_SHUTDOWN = 1 << 13, // -q, Don't run script on daemon quit | ||
40 | FLAG_INITIAL_DOWN = 1 << 14, // -l, Run "down" script on startup if no cable is detected | ||
41 | FLAG_EXTRA_ARG = 1 << 15, // -x, Specify an extra argument for action script | ||
42 | FLAG_MONITOR = 1 << 16, // -M, Use interface monitoring | ||
43 | #if ENABLE_FEATURE_PIDFILE | ||
44 | FLAG_KILL = 1 << 17, // -k, Kill a running daemon | ||
45 | #endif | ||
46 | }; | ||
47 | #if ENABLE_FEATURE_PIDFILE | ||
48 | # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:Mk" | ||
49 | #else | ||
50 | # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:M" | ||
51 | #endif | ||
52 | |||
53 | enum { // api mode | ||
54 | API_AUTO = 'a', | ||
55 | API_ETHTOOL = 'e', | ||
56 | API_MII = 'm', | ||
57 | API_PRIVATE = 'p', | ||
58 | API_WLAN = 'w', | ||
59 | API_IFF = 'i', | ||
60 | }; | ||
61 | |||
62 | enum { // interface status | ||
63 | IFSTATUS_ERR = -1, | ||
64 | IFSTATUS_DOWN = 0, | ||
65 | IFSTATUS_UP = 1, | ||
66 | }; | ||
67 | |||
68 | enum { // constant fds | ||
69 | ioctl_fd = 3, | ||
70 | netlink_fd = 4, | ||
71 | }; | ||
72 | |||
73 | struct globals { | ||
74 | smallint iface_last_status; | ||
75 | smallint iface_exists; | ||
76 | |||
77 | /* Used in getopt32, must have sizeof == sizeof(int) */ | ||
78 | unsigned poll_time; | ||
79 | unsigned delay_up; | ||
80 | unsigned delay_down; | ||
81 | |||
82 | const char *iface; | ||
83 | const char *api_mode; | ||
84 | const char *script_name; | ||
85 | const char *extra_arg; | ||
86 | |||
87 | smallint (*detect_link_func)(void); | ||
88 | smallint (*cached_detect_link_func)(void); | ||
89 | }; | ||
90 | #define G (*ptr_to_globals) | ||
91 | #define INIT_G() do { \ | ||
92 | SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ | ||
93 | G.iface_last_status = -1; \ | ||
94 | G.iface_exists = 1; \ | ||
95 | G.poll_time = 1; \ | ||
96 | G.delay_down = 5; \ | ||
97 | G.iface = "eth0"; \ | ||
98 | G.api_mode = "a"; \ | ||
99 | G.script_name = "/etc/ifplugd/ifplugd.action"; \ | ||
100 | } while (0) | ||
101 | |||
102 | |||
103 | static int run_script(const char *action) | ||
104 | { | ||
105 | pid_t pid; | ||
106 | int r; | ||
107 | |||
108 | bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action); | ||
109 | |||
110 | #if 1 | ||
111 | pid = vfork(); | ||
112 | if (pid < 0) { | ||
113 | bb_perror_msg("fork"); | ||
114 | return -1; | ||
115 | } | ||
116 | |||
117 | if (pid == 0) { | ||
118 | /* child */ | ||
119 | execlp(G.script_name, G.script_name, G.iface, action, G.extra_arg, NULL); | ||
120 | bb_perror_msg_and_die("can't execute '%s'", G.script_name); | ||
121 | } | ||
122 | |||
123 | /* parent */ | ||
124 | wait(&r); | ||
125 | r = WEXITSTATUS(r); | ||
126 | |||
127 | bb_error_msg("exit code: %u", r); | ||
128 | return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r; | ||
129 | |||
130 | #else /* insanity */ | ||
131 | |||
132 | struct fd_pair pipe_pair; | ||
133 | char buf[256]; | ||
134 | int i = 0; | ||
135 | |||
136 | xpiped_pair(pipe_pair); | ||
137 | |||
138 | pid = vfork(); | ||
139 | if (pid < 0) { | ||
140 | bb_perror_msg("fork"); | ||
141 | return -1; | ||
142 | } | ||
143 | |||
144 | /* child */ | ||
145 | if (pid == 0) { | ||
146 | xmove_fd(pipe_pair.wr, 1); | ||
147 | xdup2(1, 2); | ||
148 | if (pipe_pair.rd > 2) | ||
149 | close(pipe_pair.rd); | ||
150 | |||
151 | // umask(0022); // Set up a sane umask | ||
152 | |||
153 | execlp(G.script_name, G.script_name, G.iface, action, G.extra_arg, NULL); | ||
154 | _exit(EXIT_FAILURE); | ||
155 | } | ||
156 | |||
157 | /* parent */ | ||
158 | close(pipe_pair.wr); | ||
159 | |||
160 | while (1) { | ||
161 | if (bb_got_signal && bb_got_signal != SIGCHLD) { | ||
162 | bb_error_msg("killing child"); | ||
163 | kill(pid, SIGTERM); | ||
164 | bb_got_signal = 0; | ||
165 | break; | ||
166 | } | ||
167 | |||
168 | r = read(pipe_pair.rd, &buf[i], 1); | ||
169 | |||
170 | if (buf[i] == '\n' || i == sizeof(buf)-2 || r != 1) { | ||
171 | if (r == 1 && buf[i] != '\n') | ||
172 | i++; | ||
173 | |||
174 | buf[i] = '\0'; | ||
175 | |||
176 | if (i > 0) | ||
177 | bb_error_msg("client: %s", buf); | ||
178 | |||
179 | i = 0; | ||
180 | } else { | ||
181 | i++; | ||
182 | } | ||
183 | |||
184 | if (r != 1) | ||
185 | break; | ||
186 | } | ||
187 | |||
188 | close(pipe_pair.rd); | ||
189 | |||
190 | wait(&r); | ||
191 | |||
192 | if (!WIFEXITED(r) || WEXITSTATUS(r) != 0) { | ||
193 | bb_error_msg("program execution failed, return value is %i", | ||
194 | WEXITSTATUS(r)); | ||
195 | return option_mask32 & FLAG_IGNORE_RETVAL ? 0 : WEXITSTATUS(r); | ||
196 | } | ||
197 | bb_error_msg("program executed successfully"); | ||
198 | return 0; | ||
199 | #endif | ||
200 | } | ||
201 | |||
202 | static int network_ioctl(int request, void* data) | ||
203 | { | ||
204 | return ioctl(ioctl_fd, request, data); | ||
205 | } | ||
206 | |||
207 | static void set_ifreq_to_ifname(struct ifreq *ifreq) | ||
208 | { | ||
209 | memset(ifreq, 0, sizeof(struct ifreq)); | ||
210 | strncpy(ifreq->ifr_name, G.iface, IFNAMSIZ); | ||
211 | } | ||
212 | |||
213 | static const char *strstatus(int status) | ||
214 | { | ||
215 | if (status == IFSTATUS_ERR) | ||
216 | return "error"; | ||
217 | return "down\0up" + (status * 5); | ||
218 | } | ||
219 | |||
220 | static void up_iface(void) | ||
221 | { | ||
222 | struct ifreq ifrequest; | ||
223 | |||
224 | if (!G.iface_exists) | ||
225 | return; | ||
226 | |||
227 | set_ifreq_to_ifname(&ifrequest); | ||
228 | if (network_ioctl(SIOCGIFFLAGS, &ifrequest) < 0) { | ||
229 | bb_perror_msg("can't %cet interface flags", 'g'); | ||
230 | G.iface_exists = 0; | ||
231 | return; | ||
232 | } | ||
233 | |||
234 | if (!(ifrequest.ifr_flags & IFF_UP)) { | ||
235 | ifrequest.ifr_flags |= IFF_UP; | ||
236 | /* Let user know we mess up with interface */ | ||
237 | bb_error_msg("upping interface"); | ||
238 | if (network_ioctl(SIOCSIFFLAGS, &ifrequest) < 0) | ||
239 | bb_perror_msg_and_die("can't %cet interface flags", 's'); | ||
240 | } | ||
241 | |||
242 | #if 0 /* why do we mess with IP addr? It's not our business */ | ||
243 | if (network_ioctl(SIOCGIFADDR, &ifrequest) < 0) { | ||
244 | bb_error_msg("can't get interface address"); | ||
245 | } else if (ifrequest.ifr_addr.sa_family != AF_INET) { | ||
246 | bb_perror_msg("The interface is not IP-based"); | ||
247 | } else { | ||
248 | ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY; | ||
249 | if (network_ioctl(SIOCSIFADDR, &ifrequest) < 0) | ||
250 | bb_perror_msg("can't set interface address"); | ||
251 | } | ||
252 | if (network_ioctl(SIOCGIFFLAGS, &ifrequest) < 0) { | ||
253 | bb_perror_msg("can't get interface flags"); | ||
254 | return; | ||
255 | } | ||
256 | #endif | ||
257 | } | ||
258 | |||
259 | static void maybe_up_new_iface(void) | ||
260 | { | ||
261 | if (!(option_mask32 & FLAG_NO_AUTO)) | ||
262 | up_iface(); | ||
263 | |||
264 | #if 0 /* bloat */ | ||
265 | struct ifreq ifrequest; | ||
266 | struct ethtool_drvinfo driver_info; | ||
267 | |||
268 | set_ifreq_to_ifname(&ifrequest); | ||
269 | driver_info.cmd = ETHTOOL_GDRVINFO; | ||
270 | ifrequest.ifr_data = &driver_info; | ||
271 | if (network_ioctl(SIOCETHTOOL, &ifrequest) == 0) { | ||
272 | char buf[sizeof("/xx:xx:xx:xx:xx:xx")]; | ||
273 | |||
274 | /* Get MAC */ | ||
275 | buf[0] = '\0'; | ||
276 | set_ifreq_to_ifname(&ifrequest); | ||
277 | if (network_ioctl(SIOCGIFHWADDR, &ifrequest) == 0) { | ||
278 | sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X", | ||
279 | (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]), | ||
280 | (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]), | ||
281 | (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]), | ||
282 | (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]), | ||
283 | (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]), | ||
284 | (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5])); | ||
285 | } | ||
286 | |||
287 | bb_error_msg("Using interface %s%s with driver<%s> (version: %s)", | ||
288 | G.iface, buf, driver_info.driver, driver_info.version); | ||
289 | } | ||
290 | #endif | ||
291 | |||
292 | G.cached_detect_link_func = NULL; | ||
293 | } | ||
294 | |||
295 | static smallint detect_link_mii(void) | ||
296 | { | ||
297 | struct ifreq ifreq; | ||
298 | |||
299 | set_ifreq_to_ifname(&ifreq); | ||
300 | |||
301 | if (network_ioctl(SIOCGMIIPHY, &ifreq) < 0) { | ||
302 | bb_perror_msg("SIOCGMIIPHY failed"); | ||
303 | return IFSTATUS_ERR; | ||
304 | } | ||
305 | |||
306 | ((unsigned short*)&ifreq.ifr_data)[1] = 1; | ||
307 | |||
308 | if (network_ioctl(SIOCGMIIREG, &ifreq) < 0) { | ||
309 | bb_perror_msg("SIOCGMIIREG failed"); | ||
310 | return IFSTATUS_ERR; | ||
311 | } | ||
312 | |||
313 | return (((unsigned short*)&ifreq.ifr_data)[3] & 0x0004) ? | ||
314 | IFSTATUS_UP : IFSTATUS_DOWN; | ||
315 | } | ||
316 | |||
317 | static smallint detect_link_priv(void) | ||
318 | { | ||
319 | struct ifreq ifreq; | ||
320 | |||
321 | set_ifreq_to_ifname(&ifreq); | ||
322 | |||
323 | if (network_ioctl(SIOCDEVPRIVATE, &ifreq) < 0) { | ||
324 | bb_perror_msg("SIOCDEVPRIVATE failed"); | ||
325 | return IFSTATUS_ERR; | ||
326 | } | ||
327 | |||
328 | ((unsigned short*) &ifreq.ifr_data)[1] = 1; | ||
329 | |||
330 | if (network_ioctl(SIOCDEVPRIVATE+1, &ifreq) < 0) { | ||
331 | bb_perror_msg("SIOCDEVPRIVATE+1 failed"); | ||
332 | return IFSTATUS_ERR; | ||
333 | } | ||
334 | |||
335 | return (((unsigned short*)&ifreq.ifr_data)[3] & 0x0004) ? | ||
336 | IFSTATUS_UP : IFSTATUS_DOWN; | ||
337 | } | ||
338 | |||
339 | static smallint detect_link_ethtool(void) | ||
340 | { | ||
341 | struct ifreq ifreq; | ||
342 | struct ethtool_value edata; | ||
343 | |||
344 | set_ifreq_to_ifname(&ifreq); | ||
345 | |||
346 | edata.cmd = ETHTOOL_GLINK; | ||
347 | ifreq.ifr_data = &edata; | ||
348 | |||
349 | if (network_ioctl(SIOCETHTOOL, &ifreq) < 0) { | ||
350 | bb_perror_msg("ETHTOOL_GLINK failed"); | ||
351 | return IFSTATUS_ERR; | ||
352 | } | ||
353 | |||
354 | return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN; | ||
355 | } | ||
356 | |||
357 | static smallint detect_link_iff(void) | ||
358 | { | ||
359 | struct ifreq ifreq; | ||
360 | |||
361 | set_ifreq_to_ifname(&ifreq); | ||
362 | |||
363 | if (network_ioctl(SIOCGIFFLAGS, &ifreq) < 0) { | ||
364 | bb_perror_msg("SIOCGIFFLAGS failed"); | ||
365 | return IFSTATUS_ERR; | ||
366 | } | ||
367 | |||
368 | return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN; | ||
369 | } | ||
370 | |||
371 | static smallint detect_link_wlan(void) | ||
372 | { | ||
373 | struct iwreq iwrequest; | ||
374 | uint8_t mac[ETH_ALEN]; | ||
375 | |||
376 | memset(&iwrequest, 0, sizeof(struct iwreq)); | ||
377 | strncpy(iwrequest.ifr_ifrn.ifrn_name, G.iface, IFNAMSIZ); | ||
378 | |||
379 | if (network_ioctl(SIOCGIWAP, &iwrequest) < 0) { | ||
380 | bb_perror_msg("SIOCGIWAP failed"); | ||
381 | return IFSTATUS_ERR; | ||
382 | } | ||
383 | |||
384 | memcpy(mac, &(iwrequest.u.ap_addr.sa_data), ETH_ALEN); | ||
385 | |||
386 | if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) { | ||
387 | for (int i = 1; i < ETH_ALEN; ++i) { | ||
388 | if (mac[i] != mac[0]) | ||
389 | return IFSTATUS_UP; | ||
390 | } | ||
391 | return IFSTATUS_DOWN; | ||
392 | } | ||
393 | |||
394 | return IFSTATUS_UP; | ||
395 | } | ||
396 | |||
397 | static smallint detect_link_auto(void) | ||
398 | { | ||
399 | const char *method; | ||
400 | smallint iface_status; | ||
401 | smallint sv_logmode; | ||
402 | |||
403 | if (G.cached_detect_link_func) { | ||
404 | iface_status = G.cached_detect_link_func(); | ||
405 | if (iface_status != IFSTATUS_ERR) | ||
406 | return iface_status; | ||
407 | } | ||
408 | |||
409 | sv_logmode = logmode; | ||
410 | logmode = LOGMODE_NONE; | ||
411 | |||
412 | iface_status = detect_link_ethtool(); | ||
413 | if (iface_status != IFSTATUS_ERR) { | ||
414 | G.cached_detect_link_func = detect_link_ethtool; | ||
415 | method = "SIOCETHTOOL"; | ||
416 | found_method: | ||
417 | logmode = sv_logmode; | ||
418 | bb_error_msg("using %s detection mode", method); | ||
419 | return iface_status; | ||
420 | } | ||
421 | |||
422 | iface_status = detect_link_mii(); | ||
423 | if (iface_status != IFSTATUS_ERR) { | ||
424 | G.cached_detect_link_func = detect_link_mii; | ||
425 | method = "SIOCGMIIPHY"; | ||
426 | goto found_method; | ||
427 | } | ||
428 | |||
429 | iface_status = detect_link_priv(); | ||
430 | if (iface_status != IFSTATUS_ERR) { | ||
431 | G.cached_detect_link_func = detect_link_priv; | ||
432 | method = "SIOCDEVPRIVATE"; | ||
433 | goto found_method; | ||
434 | } | ||
435 | |||
436 | iface_status = detect_link_wlan(); | ||
437 | if (iface_status != IFSTATUS_ERR) { | ||
438 | G.cached_detect_link_func = detect_link_wlan; | ||
439 | method = "wireless extension"; | ||
440 | goto found_method; | ||
441 | } | ||
442 | |||
443 | iface_status = detect_link_iff(); | ||
444 | if (iface_status != IFSTATUS_ERR) { | ||
445 | G.cached_detect_link_func = detect_link_iff; | ||
446 | method = "IFF_RUNNING"; | ||
447 | goto found_method; | ||
448 | } | ||
449 | |||
450 | logmode = sv_logmode; | ||
451 | return iface_status; /* IFSTATUS_ERR */ | ||
452 | } | ||
453 | |||
454 | static smallint detect_link(void) | ||
455 | { | ||
456 | smallint status; | ||
457 | |||
458 | if (!G.iface_exists) | ||
459 | return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR; | ||
460 | |||
461 | #if 0 | ||
462 | /* Why? This behavior makes it hard to temporary down the iface. | ||
463 | * It makes a bit more sense to do only in maybe_up_new_iface. | ||
464 | * OTOH, maybe detect_link_wlan needs this. Then it should be done | ||
465 | * _only_ there. | ||
466 | */ | ||
467 | if (!(option_mask32 & FLAG_NO_AUTO)) | ||
468 | up_iface(); | ||
469 | #endif | ||
470 | |||
471 | status = G.detect_link_func(); | ||
472 | if (status == IFSTATUS_ERR) { | ||
473 | if (option_mask32 & FLAG_IGNORE_FAIL) | ||
474 | status = IFSTATUS_DOWN; | ||
475 | if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE) | ||
476 | status = IFSTATUS_UP; | ||
477 | } | ||
478 | |||
479 | if (status == IFSTATUS_ERR | ||
480 | && G.detect_link_func == detect_link_auto | ||
481 | ) { | ||
482 | bb_error_msg("failed to detect link status"); | ||
483 | } | ||
484 | |||
485 | if (status != G.iface_last_status) { | ||
486 | //TODO: is it safe to repeatedly do this? | ||
487 | setenv(IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_last_status), 1); | ||
488 | setenv(IFPLUGD_ENV_CURRENT, strstatus(status), 1); | ||
489 | G.iface_last_status = status; | ||
490 | } | ||
491 | |||
492 | return status; | ||
493 | } | ||
494 | |||
495 | static NOINLINE int check_existence_through_netlink(void) | ||
496 | { | ||
497 | char replybuf[1024]; | ||
498 | |||
499 | while (1) { | ||
500 | struct nlmsghdr *mhdr; | ||
501 | ssize_t bytes; | ||
502 | |||
503 | bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT); | ||
504 | if (bytes < 0) { | ||
505 | if (errno == EAGAIN) | ||
506 | return G.iface_exists; | ||
507 | if (errno == EINTR) | ||
508 | continue; | ||
509 | |||
510 | bb_perror_msg("netlink: recv"); | ||
511 | return -1; | ||
512 | } | ||
513 | |||
514 | mhdr = (struct nlmsghdr*)replybuf; | ||
515 | while (bytes > 0) { | ||
516 | if (!NLMSG_OK(mhdr, bytes) | ||
517 | || bytes < sizeof(struct nlmsghdr) | ||
518 | || bytes < mhdr->nlmsg_len | ||
519 | ) { | ||
520 | bb_error_msg("netlink packet too small or truncated"); | ||
521 | return -1; | ||
522 | } | ||
523 | |||
524 | if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) { | ||
525 | struct rtattr *attr; | ||
526 | struct ifinfomsg *imsg; | ||
527 | int attr_len; | ||
528 | |||
529 | imsg = NLMSG_DATA(mhdr); | ||
530 | |||
531 | if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) { | ||
532 | bb_error_msg("netlink packet too small or truncated"); | ||
533 | return -1; | ||
534 | } | ||
535 | |||
536 | attr = (struct rtattr*)((char*)imsg + NLMSG_ALIGN(sizeof(struct ifinfomsg))); | ||
537 | attr_len = NLMSG_PAYLOAD(mhdr, sizeof(struct ifinfomsg)); | ||
538 | |||
539 | while (RTA_OK(attr, attr_len)) { | ||
540 | if (attr->rta_type == IFLA_IFNAME) { | ||
541 | char ifname[IFNAMSIZ + 1]; | ||
542 | int len = RTA_PAYLOAD(attr); | ||
543 | |||
544 | if (len > IFNAMSIZ) | ||
545 | len = IFNAMSIZ; | ||
546 | memcpy(ifname, RTA_DATA(attr), len); | ||
547 | if (strcmp(G.iface, ifname) == 0) { | ||
548 | G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK); | ||
549 | } | ||
550 | } | ||
551 | attr = RTA_NEXT(attr, attr_len); | ||
552 | } | ||
553 | } | ||
554 | |||
555 | mhdr = NLMSG_NEXT(mhdr, bytes); | ||
556 | } | ||
557 | } | ||
558 | |||
559 | return G.iface_exists; | ||
560 | } | ||
561 | |||
562 | static NOINLINE int netlink_open(void) | ||
563 | { | ||
564 | int fd; | ||
565 | struct sockaddr_nl addr; | ||
566 | |||
567 | fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); | ||
568 | |||
569 | memset(&addr, 0, sizeof(addr)); | ||
570 | addr.nl_family = AF_NETLINK; | ||
571 | addr.nl_groups = RTMGRP_LINK; | ||
572 | addr.nl_pid = getpid(); | ||
573 | |||
574 | xbind(fd, (struct sockaddr*)&addr, sizeof(addr)); | ||
575 | |||
576 | return fd; | ||
577 | } | ||
578 | |||
579 | static NOINLINE pid_t read_pid(const char *filename) | ||
580 | { | ||
581 | int len; | ||
582 | char buf[128]; | ||
583 | |||
584 | len = open_read_close(filename, buf, 127); | ||
585 | if (len > 0) { | ||
586 | buf[len] = '\0'; | ||
587 | /* returns ULONG_MAX on error => -1 */ | ||
588 | return bb_strtoul(buf, NULL, 10); | ||
589 | } | ||
590 | return 0; | ||
591 | } | ||
592 | |||
593 | int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
594 | int ifplugd_main(int argc UNUSED_PARAM, char **argv) | ||
595 | { | ||
596 | int iface_status; | ||
597 | int delay_time; | ||
598 | const char *iface_status_str; | ||
599 | struct pollfd netlink_pollfd[1]; | ||
600 | unsigned opts; | ||
601 | #if ENABLE_FEATURE_PIDFILE | ||
602 | char *pidfile_name; | ||
603 | pid_t pid_from_pidfile; | ||
604 | #endif | ||
605 | |||
606 | INIT_G(); | ||
607 | |||
608 | opt_complementary = "t+:u+:d+"; | ||
609 | opts = getopt32(argv, OPTION_STR, | ||
610 | &G.iface, &G.script_name, &G.poll_time, &G.delay_up, | ||
611 | &G.delay_down, &G.api_mode, &G.extra_arg); | ||
612 | |||
613 | applet_name = xasprintf("ifplugd(%s)", G.iface); | ||
614 | |||
615 | #if ENABLE_FEATURE_PIDFILE | ||
616 | pidfile_name = xasprintf(_PATH_VARRUN"ifplugd.%s.pid", G.iface); | ||
617 | pid_from_pidfile = read_pid(pidfile_name); | ||
618 | |||
619 | if (opts & FLAG_KILL) { | ||
620 | if (pid_from_pidfile > 0) | ||
621 | kill(pid_from_pidfile, SIGQUIT); | ||
622 | return EXIT_SUCCESS; | ||
623 | } | ||
624 | |||
625 | if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0) | ||
626 | bb_error_msg_and_die("daemon already running"); | ||
627 | #endif | ||
628 | |||
629 | switch (G.api_mode[0]) { | ||
630 | case API_AUTO: | ||
631 | G.detect_link_func = detect_link_auto; | ||
632 | break; | ||
633 | case API_ETHTOOL: | ||
634 | G.detect_link_func = detect_link_ethtool; | ||
635 | break; | ||
636 | case API_MII: | ||
637 | G.detect_link_func = detect_link_mii; | ||
638 | break; | ||
639 | case API_PRIVATE: | ||
640 | G.detect_link_func = detect_link_priv; | ||
641 | break; | ||
642 | case API_WLAN: | ||
643 | G.detect_link_func = detect_link_wlan; | ||
644 | break; | ||
645 | case API_IFF: | ||
646 | G.detect_link_func = detect_link_iff; | ||
647 | break; | ||
648 | default: | ||
649 | bb_error_msg_and_die("unknown API mode '%s'", G.api_mode); | ||
650 | } | ||
651 | |||
652 | if (!(opts & FLAG_NO_DAEMON)) | ||
653 | bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv); | ||
654 | |||
655 | xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd); | ||
656 | if (opts & FLAG_MONITOR) { | ||
657 | xmove_fd(netlink_open(), netlink_fd); | ||
658 | } | ||
659 | |||
660 | write_pidfile(pidfile_name); | ||
661 | |||
662 | /* this can't be moved before socket creation */ | ||
663 | if (!(opts & FLAG_NO_SYSLOG)) { | ||
664 | openlog(applet_name, 0, LOG_DAEMON); | ||
665 | logmode |= LOGMODE_SYSLOG; | ||
666 | } | ||
667 | |||
668 | bb_signals(0 | ||
669 | | (1 << SIGINT ) | ||
670 | | (1 << SIGTERM) | ||
671 | | (1 << SIGQUIT) | ||
672 | | (1 << SIGHUP ) /* why we ignore it? */ | ||
673 | /* | (1 << SIGCHLD) - run_script does not use it anymore */ | ||
674 | , record_signo); | ||
675 | |||
676 | bb_error_msg("started: %s", bb_banner); | ||
677 | |||
678 | if (opts & FLAG_MONITOR) { | ||
679 | struct ifreq ifrequest; | ||
680 | set_ifreq_to_ifname(&ifrequest); | ||
681 | G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest) == 0); | ||
682 | } | ||
683 | |||
684 | if (G.iface_exists) | ||
685 | maybe_up_new_iface(); | ||
686 | |||
687 | iface_status = detect_link(); | ||
688 | if (iface_status == IFSTATUS_ERR) | ||
689 | goto exiting; | ||
690 | iface_status_str = strstatus(iface_status); | ||
691 | |||
692 | if (opts & FLAG_MONITOR) { | ||
693 | bb_error_msg("interface %s", | ||
694 | G.iface_exists ? "exists" | ||
695 | : "doesn't exist, waiting"); | ||
696 | } | ||
697 | /* else we assume it always exists, but don't mislead user | ||
698 | * by potentially lying that it really exists */ | ||
699 | |||
700 | if (G.iface_exists) { | ||
701 | bb_error_msg("link is %s", iface_status_str); | ||
702 | } | ||
703 | |||
704 | if ((!(opts & FLAG_NO_STARTUP) | ||
705 | && iface_status == IFSTATUS_UP | ||
706 | ) | ||
707 | || (opts & FLAG_INITIAL_DOWN) | ||
708 | ) { | ||
709 | if (run_script(iface_status_str) != 0) | ||
710 | goto exiting; | ||
711 | } | ||
712 | |||
713 | /* Main loop */ | ||
714 | netlink_pollfd[0].fd = netlink_fd; | ||
715 | netlink_pollfd[0].events = POLLIN; | ||
716 | delay_time = 0; | ||
717 | while (1) { | ||
718 | int iface_status_old; | ||
719 | int iface_exists_old; | ||
720 | |||
721 | switch (bb_got_signal) { | ||
722 | case SIGINT: | ||
723 | case SIGTERM: | ||
724 | bb_got_signal = 0; | ||
725 | goto cleanup; | ||
726 | case SIGQUIT: | ||
727 | bb_got_signal = 0; | ||
728 | goto exiting; | ||
729 | default: | ||
730 | bb_got_signal = 0; | ||
731 | break; | ||
732 | } | ||
733 | |||
734 | if (poll(netlink_pollfd, | ||
735 | (opts & FLAG_MONITOR) ? 1 : 0, | ||
736 | G.poll_time * 1000 | ||
737 | ) < 0 | ||
738 | ) { | ||
739 | if (errno == EINTR) | ||
740 | continue; | ||
741 | bb_perror_msg("poll"); | ||
742 | goto exiting; | ||
743 | } | ||
744 | |||
745 | iface_status_old = iface_status; | ||
746 | iface_exists_old = G.iface_exists; | ||
747 | |||
748 | if ((opts & FLAG_MONITOR) | ||
749 | && (netlink_pollfd[0].revents & POLLIN) | ||
750 | ) { | ||
751 | G.iface_exists = check_existence_through_netlink(); | ||
752 | if (G.iface_exists < 0) /* error */ | ||
753 | goto exiting; | ||
754 | if (iface_exists_old != G.iface_exists) { | ||
755 | bb_error_msg("interface %sappeared", | ||
756 | G.iface_exists ? "" : "dis"); | ||
757 | if (G.iface_exists) | ||
758 | maybe_up_new_iface(); | ||
759 | } | ||
760 | } | ||
761 | |||
762 | /* note: if !G.iface_exists, returns DOWN */ | ||
763 | iface_status = detect_link(); | ||
764 | if (iface_status == IFSTATUS_ERR) { | ||
765 | if (!(opts & FLAG_MONITOR)) | ||
766 | goto exiting; | ||
767 | iface_status = IFSTATUS_DOWN; | ||
768 | } | ||
769 | iface_status_str = strstatus(iface_status); | ||
770 | |||
771 | if (iface_status_old != iface_status) { | ||
772 | bb_error_msg("link is %s", iface_status_str); | ||
773 | |||
774 | if (delay_time) { | ||
775 | /* link restored its old status before | ||
776 | * we run script. don't run the script: */ | ||
777 | delay_time = 0; | ||
778 | } else { | ||
779 | delay_time = monotonic_sec(); | ||
780 | if (iface_status == IFSTATUS_UP) | ||
781 | delay_time += G.delay_up; | ||
782 | if (iface_status == IFSTATUS_DOWN) | ||
783 | delay_time += G.delay_down; | ||
784 | if (delay_time == 0) | ||
785 | delay_time++; | ||
786 | } | ||
787 | } | ||
788 | |||
789 | if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) { | ||
790 | delay_time = 0; | ||
791 | if (run_script(iface_status_str) != 0) | ||
792 | goto exiting; | ||
793 | } | ||
794 | } /* while (1) */ | ||
795 | |||
796 | cleanup: | ||
797 | if (!(opts & FLAG_NO_SHUTDOWN) | ||
798 | && (iface_status == IFSTATUS_UP | ||
799 | || (iface_status == IFSTATUS_DOWN && delay_time) | ||
800 | ) | ||
801 | ) { | ||
802 | setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1); | ||
803 | setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1); | ||
804 | run_script("down\0up"); /* reusing string */ | ||
805 | } | ||
806 | |||
807 | exiting: | ||
808 | remove_pidfile(pidfile_name); | ||
809 | bb_error_msg_and_die("exiting"); | ||
810 | } | ||