nagios4/lib/kvvec.c

351 lines
7.1 KiB
C

/*
* key+value vector library
*
* Small and simple, but pretty helpful when parsing configurations
* from random formats into something a program can easily make sense
* of.
*
* The main type (struct kvvec *) should possibly be opaque since
* all callers should use the kvvec_foreach() variable to trudge
* around in the key/value vector.
*/
#include <stdlib.h>
#include <string.h>
#include "kvvec.h"
struct kvvec *kvvec_init(struct kvvec *kvv, int hint)
{
if (!kvv)
return NULL;
kvv->kv_pairs = 0;
if (kvv->kv_alloc < hint && kvvec_resize(kvv, hint) < 0)
return NULL;
return kvv;
}
struct kvvec *kvvec_create(int hint)
{
struct kvvec *kvv = calloc(1, sizeof(*kvv));
if (kvv && !kvvec_init(kvv, hint)) {
free(kvv);
return NULL;
}
return kvv;
}
int kvvec_resize(struct kvvec *kvv, int hint)
{
struct key_value *kv;
if (!kvv)
return -1;
if (hint <= kvv->kv_alloc)
return 0;
kv = realloc(kvv->kv, sizeof(struct key_value) * hint);
if (!kv) {
if (kvv->kv)
free(kvv->kv);
return -1;
}
memset(&kv[kvv->kv_alloc], 0, (hint - kvv->kv_alloc) * sizeof(*kv));
kvv->kv = kv;
kvv->kv_alloc = hint;
return 0;
}
int kvvec_grow(struct kvvec *kvv, int hint)
{
if (!kvv)
return -1;
/* grow reasonably when we don't have a hint */
if (!hint)
hint = (kvv->kv_alloc / 3) + 15;
return kvvec_resize(kvv, kvv->kv_alloc + hint);
}
int kvvec_addkv_wlen(struct kvvec *kvv, const char *key, int keylen, const char *value, int valuelen)
{
struct key_value *kv;
if (!kvv || !key)
return -1;
if (kvv->kv_pairs >= kvv->kv_alloc - 1) {
if (kvvec_grow(kvv, 0))
return -1;
}
kv = &kvv->kv[kvv->kv_pairs++];
kv->key = (char *)key;
if (!keylen) {
kv->key_len = strlen(key);
} else {
kv->key_len = keylen;
}
kv->value = (char *)value;
if (value) {
if (!valuelen) {
kv->value_len = strlen(value);
} else {
kv->value_len = valuelen;
}
} else {
kv->value_len = 0;
}
kvv->kvv_sorted = 0;
return 0;
}
static int kv_compare(const void *a_, const void *b_)
{
const struct key_value *a = (const struct key_value *)a_;
const struct key_value *b = (const struct key_value *)b_;
int ret = 0;
ret = strcmp(a->key, b->key);
if (ret)
return ret;
if (!a->value && !b->value) {
return 0;
}
if (a->value && !b->value)
return -1;
if (!a->value && b->value)
return 1;
return strcmp(a->value, b->value);
}
int kvvec_sort(struct kvvec *kvv)
{
qsort(kvv->kv, kvv->kv_pairs, sizeof(struct key_value), kv_compare);
kvv->kvv_sorted = 1;
return 0;
}
int kvvec_foreach(struct kvvec *kvv, void *arg, int (*callback)(struct key_value *,void *))
{
int i;
if (!kvv)
return 0;
for (i = 0; i < kvv->kv_pairs; i++) {
callback(&kvv->kv[i], arg);
}
return 0;
}
void kvvec_free_kvpairs(struct kvvec *kvv, int flags)
{
int i;
if (flags == KVVEC_FREE_ALL) {
for (i = 0; i < kvv->kv_pairs; i++) {
free(kvv->kv[i].key);
free(kvv->kv[i].value);
}
} else if (flags == KVVEC_FREE_KEYS) {
for (i = 0; i < kvv->kv_pairs; i++) {
free(kvv->kv[i].key);
}
} else if (flags == KVVEC_FREE_VALUES) {
for (i = 0; i < kvv->kv_pairs; i++) {
free(kvv->kv[i].value);
}
}
kvv->kv_pairs = 0;
}
int kvvec_destroy(struct kvvec *kvv, int flags)
{
kvvec_free_kvpairs(kvv, flags);
free(kvv->kv);
free(kvv);
return 0;
}
/*
* Caller can tell us to over-allocate the buffer if he/she wants
* to put extra stuff at the end of it.
*/
struct kvvec_buf *kvvec2buf(struct kvvec *kvv, char kv_sep, char pair_sep, int overalloc)
{
struct kvvec_buf *kvvb;
int i;
unsigned long len = 0;
if (!kvv)
return NULL;
kvvb = malloc(sizeof(struct kvvec_buf));
if (!kvvb)
return NULL;
/* overalloc + (kv_sep_size * kv_pairs) + (pair_sep_size * kv_pairs) */
kvvb->bufsize = overalloc + (kvv->kv_pairs * 2);
for (i = 0; i < kvv->kv_pairs; i++) {
struct key_value *kv = &kvv->kv[i];
kvvb->bufsize += kv->key_len + kv->value_len;
}
kvvb->buf = malloc(kvvb->bufsize);
if (!kvvb->buf) {
free(kvvb);
return NULL;
}
for (i = 0; i < kvv->kv_pairs; i++) {
struct key_value *kv = &kvv->kv[i];
memcpy(kvvb->buf + len, kv->key, kv->key_len);
len += kv->key_len;
kvvb->buf[len++] = kv_sep;
if (kv->value_len) {
memcpy(kvvb->buf + len, kv->value, kv->value_len);
len += kv->value_len;
}
kvvb->buf[len++] = pair_sep;
}
memset(kvvb->buf + len, 0, kvvb->bufsize - len);
kvvb->buflen = len;
return kvvb;
}
unsigned int kvvec_capacity(struct kvvec *kvv)
{
if (!kvv)
return 0;
return kvv->kv_alloc - kvv->kv_pairs;
}
/*
* Converts a buffer of random bytes to a key/value vector.
* This requires a fairly rigid format in the input data to be of
* much use, but it's nifty for ipc where only computers are
* involved, and it will parse the kvvec2buf() produce nicely.
*/
int buf2kvvec_prealloc(struct kvvec *kvv, char *str,
unsigned int len, const char kvsep,
const char pair_sep, int flags)
{
unsigned int num_pairs = 0, i, offset = 0;
if (!str || !len || !kvv)
return -1;
/* first we count the number of key/value pairs */
while (offset < len) {
const char *ptr;
/* keys can't start with nul bytes */
if (*(str + offset)) {
num_pairs++;
}
ptr = memchr(str + offset, pair_sep, len - offset);
ptr++;
if (!ptr)
break;
offset += (unsigned long)ptr - ((unsigned long)str + offset);
}
if (!num_pairs) {
return 0;
}
/* make sure the key/value vector is large enough */
if (!(flags & KVVEC_APPEND)) {
kvvec_init(kvv, num_pairs);
} else if (kvvec_capacity(kvv) < num_pairs && kvvec_resize(kvv, num_pairs) < 0) {
return -1;
}
offset = 0;
for (i = 0; i < num_pairs; i++) {
struct key_value *kv;
char *key_end_ptr, *kv_end_ptr;
/* keys can't begin with nul bytes */
if (offset && str[offset] == '\0') {
return kvv->kv_pairs;
}
key_end_ptr = memchr(str + offset, kvsep, len - offset);
if (!key_end_ptr) {
break;
}
kv_end_ptr = memchr(key_end_ptr + 1, pair_sep, len - ((unsigned long)key_end_ptr - (unsigned long)str));
if (!kv_end_ptr) {
if (i != num_pairs - 1)
break;
/* last pair doesn't need a pair separator */
kv_end_ptr = str + len;
}
kv = &kvv->kv[kvv->kv_pairs++];
kv->key_len = (unsigned long)key_end_ptr - ((unsigned long)str + offset);
if (flags & KVVEC_COPY) {
kv->key = malloc(kv->key_len + 1);
memcpy(kv->key, str + offset, kv->key_len);
} else {
kv->key = str + offset;
}
kv->key[kv->key_len] = 0;
offset += kv->key_len + 1;
if (str[offset] == pair_sep) {
kv->value_len = 0;
if (flags & KVVEC_COPY) {
kv->value = strdup("");
} else {
kv->value = (char *)"";
}
} else {
kv->value_len = (unsigned long)kv_end_ptr - ((unsigned long)str + offset);
if (flags & KVVEC_COPY) {
kv->value = malloc(kv->value_len + 1);
memcpy(kv->value, str + offset, kv->value_len);
} else {
kv->value = str + offset;
}
kv->value[kv->value_len] = 0;
}
offset += kv->value_len + 1;
}
return i;
}
struct kvvec *buf2kvvec(char *str, unsigned int len, const char kvsep,
const char pair_sep, int flags)
{
struct kvvec *kvv;
kvv = kvvec_create(len / 20);
if (!kvv)
return NULL;
if (buf2kvvec_prealloc(kvv, str, len, kvsep, pair_sep, flags) >= 0)
return kvv;
free(kvv);
return NULL;
}