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