// Copyright (c) 2010, Aldis Berjoza <aldis@bsdroot.lv>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// 3. Neither the name of the nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "config.h"
#define CONF_LOADABLE_FILE_MAX_SIZE 10240 // 10KB should be nough for config file;
struct s_KeyValue *confLoad(const char *conf_file) { // {{{1
if (conf_file == NULL) {
fprintf(stderr, "NULL pointer to faile name\n");
assert(0);
exit(1);
}
if (strlen(conf_file) == 0) {
fprintf(stderr, "NULL file name provided\n");
assert(0);
exit(1);
}
int file;
if ((file = open(conf_file, O_RDONLY)) == -1) {
perror(NULL);
assert(0);
exit(1);
}
struct stat sb;
if (fstat(file, &sb) == -1) {
perror(NULL);
assert(0);
exit(1);
}
if (fstat(file, &sb) > CONF_LOADABLE_FILE_MAX_SIZE) {
fprintf(stderr, "Config file '%s' is to big. Are you sure it's config file?\n", conf_file);
assert(0);
exit(1);
}
char *buff;
if ((buff = malloc(sb.st_size + 1)) == NULL) {
perror(NULL);
assert(0);
exit(1);
}
buff[sb.st_size + 1] = 0;
int read_bytes = read(file, buff, sb.st_size);
close(file);
enum field_type {wspace1, option, wspace2, wspace3, value, wspace4, comment} field = wspace1;
int i;
int j = 0;
int k = 0;
int line = 1;
const char MSG_SYNTAX_ERROR[] = "Syntax error in configuration file '%s', at line %d\n";
for (i = 0; i < read_bytes; i++) {
switch (field) {
case wspace1:
if (isalpha(buff[i])) {
field = option;
}
else if (buff[i] == '#') {
j++;
field = comment;
}
else if (buff[i] == '\n' || buff[i] == '\r') {
j++;
line++;
}
else if (buff[i] == '\r' && buff[i+1] == '\n') {
j += 2;
i++;
line++;
}
else if (isblank(buff[i])) {
j++;
}
else {
fprintf(stderr, MSG_SYNTAX_ERROR, conf_file, line);
assert(0);
exit(1);
}
break;
case option:
if (isblank(buff[i])) {
j++;
field = wspace2;
}
else if (buff[i] == '=') {
field = wspace3;
}
else if (!(isalnum(buff[i]) || buff[i] == '_')) {
fprintf(stderr, MSG_SYNTAX_ERROR, conf_file, line);
assert(0);
exit(1);
}
break;
case wspace2:
if (isblank(buff[i])) {
j++;
}
else if (buff[i] == '=') {
field = wspace3;
}
else {
fprintf(stderr, MSG_SYNTAX_ERROR, conf_file, line);
assert(0);
exit(1);
}
break;
case wspace3:
if (isblank(buff[i])) {
j++;
}
else if (buff[i] == '"') {
j++;
field = value;
}
else if (buff[i] == '\n' || buff[i] == '\r') {
j++;
line++;
}
else if (buff[i] == '\r' && buff[i+1] == '\n') {
j += 2;
i++;
line++;
}
else {
fprintf(stderr, MSG_SYNTAX_ERROR, conf_file, line);
assert(0);
exit(1);
}
break;
case value:
if (buff[i] == '"') {
buff[i] = 0;
field = wspace4;
}
else if (buff[i] == '\\') {
buff[i-j] = buff[i+1];
j++;
i++;
}
else if (buff[i] == '\n' || buff[i] == '\r') {
j++;
line++;
}
else if (buff[i] == '\r' && buff[i+1] == '\n') {
j += 2;
i++;
line++;
}
break;
case wspace4:
if (isblank(buff[i])) {
j++;
}
else if (buff[i] == ';') {
j++;
field = wspace1;
}
else if (buff[i] == '\n' || buff[i] == '\r') {
j++;
line++;
}
else if (buff[i] == '\r' && buff[i+1] == '\n') {
j += 2;
i++;
line++;
}
else {
fprintf(stderr, MSG_SYNTAX_ERROR, conf_file, line);
assert(0);
exit(1);
}
break;
default: // comment
if (buff[i] == '\n' || buff[i] == '\r') {
j++;
line++;
field = wspace1;
}
else if (buff[i] == '\r' && buff[i+1] == '\n') {
j += 2;
i++;
line++;
field = wspace1;
}
else {
j++;
}
break;
}
if (j != 0) {
if (j == k) {
buff[i-j] = buff[i];
buff[i] = 0;
}
k = j;
}
}
if (field != wspace1 && field != comment) {
fprintf(stderr, MSG_SYNTAX_ERROR, conf_file, line);
assert(0);
exit(1);
}
struct s_KeyValue *config = NULL;
if ((config = malloc(sizeof(struct s_KeyValue))) == NULL) {
perror(NULL);
assert(0);
exit(1);
}
config->len = i - j + 1;
if ((config->data = malloc(config->len)) == NULL) {
perror(NULL);
assert(0);
exit(1);
}
memcpy(config->data, buff, config->len - 1);
config->data[config->len] = 0;
free(buff);
return config;
}
// 1}}}
char *Value(const char *restrict key, const struct s_KeyValue *restrict kv_ptr) { // {{{1
if (key == NULL) {
fprintf(stderr, "Invalid pointer to key\n");
assert(0);
exit(1);
}
if (strlen(key) == 0) {
fprintf(stderr, "Invalid option name\n");
assert(0);
exit(1);
}
if (kv_ptr == NULL) return NULL;
int l = strlen(key);
for (int i = 0; i < kv_ptr->len; i++) {
if (key[0] == (kv_ptr->data)[i]) {
int found = 1;
for (int j = 1; j < l; j++) {
if (key[j] != (kv_ptr->data)[i+j]) {
found = 0;
break;
}
}
if (found == 1 && (kv_ptr->data)[i+l] == '=') return kv_ptr->data + i + l + 1;
}
}
fprintf(stderr, "Option '%s' not found\n", key);
return NULL;
}
// 1}}}
void KeyValueFree(struct s_KeyValue *kv_ptr) { // {{{1
if (kv_ptr != NULL) {
free(kv_ptr->data);
free(kv_ptr);
}
}
// 1}}}
// vim:tabstop=4:shiftwidth=4