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