/* * $Id: rc.c,v 1.59 2020/03/27 21:10:34 tom Exp $ * * rc.c -- routines for processing the configuration file * * Copyright 2000-2019,2020 Thomas E. Dickey * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License, version 2.1 * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to * Free Software Foundation, Inc. * 51 Franklin St., Fifth Floor * Boston, MA 02110, USA. * * An earlier version of this program lists as authors * Savio Lam (lam836@cs.cuhk.hk) */ #include #include #ifdef HAVE_COLOR #include #include #define L_PAREN '(' #define R_PAREN ')' #define MIN_TOKEN 3 #ifdef HAVE_RC_FILE2 #define MAX_TOKEN 5 #else #define MAX_TOKEN MIN_TOKEN #endif #define UNKNOWN_COLOR -2 /* * For matching color names with color values */ static const color_names_st color_names[] = { #ifdef HAVE_USE_DEFAULT_COLORS {"DEFAULT", -1}, #endif {"BLACK", COLOR_BLACK}, {"RED", COLOR_RED}, {"GREEN", COLOR_GREEN}, {"YELLOW", COLOR_YELLOW}, {"BLUE", COLOR_BLUE}, {"MAGENTA", COLOR_MAGENTA}, {"CYAN", COLOR_CYAN}, {"WHITE", COLOR_WHITE}, }; /* color names */ #define COLOR_COUNT TableSize(color_names) #endif /* HAVE_COLOR */ #define GLOBALRC "/etc/dialogrc" #define DIALOGRC ".dialogrc" /* Types of values */ #define VAL_INT 0 #define VAL_STR 1 #define VAL_BOOL 2 /* Type of line in configuration file */ typedef enum { LINE_ERROR = -1, LINE_EQUALS, LINE_EMPTY } PARSE_LINE; /* number of configuration variables */ #define VAR_COUNT TableSize(vars) /* check if character is string quoting characters */ #define isquote(c) ((c) == '"' || (c) == '\'') /* get last character of string */ #define lastch(str) str[strlen(str)-1] /* * Configuration variables */ typedef struct { const char *name; /* name of configuration variable as in DIALOGRC */ void *var; /* address of actual variable to change */ int type; /* type of value */ const char *comment; /* comment to put in "rc" file */ } vars_st; /* * This table should contain only references to dialog_state, since dialog_vars * is reset specially in dialog.c before each widget. */ static const vars_st vars[] = { {"aspect", &dialog_state.aspect_ratio, VAL_INT, "Set aspect-ration."}, {"separate_widget", &dialog_state.separate_str, VAL_STR, "Set separator (for multiple widgets output)."}, {"tab_len", &dialog_state.tab_len, VAL_INT, "Set tab-length (for textbox tab-conversion)."}, {"visit_items", &dialog_state.visit_items, VAL_BOOL, "Make tab-traversal for checklist, etc., include the list."}, #ifdef HAVE_COLOR {"use_shadow", &dialog_state.use_shadow, VAL_BOOL, "Shadow dialog boxes? This also turns on color."}, {"use_colors", &dialog_state.use_colors, VAL_BOOL, "Turn color support ON or OFF"}, #endif /* HAVE_COLOR */ }; /* vars */ static int skip_whitespace(char *str, int n) { while (isblank(UCH(str[n])) && str[n] != '\0') n++; return n; } static int skip_keyword(char *str, int n) { while (isalnum(UCH(str[n])) && str[n] != '\0') n++; return n; } static void trim_token(char **tok) { char *tmp = *tok + skip_whitespace(*tok, 0); *tok = tmp; while (*tmp != '\0' && !isblank(UCH(*tmp))) tmp++; *tmp = '\0'; } static int from_boolean(const char *str) { int code = -1; if (str != NULL && *str != '\0') { if (!dlg_strcmp(str, "ON")) { code = 1; } else if (!dlg_strcmp(str, "OFF")) { code = 0; } } return code; } static int from_color_name(const char *str) { int code = UNKNOWN_COLOR; if (str != NULL && *str != '\0') { size_t i; for (i = 0; i < COLOR_COUNT; ++i) { if (!dlg_strcmp(str, color_names[i].name)) { code = color_names[i].value; break; } } } return code; } static int find_vars(char *name) { int result = -1; unsigned i; for (i = 0; i < VAR_COUNT; i++) { if (dlg_strcmp(vars[i].name, name) == 0) { result = (int) i; break; } } return result; } #ifdef HAVE_COLOR static int find_color(char *name) { int result = -1; int i; int limit = dlg_color_count(); for (i = 0; i < limit; i++) { if (dlg_strcmp(dlg_color_table[i].name, name) == 0) { result = i; break; } } return result; } static const char * to_color_name(int code) { const char *result = "?"; size_t n; for (n = 0; n < TableSize(color_names); ++n) { if (code == color_names[n].value) { result = color_names[n].name; break; } } return result; } static const char * to_boolean(int code) { return code ? "ON" : "OFF"; } /* * Extract the foreground, background and highlight values from an attribute * represented as a string in one of these forms: * * "(foreground,background,highlight,underline,reverse)" * "(foreground,background,highlight,underline)" * "(foreground,background,highlight)" * "xxxx_color" */ static int str_to_attr(char *str, DIALOG_COLORS * result) { char *tokens[MAX_TOKEN + 1]; char tempstr[MAX_LEN + 1]; size_t have; size_t i = 0; size_t tok_count = 0; memset(result, 0, sizeof(*result)); result->fg = -1; result->bg = -1; result->hilite = -1; if (str[0] != L_PAREN || lastch(str) != R_PAREN) { int ret; if ((ret = find_color(str)) >= 0) { *result = dlg_color_table[ret]; return 0; } /* invalid representation */ return -1; } /* remove the parenthesis */ have = strlen(str); if (have > MAX_LEN) { have = MAX_LEN - 1; } else { have -= 2; } memcpy(tempstr, str + 1, have); tempstr[have] = '\0'; /* parse comma-separated tokens, allow up to * one more than max tokens to detect extras */ while (tok_count < TableSize(tokens)) { tokens[tok_count++] = &tempstr[i]; while (tempstr[i] != '\0' && tempstr[i] != ',') i++; if (tempstr[i] == '\0') break; tempstr[i++] = '\0'; } if (tok_count < MIN_TOKEN || tok_count > MAX_TOKEN) { /* invalid representation */ return -1; } for (i = 0; i < tok_count; ++i) trim_token(&tokens[i]); /* validate */ if (UNKNOWN_COLOR == (result->fg = from_color_name(tokens[0])) || UNKNOWN_COLOR == (result->bg = from_color_name(tokens[1])) || UNKNOWN_COLOR == (result->hilite = from_boolean(tokens[2])) #ifdef HAVE_RC_FILE2 || (tok_count >= 4 && (result->ul = from_boolean(tokens[3])) == -1) || (tok_count >= 5 && (result->rv = from_boolean(tokens[4])) == -1) #endif /* HAVE_RC_FILE2 */ ) { /* invalid representation */ return -1; } return 0; } #endif /* HAVE_COLOR */ /* * Check if the line begins with a special keyword; if so, return true while * pointing params to its parameters. */ static int begins_with(char *line, const char *keyword, char **params) { int i = skip_whitespace(line, 0); int j = skip_keyword(line, i); if ((j - i) == (int) strlen(keyword)) { char save = line[j]; line[j] = 0; if (!dlg_strcmp(keyword, line + i)) { *params = line + skip_whitespace(line, j + 1); return 1; } line[j] = save; } return 0; } /* * Parse a line in the configuration file * * Each line is of the form: "variable = value". On exit, 'var' will contain * the variable name, and 'value' will contain the value string. * * Return values: * * LINE_EMPTY - line is blank or comment * LINE_EQUALS - line contains "variable = value" * LINE_ERROR - syntax error in line */ static PARSE_LINE parse_line(char *line, char **var, char **value) { int i = 0; /* ignore white space at beginning of line */ i = skip_whitespace(line, i); if (line[i] == '\0') /* line is blank */ return LINE_EMPTY; else if (line[i] == '#') /* line is comment */ return LINE_EMPTY; else if (line[i] == '=') /* variable names cannot start with a '=' */ return LINE_ERROR; /* set 'var' to variable name */ *var = line + i++; /* skip to next character */ /* find end of variable name */ while (!isblank(UCH(line[i])) && line[i] != '=' && line[i] != '\0') i++; if (line[i] == '\0') /* syntax error */ return LINE_ERROR; else if (line[i] == '=') line[i++] = '\0'; else { line[i++] = '\0'; /* skip white space before '=' */ i = skip_whitespace(line, i); if (line[i] != '=') /* syntax error */ return LINE_ERROR; else i++; /* skip the '=' */ } /* skip white space after '=' */ i = skip_whitespace(line, i); if (line[i] == '\0') return LINE_ERROR; else *value = line + i; /* set 'value' to value string */ /* trim trailing white space from 'value' */ i = (int) strlen(*value) - 1; while (isblank(UCH((*value)[i])) && i > 0) i--; (*value)[i + 1] = '\0'; return LINE_EQUALS; /* no syntax error in line */ } /* * Create the configuration file */ void dlg_create_rc(const char *filename) { unsigned i; FILE *rc_file; if ((rc_file = fopen(filename, "wt")) == NULL) dlg_exiterr("Error opening file for writing in dlg_create_rc()."); fprintf(rc_file, "#\n\ # Run-time configuration file for dialog\n\ #\n\ # Automatically generated by \"dialog --create-rc \"\n\ #\n\ #\n\ # Types of values:\n\ #\n\ # Number - \n\ # String - \"string\"\n\ # Boolean - \n" #ifdef HAVE_COLOR #ifdef HAVE_RC_FILE2 "\ # Attribute - (foreground,background,highlight?,underline?,reverse?)\n" #else /* HAVE_RC_FILE2 */ "\ # Attribute - (foreground,background,highlight?)\n" #endif /* HAVE_RC_FILE2 */ #endif /* HAVE_COLOR */ ); /* Print an entry for each configuration variable */ for (i = 0; i < VAR_COUNT; i++) { fprintf(rc_file, "\n# %s\n", vars[i].comment); switch (vars[i].type) { case VAL_INT: fprintf(rc_file, "%s = %d\n", vars[i].name, *((int *) vars[i].var)); break; case VAL_STR: fprintf(rc_file, "%s = \"%s\"\n", vars[i].name, (char *) vars[i].var); break; case VAL_BOOL: fprintf(rc_file, "%s = %s\n", vars[i].name, *((bool *) vars[i].var) ? "ON" : "OFF"); break; } } #ifdef HAVE_COLOR for (i = 0; i < (unsigned) dlg_color_count(); ++i) { unsigned j; bool repeat = FALSE; fprintf(rc_file, "\n# %s\n", dlg_color_table[i].comment); for (j = 0; j != i; ++j) { if (dlg_color_table[i].fg == dlg_color_table[j].fg && dlg_color_table[i].bg == dlg_color_table[j].bg && dlg_color_table[i].hilite == dlg_color_table[j].hilite) { fprintf(rc_file, "%s = %s\n", dlg_color_table[i].name, dlg_color_table[j].name); repeat = TRUE; break; } } if (!repeat) { fprintf(rc_file, "%s = %c", dlg_color_table[i].name, L_PAREN); fprintf(rc_file, "%s", to_color_name(dlg_color_table[i].fg)); fprintf(rc_file, ",%s", to_color_name(dlg_color_table[i].bg)); fprintf(rc_file, ",%s", to_boolean(dlg_color_table[i].hilite)); #ifdef HAVE_RC_FILE2 if (dlg_color_table[i].ul || dlg_color_table[i].rv) fprintf(rc_file, ",%s", to_boolean(dlg_color_table[i].ul)); if (dlg_color_table[i].rv) fprintf(rc_file, ",%s", to_boolean(dlg_color_table[i].rv)); #endif /* HAVE_RC_FILE2 */ fprintf(rc_file, "%c\n", R_PAREN); } } #endif /* HAVE_COLOR */ dlg_dump_keys(rc_file); (void) fclose(rc_file); } static void report_error(const char *filename, int line_no, const char *msg) { fprintf(stderr, "%s:%d: %s\n", filename, line_no, msg); dlg_trace_msg("%s:%d: %s\n", filename, line_no, msg); } /* * Parse the configuration file and set up variables */ int dlg_parse_rc(void) { int i; int l = 1; PARSE_LINE parse; char str[MAX_LEN + 1]; char *var; char *value; char *filename; int result = 0; FILE *rc_file = 0; char *params; /* * At startup, dialog determines the settings to use as follows: * * a) if the environment variable $DIALOGRC is set, its value determines * the name of the configuration file. * * b) if the file in (a) can't be found, use the file $HOME/.dialogrc * as the configuration file. * * c) if the file in (b) can't be found, try using the GLOBALRC file. * Usually this will be /etc/dialogrc. * * d) if the file in (c) cannot be found, use the compiled-in defaults. */ /* try step (a) */ if ((filename = getenv("DIALOGRC")) != NULL) rc_file = fopen(filename, "rt"); if (rc_file == NULL) { /* step (a) failed? */ /* try step (b) */ if ((filename = getenv("HOME")) != NULL && strlen(filename) < MAX_LEN - (sizeof(DIALOGRC) + 3)) { if (filename[0] == '\0' || lastch(filename) == '/') sprintf(str, "%s%s", filename, DIALOGRC); else sprintf(str, "%s/%s", filename, DIALOGRC); rc_file = fopen(filename = str, "rt"); } } if (rc_file == NULL) { /* step (b) failed? */ /* try step (c) */ strcpy(str, GLOBALRC); if ((rc_file = fopen(filename = str, "rt")) == NULL) return 0; /* step (c) failed, use default values */ } DLG_TRACE(("# opened rc file \"%s\"\n", filename)); /* Scan each line and set variables */ while ((result == 0) && (fgets(str, MAX_LEN, rc_file) != NULL)) { DLG_TRACE(("#\t%s", str)); if (*str == '\0' || lastch(str) != '\n') { /* ignore rest of file if line too long */ report_error(filename, l, "line too long"); result = -1; /* parse aborted */ break; } lastch(str) = '\0'; if (begins_with(str, "bindkey", ¶ms)) { if (!dlg_parse_bindkey(params)) { report_error(filename, l, "invalid bindkey"); result = -1; } continue; } parse = parse_line(str, &var, &value); /* parse current line */ switch (parse) { case LINE_EMPTY: /* ignore blank lines and comments */ break; case LINE_EQUALS: /* search table for matching config variable name */ if ((i = find_vars(var)) >= 0) { switch (vars[i].type) { case VAL_INT: *((int *) vars[i].var) = atoi(value); break; case VAL_STR: if (!isquote(value[0]) || !isquote(lastch(value)) || strlen(value) < 2) { report_error(filename, l, "expected string value"); result = -1; /* parse aborted */ } else { /* remove the (") quotes */ value++; lastch(value) = '\0'; strcpy((char *) vars[i].var, value); } break; case VAL_BOOL: if (!dlg_strcmp(value, "ON")) *((bool *) vars[i].var) = TRUE; else if (!dlg_strcmp(value, "OFF")) *((bool *) vars[i].var) = FALSE; else { report_error(filename, l, "expected boolean value"); result = -1; /* parse aborted */ } break; } #ifdef HAVE_COLOR } else if ((i = find_color(var)) >= 0) { DIALOG_COLORS temp; if (str_to_attr(value, &temp) == -1) { report_error(filename, l, "expected attribute value"); result = -1; /* parse aborted */ } else { dlg_color_table[i].fg = temp.fg; dlg_color_table[i].bg = temp.bg; dlg_color_table[i].hilite = temp.hilite; #ifdef HAVE_RC_FILE2 dlg_color_table[i].ul = temp.ul; dlg_color_table[i].rv = temp.rv; #endif /* HAVE_RC_FILE2 */ } } else { #endif /* HAVE_COLOR */ report_error(filename, l, "unknown variable"); result = -1; /* parse aborted */ } break; case LINE_ERROR: report_error(filename, l, "syntax error"); result = -1; /* parse aborted */ break; } l++; /* next line */ } (void) fclose(rc_file); return result; }