2 * Copyright (C) 2015, 2016, 2017 "IoT.bzh"
3 * Author "Fulup Ar Foll"
4 * Author José Bollo <jose.bollo@iot.bzh>
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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 #define AFB_BINDING_PRAGMA_NO_VERBOSE_MACRO
32 #include <json-c/json.h>
34 #include <systemd/sd-daemon.h>
36 #include "afb-config.h"
37 #include "afb-hswitch.h"
38 #include "afb-apiset.h"
39 #include "afb-api-so.h"
40 #include "afb-api-dbus.h"
41 #include "afb-api-ws.h"
46 #include "afb-session.h"
48 #include "afb-common.h"
49 #include "afb-monitor.h"
54 if SELF_PGROUP == 0 the launched command is the group leader
55 if SELF_PGROUP != 0 afb-daemon is the group leader
59 struct afb_apiset *main_apiset;
61 static struct afb_config *config;
62 static pid_t childpid;
64 /*----------------------------------------------------------
65 | helpers for handling list of arguments
66 +--------------------------------------------------------- */
69 * Calls the callback 'run' for each value of the 'list'
70 * until the callback returns 0 or the end of the list is reached.
71 * Returns either NULL if the end of the list is reached or a pointer
72 * to the item whose value made 'run' return 0.
73 * 'closure' is used for passing user data.
75 static struct afb_config_list *run_for_list(struct afb_config_list *list,
76 int (*run) (void *closure, char *value),
79 while (list && run(closure, list->value))
84 static int run_start(void *closure, char *value)
86 int (*starter) (const char *value, struct afb_apiset *apiset) = closure;
87 return starter(value, main_apiset) >= 0;
90 static void apiset_start_list(struct afb_config_list *list,
91 int (*starter) (const char *value, struct afb_apiset *apiset), const char *message)
93 list = run_for_list(list, run_start, starter);
95 ERROR("can't start %s %s", message, list->value);
100 /*----------------------------------------------------------
102 | Handles on exit specific actions
103 +--------------------------------------------------------- */
104 static void exit_handler()
106 struct sigaction siga;
108 memset(&siga, 0, sizeof siga);
109 siga.sa_handler = SIG_IGN;
110 sigaction(SIGTERM, &siga, NULL);
114 else if (childpid > 0)
115 killpg(childpid, SIGTERM);
119 static void on_sigterm(int signum, siginfo_t *info, void *uctx)
121 NOTICE("Received SIGTERM");
125 static void on_sighup(int signum, siginfo_t *info, void *uctx)
127 NOTICE("Received SIGHUP");
131 static void setup_daemon()
133 struct sigaction siga;
135 /* install signal handlers */
136 memset(&siga, 0, sizeof siga);
137 siga.sa_flags = SA_SIGINFO;
139 siga.sa_sigaction = on_sigterm;
140 sigaction(SIGTERM, &siga, NULL);
142 siga.sa_sigaction = on_sighup;
143 sigaction(SIGHUP, &siga, NULL);
146 atexit(exit_handler);
148 /* ignore any SIGPIPE */
149 signal(SIGPIPE, SIG_IGN);
152 /*----------------------------------------------------------
154 | set the process in background
155 +--------------------------------------------------------- */
156 static void daemonize()
161 // open /dev/console to redirect output messAFBes
162 consoleFD = open(config->console, O_WRONLY | O_APPEND | O_CREAT, 0640);
164 ERROR("AFB-daemon cannot open /dev/console (use --foreground)");
167 // fork process when running background mode
170 // if fail nothing much to do
172 ERROR("AFB-daemon Failed to fork son process");
175 // if in father process, just leave
179 // son process get all data in standalone mode
180 NOTICE("background mode [pid:%d console:%s]", getpid(),
183 // redirect default I/O on console
185 dup(consoleFD); // redirect stderr
187 dup(consoleFD); // redirect stdout
188 close(0); // no need for stdin
192 setsid(); // allow father process to fully exit
193 sleep(2); // allow main to leave and release port
197 /*---------------------------------------------------------
199 | Handles the HTTP server
200 +--------------------------------------------------------- */
201 static int init_alias(void *closure, char *spec)
203 struct afb_hsrv *hsrv = closure;
204 char *path = strchr(spec, ':');
207 ERROR("Missing ':' in alias %s. Alias ignored", spec);
211 INFO("Alias for url=%s to path=%s", spec, path);
212 return afb_hsrv_add_alias(hsrv, spec, afb_common_rootdir_get_fd(), path,
216 static int init_http_server(struct afb_hsrv *hsrv)
218 if (!afb_hsrv_add_handler
219 (hsrv, config->rootapi, afb_hswitch_websocket_switch, main_apiset, 20))
222 if (!afb_hsrv_add_handler
223 (hsrv, config->rootapi, afb_hswitch_apis, main_apiset, 10))
226 if (run_for_list(config->aliases, init_alias, hsrv))
229 if (config->roothttp != NULL) {
230 if (!afb_hsrv_add_alias
231 (hsrv, "", afb_common_rootdir_get_fd(), config->roothttp,
236 if (!afb_hsrv_add_handler
237 (hsrv, config->rootbase, afb_hswitch_one_page_api_redirect, NULL,
244 static struct afb_hsrv *start_http_server()
247 struct afb_hsrv *hsrv;
249 if (afb_hreq_init_download_path(config->uploaddir)) {
250 ERROR("unable to set the upload directory %s", config->uploaddir);
254 hsrv = afb_hsrv_create();
256 ERROR("memory allocation failure");
260 if (!afb_hsrv_set_cache_timeout(hsrv, config->cacheTimeout)
261 || !init_http_server(hsrv)) {
262 ERROR("initialisation of httpd failed");
267 NOTICE("Waiting port=%d rootdir=%s", config->httpdPort, config->rootdir);
268 NOTICE("Browser URL= http://localhost:%d", config->httpdPort);
270 rc = afb_hsrv_start(hsrv, (uint16_t) config->httpdPort, 15);
272 ERROR("starting of httpd failed");
280 /*---------------------------------------------------------
283 +--------------------------------------------------------- */
285 static void on_sigchld(int signum, siginfo_t *info, void *uctx)
287 if (info->si_pid == childpid) {
288 switch (info->si_code) {
294 killpg(info->si_pid, SIGKILL);
295 waitpid(info->si_pid, NULL, 0);
307 #define SUBST_CHAR '@'
308 #define SUBST_STR "@"
310 static char *instanciate_string(char *arg, const char *port, const char *token)
312 char *resu, *it, *wr;
315 /* get the changes */
318 it = strchrnul(arg, SUBST_CHAR);
321 case 'p': chg++; dif += (int)strlen(port) - 2; break;
322 case 't': chg++; dif += (int)strlen(token) - 2; break;
323 case SUBST_CHAR: it++; chg++; dif--; break;
326 it = strchrnul(it, SUBST_CHAR);
329 /* return arg when no change */
333 /* allocates the result */
334 resu = malloc((it - arg) + dif + 1);
336 ERROR("out of memory");
340 /* instanciate the arguments */
343 it = strchrnul(arg, SUBST_CHAR);
344 wr = mempcpy(wr, arg, it - arg);
348 case 'p': wr = stpcpy(wr, port); break;
349 case 't': wr = stpcpy(wr, token); break;
350 default: *wr++ = SUBST_CHAR;
351 case SUBST_CHAR: *wr++ = *it;
360 static int instanciate_environ(const char *port, const char *token)
362 extern char **environ;
366 /* instanciate the environment */
367 for (i = 0 ; environ[i] ; i++) {
368 repl = instanciate_string(environ[i], port, token);
376 static int instanciate_command_args(const char *port, const char *token)
381 /* instanciate the arguments */
382 for (i = 0 ; config->exec[i] ; i++) {
383 repl = instanciate_string(config->exec[i], port, token);
386 config->exec[i] = repl;
391 static int execute_command()
393 struct sigaction siga;
397 /* check whether a command is to execute or not */
398 if (!config->exec || !config->exec[0])
404 /* install signal handler */
405 memset(&siga, 0, sizeof siga);
406 siga.sa_sigaction = on_sigchld;
407 siga.sa_flags = SA_SIGINFO;
408 sigaction(SIGCHLD, &siga, NULL);
415 /* compute the string for port */
416 if (config->httpdPort)
417 rc = snprintf(port, sizeof port, "%d", config->httpdPort);
419 rc = snprintf(port, sizeof port, "%cp", SUBST_CHAR);
420 if (rc < 0 || rc >= (int)(sizeof port)) {
421 ERROR("port->txt failed");
424 /* instanciate arguments and environment */
425 if (instanciate_command_args(port, config->token) >= 0
426 && instanciate_environ(port, config->token) >= 0) {
430 execv(config->exec[0], config->exec);
431 ERROR("can't launch %s: %m", config->exec[0]);
438 /*---------------------------------------------------------
440 +--------------------------------------------------------- */
444 struct afb_xreq xreq;
447 struct afb_config_list *current;
448 struct afb_session *session;
451 static void startup_call_reply(struct afb_xreq *xreq, int iserror, struct json_object *obj)
453 struct startup_req *sreq = CONTAINER_OF_XREQ(struct startup_req, xreq);
456 NOTICE("startup call %s returned %s", sreq->current->value, json_object_get_string(obj));
458 ERROR("startup call %s ERROR! %s", sreq->current->value, json_object_get_string(obj));
463 static void startup_call_current(struct startup_req *sreq);
465 static void startup_call_unref(struct afb_xreq *xreq)
467 struct startup_req *sreq = CONTAINER_OF_XREQ(struct startup_req, xreq);
471 json_object_put(sreq->xreq.json);
472 sreq->current = sreq->current->next;
474 startup_call_current(sreq);
476 afb_session_close(sreq->session);
477 afb_session_unref(sreq->session);
482 static struct afb_xreq_query_itf startup_xreq_itf =
484 .reply = startup_call_reply,
485 .unref = startup_call_unref
488 static void startup_call_current(struct startup_req *sreq)
490 char *api, *verb, *json;
492 api = sreq->current->value;
493 verb = strchr(api, '/');
495 json = strchr(verb, ':');
497 afb_xreq_init(&sreq->xreq, &startup_xreq_itf);
498 afb_context_init(&sreq->xreq.context, sreq->session, NULL);
499 sreq->xreq.context.validated = 1;
500 sreq->api = strndup(api, verb - api);
501 sreq->verb = strndup(verb + 1, json - verb - 1);
502 sreq->xreq.api = sreq->api;
503 sreq->xreq.verb = sreq->verb;
504 sreq->xreq.json = json_tokener_parse(json + 1);
505 if (sreq->api && sreq->verb && sreq->xreq.json) {
506 afb_xreq_process(&sreq->xreq, main_apiset);
511 ERROR("Bad call specification %s", sreq->current->value);
515 static void run_startup_calls()
517 struct afb_config_list *list;
518 struct startup_req *sreq;
520 list = config->calls;
522 sreq = calloc(1, sizeof *sreq);
523 sreq->session = afb_session_create("startup", 3600);
524 sreq->current = list;
525 startup_call_current(sreq);
529 /*---------------------------------------------------------
530 | job for starting the daemon
531 +--------------------------------------------------------- */
535 struct afb_hsrv *hsrv;
537 // ------------------ sanity check ----------------------------------------
538 if (config->httpdPort <= 0) {
539 ERROR("no port is defined");
543 /* set the directories */
544 mkdir(config->workdir, S_IRWXU | S_IRGRP | S_IXGRP);
545 if (chdir(config->workdir) < 0) {
546 ERROR("Can't enter working dir %s", config->workdir);
549 if (afb_common_rootdir_set(config->rootdir) < 0) {
550 ERROR("failed to set common root directory");
554 /* configure the daemon */
555 afb_session_init(config->nbSessionMax, config->cntxTimeout, config->token);
556 if (!afb_hreq_init_cookie(config->httpdPort, config->rootapi, config->cntxTimeout)) {
557 ERROR("initialisation of cookies failed");
560 main_apiset = afb_apiset_create("main", config->apiTimeout);
562 ERROR("can't create main api set");
565 if (afb_monitor_init() < 0) {
566 ERROR("failed to setup monitor");
571 if (config->tracereq)
572 afb_hook_create_xreq(NULL, NULL, NULL, config->tracereq, NULL, NULL);
573 if (config->traceditf)
574 afb_hook_create_ditf(NULL, config->traceditf, NULL, NULL);
577 apiset_start_list(config->dbus_clients, afb_api_dbus_add_client, "the afb-dbus client");
578 apiset_start_list(config->ws_clients, afb_api_ws_add_client, "the afb-websocket client");
579 apiset_start_list(config->ldpaths, afb_api_so_add_pathset, "the binding path set");
580 apiset_start_list(config->so_bindings, afb_api_so_add_binding, "the binding");
582 apiset_start_list(config->dbus_servers, afb_api_dbus_add_server, "the afb-dbus service");
583 apiset_start_list(config->ws_servers, afb_api_ws_add_server, "the afb-websocket service");
585 DEBUG("Init config done");
587 /* start the services */
588 if (afb_apiset_start_all_services(main_apiset, 1) < 0)
591 /* start the HTTP server */
592 if (!config->noHttpd) {
593 hsrv = start_http_server();
598 /* run the command */
599 if (execute_command() < 0)
603 sd_notify(1, "READY=1");
605 /* run the startup calls */
612 /*---------------------------------------------------------
614 | Parse option and launch action
615 +--------------------------------------------------------- */
617 int main(int argc, char *argv[])
619 // let's run this program with a low priority
624 // ------------- Build session handler & init config -------
625 config = afb_config_parse_arguments(argc, argv);
626 INFO("running with pid %d", getpid());
628 // --------- run -----------
629 if (config->background) {
630 // --------- in background mode -----------
631 INFO("entering background mode");
634 // ---- in foreground mode --------------------
635 INFO("entering foreground mode");
638 /* set the daemon environment */
641 /* enter job processing */
642 jobs_start(3, 0, 50, start);
643 WARNING("hoops returned from jobs_enter! [report bug]");