#ifdef _BOOT_MODE
# undef _KERNEL_MODE
#endif
#ifndef _DEFAULT_SOURCE
#define _DEFAULT_SOURCE
#endif
#include <BeBuild.h>
#include <directories.h>
#include <driver_settings.h>
#include <FindDirectory.h>
#include <OS.h>
#ifdef _KERNEL_MODE
# include <KernelExport.h>
# include <util/list.h>
# include <lock.h>
# include <kdriver_settings.h>
# include <kernel.h>
# include <boot/kernel_args.h>
# include <boot_device.h>
#endif
#ifdef _BOOT_MODE
# include <boot/kernel_args.h>
# include <boot/stage2.h>
#else
# include <find_directory_private.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#define SETTINGS_DIRECTORY "/kernel/drivers/"
#define SETTINGS_MAGIC 'DrvS'
#define MAX_SETTINGS_SIZE 32768
#define MAX_SETTINGS_LEVEL 8
#define CONTINUE_PARAMETER 1
#define NO_PARAMETER 2
typedef struct settings_handle {
#ifdef _KERNEL_MODE
list_link link;
char name[B_OS_NAME_LENGTH];
int32 ref_count;
#endif
int32 magic;
struct driver_settings settings;
char *text;
} settings_handle;
enum assignment_mode {
NO_ASSIGNMENT,
ALLOW_ASSIGNMENT,
IGNORE_ASSIGNMENT
};
#ifdef _KERNEL_MODE
static struct list sHandles;
static mutex sLock = MUTEX_INITIALIZER("driver settings");
#endif
static inline bool
is_parameter_separator(char c)
{
return c == '\n' || c == ';';
}
static inline bool
is_word_break(char c)
{
return isspace(c) || is_parameter_separator(c);
}
static inline bool
check_handle(settings_handle *handle)
{
if (handle == NULL
|| handle->magic != SETTINGS_MAGIC)
return false;
return true;
}
static driver_parameter *
get_parameter(settings_handle *handle, const char *name)
{
int32 i;
for (i = handle->settings.parameter_count; i-- > 0;) {
if (!strcmp(handle->settings.parameters[i].name, name))
return &handle->settings.parameters[i];
}
return NULL;
}
static status_t
get_word(char **_pos, char **_word, int32 assignmentMode, bool allowNewLine)
{
char *pos = *_pos;
char quoted = 0;
bool newLine = false, end = false;
int escaped = 0;
bool charEscaped = false;
while (pos[0]
&& ((allowNewLine && (isspace(pos[0]) || is_parameter_separator(pos[0])
|| pos[0] == '#'))
|| (!allowNewLine && (pos[0] == '\t' || pos[0] == ' '))
|| (assignmentMode == ALLOW_ASSIGNMENT && pos[0] == '='))) {
if (pos[0] == '#')
pos = strchrnul(pos, '\n');
pos++;
}
if (pos[0] == '}' || pos[0] == '\0') {
*_pos = pos;
return NO_PARAMETER;
}
if (pos[0] == '"' || pos[0] == '\'') {
quoted = pos[0];
pos++;
}
*_word = pos;
while (pos[0]) {
if (charEscaped)
charEscaped = false;
else if (pos[0] == '\\') {
charEscaped = true;
escaped++;
} else if ((!quoted && (is_word_break(pos[0])
|| (assignmentMode != IGNORE_ASSIGNMENT && pos[0] == '=')))
|| (quoted && pos[0] == quoted))
break;
pos++;
}
if (quoted && pos[0] != quoted)
return B_BAD_DATA;
if (charEscaped)
return B_BAD_DATA;
end = pos[0] == '\0';
newLine = is_parameter_separator(pos[0]) || end;
pos[0] = '\0';
if (escaped) {
char *word = *_word;
int offset = 0;
while (word <= pos) {
if (word[0] == '\\') {
offset--;
word++;
}
word[offset] = word[0];
word++;
}
}
if (end) {
*_pos = pos;
return B_OK;
}
pos++;
while (true) {
*_pos = pos;
if (!pos[0])
return B_NO_ERROR;
if (is_parameter_separator(pos[0])) {
if (newLine)
return B_NO_ERROR;
newLine = true;
} else if (pos[0] == '{' || pos[0] == '}' || pos[0] == '#')
return B_NO_ERROR;
else if (!isspace(pos[0]))
return newLine ? B_NO_ERROR : CONTINUE_PARAMETER;
pos++;
}
}
static status_t
parse_parameter(struct driver_parameter *parameter, char **_pos, int32 level)
{
char *pos = *_pos;
status_t status;
memset(parameter, 0, sizeof(struct driver_parameter));
status = get_word(&pos, ¶meter->name, NO_ASSIGNMENT, true);
if (status == CONTINUE_PARAMETER) {
while (status == CONTINUE_PARAMETER) {
char **newArray, *value = NULL;
status = get_word(&pos, &value, parameter->value_count == 0
? ALLOW_ASSIGNMENT : IGNORE_ASSIGNMENT, false);
if (status < B_OK)
break;
newArray = (char**)realloc(parameter->values,
(parameter->value_count + 1) * sizeof(char *));
if (newArray == NULL)
return B_NO_MEMORY;
parameter->values = newArray;
parameter->values[parameter->value_count++] = value;
}
}
*_pos = pos;
return status;
}
static status_t
parse_parameters(struct driver_parameter **_parameters, int *_count,
char **_pos, int32 level)
{
if (level > MAX_SETTINGS_LEVEL)
return B_LINK_LIMIT;
while (true) {
struct driver_parameter parameter;
struct driver_parameter *newArray;
status_t status;
status = parse_parameter(¶meter, _pos, level);
if (status < B_OK)
return status;
if (status != NO_PARAMETER) {
driver_parameter *newParameter;
newArray = (driver_parameter*)realloc(*_parameters, (*_count + 1)
* sizeof(struct driver_parameter));
if (newArray == NULL)
return B_NO_MEMORY;
memcpy(&newArray[*_count], ¶meter, sizeof(struct driver_parameter));
newParameter = &newArray[*_count];
*_parameters = newArray;
(*_count)++;
if (**_pos == '{') {
(*_pos)++;
status = parse_parameters(&newParameter->parameters,
&newParameter->parameter_count, _pos, level + 1);
if (status < B_OK)
return status;
}
}
if ((**_pos == '}' && level > 0)
|| (**_pos == '\0' && level == 0)) {
(*_pos)++;
return B_OK;
}
if (**_pos == '}' || **_pos == '\0')
return B_ERROR;
}
}
static status_t
parse_settings(settings_handle *handle)
{
char *text = handle->text;
memset(&handle->settings, 0, sizeof(struct driver_settings));
if (text == NULL)
return B_OK;
return parse_parameters(&handle->settings.parameters,
&handle->settings.parameter_count, &text, 0);
}
static void
free_parameter(struct driver_parameter *parameter)
{
int32 i;
for (i = parameter->parameter_count; i-- > 0;)
free_parameter(¶meter->parameters[i]);
free(parameter->parameters);
free(parameter->values);
}
static void
free_settings(settings_handle *handle)
{
int32 i;
for (i = handle->settings.parameter_count; i-- > 0;)
free_parameter(&handle->settings.parameters[i]);
free(handle->settings.parameters);
free(handle->text);
free(handle);
}
static settings_handle *
new_settings(char *buffer, const char *driverName)
{
settings_handle *handle = (settings_handle*)malloc(sizeof(settings_handle));
if (handle == NULL)
return NULL;
handle->magic = SETTINGS_MAGIC;
handle->text = buffer;
#ifdef _KERNEL_MODE
if (driverName != NULL) {
handle->ref_count = 1;
strlcpy(handle->name, driverName, sizeof(handle->name));
} else {
handle->ref_count = -1;
handle->name[0] = 0;
}
#endif
if (parse_settings(handle) == B_OK)
return handle;
free(handle);
return NULL;
}
static settings_handle *
load_driver_settings_from_file(int file, const char *driverName)
{
struct stat stat;
if (fstat(file, &stat) < B_OK)
return NULL;
if (stat.st_size > B_OK && stat.st_size < MAX_SETTINGS_SIZE) {
char *text = (char *)malloc(stat.st_size + 1);
if (text != NULL && read(file, text, stat.st_size) == stat.st_size) {
settings_handle *handle;
text[stat.st_size] = '\0';
handle = new_settings(text, driverName);
if (handle != NULL) {
return handle;
}
}
free(text);
}
return NULL;
}
static bool
put_string(char **_buffer, size_t *_bufferSize, char *string)
{
size_t length, reserved, quotes;
char *buffer = *_buffer, c;
bool quoted;
if (string == NULL)
return true;
for (length = reserved = quotes = 0; (c = string[length]) != '\0'; length++) {
if (c == '"')
quotes++;
else if (is_word_break(c))
reserved++;
}
quoted = reserved || quotes;
*_bufferSize -= length + (quoted ? 2 + quotes : 0);
if (*_bufferSize <= 0)
return false;
if (quoted)
*(buffer++) = '"';
for (;(c = string[0]) != '\0'; string++) {
if (c == '"')
*(buffer++) = '\\';
*(buffer++) = c;
}
if (quoted)
*(buffer++) = '"';
buffer[0] = '\0';
*_buffer = buffer;
return true;
}
static bool
put_chars(char **_buffer, size_t *_bufferSize, const char *chars)
{
char *buffer = *_buffer;
size_t length;
if (chars == NULL)
return true;
length = strlen(chars);
if (*_bufferSize <= length) {
*_bufferSize = 0;
return false;
}
*_bufferSize -= length;
memcpy(buffer, chars, length);
buffer += length;
buffer[0] = '\0';
*_buffer = buffer;
return true;
}
static bool
put_char(char **_buffer, size_t *_bufferSize, char c)
{
char *buffer = *_buffer;
if (*_bufferSize <= 1) {
*_bufferSize = 0;
return false;
}
*_bufferSize -= 1;
buffer[0] = c;
buffer[1] = '\0';
*_buffer = buffer + 1;
return true;
}
static void
put_level_space(char **_buffer, size_t *_bufferSize, int32 level)
{
while (level-- > 0)
put_char(_buffer, _bufferSize, '\t');
}
static void
put_parameter(char **_buffer, size_t *_bufferSize,
struct driver_parameter *parameter, int32 level, bool flat)
{
int32 i;
if (!flat)
put_level_space(_buffer, _bufferSize, level);
put_string(_buffer, _bufferSize, parameter->name);
if (flat && parameter->value_count > 0)
put_chars(_buffer, _bufferSize, " =");
for (i = 0; i < parameter->value_count; i++) {
put_char(_buffer, _bufferSize, ' ');
put_string(_buffer, _bufferSize, parameter->values[i]);
}
if (parameter->parameter_count > 0) {
put_chars(_buffer, _bufferSize, " {");
if (!flat)
put_char(_buffer, _bufferSize, '\n');
for (i = 0; i < parameter->parameter_count; i++) {
put_parameter(_buffer, _bufferSize, ¶meter->parameters[i],
level + 1, flat);
if (parameter->parameters[i].parameter_count == 0)
put_chars(_buffer, _bufferSize, flat ? "; " : "\n");
}
if (!flat)
put_level_space(_buffer, _bufferSize, level);
put_chars(_buffer, _bufferSize, flat ? "}" : "}\n");
}
}
#ifdef _KERNEL_MODE
static settings_handle *
find_driver_settings(const char *name)
{
settings_handle *handle = NULL;
ASSERT_LOCKED_MUTEX(&sLock);
while ((handle = (settings_handle*)list_get_next_item(&sHandles, handle))
!= NULL) {
if (!strcmp(handle->name, name))
return handle;
}
return NULL;
}
status_t
driver_settings_init(kernel_args *args)
{
struct driver_settings_file *settings = args->driver_settings;
list_init(&sHandles);
while (settings != NULL) {
settings_handle *handle
= (settings_handle*)malloc(sizeof(settings_handle));
if (handle == NULL)
return B_NO_MEMORY;
if (settings->size != 0) {
handle->text = (char*)malloc(settings->size + 1);
if (handle->text == NULL) {
free(handle);
return B_NO_MEMORY;
}
memcpy(handle->text, settings->buffer, settings->size);
handle->text[settings->size] = '\0';
} else
handle->text = NULL;
strlcpy(handle->name, settings->name, sizeof(handle->name));
handle->settings.parameters = NULL;
handle->settings.parameter_count = 0;
handle->magic = 0;
if (!strcmp(handle->name, B_SAFEMODE_DRIVER_SETTINGS)) {
handle->ref_count = 1;
} else
handle->ref_count = 0;
list_add_item(&sHandles, handle);
settings = settings->next;
}
return B_OK;
}
#endif
status_t
unload_driver_settings(void *_handle)
{
settings_handle *handle = (settings_handle *)_handle;
if (!check_handle(handle))
return B_BAD_VALUE;
#ifdef _KERNEL_MODE
mutex_lock(&sLock);
if (handle->ref_count > 0) {
if (--handle->ref_count == 0 && gBootDevice > 0) {
list_remove_link(&handle->link);
} else
handle = NULL;
}
mutex_unlock(&sLock);
#endif
if (handle != NULL)
free_settings(handle);
return B_OK;
}
void *
load_driver_settings(const char *driverName)
{
settings_handle *handle;
int file = -1;
if (driverName == NULL)
return NULL;
#ifdef _KERNEL_MODE
mutex_lock(&sLock);
handle = find_driver_settings(driverName);
if (handle != NULL && handle->ref_count == 0 && gBootDevice > 0) {
list_remove_link(&handle->link);
free_settings(handle);
} else if (handle != NULL) {
handle->ref_count++;
if (handle->magic != SETTINGS_MAGIC) {
handle->magic = SETTINGS_MAGIC;
if (parse_settings(handle) != B_OK) {
free(handle->text);
handle->text = NULL;
handle = NULL;
}
}
mutex_unlock(&sLock);
return handle;
}
if (gKernelStartup) {
mutex_unlock(&sLock);
return NULL;
}
#endif
#ifdef _BOOT_MODE
{
struct driver_settings_file *settings = gKernelArgs.driver_settings;
while (settings != NULL) {
if (!strcmp(settings->name, driverName)) {
char *text = (char*)malloc(settings->size + 1);
if (text == NULL)
return NULL;
memcpy(text, settings->buffer, settings->size + 1);
settings_handle *handle = new_settings(text, driverName);
if (handle == NULL)
free(text);
return handle;
}
settings = settings->next;
}
}
#endif
if (driverName[0] != '/') {
char path[B_FILE_NAME_LENGTH + 64];
#ifdef _BOOT_MODE
strcpy(path, kUserSettingsDirectory);
#else
if (__find_directory(B_USER_SETTINGS_DIRECTORY, -1, false, path,
sizeof(path)) == B_OK)
#endif
{
strlcat(path, SETTINGS_DIRECTORY, sizeof(path));
strlcat(path, driverName, sizeof(path));
}
file = open(path, O_RDONLY);
} else
file = open(driverName, O_RDONLY);
if (file < B_OK) {
#ifdef _KERNEL_MODE
mutex_unlock(&sLock);
#endif
return NULL;
}
handle = load_driver_settings_from_file(file, driverName);
#ifdef _KERNEL_MODE
if (handle != NULL)
list_add_item(&sHandles, handle);
mutex_unlock(&sLock);
#endif
close(file);
return (void *)handle;
}
void*
load_driver_settings_file(int fd)
{
return load_driver_settings_from_file(fd, NULL);
}
void *
parse_driver_settings_string(const char *settingsString)
{
char *text = NULL;
if (settingsString != NULL) {
text = strdup(settingsString);
if (text == NULL)
return NULL;
}
settings_handle *handle = new_settings(text, NULL);
if (handle == NULL)
free(text);
return handle;
}
status_t
get_driver_settings_string(void *_handle, char *buffer, size_t *_bufferSize,
bool flat)
{
settings_handle *handle = (settings_handle *)_handle;
size_t bufferSize = *_bufferSize;
int32 i;
if (!check_handle(handle) || !buffer || *_bufferSize == 0)
return B_BAD_VALUE;
for (i = 0; i < handle->settings.parameter_count; i++) {
put_parameter(&buffer, &bufferSize, &handle->settings.parameters[i],
0, flat);
}
return bufferSize > 0 ? B_OK : B_BUFFER_OVERFLOW;
}
bool
get_driver_boolean_parameter(void *_handle, const char *keyName,
bool unknownValue, bool noArgValue)
{
settings_handle *handle = (settings_handle*)_handle;
driver_parameter *parameter;
char *boolean;
if (!check_handle(handle))
return unknownValue;
if ((parameter = get_parameter(handle, keyName)) == NULL)
return unknownValue;
if (parameter->value_count <= 0)
return noArgValue;
boolean = parameter->values[0];
if (!strcmp(boolean, "1")
|| !strcasecmp(boolean, "true")
|| !strcasecmp(boolean, "yes")
|| !strcasecmp(boolean, "on")
|| !strcasecmp(boolean, "enable")
|| !strcasecmp(boolean, "enabled"))
return true;
if (!strcmp(boolean, "0")
|| !strcasecmp(boolean, "false")
|| !strcasecmp(boolean, "no")
|| !strcasecmp(boolean, "off")
|| !strcasecmp(boolean, "disable")
|| !strcasecmp(boolean, "disabled"))
return false;
return unknownValue;
}
const char *
get_driver_parameter(void *_handle, const char *keyName,
const char *unknownValue, const char *noArgValue)
{
settings_handle* handle = (settings_handle*)_handle;
struct driver_parameter *parameter;
if (!check_handle(handle))
return unknownValue;
if ((parameter = get_parameter(handle, keyName)) == NULL)
return unknownValue;
if (parameter->value_count <= 0)
return noArgValue;
return parameter->values[0];
}
const driver_settings *
get_driver_settings(void *handle)
{
if (!check_handle((settings_handle*)handle))
return NULL;
return &((settings_handle *)handle)->settings;
}
#if defined(HAIKU_TARGET_PLATFORM_HAIKU) \
&& (defined(__i386__) || defined(__x86_64__))
B_DEFINE_WEAK_ALIAS(unload_driver_settings, delete_driver_settings);
#endif