2 * Copyright 2002-2007, Axel Dörfler, axeld@pinc-software.de.
3 * This file may be used under the terms of the MIT License.
6 /*! \brief Implements the driver settings API
7 This file is used by three different components with different needs:
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.
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
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"
30 #include "fssh_fcntl.h"
31 #include "fssh_lock.h"
33 #include "fssh_stat.h"
34 #include "fssh_string.h"
35 #include "fssh_unistd.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
{
56 char name
[FSSH_B_OS_NAME_LENGTH
];
59 struct fssh_driver_settings settings
;
64 enum assignment_mode
{
71 static struct list sHandles
;
72 static fssh_mutex sLock
;
75 // #pragma mark - private functions
79 Returns true for any characters that separate parameters -
80 those are ignored in the input stream and won't be added
84 is_parameter_separator(char c
)
86 return c
== '\n' || c
== ';';
90 /** Indicates if "c" begins a new word or not.
96 return isspace(c
) || is_parameter_separator(c
);
101 check_handle(void *_handle
)
103 settings_handle
*handle
= (settings_handle
*)_handle
;
105 || handle
->magic
!= SETTINGS_MAGIC
)
112 static fssh_driver_parameter
*
113 get_parameter(settings_handle
*handle
, const char *name
)
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
];
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
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.
139 get_word(char **_pos
, char **_word
, int32_t assignmentMode
, bool allowNewLine
)
143 bool newLine
= false, end
= false;
145 bool charEscaped
= false;
147 // Skip any white space and comments
149 && ((allowNewLine
&& (isspace(pos
[0]) || is_parameter_separator(pos
[0])
151 || (!allowNewLine
&& (pos
[0] == '\t' || pos
[0] == ' '))
152 || (assignmentMode
== ALLOW_ASSIGNMENT
&& pos
[0] == '='))) {
153 // skip any comment lines
155 while (pos
[0] && pos
[0] != '\n')
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
168 // Read in a word - might contain escaped (\) spaces, or it
169 // might also be quoted (" or ').
171 if (pos
[0] == '"' || pos
[0] == '\'') {
180 else if (pos
[0] == '\\') {
183 } else if ((!quoted
&& (is_word_break(pos
[0])
184 || (assignmentMode
!= IGNORE_ASSIGNMENT
&& pos
[0] == '=')))
185 || (quoted
&& pos
[0] == quoted
))
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
197 return FSSH_B_BAD_DATA
;
199 end
= pos
[0] == '\0';
200 newLine
= is_parameter_separator(pos
[0]) || end
;
203 // Correct name if there were any escaped characters
207 while (word
<= pos
) {
208 if (word
[0] == '\\') {
212 word
[offset
] = word
[0];
222 // Scan for next beginning word, open brackets, or comment start
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
234 return FSSH_B_NO_ERROR
;
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
;
248 parse_parameter(struct fssh_driver_parameter
*parameter
, char **_pos
, int32_t level
)
251 fssh_status_t status
;
253 // initialize parameter first
254 fssh_memset(parameter
, 0, sizeof(struct fssh_driver_parameter
));
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
)
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
;
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
;
290 struct fssh_driver_parameter parameter
;
291 struct fssh_driver_parameter
*newArray
;
292 fssh_status_t status
;
294 status
= parse_parameter(¶meter
, _pos
, level
);
295 if (status
< FSSH_B_OK
)
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
], ¶meter
, sizeof(struct fssh_driver_parameter
));
307 newParameter
= &newArray
[*_count
];
309 *_parameters
= newArray
;
312 // check for level beginning and end
314 // if we go a level deeper, just start all over again...
316 status
= parse_parameters(&newParameter
->parameters
,
317 &newParameter
->parameter_count
, _pos
, level
+ 1);
318 if (status
< FSSH_B_OK
)
323 if ((**_pos
== '}' && level
> 0)
324 || (**_pos
== '\0' && level
== 0)) {
325 // take the closing bracket from the stack
330 // obviously, something has gone wrong
331 if (**_pos
== '}' || **_pos
== '\0')
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
348 return parse_parameters(&handle
->settings
.parameters
,
349 &handle
->settings
.parameter_count
, &text
, 0);
354 free_parameter(struct fssh_driver_parameter
*parameter
)
357 for (i
= parameter
->parameter_count
; i
-- > 0;)
358 free_parameter(¶meter
->parameters
[i
]);
360 free(parameter
->parameters
);
361 free(parameter
->values
);
366 free_settings(settings_handle
*handle
)
369 for (i
= handle
->settings
.parameter_count
; i
-- > 0;)
370 free_parameter(&handle
->settings
.parameters
[i
]);
372 free(handle
->settings
.parameters
);
378 static settings_handle
*
379 new_settings(char *buffer
, const char *driverName
)
381 settings_handle
*handle
= (settings_handle
*)malloc(sizeof(settings_handle
));
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
)
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
406 // The fssh_driver_parameter::name field will point directly
409 if (fssh_fstat(file
, &stat
) < FSSH_B_OK
)
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!
429 // "text" might be NULL here, but that's allowed
438 put_string(char **_buffer
, fssh_size_t
*_bufferSize
, char *string
)
440 fssh_size_t length
, reserved
, quotes
;
441 char *buffer
= *_buffer
, c
;
447 for (length
= reserved
= quotes
= 0; (c
= string
[length
]) != '\0'; length
++) {
450 else if (is_word_break(c
))
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
458 *_bufferSize
-= length
+ (quoted
? 2 + quotes
: 0);
460 if (*_bufferSize
<= 0)
466 for (;(c
= string
[0]) != '\0'; string
++) {
478 // update the buffer position
486 put_chars(char **_buffer
, fssh_size_t
*_bufferSize
, const char *chars
)
488 char *buffer
= *_buffer
;
494 length
= fssh_strlen(chars
);
495 *_bufferSize
-= length
;
497 if (*_bufferSize
<= 0)
500 fssh_memcpy(buffer
, chars
, length
);
504 // update the buffer position
512 put_char(char **_buffer
, fssh_size_t
*_bufferSize
, char c
)
514 char *buffer
= *_buffer
;
518 if (*_bufferSize
<= 0)
524 // update the buffer position
525 *_buffer
= buffer
+ 1;
532 put_level_space(char **_buffer
, fssh_size_t
*_bufferSize
, int32_t level
)
535 put_char(_buffer
, _bufferSize
, '\t');
540 put_parameter(char **_buffer
, fssh_size_t
*_bufferSize
,
541 struct fssh_driver_parameter
*parameter
, int32_t level
, bool 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
, " {");
560 put_char(_buffer
, _bufferSize
, '\n');
562 for (i
= 0; i
< parameter
->parameter_count
; i
++) {
563 put_parameter(_buffer
, _bufferSize
, ¶meter
->parameters
[i
],
566 if (parameter
->parameters
[i
].parameter_count
== 0)
567 put_chars(_buffer
, _bufferSize
, flat
? "; " : "\n");
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
))
601 driver_settings_init()
603 fssh_mutex_init(&sLock
, "driver settings");
607 } // namespace FSShell
610 // #pragma mark - public API
614 fssh_unload_driver_settings(void *handle
)
616 if (!check_handle(handle
))
617 return FSSH_B_BAD_VALUE
;
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
);
626 fssh_mutex_unlock(&sLock
);
630 free_settings((settings_handle
*)handle
);
637 fssh_load_driver_settings(const char *driverName
)
639 settings_handle
*handle
;
642 if (driverName
== 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
) {
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
662 fssh_mutex_unlock(&sLock
);
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
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
);
681 file
= fssh_open(driverName
, FSSH_O_RDONLY
);
683 if (file
< FSSH_B_OK
) {
684 fssh_mutex_unlock(&sLock
);
688 handle
= load_driver_settings_from_file(file
, driverName
);
691 list_add_item(&sHandles
, handle
);
692 fssh_mutex_unlock(&sLock
);
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
704 * ~/config/settings/kernel/driver
706 * That would render this function useless.
711 fssh_load_driver_settings_from_path(const char *path
)
713 settings_handle
*handle
;
719 file
= fssh_open(path
, FSSH_O_RDONLY
);
720 if (file
< FSSH_B_OK
)
723 handle
= load_driver_settings_from_file(file
);
726 return (void *)handle
;
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.
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
;
748 if (parse_settings(handle
) == FSSH_B_OK
)
761 This function prints out a driver settings structure to a human
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.
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
;
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
],
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.
801 fssh_get_driver_boolean_parameter(void *handle
, const char *keyName
,
802 bool unknownValue
, bool noArgValue
)
804 fssh_driver_parameter
*parameter
;
807 if (!check_handle(handle
))
810 // check for the parameter
811 if ((parameter
= get_parameter((settings_handle
*)handle
, keyName
)) == NULL
)
814 // check for the argument
815 if (parameter
->value_count
<= 0)
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"))
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"))
835 // if no known keyword is found, "unknownValue" is returned
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
))
849 // check for the parameter
850 if ((parameter
= get_parameter((settings_handle
*)handle
, keyName
)) == NULL
)
853 // check for the argument
854 if (parameter
->value_count
<= 0)
857 return parameter
->values
[0];
861 const fssh_driver_settings
*
862 fssh_get_driver_settings(void *handle
)
864 if (!check_handle(handle
))
867 return &((settings_handle
*)handle
)->settings
;
872 fssh_delete_driver_settings(void *handle
)
874 return fssh_unload_driver_settings(handle
);