eee4ccc020ee1e12e5f9921091e72ef36f573a1e
[src/app-framework-binder.git] / src / afs-config.c
1 /*
2  * Copyright (C) 2015-2018 "IoT.bzh"
3  * Author José Bollo <jose.bollo@iot.bzh>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #define _GNU_SOURCE
19
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <getopt.h>
24 #include <limits.h>
25 #include <unistd.h>
26
27 #include "verbose.h"
28 #include "afs-config.h"
29
30 #if !defined(AFB_VERSION)
31 #  error "you should define AFB_VERSION"
32 #endif
33
34 #if !defined(AFS_SUPERVISOR_TOKEN)
35 #  define AFS_SUPERVISOR_TOKEN    ""
36 #endif
37 #if !defined(AFS_SUPERVISOR_PORT)
38 #  define AFS_SUPERVISOR_PORT     1619
39 #endif
40 #define _STRINGIFY_(x) #x
41 #define STRINGIFY(x) _STRINGIFY_(x)
42
43 // default
44 #define DEFLT_CNTX_TIMEOUT  32000000    // default Client Connection
45                                         // Timeout: few more than one year
46 #define DEFLT_API_TIMEOUT   20          // default Plugin API Timeout [0=NoLimit
47                                         // for Debug Only]
48 #define DEFLT_CACHE_TIMEOUT 100000      // default Static File Chache
49                                         // [Client Side Cache
50                                         // 100000~=1day]
51 #define CTX_NBCLIENTS       10          // allow a default of 10 authenticated
52                                         // clients
53
54
55 // Define command line option
56 #define SET_ROOT_DIR       6
57 #define SET_ROOT_BASE      7
58 #define SET_ROOT_API       8
59
60 #define SET_CACHE_TIMEOUT  10
61 #define SET_SESSION_DIR    11
62
63 #define SET_APITIMEOUT     14
64 #define SET_CNTXTIMEOUT    15
65
66 #define SET_SESSIONMAX     23
67
68 #define SET_ROOT_HTTP      26
69
70 #define DISPLAY_HELP       'h'
71 #define SET_NAME           'n'
72 #define SET_TCP_PORT       'p'
73 #define SET_QUIET          'q'
74 #define SET_AUTH_TOKEN     't'
75 #define SET_UPLOAD_DIR     'u'
76 #define DISPLAY_VERSION    'V'
77 #define SET_VERBOSE        'v'
78 #define SET_WORK_DIR       'w'
79
80 const char shortopts[] =
81         "hn:p:qrt:u:Vvw:"
82 ;
83
84 // Command line structure hold cli --command + help text
85 typedef struct {
86         int val;                // command number within application
87         int has_arg;            // command number within application
88         char *name;             // command as used in --xxxx cli
89         char *help;             // help text
90 } AFB_options;
91
92 // Supported option
93 static AFB_options cliOptions[] = {
94 /* *INDENT-OFF* */
95         {SET_VERBOSE,       0, "verbose",     "Verbose Mode, repeat to increase verbosity"},
96         {SET_QUIET,         0, "quiet",       "Quiet Mode, repeat to decrease verbosity"},
97
98         {SET_NAME,          1, "name",        "Set the visible name"},
99
100         {SET_TCP_PORT,      1, "port",        "HTTP listening TCP port  [default " STRINGIFY(AFS_SUPERVISOR_PORT) "]"},
101         {SET_ROOT_HTTP,     1, "roothttp",    "HTTP Root Directory [default no root http (files not served but apis still available)]"},
102         {SET_ROOT_BASE,     1, "rootbase",    "Angular Base Root URL [default /opa]"},
103         {SET_ROOT_API,      1, "rootapi",     "HTML Root API URL [default /api]"},
104
105         {SET_APITIMEOUT,    1, "apitimeout",  "Binding API timeout in seconds [default 10]"},
106         {SET_CNTXTIMEOUT,   1, "cntxtimeout", "Client Session Context Timeout [default 900]"},
107         {SET_CACHE_TIMEOUT, 1, "cache-eol",   "Client cache end of live [default 3600]"},
108
109         {SET_WORK_DIR,      1, "workdir",     "Set the working directory [default: $PWD or current working directory]"},
110         {SET_UPLOAD_DIR,    1, "uploaddir",   "Directory for uploading files [default: workdir]"},
111         {SET_ROOT_DIR,      1, "rootdir",     "Root Directory of the application [default: workdir]"},
112         {SET_SESSION_DIR,   1, "sessiondir",  "OBSOLETE (was: Sessions file path)"},
113
114         {SET_AUTH_TOKEN,    1, "token",       "Initial Secret [default=" AFS_SUPERVISOR_TOKEN ", use --token="" to allow any token]"},
115
116         {DISPLAY_VERSION,   0, "version",     "Display version and copyright"},
117         {DISPLAY_HELP,      0, "help",        "Display this help"},
118
119         {SET_SESSIONMAX,    1, "session-max", "Max count of session simultaneously [default 10]"},
120
121         {0, 0, NULL, NULL}
122 /* *INDENT-ON* */
123 };
124
125
126 struct enumdesc
127 {
128         const char *name;
129         int value;
130 };
131
132 /*----------------------------------------------------------
133  | printversion
134  |   print version and copyright
135  +--------------------------------------------------------- */
136 static void printVersion(FILE * file)
137 {
138         static const char version[] =
139                 "\n"
140                 "  afs-supervisor [Application Framework Supervisor] version="AFB_VERSION"\n"
141                 "\n"
142                 "  Copyright (C) 2015-2018 \"IoT.bzh\"\n"
143                 "  afs-supervisor comes with ABSOLUTELY NO WARRANTY.\n"
144                 "  Licence Apache 2\n"
145                 "\n";
146
147         fprintf(file, "%s", version);
148 }
149
150 /*----------------------------------------------------------
151  | printHelp
152  |   print information from long option array
153  +--------------------------------------------------------- */
154
155 static void printHelp(FILE * file, const char *name)
156 {
157         int ind;
158         char command[50];
159
160         fprintf(file, "%s:\nallowed options\n", name);
161         for (ind = 0; cliOptions[ind].name != NULL; ind++) {
162                 strcpy(command, cliOptions[ind].name);
163                 if (cliOptions[ind].has_arg)
164                         strcat(command, "=xxxx");
165                 fprintf(file, "  --%-15s %s\n", command, cliOptions[ind].help);
166         }
167         fprintf(file,
168                 "Example:\n  %s  --verbose --port=1234 --token='azerty'\n",
169                 name);
170 }
171
172
173 /*---------------------------------------------------------
174  |   helpers for argument scanning
175  +--------------------------------------------------------- */
176
177 static const char *name_of_option(int optc)
178 {
179         AFB_options *o = cliOptions;
180         while (o->name && o->val != optc)
181                 o++;
182         return o->name ? : "<unknown-option-name>";
183 }
184
185 static const char *current_argument(int optc)
186 {
187         if (optarg == 0) {
188                 ERROR("option [--%s] needs a value i.e. --%s=xxx",
189                       name_of_option(optc), name_of_option(optc));
190                 exit(1);
191         }
192         return optarg;
193 }
194
195 static char *argvalstr(int optc)
196 {
197         char *result = strdup(current_argument(optc));
198         if (result == NULL) {
199                 ERROR("can't alloc memory");
200                 exit(1);
201         }
202         return result;
203 }
204
205 static int argvalint(int optc, int mini, int maxi, int base)
206 {
207         const char *beg, *end;
208         long int val;
209         beg = current_argument(optc);
210         val = strtol(beg, (char**)&end, base);
211         if (*end || end == beg) {
212                 ERROR("option [--%s] requires a valid integer (found %s)",
213                         name_of_option(optc), beg);
214                 exit(1);
215         }
216         if (val < (long int)mini || val > (long int)maxi) {
217                 ERROR("option [--%s] value out of bounds (not %d<=%ld<=%d)",
218                         name_of_option(optc), mini, val, maxi);
219                 exit(1);
220         }
221         return (int)val;
222 }
223
224 static int argvalintdec(int optc, int mini, int maxi)
225 {
226         return argvalint(optc, mini, maxi, 10);
227 }
228
229 static void noarg(int optc)
230 {
231         if (optarg != 0) {
232                 ERROR("option [--%s] need no value (found %s)", name_of_option(optc), optarg);
233                 exit(1);
234         }
235 }
236
237 /*---------------------------------------------------------
238  |   Parse option and launch action
239  +--------------------------------------------------------- */
240
241 static void parse_arguments(int argc, char **argv, struct afs_config *config)
242 {
243         char *programName = argv[0];
244         int optc, ind;
245         int nbcmd;
246         struct option *gnuOptions;
247
248         // ------------------ Process Command Line -----------------------
249
250         // build GNU getopt info from cliOptions
251         nbcmd = sizeof(cliOptions) / sizeof(AFB_options);
252         gnuOptions = malloc(sizeof(*gnuOptions) * (unsigned)nbcmd);
253         for (ind = 0; ind < nbcmd; ind++) {
254                 gnuOptions[ind].name = cliOptions[ind].name;
255                 gnuOptions[ind].has_arg = cliOptions[ind].has_arg;
256                 gnuOptions[ind].flag = 0;
257                 gnuOptions[ind].val = cliOptions[ind].val;
258         }
259
260         // get all options from command line
261         while ((optc = getopt_long(argc, argv, shortopts, gnuOptions, NULL)) != EOF) {
262                 switch (optc) {
263                 case SET_VERBOSE:
264                         verbosity++;
265                         break;
266
267                 case SET_QUIET:
268                         verbosity--;
269                         break;
270
271                 case SET_TCP_PORT:
272                         config->httpdPort = argvalintdec(optc, 1024, 32767);
273                         break;
274
275                 case SET_APITIMEOUT:
276                         config->apiTimeout = argvalintdec(optc, 0, INT_MAX);
277                         break;
278
279                 case SET_CNTXTIMEOUT:
280                         config->cntxTimeout = argvalintdec(optc, 0, INT_MAX);
281                         break;
282
283                 case SET_ROOT_DIR:
284                         config->rootdir = argvalstr(optc);
285                         INFO("Forcing Rootdir=%s", config->rootdir);
286                         break;
287
288                 case SET_ROOT_HTTP:
289                         config->roothttp = argvalstr(optc);
290                         INFO("Forcing Root HTTP=%s", config->roothttp);
291                         break;
292
293                 case SET_ROOT_BASE:
294                         config->rootbase = argvalstr(optc);
295                         INFO("Forcing Rootbase=%s", config->rootbase);
296                         break;
297
298                 case SET_ROOT_API:
299                         config->rootapi = argvalstr(optc);
300                         INFO("Forcing Rootapi=%s", config->rootapi);
301                         break;
302
303                 case SET_AUTH_TOKEN:
304                         config->token = argvalstr(optc);
305                         break;
306
307                 case SET_UPLOAD_DIR:
308                         config->uploaddir = argvalstr(optc);
309                         break;
310
311                 case SET_WORK_DIR:
312                         config->workdir = argvalstr(optc);
313                         break;
314
315                 case SET_CACHE_TIMEOUT:
316                         config->cacheTimeout = argvalintdec(optc, 0, INT_MAX);
317                         break;
318
319                 case SET_SESSIONMAX:
320                         config->nbSessionMax = argvalintdec(optc, 1, INT_MAX);
321                         break;
322
323                 case SET_NAME:
324                         config->name = argvalstr(optc);
325                         break;
326
327                 case DISPLAY_VERSION:
328                         noarg(optc);
329                         printVersion(stdout);
330                         exit(0);
331
332                 case DISPLAY_HELP:
333                         printHelp(stdout, programName);
334                         exit(0);
335
336                 default:
337                         exit(1);
338                 }
339         }
340         free(gnuOptions);
341 }
342
343 static void fulfill_config(struct afs_config *config)
344 {
345         // default HTTP port
346         if (config->httpdPort == 0)
347                 config->httpdPort = AFS_SUPERVISOR_PORT;
348
349         // default binding API timeout
350         if (config->apiTimeout == 0)
351                 config->apiTimeout = DEFLT_API_TIMEOUT;
352
353         // cache timeout default one hour
354         if (config->cacheTimeout == 0)
355                 config->cacheTimeout = DEFLT_CACHE_TIMEOUT;
356
357         // cache timeout default one hour
358         if (config->cntxTimeout == 0)
359                 config->cntxTimeout = DEFLT_CNTX_TIMEOUT;
360
361         // max count of sessions
362         if (config->nbSessionMax == 0)
363                 config->nbSessionMax = CTX_NBCLIENTS;
364
365         /* set directories */
366         if (config->workdir == NULL)
367                 config->workdir = ".";
368
369         if (config->rootdir == NULL)
370                 config->rootdir = ".";
371
372         if (config->uploaddir == NULL)
373                 config->uploaddir = ".";
374
375         // if no Angular/HTML5 rootbase let's try '/' as default
376         if (config->rootbase == NULL)
377                 config->rootbase = "/opa";
378
379         if (config->rootapi == NULL)
380                 config->rootapi = "/api";
381
382         if (config->token == NULL)
383                 config->token = AFS_SUPERVISOR_TOKEN;
384 }
385
386 void afs_config_dump(struct afs_config *config)
387 {
388 #define NN(x)   (x)?:""
389 #define P(...)  fprintf(stderr, __VA_ARGS__)
390 #define PF(x)   P("-- %15s: ", #x)
391 #define PE      P("\n")
392 #define S(x)    PF(x);P("%s",NN(config->x));PE;
393 #define D(x)    PF(x);P("%d",config->x);PE;
394
395         P("---BEGIN-OF-CONFIG---\n");
396         S(rootdir)
397         S(roothttp)
398         S(rootbase)
399         S(rootapi)
400         S(workdir)
401         S(uploaddir)
402         S(token)
403         S(name)
404
405         D(httpdPort)
406         D(cacheTimeout)
407         D(apiTimeout)
408         D(cntxTimeout)
409         D(nbSessionMax)
410         P("---END-OF-CONFIG---\n");
411
412 #undef V
413 #undef E
414 #undef L
415 #undef B
416 #undef D
417 #undef S
418 #undef PE
419 #undef PF
420 #undef P
421 #undef NN
422 }
423
424 static void parse_environment(struct afs_config *config)
425 {
426 }
427
428 struct afs_config *afs_config_parse_arguments(int argc, char **argv)
429 {
430         struct afs_config *result;
431
432         result = calloc(1, sizeof *result);
433
434         parse_environment(result);
435         parse_arguments(argc, argv, result);
436         fulfill_config(result);
437         if (verbosity >= 3)
438                 afs_config_dump(result);
439         return result;
440 }
441