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 */
37static 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 */
63typedef 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 */
84typedef 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 */
95static 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
130static int
131skip_whitespace(char *str, int n)
132{
133    while (whitespace(str[n]) && str[n] != '\0')
134	n++;
135    return n;
136}
137
138static int
139skip_keyword(char *str, int n)
140{
141    while (isalnum(UCH(str[n])) && str[n] != '\0')
142	n++;
143    return n;
144}
145
146static int
147find_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
162static int
163find_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 */
183static char *
184attr_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 */
211static int
212str_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 */
306static int
307begins_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 */
337static PARSE_LINE
338parse_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 */
395void
396dlg_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 */
475int
476dlg_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