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