1 /* 2 * Copyright 2002-2007, Axel Dörfler, axeld@pinc-software.de. 3 * This file may be used under the terms of the MIT License. 4 */ 5 6 /*! \brief Implements the driver settings API 7 This file is used by three different components with different needs: 8 1) the boot loader 9 Buffers a list of settings files to move over to the kernel - the 10 actual buffering is located in the boot loader directly, though. 11 Creates driver_settings structures out of those on demand only. 12 2) the kernel 13 Maintains a list of settings so that no disk access is required 14 for known settings (such as those passed over from the boot 15 loader). 16 3) libroot.so 17 Exports the parser to userland applications, so that they can 18 easily make use of driver_settings styled files. 19 20 The file has to be recompiled for every component separately, so that 21 it properly exports the required functionality (which is specified by 22 _BOOT_MODE for the boot loader, and _KERNEL_MODE for the kernel). 23 */ 24 25 #include "fssh_driver_settings.h" 26 27 #include <ctype.h> 28 #include <stdlib.h> 29 30 #include "fssh_fcntl.h" 31 #include "fssh_lock.h" 32 #include "fssh_os.h" 33 #include "fssh_stat.h" 34 #include "fssh_string.h" 35 #include "fssh_unistd.h" 36 37 #include "list.h" 38 39 40 using namespace FSShell; 41 42 #define SETTINGS_DIRECTORY "/kernel/drivers/" 43 #define SETTINGS_MAGIC 'DrvS' 44 45 // Those maximum values are independent from the implementation - they 46 // have been chosen to make the code more robust against bad files 47 #define MAX_SETTINGS_SIZE 32768 48 #define MAX_SETTINGS_LEVEL 8 49 50 #define CONTINUE_PARAMETER 1 51 #define NO_PARAMETER 2 52 53 54 typedef struct settings_handle { 55 list_link link; 56 char name[FSSH_B_OS_NAME_LENGTH]; 57 int32_t ref_count; 58 int32_t magic; 59 struct fssh_driver_settings settings; 60 char *text; 61 } settings_handle; 62 63 64 enum assignment_mode { 65 NO_ASSIGNMENT, 66 ALLOW_ASSIGNMENT, 67 IGNORE_ASSIGNMENT 68 }; 69 70 71 static struct list sHandles; 72 static fssh_mutex sLock; 73 74 75 // #pragma mark - private functions 76 77 78 /*! 79 Returns true for any characters that separate parameters - 80 those are ignored in the input stream and won't be added 81 to any words. 82 */ 83 static inline bool 84 is_parameter_separator(char c) 85 { 86 return c == '\n' || c == ';'; 87 } 88 89 90 /** Indicates if "c" begins a new word or not. 91 */ 92 93 static inline bool 94 is_word_break(char c) 95 { 96 return isspace(c) || is_parameter_separator(c); 97 } 98 99 100 static inline bool 101 check_handle(void *_handle) 102 { 103 settings_handle *handle = (settings_handle *)_handle; 104 if (handle == NULL 105 || handle->magic != SETTINGS_MAGIC) 106 return false; 107 108 return true; 109 } 110 111 112 static fssh_driver_parameter * 113 get_parameter(settings_handle *handle, const char *name) 114 { 115 int32_t i; 116 for (i = handle->settings.parameter_count; i-- > 0;) { 117 if (!fssh_strcmp(handle->settings.parameters[i].name, name)) 118 return &handle->settings.parameters[i]; 119 } 120 return NULL; 121 } 122 123 124 /*! 125 Returns the next word in the input buffer passed in via "_pos" - if 126 this function returns, it will bump the input position after the word. 127 It automatically cares about quoted strings and escaped characters. 128 If "allowNewLine" is true, it reads over comments to get to the next 129 word. 130 Depending on the "assignmentMode" parameter, the '=' sign is either 131 used as a work break, or not. 132 The input buffer will be changed to contain the word without quotes 133 or escaped characters and adds a terminating NULL byte. The "_word" 134 parameter will be set to the beginning of the word. 135 If the word is followed by a newline it will return FSSH_B_OK, if white 136 spaces follows, it will return CONTINUE_PARAMETER. 137 */ 138 static fssh_status_t 139 get_word(char **_pos, char **_word, int32_t assignmentMode, bool allowNewLine) 140 { 141 char *pos = *_pos; 142 char quoted = 0; 143 bool newLine = false, end = false; 144 int escaped = 0; 145 bool charEscaped = false; 146 147 // Skip any white space and comments 148 while (pos[0] 149 && ((allowNewLine && (isspace(pos[0]) || is_parameter_separator(pos[0]) 150 || pos[0] == '#')) 151 || (!allowNewLine && (pos[0] == '\t' || pos[0] == ' ')) 152 || (assignmentMode == ALLOW_ASSIGNMENT && pos[0] == '='))) { 153 // skip any comment lines 154 if (pos[0] == '#') { 155 while (pos[0] && pos[0] != '\n') 156 pos++; 157 } 158 pos++; 159 } 160 161 if (pos[0] == '}' || pos[0] == '\0') { 162 // if we just read some white space before an end of a 163 // parameter, this is just no parameter at all 164 *_pos = pos; 165 return NO_PARAMETER; 166 } 167 168 // Read in a word - might contain escaped (\) spaces, or it 169 // might also be quoted (" or '). 170 171 if (pos[0] == '"' || pos[0] == '\'') { 172 quoted = pos[0]; 173 pos++; 174 } 175 *_word = pos; 176 177 while (pos[0]) { 178 if (charEscaped) 179 charEscaped = false; 180 else if (pos[0] == '\\') { 181 charEscaped = true; 182 escaped++; 183 } else if ((!quoted && (is_word_break(pos[0]) 184 || (assignmentMode != IGNORE_ASSIGNMENT && pos[0] == '='))) 185 || (quoted && pos[0] == quoted)) 186 break; 187 188 pos++; 189 } 190 191 // "String exceeds line" - missing end quote 192 if (quoted && pos[0] != quoted) 193 return FSSH_B_BAD_DATA; 194 195 // last character is a backslash 196 if (charEscaped) 197 return FSSH_B_BAD_DATA; 198 199 end = pos[0] == '\0'; 200 newLine = is_parameter_separator(pos[0]) || end; 201 pos[0] = '\0'; 202 203 // Correct name if there were any escaped characters 204 if (escaped) { 205 char *word = *_word; 206 int offset = 0; 207 while (word <= pos) { 208 if (word[0] == '\\') { 209 offset--; 210 word++; 211 } 212 word[offset] = word[0]; 213 word++; 214 } 215 } 216 217 if (end) { 218 *_pos = pos; 219 return FSSH_B_OK; 220 } 221 222 // Scan for next beginning word, open brackets, or comment start 223 224 pos++; 225 while (true) { 226 *_pos = pos; 227 if (!pos[0]) 228 return FSSH_B_NO_ERROR; 229 230 if (is_parameter_separator(pos[0])) { 231 // an open bracket '{' could follow after the first 232 // newline, but not later 233 if (newLine) 234 return FSSH_B_NO_ERROR; 235 236 newLine = true; 237 } else if (pos[0] == '{' || pos[0] == '}' || pos[0] == '#') 238 return FSSH_B_NO_ERROR; 239 else if (!isspace(pos[0])) 240 return newLine ? FSSH_B_NO_ERROR : CONTINUE_PARAMETER; 241 242 pos++; 243 } 244 } 245 246 247 static fssh_status_t 248 parse_parameter(struct fssh_driver_parameter *parameter, char **_pos, int32_t level) 249 { 250 char *pos = *_pos; 251 fssh_status_t status; 252 253 // initialize parameter first 254 fssh_memset(parameter, 0, sizeof(struct fssh_driver_parameter)); 255 256 status = get_word(&pos, ¶meter->name, NO_ASSIGNMENT, true); 257 if (status == CONTINUE_PARAMETER) { 258 while (status == CONTINUE_PARAMETER) { 259 char **newArray, *value; 260 status = get_word(&pos, &value, parameter->value_count == 0 261 ? ALLOW_ASSIGNMENT : IGNORE_ASSIGNMENT, false); 262 if (status < FSSH_B_OK) 263 break; 264 265 // enlarge value array and save the value 266 267 newArray = (char**)realloc(parameter->values, 268 (parameter->value_count + 1) * sizeof(char *)); 269 if (newArray == NULL) 270 return FSSH_B_NO_MEMORY; 271 272 parameter->values = newArray; 273 parameter->values[parameter->value_count++] = value; 274 } 275 } 276 277 *_pos = pos; 278 return status; 279 } 280 281 282 static fssh_status_t 283 parse_parameters(struct fssh_driver_parameter **_parameters, int *_count, 284 char **_pos, int32_t level) 285 { 286 if (level > MAX_SETTINGS_LEVEL) 287 return FSSH_B_LINK_LIMIT; 288 289 while (true) { 290 struct fssh_driver_parameter parameter; 291 struct fssh_driver_parameter *newArray; 292 fssh_status_t status; 293 294 status = parse_parameter(¶meter, _pos, level); 295 if (status < FSSH_B_OK) 296 return status; 297 298 if (status != NO_PARAMETER) { 299 fssh_driver_parameter *newParameter; 300 301 newArray = (fssh_driver_parameter*)realloc(*_parameters, (*_count + 1) 302 * sizeof(struct fssh_driver_parameter)); 303 if (newArray == NULL) 304 return FSSH_B_NO_MEMORY; 305 306 fssh_memcpy(&newArray[*_count], ¶meter, sizeof(struct fssh_driver_parameter)); 307 newParameter = &newArray[*_count]; 308 309 *_parameters = newArray; 310 (*_count)++; 311 312 // check for level beginning and end 313 if (**_pos == '{') { 314 // if we go a level deeper, just start all over again... 315 (*_pos)++; 316 status = parse_parameters(&newParameter->parameters, 317 &newParameter->parameter_count, _pos, level + 1); 318 if (status < FSSH_B_OK) 319 return status; 320 } 321 } 322 323 if ((**_pos == '}' && level > 0) 324 || (**_pos == '\0' && level == 0)) { 325 // take the closing bracket from the stack 326 (*_pos)++; 327 return FSSH_B_OK; 328 } 329 330 // obviously, something has gone wrong 331 if (**_pos == '}' || **_pos == '\0') 332 return FSSH_B_ERROR; 333 } 334 } 335 336 337 static fssh_status_t 338 parse_settings(settings_handle *handle) 339 { 340 char *text = handle->text; 341 342 fssh_memset(&handle->settings, 0, sizeof(struct fssh_driver_settings)); 343 344 // empty settings are allowed 345 if (text == NULL) 346 return FSSH_B_OK; 347 348 return parse_parameters(&handle->settings.parameters, 349 &handle->settings.parameter_count, &text, 0); 350 } 351 352 353 static void 354 free_parameter(struct fssh_driver_parameter *parameter) 355 { 356 int32_t i; 357 for (i = parameter->parameter_count; i-- > 0;) 358 free_parameter(¶meter->parameters[i]); 359 360 free(parameter->parameters); 361 free(parameter->values); 362 } 363 364 365 static void 366 free_settings(settings_handle *handle) 367 { 368 int32_t i; 369 for (i = handle->settings.parameter_count; i-- > 0;) 370 free_parameter(&handle->settings.parameters[i]); 371 372 free(handle->settings.parameters); 373 free(handle->text); 374 free(handle); 375 } 376 377 378 static settings_handle * 379 new_settings(char *buffer, const char *driverName) 380 { 381 settings_handle *handle = (settings_handle*)malloc(sizeof(settings_handle)); 382 if (handle == NULL) 383 return NULL; 384 385 handle->magic = SETTINGS_MAGIC; 386 handle->text = buffer; 387 388 fssh_strlcpy(handle->name, driverName, sizeof(handle->name)); 389 390 if (parse_settings(handle) == FSSH_B_OK) 391 return handle; 392 393 free(handle); 394 return NULL; 395 } 396 397 398 static settings_handle * 399 load_driver_settings_from_file(int file, const char *driverName) 400 { 401 struct fssh_stat stat; 402 403 // Allocate a buffer and read the whole file into it. 404 // We will keep this buffer in memory, until the settings 405 // are unloaded. 406 // The fssh_driver_parameter::name field will point directly 407 // to this buffer. 408 409 if (fssh_fstat(file, &stat) < FSSH_B_OK) 410 return NULL; 411 412 if (stat.fssh_st_size > FSSH_B_OK && stat.fssh_st_size < MAX_SETTINGS_SIZE) { 413 char *text = (char *)malloc(stat.fssh_st_size + 1); 414 if (text != NULL && fssh_read(file, text, stat.fssh_st_size) == stat.fssh_st_size) { 415 settings_handle *handle; 416 417 text[stat.fssh_st_size] = '\0'; 418 // make sure the string is null terminated 419 // to avoid misbehaviour 420 421 handle = new_settings(text, driverName); 422 if (handle != NULL) { 423 // everything went fine! 424 return handle; 425 } 426 } 427 // "text" might be NULL here, but that's allowed 428 free(text); 429 } 430 431 return NULL; 432 } 433 434 435 static bool 436 put_string(char **_buffer, fssh_ssize_t *_bufferSize, char *string) 437 { 438 fssh_size_t length, reserved, quotes; 439 char *buffer = *_buffer, c; 440 bool quoted; 441 442 if (string == NULL) 443 return true; 444 445 for (length = reserved = quotes = 0; (c = string[length]) != '\0'; length++) { 446 if (c == '"') 447 quotes++; 448 else if (is_word_break(c)) 449 reserved++; 450 } 451 quoted = reserved || quotes; 452 453 // update _bufferSize in any way, so that we can chain several 454 // of these calls without having to check the return value 455 // everytime 456 *_bufferSize -= length + (quoted ? 2 + quotes : 0); 457 458 if (*_bufferSize <= 0) 459 return false; 460 461 if (quoted) 462 *(buffer++) = '"'; 463 464 for (;(c = string[0]) != '\0'; string++) { 465 if (c == '"') 466 *(buffer++) = '\\'; 467 468 *(buffer++) = c; 469 } 470 471 if (quoted) 472 *(buffer++) = '"'; 473 474 buffer[0] = '\0'; 475 476 // update the buffer position 477 *_buffer = buffer; 478 479 return true; 480 } 481 482 483 static bool 484 put_chars(char **_buffer, fssh_ssize_t *_bufferSize, const char *chars) 485 { 486 char *buffer = *_buffer; 487 fssh_size_t length; 488 489 if (chars == NULL) 490 return true; 491 492 length = fssh_strlen(chars); 493 *_bufferSize -= length; 494 495 if (*_bufferSize <= 0) 496 return false; 497 498 fssh_memcpy(buffer, chars, length); 499 buffer += length; 500 buffer[0] = '\0'; 501 502 // update the buffer position 503 *_buffer = buffer; 504 505 return true; 506 } 507 508 509 static bool 510 put_char(char **_buffer, fssh_ssize_t *_bufferSize, char c) 511 { 512 char *buffer = *_buffer; 513 514 *_bufferSize -= 1; 515 516 if (*_bufferSize <= 0) 517 return false; 518 519 buffer[0] = c; 520 buffer[1] = '\0'; 521 522 // update the buffer position 523 *_buffer = buffer + 1; 524 525 return true; 526 } 527 528 529 static void 530 put_level_space(char **_buffer, fssh_ssize_t *_bufferSize, int32_t level) 531 { 532 while (level-- > 0) 533 put_char(_buffer, _bufferSize, '\t'); 534 } 535 536 537 static void 538 put_parameter(char **_buffer, fssh_ssize_t *_bufferSize, 539 struct fssh_driver_parameter *parameter, int32_t level, bool flat) 540 { 541 int32_t i; 542 543 if (!flat) 544 put_level_space(_buffer, _bufferSize, level); 545 546 put_string(_buffer, _bufferSize, parameter->name); 547 if (flat && parameter->value_count > 0) 548 put_chars(_buffer, _bufferSize, " ="); 549 550 for (i = 0; i < parameter->value_count; i++) { 551 put_char(_buffer, _bufferSize, ' '); 552 put_string(_buffer, _bufferSize, parameter->values[i]); 553 } 554 555 if (parameter->parameter_count > 0) { 556 put_chars(_buffer, _bufferSize, " {"); 557 if (!flat) 558 put_char(_buffer, _bufferSize, '\n'); 559 560 for (i = 0; i < parameter->parameter_count; i++) { 561 put_parameter(_buffer, _bufferSize, ¶meter->parameters[i], 562 level + 1, flat); 563 564 if (parameter->parameters[i].parameter_count == 0) 565 put_chars(_buffer, _bufferSize, flat ? "; " : "\n"); 566 } 567 568 if (!flat) 569 put_level_space(_buffer, _bufferSize, level); 570 put_chars(_buffer, _bufferSize, flat ? "}" : "}\n"); 571 } 572 } 573 574 575 // #pragma mark - Kernel only functions 576 577 578 static settings_handle * 579 find_driver_settings(const char *name) 580 { 581 settings_handle *handle = NULL; 582 583 FSSH_ASSERT_LOCKED_MUTEX(&sLock); 584 585 while ((handle = (settings_handle*)list_get_next_item(&sHandles, handle)) != NULL) { 586 if (!fssh_strcmp(handle->name, name)) 587 return handle; 588 } 589 590 return NULL; 591 } 592 593 594 namespace FSShell { 595 596 fssh_status_t 597 driver_settings_init() 598 { 599 fssh_mutex_init(&sLock, "driver settings"); 600 return FSSH_B_OK; 601 } 602 603 } // namespace FSShell 604 605 606 // #pragma mark - public API 607 608 609 fssh_status_t 610 fssh_unload_driver_settings(void *handle) 611 { 612 if (!check_handle(handle)) 613 return FSSH_B_BAD_VALUE; 614 615 #if 0 616 fssh_mutex_lock(&sLock); 617 // ToDo: as soon as "/boot" is accessible, we should start throwing away settings 618 if (--handle->ref_count == 0) { 619 list_remove_link(&handle->link); 620 } else 621 handle = NULL; 622 fssh_mutex_unlock(&sLock); 623 #endif 624 625 if (handle != NULL) 626 free_settings((settings_handle*)handle); 627 628 return FSSH_B_OK; 629 } 630 631 632 void * 633 fssh_load_driver_settings(const char *driverName) 634 { 635 settings_handle *handle; 636 int file = -1; 637 638 if (driverName == NULL) 639 return NULL; 640 641 // see if we already have these settings loaded 642 fssh_mutex_lock(&sLock); 643 handle = find_driver_settings(driverName); 644 if (handle != NULL) { 645 handle->ref_count++; 646 647 // we got it, now let's see if it already has been parsed 648 if (handle->magic != SETTINGS_MAGIC) { 649 handle->magic = SETTINGS_MAGIC; 650 651 if (parse_settings(handle) != FSSH_B_OK) { 652 // no valid settings, let's cut down its memory requirements 653 free(handle->text); 654 handle->text = NULL; 655 handle = NULL; 656 } 657 } 658 fssh_mutex_unlock(&sLock); 659 return handle; 660 } 661 662 // open the settings from the standardized location 663 if (driverName[0] != '/') { 664 char path[FSSH_B_FILE_NAME_LENGTH + 64]; 665 666 // This location makes at least a bit sense under BeOS compatible 667 // systems. 668 fssh_strcpy(path, "/boot/home/config/settings/fs_shell"); 669 670 { 671 fssh_strlcat(path, SETTINGS_DIRECTORY, sizeof(path)); 672 fssh_strlcat(path, driverName, sizeof(path)); 673 } 674 675 file = fssh_open(path, FSSH_O_RDONLY); 676 } else 677 file = fssh_open(driverName, FSSH_O_RDONLY); 678 679 if (file < FSSH_B_OK) { 680 fssh_mutex_unlock(&sLock); 681 return NULL; 682 } 683 684 handle = load_driver_settings_from_file(file, driverName); 685 686 if (handle != NULL) 687 list_add_item(&sHandles, handle); 688 fssh_mutex_unlock(&sLock); 689 690 fssh_close(file); 691 return (void *)handle; 692 } 693 694 695 /** Loads a driver settings file using the full path, instead of 696 * only defining the leaf name (as load_driver_settings() does). 697 * I am not sure if this function is really necessary - I would 698 * probably prefer something like a search order (if it's not 699 * an absolute path): 700 * ~/config/settings/kernel/driver 701 * current directory 702 * That would render this function useless. 703 */ 704 705 #if 0 706 void * 707 fssh_load_driver_settings_from_path(const char *path) 708 { 709 settings_handle *handle; 710 int file; 711 712 if (path == NULL) 713 return NULL; 714 715 file = fssh_open(path, FSSH_O_RDONLY); 716 if (file < FSSH_B_OK) 717 return NULL; 718 719 handle = load_driver_settings_from_file(file); 720 721 fssh_close(file); 722 return (void *)handle; 723 } 724 #endif 725 726 727 /*! 728 Returns a new driver_settings handle that has the parsed contents 729 of the passed string. 730 You can get an empty driver_settings object when you pass NULL as 731 the "settingsString" parameter. 732 */ 733 void * 734 fssh_parse_driver_settings_string(const char *settingsString) 735 { 736 // we simply copy the whole string to use it as our internal buffer 737 char *text = fssh_strdup(settingsString); 738 if (settingsString == NULL || text != NULL) { 739 settings_handle *handle = (settings_handle*)malloc(sizeof(settings_handle)); 740 if (handle != NULL) { 741 handle->magic = SETTINGS_MAGIC; 742 handle->text = text; 743 744 if (parse_settings(handle) == FSSH_B_OK) 745 return handle; 746 747 free(handle); 748 } 749 free(text); 750 } 751 752 return NULL; 753 } 754 755 756 /*! 757 This function prints out a driver settings structure to a human 758 readable string. 759 It's either in standard style or the single line style speficied 760 by the "flat" parameter. 761 If the buffer is too small to hold the string, FSSH_B_BUFFER_OVERFLOW 762 is returned, and the needed amount of bytes if placed in the 763 "_bufferSize" parameter. 764 If the "handle" parameter is not a valid driver settings handle, or 765 the "buffer" parameter is NULL, FSSH_B_BAD_VALUE is returned. 766 */ 767 fssh_status_t 768 fssh_get_driver_settings_string(void *_handle, char *buffer, 769 fssh_ssize_t *_bufferSize, bool flat) 770 { 771 settings_handle *handle = (settings_handle *)_handle; 772 fssh_ssize_t bufferSize = *_bufferSize; 773 int32_t i; 774 775 if (!check_handle(handle) || !buffer || *_bufferSize == 0) 776 return FSSH_B_BAD_VALUE; 777 778 for (i = 0; i < handle->settings.parameter_count; i++) { 779 put_parameter(&buffer, &bufferSize, &handle->settings.parameters[i], 780 0, flat); 781 } 782 783 *_bufferSize -= bufferSize; 784 return bufferSize >= 0 ? FSSH_B_OK : FSSH_B_BUFFER_OVERFLOW; 785 } 786 787 788 /*! 789 Matches the first value of the parameter matching "keyName" with a set 790 of boolean values like 1/true/yes/on/enabled/... 791 Returns "unknownValue" if the parameter could not be found or doesn't 792 have any valid boolean setting, and "noArgValue" if the parameter 793 doesn't have any values. 794 Also returns "unknownValue" if the handle passed in was not valid. 795 */ 796 bool 797 fssh_get_driver_boolean_parameter(void *handle, const char *keyName, 798 bool unknownValue, bool noArgValue) 799 { 800 fssh_driver_parameter *parameter; 801 char *boolean; 802 803 if (!check_handle(handle)) 804 return unknownValue; 805 806 // check for the parameter 807 if ((parameter = get_parameter((settings_handle*)handle, keyName)) == NULL) 808 return unknownValue; 809 810 // check for the argument 811 if (parameter->value_count <= 0) 812 return noArgValue; 813 814 boolean = parameter->values[0]; 815 if (!fssh_strcmp(boolean, "1") 816 || !fssh_strcasecmp(boolean, "true") 817 || !fssh_strcasecmp(boolean, "yes") 818 || !fssh_strcasecmp(boolean, "on") 819 || !fssh_strcasecmp(boolean, "enable") 820 || !fssh_strcasecmp(boolean, "enabled")) 821 return true; 822 823 if (!fssh_strcmp(boolean, "0") 824 || !fssh_strcasecmp(boolean, "false") 825 || !fssh_strcasecmp(boolean, "no") 826 || !fssh_strcasecmp(boolean, "off") 827 || !fssh_strcasecmp(boolean, "disable") 828 || !fssh_strcasecmp(boolean, "disabled")) 829 return false; 830 831 // if no known keyword is found, "unknownValue" is returned 832 return unknownValue; 833 } 834 835 836 const char * 837 fssh_get_driver_parameter(void *handle, const char *keyName, 838 const char *unknownValue, const char *noArgValue) 839 { 840 struct fssh_driver_parameter *parameter; 841 842 if (!check_handle(handle)) 843 return unknownValue; 844 845 // check for the parameter 846 if ((parameter = get_parameter((settings_handle*)handle, keyName)) == NULL) 847 return unknownValue; 848 849 // check for the argument 850 if (parameter->value_count <= 0) 851 return noArgValue; 852 853 return parameter->values[0]; 854 } 855 856 857 const fssh_driver_settings * 858 fssh_get_driver_settings(void *handle) 859 { 860 if (!check_handle(handle)) 861 return NULL; 862 863 return &((settings_handle *)handle)->settings; 864 } 865