Add initial plugin api support
[project/rpcd.git] / plugin.c
1 /*
2  * luci-rpcd - LuCI UBUS RPC server
3  *
4  *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
5  *
6  * Permission to use, copy, modify, and/or distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18
19 #include "plugin.h"
20
21 static struct blob_buf buf;
22
23 struct rpc_plugin_lookup_context {
24         uint32_t id;
25         char *name;
26         bool found;
27 };
28
29 static void
30 rpc_plugin_lookup_plugin_cb(struct ubus_context *ctx,
31                             struct ubus_object_data *obj, void *priv)
32 {
33         struct rpc_plugin_lookup_context *c = priv;
34
35         if (c->id == obj->id)
36         {
37                 c->found = true;
38                 sprintf(c->name, "%s", obj->path);
39         }
40 }
41
42 static bool
43 rpc_plugin_lookup_plugin(struct ubus_context *ctx, struct ubus_object *obj,
44                          char *strptr)
45 {
46         struct rpc_plugin_lookup_context c = { .id = obj->id, .name = strptr };
47
48         if (ubus_lookup(ctx, NULL, rpc_plugin_lookup_plugin_cb, &c))
49                 return false;
50
51         return c.found;
52 }
53
54 static int
55 rpc_plugin_call(struct ubus_context *ctx, struct ubus_object *obj,
56                 struct ubus_request_data *req, const char *method,
57                 struct blob_attr *msg)
58 {
59         pid_t pid;
60         struct stat s;
61         int rv, fd, in_fds[2], out_fds[2];
62         char *input, *plugin, *meth, output[4096] = { 0 }, path[PATH_MAX] = { 0 };
63
64         meth = strdup(method);
65         input = blobmsg_format_json(msg, true);
66         plugin = path + sprintf(path, "%s/", RPC_PLUGIN_DIRECTORY);
67
68         if (!rpc_plugin_lookup_plugin(ctx, obj, plugin))
69                 return UBUS_STATUS_NOT_FOUND;
70
71         if (stat(path, &s) || !(s.st_mode & S_IXUSR))
72                 return UBUS_STATUS_NOT_FOUND;
73
74         if (pipe(in_fds) || pipe(out_fds))
75                 return UBUS_STATUS_UNKNOWN_ERROR;
76
77         switch ((pid = fork()))
78         {
79         case -1:
80                 return UBUS_STATUS_UNKNOWN_ERROR;
81
82         case 0:
83                 uloop_done();
84
85                 fd = open("/dev/null", O_RDWR);
86
87                 if (fd > -1)
88                 {
89                         dup2(fd, 2);
90
91                         if (fd > 2)
92                                 close(fd);
93                 }
94
95                 dup2(in_fds[0], 0);
96                 dup2(out_fds[1], 1);
97
98                 close(in_fds[0]);
99                 close(in_fds[1]);
100                 close(out_fds[0]);
101                 close(out_fds[1]);
102
103                 if (execl(path, plugin, "call", meth, NULL))
104                         return UBUS_STATUS_UNKNOWN_ERROR;
105
106         default:
107                 rv = UBUS_STATUS_NO_DATA;
108
109                 if (input)
110                 {
111                         write(in_fds[1], input, strlen(input));
112                         free(input);
113                 }
114
115                 close(in_fds[0]);
116                 close(in_fds[1]);
117
118                 if (read(out_fds[0], output, sizeof(output) - 1) > 0)
119                 {
120                         blob_buf_init(&buf, 0);
121
122                         if (!blobmsg_add_json_from_string(&buf, output))
123                                 rv = UBUS_STATUS_INVALID_ARGUMENT;
124
125                         rv = UBUS_STATUS_OK;
126                 }
127
128                 close(out_fds[0]);
129                 close(out_fds[1]);
130
131                 waitpid(pid, NULL, 0);
132
133                 if (!rv)
134                         ubus_send_reply(ctx, req, buf.head);
135
136                 free(meth);
137
138                 return rv;
139         }
140 }
141
142 static bool
143 rpc_plugin_parse_signature(struct blob_attr *sig, struct ubus_method *method)
144 {
145         int rem, n_attr;
146         enum blobmsg_type type;
147         struct blob_attr *attr;
148         struct blobmsg_policy *policy = NULL;
149
150         if (!sig || blob_id(sig) != BLOBMSG_TYPE_TABLE)
151                 return false;
152
153         n_attr = 0;
154
155         blobmsg_for_each_attr(attr, sig, rem)
156                 n_attr++;
157
158         if (n_attr)
159         {
160                 policy = calloc(n_attr, sizeof(*policy));
161
162                 if (!policy)
163                         return false;
164
165                 n_attr = 0;
166
167                 blobmsg_for_each_attr(attr, sig, rem)
168                 {
169                         type = blob_id(attr);
170
171                         if (type == BLOBMSG_TYPE_INT32)
172                         {
173                                 switch (blobmsg_get_u32(attr))
174                                 {
175                                 case 8:
176                                         type = BLOBMSG_TYPE_INT8;
177                                         break;
178
179                                 case 16:
180                                         type = BLOBMSG_TYPE_INT16;
181                                         break;
182
183                                 case 64:
184                                         type = BLOBMSG_TYPE_INT64;
185                                         break;
186
187                                 default:
188                                         type = BLOBMSG_TYPE_INT32;
189                                         break;
190                                 }
191                         }
192
193                         policy[n_attr].name = strdup(blobmsg_name(attr));
194                         policy[n_attr].type = type;
195
196                         n_attr++;
197                 }
198         }
199
200         method->name = strdup(blobmsg_name(sig));
201         method->handler = rpc_plugin_call;
202         method->policy = policy;
203         method->n_policy = n_attr;
204
205         return true;
206 }
207
208 static struct ubus_object *
209 rpc_plugin_parse_plugin(const char *name, const char *listbuf)
210 {
211         int rem, n_method;
212         struct blob_attr *cur;
213         struct ubus_method *methods;
214         struct ubus_object_type *obj_type;
215         struct ubus_object *obj;
216
217         blob_buf_init(&buf, 0);
218
219         if (!blobmsg_add_json_from_string(&buf, listbuf))
220                 return NULL;
221
222         n_method = 0;
223
224         blob_for_each_attr(cur, buf.head, rem)
225                 n_method++;
226
227         if (!n_method)
228                 return NULL;
229
230         methods = calloc(n_method, sizeof(*methods));
231
232         if (!methods)
233                 return NULL;
234
235         n_method = 0;
236
237         blob_for_each_attr(cur, buf.head, rem)
238         {
239                 if (!rpc_plugin_parse_signature(cur, &methods[n_method]))
240                         continue;
241
242                 n_method++;
243         }
244
245         obj = calloc(1, sizeof(*obj));
246
247         if (!obj)
248                 return NULL;
249
250         obj_type = calloc(1, sizeof(*obj_type));
251
252         if (!obj_type)
253                 return NULL;
254
255         asprintf((char **)&obj_type->name, "luci-rpc-plugin-%s", name);
256         obj_type->methods = methods;
257         obj_type->n_methods = n_method;
258
259         obj->name = strdup(name);
260         obj->type = obj_type;
261         obj->methods = methods;
262         obj->n_methods = n_method;
263
264         return obj;
265 }
266
267 static int
268 rpc_plugin_register(struct ubus_context *ctx, const char *path)
269 {
270         pid_t pid;
271         int rv, fd, fds[2];
272         const char *name;
273         char listbuf[4096] = { 0 };
274         struct ubus_object *plugin;
275
276         name = strrchr(path, '/');
277
278         if (!name)
279                 return UBUS_STATUS_INVALID_ARGUMENT;
280
281         if (pipe(fds))
282                 return UBUS_STATUS_UNKNOWN_ERROR;
283
284         switch ((pid = fork()))
285         {
286         case -1:
287                 return UBUS_STATUS_UNKNOWN_ERROR;
288
289         case 0:
290                 fd = open("/dev/null", O_RDWR);
291
292                 if (fd > -1)
293                 {
294                         dup2(fd, 0);
295                         dup2(fd, 2);
296
297                         if (fd > 2)
298                                 close(fd);
299                 }
300
301                 dup2(fds[1], 1);
302
303                 close(fds[0]);
304                 close(fds[1]);
305
306                 if (execl(path, path, "list", NULL))
307                         return UBUS_STATUS_UNKNOWN_ERROR;
308
309         default:
310                 rv = 0;
311
312                 if (read(fds[0], listbuf, sizeof(listbuf) - 1) <= 0)
313                         goto out;
314
315                 plugin = rpc_plugin_parse_plugin(name + 1, listbuf);
316
317                 if (!plugin)
318                         goto out;
319
320                 rv = ubus_add_object(ctx, plugin);
321
322 out:
323                 close(fds[0]);
324                 close(fds[1]);
325                 waitpid(pid, NULL, 0);
326
327                 return rv;
328         }
329 }
330
331 int rpc_plugin_api_init(struct ubus_context *ctx)
332 {
333         DIR *d;
334         int rv = 0;
335         struct stat s;
336         struct dirent *e;
337         char path[PATH_MAX];
338
339         d = opendir(RPC_PLUGIN_DIRECTORY);
340
341         if (!d)
342                 return UBUS_STATUS_NOT_FOUND;
343
344         while ((e = readdir(d)) != NULL)
345         {
346                 snprintf(path, sizeof(path) - 1, RPC_PLUGIN_DIRECTORY "/%s", e->d_name);
347
348                 if (stat(path, &s) || !S_ISREG(s.st_mode) || !(s.st_mode & S_IXUSR))
349                         continue;
350
351                 rv |= rpc_plugin_register(ctx, path);
352         }
353
354         closedir(d);
355
356         return rv;
357 }