2 * Copyright (C) 2015-2018 "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.
31 #if !defined(NO_CALL_PERSONALITY)
32 #include <sys/personality.h>
35 #include <json-c/json.h>
37 #include <systemd/sd-daemon.h>
39 #include "afb-config.h"
40 #include "afb-hswitch.h"
41 #include "afb-apiset.h"
42 #include "afb-autoset.h"
43 #include "afb-api-so.h"
44 #if defined(WITH_DBUS_TRANSPARENCY)
45 # include "afb-api-dbus.h"
47 #include "afb-api-ws.h"
52 #include "afb-session.h"
54 #include "afb-common.h"
55 #include "afb-monitor.h"
57 #include "afb-debug.h"
58 #include "process-name.h"
59 #if defined(WITH_SUPERVISION)
60 # include "afb-supervision.h"
64 if SELF_PGROUP == 0 the launched command is the group leader
65 if SELF_PGROUP != 0 afb-daemon is the group leader
69 struct afb_apiset *main_apiset;
70 struct afb_config *main_config;
72 static pid_t childpid;
74 /*----------------------------------------------------------
75 | helpers for handling list of arguments
76 +--------------------------------------------------------- */
79 * Calls the callback 'run' for each value of the 'list'
80 * until the callback returns 0 or the end of the list is reached.
81 * Returns either NULL if the end of the list is reached or a pointer
82 * to the item whose value made 'run' return 0.
83 * 'closure' is used for passing user data.
85 static struct afb_config_list *run_for_list(struct afb_config_list *list,
86 int (*run) (void *closure, char *value),
89 while (list && run(closure, list->value))
94 static int run_start(void *closure, char *value)
96 int (*starter) (const char *value, struct afb_apiset *declare_set, struct afb_apiset *call_set) = closure;
97 return starter(value, main_apiset, main_apiset) >= 0;
100 static void apiset_start_list(struct afb_config_list *list,
101 int (*starter) (const char *value, struct afb_apiset *declare_set, struct afb_apiset *call_set),
104 list = run_for_list(list, run_start, starter);
106 ERROR("can't start %s %s", message, list->value);
111 /*----------------------------------------------------------
113 | Handles on exit specific actions
114 +--------------------------------------------------------- */
115 static void exit_handler()
117 struct sigaction siga;
119 memset(&siga, 0, sizeof siga);
120 siga.sa_handler = SIG_IGN;
121 sigaction(SIGTERM, &siga, NULL);
125 else if (childpid > 0)
126 killpg(childpid, SIGTERM);
129 static void on_sigterm(int signum, siginfo_t *info, void *uctx)
131 NOTICE("Received SIGTERM");
135 static void on_sighup(int signum, siginfo_t *info, void *uctx)
137 NOTICE("Received SIGHUP");
141 static void setup_daemon()
143 struct sigaction siga;
145 /* install signal handlers */
146 memset(&siga, 0, sizeof siga);
147 siga.sa_flags = SA_SIGINFO;
149 siga.sa_sigaction = on_sigterm;
150 sigaction(SIGTERM, &siga, NULL);
152 siga.sa_sigaction = on_sighup;
153 sigaction(SIGHUP, &siga, NULL);
156 atexit(exit_handler);
158 /* ignore any SIGPIPE */
159 signal(SIGPIPE, SIG_IGN);
162 /*----------------------------------------------------------
164 | set the process in background
165 +--------------------------------------------------------- */
166 static void daemonize()
171 // open /dev/console to redirect output messAFBes
172 consoleFD = open(main_config->console, O_WRONLY | O_APPEND | O_CREAT, 0640);
174 ERROR("AFB-daemon cannot open /dev/console (use --foreground)");
177 // fork process when running background mode
180 // if fail nothing much to do
182 ERROR("AFB-daemon Failed to fork son process");
185 // if in father process, just leave
189 // son process get all data in standalone mode
190 NOTICE("background mode [pid:%d console:%s]", getpid(),
191 main_config->console);
193 // redirect default I/O on console
195 dup(consoleFD); // redirect stderr
197 dup(consoleFD); // redirect stdout
198 close(0); // no need for stdin
202 setsid(); // allow father process to fully exit
203 sleep(2); // allow main to leave and release port
207 /*---------------------------------------------------------
209 | Handles the HTTP server
210 +--------------------------------------------------------- */
211 static int init_alias(void *closure, char *spec)
213 struct afb_hsrv *hsrv = closure;
214 char *path = strchr(spec, ':');
217 ERROR("Missing ':' in alias %s. Alias ignored", spec);
221 INFO("Alias for url=%s to path=%s", spec, path);
222 return afb_hsrv_add_alias(hsrv, spec, afb_common_rootdir_get_fd(), path,
226 static int init_http_server(struct afb_hsrv *hsrv)
228 if (!afb_hsrv_add_handler
229 (hsrv, main_config->rootapi, afb_hswitch_websocket_switch, main_apiset, 20))
232 if (!afb_hsrv_add_handler
233 (hsrv, main_config->rootapi, afb_hswitch_apis, main_apiset, 10))
236 if (run_for_list(main_config->aliases, init_alias, hsrv))
239 if (main_config->roothttp != NULL) {
240 if (!afb_hsrv_add_alias
241 (hsrv, "", afb_common_rootdir_get_fd(), main_config->roothttp,
246 if (!afb_hsrv_add_handler
247 (hsrv, main_config->rootbase, afb_hswitch_one_page_api_redirect, NULL,
254 static struct afb_hsrv *start_http_server()
257 struct afb_hsrv *hsrv;
259 if (afb_hreq_init_download_path(main_config->uploaddir)) {
260 ERROR("unable to set the upload directory %s", main_config->uploaddir);
264 hsrv = afb_hsrv_create();
266 ERROR("memory allocation failure");
270 if (!afb_hsrv_set_cache_timeout(hsrv, main_config->cache_timeout)
271 || !init_http_server(hsrv)) {
272 ERROR("initialisation of httpd failed");
277 NOTICE("Waiting port=%d rootdir=%s", main_config->http_port, main_config->rootdir);
278 NOTICE("Browser URL= http://localhost:%d", main_config->http_port);
280 rc = afb_hsrv_start(hsrv, (uint16_t) main_config->http_port, 15);
282 ERROR("starting of httpd failed");
290 /*---------------------------------------------------------
292 +--------------------------------------------------------- */
294 static void on_sigchld(int signum, siginfo_t *info, void *uctx)
296 if (info->si_pid == childpid) {
297 switch (info->si_code) {
303 killpg(info->si_pid, SIGKILL);
304 waitpid(info->si_pid, NULL, 0);
316 #define SUBST_CHAR '@'
317 #define SUBST_STR "@"
319 static char *instanciate_string(char *arg, const char *port, const char *token)
321 char *resu, *it, *wr;
324 /* get the changes */
327 it = strchrnul(arg, SUBST_CHAR);
330 case 'p': chg++; dif += (int)strlen(port) - 2; break;
331 case 't': chg++; dif += (int)strlen(token) - 2; break;
332 case SUBST_CHAR: it++; chg++; dif--; break;
335 it = strchrnul(it, SUBST_CHAR);
338 /* return arg when no change */
342 /* allocates the result */
343 resu = malloc((it - arg) + dif + 1);
345 ERROR("out of memory");
349 /* instanciate the arguments */
352 it = strchrnul(arg, SUBST_CHAR);
353 wr = mempcpy(wr, arg, it - arg);
357 case 'p': wr = stpcpy(wr, port); break;
358 case 't': wr = stpcpy(wr, token); break;
359 default: *wr++ = SUBST_CHAR; /*@fallthrough@*/
360 case SUBST_CHAR: *wr++ = *it;
369 static int instanciate_environ(const char *port, const char *token)
371 extern char **environ;
375 /* instanciate the environment */
376 for (i = 0 ; environ[i] ; i++) {
377 repl = instanciate_string(environ[i], port, token);
385 static int instanciate_command_args(const char *port, const char *token)
390 /* instanciate the arguments */
391 for (i = 0 ; main_config->exec[i] ; i++) {
392 repl = instanciate_string(main_config->exec[i], port, token);
395 main_config->exec[i] = repl;
400 static int execute_command()
402 struct sigaction siga;
407 /* check whether a command is to execute or not */
408 if (!main_config->exec || !main_config->exec[0])
414 /* install signal handler */
415 memset(&siga, 0, sizeof siga);
416 siga.sa_sigaction = on_sigchld;
417 siga.sa_flags = SA_SIGINFO;
418 sigaction(SIGCHLD, &siga, NULL);
425 /* compute the string for port */
426 if (main_config->http_port)
427 rc = snprintf(port, sizeof port, "%d", main_config->http_port);
429 rc = snprintf(port, sizeof port, "%cp", SUBST_CHAR);
430 if (rc < 0 || rc >= (int)(sizeof port)) {
431 ERROR("port->txt failed");
434 /* instanciate arguments and environment */
435 token = afb_session_initial_token();
436 if (instanciate_command_args(port, token) >= 0
437 && instanciate_environ(port, token) >= 0) {
441 execv(main_config->exec[0], main_config->exec);
442 ERROR("can't launch %s: %m", main_config->exec[0]);
449 /*---------------------------------------------------------
451 +--------------------------------------------------------- */
455 struct afb_xreq xreq;
458 struct afb_config_list *current;
459 struct afb_session *session;
462 static void startup_call_reply(struct afb_xreq *xreq, struct json_object *object, const char *error, const char *info)
464 struct startup_req *sreq = CONTAINER_OF_XREQ(struct startup_req, xreq);
468 NOTICE("startup call %s returned %s (%s)", sreq->current->value, json_object_get_string(object), info);
469 json_object_put(object);
471 ERROR("startup call %s ERROR! %s (%s)", sreq->current->value, error, info);
476 static void startup_call_current(struct startup_req *sreq);
478 static void startup_call_unref(struct afb_xreq *xreq)
480 struct startup_req *sreq = CONTAINER_OF_XREQ(struct startup_req, xreq);
484 json_object_put(sreq->xreq.json);
485 sreq->current = sreq->current->next;
487 startup_call_current(sreq);
489 afb_session_close(sreq->session);
490 afb_session_unref(sreq->session);
495 static struct afb_xreq_query_itf startup_xreq_itf =
497 .reply = startup_call_reply,
498 .unref = startup_call_unref
501 static void startup_call_current(struct startup_req *sreq)
503 char *api, *verb, *json;
505 api = sreq->current->value;
506 verb = strchr(api, '/');
508 json = strchr(verb, ':');
510 afb_xreq_init(&sreq->xreq, &startup_xreq_itf);
511 afb_context_init(&sreq->xreq.context, sreq->session, NULL);
512 sreq->xreq.context.validated = 1;
513 sreq->api = strndup(api, verb - api);
514 sreq->verb = strndup(verb + 1, json - verb - 1);
515 sreq->xreq.request.called_api = sreq->api;
516 sreq->xreq.request.called_verb = sreq->verb;
517 sreq->xreq.json = json_tokener_parse(json + 1);
518 if (sreq->api && sreq->verb && sreq->xreq.json) {
519 afb_xreq_process(&sreq->xreq, main_apiset);
524 ERROR("Bad call specification %s", sreq->current->value);
528 static void run_startup_calls()
530 struct afb_config_list *list;
531 struct startup_req *sreq;
533 list = main_config->calls;
535 sreq = calloc(1, sizeof *sreq);
536 sreq->session = afb_session_create(3600);
537 sreq->current = list;
538 startup_call_current(sreq);
542 /*---------------------------------------------------------
543 | job for starting the daemon
544 +--------------------------------------------------------- */
546 static void start(int signum, void *arg)
548 struct afb_hsrv *hsrv;
550 afb_debug("start-entry");
553 ERROR("start aborted: received signal %s", strsignal(signum));
557 /* set the directories */
558 mkdir(main_config->workdir, S_IRWXU | S_IRGRP | S_IXGRP);
559 if (chdir(main_config->workdir) < 0) {
560 ERROR("Can't enter working dir %s", main_config->workdir);
563 if (afb_common_rootdir_set(main_config->rootdir) < 0) {
564 ERROR("failed to set common root directory");
568 /* configure the daemon */
569 if (afb_session_init(main_config->max_session_count, main_config->session_timeout, main_config->token)) {
570 ERROR("initialisation of session manager failed");
573 main_apiset = afb_apiset_create("main", main_config->api_timeout);
575 ERROR("can't create main api set");
578 if (afb_monitor_init(main_apiset, main_apiset) < 0) {
579 ERROR("failed to setup monitor");
582 #if defined(WITH_SUPERVISION)
583 if (afb_supervision_init() < 0) {
584 ERROR("failed to setup supervision");
590 if (main_config->tracereq)
591 afb_hook_create_xreq(NULL, NULL, NULL, main_config->tracereq, NULL, NULL);
592 if (main_config->traceapi)
593 afb_hook_create_api(NULL, main_config->traceapi, NULL, NULL);
594 if (main_config->traceevt)
595 afb_hook_create_evt(NULL, main_config->traceevt, NULL, NULL);
596 if (main_config->traceses)
597 afb_hook_create_session(NULL, main_config->traceses, NULL, NULL);
600 afb_debug("start-load");
601 apiset_start_list(main_config->so_bindings, afb_api_so_add_binding, "the binding");
602 #if defined(WITH_DBUS_TRANSPARENCY)
603 apiset_start_list(main_config->dbus_clients, afb_api_dbus_add_client, "the afb-dbus client");
605 apiset_start_list(main_config->ws_clients, afb_api_ws_add_client_weak, "the afb-websocket client");
606 apiset_start_list(main_config->ldpaths, afb_api_so_add_pathset_fails, "the binding path set");
607 apiset_start_list(main_config->weak_ldpaths, afb_api_so_add_pathset_nofails, "the weak binding path set");
608 apiset_start_list(main_config->auto_api, afb_autoset_add_any, "the automatic api path set");
610 #if defined(WITH_DBUS_TRANSPARENCY)
611 apiset_start_list(main_config->dbus_servers, afb_api_dbus_add_server, "the afb-dbus service");
613 apiset_start_list(main_config->ws_servers, afb_api_ws_add_server, "the afb-websocket service");
615 DEBUG("Init config done");
617 /* start the services */
618 afb_debug("start-start");
619 #if !defined(NO_CALL_PERSONALITY)
620 personality((unsigned long)-1L);
622 if (afb_apiset_start_all_services(main_apiset, 1) < 0)
625 /* start the HTTP server */
626 afb_debug("start-http");
627 if (!main_config->no_httpd) {
628 if (main_config->http_port <= 0) {
629 ERROR("no port is defined");
633 if (!afb_hreq_init_cookie(main_config->http_port, main_config->rootapi, main_config->session_timeout)) {
634 ERROR("initialisation of HTTP cookies failed");
638 hsrv = start_http_server();
643 /* run the startup calls */
644 afb_debug("start-call");
647 /* run the command */
648 afb_debug("start-exec");
649 if (execute_command() < 0)
653 sd_notify(1, "READY=1");
659 /*---------------------------------------------------------
661 | Parse option and launch action
662 +--------------------------------------------------------- */
664 int main(int argc, char *argv[])
666 afb_debug("main-entry");
668 // let's run this program with a low priority
671 // ------------- Build session handler & init config -------
672 main_config = afb_config_parse_arguments(argc, argv);
673 if (main_config->name) {
674 verbose_set_name(main_config->name, 0);
675 process_name_set_name(main_config->name);
676 process_name_replace_cmdline(argv, main_config->name);
678 afb_debug("main-args");
680 // --------- run -----------
681 if (main_config->background) {
682 // --------- in background mode -----------
683 INFO("entering background mode");
686 // ---- in foreground mode --------------------
687 INFO("entering foreground mode");
689 INFO("running with pid %d", getpid());
691 /* set the daemon environment */
694 afb_debug("main-start");
696 /* enter job processing */
697 jobs_start(3, 0, 50, start, NULL);
698 WARNING("hoops returned from jobs_enter! [report bug]");