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