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