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