xref: /freebsd/contrib/dialog/rc.c (revision f81cdf24ba5436367377f7c8e8f51f6df2a75ca7)
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 }
666