8f092f017b66932a59187affd565450f82f623f9
[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 }