1/* driver_settings - implements the driver settings API 2** 3** Initial version by Axel Dörfler, axeld@pinc-software.de 4** This file may be used under the terms of the OpenBeOS License. 5*/ 6 7 8#include <OS.h> 9#include <driver_settings.h> 10 11#include <stdlib.h> 12#include <string.h> 13#include <unistd.h> 14#include <fcntl.h> 15#include <ctype.h> 16 17#include "Compatibility.h" 18#include "String.h" 19 20// strlcat 21size_t 22strlcat(char *dst, char const *src, size_t s) 23{ 24 size_t i, j = strnlen(dst, s); 25 26 if (!s) 27 return j + strlen(src); 28 29 dst += j; 30 31 for (i = 0; ((i < s-1) && src[i]); i++) { 32 dst[i] = src[i]; 33 } 34 35 dst[i] = 0; 36 37 return j + i + strlen(src + i); 38} 39 40#define SETTINGS_DIRECTORY "/boot/home/config/settings/kernel/drivers/" 41#define SETTINGS_MAGIC 'DrvS' 42 43// Those maximum values are independent from the implementation - they 44// have been chosen to make the code more robust against bad files 45#define MAX_SETTINGS_SIZE 32768 46#define MAX_SETTINGS_LEVEL 8 47 48#define CONTINUE_PARAMETER 1 49#define NO_PARAMETER 2 50 51 52typedef struct settings_handle { 53 void *first_buffer; 54 int32 magic; 55 struct driver_settings settings; 56 char *text; 57} settings_handle; 58 59 60enum assignment_mode { 61 NO_ASSIGNMENT, 62 ALLOW_ASSIGNMENT, 63 IGNORE_ASSIGNMENT 64}; 65 66// Functions not part of the public API 67 68 69/** Returns true for any characters that separate parameters - 70 * those are ignored in the input stream and won't be added 71 * to any words. 72 */ 73 74static inline bool 75is_parameter_separator(char c) 76{ 77 return c == '\n' || c == ';'; 78} 79 80 81/** Indicates if "c" begins a new word or not. 82 */ 83 84static inline bool 85is_word_break(char c) 86{ 87 return isspace(c) || is_parameter_separator(c); 88} 89 90 91static inline bool 92check_handle(settings_handle *handle) 93{ 94 if (handle == NULL 95 || handle->magic != SETTINGS_MAGIC) 96 return false; 97 98 return true; 99} 100 101 102static driver_parameter * 103get_parameter(settings_handle *handle, const char *name) 104{ 105 int32 i; 106 for (i = handle->settings.parameter_count; i-- > 0;) { 107 if (!strcmp(handle->settings.parameters[i].name, name)) 108 return &handle->settings.parameters[i]; 109 } 110 return NULL; 111} 112 113 114/** Returns the next word in the input buffer passed in via "_pos" - if 115 * this function returns, it will bump the input position after the word. 116 * It automatically cares about quoted strings and escaped characters. 117 * If "allowNewLine" is true, it reads over comments to get to the next 118 * word. 119 * Depending on the "assignmentMode" parameter, the '=' sign is either 120 * used as a work break, or not. 121 * The input buffer will be changed to contain the word without quotes 122 * or escaped characters and adds a terminating NULL byte. The "_word" 123 * parameter will be set to the beginning of the word. 124 * If the word is followed by a newline it will return B_OK, if white 125 * spaces follows, it will return CONTINUE_PARAMETER. 126 */ 127 128static status_t 129get_word(char **_pos, char **_word, int32 assignmentMode, bool allowNewLine) 130{ 131 char *pos = *_pos; 132 char quoted = 0; 133 bool newLine = false, end = false; 134 int escaped = 0; 135 bool charEscaped = false; 136 137 // Skip any white space and comments 138 while (pos[0] 139 && ((allowNewLine && (isspace(pos[0]) || is_parameter_separator(pos[0]) || pos[0] == '#')) 140 || (!allowNewLine && (pos[0] == '\t' || pos[0] == ' ')) 141 || (assignmentMode == ALLOW_ASSIGNMENT && pos[0] == '='))) { 142 // skip any comment lines 143 if (pos[0] == '#') { 144 while (pos[0] && pos[0] != '\n') 145 pos++; 146 } 147 pos++; 148 } 149 150 if (pos[0] == '}' || pos[0] == '\0') { 151 // if we just read some white space before an end of a 152 // parameter, this is just no parameter at all 153 *_pos = pos; 154 return NO_PARAMETER; 155 } 156 157 // Read in a word - might contain escaped (\) spaces, or it 158 // might also be quoted (" or '). 159 160 if (pos[0] == '"' || pos[0] == '\'') { 161 quoted = pos[0]; 162 pos++; 163 } 164 *_word = pos; 165 166 while (pos[0]) { 167 if (charEscaped) 168 charEscaped = false; 169 else if (pos[0] == '\\') { 170 charEscaped = true; 171 escaped++; 172 } else if ((!quoted && (is_word_break(pos[0]) 173 || (assignmentMode != IGNORE_ASSIGNMENT && pos[0] == '='))) 174 || (quoted && pos[0] == quoted)) 175 break; 176 177 pos++; 178 } 179 180 // "String exceeds line" - missing end quote 181 if (quoted && pos[0] != quoted) 182 return B_BAD_DATA; 183 184 // last character is a backslash 185 if (charEscaped) 186 return B_BAD_DATA; 187 188 end = pos[0] == '\0'; 189 newLine = is_parameter_separator(pos[0]) || end; 190 pos[0] = '\0'; 191 192 // Correct name if there were any escaped characters 193 if (escaped) { 194 char *word = *_word; 195 int offset = 0; 196 while (word <= pos) { 197 if (word[0] == '\\') { 198 offset--; 199 word++; 200 } 201 word[offset] = word[0]; 202 word++; 203 } 204 } 205 206 if (end) { 207 *_pos = pos; 208 return B_OK; 209 } 210 211 // Scan for next beginning word, open brackets, or comment start 212 213 pos++; 214 while (true) { 215 *_pos = pos; 216 if (!pos[0]) 217 return B_NO_ERROR; 218 219 if (is_parameter_separator(pos[0])) { 220 // an open bracket '{' could follow after the first 221 // newline, but not later 222 if (newLine) 223 return B_NO_ERROR; 224 225 newLine = true; 226 } else if (pos[0] == '{' || pos[0] == '}' || pos[0] == '#') 227 return B_NO_ERROR; 228 else if (!isspace(pos[0])) 229 return newLine ? B_NO_ERROR : CONTINUE_PARAMETER; 230 231 pos++; 232 } 233} 234 235 236static status_t 237parse_parameter(struct driver_parameter *parameter, char **_pos, int32 level) 238{ 239 char *pos = *_pos; 240 status_t status; 241 242 // initialize parameter first 243 memset(parameter, 0, sizeof(struct driver_parameter)); 244 245 status = get_word(&pos, ¶meter->name, NO_ASSIGNMENT, true); 246 if (status == CONTINUE_PARAMETER) { 247 while (status == CONTINUE_PARAMETER) { 248 char **newArray, *value; 249 status = get_word(&pos, &value, parameter->value_count == 0 ? ALLOW_ASSIGNMENT : IGNORE_ASSIGNMENT, false); 250 if (status < B_OK) 251 break; 252 253 // enlarge value array and save the value 254 255 newArray = realloc(parameter->values, (parameter->value_count + 1) * sizeof(char *)); 256 if (newArray == NULL) 257 return B_NO_MEMORY; 258 259 parameter->values = newArray; 260 parameter->values[parameter->value_count++] = value; 261 } 262 } 263 264 *_pos = pos; 265 return status; 266} 267 268 269static status_t 270parse_parameters(struct driver_parameter **_parameters, int *_count, char **_pos, int32 level) 271{ 272 if (level > MAX_SETTINGS_LEVEL) 273 return B_LINK_LIMIT; 274 275 while (true) { 276 struct driver_parameter parameter; 277 struct driver_parameter *newArray; 278 status_t status; 279 280 status = parse_parameter(¶meter, _pos, level); 281 if (status < B_OK) 282 return status; 283 284 if (status != NO_PARAMETER) { 285 driver_parameter *newParameter; 286 287 newArray = realloc(*_parameters, (*_count + 1) * sizeof(struct driver_parameter)); 288 if (newArray == NULL) 289 return B_NO_MEMORY; 290 291 memcpy(&newArray[*_count], ¶meter, sizeof(struct driver_parameter)); 292 newParameter = &newArray[*_count]; 293 294 *_parameters = newArray; 295 (*_count)++; 296 297 // check for level beginning and end 298 if (**_pos == '{') { 299 // if we go a level deeper, just start all over again... 300 (*_pos)++; 301 status = parse_parameters(&newParameter->parameters, 302 &newParameter->parameter_count, _pos, level + 1); 303 if (status < B_OK) 304 return status; 305 } 306 } 307 308 if ((**_pos == '}' && level > 0) 309 || (**_pos == '\0' && level == 0)) { 310 // take the closing bracket from the stack 311 (*_pos)++; 312 return B_OK; 313 } 314 315 // obviously, something has gone wrong 316 if (**_pos == '}' || **_pos == '\0') 317 return B_ERROR; 318 } 319} 320 321 322static status_t 323parse_settings(settings_handle *handle) 324{ 325 char *text = handle->text; 326 327 memset(&handle->settings, 0, sizeof(struct driver_settings)); 328 329 // empty settings are allowed 330 if (text == NULL) 331 return B_OK; 332 333 return parse_parameters(&handle->settings.parameters, &handle->settings.parameter_count, &text, 0); 334} 335 336 337static void 338free_parameter(struct driver_parameter *parameter) 339{ 340 int32 i; 341 for (i = parameter->parameter_count; i-- > 0;) 342 free_parameter(¶meter->parameters[i]); 343 344 free(parameter->parameters); 345 free(parameter->values); 346} 347 348 349static void 350free_settings(settings_handle *handle) 351{ 352 int32 i; 353 for (i = handle->settings.parameter_count; i-- > 0;) 354 free_parameter(&handle->settings.parameters[i]); 355 356 free(handle->settings.parameters); 357 358 free(handle->text); 359 free(handle); 360} 361 362 363static settings_handle * 364load_driver_settings_from_file(int file) 365{ 366 struct stat stat; 367 368 // Allocate a buffer and read the whole file into it. 369 // We will keep this buffer in memory, until the settings 370 // are unloaded. 371 // The driver_parameter::name field will point directly 372 // to this buffer. 373 374 if (fstat(file, &stat) < B_OK) 375 return NULL; 376 377 if (stat.st_size > B_OK && stat.st_size < MAX_SETTINGS_SIZE) { 378 char *text = (char *)malloc(stat.st_size + 1); 379 if (text != NULL && read(file, text, stat.st_size) == stat.st_size) { 380 settings_handle *handle = malloc(sizeof(settings_handle)); 381 if (handle != NULL) { 382 text[stat.st_size] = '\0'; 383 384 handle->magic = SETTINGS_MAGIC; 385 handle->text = text; 386 387 if (parse_settings(handle) == B_OK) { 388 return handle; 389 } 390 391 free(handle); 392 } 393 } 394 // "text" might be NULL here, but that's allowed 395 free(text); 396 } 397 398 return NULL; 399} 400 401 402static bool 403put_string(char **_buffer, size_t *_bufferSize, char *string) 404{ 405 size_t length, reserved, quotes; 406 char *buffer = *_buffer, c; 407 bool quoted; 408 409 if (string == NULL) 410 return true; 411 412 for (length = reserved = quotes = 0; (c = string[length]) != '\0'; length++) { 413 if (c == '"') 414 quotes++; 415 else if (is_word_break(c)) 416 reserved++; 417 } 418 quoted = reserved || quotes; 419 420 // update _bufferSize in any way, so that we can chain several 421 // of these calls without having to check the return value 422 // everytime 423 *_bufferSize -= length + (quoted ? 2 + quotes : 0); 424 425 if (*_bufferSize <= 0) 426 return false; 427 428 if (quoted) 429 *(buffer++) = '"'; 430 431 for (;(c = string[0]) != '\0'; string++) { 432 if (c == '"') 433 *(buffer++) = '\\'; 434 435 *(buffer++) = c; 436 } 437 438 if (quoted) 439 *(buffer++) = '"'; 440 441 buffer[0] = '\0'; 442 443 // update the buffer position 444 *_buffer = buffer; 445 446 return true; 447} 448 449 450static bool 451put_chars(char **_buffer, size_t *_bufferSize, char *chars) 452{ 453 char *buffer = *_buffer; 454 size_t length; 455 456 if (chars == NULL) 457 return true; 458 459 length = strlen(chars); 460 *_bufferSize -= length; 461 462 if (*_bufferSize <= 0) 463 return false; 464 465 memcpy(buffer, chars, length); 466 buffer += length; 467 buffer[0] = '\0'; 468 469 // update the buffer position 470 *_buffer = buffer; 471 472 return true; 473} 474 475 476static bool 477put_char(char **_buffer, size_t *_bufferSize, char c) 478{ 479 char *buffer = *_buffer; 480 481 *_bufferSize -= 1; 482 483 if (*_bufferSize <= 0) 484 return false; 485 486 buffer[0] = c; 487 buffer[1] = '\0'; 488 489 // update the buffer position 490 *_buffer = buffer + 1; 491 492 return true; 493} 494 495 496static void 497put_level_space(char **_buffer, size_t *_bufferSize, int32 level) 498{ 499 while (level-- > 0) 500 put_char(_buffer, _bufferSize, '\t'); 501} 502 503 504static bool 505put_parameter(char **_buffer, size_t *_bufferSize, struct driver_parameter *parameter, int32 level, bool flat) 506{ 507 int32 i; 508 509 if (!flat) 510 put_level_space(_buffer, _bufferSize, level); 511 512 put_string(_buffer, _bufferSize, parameter->name); 513 if (flat && parameter->value_count > 0) 514 put_chars(_buffer, _bufferSize, " ="); 515 516 for (i = 0; i < parameter->value_count; i++) { 517 put_char(_buffer, _bufferSize, ' '); 518 put_string(_buffer, _bufferSize, parameter->values[i]); 519 } 520 521 if (parameter->parameter_count > 0) { 522 put_chars(_buffer, _bufferSize, " {"); 523 if (!flat) 524 put_char(_buffer, _bufferSize, '\n'); 525 526 for (i = 0; i < parameter->parameter_count; i++) { 527 put_parameter(_buffer, _bufferSize, ¶meter->parameters[i], level + 1, flat); 528 529 if (parameter->parameters[i].parameter_count == 0) 530 put_chars(_buffer, _bufferSize, flat ? "; " : "\n"); 531 } 532 533 if (!flat) 534 put_level_space(_buffer, _bufferSize, level); 535 put_chars(_buffer, _bufferSize, flat ? "}" : "}\n"); 536 } 537 538 return *_bufferSize >= 0; 539} 540 541 542// ToDo: the API to add an item to the driver_settings is obviously accessable 543// to the kernel, so we should provide it, too (in BeOS this is used to add 544// driver settings at boot time, using the safe boot menu). 545 546//static status_t 547//add_driver_parameter(const char *name, ) 548//{ 549//} 550 551 552// #pragma mark - 553// The public API implementation 554 555 556status_t 557unload_driver_settings(void *handle) 558{ 559 if (!check_handle(handle)) 560 return B_BAD_VALUE; 561 562 free_settings(handle); 563 return B_OK; 564} 565 566 567void * 568load_driver_settings(const char *driverName) 569{ 570 settings_handle *handle; 571 int file; 572 573 if (driverName == NULL) 574 return NULL; 575 576 // open the settings from the standardized location 577 { 578 char path[B_FILE_NAME_LENGTH + 64]; 579 580 // ToDo: use the kernel's find_directory for this 581 strcpy(path, SETTINGS_DIRECTORY); 582 strlcat(path, driverName, sizeof(path)); 583 584 file = open(path, O_RDONLY); 585 } 586 if (file < B_OK) 587 return NULL; 588 589 handle = load_driver_settings_from_file(file); 590 591 close(file); 592 return (void *)handle; 593} 594 595 596/** Loads a driver settings file using the full path, instead of 597 * only defining the leaf name (as load_driver_settings() does). 598 * I am not sure if this function is really necessary - I would 599 * probably prefer something like a search order (if it's not 600 * an absolute path): 601 * ~/config/settings/kernel/driver 602 * current directory 603 * That would render this function useless. 604 */ 605 606#if 0 607void * 608load_driver_settings_from_path(const char *path) 609{ 610 settings_handle *handle; 611 int file; 612 613 if (path == NULL) 614 return NULL; 615 616 file = open(path, O_RDONLY); 617 if (file < B_OK) 618 return NULL; 619 620 handle = load_driver_settings_from_file(file); 621 622 close(file); 623 return (void *)handle; 624} 625#endif 626 627/** Returns a new driver_settings handle that has the parsed contents 628 * of the passed string. 629 * You can get an empty driver_settings object when you pass NULL as 630 * the "settingsString" parameter. 631 */ 632 633void * 634parse_driver_settings_string(const char *settingsString) 635{ 636 // we simply copy the whole string to use it as our internal buffer 637 char *text = strdup(settingsString); 638 if (settingsString == NULL || text != NULL) { 639 settings_handle *handle = malloc(sizeof(settings_handle)); 640 if (handle != NULL) { 641 handle->magic = SETTINGS_MAGIC; 642 handle->text = text; 643 644 if (parse_settings(handle) == B_OK) 645 return handle; 646 647 free(handle); 648 } 649 free(text); 650 } 651 652 return NULL; 653} 654 655 656/** This function prints out a driver settings structure to a human 657 * readable string. 658 * It's either in standard style or the single line style speficied 659 * by the "flat" parameter. 660 * If the buffer is too small to hold the string, B_BUFFER_OVERFLOW 661 * is returned, and the needed amount of bytes if placed in the 662 * "_bufferSize" parameter. 663 * If the "handle" parameter is not a valid driver settings handle, or 664 * the "buffer" parameter is NULL, B_BAD_VALUE is returned. 665 */ 666 667status_t 668get_driver_settings_string(void *_handle, char *buffer, size_t *_bufferSize, bool flat) 669{ 670 settings_handle *handle = (settings_handle *)_handle; 671 size_t bufferSize = *_bufferSize; 672 int32 i; 673 674 if (!check_handle(handle) || !buffer || *_bufferSize == 0) 675 return B_BAD_VALUE; 676 677 for (i = 0; i < handle->settings.parameter_count; i++) { 678 put_parameter(&buffer, &bufferSize, &handle->settings.parameters[i], 0, flat); 679 } 680 681 *_bufferSize -= bufferSize; 682 return bufferSize >= 0 ? B_OK : B_BUFFER_OVERFLOW; 683} 684 685 686/** Matches the first value of the parameter matching "keyName" with a set 687 * of boolean values like 1/true/yes/on/enabled/... 688 * Returns "unknownValue" if the parameter could not be found or doesn't 689 * have any valid boolean setting, and "noArgValue" if the parameter 690 * doesn't have any values. 691 * Also returns "unknownValue" if the handle passed in was not valid. 692 */ 693 694bool 695get_driver_boolean_parameter(void *handle, const char *keyName, bool unknownValue, bool noArgValue) 696{ 697 driver_parameter *parameter; 698 char *boolean; 699 700 if (!check_handle(handle)) 701 return unknownValue; 702 703 // check for the parameter 704 if ((parameter = get_parameter(handle, keyName)) == NULL) 705 return unknownValue; 706 707 // check for the argument 708 if (parameter->value_count <= 0) 709 return noArgValue; 710 711 boolean = parameter->values[0]; 712 if (!strcmp(boolean, "1") 713 || !strcasecmp(boolean, "true") 714 || !strcasecmp(boolean, "yes") 715 || !strcasecmp(boolean, "on") 716 || !strcasecmp(boolean, "enable") 717 || !strcasecmp(boolean, "enabled")) 718 return true; 719 720 if (!strcmp(boolean, "0") 721 || !strcasecmp(boolean, "false") 722 || !strcasecmp(boolean, "no") 723 || !strcasecmp(boolean, "off") 724 || !strcasecmp(boolean, "disable") 725 || !strcasecmp(boolean, "disabled")) 726 return false; 727 728 // if no known keyword is found, "unknownValue" is returned 729 return unknownValue; 730} 731 732 733const char * 734get_driver_parameter(void *handle, const char *keyName, const char *unknownValue, const char *noArgValue) 735{ 736 struct driver_parameter *parameter; 737 738 if (!check_handle(handle)) 739 return unknownValue; 740 741 // check for the parameter 742 if ((parameter = get_parameter(handle, keyName)) == NULL) 743 return unknownValue; 744 745 // check for the argument 746 if (parameter->value_count <= 0) 747 return noArgValue; 748 749 return parameter->values[0]; 750} 751 752 753const driver_settings * 754get_driver_settings(void *handle) 755{ 756 if (!check_handle(handle)) 757 return NULL; 758 759 return &((settings_handle *)handle)->settings; 760} 761 762 763// this creates an alias of the above function 764// unload_driver_settings() is the same as delete_driver_settings() 765#ifndef __MWERKS__ 766extern __typeof(unload_driver_settings) delete_driver_settings __attribute__ ((alias ("unload_driver_settings"))); 767#endif 768 769