refactoring (in progress, tbf)
[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
36 #include "../include/local-def.h"
37
38 #include "afb-req-itf.h"
39 #include "afb-apis.h"
40
41 struct api_desc {
42         AFB_plugin *plugin;     /* descriptor */
43         size_t prefixlen;
44         const char *prefix;
45         void *handle;           /* context of dlopen */
46 };
47
48 static struct api_desc *apis_array = NULL;
49 static int apis_count = 0;
50
51 static const char plugin_register_function[] = "pluginRegister";
52
53 int afb_apis_count()
54 {
55         return apis_count;
56 }
57
58 void afb_apis_free_context(int apiidx, void *context)
59 {
60         void (*cb)(void*);
61
62         assert(0 <= apiidx && apiidx < apis_count);
63         cb = apis_array[apiidx].plugin->freeCtxCB;
64         if (cb)
65                 cb(context);
66         else
67                 free(context);
68 }
69
70 const struct AFB_restapi *afb_apis_get(int apiidx, int verbidx)
71 {
72         assert(0 <= apiidx && apiidx < apis_count);
73         return &apis_array[apiidx].plugin->apis[verbidx];
74 }
75
76 int afb_apis_get_verbidx(int apiidx, const char *name)
77 {
78         const struct AFB_restapi *apis;
79         int idx;
80
81         assert(0 <= apiidx && apiidx < apis_count);
82         apis = apis_array[apiidx].plugin->apis;
83         for (idx = 0 ; apis[idx].name ; idx++)
84                 if (!strcasecmp(apis[idx].name, name))
85                         return idx;
86         return -1;
87 }
88
89 int afb_apis_get_apiidx(const char *prefix, size_t length)
90 {
91         int i;
92         const struct api_desc *a;
93
94         if (!length)
95                 length = strlen(prefix);
96
97         for (i = 0 ; i < apis_count ; i++) {
98                 a = &apis_array[i];
99                 if (a->prefixlen == length && !strcasecmp(a->prefix, prefix))
100                         return i;
101         }
102         return -1;
103 }
104
105 int afb_apis_add_plugin(const char *path)
106 {
107         struct api_desc *apis;
108         AFB_plugin *plugin;
109         AFB_plugin *(*pluginRegisterFct) (void);
110         void *handle;
111         size_t len;
112
113         // This is a loadable library let's check if it's a plugin
114         handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);
115         if (handle == NULL) {
116                 fprintf(stderr, "[%s] not loadable, continuing...\n", path);
117                 goto error;
118         }
119
120         /* retrieves the register function */
121         pluginRegisterFct = dlsym(handle, plugin_register_function);
122         if (!pluginRegisterFct) {
123                 fprintf(stderr, "[%s] not an AFB plugin, continuing...\n", path);
124                 goto error2;
125         }
126         if (verbose)
127                 fprintf(stderr, "[%s] is a valid AFB plugin\n", path);
128
129         /* allocates enough memory */
130         apis = realloc(apis_array, ((unsigned)apis_count + 1) * sizeof * apis);
131         if (apis == NULL) {
132                 fprintf(stderr, "ERROR: plugin [%s] memory missing. continuing...\n", path);
133                 goto error2;
134         }
135         apis_array = apis;
136
137         /* init the plugin */
138         plugin = pluginRegisterFct();
139         if (plugin == NULL) {
140                 fprintf(stderr, "ERROR: plugin [%s] register function failed. continuing...\n", path);
141                 goto error2;
142         }
143
144         /* check the returned structure */
145         if (plugin->type != AFB_PLUGIN_JSON) {
146                 fprintf(stderr, "ERROR: plugin [%s] invalid type %d...\n", path, plugin->type);
147                 goto error2;
148         }
149         if (plugin->prefix == NULL || *plugin->prefix == 0) {
150                 fprintf(stderr, "ERROR: plugin [%s] bad prefix...\n", path);
151                 goto error2;
152         }
153         if (plugin->info == NULL || *plugin->info == 0) {
154                 fprintf(stderr, "ERROR: plugin [%s] bad description...\n", path);
155                 goto error2;
156         }
157         if (plugin->apis == NULL) {
158                 fprintf(stderr, "ERROR: plugin [%s] no APIs...\n", path);
159                 goto error2;
160         }
161
162         /* check previously existing plugin */
163         len = strlen(plugin->prefix);
164         if (afb_apis_get_apiidx(plugin->prefix, len) >= 0) {
165                 fprintf(stderr, "ERROR: plugin [%s] prefix %s duplicated...\n", path, plugin->prefix);
166                 goto error2;
167         }
168
169         /* record the plugin */
170         if (verbose)
171                 fprintf(stderr, "Loading plugin[%lu] prefix=[%s] info=%s\n", (unsigned long)apis_count, plugin->prefix, plugin->info);
172         apis = &apis_array[apis_count];
173         apis->plugin = plugin;
174         apis->prefixlen = len;
175         apis->prefix = plugin->prefix;
176         apis->handle = handle;
177         apis_count++;
178
179         return 0;
180
181 error2:
182         dlclose(handle);
183 error:
184         return -1;
185 }
186
187 static int adddirs(char path[PATH_MAX], size_t end)
188 {
189         int rc;
190         DIR *dir;
191         struct dirent ent, *result;
192         size_t len;
193
194         /* open the DIR now */
195         dir = opendir(path);
196         if (dir == NULL) {
197                 fprintf(stderr, "ERROR in scanning plugin directory %s, %m\n", path);
198                 return -1;
199         }
200         if (verbose)
201                 fprintf(stderr, "Scanning dir=[%s] for plugins\n", path);
202
203         /* scan each entry */
204         if (end)
205                 path[end++] = '/';
206         for (;;) {
207                 readdir_r(dir, &ent, &result);
208                 if (result == NULL)
209                         break;
210
211                 len = strlen(ent.d_name);
212                 if (len + end >= PATH_MAX) {
213                         fprintf(stderr, "path too long for %s\n", ent.d_name);
214                         continue;
215                 }
216                 memcpy(&path[end], ent.d_name, len+1);
217                 if (ent.d_type == DT_DIR) {
218                         /* case of directories */
219                         if (ent.d_name[0] == '.') {
220                                 if (len == 1)
221                                         continue;
222                                 if (ent.d_name[1] == '.' && len == 2)
223                                         continue;
224                         }
225                         rc = adddirs(path, end+len);;
226                 } else if (ent.d_type == DT_REG) {
227                         /* case of files */
228                         if (!strstr(ent.d_name, ".so"))
229                                 continue;
230                         rc = afb_apis_add_plugin(path);
231                 }
232         }
233         closedir(dir);
234         return 0;
235 }
236
237 int afb_apis_add_directory(const char *path)
238 {
239         size_t length;
240         char buffer[PATH_MAX];
241
242         length = strlen(path);
243         if (length >= sizeof(buffer)) {
244                 fprintf(stderr, "path too long %lu [%.99s...]\n", (unsigned long)length, path);
245                 return -1;
246         }
247
248         memcpy(buffer, path, length + 1);
249         return adddirs(buffer, length);
250 }
251
252 int afb_apis_add_path(const char *path)
253 {
254         struct stat st;
255         int rc;
256
257         rc = stat(path, &st);
258         if (rc < 0)
259                 fprintf(stderr, "Invalid plugin path [%s]: %m\n", path);
260         else if (S_ISDIR(st.st_mode))
261                 rc = afb_apis_add_directory(path);
262         else
263                 rc = afb_apis_add_plugin(path);
264         return rc;
265 }
266
267 int afb_apis_add_pathset(const char *pathset)
268 {
269         static char sep[] = ":";
270         char *ps, *p;
271         int rc;
272
273         ps = strdupa(pathset);
274         for (;;) {
275                 p = strsep(&ps, sep);
276                 if (!p)
277                         return 0;
278                 rc = afb_apis_add_path(p);
279         };
280 }
281
282 /*
283 // Check of apiurl is declare in this plugin and call it
284 extern __thread sigjmp_buf *error_handler;
285 static int callPluginApi(AFB_request * request)
286 {
287         sigjmp_buf jmpbuf, *older;
288
289         // save context before calling the API
290         status = setjmp(jmpbuf);
291         if (status != 0) {
292                 return 0;
293         }
294
295         // Trigger a timer to protect from unacceptable long time execution
296         if (request->config->apiTimeout > 0)
297                 alarm((unsigned)request->config->apiTimeout);
298
299         older = error_handler;
300         error_handler = &jmpbuf;
301         doCallPluginApi(request, apiidx, verbidx, context);
302         error_handler = older;
303
304         // cancel timeout and plugin signal handle before next call
305         alarm(0);
306         return 1;
307 }
308 */
309
310 static void handle(struct afb_req req, const struct api_desc *api, const struct AFB_restapi *verb)
311 {
312         AFB_request request;
313
314         request.uuid = request.url = "fake";
315         request.prefix = api->prefix;
316         request.method = verb->name;
317         request.context = NULL;
318         request.restfull = 0;
319         request.errcode = 0;
320         request.config = NULL;
321         request.areq = &req;
322
323         switch(verb->session) {
324         case AFB_SESSION_CREATE:
325         case AFB_SESSION_RENEW:
326                 /*if (check) new*/
327                 break;
328         case AFB_SESSION_CLOSE:
329         case AFB_SESSION_CHECK:
330                 /*check*/
331                 break;
332         case AFB_SESSION_NONE:
333         default:
334                 break;
335         }
336         verb->callback(&request, NULL);
337
338         if (verb->session == AFB_SESSION_CLOSE)
339                 /*del*/;
340 }
341
342 int afb_apis_handle(struct afb_req req, const char *api, size_t lenapi, const char *verb, size_t lenverb)
343 {
344         int i, j;
345         const struct api_desc *a;
346         const struct AFB_restapi *v;
347
348         a = apis_array;
349         for (i = 0 ; i < apis_count ; i++, a++) {
350                 if (a->prefixlen == lenapi && !strcasecmp(a->prefix, api)) {
351                         v = a->plugin->apis;
352                         for (j = 0 ; v->name ; j++, v++) {
353                                 if (!strncasecmp(v->name, verb, lenverb) && !v->name[lenverb]) {
354                                         handle(req, a, v);
355                                         return 1;
356                                 }
357                         }
358                         break;
359                 }
360         }
361         return 0;
362 }
363