Refactor of threading and signal monitor
[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 <systemd/sd-event.h>
31 #include <systemd/sd-daemon.h>
32
33 #include <afb/afb-binding.h>
34
35 #include "afb-config.h"
36 #include "afb-hswitch.h"
37 #include "afb-apis.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-context.h"
43 #include "afb-hreq.h"
44 #include "sig-monitor.h"
45 #include "jobs.h"
46 #include "afb-session.h"
47 #include "verbose.h"
48 #include "afb-common.h"
49 #include "afb-hook.h"
50 #include "sd-fds.h"
51
52 /*
53    if SELF_PGROUP == 0 the launched command is the group leader
54    if SELF_PGROUP != 0 afb-daemon is the group leader
55 */
56 #define SELF_PGROUP 1
57
58 static struct afb_config *config;
59 static pid_t childpid;
60
61 /*----------------------------------------------------------
62  |   helpers for handling list of arguments
63  +--------------------------------------------------------- */
64
65 /*
66  * Calls the callback 'run' for each value of the 'list'
67  * until the callback returns 0 or the end of the list is reached.
68  * Returns either NULL if the end of the list is reached or a pointer
69  * to the item whose value made 'run' return 0.
70  * 'closure' is used for passing user data.
71  */
72 static struct afb_config_list *run_for_list(struct afb_config_list *list,
73                                             int (*run) (void *closure, char *value),
74                                             void *closure)
75 {
76         while (list && run(closure, list->value))
77                 list = list->next;
78         return list;
79 }
80
81 static int run_start(void *closure, char *value)
82 {
83         int (*starter) (const char *value) = closure;
84         return starter(value) >= 0;
85 }
86
87 static void start_list(struct afb_config_list *list,
88                        int (*starter) (const char *value), const char *message)
89 {
90         list = run_for_list(list, run_start, starter);
91         if (list) {
92                 ERROR("can't start %s %s", message, list->value);
93                 exit(1);
94         }
95 }
96
97 /*----------------------------------------------------------
98  | exit_handler
99  |   Handles on exit specific actions
100  +--------------------------------------------------------- */
101 static void exit_handler()
102 {
103         /* TODO: check whether using SIGHUP isn't better */
104         if (SELF_PGROUP)
105                 killpg(0, SIGKILL);
106         else if (childpid > 0)
107                 killpg(childpid, SIGKILL);
108 }
109
110 /*----------------------------------------------------------
111  | daemonize
112  |   set the process in background
113  +--------------------------------------------------------- */
114 static void daemonize()
115 {
116         int consoleFD;
117         int pid;
118
119         // open /dev/console to redirect output messAFBes
120         consoleFD = open(config->console, O_WRONLY | O_APPEND | O_CREAT, 0640);
121         if (consoleFD < 0) {
122                 ERROR("AFB-daemon cannot open /dev/console (use --foreground)");
123                 exit(1);
124         }
125         // fork process when running background mode
126         pid = fork();
127
128         // if fail nothing much to do
129         if (pid == -1) {
130                 ERROR("AFB-daemon Failed to fork son process");
131                 exit(1);
132         }
133         // if in father process, just leave
134         if (pid != 0)
135                 _exit(0);
136
137         // son process get all data in standalone mode
138         NOTICE("background mode [pid:%d console:%s]", getpid(),
139                config->console);
140
141         // redirect default I/O on console
142         close(2);
143         dup(consoleFD);         // redirect stderr
144         close(1);
145         dup(consoleFD);         // redirect stdout
146         close(0);               // no need for stdin
147         close(consoleFD);
148
149 #if 0
150         setsid();               // allow father process to fully exit
151         sleep(2);               // allow main to leave and release port
152 #endif
153 }
154
155 /*---------------------------------------------------------
156  | http server
157  |   Handles the HTTP server
158  +--------------------------------------------------------- */
159 static int init_alias(void *closure, char *spec)
160 {
161         struct afb_hsrv *hsrv = closure;
162         char *path = strchr(spec, ':');
163
164         if (path == NULL) {
165                 ERROR("Missing ':' in alias %s. Alias ignored", spec);
166                 return 1;
167         }
168         *path++ = 0;
169         INFO("Alias for url=%s to path=%s", spec, path);
170         return afb_hsrv_add_alias(hsrv, spec, afb_common_rootdir_get_fd(), path,
171                                   0, 0);
172 }
173
174 static int init_http_server(struct afb_hsrv *hsrv)
175 {
176         if (!afb_hsrv_add_handler
177             (hsrv, config->rootapi, afb_hswitch_websocket_switch, NULL, 20))
178                 return 0;
179
180         if (!afb_hsrv_add_handler
181             (hsrv, config->rootapi, afb_hswitch_apis, NULL, 10))
182                 return 0;
183
184         if (run_for_list(config->aliases, init_alias, hsrv))
185                 return 0;
186
187         if (config->roothttp != NULL) {
188                 if (!afb_hsrv_add_alias
189                     (hsrv, "", afb_common_rootdir_get_fd(), config->roothttp,
190                      -10, 1))
191                         return 0;
192         }
193
194         if (!afb_hsrv_add_handler
195             (hsrv, config->rootbase, afb_hswitch_one_page_api_redirect, NULL,
196              -20))
197                 return 0;
198
199         return 1;
200 }
201
202 static struct afb_hsrv *start_http_server()
203 {
204         int rc;
205         struct afb_hsrv *hsrv;
206
207         if (afb_hreq_init_download_path(config->uploaddir)) {
208                 ERROR("unable to set the upload directory %s", config->uploaddir);
209                 return NULL;
210         }
211
212         hsrv = afb_hsrv_create();
213         if (hsrv == NULL) {
214                 ERROR("memory allocation failure");
215                 return NULL;
216         }
217
218         if (!afb_hsrv_set_cache_timeout(hsrv, config->cacheTimeout)
219             || !init_http_server(hsrv)) {
220                 ERROR("initialisation of httpd failed");
221                 afb_hsrv_put(hsrv);
222                 return NULL;
223         }
224
225         NOTICE("Waiting port=%d rootdir=%s", config->httpdPort, config->rootdir);
226         NOTICE("Browser URL= http:/*localhost:%d", config->httpdPort);
227
228         rc = afb_hsrv_start(hsrv, (uint16_t) config->httpdPort, 15);
229         if (!rc) {
230                 ERROR("starting of httpd failed");
231                 afb_hsrv_put(hsrv);
232                 return NULL;
233         }
234
235         return hsrv;
236 }
237
238 /*---------------------------------------------------------
239  | execute_command
240  |   
241  +--------------------------------------------------------- */
242
243 static void on_sigchld(int signum, siginfo_t *info, void *uctx)
244 {
245         if (info->si_pid == childpid) {
246                 switch (info->si_code) {
247                 case CLD_EXITED:
248                 case CLD_KILLED:
249                 case CLD_DUMPED:
250                         childpid = 0;
251                         if (!SELF_PGROUP)
252                                 killpg(info->si_pid, SIGKILL);
253                         waitpid(info->si_pid, NULL, 0);
254                         exit(0);
255                 }
256         }
257 }
258
259 /*
260 # @@ @
261 # @p port
262 # @t token
263 */
264
265 #define SUBST_CHAR  '@'
266 #define SUBST_STR   "@"
267
268 static char *instanciate_string(char *arg, const char *port, const char *token)
269 {
270         char *resu, *it, *wr;
271         int chg, dif;
272
273         /* get the changes */
274         chg = 0;
275         dif = 0;
276         it = strchrnul(arg, SUBST_CHAR);
277         while (*it) {
278                 switch(*++it) {
279                 case 'p': chg++; dif += (int)strlen(port) - 2; break;
280                 case 't': chg++; dif += (int)strlen(token) - 2; break;
281                 case SUBST_CHAR: it++; chg++; dif--; break;
282                 default: break;
283                 }
284                 it = strchrnul(it, SUBST_CHAR);
285         }
286
287         /* return arg when no change */
288         if (!chg)
289                 return arg;
290
291         /* allocates the result */
292         resu = malloc((it - arg) + dif + 1);
293         if (!resu) {
294                 ERROR("out of memory");
295                 return NULL;
296         }
297
298         /* instanciate the arguments */
299         wr = resu;
300         for (;;) {
301                 it = strchrnul(arg, SUBST_CHAR);
302                 wr = mempcpy(wr, arg, it - arg);
303                 if (!*it)
304                         break;
305                 switch(*++it) {
306                 case 'p': wr = stpcpy(wr, port); break;
307                 case 't': wr = stpcpy(wr, token); break;
308                 default: *wr++ = SUBST_CHAR;
309                 case SUBST_CHAR: *wr++ = *it;
310                 }
311                 arg = ++it;
312         }
313
314         *wr = 0;
315         return resu;
316 }
317
318 static int instanciate_environ(const char *port, const char *token)
319 {
320         extern char **environ;
321         char *repl;
322         int i;
323
324         /* instanciate the environment */
325         for (i = 0 ; environ[i] ; i++) {
326                 repl = instanciate_string(environ[i], port, token);
327                 if (!repl)
328                         return -1;
329                 environ[i] = repl;
330         }
331         return 0;
332 }
333
334 static int instanciate_command_args(const char *port, const char *token)
335 {
336         char *repl;
337         int i;
338
339         /* instanciate the arguments */
340         for (i = 0 ; config->exec[i] ; i++) {
341                 repl = instanciate_string(config->exec[i], port, token);
342                 if (!repl)
343                         return -1;
344                 config->exec[i] = repl;
345         }
346         return 0;
347 }
348
349 static int execute_command()
350 {
351         struct sigaction siga;
352         char port[20];
353         int rc;
354
355         /* check whether a command is to execute or not */
356         if (!config->exec || !config->exec[0])
357                 return 0;
358
359         if (SELF_PGROUP)
360                 setpgid(0, 0);
361
362         /* install signal handler */
363         memset(&siga, 0, sizeof siga);
364         siga.sa_sigaction = on_sigchld;
365         siga.sa_flags = SA_SIGINFO;
366         sigaction(SIGCHLD, &siga, NULL);
367
368         /* fork now */
369         childpid = fork();
370         if (childpid)
371                 return 0;
372
373         /* compute the string for port */
374         if (config->httpdPort)
375                 rc = snprintf(port, sizeof port, "%d", config->httpdPort);
376         else
377                 rc = snprintf(port, sizeof port, "%cp", SUBST_CHAR);
378         if (rc < 0 || rc >= (int)(sizeof port)) {
379                 ERROR("port->txt failed");
380         }
381         else {
382                 /* instanciate arguments and environment */
383                 if (instanciate_command_args(port, config->token) >= 0
384                  && instanciate_environ(port, config->token) >= 0) {
385                         /* run */
386                         if (!SELF_PGROUP)
387                                 setpgid(0, 0);
388                         execv(config->exec[0], config->exec);
389                         ERROR("can't launch %s: %m", config->exec[0]);
390                 }
391         }
392         exit(1);
393         return -1;
394 }
395
396 /*---------------------------------------------------------
397  | main
398  |   Parse option and launch action
399  +--------------------------------------------------------- */
400
401 int main(int argc, char *argv[])
402 {
403         struct afb_hsrv *hsrv;
404         struct sd_event *eventloop;
405
406         LOGAUTH("afb-daemon");
407
408         sd_fds_init();
409
410         // ------------- Build session handler & init config -------
411         config = afb_config_parse_arguments(argc, argv);
412         atexit(exit_handler);
413
414         // ------------------ sanity check ----------------------------------------
415         if (config->httpdPort <= 0) {
416                 ERROR("no port is defined");
417                 exit(1);
418         }
419
420         mkdir(config->workdir, S_IRWXU | S_IRGRP | S_IXGRP);
421         if (chdir(config->workdir) < 0) {
422                 ERROR("Can't enter working dir %s", config->workdir);
423                 exit(1);
424         }
425
426         afb_api_so_set_timeout(config->apiTimeout);
427         start_list(config->dbus_clients, afb_api_dbus_add_client, "the afb-dbus client");
428         start_list(config->ws_clients, afb_api_ws_add_client, "the afb-websocket client");
429         start_list(config->ldpaths, afb_api_so_add_pathset, "the binding path set");
430         start_list(config->so_bindings, afb_api_so_add_binding, "the binding");
431
432         afb_session_init(config->nbSessionMax, config->cntxTimeout, config->token, afb_apis_count());
433
434         start_list(config->dbus_servers, afb_api_dbus_add_server, "the afb-dbus service");
435         start_list(config->ws_servers, afb_api_ws_add_server, "the afb-websocket service");
436
437         if (!afb_hreq_init_cookie(config->httpdPort, config->rootapi, config->cntxTimeout)) {
438                 ERROR("initialisation of cookies failed");
439                 exit(1);
440         }
441
442         if (sig_monitor_init() < 0) {
443                 ERROR("failed to initialise signal handlers");
444                 return 1;
445         }
446
447         // set the root dir
448         if (afb_common_rootdir_set(config->rootdir) < 0) {
449                 ERROR("failed to set common root directory");
450                 return 1;
451         }
452
453         if (jobs_init(3, 1, 20) < 0) {
454                 ERROR("failed to initialise threading");
455                 return 1;
456         }
457         // let's run this program with a low priority
458         nice(20);
459
460         // ------------------ Finaly Process Commands -----------------------------
461         // let's not take the risk to run as ROOT
462         //if (getuid() == 0)  goto errorNoRoot;
463
464         DEBUG("Init config done");
465
466         // --------- run -----------
467         if (config->background) {
468                 // --------- in background mode -----------
469                 INFO("entering background mode");
470                 daemonize();
471         } else {
472                 // ---- in foreground mode --------------------
473                 INFO("entering foreground mode");
474         }
475
476         /* ignore any SIGPIPE */
477         signal(SIGPIPE, SIG_IGN);
478
479         /* install trace of requests */
480         if (config->tracereq)
481                 afb_hook_req_create(NULL, NULL, NULL, config->tracereq, NULL, NULL);
482
483         /* start the services */
484         if (afb_apis_start_all_services(1) < 0)
485                 exit(1);
486
487         /* start the HTTP server */
488         if (!config->noHttpd) {
489                 hsrv = start_http_server();
490                 if (hsrv == NULL)
491                         exit(1);
492         }
493
494         /* run the command */
495         if (execute_command() < 0)
496                 exit(1);
497
498         /* signal that ready */
499         if (config->readyfd != 0) {
500                 static const char readystr[] = "READY=1";
501                 write(config->readyfd, readystr, sizeof(readystr) - 1);
502                 close(config->readyfd);
503         }
504
505         // infinite loop
506         eventloop = afb_common_get_event_loop();
507         sd_notify(1, "READY=1");
508         for (;;)
509                 sd_event_run(eventloop, 30000000);
510
511         WARNING("hoops returned from infinite loop [report bug]");
512
513         return 0;
514 }