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