#include "fssh_driver_settings.h"
#include <ctype.h>
#include <stdlib.h>
#include "fssh_fcntl.h"
#include "fssh_lock.h"
#include "fssh_os.h"
#include "fssh_stat.h"
#include "fssh_string.h"
#include "fssh_unistd.h"
#include "list.h"
using namespace FSShell;
#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 {
list_link link;
char name[FSSH_B_OS_NAME_LENGTH];
int32_t ref_count;
int32_t magic;
struct fssh_driver_settings settings;
char *text;
} settings_handle;
enum assignment_mode {
NO_ASSIGNMENT,
ALLOW_ASSIGNMENT,
IGNORE_ASSIGNMENT
};
static struct list sHandles;
static fssh_mutex sLock;
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(void *_handle)
{
settings_handle *handle = (settings_handle *)_handle;
if (handle == NULL
|| handle->magic != SETTINGS_MAGIC)
return false;
return true;
}
static fssh_driver_parameter *
get_parameter(settings_handle *handle, const char *name)
{
int32_t i;
for (i = handle->settings.parameter_count; i-- > 0;) {
if (!fssh_strcmp(handle->settings.parameters[i].name, name))
return &handle->settings.parameters[i];
}
return NULL;
}
static fssh_status_t
get_word(char **_pos, char **_word, int32_t 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] == '#') {
while (pos[0] && pos[0] != '\n')
pos++;
}
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 FSSH_B_BAD_DATA;
if (charEscaped)
return FSSH_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 FSSH_B_OK;
}
pos++;
while (true) {
*_pos = pos;
if (!pos[0])
return FSSH_B_NO_ERROR;
if (is_parameter_separator(pos[0])) {
if (newLine)
return FSSH_B_NO_ERROR;
newLine = true;
} else if (pos[0] == '{' || pos[0] == '}' || pos[0] == '#')
return FSSH_B_NO_ERROR;
else if (!isspace(pos[0]))
return newLine ? FSSH_B_NO_ERROR : CONTINUE_PARAMETER;
pos++;
}
}
static fssh_status_t
parse_parameter(struct fssh_driver_parameter *parameter, char **_pos, int32_t level)
{
char *pos = *_pos;
fssh_status_t status;
fssh_memset(parameter, 0, sizeof(struct fssh_driver_parameter));
status = get_word(&pos, ¶meter->name, NO_ASSIGNMENT, true);
if (status == CONTINUE_PARAMETER) {
while (status == CONTINUE_PARAMETER) {
char **newArray, *value;
status = get_word(&pos, &value, parameter->value_count == 0
? ALLOW_ASSIGNMENT : IGNORE_ASSIGNMENT, false);
if (status < FSSH_B_OK)
break;
newArray = (char**)realloc(parameter->values,
(parameter->value_count + 1) * sizeof(char *));
if (newArray == NULL)
return FSSH_B_NO_MEMORY;
parameter->values = newArray;
parameter->values[parameter->value_count++] = value;
}
}
*_pos = pos;
return status;
}
static fssh_status_t
parse_parameters(struct fssh_driver_parameter **_parameters, int *_count,
char **_pos, int32_t level)
{
if (level > MAX_SETTINGS_LEVEL)
return FSSH_B_LINK_LIMIT;
while (true) {
struct fssh_driver_parameter parameter;
struct fssh_driver_parameter *newArray;
fssh_status_t status;
status = parse_parameter(¶meter, _pos, level);
if (status < FSSH_B_OK)
return status;
if (status != NO_PARAMETER) {
fssh_driver_parameter *newParameter;
newArray = (fssh_driver_parameter*)realloc(*_parameters, (*_count + 1)
* sizeof(struct fssh_driver_parameter));
if (newArray == NULL)
return FSSH_B_NO_MEMORY;
fssh_memcpy(&newArray[*_count], ¶meter, sizeof(struct fssh_driver_parameter));
newParameter = &newArray[*_count];
*_parameters = newArray;
(*_count)++;
if (**_pos == '{') {
(*_pos)++;
status = parse_parameters(&newParameter->parameters,
&newParameter->parameter_count, _pos, level + 1);
if (status < FSSH_B_OK)
return status;
}
}
if ((**_pos == '}' && level > 0)
|| (**_pos == '\0' && level == 0)) {
(*_pos)++;
return FSSH_B_OK;
}
if (**_pos == '}' || **_pos == '\0')
return FSSH_B_ERROR;
}
}
static fssh_status_t
parse_settings(settings_handle *handle)
{
char *text = handle->text;
fssh_memset(&handle->settings, 0, sizeof(struct fssh_driver_settings));
if (text == NULL)
return FSSH_B_OK;
return parse_parameters(&handle->settings.parameters,
&handle->settings.parameter_count, &text, 0);
}
static void
free_parameter(struct fssh_driver_parameter *parameter)
{
int32_t 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_t 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;
fssh_strlcpy(handle->name, driverName, sizeof(handle->name));
if (parse_settings(handle) == FSSH_B_OK)
return handle;
free(handle);
return NULL;
}
static settings_handle *
load_driver_settings_from_file(int file, const char *driverName)
{
struct fssh_stat stat;
if (fssh_fstat(file, &stat) < FSSH_B_OK)
return NULL;
if (stat.fssh_st_size > FSSH_B_OK && stat.fssh_st_size < MAX_SETTINGS_SIZE) {
char *text = (char *)malloc(stat.fssh_st_size + 1);
if (text != NULL && fssh_read(file, text, stat.fssh_st_size) == stat.fssh_st_size) {
settings_handle *handle;
text[stat.fssh_st_size] = '\0';
handle = new_settings(text, driverName);
if (handle != NULL) {
return handle;
}
}
free(text);
}
return NULL;
}
static bool
put_string(char **_buffer, fssh_ssize_t *_bufferSize, char *string)
{
fssh_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, fssh_ssize_t *_bufferSize, const char *chars)
{
char *buffer = *_buffer;
fssh_size_t length;
if (chars == NULL)
return true;
length = fssh_strlen(chars);
*_bufferSize -= length;
if (*_bufferSize <= 0)
return false;
fssh_memcpy(buffer, chars, length);
buffer += length;
buffer[0] = '\0';
*_buffer = buffer;
return true;
}
static bool
put_char(char **_buffer, fssh_ssize_t *_bufferSize, char c)
{
char *buffer = *_buffer;
*_bufferSize -= 1;
if (*_bufferSize <= 0)
return false;
buffer[0] = c;
buffer[1] = '\0';
*_buffer = buffer + 1;
return true;
}
static void
put_level_space(char **_buffer, fssh_ssize_t *_bufferSize, int32_t level)
{
while (level-- > 0)
put_char(_buffer, _bufferSize, '\t');
}
static void
put_parameter(char **_buffer, fssh_ssize_t *_bufferSize,
struct fssh_driver_parameter *parameter, int32_t level, bool flat)
{
int32_t 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");
}
}
static settings_handle *
find_driver_settings(const char *name)
{
settings_handle *handle = NULL;
FSSH_ASSERT_LOCKED_MUTEX(&sLock);
while ((handle = (settings_handle*)list_get_next_item(&sHandles, handle)) != NULL) {
if (!fssh_strcmp(handle->name, name))
return handle;
}
return NULL;
}
namespace FSShell {
fssh_status_t
driver_settings_init()
{
fssh_mutex_init(&sLock, "driver settings");
return FSSH_B_OK;
}
}
fssh_status_t
fssh_unload_driver_settings(void *handle)
{
if (!check_handle(handle))
return FSSH_B_BAD_VALUE;
#if 0
fssh_mutex_lock(&sLock);
if (--handle->ref_count == 0) {
list_remove_link(&handle->link);
} else
handle = NULL;
fssh_mutex_unlock(&sLock);
#endif
if (handle != NULL)
free_settings((settings_handle*)handle);
return FSSH_B_OK;
}
void *
fssh_load_driver_settings(const char *driverName)
{
settings_handle *handle;
int file = -1;
if (driverName == NULL)
return NULL;
fssh_mutex_lock(&sLock);
handle = find_driver_settings(driverName);
if (handle != NULL) {
handle->ref_count++;
if (handle->magic != SETTINGS_MAGIC) {
handle->magic = SETTINGS_MAGIC;
if (parse_settings(handle) != FSSH_B_OK) {
free(handle->text);
handle->text = NULL;
handle = NULL;
}
}
fssh_mutex_unlock(&sLock);
return handle;
}
if (driverName[0] != '/') {
char path[FSSH_B_FILE_NAME_LENGTH + 64];
fssh_strcpy(path, "/boot/home/config/settings/fs_shell");
{
fssh_strlcat(path, SETTINGS_DIRECTORY, sizeof(path));
fssh_strlcat(path, driverName, sizeof(path));
}
file = fssh_open(path, FSSH_O_RDONLY);
} else
file = fssh_open(driverName, FSSH_O_RDONLY);
if (file < FSSH_B_OK) {
fssh_mutex_unlock(&sLock);
return NULL;
}
handle = load_driver_settings_from_file(file, driverName);
if (handle != NULL)
list_add_item(&sHandles, handle);
fssh_mutex_unlock(&sLock);
fssh_close(file);
return (void *)handle;
}
#if 0
void *
fssh_load_driver_settings_from_path(const char *path)
{
settings_handle *handle;
int file;
if (path == NULL)
return NULL;
file = fssh_open(path, FSSH_O_RDONLY);
if (file < FSSH_B_OK)
return NULL;
handle = load_driver_settings_from_file(file);
fssh_close(file);
return (void *)handle;
}
#endif
void *
fssh_parse_driver_settings_string(const char *settingsString)
{
char *text = fssh_strdup(settingsString);
if (settingsString == NULL || text != NULL) {
settings_handle *handle = (settings_handle*)malloc(sizeof(settings_handle));
if (handle != NULL) {
handle->magic = SETTINGS_MAGIC;
handle->text = text;
if (parse_settings(handle) == FSSH_B_OK)
return handle;
free(handle);
}
free(text);
}
return NULL;
}
fssh_status_t
fssh_get_driver_settings_string(void *_handle, char *buffer,
fssh_ssize_t *_bufferSize, bool flat)
{
settings_handle *handle = (settings_handle *)_handle;
fssh_ssize_t bufferSize = *_bufferSize;
int32_t i;
if (!check_handle(handle) || !buffer || *_bufferSize == 0)
return FSSH_B_BAD_VALUE;
for (i = 0; i < handle->settings.parameter_count; i++) {
put_parameter(&buffer, &bufferSize, &handle->settings.parameters[i],
0, flat);
}
*_bufferSize -= bufferSize;
return bufferSize >= 0 ? FSSH_B_OK : FSSH_B_BUFFER_OVERFLOW;
}
bool
fssh_get_driver_boolean_parameter(void *handle, const char *keyName,
bool unknownValue, bool noArgValue)
{
fssh_driver_parameter *parameter;
char *boolean;
if (!check_handle(handle))
return unknownValue;
if ((parameter = get_parameter((settings_handle*)handle, keyName)) == NULL)
return unknownValue;
if (parameter->value_count <= 0)
return noArgValue;
boolean = parameter->values[0];
if (!fssh_strcmp(boolean, "1")
|| !fssh_strcasecmp(boolean, "true")
|| !fssh_strcasecmp(boolean, "yes")
|| !fssh_strcasecmp(boolean, "on")
|| !fssh_strcasecmp(boolean, "enable")
|| !fssh_strcasecmp(boolean, "enabled"))
return true;
if (!fssh_strcmp(boolean, "0")
|| !fssh_strcasecmp(boolean, "false")
|| !fssh_strcasecmp(boolean, "no")
|| !fssh_strcasecmp(boolean, "off")
|| !fssh_strcasecmp(boolean, "disable")
|| !fssh_strcasecmp(boolean, "disabled"))
return false;
return unknownValue;
}
const char *
fssh_get_driver_parameter(void *handle, const char *keyName,
const char *unknownValue, const char *noArgValue)
{
struct fssh_driver_parameter *parameter;
if (!check_handle(handle))
return unknownValue;
if ((parameter = get_parameter((settings_handle*)handle, keyName)) == NULL)
return unknownValue;
if (parameter->value_count <= 0)
return noArgValue;
return parameter->values[0];
}
const fssh_driver_settings *
fssh_get_driver_settings(void *handle)
{
if (!check_handle(handle))
return NULL;
return &((settings_handle *)handle)->settings;
}