Convert all networking/* applets to "new style" applet definitions
[oweals/busybox.git] / networking / ifplugd.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * ifplugd for busybox, based on ifplugd 0.28 (written by Lennart Poettering).
4  *
5  * Copyright (C) 2009 Maksym Kryzhanovskyy <xmaks@email.cz>
6  *
7  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8  */
9 //config:config IFPLUGD
10 //config:       bool "ifplugd"
11 //config:       default y
12 //config:       select PLATFORM_LINUX
13 //config:       help
14 //config:         Network interface plug detection daemon.
15
16 //applet:IF_IFPLUGD(APPLET(ifplugd, BB_DIR_USR_SBIN, BB_SUID_DROP))
17
18 //kbuild:lib-$(CONFIG_IFPLUGD) += ifplugd.o
19
20 //usage:#define ifplugd_trivial_usage
21 //usage:       "[OPTIONS]"
22 //usage:#define ifplugd_full_usage "\n\n"
23 //usage:       "Network interface plug detection daemon\n"
24 //usage:     "\n        -n              Don't daemonize"
25 //usage:     "\n        -s              Don't log to syslog"
26 //usage:     "\n        -i IFACE        Interface"
27 //usage:     "\n        -f/-F           Treat link detection error as link down/link up"
28 //usage:     "\n                        (otherwise exit on error)"
29 //usage:     "\n        -a              Don't up interface at each link probe"
30 //usage:     "\n        -M              Monitor creation/destruction of interface"
31 //usage:     "\n                        (otherwise it must exist)"
32 //usage:     "\n        -r PROG         Script to run"
33 //usage:     "\n        -x ARG          Extra argument for script"
34 //usage:     "\n        -I              Don't exit on nonzero exit code from script"
35 //usage:     "\n        -p              Don't run \"up\" script on startup"
36 //usage:     "\n        -q              Don't run \"down\" script on exit"
37 //usage:     "\n        -l              Always run script on startup"
38 //usage:     "\n        -t SECS         Poll time in seconds"
39 //usage:     "\n        -u SECS         Delay before running script after link up"
40 //usage:     "\n        -d SECS         Delay after link down"
41 //usage:     "\n        -m MODE         API mode (mii, priv, ethtool, wlan, iff, auto)"
42 //usage:     "\n        -k              Kill running daemon"
43
44 #include "libbb.h"
45
46 #include "fix_u32.h"
47 #include <linux/if.h>
48 #include <linux/mii.h>
49 #include <linux/ethtool.h>
50 #ifdef HAVE_NET_ETHERNET_H
51 /* musl breakage:
52  * In file included from /usr/include/net/ethernet.h:10,
53  *                  from networking/ifplugd.c:41:
54  * /usr/include/netinet/if_ether.h:96: error: redefinition of 'struct ethhdr'
55  *
56  * Build succeeds without it on musl. Commented it out.
57  * If on your system you need it, consider removing <linux/ethtool.h>
58  * and copy-pasting its definitions here (<linux/ethtool.h> is what pulls in
59  * conflicting definition of struct ethhdr on musl).
60  */
61 /* # include <net/ethernet.h> */
62 #endif
63 #include <linux/netlink.h>
64 #include <linux/rtnetlink.h>
65 #include <linux/sockios.h>
66 #include <syslog.h>
67
68 #define __user
69 #include <linux/wireless.h>
70
71 #ifndef ETH_ALEN
72 # define ETH_ALEN  6
73 #endif
74
75 /*
76 From initial port to busybox, removed most of the redundancy by
77 converting implementation of a polymorphic interface to the strict
78 functional style. The main role is run a script when link state
79 changed, other activities like audio signal or detailed reports
80 are on the script itself.
81
82 One questionable point of the design is netlink usage:
83
84 We have 1 second timeout by default to poll the link status,
85 it is short enough so that there are no real benefits in
86 using netlink to get "instantaneous" interface creation/deletion
87 notifications. We can check for interface existence by just
88 doing some fast ioctl using its name.
89
90 Netlink code then can be just dropped (1k or more?)
91 */
92
93
94 #define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS"
95 #define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT"
96
97 enum {
98         FLAG_NO_AUTO                    = 1 <<  0, // -a, Do not enable interface automatically
99         FLAG_NO_DAEMON                  = 1 <<  1, // -n, Do not daemonize
100         FLAG_NO_SYSLOG                  = 1 <<  2, // -s, Do not use syslog, use stderr instead
101         FLAG_IGNORE_FAIL                = 1 <<  3, // -f, Ignore detection failure, retry instead (failure is treated as DOWN)
102         FLAG_IGNORE_FAIL_POSITIVE       = 1 <<  4, // -F, Ignore detection failure, retry instead (failure is treated as UP)
103         FLAG_IFACE                      = 1 <<  5, // -i, Specify ethernet interface
104         FLAG_RUN                        = 1 <<  6, // -r, Specify program to execute
105         FLAG_IGNORE_RETVAL              = 1 <<  7, // -I, Don't exit on nonzero return value of program executed
106         FLAG_POLL_TIME                  = 1 <<  8, // -t, Specify poll time in seconds
107         FLAG_DELAY_UP                   = 1 <<  9, // -u, Specify delay for configuring interface
108         FLAG_DELAY_DOWN                 = 1 << 10, // -d, Specify delay for deconfiguring interface
109         FLAG_API_MODE                   = 1 << 11, // -m, Force API mode (mii, priv, ethtool, wlan, auto)
110         FLAG_NO_STARTUP                 = 1 << 12, // -p, Don't run script on daemon startup
111         FLAG_NO_SHUTDOWN                = 1 << 13, // -q, Don't run script on daemon quit
112         FLAG_INITIAL_DOWN               = 1 << 14, // -l, Run "down" script on startup if no cable is detected
113         FLAG_EXTRA_ARG                  = 1 << 15, // -x, Specify an extra argument for action script
114         FLAG_MONITOR                    = 1 << 16, // -M, Use interface monitoring
115 #if ENABLE_FEATURE_PIDFILE
116         FLAG_KILL                       = 1 << 17, // -k, Kill a running daemon
117 #endif
118 };
119 #if ENABLE_FEATURE_PIDFILE
120 # define OPTION_STR "+ansfFi:r:It:+u:+d:+m:pqlx:Mk"
121 #else
122 # define OPTION_STR "+ansfFi:r:It:+u:+d:+m:pqlx:M"
123 #endif
124
125 enum { // interface status
126         IFSTATUS_ERR = -1,
127         IFSTATUS_DOWN = 0,
128         IFSTATUS_UP = 1,
129 };
130
131 enum { // constant fds
132         ioctl_fd = 3,
133         netlink_fd = 4,
134 };
135
136 struct globals {
137         smallint iface_last_status;
138         smallint iface_prev_status;
139         smallint iface_exists;
140         smallint api_method_num;
141
142         /* Used in getopt32, must have sizeof == sizeof(int) */
143         unsigned poll_time;
144         unsigned delay_up;
145         unsigned delay_down;
146
147         const char *iface;
148         const char *api_mode;
149         const char *script_name;
150         const char *extra_arg;
151 };
152 #define G (*ptr_to_globals)
153 #define INIT_G() do { \
154         SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
155         G.iface_last_status = -1; \
156         G.iface_exists   = 1; \
157         G.poll_time      = 1; \
158         G.delay_down     = 5; \
159         G.iface          = "eth0"; \
160         G.api_mode       = "a"; \
161         G.script_name    = "/etc/ifplugd/ifplugd.action"; \
162 } while (0)
163
164
165 /* Utility routines */
166
167 static void set_ifreq_to_ifname(struct ifreq *ifreq)
168 {
169         memset(ifreq, 0, sizeof(struct ifreq));
170         strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
171 }
172
173 static int network_ioctl(int request, void* data, const char *errmsg)
174 {
175         int r = ioctl(ioctl_fd, request, data);
176         if (r < 0 && errmsg)
177                 bb_perror_msg("%s failed", errmsg);
178         return r;
179 }
180
181 /* Link detection routines and table */
182
183 static smallint detect_link_mii(void)
184 {
185         /* char buffer instead of bona-fide struct avoids aliasing warning */
186         char buf[sizeof(struct ifreq)];
187         struct ifreq *const ifreq = (void *)buf;
188
189         struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
190
191         set_ifreq_to_ifname(ifreq);
192
193         if (network_ioctl(SIOCGMIIPHY, ifreq, "SIOCGMIIPHY") < 0) {
194                 return IFSTATUS_ERR;
195         }
196
197         mii->reg_num = 1;
198
199         if (network_ioctl(SIOCGMIIREG, ifreq, "SIOCGMIIREG") < 0) {
200                 return IFSTATUS_ERR;
201         }
202
203         return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
204 }
205
206 static smallint detect_link_priv(void)
207 {
208         /* char buffer instead of bona-fide struct avoids aliasing warning */
209         char buf[sizeof(struct ifreq)];
210         struct ifreq *const ifreq = (void *)buf;
211
212         struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
213
214         set_ifreq_to_ifname(ifreq);
215
216         if (network_ioctl(SIOCDEVPRIVATE, ifreq, "SIOCDEVPRIVATE") < 0) {
217                 return IFSTATUS_ERR;
218         }
219
220         mii->reg_num = 1;
221
222         if (network_ioctl(SIOCDEVPRIVATE+1, ifreq, "SIOCDEVPRIVATE+1") < 0) {
223                 return IFSTATUS_ERR;
224         }
225
226         return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
227 }
228
229 static smallint detect_link_ethtool(void)
230 {
231         struct ifreq ifreq;
232         struct ethtool_value edata;
233
234         set_ifreq_to_ifname(&ifreq);
235
236         edata.cmd = ETHTOOL_GLINK;
237         ifreq.ifr_data = (void*) &edata;
238
239         if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK") < 0) {
240                 return IFSTATUS_ERR;
241         }
242
243         return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
244 }
245
246 static smallint detect_link_iff(void)
247 {
248         struct ifreq ifreq;
249
250         set_ifreq_to_ifname(&ifreq);
251
252         if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS") < 0) {
253                 return IFSTATUS_ERR;
254         }
255
256         /* If IFF_UP is not set (interface is down), IFF_RUNNING is never set
257          * regardless of link status. Simply continue to report last status -
258          * no point in reporting spurious link downs if interface is disabled
259          * by admin. When/if it will be brought up,
260          * we'll report real link status.
261          */
262         if (!(ifreq.ifr_flags & IFF_UP) && G.iface_last_status != IFSTATUS_ERR)
263                 return G.iface_last_status;
264
265         return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
266 }
267
268 static smallint detect_link_wlan(void)
269 {
270         int i;
271         struct iwreq iwrequest;
272         uint8_t mac[ETH_ALEN];
273
274         memset(&iwrequest, 0, sizeof(iwrequest));
275         strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
276
277         if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP") < 0) {
278                 return IFSTATUS_ERR;
279         }
280
281         memcpy(mac, &iwrequest.u.ap_addr.sa_data, ETH_ALEN);
282
283         if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
284                 for (i = 1; i < ETH_ALEN; ++i) {
285                         if (mac[i] != mac[0])
286                                 return IFSTATUS_UP;
287                 }
288                 return IFSTATUS_DOWN;
289         }
290
291         return IFSTATUS_UP;
292 }
293
294 enum { // api mode
295         API_ETHTOOL, // 'e'
296         API_MII,     // 'm'
297         API_PRIVATE, // 'p'
298         API_WLAN,    // 'w'
299         API_IFF,     // 'i'
300         API_AUTO,    // 'a'
301 };
302
303 static const char api_modes[] ALIGN1 = "empwia";
304
305 static const struct {
306         const char *name;
307         smallint (*func)(void);
308 } method_table[] = {
309         { "SIOCETHTOOL"       , &detect_link_ethtool },
310         { "SIOCGMIIPHY"       , &detect_link_mii     },
311         { "SIOCDEVPRIVATE"    , &detect_link_priv    },
312         { "wireless extension", &detect_link_wlan    },
313         { "IFF_RUNNING"       , &detect_link_iff     },
314 };
315
316 static const char *strstatus(int status)
317 {
318         if (status == IFSTATUS_ERR)
319                 return "error";
320         return "down\0up" + (status * 5);
321 }
322
323 static int run_script(const char *action)
324 {
325         char *env_PREVIOUS, *env_CURRENT;
326         char *argv[5];
327         int r;
328
329         bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
330
331         argv[0] = (char*) G.script_name;
332         argv[1] = (char*) G.iface;
333         argv[2] = (char*) action;
334         argv[3] = (char*) G.extra_arg;
335         argv[4] = NULL;
336
337         env_PREVIOUS = xasprintf("%s=%s", IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_prev_status));
338         putenv(env_PREVIOUS);
339         env_CURRENT = xasprintf("%s=%s", IFPLUGD_ENV_CURRENT, strstatus(G.iface_last_status));
340         putenv(env_CURRENT);
341
342         /* r < 0 - can't exec, 0 <= r < 0x180 - exited, >=0x180 - killed by sig (r-0x180) */
343         r = spawn_and_wait(argv);
344
345         unsetenv(IFPLUGD_ENV_PREVIOUS);
346         unsetenv(IFPLUGD_ENV_CURRENT);
347         free(env_PREVIOUS);
348         free(env_CURRENT);
349
350         bb_error_msg("exit code: %d", r & 0xff);
351         return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
352 }
353
354 static void up_iface(void)
355 {
356         struct ifreq ifrequest;
357
358         if (!G.iface_exists)
359                 return;
360
361         set_ifreq_to_ifname(&ifrequest);
362         if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "getting interface flags") < 0) {
363                 G.iface_exists = 0;
364                 return;
365         }
366
367         if (!(ifrequest.ifr_flags & IFF_UP)) {
368                 ifrequest.ifr_flags |= IFF_UP;
369                 /* Let user know we mess up with interface */
370                 bb_error_msg("upping interface");
371                 if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "setting interface flags") < 0) {
372                         if (errno != ENODEV)
373                                 xfunc_die();
374                         G.iface_exists = 0;
375                         return;
376                 }
377         }
378
379 #if 0 /* why do we mess with IP addr? It's not our business */
380         if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
381         } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
382                 bb_perror_msg("the interface is not IP-based");
383         } else {
384                 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
385                 network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
386         }
387         network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
388 #endif
389 }
390
391 static void maybe_up_new_iface(void)
392 {
393         if (!(option_mask32 & FLAG_NO_AUTO))
394                 up_iface();
395
396 #if 0 /* bloat */
397         struct ifreq ifrequest;
398         struct ethtool_drvinfo driver_info;
399
400         set_ifreq_to_ifname(&ifrequest);
401         driver_info.cmd = ETHTOOL_GDRVINFO;
402         ifrequest.ifr_data = &driver_info;
403         if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
404                 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
405
406                 /* Get MAC */
407                 buf[0] = '\0';
408                 set_ifreq_to_ifname(&ifrequest);
409                 if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
410                         sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
411                                 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
412                                 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
413                                 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
414                                 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
415                                 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
416                                 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
417                 }
418
419                 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
420                         G.iface, buf, driver_info.driver, driver_info.version);
421         }
422 #endif
423         if (G.api_mode[0] == 'a')
424                 G.api_method_num = API_AUTO;
425 }
426
427 static smallint detect_link(void)
428 {
429         smallint status;
430
431         if (!G.iface_exists)
432                 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
433
434         /* Some drivers can't detect link status when the interface is down.
435          * I imagine detect_link_iff() is the most vulnerable.
436          * That's why -a "noauto" in an option, not a hardwired behavior.
437          */
438         if (!(option_mask32 & FLAG_NO_AUTO))
439                 up_iface();
440
441         if (G.api_method_num == API_AUTO) {
442                 int i;
443                 smallint sv_logmode;
444
445                 sv_logmode = logmode;
446                 for (i = 0; i < ARRAY_SIZE(method_table); i++) {
447                         logmode = LOGMODE_NONE;
448                         status = method_table[i].func();
449                         logmode = sv_logmode;
450                         if (status != IFSTATUS_ERR) {
451                                 G.api_method_num = i;
452                                 bb_error_msg("using %s detection mode", method_table[i].name);
453                                 break;
454                         }
455                 }
456         } else {
457                 status = method_table[G.api_method_num].func();
458         }
459
460         if (status == IFSTATUS_ERR) {
461                 if (option_mask32 & FLAG_IGNORE_FAIL)
462                         status = IFSTATUS_DOWN;
463                 else if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
464                         status = IFSTATUS_UP;
465                 else if (G.api_mode[0] == 'a')
466                         bb_error_msg("can't detect link status");
467         }
468
469         if (status != G.iface_last_status) {
470                 G.iface_prev_status = G.iface_last_status;
471                 G.iface_last_status = status;
472         }
473
474         return status;
475 }
476
477 static NOINLINE int check_existence_through_netlink(void)
478 {
479         int iface_len;
480         /* Buffer was 1K, but on linux-3.9.9 it was reported to be too small.
481          * netlink.h: "limit to 8K to avoid MSG_TRUNC when PAGE_SIZE is very large".
482          * Note: on error returns (-1) we exit, no need to free replybuf.
483          */
484         enum { BUF_SIZE = 8 * 1024 };
485         char *replybuf = xmalloc(BUF_SIZE);
486
487         iface_len = strlen(G.iface);
488         while (1) {
489                 struct nlmsghdr *mhdr;
490                 ssize_t bytes;
491
492                 bytes = recv(netlink_fd, replybuf, BUF_SIZE, MSG_DONTWAIT);
493                 if (bytes < 0) {
494                         if (errno == EAGAIN)
495                                 goto ret;
496                         if (errno == EINTR)
497                                 continue;
498                         bb_perror_msg("netlink: recv");
499                         return -1;
500                 }
501
502                 mhdr = (struct nlmsghdr*)replybuf;
503                 while (bytes > 0) {
504                         if (!NLMSG_OK(mhdr, bytes)) {
505                                 bb_error_msg("netlink packet too small or truncated");
506                                 return -1;
507                         }
508
509                         if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
510                                 struct rtattr *attr;
511                                 int attr_len;
512
513                                 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
514                                         bb_error_msg("netlink packet too small or truncated");
515                                         return -1;
516                                 }
517
518                                 attr = IFLA_RTA(NLMSG_DATA(mhdr));
519                                 attr_len = IFLA_PAYLOAD(mhdr);
520
521                                 while (RTA_OK(attr, attr_len)) {
522                                         if (attr->rta_type == IFLA_IFNAME) {
523                                                 int len = RTA_PAYLOAD(attr);
524                                                 if (len > IFNAMSIZ)
525                                                         len = IFNAMSIZ;
526                                                 if (iface_len <= len
527                                                  && strncmp(G.iface, RTA_DATA(attr), len) == 0
528                                                 ) {
529                                                         G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
530                                                 }
531                                         }
532                                         attr = RTA_NEXT(attr, attr_len);
533                                 }
534                         }
535
536                         mhdr = NLMSG_NEXT(mhdr, bytes);
537                 }
538         }
539
540  ret:
541         free(replybuf);
542         return G.iface_exists;
543 }
544
545 #if ENABLE_FEATURE_PIDFILE
546 static NOINLINE pid_t read_pid(const char *filename)
547 {
548         int len;
549         char buf[128];
550
551         len = open_read_close(filename, buf, 127);
552         if (len > 0) {
553                 buf[len] = '\0';
554                 /* returns ULONG_MAX on error => -1 */
555                 return bb_strtoul(buf, NULL, 10);
556         }
557         return 0;
558 }
559 #endif
560
561 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
562 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
563 {
564         int iface_status;
565         int delay_time;
566         const char *iface_status_str;
567         struct pollfd netlink_pollfd[1];
568         unsigned opts;
569         const char *api_mode_found;
570 #if ENABLE_FEATURE_PIDFILE
571         char *pidfile_name;
572         pid_t pid_from_pidfile;
573 #endif
574
575         INIT_G();
576
577         opts = getopt32(argv, OPTION_STR,
578                 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
579                 &G.delay_down, &G.api_mode, &G.extra_arg);
580         G.poll_time *= 1000;
581
582         applet_name = xasprintf("ifplugd(%s)", G.iface);
583
584 #if ENABLE_FEATURE_PIDFILE
585         pidfile_name = xasprintf(CONFIG_PID_FILE_PATH "/ifplugd.%s.pid", G.iface);
586         pid_from_pidfile = read_pid(pidfile_name);
587
588         if (opts & FLAG_KILL) {
589                 if (pid_from_pidfile > 0)
590                         /* Upstream tool use SIGINT for -k */
591                         kill(pid_from_pidfile, SIGINT);
592                 return EXIT_SUCCESS;
593         }
594
595         if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
596                 bb_error_msg_and_die("daemon already running");
597 #endif
598
599         api_mode_found = strchr(api_modes, G.api_mode[0]);
600         if (!api_mode_found)
601                 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
602         G.api_method_num = api_mode_found - api_modes;
603
604         if (!(opts & FLAG_NO_DAEMON))
605                 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
606
607         xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
608         if (opts & FLAG_MONITOR) {
609                 struct sockaddr_nl addr;
610                 int fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
611
612                 memset(&addr, 0, sizeof(addr));
613                 addr.nl_family = AF_NETLINK;
614                 addr.nl_groups = RTMGRP_LINK;
615                 addr.nl_pid = getpid();
616
617                 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
618                 xmove_fd(fd, netlink_fd);
619         }
620
621         write_pidfile(pidfile_name);
622
623         /* this can't be moved before socket creation */
624         if (!(opts & FLAG_NO_SYSLOG)) {
625                 openlog(applet_name, 0, LOG_DAEMON);
626                 logmode |= LOGMODE_SYSLOG;
627         }
628
629         bb_signals(0
630                 | (1 << SIGINT )
631                 | (1 << SIGTERM)
632                 | (1 << SIGQUIT)
633                 | (1 << SIGHUP ) /* why we ignore it? */
634                 /* | (1 << SIGCHLD) - run_script does not use it anymore */
635                 , record_signo);
636
637         bb_error_msg("started: %s", bb_banner);
638
639         if (opts & FLAG_MONITOR) {
640                 struct ifreq ifrequest;
641                 set_ifreq_to_ifname(&ifrequest);
642                 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
643         }
644
645         if (G.iface_exists)
646                 maybe_up_new_iface();
647
648         iface_status = detect_link();
649         if (iface_status == IFSTATUS_ERR)
650                 goto exiting;
651         iface_status_str = strstatus(iface_status);
652
653         if (opts & FLAG_MONITOR) {
654                 bb_error_msg("interface %s",
655                         G.iface_exists ? "exists"
656                         : "doesn't exist, waiting");
657         }
658         /* else we assume it always exists, but don't mislead user
659          * by potentially lying that it really exists */
660
661         if (G.iface_exists) {
662                 bb_error_msg("link is %s", iface_status_str);
663         }
664
665         if ((!(opts & FLAG_NO_STARTUP)
666              && iface_status == IFSTATUS_UP
667             )
668          || (opts & FLAG_INITIAL_DOWN)
669         ) {
670                 if (run_script(iface_status_str) != 0)
671                         goto exiting;
672         }
673
674         /* Main loop */
675         netlink_pollfd[0].fd = netlink_fd;
676         netlink_pollfd[0].events = POLLIN;
677         delay_time = 0;
678         while (1) {
679                 int iface_status_old;
680
681                 switch (bb_got_signal) {
682                 case SIGINT:
683                 case SIGTERM:
684                         bb_got_signal = 0;
685                         goto cleanup;
686                 case SIGQUIT:
687                         bb_got_signal = 0;
688                         goto exiting;
689                 default:
690                         bb_got_signal = 0;
691                         break;
692                 }
693
694                 if (poll(netlink_pollfd,
695                                 (opts & FLAG_MONITOR) ? 1 : 0,
696                                 G.poll_time
697                         ) < 0
698                 ) {
699                         if (errno == EINTR)
700                                 continue;
701                         bb_perror_msg("poll");
702                         goto exiting;
703                 }
704
705                 if ((opts & FLAG_MONITOR)
706                  && (netlink_pollfd[0].revents & POLLIN)
707                 ) {
708                         int iface_exists_old;
709
710                         iface_exists_old = G.iface_exists;
711                         G.iface_exists = check_existence_through_netlink();
712                         if (G.iface_exists < 0) /* error */
713                                 goto exiting;
714                         if (iface_exists_old != G.iface_exists) {
715                                 bb_error_msg("interface %sappeared",
716                                                 G.iface_exists ? "" : "dis");
717                                 if (G.iface_exists)
718                                         maybe_up_new_iface();
719                         }
720                 }
721
722                 /* note: if !G.iface_exists, returns DOWN */
723                 iface_status_old = iface_status;
724                 iface_status = detect_link();
725                 if (iface_status == IFSTATUS_ERR) {
726                         if (!(opts & FLAG_MONITOR))
727                                 goto exiting;
728                         iface_status = IFSTATUS_DOWN;
729                 }
730                 iface_status_str = strstatus(iface_status);
731
732                 if (iface_status_old != iface_status) {
733                         bb_error_msg("link is %s", iface_status_str);
734
735                         if (delay_time) {
736                                 /* link restored its old status before
737                                  * we ran script. don't run the script: */
738                                 delay_time = 0;
739                         } else {
740                                 delay_time = monotonic_sec();
741                                 if (iface_status == IFSTATUS_UP)
742                                         delay_time += G.delay_up;
743                                 if (iface_status == IFSTATUS_DOWN)
744                                         delay_time += G.delay_down;
745 #if 0  /* if you are back in 1970... */
746                                 if (delay_time == 0) {
747                                         sleep(1);
748                                         delay_time = 1;
749                                 }
750 #endif
751                         }
752                 }
753
754                 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
755                         if (run_script(iface_status_str) != 0)
756                                 goto exiting;
757                         delay_time = 0;
758                 }
759         } /* while (1) */
760
761  cleanup:
762         if (!(opts & FLAG_NO_SHUTDOWN)
763          && (iface_status == IFSTATUS_UP
764              || (iface_status == IFSTATUS_DOWN && delay_time)
765             )
766         ) {
767                 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
768                 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
769                 run_script("down\0up"); /* reusing string */
770         }
771
772  exiting:
773         remove_pidfile(pidfile_name);
774         bb_error_msg_and_die("exiting");
775 }