CMake: bump the minimum required CMake version to 3.5
[project/netifd.git] / interface-event.c
1 /*
2 * netifd - network interface daemon
3 * Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2
7 * as published by the Free Software Foundation
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 */
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17
18 #include <libubox/uloop.h>
19
20 #include "netifd.h"
21 #include "interface.h"
22 #include "ubus.h"
23
24 char *hotplug_cmd_path = DEFAULT_HOTPLUG_PATH;
25 static struct interface *current;
26 static enum interface_event current_ev;
27 static struct list_head pending = LIST_HEAD_INIT(pending);
28
29 static void task_complete(struct uloop_process *proc, int ret);
30 static struct uloop_process task = {
31 .cb = task_complete,
32 };
33 static const char * const eventnames[] = {
34 [IFEV_DOWN] = "ifdown",
35 [IFEV_UP] = "ifup",
36 [IFEV_UP_FAILED] = "ifup-failed",
37 [IFEV_UPDATE] = "ifupdate",
38 [IFEV_FREE] = "free",
39 [IFEV_RELOAD] = "reload",
40 [IFEV_LINK_UP] = "iflink",
41 [IFEV_CREATE] = "create",
42 };
43
44 static void
45 run_cmd(const char *ifname, const char *device, enum interface_event event,
46 enum interface_update_flags updated)
47 {
48 char *argv[3];
49 int pid;
50
51 pid = fork();
52 if (pid < 0) {
53 task_complete(NULL, -1);
54 return;
55 }
56
57 if (pid > 0) {
58 task.pid = pid;
59 uloop_process_add(&task);
60 return;
61 }
62
63 setenv("ACTION", eventnames[event], 1);
64 setenv("INTERFACE", ifname, 1);
65 if (device)
66 setenv("DEVICE", device, 1);
67
68 if (event == IFEV_UPDATE) {
69 if (updated & IUF_ADDRESS)
70 setenv("IFUPDATE_ADDRESSES", "1", 1);
71 if (updated & IUF_ROUTE)
72 setenv("IFUPDATE_ROUTES", "1", 1);
73 if (updated & IUF_PREFIX)
74 setenv("IFUPDATE_PREFIXES", "1", 1);
75 if (updated & IUF_DATA)
76 setenv("IFUPDATE_DATA", "1", 1);
77 }
78
79 argv[0] = hotplug_cmd_path;
80 argv[1] = "iface";
81 argv[2] = NULL;
82 execvp(argv[0], argv);
83 exit(127);
84 }
85
86 static void
87 call_hotplug(void)
88 {
89 const char *device = NULL;
90 if (list_empty(&pending))
91 return;
92
93 current = list_first_entry(&pending, struct interface, hotplug_list);
94 current_ev = current->hotplug_ev;
95 list_del_init(&current->hotplug_list);
96
97 if ((current_ev == IFEV_UP || current_ev == IFEV_UPDATE) && current->l3_dev.dev)
98 device = current->l3_dev.dev->ifname;
99
100 D(SYSTEM, "Call hotplug handler for interface '%s', event '%s' (%s)",
101 current->name, eventnames[current_ev], device ? device : "none");
102 run_cmd(current->name, device, current_ev, current->updated);
103 }
104
105 static void
106 task_complete(struct uloop_process *proc, int ret)
107 {
108 if (current)
109 D(SYSTEM, "Complete hotplug handler for interface '%s'", current->name);
110 current = NULL;
111 call_hotplug();
112 }
113
114 /*
115 * Queue an interface for an up/down event.
116 * An interface can only have one event in the queue and one
117 * event waiting for completion.
118 * When queueing an event that is the same as the one waiting for
119 * completion, remove the interface from the queue
120 */
121 static void
122 interface_queue_event(struct interface *iface, enum interface_event ev)
123 {
124 D(SYSTEM, "Queue hotplug handler for interface '%s', event '%s'",
125 iface->name, eventnames[ev]);
126 if (ev == IFEV_UP || ev == IFEV_DOWN)
127 netifd_ubus_interface_event(iface, ev == IFEV_UP);
128
129 netifd_ubus_interface_notify(iface, ev != IFEV_DOWN);
130
131 /* no hotplug.d calls for link up */
132 if (ev == IFEV_LINK_UP)
133 return;
134
135 if (current == iface) {
136 /* an event for iface is being processed */
137 if (!list_empty(&iface->hotplug_list)) {
138 /* an additional event for iface is pending */
139 /* overwrite pending event if it differs from */
140 /* an update */
141 if (ev != IFEV_UPDATE)
142 iface->hotplug_ev = ev;
143 }
144 else {
145 /* no additional event for iface is pending */
146 if (ev != current_ev || ev == IFEV_UPDATE) {
147 /* only add the interface to the pending list if
148 * the event is different from the one being
149 * handled or if it is an update */
150 iface->hotplug_ev = ev;
151 /* Handle hotplug calls FIFO */
152 list_add_tail(&iface->hotplug_list, &pending);
153 }
154 }
155 }
156 else {
157 /* currently not handling an event or handling an event
158 * for another interface */
159 if (!list_empty(&iface->hotplug_list)) {
160 /* an event for iface is pending */
161 if (!(iface->hotplug_ev == IFEV_UP &&
162 ev == IFEV_UPDATE)) {
163 /* overwrite pending event, unless the incoming
164 * event is an ifupdate while the pending one
165 * is an ifup */
166 iface->hotplug_ev = ev;
167 }
168 }
169 else {
170 /* an event for the interface is not yet pending,
171 * queue it */
172 iface->hotplug_ev = ev;
173 /* Handle hotplug calls FIFO */
174 list_add_tail(&iface->hotplug_list, &pending);
175 }
176 }
177
178 if (!task.pending && !current)
179 call_hotplug();
180 }
181
182 static void
183 interface_dequeue_event(struct interface *iface)
184 {
185 if (iface == current)
186 current = NULL;
187
188 if (!list_empty(&iface->hotplug_list))
189 list_del_init(&iface->hotplug_list);
190 }
191
192 static void interface_event_cb(struct interface_user *dep, struct interface *iface,
193 enum interface_event ev)
194 {
195 switch (ev) {
196 case IFEV_LINK_UP:
197 case IFEV_UP:
198 case IFEV_UP_FAILED:
199 case IFEV_UPDATE:
200 case IFEV_DOWN:
201 interface_queue_event(iface, ev);
202 break;
203 case IFEV_FREE:
204 interface_dequeue_event(iface);
205 break;
206 default:
207 break;
208 }
209 }
210
211 static struct interface_user event_user = {
212 .cb = interface_event_cb
213 };
214
215 static void __init interface_event_init(void)
216 {
217 interface_add_user(&event_user, NULL);
218 }