simplification of config
[src/app-framework-binder.git] / src / main.c
1 /* 
2  * Copyright (C) 2015 "IoT.bzh"
3  * Author "Fulup Ar Foll"
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 /* 
20  * File:   main.c
21  * Author: "Fulup Ar Foll"
22  *
23  * Created on 05 December 2015, 15:38
24  */
25
26 #include "local-def.h"
27
28 #include <syslog.h>
29 #include <setjmp.h>
30 #include <signal.h>
31 #include <getopt.h>
32 #include <pwd.h>
33 #include <sys/stat.h>
34 #include <sys/types.h>
35
36 #define AFB_VERSION    "0.1"
37
38 static sigjmp_buf exitPoint; // context save for set/longjmp
39
40 /*----------------------------------------------------------
41  | printversion
42  |   print version and copyright
43  +--------------------------------------------------------- */
44  static void printVersion (void) {
45
46    fprintf (stderr,"\n----------------------------------------- \n");
47    fprintf (stderr,"|  AFB [Application Framework Binder] version=%s |\n", AFB_VERSION);
48    fprintf (stderr,"----------------------------------------- \n");
49    fprintf (stderr,"|  Copyright(C) 2016 /IoT.bzh [fulup -at- iot.bzh]\n");
50    fprintf (stderr,"|  AFB comes with ABSOLUTELY NO WARRANTY.\n");
51    fprintf (stderr,"|  Licence [what ever makes you happy] until you fix bugs by yourself :)\n\n");
52    exit (0);
53  } // end printVersion
54
55
56 // Define command line option
57 #define SET_VERBOSE        1
58 #define SET_BACKGROUND     2
59 #define SET_FORGROUND      3
60 #define SET_FAKE_MOD       4
61
62 #define SET_TCP_PORT       5
63 #define SET_ROOT_DIR       6
64 #define SET_ROOT_BASE      7
65 #define SET_ROOT_API       8
66 #define SET_ROOT_ALIAS     9
67
68 #define SET_CACHE_TIMEOUT  10
69 #define SET_SESSION_DIR    11
70
71 #define SET_AUTH_TOKEN     12
72 #define SET_LDPATH         13
73 #define SET_APITIMEOUT     14
74 #define SET_CNTXTIMEOUT    15
75
76 #define DISPLAY_VERSION    16
77 #define DISPLAY_HELP       17
78
79 #define SET_MODE           18
80 #define SET_READYFD        19
81
82 // Command line structure hold cli --command + help text
83 typedef struct {
84   int  val;        // command number within application
85   int  has_arg;    // command number within application
86   char *name;      // command as used in --xxxx cli
87   char *help;      // help text
88 } AFB_options;
89
90
91 // Supported option
92 static  AFB_options cliOptions [] = {
93   {SET_VERBOSE      ,0,"verbose"         , "Verbose Mode"},
94
95   {SET_FORGROUND    ,0,"foreground"      , "Get all in foreground mode"},
96   {SET_BACKGROUND   ,0,"daemon"          , "Get all in background mode"},
97
98   {SET_TCP_PORT     ,1,"port"            , "HTTP listening TCP port  [default 1234]"},
99   {SET_ROOT_DIR     ,1,"rootdir"         , "HTTP Root Directory [default $HOME/.AFB]"},
100   {SET_ROOT_BASE    ,1,"rootbase"        , "Angular Base Root URL [default /opa]"},
101   {SET_ROOT_API     ,1,"rootapi"         , "HTML Root API URL [default /api]"},
102   {SET_ROOT_ALIAS   ,1,"alias"           , "Muliple url map outside of rootdir [eg: --alias=/icons:/usr/share/icons]"},
103   
104   {SET_APITIMEOUT   ,1,"apitimeout"      , "Plugin API timeout in seconds [default 10]"},
105   {SET_CNTXTIMEOUT  ,1,"cntxtimeout"     , "Client Session Context Timeout [default 900]"},
106   {SET_CACHE_TIMEOUT     ,1,"cache-eol"       , "Client cache end of live [default 3600s]"},
107   
108   {SET_SESSION_DIR  ,1,"sessiondir"      , "Sessions file path [default rootdir/sessions]"},
109
110   {SET_LDPATH       ,1,"ldpaths"         , "Load Plugins from dir1:dir2:... [default = PLUGIN_INSTALL_DIR"},
111   {SET_AUTH_TOKEN   ,1,"token"           , "Initial Secret [default=no-session, --token="" for session without authentication]"},
112   
113   {DISPLAY_VERSION  ,0,"version"         , "Display version and copyright"},
114   {DISPLAY_HELP     ,0,"help"            , "Display this help"},
115
116   {SET_MODE         ,1,"mode"            , "set the mode: either local, remote or global"},
117   {SET_READYFD      ,1,"readyfd"         , "set the #fd to signal when ready"},
118   {0, 0, NULL, NULL}
119  };
120
121 static AFB_aliasdir aliasdir[MAX_ALIAS];
122 static int aliascount=0;
123
124 // load config from disk and merge with CLI option
125 static AFB_error config_set_default (AFB_session * session)
126 {
127    static char cacheTimeout [10];
128    
129    // default HTTP port
130    if (session->config->httpdPort == 0) session->config->httpdPort=1234;
131    
132    // default Plugin API timeout
133    if (session->config->apiTimeout == 0) session->config->apiTimeout=DEFLT_API_TIMEOUT;
134    
135    // default AUTH_TOKEN
136    if (session->config->token == NULL) session->config->token= DEFLT_AUTH_TOKEN;
137
138    // cache timeout default one hour
139    if (session->config->cacheTimeout == 0) session->config->cacheTimeout=DEFLT_CACHE_TIMEOUT;
140
141    // cache timeout default one hour
142    if (session->config->cntxTimeout == 0) session->config->cntxTimeout=DEFLT_CNTX_TIMEOUT;
143
144    if (session->config->rootdir == NULL) {
145        session->config->rootdir = getenv("AFBDIR");
146        if (session->config->rootdir == NULL) {
147            session->config->rootdir = malloc (512);
148            strncpy  (session->config->rootdir, getenv("HOME"),512);
149            strncat (session->config->rootdir, "/.AFB",512);
150        }
151        // if directory does not exist createit
152        mkdir (session->config->rootdir,  O_RDWR | S_IRWXU | S_IRGRP);
153    }
154    
155    // if no Angular/HTML5 rootbase let's try '/' as default
156    if  (session->config->rootbase == NULL) {
157        session->config->rootbase = "/opa";
158    }
159    
160    if  (session->config->rootapi == NULL) {
161        session->config->rootapi = "/api";
162    }
163
164    if  (session->config->ldpaths == NULL) {
165        session->config->ldpaths = PLUGIN_INSTALL_DIR;
166    }
167
168    // if no session dir create a default path from rootdir
169    if  (session->config->sessiondir == NULL) {
170        session->config->sessiondir = malloc (512);
171        strncpy (session->config->sessiondir, session->config->rootdir, 512);
172        strncat (session->config->sessiondir, "/sessions",512);
173    }
174
175    // if no config dir create a default path from sessiondir
176    if  (session->config->console == NULL) {
177        session->config->console = malloc (512);
178        strncpy (session->config->console, session->config->sessiondir, 512);
179        strncat (session->config->console, "/AFB-console.out",512);
180    }
181
182    // cacheTimeout is an integer but HTTPd wants it as a string
183    snprintf (cacheTimeout, sizeof (cacheTimeout),"%d", session->config->cacheTimeout);
184    session->cacheTimeout = cacheTimeout; // httpd uses cacheTimeout string version
185
186    return AFB_SUCCESS;
187 }
188
189
190
191 /*----------------------------------------------------------
192  | timeout signalQuit
193  |
194  +--------------------------------------------------------- */
195 void signalQuit (int signum) {
196
197   sigset_t sigset;
198
199   // unlock timeout signal to allow a new signal to come
200   sigemptyset (&sigset);
201   sigaddset   (&sigset, SIGABRT);
202   sigprocmask (SIG_UNBLOCK, &sigset, 0);
203
204   fprintf (stderr, "%s ERR:Received signal quit\n",configTime());
205   syslog (LOG_ERR, "Daemon got kill3 & quit [please report bug]");
206   longjmp (exitPoint, signum);
207 }
208
209
210 /*----------------------------------------------------------
211  | printHelp
212  |   print information from long option array
213  +--------------------------------------------------------- */
214
215  static void printHelp(char *name) {
216     int ind;
217     char command[20];
218
219     fprintf (stderr,"%s:\nallowed options\n", name);
220     for (ind=0; cliOptions [ind].name != NULL;ind++)
221     {
222       // display options
223       if (cliOptions [ind].has_arg == 0 )
224       {
225              fprintf (stderr,"  --%-15s %s\n", cliOptions [ind].name, cliOptions[ind].help);
226       } else {
227          sprintf(command,"%s=xxxx", cliOptions [ind].name);
228          fprintf (stderr,"  --%-15s %s\n", command, cliOptions[ind].help);
229       }
230     }
231     fprintf (stderr,"Example:\n  %s\\\n  --verbose --port=1234 --token='azerty' --ldpaths=build/plugins:/usr/lib64/agl/plugins\n", name);
232 } // end printHelp
233
234 /*----------------------------------------------------------
235  | closeSession
236  |   try to close everything before leaving
237  +--------------------------------------------------------- */
238 static void closeSession (AFB_session *session) {
239
240
241 }
242
243 /*----------------------------------------------------------
244  | listenLoop
245  |   Main listening HTTP loop
246  +--------------------------------------------------------- */
247 static void listenLoop (AFB_session *session) {
248   AFB_error  err;
249
250   if (signal (SIGABRT, signalQuit) == SIG_ERR) {
251         fprintf (stderr, "%s ERR: main fail to install Signal handler\n", configTime());
252         return;
253   }
254
255   // ------ Start httpd server
256   if (session->config->httpdPort > 0) {
257
258         err = httpdStart (session);
259         if (err != AFB_SUCCESS) return;
260
261         if (session->readyfd != 0) {
262                 static const char readystr[] = "READY=1";
263                 write(session->readyfd, readystr, sizeof(readystr) - 1);
264                 close(session->readyfd);
265         }
266
267         // infinite loop
268         httpdLoop(session);
269
270         fprintf (stderr, "hoops returned from infinite loop [report bug]\n");
271   }
272 }
273   
274 /*---------------------------------------------------------
275  | main
276  |   Parse option and launch action
277  +--------------------------------------------------------- */
278
279 static void parse_arguments(int argc, char *argv[], AFB_session *session)
280 {
281   char*          programName = argv [0];
282   int            optionIndex = 0;
283   int            optc, ind;
284   int            nbcmd;
285   struct option *gnuOptions;
286
287   // ------------- Build session handler & init config -------
288   memset(&aliasdir  ,0,sizeof(aliasdir));
289   session->config->aliasdir = aliasdir;
290
291   // ------------------ Process Command Line -----------------------
292
293   // if no argument print help and return
294   if (argc < 2) {
295        printHelp(programName);
296        exit(1);
297   }
298
299   // build GNU getopt info from cliOptions
300   nbcmd = sizeof (cliOptions) / sizeof (AFB_options);
301   gnuOptions = malloc (sizeof (*gnuOptions) * (unsigned)nbcmd);
302   for (ind=0; ind < nbcmd;ind++) {
303     gnuOptions [ind].name    = cliOptions[ind].name;
304     gnuOptions [ind].has_arg = cliOptions[ind].has_arg;
305     gnuOptions [ind].flag    = 0;
306     gnuOptions [ind].val     = cliOptions[ind].val;
307   }
308
309   // get all options from command line
310   while ((optc = getopt_long (argc, argv, "vsp?", gnuOptions, &optionIndex))
311         != EOF)
312   {
313     switch (optc)
314     {
315      case SET_VERBOSE:
316        verbose = 1;
317        break;
318
319     case SET_TCP_PORT:
320        if (optarg == 0) goto needValueForOption;
321        if (!sscanf (optarg, "%d", &session->config->httpdPort)) goto notAnInteger;
322        break;
323        
324     case SET_APITIMEOUT:
325        if (optarg == 0) goto needValueForOption;
326        if (!sscanf (optarg, "%d", &session->config->apiTimeout)) goto notAnInteger;
327        break;
328
329     case SET_CNTXTIMEOUT:
330        if (optarg == 0) goto needValueForOption;
331        if (!sscanf (optarg, "%d", &session->config->cntxTimeout)) goto notAnInteger;
332        break;
333
334     case SET_ROOT_DIR:
335        if (optarg == 0) goto needValueForOption;
336        session->config->rootdir   = optarg;
337        if (verbose) fprintf(stderr, "Forcing Rootdir=%s\n",session->config->rootdir);
338        break;       
339        
340     case SET_ROOT_BASE:
341        if (optarg == 0) goto needValueForOption;
342        session->config->rootbase   = optarg;
343        if (verbose) fprintf(stderr, "Forcing Rootbase=%s\n",session->config->rootbase);
344        break;
345
346     case SET_ROOT_API:
347        if (optarg == 0) goto needValueForOption;
348        session->config->rootapi   = optarg;
349        if (verbose) fprintf(stderr, "Forcing Rootapi=%s\n",session->config->rootapi);
350        break;
351        
352     case SET_ROOT_ALIAS:
353        if (optarg == 0) goto needValueForOption;
354        if (aliascount < MAX_ALIAS) {
355             aliasdir[aliascount].url  = strsep(&optarg,":");
356             if (optarg == NULL) {
357               fprintf(stderr, "missing ':' in alias %s, ignored\n", aliasdir[aliascount].url);
358             } else {
359               aliasdir[aliascount].path = optarg;
360               aliasdir[aliascount].len  = strlen(aliasdir[aliascount].url);
361               if (verbose) fprintf(stderr, "Alias url=%s path=%s\n", aliasdir[aliascount].url, aliasdir[aliascount].path);
362               aliascount++;
363             }
364        } else {
365            fprintf(stderr, "Too many aliases [max:%d] %s ignored\n", MAX_ALIAS, optarg);
366        }     
367        break;
368        
369     case SET_AUTH_TOKEN:
370        if (optarg == 0) goto needValueForOption;
371        session->config->token   = optarg;
372        break;
373
374     case SET_LDPATH:
375        if (optarg == 0) goto needValueForOption;
376        session->config->ldpaths = optarg;
377        break;
378
379     case SET_SESSION_DIR:
380        if (optarg == 0) goto needValueForOption;
381        session->config->sessiondir   = optarg;
382        break;
383
384     case  SET_CACHE_TIMEOUT:
385        if (optarg == 0) goto needValueForOption;
386        if (!sscanf (optarg, "%d", &session->config->cacheTimeout)) goto notAnInteger;
387        break;
388
389     case SET_FAKE_MOD:
390        if (optarg != 0) goto noValueForOption;
391        session->fakemod  = 1;
392        break;
393
394     case SET_FORGROUND:
395        if (optarg != 0) goto noValueForOption;
396        session->foreground  = 1;
397        break;
398
399     case SET_BACKGROUND:
400        if (optarg != 0) goto noValueForOption;
401        session->background  = 1;
402        break;
403
404     case SET_MODE:
405        if (optarg == 0) goto needValueForOption;
406        if (!strcmp(optarg, "local")) session->config->mode = AFB_MODE_LOCAL;
407        else if (!strcmp(optarg, "remote")) session->config->mode = AFB_MODE_REMOTE;
408        else if (!strcmp(optarg, "global")) session->config->mode = AFB_MODE_GLOBAL;
409        else goto badMode;
410        break;
411
412     case SET_READYFD:
413        if (optarg == 0) goto needValueForOption;
414        if (!sscanf (optarg, "%u", &session->readyfd)) goto notAnInteger;
415        break;
416
417     case DISPLAY_VERSION:
418        if (optarg != 0) goto noValueForOption;
419        printVersion();
420        exit(0);
421
422     case DISPLAY_HELP:
423      default:
424        printHelp(programName);
425        exit(0);
426     }
427   }
428   free(gnuOptions);
429  
430   // if exist merge config file with CLI arguments
431   config_set_default  (session);
432   return;
433
434
435 needValueForOption:
436   fprintf (stderr,"\nERR: AFB-daemon option [--%s] need a value i.e. --%s=xxx\n\n"
437           ,gnuOptions[optionIndex].name, gnuOptions[optionIndex].name);
438   exit (1);
439
440 notAnInteger:
441   fprintf (stderr,"\nERR: AFB-daemon option [--%s] requirer an interger i.e. --%s=9\n\n"
442           ,gnuOptions[optionIndex].name, gnuOptions[optionIndex].name);
443   exit (1);
444
445 noValueForOption:
446   fprintf (stderr,"\nERR: AFB-daemon option [--%s] don't take value\n\n"
447           ,gnuOptions[optionIndex].name);
448   exit (1);
449
450 badMode:
451   fprintf (stderr,"\nERR: AFB-daemon option [--%s] only accepts local, global or remote.\n\n"
452           ,gnuOptions[optionIndex].name);
453   exit (1);
454 }
455
456 /*---------------------------------------------------------
457  | main
458  |   Parse option and launch action
459  +--------------------------------------------------------- */
460
461 int main(int argc, char *argv[])  {
462   AFB_session    *session;
463   int            consoleFD;
464   int            pid, status;
465
466   // ------------- Build session handler & init config -------
467   session = calloc (1, sizeof (AFB_session));
468   session->config = calloc (1, sizeof (AFB_config));
469   parse_arguments(argc, argv, session);
470
471   initPlugins(session);
472
473   // ------------------ sanity check ----------------------------------------
474   if  ((session->background) && (session->foreground)) {
475     fprintf (stderr, "%s ERR: cannot select foreground & background at the same time\n",configTime());
476      exit (1);
477   }
478
479   // ------------------ Some useful default values -------------------------
480   if  ((session->background == 0) && (session->foreground == 0)) session->foreground=1;
481
482   // open syslog if ever needed
483   openlog("AFB-log", 0, LOG_DAEMON);
484
485   // ------------------ clean exit on CTR-C signal ------------------------
486   if (signal (SIGINT, signalQuit) == SIG_ERR) {
487     fprintf (stderr, "%s Quit Signal received.",configTime());
488     return 1;
489   }
490
491   // save exitPoint context when returning from longjmp closeSession and exit
492   status = setjmp (exitPoint); // return !+ when coming from longjmp
493   if (status != 0) {
494     if (verbose) printf ("INF: main returning from longjump after signal [%d]\n", status);
495     closeSession (session);
496     goto exitOnSignal;
497   }
498
499   // let's run this program with a low priority
500   status=nice (20);
501
502   // ------------------ Finaly Process Commands -----------------------------
503     // let's not take the risk to run as ROOT
504     //if (getuid() == 0)  goto errorNoRoot;
505
506     // check session dir and create if it does not exist
507     if (sessionCheckdir (session) != AFB_SUCCESS) goto errSessiondir;
508     if (verbose) fprintf (stderr, "AFB: notice Init config done\n");
509
510
511
512     // ---- run in foreground mode --------------------
513     if (session->foreground) {
514
515         if (verbose) fprintf (stderr,"AFB: notice Foreground mode\n");
516
517         // enter listening loop in foreground
518         listenLoop(session);
519         goto exitInitLoop;
520   } // end foreground
521
522
523   // --------- run in background mode -----------
524   if (session->background) {
525
526        // if (status != 0) goto errorCommand;
527       if (verbose) printf ("AFB: Entering background mode\n");
528
529       // open /dev/console to redirect output messAFBes
530       consoleFD = open(session->config->console, O_WRONLY | O_APPEND | O_CREAT , 0640);
531       if (consoleFD < 0) goto errConsole;
532
533       // fork process when running background mode
534       pid = fork ();
535
536       // son process get all data in standalone mode
537       if (pid == 0) {
538
539              printf ("\nAFB: background mode [pid:%d console:%s]\n", getpid(),session->config->console);
540
541          // redirect default I/O on console
542          close (2); status=dup(consoleFD);  // redirect stderr
543          close (1); status=dup(consoleFD);  // redirect stdout
544          close (0);           // no need for stdin
545          close (consoleFD);
546
547          setsid();   // allow father process to fully exit
548              sleep (2);  // allow main to leave and release port
549
550          fprintf (stderr, "----------------------------\n");
551          fprintf (stderr, "%s INF: main background pid=%d\n", configTime(), getpid());
552          fflush  (stderr);
553
554          // if everything look OK then look forever
555          syslog (LOG_ERR, "AFB: Entering infinite loop in background mode");
556
557          // should normally never return from this loop
558          listenLoop(session);
559          syslog (LOG_ERR, "AFB: FAIL background infinite loop exited check [%s]\n", session->config->console);
560
561          goto exitInitLoop;
562       }
563
564       // if fail nothing much to do
565       if (pid == -1) goto errorFork;
566
567       // we are in father process, we don't need this one
568       _exit (0);
569
570   } // end background-foreground
571
572 normalExit:
573   closeSession (session);   // try to close everything before leaving
574   if (verbose) printf ("\n---- Application Framework Binder Normal End ------\n");
575   exit(0);
576
577 // ------------- Fatal ERROR display error and quit  -------------
578 errorFork:
579   fprintf (stderr,"\nERR: AFB-daemon Failed to fork son process\n\n");
580   exit (1);
581
582 exitOnSignal:
583   fprintf (stderr,"\n%s INF: AFB-daemon pid=%d received exit signal (Hopefully crtl-C or --kill-previous !!!)\n\n"
584                  ,configTime(), getpid());
585   exit (1);
586
587 errConsole:
588   fprintf (stderr,"\nERR: AFB-daemon cannot open /dev/console (use --foreground)\n\n");
589   exit (1);
590
591 errSessiondir:
592   fprintf (stderr,"\nERR: AFB-daemon cannot read/write session dir\n\n");
593   exit (1);
594
595 exitInitLoop:
596   exit (1);
597 }
598
599