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, &parameter->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(&parameter, _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], &parameter, 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(&parameter->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, &parameter->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