1355894d74e9958c143a6cb94431db6801f3d253
[src/app-framework-binder.git] / src / afb-apis.c
1 /*
2  * Copyright (C) 2016, 2017 "IoT.bzh"
3  * Author "Fulup Ar Foll"
4  * Author José Bollo <jose.bollo@iot.bzh>
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *   http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 #define _GNU_SOURCE
20
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <errno.h>
25
26 #include "afb-session.h"
27 #include "verbose.h"
28 #include "afb-apis.h"
29 #include "afb-context.h"
30 #include "afb-xreq.h"
31 #include "jobs.h"
32
33 #include <afb/afb-req-itf.h>
34
35 /**
36  * Internal description of an api
37  */
38 struct api_desc {
39         const char *name;       /**< name of the api */
40         struct afb_api api;     /**< handler of the api */
41 };
42
43 static struct api_desc *apis_array = NULL;
44 static int apis_count = 0;
45 static int apis_timeout = 15;
46
47 /**
48  * Set the API timeout
49  * @param to the timeout in seconds
50  */
51 void afb_apis_set_timeout(int to)
52 {
53         apis_timeout = to;
54 }
55
56 /**
57  * Checks wether 'name' is a valid API name.
58  * @return 1 if valid, 0 otherwise
59  */
60 int afb_apis_is_valid_api_name(const char *name)
61 {
62         unsigned char c;
63
64         c = (unsigned char)*name;
65         if (c == 0)
66                 /* empty names aren't valid */
67                 return 0;
68
69         do {
70                 if (c < (unsigned char)'\x80') {
71                         switch(c) {
72                         default:
73                                 if (c > ' ')
74                                         break;
75                         case '"':
76                         case '#':
77                         case '%':
78                         case '&':
79                         case '\'':
80                         case '/':
81                         case '?':
82                         case '`':
83                         case '\\':
84                         case '\x7f':
85                                 return 0;
86                         }
87                 }
88                 c = (unsigned char)*++name;
89         } while(c != 0);
90         return 1;
91 }
92
93 /**
94  * Adds the api of 'name' described by 'api'.
95  * @param name the name of the api to add (have to survive, not copied!)
96  * @param api the api
97  * @returns 0 in case of success or -1 in case
98  * of error with errno set:
99  *   - EINVAL if name isn't valid
100  *   - EEXIST if name already registered
101  *   - ENOMEM when out of memory
102  */
103 int afb_apis_add(const char *name, struct afb_api api)
104 {
105         struct api_desc *apis;
106         int i, c;
107
108         /* Checks the api name */
109         if (!afb_apis_is_valid_api_name(name)) {
110                 ERROR("invalid api name forbidden (name is '%s')", name);
111                 errno = EINVAL;
112                 goto error;
113         }
114
115         /* check previously existing plugin */
116         for (i = 0 ; i < apis_count ; i++) {
117                 c = strcasecmp(apis_array[i].name, name);
118                 if (c == 0) {
119                         ERROR("api of name %s already exists", name);
120                         errno = EEXIST;
121                         goto error;
122                 }
123                 if (c > 0)
124                         break;
125         }
126
127         /* allocates enough memory */
128         apis = realloc(apis_array, ((unsigned)apis_count + 1) * sizeof * apis);
129         if (apis == NULL) {
130                 ERROR("out of memory");
131                 errno = ENOMEM;
132                 goto error;
133         }
134         apis_array = apis;
135
136         /* copy higher part of the array */
137         c = apis_count;
138         while (c > i) {
139                 apis_array[c] = apis_array[c - 1];
140                 c--;
141         }
142
143         /* record the plugin */
144         apis = &apis_array[i];
145         apis->api = api;
146         apis->name = name;
147         apis_count++;
148
149         NOTICE("API %s added", name);
150
151         return 0;
152
153 error:
154         return -1;
155 }
156
157 /**
158  * Search the 'api'.
159  * @param api the api of the verb
160  * @return the descriptor if found or NULL otherwise
161  */
162 static const struct api_desc *search(const char *api)
163 {
164         int i, c, up, lo;
165         const struct api_desc *a;
166
167         /* dichotomic search of the api */
168         /* initial slice */
169         lo = 0;
170         up = apis_count;
171         for (;;) {
172                 /* check remaining slice */
173                 if (lo >= up) {
174                         /* not found */
175                         return NULL;
176                 }
177                 /* check the mid of the slice */
178                 i = (lo + up) >> 1;
179                 a = &apis_array[i];
180                 c = strcasecmp(a->name, api);
181                 if (c == 0) {
182                         /* found */
183                         return a;
184                 }
185                 /* update the slice */
186                 if (c < 0)
187                         lo = i + 1;
188                 else
189                         up = i;
190         }
191 }
192
193 /**
194  * Starts a service by its 'api' name.
195  * @param api name of the service to start
196  * @param share_session if true start the servic"e in a shared session
197  *                      if false start it in its own session
198  * @param onneed if true start the service if possible, if false the api
199  *               must be a service
200  * @return a positive number on success
201  */
202 int afb_apis_start_service(const char *api, int share_session, int onneed)
203 {
204         int i;
205
206         for (i = 0 ; i < apis_count ; i++) {
207                 if (!strcasecmp(apis_array[i].name, api))
208                         return apis_array[i].api.service_start(apis_array[i].api.closure, share_session, onneed);
209         }
210         ERROR("can't find service %s", api);
211         errno = ENOENT;
212         return -1;
213 }
214
215 /**
216  * Starts all possible services but stops at first error.
217  * @param share_session if true start the servic"e in a shared session
218  *                      if false start it in its own session
219  * @return 0 on success or a negative number when an error is found
220  */
221 int afb_apis_start_all_services(int share_session)
222 {
223         int i, rc;
224
225         for (i = 0 ; i < apis_count ; i++) {
226                 rc = apis_array[i].api.service_start(apis_array[i].api.closure, share_session, 1);
227                 if (rc < 0)
228                         return rc;
229         }
230         return 0;
231 }
232
233 /**
234  * Internal direct dispatch of the request 'xreq'
235  * @param xreq the request to dispatch
236  */
237 static void do_call_direct(struct afb_xreq *xreq)
238 {
239         const struct api_desc *a;
240
241         /* search the api */
242         a = search(xreq->api);
243         if (!a)
244                 afb_xreq_fail_f(xreq, "unknown-api", "api %s not found", xreq->api);
245         else {
246                 xreq->context.api_key = a->api.closure;
247                 a->api.call(a->api.closure, xreq);
248         }
249 }
250
251 /**
252  * Asynchronous dispatch callback for the request 'xreq'
253  * @param signum 0 on normal flow or the signal number that interupted the normal flow
254  */
255 static void do_call_async(int signum, void *arg)
256 {
257         struct afb_xreq *xreq = arg;
258
259         if (signum != 0)
260                 afb_xreq_fail_f(xreq, "aborted", "signal %s(%d) caught", strsignal(signum), signum);
261         else {
262                 do_call_direct(xreq);
263         }
264         afb_xreq_unref(xreq);
265 }
266
267 /**
268  * Dispatch the request 'xreq' synchronously and directly.
269  * @param xreq the request to dispatch
270  */
271 void afb_apis_call_direct(struct afb_xreq *xreq)
272 {
273         afb_xreq_begin(xreq);
274         do_call_direct(xreq);
275 }
276
277 /**
278  * Dispatch the request 'xreq' asynchronously.
279  * @param xreq the request to dispatch
280  */
281 void afb_apis_call(struct afb_xreq *xreq)
282 {
283         int rc;
284
285         afb_xreq_begin(xreq);
286         afb_xreq_addref(xreq);
287         rc = jobs_queue(NULL, apis_timeout, do_call_async, xreq);
288         if (rc < 0) {
289                 /* TODO: allows or not to proccess it directly as when no threading? (see above) */
290                 ERROR("can't process job with threads: %m");
291                 afb_xreq_fail_f(xreq, "cancelled", "not able to create a job for the task");
292                 afb_xreq_unref(xreq);
293         }
294 }
295