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