38598189cf21fb3e36e802aa8d1c092062ca6940
[src/app-framework-binder.git] / src / main.c
1 /*
2  * Copyright (C) 2015, 2016 "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 <getopt.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 "session.h"
44 #include "verbose.h"
45 #include "afb-common.h"
46
47 #include <afb/afb-binding.h>
48
49 #if !defined(BINDING_INSTALL_DIR)
50 #error "you should define BINDING_INSTALL_DIR"
51 #endif
52
53 #define AFB_VERSION    "0.5"
54
55 // Define command line option
56 #define SET_VERBOSE        1
57 #define SET_BACKGROUND     2
58 #define SET_FORGROUND      3
59
60 #define SET_TCP_PORT       5
61 #define SET_ROOT_DIR       6
62 #define SET_ROOT_BASE      7
63 #define SET_ROOT_API       8
64 #define SET_ALIAS          9
65
66 #define SET_CACHE_TIMEOUT  10
67 #define SET_SESSION_DIR    11
68
69 #define SET_AUTH_TOKEN     12
70 #define SET_LDPATH         13
71 #define SET_APITIMEOUT     14
72 #define SET_CNTXTIMEOUT    15
73
74 #define DISPLAY_VERSION    16
75 #define DISPLAY_HELP       17
76
77 #define SET_MODE           18
78 #define SET_READYFD        19
79
80 #define DBUS_CLIENT        20
81 #define DBUS_SERVICE       21
82 #define SO_BINDING         22
83
84 #define SET_SESSIONMAX     23
85
86 #define WS_CLIENT          24
87 #define WS_SERVICE         25
88
89 #define SET_ROOT_HTTP      26
90
91 // Command line structure hold cli --command + help text
92 typedef struct {
93   int  val;        // command number within application
94   int  has_arg;    // command number within application
95   char *name;      // command as used in --xxxx cli
96   char *help;      // help text
97 } AFB_options;
98
99
100 // Supported option
101 static  AFB_options cliOptions [] = {
102   {SET_VERBOSE      ,0,"verbose"         , "Verbose Mode"},
103
104   {SET_FORGROUND    ,0,"foreground"      , "Get all in foreground mode"},
105   {SET_BACKGROUND   ,0,"daemon"          , "Get all in background mode"},
106
107   {SET_TCP_PORT     ,1,"port"            , "HTTP listening TCP port  [default 1234]"},
108   {SET_ROOT_DIR     ,1,"rootdir"         , "Root Directory [default $HOME/.AFB]"},
109   {SET_ROOT_HTTP    ,1,"roothttp"        , "HTTP Root Directory [default rootdir]"},
110   {SET_ROOT_BASE    ,1,"rootbase"        , "Angular Base Root URL [default /opa]"},
111   {SET_ROOT_API     ,1,"rootapi"         , "HTML Root API URL [default /api]"},
112   {SET_ALIAS        ,1,"alias"           , "Muliple url map outside of rootdir [eg: --alias=/icons:/usr/share/icons]"},
113
114   {SET_APITIMEOUT   ,1,"apitimeout"      , "Binding API timeout in seconds [default 10]"},
115   {SET_CNTXTIMEOUT  ,1,"cntxtimeout"     , "Client Session Context Timeout [default 900]"},
116   {SET_CACHE_TIMEOUT,1,"cache-eol"       , "Client cache end of live [default 3600]"},
117
118   {SET_SESSION_DIR  ,1,"sessiondir"      , "Sessions file path [default rootdir/sessions]"},
119
120   {SET_LDPATH       ,1,"ldpaths"         , "Load bindingss from dir1:dir2:... [default = "BINDING_INSTALL_DIR"]"},
121   {SET_AUTH_TOKEN   ,1,"token"           , "Initial Secret [default=no-session, --token="" for session without authentication]"},
122
123   {DISPLAY_VERSION  ,0,"version"         , "Display version and copyright"},
124   {DISPLAY_HELP     ,0,"help"            , "Display this help"},
125
126   {SET_MODE         ,1,"mode"            , "set the mode: either local, remote or global"},
127   {SET_READYFD      ,1,"readyfd"         , "set the #fd to signal when ready"},
128
129   {DBUS_CLIENT      ,1,"dbus-client"     , "bind to an afb service through dbus"},
130   {DBUS_SERVICE     ,1,"dbus-server"     , "provides an afb service through dbus"},
131   {WS_CLIENT        ,1,"ws-client"       , "bind to an afb service through websocket"},
132   {WS_SERVICE       ,1,"ws-server"       , "provides an afb service through websockets"},
133   {SO_BINDING       ,1,"binding"         , "load the binding of path"},
134
135   {SET_SESSIONMAX   ,1,"session-max"     , "max count of session simultaneously [default 10]"},
136
137   {0, 0, NULL, NULL}
138  };
139
140 /*----------------------------------------------------------
141  | printversion
142  |   print version and copyright
143  +--------------------------------------------------------- */
144 static void printVersion (FILE *file)
145 {
146    fprintf(file, "\n----------------------------------------- \n");
147    fprintf(file, "  AFB [Application Framework Binder] version=%s |\n", AFB_VERSION);
148    fprintf(file, " \n");
149    fprintf(file, "  Copyright (C) 2015, 2016 \"IoT.bzh\" [fulup -at- iot.bzh]\n");
150    fprintf(file, "  AFB comes with ABSOLUTELY NO WARRANTY.\n");
151    fprintf(file, "  Licence Apache 2\n\n");
152    exit (0);
153 }
154
155 /*----------------------------------------------------------
156  | printHelp
157  |   print information from long option array
158  +--------------------------------------------------------- */
159
160 static void printHelp(FILE *file, const char *name)
161 {
162     int ind;
163     char command[50];
164
165     fprintf (file, "%s:\nallowed options\n", name);
166     for (ind=0; cliOptions [ind].name != NULL;ind++)
167     {
168       // display options
169       if (cliOptions [ind].has_arg == 0 )
170       {
171              fprintf (file, "  --%-15s %s\n", cliOptions [ind].name, cliOptions[ind].help);
172       } else {
173          sprintf(command, "%s=xxxx", cliOptions [ind].name);
174          fprintf (file, "  --%-15s %s\n", command, cliOptions[ind].help);
175       }
176     }
177     fprintf (file, "Example:\n  %s\\\n  --verbose --port=1234 --token='azerty' --ldpaths=build/bindings:/usr/lib64/agl/bindings\n", name);
178 }
179
180 // load config from disk and merge with CLI option
181 static void config_set_default (struct afb_config * config)
182 {
183    // default HTTP port
184    if (config->httpdPort == 0)
185         config->httpdPort = 1234;
186
187    // default binding API timeout
188    if (config->apiTimeout == 0)
189         config->apiTimeout = DEFLT_API_TIMEOUT;
190
191    // default AUTH_TOKEN
192    if (config->token == NULL)
193                 config->token = DEFLT_AUTH_TOKEN;
194
195    // cache timeout default one hour
196    if (config->cacheTimeout == 0)
197                 config->cacheTimeout = DEFLT_CACHE_TIMEOUT;
198
199    // cache timeout default one hour
200    if (config->cntxTimeout == 0)
201                 config->cntxTimeout = DEFLT_CNTX_TIMEOUT;
202
203    // max count of sessions
204    if (config->nbSessionMax == 0)
205        config->nbSessionMax = CTX_NBCLIENTS;
206
207    if (config->rootdir == NULL) {
208        config->rootdir = getenv("AFBDIR");
209        if (config->rootdir == NULL) {
210            config->rootdir = malloc (512);
211            strncpy (config->rootdir, getenv("HOME"),512);
212            strncat (config->rootdir, "/.AFB",512);
213        }
214        // if directory does not exist createit
215        mkdir (config->rootdir,  O_RDWR | S_IRWXU | S_IRGRP);
216    }
217
218    // if no Angular/HTML5 rootbase let's try '/' as default
219    if  (config->roothttp == NULL)
220        config->roothttp = ".";
221
222    if  (config->rootbase == NULL)
223        config->rootbase = "/opa";
224
225    if  (config->rootapi == NULL)
226        config->rootapi = "/api";
227
228    if  (config->ldpaths == NULL)
229        config->ldpaths = BINDING_INSTALL_DIR;
230
231    // if no session dir create a default path from rootdir
232    if  (config->sessiondir == NULL) {
233        config->sessiondir = malloc (512);
234        strncpy (config->sessiondir, config->rootdir, 512);
235        strncat (config->sessiondir, "/sessions",512);
236    }
237
238    // if no config dir create a default path from sessiondir
239    if  (config->console == NULL) {
240        config->console = malloc (512);
241        strncpy (config->console, config->sessiondir, 512);
242        strncat (config->console, "/AFB-console.out",512);
243    }
244 }
245
246
247 /*---------------------------------------------------------
248  | main
249  |   Parse option and launch action
250  +--------------------------------------------------------- */
251
252 static void add_item(struct afb_config *config, int kind, char *value)
253 {
254         struct afb_config_item *item = malloc(sizeof *item);
255         if (item == NULL) {
256                 ERROR("out of memory");
257                 exit(1);
258         }
259         item->kind = kind;
260         item->value = value;
261         item->previous = config->items;
262         config->items = item;
263 }
264
265 static void parse_arguments(int argc, char *argv[], struct afb_config *config)
266 {
267   char*          programName = argv [0];
268   int            optionIndex = 0;
269   int            optc, ind;
270   int            nbcmd;
271   struct option *gnuOptions;
272
273   // ------------------ Process Command Line -----------------------
274
275   // if no argument print help and return
276   if (argc < 2) {
277        printHelp(stderr, programName);
278        exit(1);
279   }
280
281   // build GNU getopt info from cliOptions
282   nbcmd = sizeof (cliOptions) / sizeof (AFB_options);
283   gnuOptions = malloc (sizeof (*gnuOptions) * (unsigned)nbcmd);
284   for (ind=0; ind < nbcmd;ind++) {
285     gnuOptions [ind].name    = cliOptions[ind].name;
286     gnuOptions [ind].has_arg = cliOptions[ind].has_arg;
287     gnuOptions [ind].flag    = 0;
288     gnuOptions [ind].val     = cliOptions[ind].val;
289   }
290
291   // get all options from command line
292   while ((optc = getopt_long (argc, argv, "vsp?", gnuOptions, &optionIndex))
293         != EOF)
294   {
295     switch (optc)
296     {
297      case SET_VERBOSE:
298        verbosity++;
299        break;
300
301     case SET_TCP_PORT:
302        if (optarg == 0) goto needValueForOption;
303        if (!sscanf (optarg, "%d", &config->httpdPort)) goto notAnInteger;
304        break;
305
306     case SET_APITIMEOUT:
307        if (optarg == 0) goto needValueForOption;
308        if (!sscanf (optarg, "%d", &config->apiTimeout)) goto notAnInteger;
309        break;
310
311     case SET_CNTXTIMEOUT:
312        if (optarg == 0) goto needValueForOption;
313        if (!sscanf (optarg, "%d", &config->cntxTimeout)) goto notAnInteger;
314        break;
315
316     case SET_ROOT_DIR:
317        if (optarg == 0) goto needValueForOption;
318        config->rootdir   = optarg;
319        INFO("Forcing Rootdir=%s",config->rootdir);
320        break;
321
322     case SET_ROOT_HTTP:
323        if (optarg == 0) goto needValueForOption;
324        config->roothttp   = optarg;
325        INFO("Forcing Root HTTP=%s",config->roothttp);
326        break;
327
328     case SET_ROOT_BASE:
329        if (optarg == 0) goto needValueForOption;
330        config->rootbase   = optarg;
331        INFO("Forcing Rootbase=%s",config->rootbase);
332        break;
333
334     case SET_ROOT_API:
335        if (optarg == 0) goto needValueForOption;
336        config->rootapi   = optarg;
337        INFO("Forcing Rootapi=%s",config->rootapi);
338        break;
339
340     case SET_ALIAS:
341        if (optarg == 0) goto needValueForOption;
342        if ((unsigned)config->aliascount < sizeof (config->aliasdir) / sizeof (config->aliasdir[0])) {
343             config->aliasdir[config->aliascount].url  = strsep(&optarg,":");
344             if (optarg == NULL) {
345               ERROR("missing ':' in alias %s, ignored", config->aliasdir[config->aliascount].url);
346             } else {
347               config->aliasdir[config->aliascount].path = optarg;
348               INFO("Alias url=%s path=%s", config->aliasdir[config->aliascount].url, config->aliasdir[config->aliascount].path);
349               config->aliascount++;
350             }
351        } else {
352            ERROR("Too many aliases [max:%d] %s ignored", MAX_ALIAS, optarg);
353        }
354        break;
355
356     case SET_AUTH_TOKEN:
357        if (optarg == 0) goto needValueForOption;
358        config->token   = optarg;
359        break;
360
361     case SET_LDPATH:
362        if (optarg == 0) goto needValueForOption;
363        config->ldpaths = optarg;
364        break;
365
366     case SET_SESSION_DIR:
367        if (optarg == 0) goto needValueForOption;
368        config->sessiondir   = optarg;
369        break;
370
371     case  SET_CACHE_TIMEOUT:
372        if (optarg == 0) goto needValueForOption;
373        if (!sscanf (optarg, "%d", &config->cacheTimeout)) goto notAnInteger;
374        break;
375
376     case  SET_SESSIONMAX:
377        if (optarg == 0) goto needValueForOption;
378        if (!sscanf (optarg, "%d", &config->nbSessionMax)) goto notAnInteger;
379        break;
380
381     case SET_FORGROUND:
382        if (optarg != 0) goto noValueForOption;
383        config->background  = 0;
384        break;
385
386     case SET_BACKGROUND:
387        if (optarg != 0) goto noValueForOption;
388        config->background  = 1;
389        break;
390
391     case SET_MODE:
392        if (optarg == 0) goto needValueForOption;
393        if (!strcmp(optarg, "local")) config->mode = AFB_MODE_LOCAL;
394        else if (!strcmp(optarg, "remote")) config->mode = AFB_MODE_REMOTE;
395        else if (!strcmp(optarg, "global")) config->mode = AFB_MODE_GLOBAL;
396        else goto badMode;
397        break;
398
399     case SET_READYFD:
400        if (optarg == 0) goto needValueForOption;
401        if (!sscanf (optarg, "%u", &config->readyfd)) goto notAnInteger;
402        break;
403
404     case DBUS_CLIENT:
405     case DBUS_SERVICE:
406     case WS_CLIENT:
407     case WS_SERVICE:
408     case SO_BINDING:
409        if (optarg == 0) goto needValueForOption;
410        add_item(config, optc, optarg);
411        break;
412
413     case DISPLAY_VERSION:
414        if (optarg != 0) goto noValueForOption;
415        printVersion(stdout);
416        break;
417
418     case DISPLAY_HELP:
419      default:
420        printHelp(stdout, programName);
421        exit(0);
422     }
423   }
424   free(gnuOptions);
425
426   config_set_default  (config);
427   return;
428
429
430 needValueForOption:
431   ERROR("AFB-daemon option [--%s] need a value i.e. --%s=xxx"
432           ,gnuOptions[optionIndex].name, gnuOptions[optionIndex].name);
433   exit (1);
434
435 notAnInteger:
436   ERROR("AFB-daemon option [--%s] requirer an interger i.e. --%s=9"
437           ,gnuOptions[optionIndex].name, gnuOptions[optionIndex].name);
438   exit (1);
439
440 noValueForOption:
441   ERROR("AFB-daemon option [--%s] don't take value"
442           ,gnuOptions[optionIndex].name);
443   exit (1);
444
445 badMode:
446   ERROR("AFB-daemon option [--%s] only accepts local, global or remote."
447           ,gnuOptions[optionIndex].name);
448   exit (1);
449 }
450
451 /*----------------------------------------------------------
452  | closeSession
453  |   try to close everything before leaving
454  +--------------------------------------------------------- */
455 static void closeSession (int status, void *data) {
456         /* struct afb_config *config = data; */
457 }
458
459 /*----------------------------------------------------------
460  | daemonize
461  |   set the process in background
462  +--------------------------------------------------------- */
463 static void daemonize(struct afb_config *config)
464 {
465   int            consoleFD;
466   int            pid;
467
468       // open /dev/console to redirect output messAFBes
469       consoleFD = open(config->console, O_WRONLY | O_APPEND | O_CREAT , 0640);
470       if (consoleFD < 0) {
471                 ERROR("AFB-daemon cannot open /dev/console (use --foreground)");
472                 exit (1);
473       }
474
475       // fork process when running background mode
476       pid = fork ();
477
478       // if fail nothing much to do
479       if (pid == -1) {
480                 ERROR("AFB-daemon Failed to fork son process");
481                 exit (1);
482         }
483
484       // if in father process, just leave
485       if (pid != 0) _exit (0);
486
487       // son process get all data in standalone mode
488      NOTICE("background mode [pid:%d console:%s]", getpid(),config->console);
489
490       // redirect default I/O on console
491       close (2); dup(consoleFD);  // redirect stderr
492       close (1); dup(consoleFD);  // redirect stdout
493       close (0);           // no need for stdin
494       close (consoleFD);
495
496 #if 0
497          setsid();   // allow father process to fully exit
498      sleep (2);  // allow main to leave and release port
499 #endif
500 }
501
502 /*---------------------------------------------------------
503  | http server
504  |   Handles the HTTP server
505  +--------------------------------------------------------- */
506 static int init_http_server(struct afb_hsrv *hsrv, struct afb_config * config)
507 {
508         int idx, dfd;
509
510         dfd = afb_common_rootdir_get_fd();
511
512         if (!afb_hsrv_add_handler(hsrv, config->rootapi, afb_hswitch_websocket_switch, NULL, 20))
513                 return 0;
514
515         if (!afb_hsrv_add_handler(hsrv, config->rootapi, afb_hswitch_apis, NULL, 10))
516                 return 0;
517
518         for (idx = 0; idx < config->aliascount; idx++)
519                 if (!afb_hsrv_add_alias (hsrv, config->aliasdir[idx].url, dfd, config->aliasdir[idx].path, 0, 0))
520                         return 0;
521
522         if (!afb_hsrv_add_alias(hsrv, "", dfd, config->roothttp, -10, 1))
523                 return 0;
524
525         if (!afb_hsrv_add_handler(hsrv, config->rootbase, afb_hswitch_one_page_api_redirect, NULL, -20))
526                 return 0;
527
528         return 1;
529 }
530
531 static struct afb_hsrv *start_http_server(struct afb_config * config)
532 {
533         int rc;
534         struct afb_hsrv *hsrv;
535
536         if (afb_hreq_init_download_path("/tmp")) { /* TODO: sessiondir? */
537                 ERROR("unable to set the tmp directory");
538                 return NULL;
539         }
540
541         hsrv = afb_hsrv_create();
542         if (hsrv == NULL) {
543                 ERROR("memory allocation failure");
544                 return NULL;
545         }
546
547         if (!afb_hsrv_set_cache_timeout(hsrv, config->cacheTimeout)
548         || !init_http_server(hsrv, config)) {
549                 ERROR("initialisation of httpd failed");
550                 afb_hsrv_put(hsrv);
551                 return NULL;
552         }
553
554         NOTICE("Waiting port=%d rootdir=%s", config->httpdPort, config->rootdir);
555         NOTICE("Browser URL= http:/*localhost:%d", config->httpdPort);
556
557         rc = afb_hsrv_start(hsrv, (uint16_t) config->httpdPort, 15);
558         if (!rc) {
559                 ERROR("starting of httpd failed");
560                 afb_hsrv_put(hsrv);
561                 return NULL;
562         }
563
564         return hsrv;
565 }
566
567 static void start_items(struct afb_config_item *item)
568 {
569   if (item != NULL) {
570     /* keeps the order */
571     start_items(item->previous);
572     switch(item->kind) {
573     case DBUS_CLIENT:
574       if (afb_api_dbus_add_client(item->value) < 0) {
575         ERROR("can't start the afb-dbus client of path %s",item->value);
576         exit(1);
577       }
578       break;
579     case DBUS_SERVICE:
580       if (afb_api_dbus_add_server(item->value) < 0) {
581         ERROR("can't start the afb-dbus service of path %s",item->value);
582         exit(1);
583       }
584       break;
585     case WS_CLIENT:
586       if (afb_api_ws_add_client(item->value) < 0) {
587         ERROR("can't start the afb-websocket client of path %s",item->value);
588         exit(1);
589       }
590       break;
591     case WS_SERVICE:
592       if (afb_api_ws_add_server(item->value) < 0) {
593         ERROR("can't start the afb-websocket service of path %s",item->value);
594         exit(1);
595       }
596       break;
597     case SO_BINDING:
598       if (afb_api_so_add_binding(item->value) < 0) {
599         ERROR("can't start the binding of path %s",item->value);
600         exit(1);
601       }
602       break;
603     default:
604       ERROR("unexpected internal error");
605       exit(1);
606     }
607     /* frre the item */
608     free(item);
609   }
610 }
611
612 /*---------------------------------------------------------
613  | main
614  |   Parse option and launch action
615  +--------------------------------------------------------- */
616
617 int main(int argc, char *argv[])  {
618   struct afb_hsrv *hsrv;
619   struct afb_config *config;
620   struct sd_event *eventloop;
621
622   LOGAUTH("afb-daemon");
623
624   // ------------- Build session handler & init config -------
625   config = calloc (1, sizeof (struct afb_config));
626
627   on_exit(closeSession, config);
628   parse_arguments(argc, argv, config);
629
630   // ------------------ sanity check ----------------------------------------
631   if (config->httpdPort <= 0) {
632      ERROR("no port is defined");
633      exit (1);
634   }
635
636   afb_api_so_set_timeout(config->apiTimeout);
637   if (config->ldpaths) {
638     if (afb_api_so_add_pathset(config->ldpaths) < 0) {
639       ERROR("initialisation of bindings within %s failed", config->ldpaths);
640       exit(1);
641     }
642   }
643
644   start_items(config->items);
645   config->items = NULL;
646
647   ctxStoreInit(config->nbSessionMax, config->cntxTimeout, config->token, afb_apis_count());
648   if (!afb_hreq_init_cookie(config->httpdPort, config->rootapi, DEFLT_CNTX_TIMEOUT)) {
649      ERROR("initialisation of cookies failed");
650      exit (1);
651   }
652
653   if (afb_sig_handler_init() < 0) {
654      ERROR("failed to initialise signal handlers");
655      return 1;
656   }
657
658   if (afb_common_rootdir_set(config->rootdir) < 0) {
659      ERROR("failed to set common root directory");
660      return 1;
661   }
662
663   if (afb_thread_init(3, 1, 20) < 0) {
664      ERROR("failed to initialise threading");
665      return 1;
666   }
667
668   // let's run this program with a low priority
669   nice (20);
670
671   // ------------------ Finaly Process Commands -----------------------------
672   // let's not take the risk to run as ROOT
673   //if (getuid() == 0)  goto errorNoRoot;
674
675   DEBUG("Init config done");
676
677   // --------- run -----------
678   if (config->background) {
679       // --------- in background mode -----------
680       INFO("entering background mode");
681       daemonize(config);
682   } else {
683       // ---- in foreground mode --------------------
684       INFO("entering foreground mode");
685   }
686
687   /* ignore any SIGPIPE */
688   signal(SIGPIPE, SIG_IGN);
689
690    /* start the HTTP server */
691    hsrv = start_http_server(config);
692    if (hsrv == NULL)
693         exit(1);
694
695    /* start the services */
696    if (afb_apis_start_all_services(1) < 0)
697         exit(1);
698
699    if (config->readyfd != 0) {
700                 static const char readystr[] = "READY=1";
701                 write(config->readyfd, readystr, sizeof(readystr) - 1);
702                 close(config->readyfd);
703   }
704
705    // infinite loop
706   eventloop = afb_common_get_event_loop();
707   for(;;)
708     sd_event_run(eventloop, 30000000);
709
710   WARNING("hoops returned from infinite loop [report bug]");
711
712   return 0;
713 }
714