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		// "text" might be NULL here, but that's allowed
428		free(text);
429	}
430
431	return NULL;
432}
433
434
435static bool
436put_string(char **_buffer, fssh_ssize_t *_bufferSize, char *string)
437{
438	fssh_size_t length, reserved, quotes;
439	char *buffer = *_buffer, c;
440	bool quoted;
441
442	if (string == NULL)
443		return true;
444
445	for (length = reserved = quotes = 0; (c = string[length]) != '\0'; length++) {
446		if (c == '"')
447			quotes++;
448		else if (is_word_break(c))
449			reserved++;
450	}
451	quoted = reserved || quotes;
452
453	// update _bufferSize in any way, so that we can chain several
454	// of these calls without having to check the return value
455	// everytime
456	*_bufferSize -= length + (quoted ? 2 + quotes : 0);
457
458	if (*_bufferSize <= 0)
459		return false;
460
461	if (quoted)
462		*(buffer++) = '"';
463
464	for (;(c = string[0]) != '\0'; string++) {
465		if (c == '"')
466			*(buffer++) = '\\';
467
468		*(buffer++) = c;
469	}
470
471	if (quoted)
472		*(buffer++) = '"';
473
474	buffer[0] = '\0';
475
476	// update the buffer position
477	*_buffer = buffer;
478
479	return true;
480}
481
482
483static bool
484put_chars(char **_buffer, fssh_ssize_t *_bufferSize, const char *chars)
485{
486	char *buffer = *_buffer;
487	fssh_size_t length;
488
489	if (chars == NULL)
490		return true;
491
492	length = fssh_strlen(chars);
493	*_bufferSize -= length;
494
495	if (*_bufferSize <= 0)
496		return false;
497
498	fssh_memcpy(buffer, chars, length);
499	buffer += length;
500	buffer[0] = '\0';
501
502	// update the buffer position
503	*_buffer = buffer;
504
505	return true;
506}
507
508
509static bool
510put_char(char **_buffer, fssh_ssize_t *_bufferSize, char c)
511{
512	char *buffer = *_buffer;
513
514	*_bufferSize -= 1;
515
516	if (*_bufferSize <= 0)
517		return false;
518
519	buffer[0] = c;
520	buffer[1] = '\0';
521
522	// update the buffer position
523	*_buffer = buffer + 1;
524
525	return true;
526}
527
528
529static void
530put_level_space(char **_buffer, fssh_ssize_t *_bufferSize, int32_t level)
531{
532	while (level-- > 0)
533		put_char(_buffer, _bufferSize, '\t');
534}
535
536
537static void
538put_parameter(char **_buffer, fssh_ssize_t *_bufferSize,
539	struct fssh_driver_parameter *parameter, int32_t level, bool flat)
540{
541	int32_t i;
542
543	if (!flat)
544		put_level_space(_buffer, _bufferSize, level);
545
546	put_string(_buffer, _bufferSize, parameter->name);
547	if (flat && parameter->value_count > 0)
548		put_chars(_buffer, _bufferSize, " =");
549
550	for (i = 0; i < parameter->value_count; i++) {
551		put_char(_buffer, _bufferSize, ' ');
552		put_string(_buffer, _bufferSize, parameter->values[i]);
553	}
554
555	if (parameter->parameter_count > 0) {
556		put_chars(_buffer, _bufferSize, " {");
557		if (!flat)
558			put_char(_buffer, _bufferSize, '\n');
559
560		for (i = 0; i < parameter->parameter_count; i++) {
561			put_parameter(_buffer, _bufferSize, &parameter->parameters[i],
562				level + 1, flat);
563
564			if (parameter->parameters[i].parameter_count == 0)
565				put_chars(_buffer, _bufferSize, flat ? "; " : "\n");
566		}
567
568		if (!flat)
569			put_level_space(_buffer, _bufferSize, level);
570		put_chars(_buffer, _bufferSize, flat ? "}" : "}\n");
571	}
572}
573
574
575//	#pragma mark - Kernel only functions
576
577
578static settings_handle *
579find_driver_settings(const char *name)
580{
581	settings_handle *handle = NULL;
582
583	FSSH_ASSERT_LOCKED_MUTEX(&sLock);
584
585	while ((handle = (settings_handle*)list_get_next_item(&sHandles, handle)) != NULL) {
586		if (!fssh_strcmp(handle->name, name))
587			return handle;
588	}
589
590	return NULL;
591}
592
593
594namespace FSShell {
595
596fssh_status_t
597driver_settings_init()
598{
599	fssh_mutex_init(&sLock, "driver settings");
600	return FSSH_B_OK;
601}
602
603}	// namespace FSShell
604
605
606//	#pragma mark - public API
607
608
609fssh_status_t
610fssh_unload_driver_settings(void *handle)
611{
612	if (!check_handle(handle))
613		return FSSH_B_BAD_VALUE;
614
615#if 0
616	fssh_mutex_lock(&sLock);
617	// ToDo: as soon as "/boot" is accessible, we should start throwing away settings
618	if (--handle->ref_count == 0) {
619		list_remove_link(&handle->link);
620	} else
621		handle = NULL;
622	fssh_mutex_unlock(&sLock);
623#endif
624
625	if (handle != NULL)
626		free_settings((settings_handle*)handle);
627
628	return FSSH_B_OK;
629}
630
631
632void *
633fssh_load_driver_settings(const char *driverName)
634{
635	settings_handle *handle;
636	int file = -1;
637
638	if (driverName == NULL)
639		return NULL;
640
641	// see if we already have these settings loaded
642	fssh_mutex_lock(&sLock);
643	handle = find_driver_settings(driverName);
644	if (handle != NULL) {
645		handle->ref_count++;
646
647		// we got it, now let's see if it already has been parsed
648		if (handle->magic != SETTINGS_MAGIC) {
649			handle->magic = SETTINGS_MAGIC;
650
651			if (parse_settings(handle) != FSSH_B_OK) {
652				// no valid settings, let's cut down its memory requirements
653				free(handle->text);
654				handle->text = NULL;
655				handle = NULL;
656			}
657		}
658		fssh_mutex_unlock(&sLock);
659		return handle;
660	}
661
662	// open the settings from the standardized location
663	if (driverName[0] != '/') {
664		char path[FSSH_B_FILE_NAME_LENGTH + 64];
665
666		// This location makes at least a bit sense under BeOS compatible
667		// systems.
668		fssh_strcpy(path, "/boot/home/config/settings/fs_shell");
669
670		{
671			fssh_strlcat(path, SETTINGS_DIRECTORY, sizeof(path));
672			fssh_strlcat(path, driverName, sizeof(path));
673		}
674
675		file = fssh_open(path, FSSH_O_RDONLY);
676	} else
677		file = fssh_open(driverName, FSSH_O_RDONLY);
678
679	if (file < FSSH_B_OK) {
680		fssh_mutex_unlock(&sLock);
681		return NULL;
682	}
683
684	handle = load_driver_settings_from_file(file, driverName);
685
686	if (handle != NULL)
687		list_add_item(&sHandles, handle);
688	fssh_mutex_unlock(&sLock);
689
690	fssh_close(file);
691	return (void *)handle;
692}
693
694
695/** Loads a driver settings file using the full path, instead of
696 *	only defining the leaf name (as load_driver_settings() does).
697 *	I am not sure if this function is really necessary - I would
698 *	probably prefer something like a search order (if it's not
699 *	an absolute path):
700 *		~/config/settings/kernel/driver
701 *		current directory
702 *	That would render this function useless.
703 */
704
705#if 0
706void *
707fssh_load_driver_settings_from_path(const char *path)
708{
709	settings_handle *handle;
710	int file;
711
712	if (path == NULL)
713		return NULL;
714
715	file = fssh_open(path, FSSH_O_RDONLY);
716	if (file < FSSH_B_OK)
717		return NULL;
718
719	handle = load_driver_settings_from_file(file);
720
721	fssh_close(file);
722	return (void *)handle;
723}
724#endif
725
726
727/*!
728	Returns a new driver_settings handle that has the parsed contents
729	of the passed string.
730	You can get an empty driver_settings object when you pass NULL as
731	the "settingsString" parameter.
732*/
733void *
734fssh_parse_driver_settings_string(const char *settingsString)
735{
736	// we simply copy the whole string to use it as our internal buffer
737	char *text = fssh_strdup(settingsString);
738	if (settingsString == NULL || text != NULL) {
739		settings_handle *handle = (settings_handle*)malloc(sizeof(settings_handle));
740		if (handle != NULL) {
741			handle->magic = SETTINGS_MAGIC;
742			handle->text = text;
743
744			if (parse_settings(handle) == FSSH_B_OK)
745				return handle;
746
747			free(handle);
748		}
749		free(text);
750	}
751
752	return NULL;
753}
754
755
756/*!
757	This function prints out a driver settings structure to a human
758	readable string.
759	It's either in standard style or the single line style speficied
760	by the "flat" parameter.
761	If the buffer is too small to hold the string, FSSH_B_BUFFER_OVERFLOW
762	is returned, and the needed amount of bytes if placed in the
763	"_bufferSize" parameter.
764	If the "handle" parameter is not a valid driver settings handle, or
765	the "buffer" parameter is NULL, FSSH_B_BAD_VALUE is returned.
766*/
767fssh_status_t
768fssh_get_driver_settings_string(void *_handle, char *buffer,
769	fssh_ssize_t *_bufferSize, bool flat)
770{
771	settings_handle *handle = (settings_handle *)_handle;
772	fssh_ssize_t bufferSize = *_bufferSize;
773	int32_t i;
774
775	if (!check_handle(handle) || !buffer || *_bufferSize == 0)
776		return FSSH_B_BAD_VALUE;
777
778	for (i = 0; i < handle->settings.parameter_count; i++) {
779		put_parameter(&buffer, &bufferSize, &handle->settings.parameters[i],
780			0, flat);
781	}
782
783	*_bufferSize -= bufferSize;
784	return bufferSize >= 0 ? FSSH_B_OK : FSSH_B_BUFFER_OVERFLOW;
785}
786
787
788/*!
789	Matches the first value of the parameter matching "keyName" with a set
790	of boolean values like 1/true/yes/on/enabled/...
791	Returns "unknownValue" if the parameter could not be found or doesn't
792	have any valid boolean setting, and "noArgValue" if the parameter
793	doesn't have any values.
794	Also returns "unknownValue" if the handle passed in was not valid.
795*/
796bool
797fssh_get_driver_boolean_parameter(void *handle, const char *keyName,
798	bool unknownValue, bool noArgValue)
799{
800	fssh_driver_parameter *parameter;
801	char *boolean;
802
803	if (!check_handle(handle))
804		return unknownValue;
805
806	// check for the parameter
807	if ((parameter = get_parameter((settings_handle*)handle, keyName)) == NULL)
808		return unknownValue;
809
810	// check for the argument
811	if (parameter->value_count <= 0)
812		return noArgValue;
813
814	boolean = parameter->values[0];
815	if (!fssh_strcmp(boolean, "1")
816		|| !fssh_strcasecmp(boolean, "true")
817		|| !fssh_strcasecmp(boolean, "yes")
818		|| !fssh_strcasecmp(boolean, "on")
819		|| !fssh_strcasecmp(boolean, "enable")
820		|| !fssh_strcasecmp(boolean, "enabled"))
821		return true;
822
823	if (!fssh_strcmp(boolean, "0")
824		|| !fssh_strcasecmp(boolean, "false")
825		|| !fssh_strcasecmp(boolean, "no")
826		|| !fssh_strcasecmp(boolean, "off")
827		|| !fssh_strcasecmp(boolean, "disable")
828		|| !fssh_strcasecmp(boolean, "disabled"))
829		return false;
830
831	// if no known keyword is found, "unknownValue" is returned
832	return unknownValue;
833}
834
835
836const char *
837fssh_get_driver_parameter(void *handle, const char *keyName,
838	const char *unknownValue, const char *noArgValue)
839{
840	struct fssh_driver_parameter *parameter;
841
842	if (!check_handle(handle))
843		return unknownValue;
844
845	// check for the parameter
846	if ((parameter = get_parameter((settings_handle*)handle, keyName)) == NULL)
847		return unknownValue;
848
849	// check for the argument
850	if (parameter->value_count <= 0)
851		return noArgValue;
852
853	return parameter->values[0];
854}
855
856
857const fssh_driver_settings *
858fssh_get_driver_settings(void *handle)
859{
860	if (!check_handle(handle))
861		return NULL;
862
863	return &((settings_handle *)handle)->settings;
864}
865
866
867fssh_status_t
868fssh_delete_driver_settings(void *handle)
869{
870	return fssh_unload_driver_settings(handle);
871}
872