Optimisation of xreq
[src/app-framework-binder.git] / src / main.c
1 /*
2  * Copyright (C) 2015, 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 #define NO_BINDING_VERBOSE_MACRO
21
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <sys/stat.h>
28 #include <sys/wait.h>
29
30 #include <json-c/json.h>
31
32 #include <systemd/sd-event.h>
33 #include <systemd/sd-daemon.h>
34
35 #include <afb/afb-binding.h>
36
37 #include "afb-config.h"
38 #include "afb-hswitch.h"
39 #include "afb-apis.h"
40 #include "afb-api-so.h"
41 #include "afb-api-dbus.h"
42 #include "afb-api-ws.h"
43 #include "afb-hsrv.h"
44 #include "afb-context.h"
45 #include "afb-hreq.h"
46 #include "afb-xreq.h"
47 #include "jobs.h"
48 #include "afb-session.h"
49 #include "verbose.h"
50 #include "afb-common.h"
51 #include "afb-hook.h"
52 #include "sd-fds.h"
53
54 /*
55    if SELF_PGROUP == 0 the launched command is the group leader
56    if SELF_PGROUP != 0 afb-daemon is the group leader
57 */
58 #define SELF_PGROUP 1
59
60 static struct afb_config *config;
61 static pid_t childpid;
62
63 /*----------------------------------------------------------
64  |   helpers for handling list of arguments
65  +--------------------------------------------------------- */
66
67 /*
68  * Calls the callback 'run' for each value of the 'list'
69  * until the callback returns 0 or the end of the list is reached.
70  * Returns either NULL if the end of the list is reached or a pointer
71  * to the item whose value made 'run' return 0.
72  * 'closure' is used for passing user data.
73  */
74 static struct afb_config_list *run_for_list(struct afb_config_list *list,
75                                             int (*run) (void *closure, char *value),
76                                             void *closure)
77 {
78         while (list && run(closure, list->value))
79                 list = list->next;
80         return list;
81 }
82
83 static int run_start(void *closure, char *value)
84 {
85         int (*starter) (const char *value) = closure;
86         return starter(value) >= 0;
87 }
88
89 static void start_list(struct afb_config_list *list,
90                        int (*starter) (const char *value), const char *message)
91 {
92         list = run_for_list(list, run_start, starter);
93         if (list) {
94                 ERROR("can't start %s %s", message, list->value);
95                 exit(1);
96         }
97 }
98
99 /*----------------------------------------------------------
100  | exit_handler
101  |   Handles on exit specific actions
102  +--------------------------------------------------------- */
103 static void exit_handler()
104 {
105         struct sigaction siga;
106
107         memset(&siga, 0, sizeof siga);
108         siga.sa_handler = SIG_IGN;
109         sigaction(SIGTERM, &siga, NULL);
110
111         if (SELF_PGROUP)
112                 killpg(0, SIGTERM);
113         else if (childpid > 0)
114                 killpg(childpid, SIGTERM);
115 }
116
117 static void on_sigterm(int signum, siginfo_t *info, void *uctx)
118 {
119         NOTICE("Received SIGTERM");
120         exit(0);
121 }
122
123 static void on_sighup(int signum, siginfo_t *info, void *uctx)
124 {
125         NOTICE("Received SIGHUP");
126         /* TODO */
127 }
128
129 static void setup_daemon()
130 {
131         struct sigaction siga;
132
133         /* install signal handlers */
134         memset(&siga, 0, sizeof siga);
135         siga.sa_flags = SA_SIGINFO;
136
137         siga.sa_sigaction = on_sigterm;
138         sigaction(SIGTERM, &siga, NULL);
139
140         siga.sa_sigaction = on_sighup;
141         sigaction(SIGHUP, &siga, NULL);
142
143         /* handle groups */
144         atexit(exit_handler);
145
146         /* ignore any SIGPIPE */
147         signal(SIGPIPE, SIG_IGN);
148 }
149
150 /*----------------------------------------------------------
151  | daemonize
152  |   set the process in background
153  +--------------------------------------------------------- */
154 static void daemonize()
155 {
156         int consoleFD;
157         int pid;
158
159         // open /dev/console to redirect output messAFBes
160         consoleFD = open(config->console, O_WRONLY | O_APPEND | O_CREAT, 0640);
161         if (consoleFD < 0) {
162                 ERROR("AFB-daemon cannot open /dev/console (use --foreground)");
163                 exit(1);
164         }
165         // fork process when running background mode
166         pid = fork();
167
168         // if fail nothing much to do
169         if (pid == -1) {
170                 ERROR("AFB-daemon Failed to fork son process");
171                 exit(1);
172         }
173         // if in father process, just leave
174         if (pid != 0)
175                 _exit(0);
176
177         // son process get all data in standalone mode
178         NOTICE("background mode [pid:%d console:%s]", getpid(),
179                config->console);
180
181         // redirect default I/O on console
182         close(2);
183         dup(consoleFD);         // redirect stderr
184         close(1);
185         dup(consoleFD);         // redirect stdout
186         close(0);               // no need for stdin
187         close(consoleFD);
188
189 #if 0
190         setsid();               // allow father process to fully exit
191         sleep(2);               // allow main to leave and release port
192 #endif
193 }
194
195 /*---------------------------------------------------------
196  | http server
197  |   Handles the HTTP server
198  +--------------------------------------------------------- */
199 static int init_alias(void *closure, char *spec)
200 {
201         struct afb_hsrv *hsrv = closure;
202         char *path = strchr(spec, ':');
203
204         if (path == NULL) {
205                 ERROR("Missing ':' in alias %s. Alias ignored", spec);
206                 return 1;
207         }
208         *path++ = 0;
209         INFO("Alias for url=%s to path=%s", spec, path);
210         return afb_hsrv_add_alias(hsrv, spec, afb_common_rootdir_get_fd(), path,
211                                   0, 0);
212 }
213
214 static int init_http_server(struct afb_hsrv *hsrv)
215 {
216         if (!afb_hsrv_add_handler
217             (hsrv, config->rootapi, afb_hswitch_websocket_switch, NULL, 20))
218                 return 0;
219
220         if (!afb_hsrv_add_handler
221             (hsrv, config->rootapi, afb_hswitch_apis, NULL, 10))
222                 return 0;
223
224         if (run_for_list(config->aliases, init_alias, hsrv))
225                 return 0;
226
227         if (config->roothttp != NULL) {
228                 if (!afb_hsrv_add_alias
229                     (hsrv, "", afb_common_rootdir_get_fd(), config->roothttp,
230                      -10, 1))
231                         return 0;
232         }
233
234         if (!afb_hsrv_add_handler
235             (hsrv, config->rootbase, afb_hswitch_one_page_api_redirect, NULL,
236              -20))
237                 return 0;
238
239         return 1;
240 }
241
242 static struct afb_hsrv *start_http_server()
243 {
244         int rc;
245         struct afb_hsrv *hsrv;
246
247         if (afb_hreq_init_download_path(config->uploaddir)) {
248                 ERROR("unable to set the upload directory %s", config->uploaddir);
249                 return NULL;
250         }
251
252         hsrv = afb_hsrv_create();
253         if (hsrv == NULL) {
254                 ERROR("memory allocation failure");
255                 return NULL;
256         }
257
258         if (!afb_hsrv_set_cache_timeout(hsrv, config->cacheTimeout)
259             || !init_http_server(hsrv)) {
260                 ERROR("initialisation of httpd failed");
261                 afb_hsrv_put(hsrv);
262                 return NULL;
263         }
264
265         NOTICE("Waiting port=%d rootdir=%s", config->httpdPort, config->rootdir);
266         NOTICE("Browser URL= http://localhost:%d", config->httpdPort);
267
268         rc = afb_hsrv_start(hsrv, (uint16_t) config->httpdPort, 15);
269         if (!rc) {
270                 ERROR("starting of httpd failed");
271                 afb_hsrv_put(hsrv);
272                 return NULL;
273         }
274
275         return hsrv;
276 }
277
278 /*---------------------------------------------------------
279  | execute_command
280  |   
281  +--------------------------------------------------------- */
282
283 static void on_sigchld(int signum, siginfo_t *info, void *uctx)
284 {
285         if (info->si_pid == childpid) {
286                 switch (info->si_code) {
287                 case CLD_EXITED:
288                 case CLD_KILLED:
289                 case CLD_DUMPED:
290                         childpid = 0;
291                         if (!SELF_PGROUP)
292                                 killpg(info->si_pid, SIGKILL);
293                         waitpid(info->si_pid, NULL, 0);
294                         exit(0);
295                 }
296         }
297 }
298
299 /*
300 # @@ @
301 # @p port
302 # @t token
303 */
304
305 #define SUBST_CHAR  '@'
306 #define SUBST_STR   "@"
307
308 static char *instanciate_string(char *arg, const char *port, const char *token)
309 {
310         char *resu, *it, *wr;
311         int chg, dif;
312
313         /* get the changes */
314         chg = 0;
315         dif = 0;
316         it = strchrnul(arg, SUBST_CHAR);
317         while (*it) {
318                 switch(*++it) {
319                 case 'p': chg++; dif += (int)strlen(port) - 2; break;
320                 case 't': chg++; dif += (int)strlen(token) - 2; break;
321                 case SUBST_CHAR: it++; chg++; dif--; break;
322                 default: break;
323                 }
324                 it = strchrnul(it, SUBST_CHAR);
325         }
326
327         /* return arg when no change */
328         if (!chg)
329                 return arg;
330
331         /* allocates the result */
332         resu = malloc((it - arg) + dif + 1);
333         if (!resu) {
334                 ERROR("out of memory");
335                 return NULL;
336         }
337
338         /* instanciate the arguments */
339         wr = resu;
340         for (;;) {
341                 it = strchrnul(arg, SUBST_CHAR);
342                 wr = mempcpy(wr, arg, it - arg);
343                 if (!*it)
344                         break;
345                 switch(*++it) {
346                 case 'p': wr = stpcpy(wr, port); break;
347                 case 't': wr = stpcpy(wr, token); break;
348                 default: *wr++ = SUBST_CHAR;
349                 case SUBST_CHAR: *wr++ = *it;
350                 }
351                 arg = ++it;
352         }
353
354         *wr = 0;
355         return resu;
356 }
357
358 static int instanciate_environ(const char *port, const char *token)
359 {
360         extern char **environ;
361         char *repl;
362         int i;
363
364         /* instanciate the environment */
365         for (i = 0 ; environ[i] ; i++) {
366                 repl = instanciate_string(environ[i], port, token);
367                 if (!repl)
368                         return -1;
369                 environ[i] = repl;
370         }
371         return 0;
372 }
373
374 static int instanciate_command_args(const char *port, const char *token)
375 {
376         char *repl;
377         int i;
378
379         /* instanciate the arguments */
380         for (i = 0 ; config->exec[i] ; i++) {
381                 repl = instanciate_string(config->exec[i], port, token);
382                 if (!repl)
383                         return -1;
384                 config->exec[i] = repl;
385         }
386         return 0;
387 }
388
389 static int execute_command()
390 {
391         struct sigaction siga;
392         char port[20];
393         int rc;
394
395         /* check whether a command is to execute or not */
396         if (!config->exec || !config->exec[0])
397                 return 0;
398
399         if (SELF_PGROUP)
400                 setpgid(0, 0);
401
402         /* install signal handler */
403         memset(&siga, 0, sizeof siga);
404         siga.sa_sigaction = on_sigchld;
405         siga.sa_flags = SA_SIGINFO;
406         sigaction(SIGCHLD, &siga, NULL);
407
408         /* fork now */
409         childpid = fork();
410         if (childpid)
411                 return 0;
412
413         /* compute the string for port */
414         if (config->httpdPort)
415                 rc = snprintf(port, sizeof port, "%d", config->httpdPort);
416         else
417                 rc = snprintf(port, sizeof port, "%cp", SUBST_CHAR);
418         if (rc < 0 || rc >= (int)(sizeof port)) {
419                 ERROR("port->txt failed");
420         }
421         else {
422                 /* instanciate arguments and environment */
423                 if (instanciate_command_args(port, config->token) >= 0
424                  && instanciate_environ(port, config->token) >= 0) {
425                         /* run */
426                         if (!SELF_PGROUP)
427                                 setpgid(0, 0);
428                         execv(config->exec[0], config->exec);
429                         ERROR("can't launch %s: %m", config->exec[0]);
430                 }
431         }
432         exit(1);
433         return -1;
434 }
435
436 /*---------------------------------------------------------
437  | startup calls
438  +--------------------------------------------------------- */
439
440 struct startup_req
441 {
442         struct afb_xreq xreq;
443         char *api;
444         char *verb;
445         struct afb_config_list *current;
446         struct afb_session *session;
447 };
448
449 static void startup_call_reply(struct afb_xreq *xreq, int iserror, struct json_object *obj)
450 {
451         struct startup_req *sreq = CONTAINER_OF_XREQ(struct startup_req, xreq);
452
453         if (!iserror)
454                 NOTICE("startup call %s returned %s", sreq->current->value, json_object_get_string(obj));
455         else {
456                 ERROR("startup call %s ERROR! %s", sreq->current->value, json_object_get_string(obj));
457                 exit(1);
458         }
459 }
460
461 static void startup_call_current(struct startup_req *sreq);
462
463 static void startup_call_unref(struct afb_xreq *xreq)
464 {
465         struct startup_req *sreq = CONTAINER_OF_XREQ(struct startup_req, xreq);
466
467         free(sreq->api);
468         free(sreq->verb);
469         json_object_put(sreq->xreq.json);
470         sreq->current = sreq->current->next;
471         if (sreq->current)
472                 startup_call_current(sreq);
473         else {
474                 afb_session_close(sreq->session);
475                 afb_session_unref(sreq->session);
476                 free(sreq);
477         }
478 }
479
480 static struct afb_xreq_query_itf startup_xreq_itf =
481 {
482         .reply = startup_call_reply,
483         .unref = startup_call_unref
484 };
485
486 static void startup_call_current(struct startup_req *sreq)
487 {
488         char *api, *verb, *json;
489
490         api = sreq->current->value;
491         verb = strchr(api, '/');
492         if (verb) {
493                 json = strchr(verb, ':');
494                 if (json) {
495                         memset(&sreq->xreq, 0, sizeof sreq->xreq);
496                         afb_xreq_init(&sreq->xreq, &startup_xreq_itf);
497                         afb_context_init(&sreq->xreq.context, sreq->session, NULL);
498                         sreq->xreq.context.validated = 1;
499                         sreq->api = strndup(api, verb - api);
500                         sreq->verb = strndup(verb + 1, json - verb - 1);
501                         sreq->xreq.api = sreq->api;
502                         sreq->xreq.verb = sreq->verb;
503                         sreq->xreq.json = json_tokener_parse(json + 1);
504                         if (sreq->api && sreq->verb && sreq->xreq.json) {
505                                 afb_apis_call(&sreq->xreq);
506                                 afb_xreq_unref(&sreq->xreq);
507                                 return;
508                         }
509                 }
510         }
511         ERROR("Bad call specification %s", sreq->current->value);
512         exit(1);
513 }
514
515 static void run_startup_calls()
516 {
517         struct afb_config_list *list;
518         struct startup_req *sreq;
519
520         list = config->calls;
521         if (list) {
522                 sreq = calloc(1, sizeof *sreq);
523                 sreq->session = afb_session_create("startup", 3600);
524                 sreq->current = list;
525                 startup_call_current(sreq);
526         }
527 }
528
529 /*---------------------------------------------------------
530  | job for starting the daemon
531  +--------------------------------------------------------- */
532
533 static void start()
534 {
535         struct afb_hsrv *hsrv;
536
537         // ------------------ sanity check ----------------------------------------
538         if (config->httpdPort <= 0) {
539                 ERROR("no port is defined");
540                 goto error;
541         }
542
543         /* set the directories */
544         mkdir(config->workdir, S_IRWXU | S_IRGRP | S_IXGRP);
545         if (chdir(config->workdir) < 0) {
546                 ERROR("Can't enter working dir %s", config->workdir);
547                 goto error;
548         }
549         if (afb_common_rootdir_set(config->rootdir) < 0) {
550                 ERROR("failed to set common root directory");
551                 goto error;
552         }
553
554         /* configure the daemon */
555         afb_apis_set_timeout(config->apiTimeout);
556         afb_session_init(config->nbSessionMax, config->cntxTimeout, config->token);
557         if (!afb_hreq_init_cookie(config->httpdPort, config->rootapi, config->cntxTimeout)) {
558                 ERROR("initialisation of cookies failed");
559                 goto error;
560         }
561
562         /* install hooks */
563         if (config->tracereq)
564                 afb_hook_create_xreq(NULL, NULL, NULL, config->tracereq, NULL, NULL);
565         if (config->traceditf)
566                 afb_hook_create_ditf(NULL, config->traceditf, NULL, NULL);
567
568         /* load bindings */
569         start_list(config->dbus_clients, afb_api_dbus_add_client, "the afb-dbus client");
570         start_list(config->ws_clients, afb_api_ws_add_client, "the afb-websocket client");
571         start_list(config->ldpaths, afb_api_so_add_pathset, "the binding path set");
572         start_list(config->so_bindings, afb_api_so_add_binding, "the binding");
573
574         start_list(config->dbus_servers, afb_api_dbus_add_server, "the afb-dbus service");
575         start_list(config->ws_servers, afb_api_ws_add_server, "the afb-websocket service");
576
577         DEBUG("Init config done");
578
579         /* start the services */
580         if (afb_apis_start_all_services(1) < 0)
581                 goto error;
582
583         /* start the HTTP server */
584         if (!config->noHttpd) {
585                 hsrv = start_http_server();
586                 if (hsrv == NULL)
587                         goto error;
588         }
589
590         /* run the command */
591         if (execute_command() < 0)
592                 goto error;
593
594         /* ready */
595         sd_notify(1, "READY=1");
596
597         /* run the startup calls */
598         run_startup_calls();
599         return;
600 error:
601         exit(1);
602 }
603
604 /*---------------------------------------------------------
605  | main
606  |   Parse option and launch action
607  +--------------------------------------------------------- */
608
609 int main(int argc, char *argv[])
610 {
611         // let's run this program with a low priority
612         nice(20);
613
614         sd_fds_init();
615
616         // ------------- Build session handler & init config -------
617         config = afb_config_parse_arguments(argc, argv);
618         INFO("running with pid %d", getpid());
619
620         // --------- run -----------
621         if (config->background) {
622                 // --------- in background mode -----------
623                 INFO("entering background mode");
624                 daemonize();
625         } else {
626                 // ---- in foreground mode --------------------
627                 INFO("entering foreground mode");
628         }
629
630         /* set the daemon environment */
631         setup_daemon();
632
633         /* enter job processing */
634         jobs_start(3, 0, 50, start);
635         WARNING("hoops returned from jobs_enter! [report bug]");
636         return 1;
637 }
638