#include "out.h"
#include "obj.h"
#include "ctl.h"
#include "os.h"
#define CTL_MAX_ENTRIES 100
#define MAX_CONFIG_FILE_LEN (1 << 20)
#define CTL_STRING_QUERY_SEPARATOR ";"
#define CTL_NAME_VALUE_SEPARATOR "="
#define CTL_QUERY_NODE_SEPARATOR "."
#define CTL_VALUE_ARG_SEPARATOR ","
static int ctl_global_first_free = 0;
static struct ctl_node CTL_NODE(global)[CTL_MAX_ENTRIES];
struct ctl {
struct ctl_node root[CTL_MAX_ENTRIES];
int first_free;
};
static struct ctl_node *
ctl_find_node(struct ctl_node *nodes, const char *name,
struct ctl_indexes *indexes)
{
LOG(3, "nodes %p name %s indexes %p", nodes, name, indexes);
struct ctl_node *n = NULL;
char *sptr = NULL;
char *parse_str = Strdup(name);
if (parse_str == NULL)
return NULL;
char *node_name = strtok_r(parse_str, CTL_QUERY_NODE_SEPARATOR, &sptr);
while (node_name != NULL) {
char *endptr;
int tmp_errno = errno;
long index_value = strtol(node_name, &endptr, 0);
errno = tmp_errno;
struct ctl_index *index_entry = NULL;
if (endptr != node_name) {
index_entry = Malloc(sizeof(*index_entry));
if (index_entry == NULL)
goto error;
index_entry->value = index_value;
SLIST_INSERT_HEAD(indexes, index_entry, entry);
}
for (n = &nodes[0]; n->name != NULL; ++n) {
if (index_entry && n->type == CTL_NODE_INDEXED)
break;
else if (strcmp(n->name, node_name) == 0)
break;
}
if (n->name == NULL)
goto error;
if (index_entry)
index_entry->name = n->name;
nodes = n->children;
node_name = strtok_r(NULL, CTL_QUERY_NODE_SEPARATOR, &sptr);
}
Free(parse_str);
return n;
error:
Free(parse_str);
return NULL;
}
static void
ctl_delete_indexes(struct ctl_indexes *indexes)
{
while (!SLIST_EMPTY(indexes)) {
struct ctl_index *index = SLIST_FIRST(indexes);
SLIST_REMOVE_HEAD(indexes, entry);
Free(index);
}
}
static void *
ctl_parse_args(struct ctl_argument *arg_proto, char *arg)
{
ASSERTne(arg, NULL);
char *dest_arg = Malloc(arg_proto->dest_size);
if (dest_arg == NULL)
return NULL;
char *sptr = NULL;
char *arg_sep = strtok_r(arg, CTL_VALUE_ARG_SEPARATOR, &sptr);
for (struct ctl_argument_parser *p = arg_proto->parsers;
p->parser != NULL; ++p) {
ASSERT(p->dest_offset + p->dest_size <= arg_proto->dest_size);
if (arg_sep == NULL)
goto error_parsing;
if (p->parser(arg_sep, dest_arg + p->dest_offset,
p->dest_size) != 0)
goto error_parsing;
arg_sep = strtok_r(NULL, CTL_VALUE_ARG_SEPARATOR, &sptr);
}
return dest_arg;
error_parsing:
Free(dest_arg);
return NULL;
}
static void *
ctl_query_get_real_args(struct ctl_node *n, void *write_arg,
enum ctl_query_source source)
{
void *real_arg = NULL;
switch (source) {
case CTL_QUERY_CONFIG_INPUT:
real_arg = ctl_parse_args(n->arg, write_arg);
break;
case CTL_QUERY_PROGRAMMATIC:
real_arg = write_arg;
break;
default:
ASSERT(0);
break;
}
return real_arg;
}
static void
ctl_query_cleanup_real_args(struct ctl_node *n, void *real_arg,
enum ctl_query_source source)
{
switch (source) {
case CTL_QUERY_CONFIG_INPUT:
Free(real_arg);
break;
case CTL_QUERY_PROGRAMMATIC:
break;
default:
ASSERT(0);
break;
}
}
static int
ctl_exec_query_read(PMEMobjpool *pop, struct ctl_node *n,
enum ctl_query_source source, void *arg, struct ctl_indexes *indexes)
{
if (arg == NULL) {
ERR("read queries require non-NULL argument");
errno = EINVAL;
return -1;
}
return n->cb[CTL_QUERY_READ](pop, source, arg, indexes);
}
static int
ctl_exec_query_write(PMEMobjpool *pop, struct ctl_node *n,
enum ctl_query_source source, void *arg, struct ctl_indexes *indexes)
{
if (arg == NULL) {
ERR("write queries require non-NULL argument");
errno = EINVAL;
return -1;
}
void *real_arg = ctl_query_get_real_args(n, arg, source);
if (real_arg == NULL) {
errno = EINVAL;
ERR("invalid arguments");
return -1;
}
int ret = n->cb[CTL_QUERY_WRITE](pop, source, real_arg, indexes);
ctl_query_cleanup_real_args(n, real_arg, source);
return ret;
}
static int
ctl_exec_query_runnable(PMEMobjpool *pop, struct ctl_node *n,
enum ctl_query_source source, void *arg, struct ctl_indexes *indexes)
{
return n->cb[CTL_QUERY_RUNNABLE](pop, source, arg, indexes);
}
static int (*ctl_exec_query[MAX_CTL_QUERY_TYPE])(PMEMobjpool *pop,
struct ctl_node *n, enum ctl_query_source source, void *arg,
struct ctl_indexes *indexes) = {
ctl_exec_query_read,
ctl_exec_query_write,
ctl_exec_query_runnable,
};
static int
ctl_query(PMEMobjpool *pop, enum ctl_query_source source,
const char *name, enum ctl_query_type type, void *arg)
{
LOG(3, "pop %p source %d name %s", pop, source, name);
if (name == NULL) {
ERR("invalid query");
errno = EINVAL;
return -1;
}
struct ctl_indexes indexes;
SLIST_INIT(&indexes);
int ret = -1;
struct ctl_node *n = ctl_find_node(CTL_NODE(global),
name, &indexes);
if (n == NULL && pop) {
ctl_delete_indexes(&indexes);
n = ctl_find_node(pop->ctl->root, name, &indexes);
}
if (n == NULL || n->type != CTL_NODE_LEAF || n->cb[type] == NULL) {
ERR("invalid query entry point %s", name);
errno = EINVAL;
goto out;
}
ret = ctl_exec_query[type](pop, n, source, arg, &indexes);
out:
ctl_delete_indexes(&indexes);
return ret;
}
#ifndef _WIN32
static inline
#endif
int
pmemobj_ctl_getU(PMEMobjpool *pop, const char *name, void *arg)
{
LOG(3, "pop %p name %s arg %p", pop, name, arg);
return ctl_query(pop, CTL_QUERY_PROGRAMMATIC,
name, CTL_QUERY_READ, arg);
}
#ifndef _WIN32
static inline
#endif
int
pmemobj_ctl_setU(PMEMobjpool *pop, const char *name, void *arg)
{
LOG(3, "pop %p name %s arg %p", pop, name, arg);
return ctl_query(pop, CTL_QUERY_PROGRAMMATIC,
name, CTL_QUERY_WRITE, arg);
}
#ifndef _WIN32
static inline
#endif
int
pmemobj_ctl_execU(PMEMobjpool *pop, const char *name, void *arg)
{
LOG(3, "pop %p name %s arg %p", pop, name, arg);
return ctl_query(pop, CTL_QUERY_PROGRAMMATIC,
name, CTL_QUERY_RUNNABLE, arg);
}
#ifndef _WIN32
int
pmemobj_ctl_get(PMEMobjpool *pop, const char *name, void *arg)
{
return pmemobj_ctl_getU(pop, name, arg);
}
int
pmemobj_ctl_set(PMEMobjpool *pop, const char *name, void *arg)
{
return pmemobj_ctl_setU(pop, name, arg);
}
int
pmemobj_ctl_exec(PMEMobjpool *pop, const char *name, void *arg)
{
return pmemobj_ctl_execU(pop, name, arg);
}
#else
int
pmemobj_ctl_getW(PMEMobjpool *pop, const wchar_t *name, void *arg)
{
char *uname = util_toUTF8(name);
if (uname == NULL)
return -1;
int ret = pmemobj_ctl_getU(pop, uname, arg);
util_free_UTF8(uname);
return ret;
}
int
pmemobj_ctl_setW(PMEMobjpool *pop, const wchar_t *name, void *arg)
{
char *uname = util_toUTF8(name);
if (uname == NULL)
return -1;
int ret = pmemobj_ctl_setU(pop, uname, arg);
util_free_UTF8(uname);
return ret;
}
int
pmemobj_ctl_execW(PMEMobjpool *pop, const wchar_t *name, void *arg)
{
char *uname = util_toUTF8(name);
if (uname == NULL)
return -1;
int ret = pmemobj_ctl_execU(pop, uname, arg);
util_free_UTF8(uname);
return ret;
}
#endif
void
ctl_register_module_node(struct ctl *c, const char *name, struct ctl_node *n)
{
struct ctl_node *nnode = c == NULL ?
&CTL_NODE(global)[ctl_global_first_free++] :
&c->root[c->first_free++];
nnode->children = n;
nnode->type = CTL_NODE_NAMED;
nnode->name = name;
}
static int
ctl_parse_query(char *qbuf, char **name, char **value)
{
if (qbuf == NULL)
return 1;
char *sptr;
*name = strtok_r(qbuf, CTL_NAME_VALUE_SEPARATOR, &sptr);
if (*name == NULL)
return -1;
*value = strtok_r(NULL, CTL_NAME_VALUE_SEPARATOR, &sptr);
if (*value == NULL)
return -1;
char *extra = strtok_r(NULL, CTL_NAME_VALUE_SEPARATOR, &sptr);
if (extra != NULL)
return -1;
return 0;
}
static int
ctl_load_config(PMEMobjpool *pop, char *buf)
{
int r = 0;
char *sptr = NULL;
char *name;
char *value;
ASSERTne(buf, NULL);
char *qbuf = strtok_r(buf, CTL_STRING_QUERY_SEPARATOR, &sptr);
do {
r = ctl_parse_query(qbuf, &name, &value);
if (r == 0)
r = ctl_query(pop, CTL_QUERY_CONFIG_INPUT,
name, CTL_QUERY_WRITE, value);
if (r == -1) {
ERR("failed to parse query %s", qbuf);
return -1;
}
qbuf = strtok_r(NULL, CTL_STRING_QUERY_SEPARATOR, &sptr);
} while (r == 0 && qbuf != NULL);
return r >= 0 ? 0 : -1;
}
int
ctl_load_config_from_string(PMEMobjpool *pop, const char *cfg_string)
{
LOG(3, "pop %p cfg_string \"%s\"", pop, cfg_string);
char *buf = Strdup(cfg_string);
if (buf == NULL) {
ERR("!Strdup");
return -1;
}
int ret = ctl_load_config(pop, buf);
Free(buf);
return ret;
}
int
ctl_load_config_from_file(PMEMobjpool *pop, const char *cfg_file)
{
LOG(3, "pop %p cfg_file \"%s\"", pop, cfg_file);
int ret = -1;
FILE *fp = os_fopen(cfg_file, "r");
if (fp == NULL)
return ret;
int err;
if ((err = fseek(fp, 0, SEEK_END)) != 0)
goto error_file_parse;
long fsize = ftell(fp);
if (fsize == -1)
goto error_file_parse;
if (fsize > MAX_CONFIG_FILE_LEN) {
ERR("Config file too large");
goto error_file_parse;
}
if ((err = fseek(fp, 0, SEEK_SET)) != 0)
goto error_file_parse;
char *buf = Zalloc((size_t)fsize + 1);
if (buf == NULL) {
ERR("!Zalloc");
goto error_file_parse;
}
size_t bufpos = 0;
int c;
int is_comment_section = 0;
while ((c = fgetc(fp)) != EOF) {
if (c == '#')
is_comment_section = 1;
else if (c == '\n')
is_comment_section = 0;
else if (!is_comment_section && !isspace(c))
buf[bufpos++] = (char)c;
}
ret = ctl_load_config(pop, buf);
Free(buf);
error_file_parse:
(void) fclose(fp);
return ret;
}
struct ctl *
ctl_new(void)
{
struct ctl *c = Zalloc(sizeof(struct ctl));
if (c == NULL)
return NULL;
c->first_free = 0;
return c;
}
void
ctl_delete(struct ctl *c)
{
Free(c);
}
static long long
ctl_parse_ll(const char *str)
{
char *endptr;
int olderrno = errno;
errno = 0;
long long val = strtoll(str, &endptr, 0);
if (endptr == str || errno != 0)
return LLONG_MIN;
errno = olderrno;
return val;
}
int
ctl_arg_boolean(const void *arg, void *dest, size_t dest_size)
{
int *intp = dest;
char in = ((char *)arg)[0];
if (tolower(in) == 'y' || in == '1') {
*intp = 1;
return 0;
} else if (tolower(in) == 'n' || in == '0') {
*intp = 0;
return 0;
}
return -1;
}
int
ctl_arg_integer(const void *arg, void *dest, size_t dest_size)
{
long long val = ctl_parse_ll(arg);
if (val == LLONG_MIN)
return -1;
switch (dest_size) {
case sizeof(int):
if (val > INT_MAX || val < INT_MIN)
return -1;
*(int *)dest = (int)val;
break;
case sizeof(long long):
*(long long *)dest = val;
break;
case sizeof(uint8_t):
if (val > UINT8_MAX || val < 0)
return -1;
*(uint8_t *)dest = (uint8_t)val;
break;
}
return 0;
}
int
ctl_arg_string(const void *arg, void *dest, size_t dest_size)
{
if (strnlen(arg, dest_size) == dest_size)
return -1;
strncpy(dest, arg, dest_size);
return 0;
}