2 * Copyright (C) 2015 "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.
28 #include <sys/types.h>
35 #include <systemd/sd-event.h>
37 #include "afb-config.h"
38 #include "afb-hswitch.h"
40 #include "afb-api-so.h"
41 #include "afb-api-dbus.h"
43 #include "afb-context.h"
47 #include "afb-common.h"
49 #include "afb-plugin.h"
51 #if !defined(PLUGIN_INSTALL_DIR)
52 #error "you should define PLUGIN_INSTALL_DIR"
55 #define AFB_VERSION "0.4"
57 // Define command line option
59 #define SET_BACKGROUND 2
60 #define SET_FORGROUND 3
62 #define SET_TCP_PORT 5
63 #define SET_ROOT_DIR 6
64 #define SET_ROOT_BASE 7
65 #define SET_ROOT_API 8
68 #define SET_CACHE_TIMEOUT 10
69 #define SET_SESSION_DIR 11
71 #define SET_AUTH_TOKEN 12
73 #define SET_APITIMEOUT 14
74 #define SET_CNTXTIMEOUT 15
76 #define DISPLAY_VERSION 16
77 #define DISPLAY_HELP 17
80 #define SET_READYFD 19
82 #define DBUS_CLIENT 20
83 #define DBUS_SERVICE 21
86 // Command line structure hold cli --command + help text
88 int val; // command number within application
89 int has_arg; // command number within application
90 char *name; // command as used in --xxxx cli
91 char *help; // help text
96 static AFB_options cliOptions [] = {
97 {SET_VERBOSE ,0,"verbose" , "Verbose Mode"},
99 {SET_FORGROUND ,0,"foreground" , "Get all in foreground mode"},
100 {SET_BACKGROUND ,0,"daemon" , "Get all in background mode"},
102 {SET_TCP_PORT ,1,"port" , "HTTP listening TCP port [default 1234]"},
103 {SET_ROOT_DIR ,1,"rootdir" , "HTTP Root Directory [default $HOME/.AFB]"},
104 {SET_ROOT_BASE ,1,"rootbase" , "Angular Base Root URL [default /opa]"},
105 {SET_ROOT_API ,1,"rootapi" , "HTML Root API URL [default /api]"},
106 {SET_ALIAS ,1,"alias" , "Muliple url map outside of rootdir [eg: --alias=/icons:/usr/share/icons]"},
108 {SET_APITIMEOUT ,1,"apitimeout" , "Plugin API timeout in seconds [default 10]"},
109 {SET_CNTXTIMEOUT ,1,"cntxtimeout" , "Client Session Context Timeout [default 900]"},
110 {SET_CACHE_TIMEOUT,1,"cache-eol" , "Client cache end of live [default 3600s]"},
112 {SET_SESSION_DIR ,1,"sessiondir" , "Sessions file path [default rootdir/sessions]"},
114 {SET_LDPATH ,1,"ldpaths" , "Load Plugins from dir1:dir2:... [default = PLUGIN_INSTALL_DIR"},
115 {SET_AUTH_TOKEN ,1,"token" , "Initial Secret [default=no-session, --token="" for session without authentication]"},
117 {DISPLAY_VERSION ,0,"version" , "Display version and copyright"},
118 {DISPLAY_HELP ,0,"help" , "Display this help"},
120 {SET_MODE ,1,"mode" , "set the mode: either local, remote or global"},
121 {SET_READYFD ,1,"readyfd" , "set the #fd to signal when ready"},
123 {DBUS_CLIENT ,1,"dbus-client" , "bind to an afb service through dbus"},
124 {DBUS_SERVICE ,1,"dbus-server" , "provides an afb service through dbus"},
125 {SO_PLUGIN ,1,"plugin" , "load the plugin of path"},
132 /*----------------------------------------------------------
134 | print version and copyright
135 +--------------------------------------------------------- */
136 static void printVersion (FILE *file)
138 fprintf(file, "\n----------------------------------------- \n");
139 fprintf(file, " AFB [Application Framework Binder] version=%s |\n", AFB_VERSION);
140 fprintf(file, " \n");
141 fprintf(file, " Copyright(C) 2016 /IoT.bzh [fulup -at- iot.bzh]\n");
142 fprintf(file, " AFB comes with ABSOLUTELY NO WARRANTY.\n");
143 fprintf(file, " Licence Apache 2\n\n");
147 /*----------------------------------------------------------
149 | print information from long option array
150 +--------------------------------------------------------- */
152 static void printHelp(FILE *file, const char *name)
157 fprintf (file, "%s:\nallowed options\n", name);
158 for (ind=0; cliOptions [ind].name != NULL;ind++)
161 if (cliOptions [ind].has_arg == 0 )
163 fprintf (file, " --%-15s %s\n", cliOptions [ind].name, cliOptions[ind].help);
165 sprintf(command, "%s=xxxx", cliOptions [ind].name);
166 fprintf (file, " --%-15s %s\n", command, cliOptions[ind].help);
169 fprintf (file, "Example:\n %s\\\n --verbose --port=1234 --token='azerty' --ldpaths=build/plugins:/usr/lib64/agl/plugins\n", name);
172 // load config from disk and merge with CLI option
173 static void config_set_default (struct afb_config * config)
176 if (config->httpdPort == 0)
177 config->httpdPort = 1234;
179 // default Plugin API timeout
180 if (config->apiTimeout == 0)
181 config->apiTimeout = DEFLT_API_TIMEOUT;
183 // default AUTH_TOKEN
184 if (config->token == NULL)
185 config->token = DEFLT_AUTH_TOKEN;
187 // cache timeout default one hour
188 if (config->cacheTimeout == 0)
189 config->cacheTimeout = DEFLT_CACHE_TIMEOUT;
191 // cache timeout default one hour
192 if (config->cntxTimeout == 0)
193 config->cntxTimeout = DEFLT_CNTX_TIMEOUT;
195 if (config->rootdir == NULL) {
196 config->rootdir = getenv("AFBDIR");
197 if (config->rootdir == NULL) {
198 config->rootdir = malloc (512);
199 strncpy (config->rootdir, getenv("HOME"),512);
200 strncat (config->rootdir, "/.AFB",512);
202 // if directory does not exist createit
203 mkdir (config->rootdir, O_RDWR | S_IRWXU | S_IRGRP);
206 // if no Angular/HTML5 rootbase let's try '/' as default
207 if (config->rootbase == NULL)
208 config->rootbase = "/opa";
210 if (config->rootapi == NULL)
211 config->rootapi = "/api";
213 if (config->ldpaths == NULL)
214 config->ldpaths = PLUGIN_INSTALL_DIR;
216 // if no session dir create a default path from rootdir
217 if (config->sessiondir == NULL) {
218 config->sessiondir = malloc (512);
219 strncpy (config->sessiondir, config->rootdir, 512);
220 strncat (config->sessiondir, "/sessions",512);
223 // if no config dir create a default path from sessiondir
224 if (config->console == NULL) {
225 config->console = malloc (512);
226 strncpy (config->console, config->sessiondir, 512);
227 strncat (config->console, "/AFB-console.out",512);
232 /*---------------------------------------------------------
234 | Parse option and launch action
235 +--------------------------------------------------------- */
237 static void add_item(struct afb_config *config, int kind, char *value)
239 struct afb_config_item *item = malloc(sizeof *item);
241 ERROR("out of memory");
246 item->previous = config->items;
247 config->items = item;
250 static void parse_arguments(int argc, char *argv[], struct afb_config *config)
252 char* programName = argv [0];
256 struct option *gnuOptions;
258 // ------------------ Process Command Line -----------------------
260 // if no argument print help and return
262 printHelp(stderr, programName);
266 // build GNU getopt info from cliOptions
267 nbcmd = sizeof (cliOptions) / sizeof (AFB_options);
268 gnuOptions = malloc (sizeof (*gnuOptions) * (unsigned)nbcmd);
269 for (ind=0; ind < nbcmd;ind++) {
270 gnuOptions [ind].name = cliOptions[ind].name;
271 gnuOptions [ind].has_arg = cliOptions[ind].has_arg;
272 gnuOptions [ind].flag = 0;
273 gnuOptions [ind].val = cliOptions[ind].val;
276 // get all options from command line
277 while ((optc = getopt_long (argc, argv, "vsp?", gnuOptions, &optionIndex))
287 if (optarg == 0) goto needValueForOption;
288 if (!sscanf (optarg, "%d", &config->httpdPort)) goto notAnInteger;
292 if (optarg == 0) goto needValueForOption;
293 if (!sscanf (optarg, "%d", &config->apiTimeout)) goto notAnInteger;
296 case SET_CNTXTIMEOUT:
297 if (optarg == 0) goto needValueForOption;
298 if (!sscanf (optarg, "%d", &config->cntxTimeout)) goto notAnInteger;
302 if (optarg == 0) goto needValueForOption;
303 config->rootdir = optarg;
304 INFO("Forcing Rootdir=%s",config->rootdir);
308 if (optarg == 0) goto needValueForOption;
309 config->rootbase = optarg;
310 INFO("Forcing Rootbase=%s",config->rootbase);
314 if (optarg == 0) goto needValueForOption;
315 config->rootapi = optarg;
316 INFO("Forcing Rootapi=%s",config->rootapi);
320 if (optarg == 0) goto needValueForOption;
321 if ((unsigned)config->aliascount < sizeof (config->aliasdir) / sizeof (config->aliasdir[0])) {
322 config->aliasdir[config->aliascount].url = strsep(&optarg,":");
323 if (optarg == NULL) {
324 ERROR("missing ':' in alias %s, ignored", config->aliasdir[config->aliascount].url);
326 config->aliasdir[config->aliascount].path = optarg;
327 INFO("Alias url=%s path=%s", config->aliasdir[config->aliascount].url, config->aliasdir[config->aliascount].path);
328 config->aliascount++;
331 ERROR("Too many aliases [max:%d] %s ignored", MAX_ALIAS, optarg);
336 if (optarg == 0) goto needValueForOption;
337 config->token = optarg;
341 if (optarg == 0) goto needValueForOption;
342 config->ldpaths = optarg;
345 case SET_SESSION_DIR:
346 if (optarg == 0) goto needValueForOption;
347 config->sessiondir = optarg;
350 case SET_CACHE_TIMEOUT:
351 if (optarg == 0) goto needValueForOption;
352 if (!sscanf (optarg, "%d", &config->cacheTimeout)) goto notAnInteger;
356 if (optarg != 0) goto noValueForOption;
357 config->background = 0;
361 if (optarg != 0) goto noValueForOption;
362 config->background = 1;
366 if (optarg == 0) goto needValueForOption;
367 if (!strcmp(optarg, "local")) config->mode = AFB_MODE_LOCAL;
368 else if (!strcmp(optarg, "remote")) config->mode = AFB_MODE_REMOTE;
369 else if (!strcmp(optarg, "global")) config->mode = AFB_MODE_GLOBAL;
374 if (optarg == 0) goto needValueForOption;
375 if (!sscanf (optarg, "%u", &config->readyfd)) goto notAnInteger;
381 if (optarg == 0) goto needValueForOption;
382 add_item(config, optc, optarg);
385 case DISPLAY_VERSION:
386 if (optarg != 0) goto noValueForOption;
387 printVersion(stdout);
392 printHelp(stdout, programName);
398 config_set_default (config);
403 ERROR("AFB-daemon option [--%s] need a value i.e. --%s=xxx"
404 ,gnuOptions[optionIndex].name, gnuOptions[optionIndex].name);
408 ERROR("AFB-daemon option [--%s] requirer an interger i.e. --%s=9"
409 ,gnuOptions[optionIndex].name, gnuOptions[optionIndex].name);
413 ERROR("AFB-daemon option [--%s] don't take value"
414 ,gnuOptions[optionIndex].name);
418 ERROR("AFB-daemon option [--%s] only accepts local, global or remote."
419 ,gnuOptions[optionIndex].name);
423 /*----------------------------------------------------------
425 | try to close everything before leaving
426 +--------------------------------------------------------- */
427 static void closeSession (int status, void *data) {
428 /* struct afb_config *config = data; */
431 /*----------------------------------------------------------
433 +--------------------------------------------------------- */
434 void signalQuit (int signum)
436 ERROR("Terminating signal received %s", strsignal(signum));
440 /*----------------------------------------------------------
443 +--------------------------------------------------------- */
444 __thread sigjmp_buf *error_handler;
445 static void signalError(int signum)
449 // unlock signal to allow a new signal to come
450 if (error_handler != NULL) {
451 sigemptyset(&sigset);
452 sigaddset(&sigset, signum);
453 sigprocmask(SIG_UNBLOCK, &sigset, 0);
454 longjmp(*error_handler, signum);
456 if (signum == SIGALRM)
458 ERROR("Unmonitored signal received %s", strsignal(signum));
462 static void install_error_handlers()
464 int i, signals[] = { SIGALRM, SIGSEGV, SIGFPE, 0 };
466 for (i = 0; signals[i] != 0; i++) {
467 if (signal(signals[i], signalError) == SIG_ERR) {
468 ERROR("Signal handler error");
474 /*----------------------------------------------------------
476 | set the process in background
477 +--------------------------------------------------------- */
478 static void daemonize(struct afb_config *config)
483 // open /dev/console to redirect output messAFBes
484 consoleFD = open(config->console, O_WRONLY | O_APPEND | O_CREAT , 0640);
486 ERROR("AFB-daemon cannot open /dev/console (use --foreground)");
490 // fork process when running background mode
493 // if fail nothing much to do
495 ERROR("AFB-daemon Failed to fork son process");
499 // if in father process, just leave
500 if (pid != 0) _exit (0);
502 // son process get all data in standalone mode
503 NOTICE("background mode [pid:%d console:%s]", getpid(),config->console);
505 // redirect default I/O on console
506 close (2); dup(consoleFD); // redirect stderr
507 close (1); dup(consoleFD); // redirect stdout
508 close (0); // no need for stdin
512 setsid(); // allow father process to fully exit
513 sleep (2); // allow main to leave and release port
517 /*---------------------------------------------------------
519 | Handles the HTTP server
520 +--------------------------------------------------------- */
521 static int init_http_server(struct afb_hsrv *hsrv, struct afb_config * config)
525 if (!afb_hsrv_add_handler(hsrv, config->rootapi, afb_hswitch_websocket_switch, NULL, 20))
528 if (!afb_hsrv_add_handler(hsrv, config->rootapi, afb_hswitch_apis, NULL, 10))
531 for (idx = 0; idx < config->aliascount; idx++)
532 if (!afb_hsrv_add_alias (hsrv, config->aliasdir[idx].url, config->aliasdir[idx].path, 0))
535 if (!afb_hsrv_add_alias(hsrv, "", config->rootdir, -10))
538 if (!afb_hsrv_add_handler(hsrv, config->rootbase, afb_hswitch_one_page_api_redirect, NULL, -20))
544 static struct afb_hsrv *start_http_server(struct afb_config * config)
547 struct afb_hsrv *hsrv;
549 if (afb_hreq_init_download_path("/tmp")) { /* TODO: sessiondir? */
550 ERROR("unable to set the tmp directory");
554 hsrv = afb_hsrv_create();
556 ERROR("memory allocation failure");
560 if (!afb_hsrv_set_cache_timeout(hsrv, config->cacheTimeout)
561 || !init_http_server(hsrv, config)) {
562 ERROR("initialisation of httpd failed");
567 NOTICE("Waiting port=%d rootdir=%s", config->httpdPort, config->rootdir);
568 NOTICE("Browser URL= http:/*localhost:%d", config->httpdPort);
570 rc = afb_hsrv_start(hsrv, (uint16_t) config->httpdPort, 15);
572 ERROR("starting of httpd failed");
580 static void start_items(struct afb_config_item *item)
583 /* keeps the order */
584 start_items(item->previous);
587 if (afb_api_dbus_add_client(item->value) < 0) {
588 ERROR("can't start the afb-dbus client of path %s",item->value);
593 if (afb_api_dbus_add_server(item->value) < 0) {
594 ERROR("can't start the afb-dbus service of path %s",item->value);
599 if (afb_api_so_add_plugin(item->value) < 0) {
600 ERROR("can't start the plugin of path %s",item->value);
605 ERROR("unexpected internal error");
613 /*---------------------------------------------------------
615 | Parse option and launch action
616 +--------------------------------------------------------- */
618 int main(int argc, char *argv[]) {
619 struct afb_hsrv *hsrv;
620 struct afb_config *config;
621 struct sd_event *eventloop;
623 // open syslog if ever needed
624 openlog("afb-daemon", 0, LOG_DAEMON);
626 // ------------- Build session handler & init config -------
627 config = calloc (1, sizeof (struct afb_config));
629 on_exit(closeSession, config);
630 parse_arguments(argc, argv, config);
632 // ------------------ sanity check ----------------------------------------
633 if (config->httpdPort <= 0) {
634 ERROR("no port is defined");
639 afb_api_so_add_pathset(config->ldpaths);
641 start_items(config->items);
642 config->items = NULL;
644 ctxStoreInit(CTX_NBCLIENTS, config->cntxTimeout, config->token, afb_apis_count());
645 if (!afb_hreq_init_cookie(config->httpdPort, config->rootapi, DEFLT_CNTX_TIMEOUT)) {
646 ERROR("initialisation of cookies failed");
650 install_error_handlers();
652 // ------------------ clean exit on CTR-C signal ------------------------
653 if (signal (SIGINT, signalQuit) == SIG_ERR || signal (SIGABRT, signalQuit) == SIG_ERR) {
654 ERROR("main fail to install Signal handler");
658 // let's run this program with a low priority
661 // ------------------ Finaly Process Commands -----------------------------
662 // let's not take the risk to run as ROOT
663 //if (getuid() == 0) goto errorNoRoot;
665 DEBUG("Init config done");
667 // --------- run -----------
668 if (config->background) {
669 // --------- in background mode -----------
670 INFO("entering background mode");
673 // ---- in foreground mode --------------------
674 INFO("entering foreground mode");
677 hsrv = start_http_server(config);
681 if (config->readyfd != 0) {
682 static const char readystr[] = "READY=1";
683 write(config->readyfd, readystr, sizeof(readystr) - 1);
684 close(config->readyfd);
688 eventloop = afb_common_get_event_loop();
690 sd_event_run(eventloop, 30000000);
692 WARNING("hoops returned from infinite loop [report bug]");