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