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