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