/* * Yet another daemon library especially designed to be used * with libsxmp based daemons. * * (c) Alexander Vdolainen 2016 * * libsxmp is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * libsxmp is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see ."; * */ #include #include #include #include #include #include #include #include #include #define _MAX_GROUPS 256 #define _MAX_VALUES 4096 /* * TODO: * - Optimize where it possible */ static long __cmp_strs(const void *a, const void *b) { return strcmp((const char *)a, (const char *)b); } static void __destroy_val(ydc_conf_val_t *val) { if(val->on_eventv != NULL) val->on_eventv(val, NULL, NULL, YC_EVENT_DESTROY); /* ok, let's begin to free all memory */ if(val->type == SEXPR) sexp_t_deallocate((sexp_t *)val->priv); free(val->value); free(val->name); return; } static int __ydc_conf_get_val_f(ydc_conf_t *c, const char *phvar, ydc_conf_val_t **rval, ydc_pgroup_t **rgrp) { int err = 0; int len = 0; usrtc_t *ptree = &(c->p_groups), *tree = NULL; usrtc_node_t *node; char *group_name = NULL, *var_name = NULL; ydc_conf_val_t *val = NULL; ydc_pgroup_t *pgroup = NULL; /* ok, first try to understand what to do */ if(!(var_name = strchr(phvar, '/'))) { err = EINVAL; goto __failure; } else { len = strlen(phvar) - strlen(var_name); if(!(group_name = strndup(phvar, len))) { err = ENOMEM; goto __failure; } if(!(node = usrtc_lookup(ptree, (const void *)group_name))) { err = ENOENT; goto __failure; } else { pgroup = (ydc_pgroup_t *)usrtc_node_getdata(node); pthread_rwlock_rdlock(&(pgroup->val_lock)); } } free(group_name); /* we should know the value name now */ var_name += sizeof(char); tree = &(pgroup->values); if(!(node = usrtc_lookup(tree, (const void *)var_name))) { err = ENOENT; goto __failure_1; } val = (ydc_conf_val_t *)usrtc_node_getdata(node); /* fill this */ if(rval) *rval = val; if(rgrp) *rgrp = pgroup; __failure_1: pthread_rwlock_unlock(&(pgroup->val_lock)); __failure: return err; } int ydc_conf_init(ydc_conf_t *c) { int err = ENOMEM; usrtc_t *tree = &(c->p_groups); /* zero all to keep it simple as possible */ memset(c, 0, sizeof(ydc_conf_t)); /* init locking */ if(pthread_rwlock_init(&(c->grp_lock), NULL)) goto __fail_0; if(pthread_mutex_init(&(c->conf_lock), NULL)) goto __fail_0_1; /* initialize tree structure */ usrtc_init(tree, USRTC_SPLAY, _MAX_GROUPS, __cmp_strs); return 0; __fail_0_1: pthread_rwlock_destroy(&(c->grp_lock)); __fail_0: return err; } int ydc_conf_create_pgroup(ydc_conf_t *c, char *nm) { int err = ENOMEM; ydc_pgroup_t *pg = malloc(sizeof(ydc_pgroup_t)); usrtc_t *tree = NULL, *ptree = &(c->p_groups); usrtc_node_t *node = NULL; /* initialize */ if(!pg) { err = ENOMEM; goto __failure; } else if(!(pg->name = strdup(nm))){ err = ENOMEM; goto __failure_1; } /* check availability */ /* from here we will lock conf */ pthread_rwlock_wrlock(&(c->grp_lock)); if((node = usrtc_lookup(ptree, (const void *)nm))) { err = EEXIST; goto __failure_2; } if(usrtc_count(ptree) >= _MAX_GROUPS) { err = ENOMEM; goto __failure_2; } /* init rwlock */ if(pthread_rwlock_init(&(pg->val_lock), NULL)) { err = ENOMEM; goto __failure_2; } /* init tree structures */ tree = &(pg->values); node = &(pg->unode); usrtc_init(tree, USRTC_SPLAY, _MAX_VALUES, __cmp_strs); usrtc_node_init(node, pg); /* insert */ usrtc_insert(ptree, node, pg->name); pthread_rwlock_unlock(&(c->grp_lock)); return 0; __failure_2: pthread_rwlock_unlock(&(c->grp_lock)); __failure_1: free(pg); __failure: return err; } int ydc_conf_destroy_pgroup(ydc_conf_t *c, char *nm, int force) { int err = 0; usrtc_t *ptree = &(c->p_groups), *tree; usrtc_node_t *node = NULL, *nn = NULL; ydc_pgroup_t *pg = NULL; ydc_conf_val_t *val = NULL; /* take an rwlock for write due to the our sirection to write somethings */ pthread_rwlock_wrlock(&(c->grp_lock)); node = usrtc_lookup(ptree, (const void *)nm); if(!node) { pthread_rwlock_unlock(&(c->grp_lock)); return ENOENT; } else { pg = (ydc_pgroup_t *)usrtc_node_getdata(node); tree = &(pg->values); pthread_rwlock_rdlock(&(pg->val_lock)); } if(!force && usrtc_count(tree)) { pthread_rwlock_unlock(&(c->grp_lock)); pthread_rwlock_unlock(&(pg->val_lock)); return ENOTEMPTY; /* auuhh ... */ } /* let's retake lock */ pthread_rwlock_unlock(&(pg->val_lock)); pthread_rwlock_wrlock(&(pg->val_lock)); /* remove from the tree */ usrtc_delete(ptree, node); pthread_rwlock_unlock(&(c->grp_lock)); for(nn = usrtc_first(tree); nn != NULL; nn = usrtc_first(tree)) { val = usrtc_node_getdata(nn); __destroy_val(val); usrtc_delete(tree, nn); } /* free all */ pthread_rwlock_unlock(&(pg->val_lock)); pthread_rwlock_destroy(&(pg->val_lock)); free(pg->name); free(pg); return err; } int ydc_conf_add_val_p(ydc_conf_t *c, const char *phvar, int cstr) { int err = 0; int len = 0, p1 = 0, p2 = 0; unsigned long siv = 0; unsigned long uiv = 0; usrtc_t *ptree = &(c->p_groups), *tree = NULL; usrtc_node_t *node; char *grp_name, *val_name, *value; ydc_pgroup_t *pg = NULL; ydc_conf_val_t *val = malloc(sizeof(ydc_conf_val_t)), *oldval = NULL; if(!val) return ENOMEM; else memset(val, 0, sizeof(ydc_conf_val_t)); /* take the path, group name first */ if(!(val_name = strchr(phvar, '/'))) { err = EINVAL; goto __failure; } else { grp_name = val_name; len = strlen(phvar) - strlen(grp_name); if(!(grp_name = strndup(phvar, len))) { /* we got it */ err = ENOMEM; goto __failure; } else { /* looking for existence of the group */ pthread_rwlock_rdlock(&(c->grp_lock)); node = usrtc_lookup(ptree, (const void *)grp_name); free(grp_name); pthread_rwlock_unlock(&(c->grp_lock)); if(!node) { err = ENOENT; goto __failure; } else { pg = (ydc_pgroup_t *)usrtc_node_getdata(node); pthread_rwlock_wrlock(&(pg->val_lock)); /* we apologize to change something */ } tree = &(pg->values); /* get the value name */ if(!(value = strchr(val_name, ':'))) { err = EINVAL; goto __failure_0; } else { len = (strlen(val_name) - strlen(value)) - sizeof(char); if(!(val_name = strndup(val_name + sizeof(char), len))) { /* gotcha ! */ err = ENOMEM; goto __failure_0; } else if(!(value = strdup(value + sizeof(char)))) { /* take the value itself */ free(val_name); err = ENOMEM; goto __failure_0; } /* check out if we're have one */ if((node = usrtc_lookup(tree, (const void *)val_name))) { /* oops, then remove */ oldval = (ydc_conf_val_t *)usrtc_node_getdata(node); usrtc_delete(tree, node); } if(usrtc_count(tree) >= _MAX_VALUES) { /* know your limits (c) British ale and beer stickers */ err = ENOMEM; goto __failure_1; } /* ok, here we can create a new value */ /* detect the type */ len = strlen(value); /* first for long values */ if(isdigit(value[0]) || (isdigit(value[1]) && (value[0] == '-'))) { /* looks like a digit */ if(value[0] == '-') { /* signed should be used */ if(len > 9) val->type = LONG; else val->type = INT; } else { if(len > 9) val->type = ULONG; else val->type = UINT; } } else if(value[0] == '(' && value[len - 1] == ')') { /* might be a sexp */ /* first, we must count parentes */ for(grp_name = strchr(value, '('); grp_name != NULL; grp_name = strchr(grp_name + sizeof(char), '(')) p1++; for(grp_name = strchr(value, ')'); grp_name != NULL; grp_name = strchr(grp_name + sizeof(char), ')')) p2++; if(p1 != p2) val->type = STRING; /* looks like string */ else if((grp_name = strchr(value, ' '))) val->type = SEXPR; /* should be at least one space */ else val->type = STRING; } else val->type = STRING; /* if forced cstring */ if(cstr) val->type = STRING; /* let's decide what to do */ switch(val->type) { case STRING: val->len = len + sizeof(char); val->value = (void *)value; break; case INT: case LONG: val->len = sizeof(unsigned long); if(!(val->value = malloc(sizeof(unsigned long)))) { err = ENOMEM; goto __failure_2; } siv = strtoll(value, NULL, 0); *(long *)val->value = siv; #if 0 /* FIXME: */ if((siv != MAXINT64) || (siv != G_MININT64)) { *(gint64 *)val->value = siv; } else { g_free(val->value); err = EINVAL; goto __failure_2; } #endif free(value); break; case UINT: case ULONG: val->len = sizeof(unsigned long); if(!(val->value = malloc(sizeof(unsigned long)))) { err = ENOMEM; goto __failure_2; } uiv = strtoull(value, NULL, 0); *(unsigned long *)val->value = uiv; #if 0 if(uiv != G_MAXUINT64) { *(guint64 *)val->value = uiv; } else { g_free(val->value); err = EINVAL; goto __failure_2; } #endif free(value); break; case SEXPR: val->len = len; val->value = value; if(!(val->priv = (void *)parse_sexp(value, len))) { errno = EINVAL; goto __failure_2; } break; default: /* just to keep gcc happy */ break; } val->name = val_name; /* well, init tree and other stuff */ node = &(val->unode); usrtc_node_init(node, val); usrtc_insert(tree, node, (const void *)val->name); if(oldval) { if(oldval->on_eventv) { oldval->on_eventv(val, oldval, c, YC_EVENT_MODIFY); val->on_eventv = oldval->on_eventv; } __destroy_val(oldval); free(oldval); } } } } pthread_rwlock_unlock(&(pg->val_lock)); return 0; __failure_2: if(oldval) { node = &(oldval->unode); usrtc_insert(tree, node, (const void *)oldval->name); } __failure_1: free(val_name); free(value); __failure_0: pthread_rwlock_unlock(&(pg->val_lock)); __failure: free(val); return err; } int ydc_conf_rm_val(ydc_conf_t *c, const char *phvar) { int err = 0; usrtc_node_t *node; usrtc_t *tree; ydc_conf_val_t *val = NULL; ydc_pgroup_t *pgroup = NULL; pthread_rwlock_rdlock(&(c->grp_lock)); if((err = __ydc_conf_get_val_f(c, phvar, &val, &pgroup))) goto __failure; /* delete it */ pthread_rwlock_wrlock(&(pgroup->val_lock)); tree = &(pgroup->values); node = &(val->unode); usrtc_delete(tree, node); pthread_rwlock_unlock(&(pgroup->val_lock)); __destroy_val(val); free(val); __failure: pthread_rwlock_unlock(&(c->grp_lock)); return err; } int ydc_conf_get_val(ydc_conf_t *c, const char *phvar, ydc_conf_val_t **rval) { int err = 0; ydc_conf_val_t *val = NULL; pthread_rwlock_rdlock(&(c->grp_lock)); if((err = __ydc_conf_get_val_f(c, phvar, &val, NULL))) goto __failure; /* fill this */ *rval = val; __failure: pthread_rwlock_unlock(&(c->grp_lock)); return err; } /* more code, but faster than double search */ void ydc_conf_destroy(ydc_conf_t *c) { usrtc_t *ptree = &(c->p_groups), *tree; usrtc_node_t *node, *nnode; ydc_pgroup_t *grp; ydc_conf_val_t *val; /* lock for all operations */ pthread_mutex_lock(&(c->conf_lock)); pthread_rwlock_wrlock(&(c->grp_lock)); if(!usrtc_count(ptree)) goto __e_out; /* all is clean*/ /* well let's clean all groups */ for(node = usrtc_first(ptree); node != NULL; node = usrtc_first(ptree)) { grp = (ydc_pgroup_t *)usrtc_node_getdata(node); pthread_rwlock_wrlock(&(grp->val_lock)); tree = &(grp->values); if(usrtc_count(tree)) { for(nnode = usrtc_first(tree); nnode != NULL; nnode = usrtc_first(tree)) { val = (ydc_conf_val_t *)usrtc_node_getdata(nnode); __destroy_val(val); usrtc_delete(tree, nnode); free(val); } } usrtc_delete(ptree, node); pthread_rwlock_destroy(&(grp->val_lock)); free(grp->name); free(grp); } __e_out: pthread_mutex_destroy(&(c->conf_lock)); pthread_rwlock_destroy(&(c->grp_lock)); /* secure */ memset(c, 0, sizeof(ydc_conf_t)); free(c); return; } int ydc_conf_val_attach_event(ydc_conf_t *c, const char *phvar, void (*on_eventv)(struct __ym_conf_value *, struct __ym_conf_value *, ydc_conf_t *, ydc_event_t)) { int err = 0; ydc_conf_val_t *val = NULL; ydc_pgroup_t *grp; pthread_rwlock_rdlock(&(c->grp_lock)); if((err = __ydc_conf_get_val_f(c, phvar, &val, &grp))) goto __failure; pthread_rwlock_wrlock(&(grp->val_lock)); /* attach callback */ val->on_eventv = on_eventv; val->on_eventv(val, NULL, c, YC_EVENT_ATTACH); /* yep, it's an event */ pthread_rwlock_unlock(&(grp->val_lock)); __failure: pthread_rwlock_unlock(&(c->grp_lock)); return err; } int ydc_conf_modify_val(ydc_conf_t *c, const char *phvar, void *data, int len) { ydc_conf_val_t *val; ydc_pgroup_t *grp; char *t = NULL; void *tb = NULL; sexp_t *nsexp = NULL; int err = 0; pthread_rwlock_rdlock(&(c->grp_lock)); if((err = __ydc_conf_get_val_f(c, phvar, &val, &grp))) goto __failure; pthread_rwlock_unlock(&(c->grp_lock)); /* take group lock */ pthread_rwlock_wrlock(&(grp->val_lock)); switch(val->type) { case STRING: t = val->value; if(!(val->value = strndup((const char *)data, len))) { err = ENOMEM; val->value = t; goto __failure_1; } else free(t); break; case INT: case LONG: *(long *)val->value = *(long *)data; break; case ULONG: case UINT: *(unsigned long *)val->value = *(unsigned long *)data; break; case CUSTOM: /* that means we must call event callback before data change also */ if(val->on_eventv) val->on_eventv(val, val, c, YC_EVENT_MODIFY); /* oldval is val, since it pre call */ case BLOB: tb = val->value; if(!(val->value = malloc(len))) { err = ENOMEM; val->value = tb; goto __failure_1; } else memcpy(data, val->value, len); free(tb); break; case SEXPR: t = val->value; if(!(val->value = strndup((const char *)data, len))) { err = ENOMEM; val->value = t; goto __failure_1; } if(!(nsexp = parse_sexp(val->value, len))) { err = EINVAL; val->value = t; goto __failure_1; } else { /* change it */ free(t); sexp_t_deallocate((sexp_t *)val->priv); val->priv = (void *)nsexp; } break; } /* well, run callback if exists */ if(val->on_eventv) val->on_eventv(val, NULL, c, YC_EVENT_MODIFY); /* oldval is nil, incdicate * post event for CUSTOM type */ __failure_1: pthread_rwlock_unlock(&(grp->val_lock)); return err; __failure: pthread_rwlock_unlock(&(c->grp_lock)); return err; } int ydc_conf_clone_val(ydc_conf_val_t *s, ydc_conf_val_t *c) { int e = 0; /* zeroing for our clone */ memset(c, 0, sizeof(ydc_conf_val_t)); /* clone name and data */ if((c->name = strdup(s->name))) { e = ENOMEM; goto __fail; } if(!(c->value = malloc(s->len))) { free(c->name); e = ENOMEM; goto __fail; } else memcpy(c->value, s->value, s->len); /* clone others */ c->type = s->type; c->priv = s->priv; __fail: return e; } void ydc_conf_val_destroy_clone(ydc_conf_val_t *c) { free(c->name); free(c->value); /* this function will not free structure itself, instead we will * zero all stuff within structure, since we don't know how it was allocated, * it's better to be sure about cloned data */ memset(c, 0, sizeof(ydc_conf_val_t)); return; }