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
40using 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
54typedef 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
64enum assignment_mode {
65	NO_ASSIGNMENT,
66	ALLOW_ASSIGNMENT,
67	IGNORE_ASSIGNMENT
68};
69
70
71static struct list sHandles;
72static 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*/
83static inline bool
84is_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
93static inline bool
94is_word_break(char c)
95{
96	return isspace(c) || is_parameter_separator(c);
97}
98
99
100static inline bool
101check_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
112static fssh_driver_parameter *
113get_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*/
138static fssh_status_t
139get_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
247static fssh_status_t
248parse_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, &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;
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
282static fssh_status_t
283parse_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(&parameter, _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], &parameter, 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
337static fssh_status_t
338parse_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
353static void
354free_parameter(struct fssh_driver_parameter *parameter)
355{
356	int32_t i;
357	for (i = parameter->parameter_count; i-- > 0;)
358		free_parameter(&parameter->parameters[i]);
359
360	free(parameter->parameters);
361	free(parameter->values);
362}
363
364
365static void
366free_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
378static settings_handle *
379new_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
398static settings_handle *
399load_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			free(handle);
428		}
429		// "text" might be NULL here, but that's allowed
430		free(text);
431	}
432
433	return NULL;
434}
435
436
437static bool
438put_string(char **_buffer, fssh_size_t *_bufferSize, char *string)
439{
440	fssh_size_t length, reserved, quotes;
441	char *buffer = *_buffer, c;
442	bool quoted;
443
444	if (string == NULL)
445		return true;
446
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++;
452	}
453	quoted = reserved || quotes;
454
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);
459
460	if (*_bufferSize <= 0)
461		return false;
462
463	if (quoted)
464		*(buffer++) = '"';
465
466	for (;(c = string[0]) != '\0'; string++) {
467		if (c == '"')
468			*(buffer++) = '\\';
469
470		*(buffer++) = c;
471	}
472
473	if (quoted)
474		*(buffer++) = '"';
475
476	buffer[0] = '\0';
477
478	// update the buffer position
479	*_buffer = buffer;
480
481	return true;
482}
483
484
485static bool
486put_chars(char **_buffer, fssh_size_t *_bufferSize, const char *chars)
487{
488	char *buffer = *_buffer;
489	fssh_size_t length;
490
491	if (chars == NULL)
492		return true;
493
494	length = fssh_strlen(chars);
495	*_bufferSize -= length;
496
497	if (*_bufferSize <= 0)
498		return false;
499
500	fssh_memcpy(buffer, chars, length);
501	buffer += length;
502	buffer[0] = '\0';
503
504	// update the buffer position
505	*_buffer = buffer;
506
507	return true;
508}
509
510
511static bool
512put_char(char **_buffer, fssh_size_t *_bufferSize, char c)
513{
514	char *buffer = *_buffer;
515
516	*_bufferSize -= 1;
517
518	if (*_bufferSize <= 0)
519		return false;
520
521	buffer[0] = c;
522	buffer[1] = '\0';
523
524	// update the buffer position
525	*_buffer = buffer + 1;
526
527	return true;
528}
529
530
531static void
532put_level_space(char **_buffer, fssh_size_t *_bufferSize, int32_t level)
533{
534	while (level-- > 0)
535		put_char(_buffer, _bufferSize, '\t');
536}
537
538
539static bool
540put_parameter(char **_buffer, fssh_size_t *_bufferSize,
541	struct fssh_driver_parameter *parameter, int32_t level, bool flat)
542{
543	int32_t i;
544
545	if (!flat)
546		put_level_space(_buffer, _bufferSize, level);
547
548	put_string(_buffer, _bufferSize, parameter->name);
549	if (flat && parameter->value_count > 0)
550		put_chars(_buffer, _bufferSize, " =");
551
552	for (i = 0; i < parameter->value_count; i++) {
553		put_char(_buffer, _bufferSize, ' ');
554		put_string(_buffer, _bufferSize, parameter->values[i]);
555	}
556
557	if (parameter->parameter_count > 0) {
558		put_chars(_buffer, _bufferSize, " {");
559		if (!flat)
560			put_char(_buffer, _bufferSize, '\n');
561
562		for (i = 0; i < parameter->parameter_count; i++) {
563			put_parameter(_buffer, _bufferSize, &parameter->parameters[i],
564				level + 1, flat);
565
566			if (parameter->parameters[i].parameter_count == 0)
567				put_chars(_buffer, _bufferSize, flat ? "; " : "\n");
568		}
569
570		if (!flat)
571			put_level_space(_buffer, _bufferSize, level);
572		put_chars(_buffer, _bufferSize, flat ? "}" : "}\n");
573	}
574
575	return *_bufferSize >= 0;
576}
577
578
579//	#pragma mark - Kernel only functions
580
581
582static settings_handle *
583find_driver_settings(const char *name)
584{
585	settings_handle *handle = NULL;
586
587	FSSH_ASSERT_LOCKED_MUTEX(&sLock);
588
589	while ((handle = (settings_handle*)list_get_next_item(&sHandles, handle)) != NULL) {
590		if (!fssh_strcmp(handle->name, name))
591			return handle;
592	}
593
594	return NULL;
595}
596
597
598namespace FSShell {
599
600fssh_status_t
601driver_settings_init()
602{
603	fssh_mutex_init(&sLock, "driver settings");
604	return FSSH_B_OK;
605}
606
607}	// namespace FSShell
608
609
610//	#pragma mark - public API
611
612
613fssh_status_t
614fssh_unload_driver_settings(void *handle)
615{
616	if (!check_handle(handle))
617		return FSSH_B_BAD_VALUE;
618
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
628
629	if (handle != NULL)
630		free_settings((settings_handle*)handle);
631
632	return FSSH_B_OK;
633}
634
635
636void *
637fssh_load_driver_settings(const char *driverName)
638{
639	settings_handle *handle;
640	int file = -1;
641
642	if (driverName == NULL)
643		return NULL;
644
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++;
650
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;
654
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;
660			}
661		}
662		fssh_mutex_unlock(&sLock);
663		return handle;
664	}
665
666	// open the settings from the standardized location
667	if (driverName[0] != '/') {
668		char path[FSSH_B_FILE_NAME_LENGTH + 64];
669
670		// This location makes at least a bit sense under BeOS compatible
671		// systems.
672		fssh_strcpy(path, "/boot/home/config/settings/fs_shell");
673
674		{
675			fssh_strlcat(path, SETTINGS_DIRECTORY, sizeof(path));
676			fssh_strlcat(path, driverName, sizeof(path));
677		}
678
679		file = fssh_open(path, FSSH_O_RDONLY);
680	} else
681		file = fssh_open(driverName, FSSH_O_RDONLY);
682
683	if (file < FSSH_B_OK) {
684		fssh_mutex_unlock(&sLock);
685		return NULL;
686	}
687
688	handle = load_driver_settings_from_file(file, driverName);
689
690	if (handle != NULL)
691		list_add_item(&sHandles, handle);
692	fssh_mutex_unlock(&sLock);
693
694	fssh_close(file);
695	return (void *)handle;
696}
697
698
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.
707 */
708
709#if 0
710void *
711fssh_load_driver_settings_from_path(const char *path)
712{
713	settings_handle *handle;
714	int file;
715
716	if (path == NULL)
717		return NULL;
718
719	file = fssh_open(path, FSSH_O_RDONLY);
720	if (file < FSSH_B_OK)
721		return NULL;
722
723	handle = load_driver_settings_from_file(file);
724
725	fssh_close(file);
726	return (void *)handle;
727}
728#endif
729
730
731/*!
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.
736*/
737void *
738fssh_parse_driver_settings_string(const char *settingsString)
739{
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;
747
748			if (parse_settings(handle) == FSSH_B_OK)
749				return handle;
750
751			free(handle);
752		}
753		free(text);
754	}
755
756	return NULL;
757}
758
759
760/*!
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.
770*/
771fssh_status_t
772fssh_get_driver_settings_string(void *_handle, char *buffer,
773	fssh_size_t *_bufferSize, bool flat)
774{
775	settings_handle *handle = (settings_handle *)_handle;
776	fssh_size_t bufferSize = *_bufferSize;
777	int32_t i;
778
779	if (!check_handle(handle) || !buffer || *_bufferSize == 0)
780		return FSSH_B_BAD_VALUE;
781
782	for (i = 0; i < handle->settings.parameter_count; i++) {
783		put_parameter(&buffer, &bufferSize, &handle->settings.parameters[i],
784			0, flat);
785	}
786
787	*_bufferSize -= bufferSize;
788	return bufferSize >= 0 ? FSSH_B_OK : FSSH_B_BUFFER_OVERFLOW;
789}
790
791
792/*!
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.
799*/
800bool
801fssh_get_driver_boolean_parameter(void *handle, const char *keyName,
802	bool unknownValue, bool noArgValue)
803{
804	fssh_driver_parameter *parameter;
805	char *boolean;
806
807	if (!check_handle(handle))
808		return unknownValue;
809
810	// check for the parameter
811	if ((parameter = get_parameter((settings_handle*)handle, keyName)) == NULL)
812		return unknownValue;
813
814	// check for the argument
815	if (parameter->value_count <= 0)
816		return noArgValue;
817
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;
826
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;
834
835	// if no known keyword is found, "unknownValue" is returned
836	return unknownValue;
837}
838
839
840const char *
841fssh_get_driver_parameter(void *handle, const char *keyName,
842	const char *unknownValue, const char *noArgValue)
843{
844	struct fssh_driver_parameter *parameter;
845
846	if (!check_handle(handle))
847		return unknownValue;
848
849	// check for the parameter
850	if ((parameter = get_parameter((settings_handle*)handle, keyName)) == NULL)
851		return unknownValue;
852
853	// check for the argument
854	if (parameter->value_count <= 0)
855		return noArgValue;
856
857	return parameter->values[0];
858}
859
860
861const fssh_driver_settings *
862fssh_get_driver_settings(void *handle)
863{
864	if (!check_handle(handle))
865		return NULL;
866
867	return &((settings_handle *)handle)->settings;
868}
869
870
871fssh_status_t
872fssh_delete_driver_settings(void *handle)
873{
874	return fssh_unload_driver_settings(handle);
875}
876