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