blob: 731e86b8a849296ab12154bf1a07cce0b411a5af [file]
/* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version
* 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* etch_config.c
* config items and config file parse.
* config file is formatted as a java-style properties file expected as ansi or utf-8.
*/
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <ctype.h>
#pragma warning(disable:4996) /* "unsafe" function warning */
#include "etch_config.h"
#include "etchlog.h"
char* ETCHCFIG = "CFIG";
#define ETCHMAXCONFIGLINESIZE 240
#define ETCHCONFIG_TOKEN_FOUND 0
#define ETCHCONFIG_LINE_IS_BLANK_OR_COMMENT 1
#define ETCHCONFIG_DATATYPE_INT 1
#define ETCHCONFIG_DATATYPE_FIXED 2
#define ETCHCONFIG_DATATYPE_CHAR 3
#define NOTWHITESPACE(p) (*p != ' ' && *p != '\t')
#define ISWHITESPACE(p) (*p == ' ' || *p == '\t')
#define NOTENDOFLINE(p) (*p != '\0' && *p != '\n' && *p != '\r')
#define ISENDOFLINE(p) (*p == '\0' || *p == '\n' || *p == '\r')
#define ETCHSERVERDEFAULTCONFIGFILEPATH "../etch-c-server.properties"
#define ETCHCLIENTDEFAULTCONFIGFILEPATH "../etch-c-client.properties"
typedef struct ndx {int id; char* key;} ndx;
typedef struct etchconfig_work
{ int keycount, item, datatype, length, value, is_bool;
char *beg, *end, *endobj, *begkey, *equal, *begval, *endval;
double fixedval;
} etchconfig_work;
int etchconfig_identify_value (etchconfig_work*);
int etchconfig_assign_value (const etchconfig_work*);
int etchconfig_identify_item (etchconfig_work*, char* line, ndx* keys);
/**
* get_etch_server_configfile_path()
* return the path to the server config file.
*/
char* get_etch_server_configfile_path()
{
static char* path = ETCHSERVERDEFAULTCONFIGFILEPATH;
return path;
}
/**
* get_etch_client_configfile_path()
* return the path to the client config file.
*/
char* get_etch_client_configfile_path()
{
static char* path = ETCHCLIENTDEFAULTCONFIGFILEPATH;
return path;
}
/**
* open_etch_configfile()
*/
int open_etch_configfile (const int is_client, FILE** f)
{
int result = 0;
char* path = is_client? get_etch_client_configfile_path(): get_etch_server_configfile_path();
if (NULL == (*f = fopen(path, "r")))
{ path = is_client? ETCHCLIENTDEFAULTCONFIGFILEPATH: ETCHSERVERDEFAULTCONFIGFILEPATH;
if (NULL == (*f = fopen(path, "r")))
{ etchlog(ETCHCFIG, ETCHLOG_WARNING, "could not open config %s - using defaults\n", path);
result = -1;
}
}
return result;
}
/**
* etch_reset_config()
* initialize global config items with etch defaults.
* @remarks whenever a new config item is added, a setter for it must be coded
* herein in order to ensure that a default value exists for the item, and thus
* it will have an appropriate value if either no config file entry is coded for
* it, or if a config file can't be located and read.
*/
int etch_reset_config (const int is_client)
{
memset(&config, 0, sizeof(struct etch_config));
config.memory_watch_id = 0;
config.log_level = ETCHCONFIGDEF_LOG_LEVEL;
config.loglevel = get_etch_loglevel(ETCHCONFIGDEF_LOG_LEVEL);
config.max_log_files = ETCHCONFIGDEF_MAX_LOGFILES;
config.max_log_lines = ETCHCONFIGDEF_MAX_LOGLINES;
config.is_validate_on_read = ETCHCONFIGDEF_IS_VALIDATE_ON_READ;
config.is_validate_on_write = ETCHCONFIGDEF_IS_VALIDATE_ON_WRITE;
config.is_log_to_file = ETCHCONFIGDEF_IS_LOG_TO_FILE;
config.is_log_to_console = ETCHCONFIGDEF_IS_LOG_TO_CONSOLE;
config.is_log_memory_leak_detail = ETCHCONFIGDEF_IS_LOG_MEMORY_LEAK_DETAIL;
config.is_destroy_messages_with_mailbox = ETCHCONFIGDEF_IS_DESTROY_MESSAGES_WITH_MAILBOX;
config.default_mailbox_read_waitms = ETCHCONFIGDEF_DEFAULT_MAILBOX_READ_WAITMS;
config.sleepSecondsPriorClientExit = ETCHCONFIGDEF_SLEEP_SECONDS_PRIOR_CLIENT_EXIT;
strncpy(config.example_string, ETCHCONFIGDEF_EXAMPLE_STRING_VALUE, ETCHCONFIGDEF_EXAMPLE_STRING_MAXLEN);
return 0;
}
/**
* etch_read_configfile()
* read and validate config file, populate global config items.
* @param is_client boolean indicating if reading config on client side.
* @param is_reset boolean indicating if global config items should be reset
* to default values prior to reading the config file.
* @return 0 success, -1 failure.
* @remarks whenever a new config item is added, a mapping from that item's
* key to its external config file name must be coded herein.
*/
int etch_read_configfile (const int is_client, const int is_reset)
{
int result = 0, linenum = 0, itemcount = 0;
char line [ETCHMAXCONFIGLINESIZE+1];
etchconfig_work work;
FILE* f = 0;
ndx config_keys[] =
{ {ETCHCONFIGKEY_LOG_LEVEL, "logLevel"},
{ETCHCONFIGKEY_MAX_LOGFILES, "maxLogFiles"},
{ETCHCONFIGKEY_MAX_LOGLINES, "maxLogLines"},
{ETCHCONFIGKEY_ETCH_WATCH_ID, "memoryWatchID"},
{ETCHCONFIGKEY_IS_LOG_TO_FILE, "isLogToFile"},
{ETCHCONFIGKEY_IS_LOG_TO_CONSOLE, "isLogToConsole"},
{ETCHCONFIGKEY_IS_VALIDATE_ON_READ, "isValidateOnRead"},
{ETCHCONFIGKEY_IS_VALIDATE_ON_WRITE, "isValidateOnWrite"},
{ETCHCONFIGKEY_IS_DESTROY_MESSAGES_WITH_MAILBOX, "isDestroyMessagesWithMailbox"},
{ETCHCONFIGKEY_IS_DISPLAY_MEMORY_LEAK_DETAIL, "isDisplayMemoryLeakDetail"},
{ETCHCONFIGKEY_DEFAULT_MAILBOX_READ_WAITMS , "defaultMailboxReadWaitms"},
{ETCHCONFIGKEY_SLEEP_SECONDS_PRIOR_CLIENT_EXIT, "sleepSecondsPriorClientExit"},
{ETCHCONFIGKEY_TEST_STRING, "testString"}, /* for testing only */
{ETCHCONFIGKEY_TEST_INT, "testInt"}, /* for testing only */
{ETCHCONFIGKEY_TEST_BOOL, "testBool"}, /* for testing only */
{ETCHCONFIGKEY_TEST_FIXED, "testFixed"}, /* for testing only */
};
memset(&work, 0, sizeof(etchconfig_work));
work.keycount = sizeof(config_keys) / sizeof(ndx);
if (is_reset) /* reset config entries to etch defaults */
etch_reset_config (is_client);
if (-1 == open_etch_configfile (is_client, &f)) /* open config file */
return -1;
while(1) /* while not eof on config file ... */
{
if (NULL == fgets (line, ETCHMAXCONFIGLINESIZE, f)) break;
work.item = 0;
linenum++;
result = etchconfig_identify_item (&work, line, config_keys);
if (ETCHCONFIG_LINE_IS_BLANK_OR_COMMENT == result) continue;
if (-1 == result)
{ etchlog(ETCHCFIG, ETCHLOG_ERROR, "at line %d: name not recognized\n", linenum);
continue;
}
if((-1 == etchconfig_identify_value(&work)) || (-1 == etchconfig_assign_value(&work)))
{
etchlog(ETCHCFIG, ETCHLOG_ERROR, "at line %d: invalid item value\n", linenum);
continue;
}
if (*work.endval == ';') /* warning that a semicolon may have been typed in error */
etchlog(ETCHCFIG, ETCHLOG_WARNING, "at line %d: value ends with semicolon\n", linenum);
itemcount++;
}
fclose(f);
etchlog(ETCHCFIG, ETCHLOG_DEBUG, "%d config entries read\n", itemcount);
return itemcount;
}
/**
* etchconfig_identify_item()
* parse out and identify object token
*/
int etchconfig_identify_item (etchconfig_work* work, char* line, ndx* keys)
{
int i;
ndx *keyi = keys;
char csave, *q, *p = line;
while(ISWHITESPACE(p)) p++; /* find first noblank character */
if (ISENDOFLINE(p)) return ETCHCONFIG_LINE_IS_BLANK_OR_COMMENT;
work->beg = p; /* mark initial character */
while(*p && *p != '#' && *p != '=') p++; /* find equal sign */
if (*p == '#') return ETCHCONFIG_LINE_IS_BLANK_OR_COMMENT;
if (ISENDOFLINE(p)) return -1;
work->equal = p; /* mark equal sign */
q = work->equal; q--;
while(ISWHITESPACE(q)) q--; /* strip trailing whitespace */
work->end = q; /* mark final character */
q++; /* point after item name */
csave = *q; /* save character at that position */
*q = '\0'; /* and terminate item name string */
for(i = 0, work->item = 0; i < (const int) work->keycount; i++, keyi++)
{ /* compare found item name with expected item names */
if (stricmp(keyi->key, work->beg) == 0)
{ work->item = keyi->id;
break;
}
}
*q = csave; /* restore character replaced above */
return work->item? ETCHCONFIG_TOKEN_FOUND: -1;
}
/**
* etchconfig_identify_item()
* parse out item value and determine its data type.
*/
int etchconfig_identify_value (etchconfig_work* work)
{
char *p = NULL, *q = NULL;
int i = 0, digits = 0, expecteddigits = 0, dots = 0, isnegative = 0;
work->length = work->datatype = work->is_bool = 0;
work->begval = work->endval = NULL;
p = work->equal; p++; /* start after equal sign */
while(*p && ISWHITESPACE(p)) p++; /* find first noblank character */
if (ISENDOFLINE(p) || *p == '#') return -1;
q = p;
while(NOTENDOFLINE(q) && *q != '#') q++; /* find end of line or comment */
q--;
while(ISWHITESPACE(q)) q--; /* strip trailing whitespace */
/* calculate length of value */
work->length = (int) ((size_t) q - (size_t) p + 1);
if (work->length < 1) return -1;
if (*p == '\"') /* if quoted string ... */
{ if (*q != '\"') return -1; /* test for close quote */
p++; q--; /* remove quotes */
work->datatype = ETCHCONFIG_DATATYPE_CHAR;
}
work->begval = p; /* set value boundaries */
work->endval = q;
*++q = '\0'; /* add terminator for logging */
if (work->datatype) /* data type now known? */
return ETCHCONFIG_TOKEN_FOUND;
/* determine data type of value */
expecteddigits = work->length;
for(; i < (const int) work->length; i++, p++)
{
if (isdigit(*p))
digits++;
else
if (*p == '-' && i == 0) /* negative sign? */
isnegative = TRUE;
else /* decimal point? */
if (*p == '.')
dots++;
}
if (digits > 0)
{ if (isnegative) expecteddigits--;
if (dots == 1) expecteddigits--;
}
if (digits == expecteddigits && dots == 0)
{ /* integral value e.g. 314159 */
work->datatype = ETCHCONFIG_DATATYPE_INT;
work->value = atoi(work->begval);
if (work->value == 0 ||work->value == 1)
work->is_bool = TRUE; /* integer is boolean-valued */
}
else
if (digits == expecteddigits && dots == 1)
{ /* fixed decimal value e.g. 3.14159 */
work->datatype = ETCHCONFIG_DATATYPE_FIXED;
work->fixedval = strtod(work->begval, &q);
} /* unquoted string */
else work->datatype = ETCHCONFIG_DATATYPE_CHAR;
return ETCHCONFIG_TOKEN_FOUND;
}
/**
* get_etch_loglevel()
* convert log level from character supplied in config to ordinal used by logger.
*/
int get_etch_loglevel (const unsigned char loglvl)
{
int thislevel = -1;
switch(toupper(loglvl))
{ case 'D': thislevel = ETCHLOG_DEBUG; break;
case 'I': thislevel = ETCHLOG_INFO; break;
case 'X': thislevel = ETCHLOG_XDEBUG; break;
case 'E': thislevel = ETCHLOG_ERROR; break;
case 'W': thislevel = ETCHLOG_WARNING; break;
}
return thislevel;
}
/**
* etchconfig_assign_value()
* edit value from config file and assign to internal config item.
* @remarks whenever a new config item is added, its key, plus code
* to validate the value, must be added to the switch herein.
*/
int etchconfig_assign_value (const etchconfig_work* work)
{
const int datatype = work->datatype;
const int ivalue = work->value, is_bool_value = work->is_bool;
const char* cvalue = work->begval;
int result = 0, n = 0;
switch(work->item)
{
case ETCHCONFIGKEY_LOG_LEVEL:
if (datatype != ETCHCONFIG_DATATYPE_CHAR)
result = -1;
else /* translate character level to ordinal */
if (-1 == (n = get_etch_loglevel (*cvalue)))
result = -1;
else
{ config.loglevel = n; /* ordinal level */
config.log_level = *cvalue; /* character level */
}
break;
case ETCHCONFIGKEY_MAX_LOGFILES:
if (datatype != ETCHCONFIG_DATATYPE_INT) result = -1;
else config.max_log_files = ivalue;
break;
case ETCHCONFIGKEY_MAX_LOGLINES:
if (datatype != ETCHCONFIG_DATATYPE_INT) result = -1;
else config.max_log_lines = ivalue;
break;
case ETCHCONFIGKEY_ETCH_WATCH_ID:
if (datatype != ETCHCONFIG_DATATYPE_INT) result = -1;
else config.memory_watch_id = ivalue;
break;
case ETCHCONFIGKEY_DEFAULT_MAILBOX_READ_WAITMS:
if (datatype != ETCHCONFIG_DATATYPE_INT || ivalue < (-1))
result = -1;
else config.default_mailbox_read_waitms = ivalue;
break;
case ETCHCONFIGKEY_IS_LOG_TO_FILE:
if (!is_bool_value) result = -1;
else config.is_log_to_file = ivalue;
break;
case ETCHCONFIGKEY_IS_LOG_TO_CONSOLE:
if (!is_bool_value) result = -1;
else config.is_log_to_console = ivalue;
break;
case ETCHCONFIGKEY_IS_VALIDATE_ON_READ:
if (!is_bool_value) result = -1;
else config.is_validate_on_read = ivalue;
break;
case ETCHCONFIGKEY_IS_VALIDATE_ON_WRITE:
if (!is_bool_value) result = -1;
else config.is_validate_on_write = ivalue;
break;
case ETCHCONFIGKEY_IS_DESTROY_MESSAGES_WITH_MAILBOX:
if (!is_bool_value) result = -1;
else config.is_destroy_messages_with_mailbox = ivalue;
break;
case ETCHCONFIGKEY_IS_DISPLAY_MEMORY_LEAK_DETAIL:
if (!is_bool_value) result = -1;
else config.is_log_memory_leak_detail = ivalue;
break;
case ETCHCONFIGKEY_SLEEP_SECONDS_PRIOR_CLIENT_EXIT:
if (datatype != ETCHCONFIG_DATATYPE_INT || ivalue < 0)
result = -1;
else config.sleepSecondsPriorClientExit = ivalue;
break;
/* test items for testing config file parsing */
case ETCHCONFIGKEY_TEST_STRING: /* example for a string config entry */
if (datatype != ETCHCONFIG_DATATYPE_CHAR) result = -1;
else if (strlen(cvalue) > ETCHCONFIGDEF_EXAMPLE_STRING_MAXLEN) result = -1;
else strcpy(config.example_string, cvalue);
break;
case ETCHCONFIGKEY_TEST_INT:
if (datatype != ETCHCONFIG_DATATYPE_INT) result = -1;
break;
case ETCHCONFIGKEY_TEST_BOOL:
if (!is_bool_value) result = -1;
break;
case ETCHCONFIGKEY_TEST_FIXED:
if (datatype != ETCHCONFIG_DATATYPE_FIXED) result = -1;
break;
default: result = -1;
}
return result;
}