add support for interrupting shell protocol setup
[project/netifd.git] / proto-shell.c
1 #include <string.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <glob.h>
5 #include <unistd.h>
6 #include <fcntl.h>
7 #include <signal.h>
8
9 #include <libubox/blobmsg_json.h>
10
11 #include "netifd.h"
12 #include "interface.h"
13 #include "interface-ip.h"
14 #include "proto.h"
15
16 static LIST_HEAD(handlers);
17 static int proto_fd;
18
19 struct proto_shell_handler {
20 struct list_head list;
21 struct proto_handler proto;
22 struct config_param_list config;
23 char *config_buf;
24 char script_name[];
25 };
26
27 struct proto_shell_state {
28 struct interface_proto_state proto;
29 struct proto_shell_handler *handler;
30 struct blob_attr *config;
31
32 struct uloop_process setup_task;
33 struct uloop_process teardown_task;
34 bool teardown_pending;
35 };
36
37 static int
38 run_script(const char **argv, struct uloop_process *proc)
39 {
40 int pid;
41
42 if ((pid = fork()) < 0)
43 return -1;
44
45 if (!pid) {
46 fchdir(proto_fd);
47 execvp(argv[0], (char **) argv);
48 exit(127);
49 }
50
51 if (pid < 0)
52 return -1;
53
54 proc->pid = pid;
55 uloop_process_add(proc);
56
57 return 0;
58 }
59
60 static int
61 proto_shell_handler(struct interface_proto_state *proto,
62 enum interface_proto_cmd cmd, bool force)
63 {
64 struct proto_shell_state *state;
65 struct proto_shell_handler *handler;
66 struct uloop_process *proc;
67 const char *argv[6];
68 const char *action;
69 char *config;
70 int ret, i = 0;
71
72 state = container_of(proto, struct proto_shell_state, proto);
73 handler = state->handler;
74
75 if (cmd == PROTO_CMD_SETUP) {
76 action = "setup";
77 proc = &state->setup_task;
78 } else {
79 action = "teardown";
80 proc = &state->teardown_task;
81 if (state->setup_task.pending) {
82 kill(state->setup_task.pid, SIGINT);
83 state->teardown_pending = true;
84 return 0;
85 }
86 }
87
88 config = blobmsg_format_json(state->config, true);
89 if (!config)
90 return -1;
91
92 argv[i++] = handler->script_name;
93 argv[i++] = handler->proto.name;
94 argv[i++] = action;
95 argv[i++] = proto->iface->name;
96 argv[i++] = config;
97 if (proto->iface->main_dev.dev)
98 argv[i++] = proto->iface->main_dev.dev->ifname;
99 argv[i] = NULL;
100
101 ret = run_script(argv, proc);
102 free(config);
103
104 return ret;
105 }
106
107 static void
108 proto_shell_setup_cb(struct uloop_process *p, int ret)
109 {
110 struct proto_shell_state *state;
111
112 state = container_of(p, struct proto_shell_state, setup_task);
113 if (state->teardown_pending) {
114 state->teardown_pending = 0;
115 proto_shell_handler(&state->proto, PROTO_CMD_TEARDOWN, false);
116 }
117 }
118
119 static void
120 proto_shell_teardown_cb(struct uloop_process *p, int ret)
121 {
122 struct proto_shell_state *state;
123
124 state = container_of(p, struct proto_shell_state, teardown_task);
125 state->proto.proto_event(&state->proto, IFPEV_DOWN);
126 }
127
128 static void
129 proto_shell_free(struct interface_proto_state *proto)
130 {
131 struct proto_shell_state *state;
132
133 state = container_of(proto, struct proto_shell_state, proto);
134 free(state->config);
135 free(state);
136 }
137
138 struct interface_proto_state *
139 proto_shell_attach(const struct proto_handler *h, struct interface *iface,
140 struct blob_attr *attr)
141 {
142 struct proto_shell_state *state;
143
144 state = calloc(1, sizeof(*state));
145 state->config = malloc(blob_pad_len(attr));
146 if (!state->config)
147 goto error;
148
149 memcpy(state->config, attr, blob_pad_len(attr));
150 state->proto.free = proto_shell_free;
151 state->proto.cb = proto_shell_handler;
152 state->setup_task.cb = proto_shell_setup_cb;
153 state->teardown_task.cb = proto_shell_teardown_cb;
154 state->handler = container_of(h, struct proto_shell_handler, proto);
155
156 return &state->proto;
157
158 error:
159 free(state);
160 return NULL;
161 }
162
163 static json_object *
164 check_type(json_object *obj, json_type type)
165 {
166 if (!obj)
167 return NULL;
168
169 if (json_object_get_type(obj) != type)
170 return NULL;
171
172 return obj;
173 }
174
175 static inline json_object *
176 get_field(json_object *obj, const char *name, json_type type)
177 {
178 return check_type(json_object_object_get(obj, name), type);
179 }
180
181 static char *
182 proto_shell_parse_config(struct config_param_list *config, json_object *obj)
183 {
184 struct blobmsg_policy *attrs;
185 char *str_buf, *str_cur;
186 int str_len = 0;
187 int i;
188
189 attrs = calloc(1, sizeof(*attrs));
190 if (!attrs)
191 return NULL;
192
193 config->n_params = json_object_array_length(obj);
194 config->params = attrs;
195 for (i = 0; i < config->n_params; i++) {
196 json_object *cur, *name, *type;
197
198 cur = check_type(json_object_array_get_idx(obj, i), json_type_array);
199 if (!cur)
200 goto error;
201
202 name = check_type(json_object_array_get_idx(cur, 0), json_type_string);
203 if (!name)
204 goto error;
205
206 type = check_type(json_object_array_get_idx(cur, 1), json_type_int);
207 if (!type)
208 goto error;
209
210 attrs[i].name = json_object_get_string(name);
211 attrs[i].type = json_object_get_int(type);
212 if (attrs[i].type > BLOBMSG_TYPE_LAST)
213 goto error;
214
215 str_len += strlen(attrs[i].name + 1);
216 }
217
218 str_buf = malloc(str_len);
219 if (!str_buf)
220 goto error;
221
222 str_cur = str_buf;
223 for (i = 0; i < config->n_params; i++) {
224 const char *name = attrs[i].name;
225
226 attrs[i].name = str_cur;
227 str_cur += sprintf(str_cur, "%s", name) + 1;
228 }
229
230 return str_buf;
231
232 error:
233 free(attrs);
234 config->n_params = 0;
235 return NULL;
236 }
237
238 static void
239 proto_shell_add_handler(const char *script, json_object *obj)
240 {
241 struct proto_shell_handler *handler;
242 struct proto_handler *proto;
243 json_object *config, *tmp;
244 const char *name;
245 char *str;
246
247 if (!check_type(obj, json_type_object))
248 return;
249
250 tmp = get_field(obj, "name", json_type_string);
251 if (!tmp)
252 return;
253
254 name = json_object_get_string(tmp);
255
256 handler = calloc(1, sizeof(*handler) +
257 strlen(script) + 1 +
258 strlen(name) + 1);
259 if (!handler)
260 return;
261
262 strcpy(handler->script_name, script);
263
264 str = handler->script_name + strlen(handler->script_name) + 1;
265 strcpy(str, name);
266
267 proto = &handler->proto;
268 proto->name = str;
269 proto->config_params = &handler->config;
270 proto->attach = proto_shell_attach;
271
272 tmp = get_field(obj, "no-device", json_type_boolean);
273 if (tmp && json_object_get_boolean(tmp))
274 handler->proto.flags |= PROTO_FLAG_NODEV;
275
276 config = get_field(obj, "config", json_type_array);
277 if (config)
278 handler->config_buf = proto_shell_parse_config(&handler->config, config);
279
280 DPRINTF("Add handler for script %s: %s\n", script, proto->name);
281 add_proto_handler(proto);
282 }
283
284 static void proto_shell_add_script(const char *name)
285 {
286 struct json_tokener *tok = NULL;
287 json_object *obj;
288 static char buf[512];
289 char *start, *end, *cmd;
290 FILE *f;
291 int buflen, len;
292
293 #define DUMP_SUFFIX " '' dump"
294
295 cmd = alloca(strlen(name) + 1 + sizeof(DUMP_SUFFIX));
296 sprintf(cmd, "%s" DUMP_SUFFIX, name);
297
298 f = popen(cmd, "r");
299 if (!f)
300 return;
301
302 do {
303 buflen = fread(buf, 1, sizeof(buf) - 1, f);
304 if (buflen <= 0)
305 continue;
306
307 start = buf;
308 len = buflen;
309 do {
310 end = memchr(start, '\n', len);
311 if (end)
312 len = end - start;
313
314 if (!tok)
315 tok = json_tokener_new();
316
317 obj = json_tokener_parse_ex(tok, start, len);
318 if (!is_error(obj)) {
319 proto_shell_add_handler(name, obj);
320 json_object_put(obj);
321 json_tokener_free(tok);
322 tok = NULL;
323 }
324
325 if (end) {
326 start = end + 1;
327 len = buflen - (start - buf);
328 }
329 } while (len > 0);
330 } while (!feof(f) && !ferror(f));
331
332 if (tok)
333 json_tokener_free(tok);
334
335 pclose(f);
336 }
337
338 void __init proto_shell_init(void)
339 {
340 glob_t g;
341 int main_fd;
342 int i;
343
344 main_fd = open(".", O_RDONLY | O_DIRECTORY);
345 if (main_fd < 0)
346 return;
347
348 if (chdir(main_path)) {
349 perror("chdir(main path)");
350 goto close_cur;
351 }
352
353 if (chdir("./proto"))
354 goto close_cur;
355
356 proto_fd = open(".", O_RDONLY | O_DIRECTORY);
357 if (proto_fd < 0)
358 goto close_cur;
359
360 glob("./*.sh", 0, NULL, &g);
361 for (i = 0; i < g.gl_pathc; i++)
362 proto_shell_add_script(g.gl_pathv[i]);
363
364 close_cur:
365 fchdir(main_fd);
366 close(main_fd);
367 }