xref: /dragonfly/contrib/dialog/rc.c (revision ec1c3f3a)
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
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
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
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
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
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
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
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 *
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 *
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
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
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
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
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
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
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", &params)) {
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