8fbd9fc9f453f84acd4fb2e20b521473168ce678
[src/app-framework-binder.git] / src / afb-apis.c
1 /*
2  * Copyright (C) 2016 "IoT.bzh"
3  * Author "Fulup Ar Foll"
4  * Author José Bollo <jose.bollo@iot.bzh>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  * 
19  * Contain all generic part to handle REST/API
20  * 
21  *  https://www.gnu.org/software/libmicrohttpd/tutorial.html [search 'largepost.c']
22  */
23
24 #define _GNU_SOURCE
25
26 #include <stdio.h>
27 #include <assert.h>
28 #include <string.h>
29 #include <dirent.h>
30 #include <dlfcn.h>
31 #include <unistd.h>
32 #include <limits.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <signal.h>
36 #include <time.h>
37 #include <sys/syscall.h>
38 #include <setjmp.h>
39
40 #include "local-def.h"
41
42 #include "afb-req-itf.h"
43 #include "afb-apis.h"
44
45 struct api_desc {
46         AFB_plugin *plugin;     /* descriptor */
47         size_t prefixlen;
48         const char *prefix;
49         void *handle;           /* context of dlopen */
50 };
51
52 static int api_timeout = 15;
53 static struct api_desc *apis_array = NULL;
54 static int apis_count = 0;
55
56 static const char plugin_register_function[] = "pluginRegister";
57
58 int afb_apis_count()
59 {
60         return apis_count;
61 }
62
63 void afb_apis_free_context(int apiidx, void *context)
64 {
65         void (*cb)(void*);
66
67         assert(0 <= apiidx && apiidx < apis_count);
68         cb = apis_array[apiidx].plugin->freeCtxCB;
69         if (cb)
70                 cb(context);
71         else
72                 free(context);
73 }
74
75 const struct AFB_restapi *afb_apis_get(int apiidx, int verbidx)
76 {
77         assert(0 <= apiidx && apiidx < apis_count);
78         return &apis_array[apiidx].plugin->apis[verbidx];
79 }
80
81 int afb_apis_get_verbidx(int apiidx, const char *name)
82 {
83         const struct AFB_restapi *apis;
84         int idx;
85
86         assert(0 <= apiidx && apiidx < apis_count);
87         apis = apis_array[apiidx].plugin->apis;
88         for (idx = 0 ; apis[idx].name ; idx++)
89                 if (!strcasecmp(apis[idx].name, name))
90                         return idx;
91         return -1;
92 }
93
94 int afb_apis_get_apiidx(const char *prefix, size_t length)
95 {
96         int i;
97         const struct api_desc *a;
98
99         if (!length)
100                 length = strlen(prefix);
101
102         for (i = 0 ; i < apis_count ; i++) {
103                 a = &apis_array[i];
104                 if (a->prefixlen == length && !strcasecmp(a->prefix, prefix))
105                         return i;
106         }
107         return -1;
108 }
109
110 int afb_apis_add_plugin(const char *path)
111 {
112         struct api_desc *apis;
113         AFB_plugin *plugin;
114         AFB_plugin *(*pluginRegisterFct) (void);
115         void *handle;
116         size_t len;
117
118         // This is a loadable library let's check if it's a plugin
119         handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);
120         if (handle == NULL) {
121                 fprintf(stderr, "[%s] not loadable, continuing...\n", path);
122                 goto error;
123         }
124
125         /* retrieves the register function */
126         pluginRegisterFct = dlsym(handle, plugin_register_function);
127         if (!pluginRegisterFct) {
128                 fprintf(stderr, "[%s] not an AFB plugin, continuing...\n", path);
129                 goto error2;
130         }
131         if (verbose)
132                 fprintf(stderr, "[%s] is a valid AFB plugin\n", path);
133
134         /* allocates enough memory */
135         apis = realloc(apis_array, ((unsigned)apis_count + 1) * sizeof * apis);
136         if (apis == NULL) {
137                 fprintf(stderr, "ERROR: plugin [%s] memory missing. continuing...\n", path);
138                 goto error2;
139         }
140         apis_array = apis;
141
142         /* init the plugin */
143         plugin = pluginRegisterFct();
144         if (plugin == NULL) {
145                 fprintf(stderr, "ERROR: plugin [%s] register function failed. continuing...\n", path);
146                 goto error2;
147         }
148
149         /* check the returned structure */
150         if (plugin->type != AFB_PLUGIN_JSON) {
151                 fprintf(stderr, "ERROR: plugin [%s] invalid type %d...\n", path, plugin->type);
152                 goto error2;
153         }
154         if (plugin->prefix == NULL || *plugin->prefix == 0) {
155                 fprintf(stderr, "ERROR: plugin [%s] bad prefix...\n", path);
156                 goto error2;
157         }
158         if (plugin->info == NULL || *plugin->info == 0) {
159                 fprintf(stderr, "ERROR: plugin [%s] bad description...\n", path);
160                 goto error2;
161         }
162         if (plugin->apis == NULL) {
163                 fprintf(stderr, "ERROR: plugin [%s] no APIs...\n", path);
164                 goto error2;
165         }
166
167         /* check previously existing plugin */
168         len = strlen(plugin->prefix);
169         if (afb_apis_get_apiidx(plugin->prefix, len) >= 0) {
170                 fprintf(stderr, "ERROR: plugin [%s] prefix %s duplicated...\n", path, plugin->prefix);
171                 goto error2;
172         }
173
174         /* record the plugin */
175         if (verbose)
176                 fprintf(stderr, "Loading plugin[%lu] prefix=[%s] info=%s\n", (unsigned long)apis_count, plugin->prefix, plugin->info);
177         apis = &apis_array[apis_count];
178         apis->plugin = plugin;
179         apis->prefixlen = len;
180         apis->prefix = plugin->prefix;
181         apis->handle = handle;
182         apis_count++;
183
184         return 0;
185
186 error2:
187         dlclose(handle);
188 error:
189         return -1;
190 }
191
192 static int adddirs(char path[PATH_MAX], size_t end)
193 {
194         int rc;
195         DIR *dir;
196         struct dirent ent, *result;
197         size_t len;
198
199         /* open the DIR now */
200         dir = opendir(path);
201         if (dir == NULL) {
202                 fprintf(stderr, "ERROR in scanning plugin directory %s, %m\n", path);
203                 return -1;
204         }
205         if (verbose)
206                 fprintf(stderr, "Scanning dir=[%s] for plugins\n", path);
207
208         /* scan each entry */
209         if (end)
210                 path[end++] = '/';
211         for (;;) {
212                 readdir_r(dir, &ent, &result);
213                 if (result == NULL)
214                         break;
215
216                 len = strlen(ent.d_name);
217                 if (len + end >= PATH_MAX) {
218                         fprintf(stderr, "path too long for %s\n", ent.d_name);
219                         continue;
220                 }
221                 memcpy(&path[end], ent.d_name, len+1);
222                 if (ent.d_type == DT_DIR) {
223                         /* case of directories */
224                         if (ent.d_name[0] == '.') {
225                                 if (len == 1)
226                                         continue;
227                                 if (ent.d_name[1] == '.' && len == 2)
228                                         continue;
229                         }
230                         rc = adddirs(path, end+len);;
231                 } else if (ent.d_type == DT_REG) {
232                         /* case of files */
233                         if (!strstr(ent.d_name, ".so"))
234                                 continue;
235                         rc = afb_apis_add_plugin(path);
236                 }
237         }
238         closedir(dir);
239         return 0;
240 }
241
242 int afb_apis_add_directory(const char *path)
243 {
244         size_t length;
245         char buffer[PATH_MAX];
246
247         length = strlen(path);
248         if (length >= sizeof(buffer)) {
249                 fprintf(stderr, "path too long %lu [%.99s...]\n", (unsigned long)length, path);
250                 return -1;
251         }
252
253         memcpy(buffer, path, length + 1);
254         return adddirs(buffer, length);
255 }
256
257 int afb_apis_add_path(const char *path)
258 {
259         struct stat st;
260         int rc;
261
262         rc = stat(path, &st);
263         if (rc < 0)
264                 fprintf(stderr, "Invalid plugin path [%s]: %m\n", path);
265         else if (S_ISDIR(st.st_mode))
266                 rc = afb_apis_add_directory(path);
267         else
268                 rc = afb_apis_add_plugin(path);
269         return rc;
270 }
271
272 int afb_apis_add_pathset(const char *pathset)
273 {
274         static char sep[] = ":";
275         char *ps, *p;
276         int rc;
277
278         ps = strdupa(pathset);
279         for (;;) {
280                 p = strsep(&ps, sep);
281                 if (!p)
282                         return 0;
283                 rc = afb_apis_add_path(p);
284         };
285 }
286
287 // Check of apiurl is declare in this plugin and call it
288 extern __thread sigjmp_buf *error_handler;
289 static void trapping_handle(AFB_request * request, struct json_object *(*cb)(AFB_request *,void*))
290 {
291         volatile int signum, timerset;
292         timer_t timerid;
293         sigjmp_buf jmpbuf, *older;
294         struct sigevent sevp;
295         struct itimerspec its;
296
297         // save context before calling the API
298         timerset = 0;
299         older = error_handler;
300         signum = setjmp(jmpbuf);
301         if (signum != 0) {
302                 afb_req_fail_f(*request->areq, "aborted", "signal %d caught", signum);
303         }
304         else {
305                 error_handler = &jmpbuf;
306                 if (api_timeout > 0) {
307                         timerset = 1; /* TODO: check statuses */
308                         sevp.sigev_notify = SIGEV_THREAD_ID;
309                         sevp.sigev_signo = SIGALRM;
310 #if defined(sigev_notify_thread_id)
311                         sevp.sigev_notify_thread_id = (pid_t)syscall(SYS_gettid);
312 #else
313                         sevp._sigev_un._tid = (pid_t)syscall(SYS_gettid);
314 #endif
315                         timer_create(CLOCK_THREAD_CPUTIME_ID, &sevp, &timerid);
316                         its.it_interval.tv_sec = 0;
317                         its.it_interval.tv_nsec = 0;
318                         its.it_value.tv_sec = api_timeout;
319                         its.it_value.tv_nsec = 0;
320                         timer_settime(timerid, 0, &its, NULL);
321                 }
322
323                 cb(request, NULL);
324         }
325         if (timerset)
326                 timer_delete(timerid);
327         error_handler = older;
328 }
329
330 static void handle(struct afb_req req, const struct api_desc *api, const struct AFB_restapi *verb)
331 {
332         AFB_request request;
333
334         request.uuid = request.url = "fake";
335         request.prefix = api->prefix;
336         request.method = verb->name;
337         request.context = NULL;
338         request.restfull = 0;
339         request.errcode = 0;
340         request.config = NULL;
341         request.areq = &req;
342
343         switch(verb->session) {
344         case AFB_SESSION_CREATE:
345         case AFB_SESSION_RENEW:
346                 /*if (check) new*/
347                 break;
348         case AFB_SESSION_CLOSE:
349         case AFB_SESSION_CHECK:
350                 /*check*/
351                 break;
352         case AFB_SESSION_NONE:
353         default:
354                 break;
355         }
356         trapping_handle(&request, verb->callback);
357
358         if (verb->session == AFB_SESSION_CLOSE)
359                 /*close*/;
360 }
361
362 int afb_apis_handle(struct afb_req req, const char *api, size_t lenapi, const char *verb, size_t lenverb)
363 {
364         int i, j;
365         const struct api_desc *a;
366         const struct AFB_restapi *v;
367
368         a = apis_array;
369         for (i = 0 ; i < apis_count ; i++, a++) {
370                 if (a->prefixlen == lenapi && !strncasecmp(a->prefix, api, lenapi)) {
371                         v = a->plugin->apis;
372                         for (j = 0 ; v->name ; j++, v++) {
373                                 if (!strncasecmp(v->name, verb, lenverb) && !v->name[lenverb]) {
374                                         handle(req, a, v);
375                                         return 1;
376                                 }
377                         }
378                         afb_req_fail_f(req, "unknown-verb", "verb %.*s unknown within api %s", (int)lenverb, verb, a->prefix);
379                         return 1;
380                 }
381         }
382         return 0;
383 }
384