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