Beginning of integration as systemd service
[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 "afb-sig-handler.h"
45 #include "afb-thread.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 int instanciate_command_args()
269 {
270         char *orig, *repl, *sub, *val, port[20];
271         int i, rc, r;
272         size_t s, l;
273
274         rc = snprintf(port, sizeof port, "%d", config->httpdPort);
275         if (rc < 0 || rc >= (int)(sizeof port))
276                 return -1;
277
278         for (i = 0 ; (orig = config->exec[i]) ; i++) {
279                 repl = 0;
280                 s = 0;
281                 for(;;) {
282                         sub = strchrnul(orig, SUBST_CHAR);
283                         l = sub - orig;
284                         if (repl)
285                                 repl = mempcpy(repl, orig, l);
286                         else
287                                 s += l;
288                         if (!*sub) {
289                                 /* at end */
290                                 if (repl || orig == config->exec[i])
291                                         break;
292                                 repl = malloc(1 + s);
293                                 if (!repl)
294                                         return -1;
295                                 orig = config->exec[i];
296                                 config->exec[i] = repl;
297                                 repl[s] = 0;
298                         } else {
299                                 r = 2;
300                                 switch(sub[1]) {
301                                 case 'p': val = port;  break;
302                                 case 't': val = config->token ? : ""; break;
303                                 default: r = 1;
304                                 case SUBST_CHAR: val = SUBST_STR; break;
305                                 }
306                                 orig = &sub[r];
307                                 l = strlen(val);
308                                 if (repl)
309                                         repl = mempcpy(repl, val, l);
310                                 else
311                                         s += l;
312                         }
313                 }
314         }
315         return 0;
316 }
317
318 static int execute_command()
319 {
320         struct sigaction siga;
321
322         /* check whether a command is to execute or not */
323         if (!config->exec || !config->exec[0])
324                 return 0;
325
326         if (SELF_PGROUP)
327                 setpgid(0, 0);
328
329         /* install signal handler */
330         memset(&siga, 0, sizeof siga);
331         siga.sa_sigaction = on_sigchld;
332         siga.sa_flags = SA_SIGINFO;
333         sigaction(SIGCHLD, &siga, NULL);
334
335         /* fork now */
336         childpid = fork();
337         if (childpid)
338                 return 0;
339
340         /* makes arguments */
341         if (instanciate_command_args() >= 0) {
342                 if (!SELF_PGROUP)
343                         setpgid(0, 0);
344                 execv(config->exec[0], config->exec);
345                 ERROR("can't launch %s: %m", config->exec[0]);
346         }
347         exit(1);
348         return -1;
349 }
350
351 /*---------------------------------------------------------
352  | main
353  |   Parse option and launch action
354  +--------------------------------------------------------- */
355
356 int main(int argc, char *argv[])
357 {
358         struct afb_hsrv *hsrv;
359         struct sd_event *eventloop;
360
361         LOGAUTH("afb-daemon");
362
363         sd_fds_init();
364
365         // ------------- Build session handler & init config -------
366         config = afb_config_parse_arguments(argc, argv);
367         atexit(exit_handler);
368
369         // ------------------ sanity check ----------------------------------------
370         if (config->httpdPort <= 0) {
371                 ERROR("no port is defined");
372                 exit(1);
373         }
374
375         mkdir(config->workdir, S_IRWXU | S_IRGRP | S_IXGRP);
376         if (chdir(config->workdir) < 0) {
377                 ERROR("Can't enter working dir %s", config->workdir);
378                 exit(1);
379         }
380
381         afb_api_so_set_timeout(config->apiTimeout);
382         start_list(config->dbus_clients, afb_api_dbus_add_client, "the afb-dbus client");
383         start_list(config->ws_clients, afb_api_ws_add_client, "the afb-websocket client");
384         start_list(config->ldpaths, afb_api_so_add_pathset, "the binding path set");
385         start_list(config->so_bindings, afb_api_so_add_binding, "the binding");
386
387         afb_session_init(config->nbSessionMax, config->cntxTimeout, config->token, afb_apis_count());
388
389         start_list(config->dbus_servers, afb_api_dbus_add_server, "the afb-dbus service");
390         start_list(config->ws_servers, afb_api_ws_add_server, "the afb-websocket service");
391
392         if (!afb_hreq_init_cookie(config->httpdPort, config->rootapi, config->cntxTimeout)) {
393                 ERROR("initialisation of cookies failed");
394                 exit(1);
395         }
396
397         if (afb_sig_handler_init() < 0) {
398                 ERROR("failed to initialise signal handlers");
399                 return 1;
400         }
401
402         // set the root dir
403         if (afb_common_rootdir_set(config->rootdir) < 0) {
404                 ERROR("failed to set common root directory");
405                 return 1;
406         }
407
408         if (afb_thread_init(3, 1, 20) < 0) {
409                 ERROR("failed to initialise threading");
410                 return 1;
411         }
412         // let's run this program with a low priority
413         nice(20);
414
415         // ------------------ Finaly Process Commands -----------------------------
416         // let's not take the risk to run as ROOT
417         //if (getuid() == 0)  goto errorNoRoot;
418
419         DEBUG("Init config done");
420
421         // --------- run -----------
422         if (config->background) {
423                 // --------- in background mode -----------
424                 INFO("entering background mode");
425                 daemonize();
426         } else {
427                 // ---- in foreground mode --------------------
428                 INFO("entering foreground mode");
429         }
430
431         /* ignore any SIGPIPE */
432         signal(SIGPIPE, SIG_IGN);
433
434         /* install trace of requests */
435         if (config->tracereq)
436                 afb_hook_req_create(NULL, NULL, NULL, config->tracereq, NULL, NULL);
437
438         /* start the services */
439         if (afb_apis_start_all_services(1) < 0)
440                 exit(1);
441
442         /* start the HTTP server */
443         if (!config->noHttpd) {
444                 hsrv = start_http_server();
445                 if (hsrv == NULL)
446                         exit(1);
447         }
448
449         /* run the command */
450         if (execute_command() < 0)
451                 exit(1);
452
453         /* signal that ready */
454         if (config->readyfd != 0) {
455                 static const char readystr[] = "READY=1";
456                 write(config->readyfd, readystr, sizeof(readystr) - 1);
457                 close(config->readyfd);
458         }
459
460         // infinite loop
461         eventloop = afb_common_get_event_loop();
462         sd_notify(1, "READY=1");
463         for (;;)
464                 sd_event_run(eventloop, 30000000);
465
466         WARNING("hoops returned from infinite loop [report bug]");
467
468         return 0;
469 }