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
skip_whitespace(char * str,int n)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
skip_keyword(char * str,int n)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
trim_token(char ** tok)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
from_boolean(const char * str)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
from_color_name(const char * str)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
find_vars(char * name)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
find_color(char * name)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 *
to_color_name(int code)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 *
to_boolean(int code)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
str_to_attr(char * str,DIALOG_COLORS * result)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
begins_with(char * line,const char * keyword,char ** params)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
parse_line(char * line,char ** var,char ** value)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
dlg_create_rc(const char * filename)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
report_error(const char * filename,int line_no,const char * msg)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
dlg_parse_rc(void)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", ¶ms)) {
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 }
666