]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/dialog/rc.c
Merge commit 'ee914ef902ae018bd4f67192832120f9bf05651f' into new_merge
[FreeBSD/FreeBSD.git] / contrib / dialog / rc.c
1 /*
2  *  $Id: rc.c,v 1.60 2020/11/25 00:06:40 tom Exp $
3  *
4  *  rc.c -- routines for processing the configuration file
5  *
6  *  Copyright 2000-2019,2020    Thomas E. Dickey
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU Lesser General Public License, version 2.1
10  *  as published by the Free Software Foundation.
11  *
12  *  This program is distributed in the hope that it will be useful, but
13  *  WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *  Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public
18  *  License along with this program; if not, write to
19  *      Free Software Foundation, Inc.
20  *      51 Franklin St., Fifth Floor
21  *      Boston, MA 02110, USA.
22  *
23  *  An earlier version of this program lists as authors
24  *      Savio Lam (lam836@cs.cuhk.hk)
25  */
26
27 #include <dialog.h>
28
29 #include <dlg_keys.h>
30
31 #ifdef HAVE_COLOR
32 #include <dlg_colors.h>
33 #include <dlg_internals.h>
34
35 #define L_PAREN '('
36 #define R_PAREN ')'
37
38 #define MIN_TOKEN 3
39 #ifdef HAVE_RC_FILE2
40 #define MAX_TOKEN 5
41 #else
42 #define MAX_TOKEN MIN_TOKEN
43 #endif
44
45 #define UNKNOWN_COLOR -2
46
47 /*
48  * For matching color names with color values
49  */
50 static const color_names_st color_names[] =
51 {
52 #ifdef HAVE_USE_DEFAULT_COLORS
53     {"DEFAULT", -1},
54 #endif
55     {"BLACK", COLOR_BLACK},
56     {"RED", COLOR_RED},
57     {"GREEN", COLOR_GREEN},
58     {"YELLOW", COLOR_YELLOW},
59     {"BLUE", COLOR_BLUE},
60     {"MAGENTA", COLOR_MAGENTA},
61     {"CYAN", COLOR_CYAN},
62     {"WHITE", COLOR_WHITE},
63 };                              /* color names */
64 #define COLOR_COUNT     TableSize(color_names)
65 #endif /* HAVE_COLOR */
66
67 #define GLOBALRC "/etc/dialogrc"
68 #define DIALOGRC ".dialogrc"
69
70 /* Types of values */
71 #define VAL_INT  0
72 #define VAL_STR  1
73 #define VAL_BOOL 2
74
75 /* Type of line in configuration file */
76 typedef enum {
77     LINE_ERROR = -1,
78     LINE_EQUALS,
79     LINE_EMPTY
80 } PARSE_LINE;
81
82 /* number of configuration variables */
83 #define VAR_COUNT        TableSize(vars)
84
85 /* check if character is string quoting characters */
86 #define isquote(c)       ((c) == '"' || (c) == '\'')
87
88 /* get last character of string */
89 #define lastch(str)      str[strlen(str)-1]
90
91 /*
92  * Configuration variables
93  */
94 typedef struct {
95     const char *name;           /* name of configuration variable as in DIALOGRC */
96     void *var;                  /* address of actual variable to change */
97     int type;                   /* type of value */
98     const char *comment;        /* comment to put in "rc" file */
99 } vars_st;
100
101 /*
102  * This table should contain only references to dialog_state, since dialog_vars
103  * is reset specially in dialog.c before each widget.
104  */
105 static const vars_st vars[] =
106 {
107     {"aspect",
108      &dialog_state.aspect_ratio,
109      VAL_INT,
110      "Set aspect-ration."},
111
112     {"separate_widget",
113      &dialog_state.separate_str,
114      VAL_STR,
115      "Set separator (for multiple widgets output)."},
116
117     {"tab_len",
118      &dialog_state.tab_len,
119      VAL_INT,
120      "Set tab-length (for textbox tab-conversion)."},
121
122     {"visit_items",
123      &dialog_state.visit_items,
124      VAL_BOOL,
125      "Make tab-traversal for checklist, etc., include the list."},
126
127 #ifdef HAVE_COLOR
128     {"use_shadow",
129      &dialog_state.use_shadow,
130      VAL_BOOL,
131      "Shadow dialog boxes? This also turns on color."},
132
133     {"use_colors",
134      &dialog_state.use_colors,
135      VAL_BOOL,
136      "Turn color support ON or OFF"},
137 #endif                          /* HAVE_COLOR */
138 };                              /* vars */
139
140 static int
141 skip_whitespace(char *str, int n)
142 {
143     while (isblank(UCH(str[n])) && str[n] != '\0')
144         n++;
145     return n;
146 }
147
148 static int
149 skip_keyword(char *str, int n)
150 {
151     while (isalnum(UCH(str[n])) && str[n] != '\0')
152         n++;
153     return n;
154 }
155
156 static void
157 trim_token(char **tok)
158 {
159     char *tmp = *tok + skip_whitespace(*tok, 0);
160
161     *tok = tmp;
162
163     while (*tmp != '\0' && !isblank(UCH(*tmp)))
164         tmp++;
165
166     *tmp = '\0';
167 }
168
169 static int
170 from_boolean(const char *str)
171 {
172     int code = -1;
173
174     if (str != NULL && *str != '\0') {
175         if (!dlg_strcmp(str, "ON")) {
176             code = 1;
177         } else if (!dlg_strcmp(str, "OFF")) {
178             code = 0;
179         }
180     }
181     return code;
182 }
183
184 static int
185 from_color_name(const char *str)
186 {
187     int code = UNKNOWN_COLOR;
188
189     if (str != NULL && *str != '\0') {
190         size_t i;
191
192         for (i = 0; i < COLOR_COUNT; ++i) {
193             if (!dlg_strcmp(str, color_names[i].name)) {
194                 code = color_names[i].value;
195                 break;
196             }
197         }
198     }
199     return code;
200 }
201
202 static int
203 find_vars(char *name)
204 {
205     int result = -1;
206     unsigned i;
207
208     for (i = 0; i < VAR_COUNT; i++) {
209         if (dlg_strcmp(vars[i].name, name) == 0) {
210             result = (int) i;
211             break;
212         }
213     }
214     return result;
215 }
216
217 #ifdef HAVE_COLOR
218 static int
219 find_color(char *name)
220 {
221     int result = -1;
222     int i;
223     int limit = dlg_color_count();
224
225     for (i = 0; i < limit; i++) {
226         if (dlg_strcmp(dlg_color_table[i].name, name) == 0) {
227             result = i;
228             break;
229         }
230     }
231     return result;
232 }
233
234 static const char *
235 to_color_name(int code)
236 {
237     const char *result = "?";
238     size_t n;
239     for (n = 0; n < TableSize(color_names); ++n) {
240         if (code == color_names[n].value) {
241             result = color_names[n].name;
242             break;
243         }
244     }
245     return result;
246 }
247
248 static const char *
249 to_boolean(int code)
250 {
251     return code ? "ON" : "OFF";
252 }
253
254 /*
255  * Extract the foreground, background and highlight values from an attribute
256  * represented as a string in one of these forms:
257  *
258  * "(foreground,background,highlight,underline,reverse)"
259  * "(foreground,background,highlight,underline)"
260  * "(foreground,background,highlight)"
261  * "xxxx_color"
262  */
263 static int
264 str_to_attr(char *str, DIALOG_COLORS * result)
265 {
266     char *tokens[MAX_TOKEN + 1];
267     char tempstr[MAX_LEN + 1];
268     size_t have;
269     size_t i = 0;
270     size_t tok_count = 0;
271
272     memset(result, 0, sizeof(*result));
273     result->fg = -1;
274     result->bg = -1;
275     result->hilite = -1;
276
277     if (str[0] != L_PAREN || lastch(str) != R_PAREN) {
278         int ret;
279
280         if ((ret = find_color(str)) >= 0) {
281             *result = dlg_color_table[ret];
282             return 0;
283         }
284         /* invalid representation */
285         return -1;
286     }
287
288     /* remove the parenthesis */
289     have = strlen(str);
290     if (have > MAX_LEN) {
291         have = MAX_LEN - 1;
292     } else {
293         have -= 2;
294     }
295     memcpy(tempstr, str + 1, have);
296     tempstr[have] = '\0';
297
298     /* parse comma-separated tokens, allow up to
299      * one more than max tokens to detect extras */
300     while (tok_count < TableSize(tokens)) {
301
302         tokens[tok_count++] = &tempstr[i];
303
304         while (tempstr[i] != '\0' && tempstr[i] != ',')
305             i++;
306
307         if (tempstr[i] == '\0')
308             break;
309
310         tempstr[i++] = '\0';
311     }
312
313     if (tok_count < MIN_TOKEN || tok_count > MAX_TOKEN) {
314         /* invalid representation */
315         return -1;
316     }
317
318     for (i = 0; i < tok_count; ++i)
319         trim_token(&tokens[i]);
320
321     /* validate */
322     if (UNKNOWN_COLOR == (result->fg = from_color_name(tokens[0]))
323         || UNKNOWN_COLOR == (result->bg = from_color_name(tokens[1]))
324         || UNKNOWN_COLOR == (result->hilite = from_boolean(tokens[2]))
325 #ifdef HAVE_RC_FILE2
326         || (tok_count >= 4 && (result->ul = from_boolean(tokens[3])) == -1)
327         || (tok_count >= 5 && (result->rv = from_boolean(tokens[4])) == -1)
328 #endif /* HAVE_RC_FILE2 */
329         ) {
330         /* invalid representation */
331         return -1;
332     }
333
334     return 0;
335 }
336 #endif /* HAVE_COLOR */
337
338 /*
339  * Check if the line begins with a special keyword; if so, return true while
340  * pointing params to its parameters.
341  */
342 static int
343 begins_with(char *line, const char *keyword, char **params)
344 {
345     int i = skip_whitespace(line, 0);
346     int j = skip_keyword(line, i);
347
348     if ((j - i) == (int) strlen(keyword)) {
349         char save = line[j];
350         line[j] = 0;
351         if (!dlg_strcmp(keyword, line + i)) {
352             *params = line + skip_whitespace(line, j + 1);
353             return 1;
354         }
355         line[j] = save;
356     }
357
358     return 0;
359 }
360
361 /*
362  * Parse a line in the configuration file
363  *
364  * Each line is of the form:  "variable = value". On exit, 'var' will contain
365  * the variable name, and 'value' will contain the value string.
366  *
367  * Return values:
368  *
369  * LINE_EMPTY   - line is blank or comment
370  * LINE_EQUALS  - line contains "variable = value"
371  * LINE_ERROR   - syntax error in line
372  */
373 static PARSE_LINE
374 parse_line(char *line, char **var, char **value)
375 {
376     int i = 0;
377
378     /* ignore white space at beginning of line */
379     i = skip_whitespace(line, i);
380
381     if (line[i] == '\0')        /* line is blank */
382         return LINE_EMPTY;
383     else if (line[i] == '#')    /* line is comment */
384         return LINE_EMPTY;
385     else if (line[i] == '=')    /* variable names cannot start with a '=' */
386         return LINE_ERROR;
387
388     /* set 'var' to variable name */
389     *var = line + i++;          /* skip to next character */
390
391     /* find end of variable name */
392     while (!isblank(UCH(line[i])) && line[i] != '=' && line[i] != '\0')
393         i++;
394
395     if (line[i] == '\0')        /* syntax error */
396         return LINE_ERROR;
397     else if (line[i] == '=')
398         line[i++] = '\0';
399     else {
400         line[i++] = '\0';
401
402         /* skip white space before '=' */
403         i = skip_whitespace(line, i);
404
405         if (line[i] != '=')     /* syntax error */
406             return LINE_ERROR;
407         else
408             i++;                /* skip the '=' */
409     }
410
411     /* skip white space after '=' */
412     i = skip_whitespace(line, i);
413
414     if (line[i] == '\0')
415         return LINE_ERROR;
416     else
417         *value = line + i;      /* set 'value' to value string */
418
419     /* trim trailing white space from 'value' */
420     i = (int) strlen(*value) - 1;
421     while (isblank(UCH((*value)[i])) && i > 0)
422         i--;
423     (*value)[i + 1] = '\0';
424
425     return LINE_EQUALS;         /* no syntax error in line */
426 }
427
428 /*
429  * Create the configuration file
430  */
431 void
432 dlg_create_rc(const char *filename)
433 {
434     unsigned i;
435     FILE *rc_file;
436
437     if ((rc_file = fopen(filename, "wt")) == NULL)
438         dlg_exiterr("Error opening file for writing in dlg_create_rc().");
439
440     fprintf(rc_file, "#\n\
441 # Run-time configuration file for dialog\n\
442 #\n\
443 # Automatically generated by \"dialog --create-rc <file>\"\n\
444 #\n\
445 #\n\
446 # Types of values:\n\
447 #\n\
448 # Number     -  <number>\n\
449 # String     -  \"string\"\n\
450 # Boolean    -  <ON|OFF>\n"
451 #ifdef HAVE_COLOR
452 #ifdef HAVE_RC_FILE2
453             "\
454 # Attribute  -  (foreground,background,highlight?,underline?,reverse?)\n"
455 #else /* HAVE_RC_FILE2 */
456             "\
457 # Attribute  -  (foreground,background,highlight?)\n"
458 #endif /* HAVE_RC_FILE2 */
459 #endif /* HAVE_COLOR */
460         );
461
462     /* Print an entry for each configuration variable */
463     for (i = 0; i < VAR_COUNT; i++) {
464         fprintf(rc_file, "\n# %s\n", vars[i].comment);
465         switch (vars[i].type) {
466         case VAL_INT:
467             fprintf(rc_file, "%s = %d\n", vars[i].name,
468                     *((int *) vars[i].var));
469             break;
470         case VAL_STR:
471             fprintf(rc_file, "%s = \"%s\"\n", vars[i].name,
472                     (char *) vars[i].var);
473             break;
474         case VAL_BOOL:
475             fprintf(rc_file, "%s = %s\n", vars[i].name,
476                     *((bool *) vars[i].var) ? "ON" : "OFF");
477             break;
478         }
479     }
480 #ifdef HAVE_COLOR
481     for (i = 0; i < (unsigned) dlg_color_count(); ++i) {
482         unsigned j;
483         bool repeat = FALSE;
484
485         fprintf(rc_file, "\n# %s\n", dlg_color_table[i].comment);
486         for (j = 0; j != i; ++j) {
487             if (dlg_color_table[i].fg == dlg_color_table[j].fg
488                 && dlg_color_table[i].bg == dlg_color_table[j].bg
489                 && dlg_color_table[i].hilite == dlg_color_table[j].hilite) {
490                 fprintf(rc_file, "%s = %s\n",
491                         dlg_color_table[i].name,
492                         dlg_color_table[j].name);
493                 repeat = TRUE;
494                 break;
495             }
496         }
497
498         if (!repeat) {
499             fprintf(rc_file, "%s = %c", dlg_color_table[i].name, L_PAREN);
500             fprintf(rc_file, "%s", to_color_name(dlg_color_table[i].fg));
501             fprintf(rc_file, ",%s", to_color_name(dlg_color_table[i].bg));
502             fprintf(rc_file, ",%s", to_boolean(dlg_color_table[i].hilite));
503 #ifdef HAVE_RC_FILE2
504             if (dlg_color_table[i].ul || dlg_color_table[i].rv)
505                 fprintf(rc_file, ",%s", to_boolean(dlg_color_table[i].ul));
506             if (dlg_color_table[i].rv)
507                 fprintf(rc_file, ",%s", to_boolean(dlg_color_table[i].rv));
508 #endif /* HAVE_RC_FILE2 */
509             fprintf(rc_file, "%c\n", R_PAREN);
510         }
511     }
512 #endif /* HAVE_COLOR */
513     dlg_dump_keys(rc_file);
514
515     (void) fclose(rc_file);
516 }
517
518 static void
519 report_error(const char *filename, int line_no, const char *msg)
520 {
521     fprintf(stderr, "%s:%d: %s\n", filename, line_no, msg);
522     dlg_trace_msg("%s:%d: %s\n", filename, line_no, msg);
523 }
524
525 /*
526  * Parse the configuration file and set up variables
527  */
528 int
529 dlg_parse_rc(void)
530 {
531     int i;
532     int l = 1;
533     PARSE_LINE parse;
534     char str[MAX_LEN + 1];
535     char *var;
536     char *value;
537     char *filename;
538     int result = 0;
539     FILE *rc_file = 0;
540     char *params;
541
542     /*
543      *  At startup, dialog determines the settings to use as follows:
544      *
545      *  a) if the environment variable $DIALOGRC is set, its value determines
546      *     the name of the configuration file.
547      *
548      *  b) if the file in (a) can't be found, use the file $HOME/.dialogrc
549      *     as the configuration file.
550      *
551      *  c) if the file in (b) can't be found, try using the GLOBALRC file.
552      *     Usually this will be /etc/dialogrc.
553      *
554      *  d) if the file in (c) cannot be found, use the compiled-in defaults.
555      */
556
557     /* try step (a) */
558     if ((filename = dlg_getenv_str("DIALOGRC")) != NULL)
559         rc_file = fopen(filename, "rt");
560
561     if (rc_file == NULL) {      /* step (a) failed? */
562         /* try step (b) */
563         if ((filename = dlg_getenv_str("HOME")) != NULL
564             && strlen(filename) < MAX_LEN - (sizeof(DIALOGRC) + 3)) {
565             if (filename[0] == '\0' || lastch(filename) == '/')
566                 sprintf(str, "%s%s", filename, DIALOGRC);
567             else
568                 sprintf(str, "%s/%s", filename, DIALOGRC);
569             rc_file = fopen(filename = str, "rt");
570         }
571     }
572
573     if (rc_file == NULL) {      /* step (b) failed? */
574         /* try step (c) */
575         strcpy(str, GLOBALRC);
576         if ((rc_file = fopen(filename = str, "rt")) == NULL)
577             return 0;           /* step (c) failed, use default values */
578     }
579
580     DLG_TRACE(("# opened rc file \"%s\"\n", filename));
581     /* Scan each line and set variables */
582     while ((result == 0) && (fgets(str, MAX_LEN, rc_file) != NULL)) {
583         DLG_TRACE(("#\t%s", str));
584         if (*str == '\0' || lastch(str) != '\n') {
585             /* ignore rest of file if line too long */
586             report_error(filename, l, "line too long");
587             result = -1;        /* parse aborted */
588             break;
589         }
590
591         lastch(str) = '\0';
592         if (begins_with(str, "bindkey", &params)) {
593             if (!dlg_parse_bindkey(params)) {
594                 report_error(filename, l, "invalid bindkey");
595                 result = -1;
596             }
597             continue;
598         }
599         parse = parse_line(str, &var, &value);  /* parse current line */
600
601         switch (parse) {
602         case LINE_EMPTY:        /* ignore blank lines and comments */
603             break;
604         case LINE_EQUALS:
605             /* search table for matching config variable name */
606             if ((i = find_vars(var)) >= 0) {
607                 switch (vars[i].type) {
608                 case VAL_INT:
609                     *((int *) vars[i].var) = atoi(value);
610                     break;
611                 case VAL_STR:
612                     if (!isquote(value[0]) || !isquote(lastch(value))
613                         || strlen(value) < 2) {
614                         report_error(filename, l, "expected string value");
615                         result = -1;    /* parse aborted */
616                     } else {
617                         /* remove the (") quotes */
618                         value++;
619                         lastch(value) = '\0';
620                         strcpy((char *) vars[i].var, value);
621                     }
622                     break;
623                 case VAL_BOOL:
624                     if (!dlg_strcmp(value, "ON"))
625                         *((bool *) vars[i].var) = TRUE;
626                     else if (!dlg_strcmp(value, "OFF"))
627                         *((bool *) vars[i].var) = FALSE;
628                     else {
629                         report_error(filename, l, "expected boolean value");
630                         result = -1;    /* parse aborted */
631                     }
632                     break;
633                 }
634 #ifdef HAVE_COLOR
635             } else if ((i = find_color(var)) >= 0) {
636                 DIALOG_COLORS temp;
637                 if (str_to_attr(value, &temp) == -1) {
638                     report_error(filename, l, "expected attribute value");
639                     result = -1;        /* parse aborted */
640                 } else {
641                     dlg_color_table[i].fg = temp.fg;
642                     dlg_color_table[i].bg = temp.bg;
643                     dlg_color_table[i].hilite = temp.hilite;
644 #ifdef HAVE_RC_FILE2
645                     dlg_color_table[i].ul = temp.ul;
646                     dlg_color_table[i].rv = temp.rv;
647 #endif /* HAVE_RC_FILE2 */
648                 }
649             } else {
650 #endif /* HAVE_COLOR */
651                 report_error(filename, l, "unknown variable");
652                 result = -1;    /* parse aborted */
653             }
654             break;
655         case LINE_ERROR:
656             report_error(filename, l, "syntax error");
657             result = -1;        /* parse aborted */
658             break;
659         }
660         l++;                    /* next line */
661     }
662
663     (void) fclose(rc_file);
664     return result;
665 }