simplify main file
[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 <stdint.h>
24 #include <signal.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28 #include <sys/stat.h>
29 #include <sys/wait.h>
30
31 #include <json-c/json.h>
32
33 #include <systemd/sd-daemon.h>
34
35 #include "afb-config.h"
36 #include "afb-hswitch.h"
37 #include "afb-apiset.h"
38 #include "afb-api-so.h"
39 #include "afb-api-dbus.h"
40 #include "afb-api-ws.h"
41 #include "afb-hsrv.h"
42 #include "afb-hreq.h"
43 #include "afb-xreq.h"
44 #include "jobs.h"
45 #include "afb-session.h"
46 #include "verbose.h"
47 #include "afb-common.h"
48 #include "afb-hook.h"
49 #include "sd-fds.h"
50
51 /*
52    if SELF_PGROUP == 0 the launched command is the group leader
53    if SELF_PGROUP != 0 afb-daemon is the group leader
54 */
55 #define SELF_PGROUP 1
56
57 struct afb_apiset *main_apiset;
58
59 static struct afb_config *config;
60 static pid_t childpid;
61
62 /*----------------------------------------------------------
63  |   helpers for handling list of arguments
64  +--------------------------------------------------------- */
65
66 /*
67  * Calls the callback 'run' for each value of the 'list'
68  * until the callback returns 0 or the end of the list is reached.
69  * Returns either NULL if the end of the list is reached or a pointer
70  * to the item whose value made 'run' return 0.
71  * 'closure' is used for passing user data.
72  */
73 static struct afb_config_list *run_for_list(struct afb_config_list *list,
74                                             int (*run) (void *closure, char *value),
75                                             void *closure)
76 {
77         while (list && run(closure, list->value))
78                 list = list->next;
79         return list;
80 }
81
82 static int run_start(void *closure, char *value)
83 {
84         int (*starter) (const char *value, struct afb_apiset *apiset) = closure;
85         return starter(value, main_apiset) >= 0;
86 }
87
88 static void apiset_start_list(struct afb_config_list *list,
89                        int (*starter) (const char *value, struct afb_apiset *apiset), const char *message)
90 {
91         list = run_for_list(list, run_start, starter);
92         if (list) {
93                 ERROR("can't start %s %s", message, list->value);
94                 exit(1);
95         }
96 }
97
98 /*----------------------------------------------------------
99  | exit_handler
100  |   Handles on exit specific actions
101  +--------------------------------------------------------- */
102 static void exit_handler()
103 {
104         struct sigaction siga;
105
106         memset(&siga, 0, sizeof siga);
107         siga.sa_handler = SIG_IGN;
108         sigaction(SIGTERM, &siga, NULL);
109
110         if (SELF_PGROUP)
111                 killpg(0, SIGTERM);
112         else if (childpid > 0)
113                 killpg(childpid, SIGTERM);
114         exit(0);
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, main_apiset, 20))
218                 return 0;
219
220         if (!afb_hsrv_add_handler
221             (hsrv, config->rootapi, afb_hswitch_apis, main_apiset, 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                         afb_xreq_init(&sreq->xreq, &startup_xreq_itf);
496                         afb_context_init(&sreq->xreq.context, sreq->session, NULL);
497                         sreq->xreq.context.validated = 1;
498                         sreq->api = strndup(api, verb - api);
499                         sreq->verb = strndup(verb + 1, json - verb - 1);
500                         sreq->xreq.api = sreq->api;
501                         sreq->xreq.verb = sreq->verb;
502                         sreq->xreq.json = json_tokener_parse(json + 1);
503                         if (sreq->api && sreq->verb && sreq->xreq.json) {
504                                 afb_xreq_process(&sreq->xreq, main_apiset);
505                                 return;
506                         }
507                 }
508         }
509         ERROR("Bad call specification %s", sreq->current->value);
510         exit(1);
511 }
512
513 static void run_startup_calls()
514 {
515         struct afb_config_list *list;
516         struct startup_req *sreq;
517
518         list = config->calls;
519         if (list) {
520                 sreq = calloc(1, sizeof *sreq);
521                 sreq->session = afb_session_create("startup", 3600);
522                 sreq->current = list;
523                 startup_call_current(sreq);
524         }
525 }
526
527 /*---------------------------------------------------------
528  | job for starting the daemon
529  +--------------------------------------------------------- */
530
531 static void start()
532 {
533         struct afb_hsrv *hsrv;
534
535         // ------------------ sanity check ----------------------------------------
536         if (config->httpdPort <= 0) {
537                 ERROR("no port is defined");
538                 goto error;
539         }
540
541         /* set the directories */
542         mkdir(config->workdir, S_IRWXU | S_IRGRP | S_IXGRP);
543         if (chdir(config->workdir) < 0) {
544                 ERROR("Can't enter working dir %s", config->workdir);
545                 goto error;
546         }
547         if (afb_common_rootdir_set(config->rootdir) < 0) {
548                 ERROR("failed to set common root directory");
549                 goto error;
550         }
551
552         /* configure the daemon */
553         main_apiset = afb_apiset_create("main", config->apiTimeout, NULL);
554         if (!main_apiset) {
555                 ERROR("can't create main api set");
556                 goto error;
557         }
558         afb_session_init(config->nbSessionMax, config->cntxTimeout, config->token);
559         if (!afb_hreq_init_cookie(config->httpdPort, config->rootapi, config->cntxTimeout)) {
560                 ERROR("initialisation of cookies failed");
561                 goto error;
562         }
563
564         /* install hooks */
565         if (config->tracereq)
566                 afb_hook_create_xreq(NULL, NULL, NULL, config->tracereq, NULL, NULL);
567         if (config->traceditf)
568                 afb_hook_create_ditf(NULL, config->traceditf, NULL, NULL);
569
570         /* load bindings */
571         apiset_start_list(config->dbus_clients, afb_api_dbus_add_client, "the afb-dbus client");
572         apiset_start_list(config->ws_clients, afb_api_ws_add_client, "the afb-websocket client");
573         apiset_start_list(config->ldpaths, afb_api_so_add_pathset, "the binding path set");
574         apiset_start_list(config->so_bindings, afb_api_so_add_binding, "the binding");
575
576         apiset_start_list(config->dbus_servers, afb_api_dbus_add_server, "the afb-dbus service");
577         apiset_start_list(config->ws_servers, afb_api_ws_add_server, "the afb-websocket service");
578
579         DEBUG("Init config done");
580
581         /* start the services */
582         if (afb_apiset_start_all_services(main_apiset, 1) < 0)
583                 goto error;
584
585         /* start the HTTP server */
586         if (!config->noHttpd) {
587                 hsrv = start_http_server();
588                 if (hsrv == NULL)
589                         goto error;
590         }
591
592         /* run the command */
593         if (execute_command() < 0)
594                 goto error;
595
596         /* ready */
597         sd_notify(1, "READY=1");
598
599         /* run the startup calls */
600         run_startup_calls();
601         return;
602 error:
603         exit(1);
604 }
605
606 /*---------------------------------------------------------
607  | main
608  |   Parse option and launch action
609  +--------------------------------------------------------- */
610
611 int main(int argc, char *argv[])
612 {
613         // let's run this program with a low priority
614         nice(20);
615
616         sd_fds_init();
617
618         // ------------- Build session handler & init config -------
619         config = afb_config_parse_arguments(argc, argv);
620         INFO("running with pid %d", getpid());
621
622         // --------- run -----------
623         if (config->background) {
624                 // --------- in background mode -----------
625                 INFO("entering background mode");
626                 daemonize();
627         } else {
628                 // ---- in foreground mode --------------------
629                 INFO("entering foreground mode");
630         }
631
632         /* set the daemon environment */
633         setup_daemon();
634
635         /* enter job processing */
636         jobs_start(3, 0, 50, start);
637         WARNING("hoops returned from jobs_enter! [report bug]");
638         return 1;
639 }
640