wget: add support for https using "openssl s_client" as a helper
[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 \"up\" script on startup"
26 //usage:     "\n        -q              Don't run \"down\" script on exit"
27 //usage:     "\n        -l              Always run script on startup"
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 #ifdef HAVE_NET_ETHERNET_H
41 # include <net/ethernet.h>
42 #endif
43 #include <linux/netlink.h>
44 #include <linux/rtnetlink.h>
45 #include <linux/sockios.h>
46 #include <syslog.h>
47
48 #define __user
49 #include <linux/wireless.h>
50
51 /*
52 From initial port to busybox, removed most of the redundancy by
53 converting implementation of a polymorphic interface to the strict
54 functional style. The main role is run a script when link state
55 changed, other activities like audio signal or detailed reports
56 are on the script itself.
57
58 One questionable point of the design is netlink usage:
59
60 We have 1 second timeout by default to poll the link status,
61 it is short enough so that there are no real benefits in
62 using netlink to get "instantaneous" interface creation/deletion
63 notifications. We can check for interface existence by just
64 doing some fast ioctl using its name.
65
66 Netlink code then can be just dropped (1k or more?)
67 */
68
69
70 #define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS"
71 #define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT"
72
73 enum {
74         FLAG_NO_AUTO                    = 1 <<  0, // -a, Do not enable interface automatically
75         FLAG_NO_DAEMON                  = 1 <<  1, // -n, Do not daemonize
76         FLAG_NO_SYSLOG                  = 1 <<  2, // -s, Do not use syslog, use stderr instead
77         FLAG_IGNORE_FAIL                = 1 <<  3, // -f, Ignore detection failure, retry instead (failure is treated as DOWN)
78         FLAG_IGNORE_FAIL_POSITIVE       = 1 <<  4, // -F, Ignore detection failure, retry instead (failure is treated as UP)
79         FLAG_IFACE                      = 1 <<  5, // -i, Specify ethernet interface
80         FLAG_RUN                        = 1 <<  6, // -r, Specify program to execute
81         FLAG_IGNORE_RETVAL              = 1 <<  7, // -I, Don't exit on nonzero return value of program executed
82         FLAG_POLL_TIME                  = 1 <<  8, // -t, Specify poll time in seconds
83         FLAG_DELAY_UP                   = 1 <<  9, // -u, Specify delay for configuring interface
84         FLAG_DELAY_DOWN                 = 1 << 10, // -d, Specify delay for deconfiguring interface
85         FLAG_API_MODE                   = 1 << 11, // -m, Force API mode (mii, priv, ethtool, wlan, auto)
86         FLAG_NO_STARTUP                 = 1 << 12, // -p, Don't run script on daemon startup
87         FLAG_NO_SHUTDOWN                = 1 << 13, // -q, Don't run script on daemon quit
88         FLAG_INITIAL_DOWN               = 1 << 14, // -l, Run "down" script on startup if no cable is detected
89         FLAG_EXTRA_ARG                  = 1 << 15, // -x, Specify an extra argument for action script
90         FLAG_MONITOR                    = 1 << 16, // -M, Use interface monitoring
91 #if ENABLE_FEATURE_PIDFILE
92         FLAG_KILL                       = 1 << 17, // -k, Kill a running daemon
93 #endif
94 };
95 #if ENABLE_FEATURE_PIDFILE
96 # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:Mk"
97 #else
98 # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:M"
99 #endif
100
101 enum { // interface status
102         IFSTATUS_ERR = -1,
103         IFSTATUS_DOWN = 0,
104         IFSTATUS_UP = 1,
105 };
106
107 enum { // constant fds
108         ioctl_fd = 3,
109         netlink_fd = 4,
110 };
111
112 struct globals {
113         smallint iface_last_status;
114         smallint iface_prev_status;
115         smallint iface_exists;
116         smallint api_method_num;
117
118         /* Used in getopt32, must have sizeof == sizeof(int) */
119         unsigned poll_time;
120         unsigned delay_up;
121         unsigned delay_down;
122
123         const char *iface;
124         const char *api_mode;
125         const char *script_name;
126         const char *extra_arg;
127 };
128 #define G (*ptr_to_globals)
129 #define INIT_G() do { \
130         SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
131         G.iface_last_status = -1; \
132         G.iface_exists   = 1; \
133         G.poll_time      = 1; \
134         G.delay_down     = 5; \
135         G.iface          = "eth0"; \
136         G.api_mode       = "a"; \
137         G.script_name    = "/etc/ifplugd/ifplugd.action"; \
138 } while (0)
139
140
141 /* Utility routines */
142
143 static void set_ifreq_to_ifname(struct ifreq *ifreq)
144 {
145         memset(ifreq, 0, sizeof(struct ifreq));
146         strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
147 }
148
149 static int network_ioctl(int request, void* data, const char *errmsg)
150 {
151         int r = ioctl(ioctl_fd, request, data);
152         if (r < 0 && errmsg)
153                 bb_perror_msg("%s failed", errmsg);
154         return r;
155 }
156
157 /* Link detection routines and table */
158
159 static smallint detect_link_mii(void)
160 {
161         /* char buffer instead of bona-fide struct avoids aliasing warning */
162         char buf[sizeof(struct ifreq)];
163         struct ifreq *const ifreq = (void *)buf;
164
165         struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
166
167         set_ifreq_to_ifname(ifreq);
168
169         if (network_ioctl(SIOCGMIIPHY, ifreq, "SIOCGMIIPHY") < 0) {
170                 return IFSTATUS_ERR;
171         }
172
173         mii->reg_num = 1;
174
175         if (network_ioctl(SIOCGMIIREG, ifreq, "SIOCGMIIREG") < 0) {
176                 return IFSTATUS_ERR;
177         }
178
179         return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
180 }
181
182 static smallint detect_link_priv(void)
183 {
184         /* char buffer instead of bona-fide struct avoids aliasing warning */
185         char buf[sizeof(struct ifreq)];
186         struct ifreq *const ifreq = (void *)buf;
187
188         struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
189
190         set_ifreq_to_ifname(ifreq);
191
192         if (network_ioctl(SIOCDEVPRIVATE, ifreq, "SIOCDEVPRIVATE") < 0) {
193                 return IFSTATUS_ERR;
194         }
195
196         mii->reg_num = 1;
197
198         if (network_ioctl(SIOCDEVPRIVATE+1, ifreq, "SIOCDEVPRIVATE+1") < 0) {
199                 return IFSTATUS_ERR;
200         }
201
202         return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
203 }
204
205 static smallint detect_link_ethtool(void)
206 {
207         struct ifreq ifreq;
208         struct ethtool_value edata;
209
210         set_ifreq_to_ifname(&ifreq);
211
212         edata.cmd = ETHTOOL_GLINK;
213         ifreq.ifr_data = (void*) &edata;
214
215         if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK") < 0) {
216                 return IFSTATUS_ERR;
217         }
218
219         return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
220 }
221
222 static smallint detect_link_iff(void)
223 {
224         struct ifreq ifreq;
225
226         set_ifreq_to_ifname(&ifreq);
227
228         if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS") < 0) {
229                 return IFSTATUS_ERR;
230         }
231
232         /* If IFF_UP is not set (interface is down), IFF_RUNNING is never set
233          * regardless of link status. Simply continue to report last status -
234          * no point in reporting spurious link downs if interface is disabled
235          * by admin. When/if it will be brought up,
236          * we'll report real link status.
237          */
238         if (!(ifreq.ifr_flags & IFF_UP) && G.iface_last_status != IFSTATUS_ERR)
239                 return G.iface_last_status;
240
241         return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
242 }
243
244 static smallint detect_link_wlan(void)
245 {
246         int i;
247         struct iwreq iwrequest;
248         uint8_t mac[ETH_ALEN];
249
250         memset(&iwrequest, 0, sizeof(iwrequest));
251         strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
252
253         if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP") < 0) {
254                 return IFSTATUS_ERR;
255         }
256
257         memcpy(mac, &iwrequest.u.ap_addr.sa_data, ETH_ALEN);
258
259         if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
260                 for (i = 1; i < ETH_ALEN; ++i) {
261                         if (mac[i] != mac[0])
262                                 return IFSTATUS_UP;
263                 }
264                 return IFSTATUS_DOWN;
265         }
266
267         return IFSTATUS_UP;
268 }
269
270 enum { // api mode
271         API_ETHTOOL, // 'e'
272         API_MII,     // 'm'
273         API_PRIVATE, // 'p'
274         API_WLAN,    // 'w'
275         API_IFF,     // 'i'
276         API_AUTO,    // 'a'
277 };
278
279 static const char api_modes[] ALIGN1 = "empwia";
280
281 static const struct {
282         const char *name;
283         smallint (*func)(void);
284 } method_table[] = {
285         { "SIOCETHTOOL"       , &detect_link_ethtool },
286         { "SIOCGMIIPHY"       , &detect_link_mii     },
287         { "SIOCDEVPRIVATE"    , &detect_link_priv    },
288         { "wireless extension", &detect_link_wlan    },
289         { "IFF_RUNNING"       , &detect_link_iff     },
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         /* Buffer was 1K, but on linux-3.9.9 it was reported to be too small.
453          * netlink.h: "limit to 8K to avoid MSG_TRUNC when PAGE_SIZE is very large".
454          * Note: on error returns (-1) we exit, no need to free replybuf.
455          */
456         enum { BUF_SIZE = 8 * 1024 };
457         char *replybuf = xmalloc(BUF_SIZE);
458
459         iface_len = strlen(G.iface);
460         while (1) {
461                 struct nlmsghdr *mhdr;
462                 ssize_t bytes;
463
464                 bytes = recv(netlink_fd, replybuf, BUF_SIZE, MSG_DONTWAIT);
465                 if (bytes < 0) {
466                         if (errno == EAGAIN)
467                                 goto ret;
468                         if (errno == EINTR)
469                                 continue;
470                         bb_perror_msg("netlink: recv");
471                         return -1;
472                 }
473
474                 mhdr = (struct nlmsghdr*)replybuf;
475                 while (bytes > 0) {
476                         if (!NLMSG_OK(mhdr, bytes)) {
477                                 bb_error_msg("netlink packet too small or truncated");
478                                 return -1;
479                         }
480
481                         if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
482                                 struct rtattr *attr;
483                                 int attr_len;
484
485                                 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
486                                         bb_error_msg("netlink packet too small or truncated");
487                                         return -1;
488                                 }
489
490                                 attr = IFLA_RTA(NLMSG_DATA(mhdr));
491                                 attr_len = IFLA_PAYLOAD(mhdr);
492
493                                 while (RTA_OK(attr, attr_len)) {
494                                         if (attr->rta_type == IFLA_IFNAME) {
495                                                 int len = RTA_PAYLOAD(attr);
496                                                 if (len > IFNAMSIZ)
497                                                         len = IFNAMSIZ;
498                                                 if (iface_len <= len
499                                                  && strncmp(G.iface, RTA_DATA(attr), len) == 0
500                                                 ) {
501                                                         G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
502                                                 }
503                                         }
504                                         attr = RTA_NEXT(attr, attr_len);
505                                 }
506                         }
507
508                         mhdr = NLMSG_NEXT(mhdr, bytes);
509                 }
510         }
511
512  ret:
513         free(replybuf);
514         return G.iface_exists;
515 }
516
517 #if ENABLE_FEATURE_PIDFILE
518 static NOINLINE pid_t read_pid(const char *filename)
519 {
520         int len;
521         char buf[128];
522
523         len = open_read_close(filename, buf, 127);
524         if (len > 0) {
525                 buf[len] = '\0';
526                 /* returns ULONG_MAX on error => -1 */
527                 return bb_strtoul(buf, NULL, 10);
528         }
529         return 0;
530 }
531 #endif
532
533 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
534 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
535 {
536         int iface_status;
537         int delay_time;
538         const char *iface_status_str;
539         struct pollfd netlink_pollfd[1];
540         unsigned opts;
541         const char *api_mode_found;
542 #if ENABLE_FEATURE_PIDFILE
543         char *pidfile_name;
544         pid_t pid_from_pidfile;
545 #endif
546
547         INIT_G();
548
549         opt_complementary = "t+:u+:d+";
550         opts = getopt32(argv, OPTION_STR,
551                 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
552                 &G.delay_down, &G.api_mode, &G.extra_arg);
553         G.poll_time *= 1000;
554
555         applet_name = xasprintf("ifplugd(%s)", G.iface);
556
557 #if ENABLE_FEATURE_PIDFILE
558         pidfile_name = xasprintf(CONFIG_PID_FILE_PATH "/ifplugd.%s.pid", G.iface);
559         pid_from_pidfile = read_pid(pidfile_name);
560
561         if (opts & FLAG_KILL) {
562                 if (pid_from_pidfile > 0)
563                         /* Upstream tool use SIGINT for -k */
564                         kill(pid_from_pidfile, SIGINT);
565                 return EXIT_SUCCESS;
566         }
567
568         if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
569                 bb_error_msg_and_die("daemon already running");
570 #endif
571
572         api_mode_found = strchr(api_modes, G.api_mode[0]);
573         if (!api_mode_found)
574                 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
575         G.api_method_num = api_mode_found - api_modes;
576
577         if (!(opts & FLAG_NO_DAEMON))
578                 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
579
580         xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
581         if (opts & FLAG_MONITOR) {
582                 struct sockaddr_nl addr;
583                 int fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
584
585                 memset(&addr, 0, sizeof(addr));
586                 addr.nl_family = AF_NETLINK;
587                 addr.nl_groups = RTMGRP_LINK;
588                 addr.nl_pid = getpid();
589
590                 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
591                 xmove_fd(fd, netlink_fd);
592         }
593
594         write_pidfile(pidfile_name);
595
596         /* this can't be moved before socket creation */
597         if (!(opts & FLAG_NO_SYSLOG)) {
598                 openlog(applet_name, 0, LOG_DAEMON);
599                 logmode |= LOGMODE_SYSLOG;
600         }
601
602         bb_signals(0
603                 | (1 << SIGINT )
604                 | (1 << SIGTERM)
605                 | (1 << SIGQUIT)
606                 | (1 << SIGHUP ) /* why we ignore it? */
607                 /* | (1 << SIGCHLD) - run_script does not use it anymore */
608                 , record_signo);
609
610         bb_error_msg("started: %s", bb_banner);
611
612         if (opts & FLAG_MONITOR) {
613                 struct ifreq ifrequest;
614                 set_ifreq_to_ifname(&ifrequest);
615                 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
616         }
617
618         if (G.iface_exists)
619                 maybe_up_new_iface();
620
621         iface_status = detect_link();
622         if (iface_status == IFSTATUS_ERR)
623                 goto exiting;
624         iface_status_str = strstatus(iface_status);
625
626         if (opts & FLAG_MONITOR) {
627                 bb_error_msg("interface %s",
628                         G.iface_exists ? "exists"
629                         : "doesn't exist, waiting");
630         }
631         /* else we assume it always exists, but don't mislead user
632          * by potentially lying that it really exists */
633
634         if (G.iface_exists) {
635                 bb_error_msg("link is %s", iface_status_str);
636         }
637
638         if ((!(opts & FLAG_NO_STARTUP)
639              && iface_status == IFSTATUS_UP
640             )
641          || (opts & FLAG_INITIAL_DOWN)
642         ) {
643                 if (run_script(iface_status_str) != 0)
644                         goto exiting;
645         }
646
647         /* Main loop */
648         netlink_pollfd[0].fd = netlink_fd;
649         netlink_pollfd[0].events = POLLIN;
650         delay_time = 0;
651         while (1) {
652                 int iface_status_old;
653
654                 switch (bb_got_signal) {
655                 case SIGINT:
656                 case SIGTERM:
657                         bb_got_signal = 0;
658                         goto cleanup;
659                 case SIGQUIT:
660                         bb_got_signal = 0;
661                         goto exiting;
662                 default:
663                         bb_got_signal = 0;
664                         break;
665                 }
666
667                 if (poll(netlink_pollfd,
668                                 (opts & FLAG_MONITOR) ? 1 : 0,
669                                 G.poll_time
670                         ) < 0
671                 ) {
672                         if (errno == EINTR)
673                                 continue;
674                         bb_perror_msg("poll");
675                         goto exiting;
676                 }
677
678                 if ((opts & FLAG_MONITOR)
679                  && (netlink_pollfd[0].revents & POLLIN)
680                 ) {
681                         int iface_exists_old;
682
683                         iface_exists_old = G.iface_exists;
684                         G.iface_exists = check_existence_through_netlink();
685                         if (G.iface_exists < 0) /* error */
686                                 goto exiting;
687                         if (iface_exists_old != G.iface_exists) {
688                                 bb_error_msg("interface %sappeared",
689                                                 G.iface_exists ? "" : "dis");
690                                 if (G.iface_exists)
691                                         maybe_up_new_iface();
692                         }
693                 }
694
695                 /* note: if !G.iface_exists, returns DOWN */
696                 iface_status_old = iface_status;
697                 iface_status = detect_link();
698                 if (iface_status == IFSTATUS_ERR) {
699                         if (!(opts & FLAG_MONITOR))
700                                 goto exiting;
701                         iface_status = IFSTATUS_DOWN;
702                 }
703                 iface_status_str = strstatus(iface_status);
704
705                 if (iface_status_old != iface_status) {
706                         bb_error_msg("link is %s", iface_status_str);
707
708                         if (delay_time) {
709                                 /* link restored its old status before
710                                  * we ran script. don't run the script: */
711                                 delay_time = 0;
712                         } else {
713                                 delay_time = monotonic_sec();
714                                 if (iface_status == IFSTATUS_UP)
715                                         delay_time += G.delay_up;
716                                 if (iface_status == IFSTATUS_DOWN)
717                                         delay_time += G.delay_down;
718 #if 0  /* if you are back in 1970... */
719                                 if (delay_time == 0) {
720                                         sleep(1);
721                                         delay_time = 1;
722                                 }
723 #endif
724                         }
725                 }
726
727                 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
728                         if (run_script(iface_status_str) != 0)
729                                 goto exiting;
730                         delay_time = 0;
731                 }
732         } /* while (1) */
733
734  cleanup:
735         if (!(opts & FLAG_NO_SHUTDOWN)
736          && (iface_status == IFSTATUS_UP
737              || (iface_status == IFSTATUS_DOWN && delay_time)
738             )
739         ) {
740                 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
741                 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
742                 run_script("down\0up"); /* reusing string */
743         }
744
745  exiting:
746         remove_pidfile(pidfile_name);
747         bb_error_msg_and_die("exiting");
748 }