HaikuDepot: notify work status from main window
[haiku.git] / src / tools / fs_shell / driver_settings.cpp
blob01547f7560f2d252d906240b94ba4703aaf65ba6
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 */
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.
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).
25 #include "fssh_driver_settings.h"
27 #include <ctype.h>
28 #include <stdlib.h>
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"
37 #include "list.h"
40 using namespace FSShell;
42 #define SETTINGS_DIRECTORY "/kernel/drivers/"
43 #define SETTINGS_MAGIC 'DrvS'
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
50 #define CONTINUE_PARAMETER 1
51 #define NO_PARAMETER 2
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;
64 enum assignment_mode {
65 NO_ASSIGNMENT,
66 ALLOW_ASSIGNMENT,
67 IGNORE_ASSIGNMENT
71 static struct list sHandles;
72 static fssh_mutex sLock;
75 // #pragma mark - private functions
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.
83 static inline bool
84 is_parameter_separator(char c)
86 return c == '\n' || c == ';';
90 /** Indicates if "c" begins a new word or not.
93 static inline bool
94 is_word_break(char c)
96 return isspace(c) || is_parameter_separator(c);
100 static inline bool
101 check_handle(void *_handle)
103 settings_handle *handle = (settings_handle *)_handle;
104 if (handle == NULL
105 || handle->magic != SETTINGS_MAGIC)
106 return false;
108 return true;
112 static fssh_driver_parameter *
113 get_parameter(settings_handle *handle, const char *name)
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];
120 return NULL;
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.
138 static fssh_status_t
139 get_word(char **_pos, char **_word, int32_t assignmentMode, bool allowNewLine)
141 char *pos = *_pos;
142 char quoted = 0;
143 bool newLine = false, end = false;
144 int escaped = 0;
145 bool charEscaped = false;
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++;
158 pos++;
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;
168 // Read in a word - might contain escaped (\) spaces, or it
169 // might also be quoted (" or ').
171 if (pos[0] == '"' || pos[0] == '\'') {
172 quoted = pos[0];
173 pos++;
175 *_word = pos;
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;
188 pos++;
191 // "String exceeds line" - missing end quote
192 if (quoted && pos[0] != quoted)
193 return FSSH_B_BAD_DATA;
195 // last character is a backslash
196 if (charEscaped)
197 return FSSH_B_BAD_DATA;
199 end = pos[0] == '\0';
200 newLine = is_parameter_separator(pos[0]) || end;
201 pos[0] = '\0';
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++;
212 word[offset] = word[0];
213 word++;
217 if (end) {
218 *_pos = pos;
219 return FSSH_B_OK;
222 // Scan for next beginning word, open brackets, or comment start
224 pos++;
225 while (true) {
226 *_pos = pos;
227 if (!pos[0])
228 return FSSH_B_NO_ERROR;
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;
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;
242 pos++;
247 static fssh_status_t
248 parse_parameter(struct fssh_driver_parameter *parameter, char **_pos, int32_t level)
250 char *pos = *_pos;
251 fssh_status_t status;
253 // initialize parameter first
254 fssh_memset(parameter, 0, sizeof(struct fssh_driver_parameter));
256 status = get_word(&pos, &parameter->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;
265 // enlarge value array and save the value
267 newArray = (char**)realloc(parameter->values,
268 (parameter->value_count + 1) * sizeof(char *));
269 if (newArray == NULL)
270 return FSSH_B_NO_MEMORY;
272 parameter->values = newArray;
273 parameter->values[parameter->value_count++] = value;
277 *_pos = pos;
278 return status;
282 static fssh_status_t
283 parse_parameters(struct fssh_driver_parameter **_parameters, int *_count,
284 char **_pos, int32_t level)
286 if (level > MAX_SETTINGS_LEVEL)
287 return FSSH_B_LINK_LIMIT;
289 while (true) {
290 struct fssh_driver_parameter parameter;
291 struct fssh_driver_parameter *newArray;
292 fssh_status_t status;
294 status = parse_parameter(&parameter, _pos, level);
295 if (status < FSSH_B_OK)
296 return status;
298 if (status != NO_PARAMETER) {
299 fssh_driver_parameter *newParameter;
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;
306 fssh_memcpy(&newArray[*_count], &parameter, sizeof(struct fssh_driver_parameter));
307 newParameter = &newArray[*_count];
309 *_parameters = newArray;
310 (*_count)++;
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;
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;
330 // obviously, something has gone wrong
331 if (**_pos == '}' || **_pos == '\0')
332 return FSSH_B_ERROR;
337 static fssh_status_t
338 parse_settings(settings_handle *handle)
340 char *text = handle->text;
342 fssh_memset(&handle->settings, 0, sizeof(struct fssh_driver_settings));
344 // empty settings are allowed
345 if (text == NULL)
346 return FSSH_B_OK;
348 return parse_parameters(&handle->settings.parameters,
349 &handle->settings.parameter_count, &text, 0);
353 static void
354 free_parameter(struct fssh_driver_parameter *parameter)
356 int32_t i;
357 for (i = parameter->parameter_count; i-- > 0;)
358 free_parameter(&parameter->parameters[i]);
360 free(parameter->parameters);
361 free(parameter->values);
365 static void
366 free_settings(settings_handle *handle)
368 int32_t i;
369 for (i = handle->settings.parameter_count; i-- > 0;)
370 free_parameter(&handle->settings.parameters[i]);
372 free(handle->settings.parameters);
373 free(handle->text);
374 free(handle);
378 static settings_handle *
379 new_settings(char *buffer, const char *driverName)
381 settings_handle *handle = (settings_handle*)malloc(sizeof(settings_handle));
382 if (handle == NULL)
383 return NULL;
385 handle->magic = SETTINGS_MAGIC;
386 handle->text = buffer;
388 fssh_strlcpy(handle->name, driverName, sizeof(handle->name));
390 if (parse_settings(handle) == FSSH_B_OK)
391 return handle;
393 free(handle);
394 return NULL;
398 static settings_handle *
399 load_driver_settings_from_file(int file, const char *driverName)
401 struct fssh_stat stat;
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.
409 if (fssh_fstat(file, &stat) < FSSH_B_OK)
410 return NULL;
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;
417 text[stat.fssh_st_size] = '\0';
418 // make sure the string is null terminated
419 // to avoid misbehaviour
421 handle = new_settings(text, driverName);
422 if (handle != NULL) {
423 // everything went fine!
424 return handle;
427 free(handle);
429 // "text" might be NULL here, but that's allowed
430 free(text);
433 return NULL;
437 static bool
438 put_string(char **_buffer, fssh_size_t *_bufferSize, char *string)
440 fssh_size_t length, reserved, quotes;
441 char *buffer = *_buffer, c;
442 bool quoted;
444 if (string == NULL)
445 return true;
447 for (length = reserved = quotes = 0; (c = string[length]) != '\0'; length++) {
448 if (c == '"')
449 quotes++;
450 else if (is_word_break(c))
451 reserved++;
453 quoted = reserved || quotes;
455 // update _bufferSize in any way, so that we can chain several
456 // of these calls without having to check the return value
457 // everytime
458 *_bufferSize -= length + (quoted ? 2 + quotes : 0);
460 if (*_bufferSize <= 0)
461 return false;
463 if (quoted)
464 *(buffer++) = '"';
466 for (;(c = string[0]) != '\0'; string++) {
467 if (c == '"')
468 *(buffer++) = '\\';
470 *(buffer++) = c;
473 if (quoted)
474 *(buffer++) = '"';
476 buffer[0] = '\0';
478 // update the buffer position
479 *_buffer = buffer;
481 return true;
485 static bool
486 put_chars(char **_buffer, fssh_size_t *_bufferSize, const char *chars)
488 char *buffer = *_buffer;
489 fssh_size_t length;
491 if (chars == NULL)
492 return true;
494 length = fssh_strlen(chars);
495 *_bufferSize -= length;
497 if (*_bufferSize <= 0)
498 return false;
500 fssh_memcpy(buffer, chars, length);
501 buffer += length;
502 buffer[0] = '\0';
504 // update the buffer position
505 *_buffer = buffer;
507 return true;
511 static bool
512 put_char(char **_buffer, fssh_size_t *_bufferSize, char c)
514 char *buffer = *_buffer;
516 *_bufferSize -= 1;
518 if (*_bufferSize <= 0)
519 return false;
521 buffer[0] = c;
522 buffer[1] = '\0';
524 // update the buffer position
525 *_buffer = buffer + 1;
527 return true;
531 static void
532 put_level_space(char **_buffer, fssh_size_t *_bufferSize, int32_t level)
534 while (level-- > 0)
535 put_char(_buffer, _bufferSize, '\t');
539 static bool
540 put_parameter(char **_buffer, fssh_size_t *_bufferSize,
541 struct fssh_driver_parameter *parameter, int32_t level, bool flat)
543 int32_t i;
545 if (!flat)
546 put_level_space(_buffer, _bufferSize, level);
548 put_string(_buffer, _bufferSize, parameter->name);
549 if (flat && parameter->value_count > 0)
550 put_chars(_buffer, _bufferSize, " =");
552 for (i = 0; i < parameter->value_count; i++) {
553 put_char(_buffer, _bufferSize, ' ');
554 put_string(_buffer, _bufferSize, parameter->values[i]);
557 if (parameter->parameter_count > 0) {
558 put_chars(_buffer, _bufferSize, " {");
559 if (!flat)
560 put_char(_buffer, _bufferSize, '\n');
562 for (i = 0; i < parameter->parameter_count; i++) {
563 put_parameter(_buffer, _bufferSize, &parameter->parameters[i],
564 level + 1, flat);
566 if (parameter->parameters[i].parameter_count == 0)
567 put_chars(_buffer, _bufferSize, flat ? "; " : "\n");
570 if (!flat)
571 put_level_space(_buffer, _bufferSize, level);
572 put_chars(_buffer, _bufferSize, flat ? "}" : "}\n");
575 return *_bufferSize >= 0;
579 // #pragma mark - Kernel only functions
582 static settings_handle *
583 find_driver_settings(const char *name)
585 settings_handle *handle = NULL;
587 FSSH_ASSERT_LOCKED_MUTEX(&sLock);
589 while ((handle = (settings_handle*)list_get_next_item(&sHandles, handle)) != NULL) {
590 if (!fssh_strcmp(handle->name, name))
591 return handle;
594 return NULL;
598 namespace FSShell {
600 fssh_status_t
601 driver_settings_init()
603 fssh_mutex_init(&sLock, "driver settings");
604 return FSSH_B_OK;
607 } // namespace FSShell
610 // #pragma mark - public API
613 fssh_status_t
614 fssh_unload_driver_settings(void *handle)
616 if (!check_handle(handle))
617 return FSSH_B_BAD_VALUE;
619 #if 0
620 fssh_mutex_lock(&sLock);
621 // ToDo: as soon as "/boot" is accessible, we should start throwing away settings
622 if (--handle->ref_count == 0) {
623 list_remove_link(&handle->link);
624 } else
625 handle = NULL;
626 fssh_mutex_unlock(&sLock);
627 #endif
629 if (handle != NULL)
630 free_settings((settings_handle*)handle);
632 return FSSH_B_OK;
636 void *
637 fssh_load_driver_settings(const char *driverName)
639 settings_handle *handle;
640 int file = -1;
642 if (driverName == NULL)
643 return NULL;
645 // see if we already have these settings loaded
646 fssh_mutex_lock(&sLock);
647 handle = find_driver_settings(driverName);
648 if (handle != NULL) {
649 handle->ref_count++;
651 // we got it, now let's see if it already has been parsed
652 if (handle->magic != SETTINGS_MAGIC) {
653 handle->magic = SETTINGS_MAGIC;
655 if (parse_settings(handle) != FSSH_B_OK) {
656 // no valid settings, let's cut down its memory requirements
657 free(handle->text);
658 handle->text = NULL;
659 handle = NULL;
662 fssh_mutex_unlock(&sLock);
663 return handle;
666 // open the settings from the standardized location
667 if (driverName[0] != '/') {
668 char path[FSSH_B_FILE_NAME_LENGTH + 64];
670 // This location makes at least a bit sense under BeOS compatible
671 // systems.
672 fssh_strcpy(path, "/boot/home/config/settings/fs_shell");
675 fssh_strlcat(path, SETTINGS_DIRECTORY, sizeof(path));
676 fssh_strlcat(path, driverName, sizeof(path));
679 file = fssh_open(path, FSSH_O_RDONLY);
680 } else
681 file = fssh_open(driverName, FSSH_O_RDONLY);
683 if (file < FSSH_B_OK) {
684 fssh_mutex_unlock(&sLock);
685 return NULL;
688 handle = load_driver_settings_from_file(file, driverName);
690 if (handle != NULL)
691 list_add_item(&sHandles, handle);
692 fssh_mutex_unlock(&sLock);
694 fssh_close(file);
695 return (void *)handle;
699 /** Loads a driver settings file using the full path, instead of
700 * only defining the leaf name (as load_driver_settings() does).
701 * I am not sure if this function is really necessary - I would
702 * probably prefer something like a search order (if it's not
703 * an absolute path):
704 * ~/config/settings/kernel/driver
705 * current directory
706 * That would render this function useless.
709 #if 0
710 void *
711 fssh_load_driver_settings_from_path(const char *path)
713 settings_handle *handle;
714 int file;
716 if (path == NULL)
717 return NULL;
719 file = fssh_open(path, FSSH_O_RDONLY);
720 if (file < FSSH_B_OK)
721 return NULL;
723 handle = load_driver_settings_from_file(file);
725 fssh_close(file);
726 return (void *)handle;
728 #endif
732 Returns a new driver_settings handle that has the parsed contents
733 of the passed string.
734 You can get an empty driver_settings object when you pass NULL as
735 the "settingsString" parameter.
737 void *
738 fssh_parse_driver_settings_string(const char *settingsString)
740 // we simply copy the whole string to use it as our internal buffer
741 char *text = fssh_strdup(settingsString);
742 if (settingsString == NULL || text != NULL) {
743 settings_handle *handle = (settings_handle*)malloc(sizeof(settings_handle));
744 if (handle != NULL) {
745 handle->magic = SETTINGS_MAGIC;
746 handle->text = text;
748 if (parse_settings(handle) == FSSH_B_OK)
749 return handle;
751 free(handle);
753 free(text);
756 return NULL;
761 This function prints out a driver settings structure to a human
762 readable string.
763 It's either in standard style or the single line style speficied
764 by the "flat" parameter.
765 If the buffer is too small to hold the string, FSSH_B_BUFFER_OVERFLOW
766 is returned, and the needed amount of bytes if placed in the
767 "_bufferSize" parameter.
768 If the "handle" parameter is not a valid driver settings handle, or
769 the "buffer" parameter is NULL, FSSH_B_BAD_VALUE is returned.
771 fssh_status_t
772 fssh_get_driver_settings_string(void *_handle, char *buffer,
773 fssh_size_t *_bufferSize, bool flat)
775 settings_handle *handle = (settings_handle *)_handle;
776 fssh_size_t bufferSize = *_bufferSize;
777 int32_t i;
779 if (!check_handle(handle) || !buffer || *_bufferSize == 0)
780 return FSSH_B_BAD_VALUE;
782 for (i = 0; i < handle->settings.parameter_count; i++) {
783 put_parameter(&buffer, &bufferSize, &handle->settings.parameters[i],
784 0, flat);
787 *_bufferSize -= bufferSize;
788 return bufferSize >= 0 ? FSSH_B_OK : FSSH_B_BUFFER_OVERFLOW;
793 Matches the first value of the parameter matching "keyName" with a set
794 of boolean values like 1/true/yes/on/enabled/...
795 Returns "unknownValue" if the parameter could not be found or doesn't
796 have any valid boolean setting, and "noArgValue" if the parameter
797 doesn't have any values.
798 Also returns "unknownValue" if the handle passed in was not valid.
800 bool
801 fssh_get_driver_boolean_parameter(void *handle, const char *keyName,
802 bool unknownValue, bool noArgValue)
804 fssh_driver_parameter *parameter;
805 char *boolean;
807 if (!check_handle(handle))
808 return unknownValue;
810 // check for the parameter
811 if ((parameter = get_parameter((settings_handle*)handle, keyName)) == NULL)
812 return unknownValue;
814 // check for the argument
815 if (parameter->value_count <= 0)
816 return noArgValue;
818 boolean = parameter->values[0];
819 if (!fssh_strcmp(boolean, "1")
820 || !fssh_strcasecmp(boolean, "true")
821 || !fssh_strcasecmp(boolean, "yes")
822 || !fssh_strcasecmp(boolean, "on")
823 || !fssh_strcasecmp(boolean, "enable")
824 || !fssh_strcasecmp(boolean, "enabled"))
825 return true;
827 if (!fssh_strcmp(boolean, "0")
828 || !fssh_strcasecmp(boolean, "false")
829 || !fssh_strcasecmp(boolean, "no")
830 || !fssh_strcasecmp(boolean, "off")
831 || !fssh_strcasecmp(boolean, "disable")
832 || !fssh_strcasecmp(boolean, "disabled"))
833 return false;
835 // if no known keyword is found, "unknownValue" is returned
836 return unknownValue;
840 const char *
841 fssh_get_driver_parameter(void *handle, const char *keyName,
842 const char *unknownValue, const char *noArgValue)
844 struct fssh_driver_parameter *parameter;
846 if (!check_handle(handle))
847 return unknownValue;
849 // check for the parameter
850 if ((parameter = get_parameter((settings_handle*)handle, keyName)) == NULL)
851 return unknownValue;
853 // check for the argument
854 if (parameter->value_count <= 0)
855 return noArgValue;
857 return parameter->values[0];
861 const fssh_driver_settings *
862 fssh_get_driver_settings(void *handle)
864 if (!check_handle(handle))
865 return NULL;
867 return &((settings_handle *)handle)->settings;
871 fssh_status_t
872 fssh_delete_driver_settings(void *handle)
874 return fssh_unload_driver_settings(handle);