diff --git a/Makefile.am b/Makefile.am index 4539633..b2bc9a3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -10,6 +10,17 @@ AM_CFLAGS = \ -Wno-conversion -Wunused-variable -Wunreachable-code \ -Wall -W -D_FORTIFY_SOURCE=2 -std=c99 +noinst_LTLIBRARIES = \ + libnica.la + +# Convenience library to prevent external linking with currently unstable ABI +libnica_la_SOURCES = \ + src/nica/hashmap.h \ + src/nica/hashmap.c \ + src/nica/inifile.h \ + src/nica/inifile.c \ + src/nica/util.h + bin_PROGRAMS = \ steam @@ -17,3 +28,6 @@ steam_SOURCES = \ src/lsi.h \ src/lsi.c \ src/shim.c + +steam_LDADD = \ + libnica.la diff --git a/src/nica/hashmap.c b/src/nica/hashmap.c new file mode 100644 index 0000000..45c91f9 --- /dev/null +++ b/src/nica/hashmap.c @@ -0,0 +1,472 @@ +/* + * This file is part of libnica. + * + * Copyright (C) 2016 Intel Corporation + * + * libnica 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 2.1 + * of the License, or (at your option) any later version. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "hashmap.h" + +#define INITIAL_SIZE 61 + +/* When we're "full", 70% */ +#define FULL_FACTOR 0.7 + +/* Multiple to increase bucket size by */ +#define INCREASE_FACTOR 4 + +/** + * An bucket/chain within the hashmap + */ +typedef struct NcHashmapEntry { + void *hash; /**size >= self->next_size) { + return true; + } + return false; +} + +static NcHashmap *nc_hashmap_new_internal(nc_hash_create_func create, nc_hash_compare_func compare, + nc_hash_free_func key_free, nc_hash_free_func value_free) +{ + NcHashmap *map = NULL; + NcHashmapEntry *buckets = NULL; + + map = calloc(1, sizeof(NcHashmap)); + if (!map) { + return NULL; + } + + buckets = calloc(INITIAL_SIZE, sizeof(NcHashmapEntry)); + if (!buckets) { + free(map); + return NULL; + } + map->buckets = buckets; + map->n_buckets = INITIAL_SIZE; + map->hash = create ? create : nc_simple_hash; + map->compare = compare ? compare : nc_simple_compare; + map->key_free = key_free; + map->value_free = value_free; + map->size = 0; + + nc_hashmap_update_next_size(map); + + return map; +} + +NcHashmap *nc_hashmap_new(nc_hash_create_func create, nc_hash_compare_func compare) +{ + return nc_hashmap_new_internal(create, compare, NULL, NULL); +} + +NcHashmap *nc_hashmap_new_full(nc_hash_create_func create, nc_hash_compare_func compare, + nc_hash_free_func key_free, nc_hash_free_func value_free) +{ + return nc_hashmap_new_internal(create, compare, key_free, value_free); +} + +static inline unsigned nc_hashmap_get_hash(NcHashmap *self, const void *key) +{ + unsigned hash = self->hash(key); + return hash; +} + +static bool nc_hashmap_insert_bucket(NcHashmap *self, NcHashmapEntry *buckets, int n_buckets, + unsigned hash, const void *key, void *value) +{ + NcHashmapEntry *row = &(buckets[hash % n_buckets]); + NcHashmapEntry *head = NULL; + NcHashmapEntry *parent = head = row; + bool can_replace = false; + NcHashmapEntry *tomb = NULL; + int ret = 1; + + while (row) { + if (!row->occ) { + tomb = row; + } + parent = row; + if (row->occ && row->hash == key) { + if (self->compare(row->hash, key)) { + can_replace = true; + break; + } + } + row = row->next; + } + + if (can_replace) { + /* Replace existing allocations. */ + if (self->value_free) { + self->value_free(row->value); + } + if (self->key_free) { + self->key_free(row->hash); + } + ret = 0; + } else if (tomb) { + row = tomb; + } + + if (!row) { + row = calloc(1, sizeof(NcHashmapEntry)); + if (!row) { + return -1; + } + } + + row->hash = (void *)key; + row->value = value; + row->occ = true; + if (parent != row && parent) { + parent->next = row; + } + + return ret; +} + +bool nc_hashmap_put(NcHashmap *self, const void *key, void *value) +{ + if (!self) { + return false; + } + int inc; + + if (nc_hashmap_maybe_resize(self)) { + if (!nc_hashmap_resize(self)) { + return false; + } + } + unsigned hash = nc_hashmap_get_hash(self, key); + inc = nc_hashmap_insert_bucket(self, self->buckets, self->n_buckets, hash, key, value); + if (inc > 0) { + self->size += inc; + return true; + } else { + return false; + } +} + +static NcHashmapEntry *nc_hashmap_get_entry(NcHashmap *self, const void *key) +{ + if (!self) { + return NULL; + } + + unsigned hash = nc_hashmap_get_hash(self, key); + NcHashmapEntry *row = &(self->buckets[hash % self->n_buckets]); + + while (row) { + if (self->compare(row->hash, key)) { + return row; + } + row = row->next; + } + return NULL; +} + +void *nc_hashmap_get(NcHashmap *self, const void *key) +{ + if (!self) { + return NULL; + } + + NcHashmapEntry *row = nc_hashmap_get_entry(self, key); + if (row) { + return row->value; + } + return NULL; +} + +static bool nc_hashmap_remove_internal(NcHashmap *self, const void *key, bool remove) +{ + if (!self) { + return false; + } + NcHashmapEntry *row = nc_hashmap_get_entry(self, key); + + if (!row) { + return false; + } + + if (remove) { + if (self->key_free) { + self->key_free(row->hash); + } + if (self->value_free) { + self->value_free(row->value); + } + } + self->size -= 1; + row->hash = NULL; + row->value = NULL; + row->occ = false; + + return true; +} + +bool nc_hashmap_steal(NcHashmap *self, const void *key) +{ + return nc_hashmap_remove_internal(self, key, false); +} + +bool nc_hashmap_remove(NcHashmap *self, const void *key) +{ + return nc_hashmap_remove_internal(self, key, true); +} + +bool nc_hashmap_contains(NcHashmap *self, const void *key) +{ + return (nc_hashmap_get(self, key)) != NULL; +} + +static inline void nc_hashmap_free_bucket(NcHashmap *self, NcHashmapEntry *bucket, bool nuke) +{ + if (!self) { + return; + } + NcHashmapEntry *tmp = bucket; + NcHashmapEntry *bk = bucket; + NcHashmapEntry *root = bucket; + + while (tmp) { + bk = NULL; + if (tmp->next) { + bk = tmp->next; + } + + if (nuke && tmp->occ) { + if (self->key_free) { + self->key_free(tmp->hash); + } + if (self->value_free) { + self->value_free(tmp->value); + } + } + if (tmp != root) { + free(tmp); + } + tmp = bk; + } +} + +void nc_hashmap_free(NcHashmap *self) +{ + if (!self) { + return; + } + for (int i = 0; i < self->n_buckets; i++) { + NcHashmapEntry *row = &(self->buckets[i]); + nc_hashmap_free_bucket(self, row, true); + } + if (self->buckets) { + free(self->buckets); + } + + free(self); +} + +static void nc_hashmap_update_next_size(NcHashmap *self) +{ + if (!self) { + return; + } + self->next_size = (int)(self->n_buckets * FULL_FACTOR); +} + +int nc_hashmap_size(NcHashmap *self) +{ + if (!self) { + return -1; + } + return self->size; +} + +static bool nc_hashmap_resize(NcHashmap *self) +{ + if (!self || !self->buckets) { + return false; + } + + NcHashmapEntry *old_buckets = self->buckets; + NcHashmapEntry *new_buckets = NULL; + NcHashmapEntry *entry = NULL; + int incr; + + int old_size, new_size; + int items = 0; + + new_size = old_size = self->n_buckets; + new_size *= INCREASE_FACTOR; + + new_buckets = calloc(new_size, sizeof(NcHashmapEntry)); + if (!new_buckets) { + return false; + } + + for (int i = 0; i < old_size; i++) { + entry = &(old_buckets[i]); + while (entry) { + if (entry->occ) { + unsigned hash = nc_hashmap_get_hash(self, entry->hash); + if ((incr = nc_hashmap_insert_bucket(self, + new_buckets, + new_size, + hash, + entry->hash, + entry->value)) > 0) { + items += incr; + } else { + /* Likely a memory issue */ + goto failure; + } + } + entry = entry->next; + } + } + /* Successfully resized - do this separately because we need to + * gaurantee old data is preserved */ + for (int i = 0; i < old_size; i++) { + nc_hashmap_free_bucket(self, &(old_buckets[i]), false); + } + + free(old_buckets); + self->n_buckets = new_size; + self->size = items; + self->buckets = new_buckets; + + nc_hashmap_update_next_size(self); + return true; + +failure: + for (int i = 0; i < new_size; i++) { + nc_hashmap_free_bucket(self, &(new_buckets[i]), true); + } + free(new_buckets); + return false; +} + +void nc_hashmap_iter_init(NcHashmap *map, NcHashmapIter *citer) +{ + _NcHashmapIter *iter = NULL; + if (!map || !citer) { + return; + } + iter = (_NcHashmapIter *)citer; + _NcHashmapIter it = { + .bucket = -1, .map = map, .item = NULL, + }; + *iter = it; +} + +bool nc_hashmap_iter_next(NcHashmapIter *citer, void **key, void **value) +{ + _NcHashmapIter *iter = NULL; + NcHashmapEntry *item = NULL; + NcHashmap *map = NULL; + int n_buckets = 0; + + if (!citer) { + return false; + } + + iter = (_NcHashmapIter *)citer; + map = iter->map; + if (!map) { + return false; + } + n_buckets = map->n_buckets; + item = iter->item; + + for (;;) { + if (iter->bucket >= n_buckets) { + if (item && !item->next) { + return false; + } + } + if (!item) { + iter->bucket++; + if (iter->bucket > n_buckets - 1) { + return false; + } + item = &(map->buckets[iter->bucket]); + } + if (item && item->occ) { + goto success; + } + item = item->next; + } + return false; + +success: + iter->item = item->next; + if (key) { + *key = item->hash; + } + if (value) { + *value = item->value; + } + + return true; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=8 tabstop=8 expandtab: + * :indentSize=8:tabSize=8:noTabs=true: + */ diff --git a/src/nica/hashmap.h b/src/nica/hashmap.h new file mode 100644 index 0000000..7530e80 --- /dev/null +++ b/src/nica/hashmap.h @@ -0,0 +1,240 @@ +/* + * This file is part of libnica. + * + * Copyright (C) 2016 Intel Corporation + * + * libnica 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 2.1 + * of the License, or (at your option) any later version. + * + * This is a chained Hashmap implementation. It focuses on being clean + * and efficient, and is comparable (at -O2) to open addressing hashmaps + * up until around 105,000 elements, where open addressing will begin + * to come out in front. At 1 million elements, open addressing is a clear + * winner, however currently we only need a maximum of 70k elements in + * our implementation. + * + * Given the memory required to even begin to deal with 1 million elements, + * it becomes very questionable whether a hashmap should have even been + * used in the first place (mmap'd db?) + */ + +#pragma once + +#define _GNU_SOURCE + +#include +#include + +#include "util.h" + +/* Convert between uint and void* */ +#define NC_HASH_KEY(x) ((void *)((uintptr_t)(x))) +#define NC_HASH_VALUE(x) NC_HASH_KEY(x) +#define NC_UNHASH_KEY(x) ((unsigned int)((uintptr_t)(x))) +#define NC_UNHASH_VALUE(x) NC_UNHASH_KEY(x) + +typedef struct NcHashmap NcHashmap; + +/** + * Iteration object + */ +typedef struct NcHashmapIter { + int n0; + void *n1; + void *n2; +} NcHashmapIter; + +/** + * Hash comparison function definition + * + * @param l First value to compare + * @param r Second value to compare + * + * @return true if l and r both match, otherwise false + */ +typedef bool (*nc_hash_compare_func)(const void *l, const void *r); + +/** + * Hash creation function definition + * + * @param key Key to generate a hash for + * + * @return an unsigned integer hash result + */ +typedef unsigned (*nc_hash_create_func)(const void *key); + +/** + * Callback definition to free keys and values + * + * @param p Single-depth pointer to either a key or value that should be freed + */ +typedef void (*nc_hash_free_func)(void *p); + +/** + * Default hash/comparison functions + * + * @note These are only used for comparison of *keys*, not values. Unless + * explicitly using string keys, you should most likely stick with the default + * nc_simple_hash and nc_simple_compare functions + */ + +/* Default string hash */ +static inline unsigned nc_string_hash(const void *key) +{ + unsigned hash = 5381; + const signed char *c; + + /* DJB's hash function */ + for (c = key; *c != '\0'; c++) { + hash = (hash << 5) + hash + (unsigned)*c; + } + return hash; +} + +/** + * Trivial pointer->uint hash + */ +static inline unsigned nc_simple_hash(const void *source) +{ + return NC_UNHASH_KEY(source); +} + +/** + * Comparison of string keys + */ +static inline bool nc_string_compare(const void *l, const void *r) +{ + if (!l || !r) { + return false; + } + return (strcmp(l, r) == 0); +} + +/** + * Trivial pointer comparison + */ +static inline bool nc_simple_compare(const void *l, const void *r) +{ + return (l == r); +} + +/** + * Create a new NcHashmap + * + * @param hash Hash creation function + * @param compare Key comparison function + * + * @return A newly allocated NcHashmap + */ +NcHashmap *nc_hashmap_new(nc_hash_create_func hash, nc_hash_compare_func compare); + +/** + * Create a new NcHashmap with cleanup functions + * + * @param hash Hash creation function + * @param compare Key comparison function + * @param key_free Function to free keys when removed/destroyed + * @param value_free Function to free values when removed/destroyed + * + * @return A newly allocated NcHashmap + */ +NcHashmap *nc_hashmap_new_full(nc_hash_create_func hash, nc_hash_compare_func compare, + nc_hash_free_func key_free, + nc_hash_free_func value_free); + +/** + * Store a key/value pair in the hashmap + * + * @note This will displace duplicate keys, and may free both the key + * and value if key_free and value_free are non null + * + * @param key Key to store in the hashmap + * @param value Value to be associated with the key + * + * @return true if the operation succeeded. + */ +bool nc_hashmap_put(NcHashmap *map, const void *key, void *value); + +/** + * Get the value associated with the unique key + * + * @param key Unique key to obtain a value for + * @return The associated value if it exists, otherwise NULL + */ +void *nc_hashmap_get(NcHashmap *map, const void *key); + +/** + * Determine if the key has an associated value in the NcHashmap + * + * @param key Unique key to check value for + * @return True if the key exists in the hashmap + */ +bool nc_hashmap_contains(NcHashmap *self, const void *key); + +/** + * Free the given NcHashmap, and all keys/values if appropriate + */ +void nc_hashmap_free(NcHashmap *map); + +/** + * Remove the value and key identified by key. + * + * @note If key_free or value_free are non-NULL, they will be invoked + * for both the key and value being removed + * + * @param key The unique key to remove + * @return true if the key/value pair were removed + */ +bool nc_hashmap_remove(NcHashmap *map, const void *key); + +/** + * Remove the value and key identified by key without freeing them + * + * @return true if the key/value pair were stolen + */ +bool nc_hashmap_steal(NcHashmap *map, const void *key); + +/** + * Return the current size of the hashmap + * + * @return size of the current hashmap (element count) + */ +int nc_hashmap_size(NcHashmap *map); + +/** + * Initialise a NcHashmapIter for iteration purposes + * + * @note The iter *must* be re-inited to re-use, or if it becomes + * exhausted from a previous iteration run + * + * @param iter pointer to a NcHashmapIter + */ +void nc_hashmap_iter_init(NcHashmap *map, NcHashmapIter *iter); + +/** + * Iterate every key/value pair in the hashmap + * + * @param iter A correctly initialised NcHashmapIter + * @param key Pointers to store the key in, may be NULL to skip + * @param value Pointer to store the value in, may be NULL to skip + * + * @return true if it's possible to iterate + */ +bool nc_hashmap_iter_next(NcHashmapIter *iter, void **key, void **value); + +DEF_AUTOFREE(NcHashmap, nc_hashmap_free) + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=8 tabstop=8 expandtab: + * :indentSize=8:tabSize=8:noTabs=true: + */ diff --git a/src/nica/inifile.c b/src/nica/inifile.c new file mode 100644 index 0000000..725983c --- /dev/null +++ b/src/nica/inifile.c @@ -0,0 +1,341 @@ +/* + * This file is part of libnica. + * + * Copyright (C) 2016 Intel Corporation + * + * libnica 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 2.1 + * of the License, or (at your option) any later version. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include + +#include "hashmap.h" +#include "inifile.h" + +/** + * Mapping of @NcIniError to static strings + */ +static const char *_errors[] = {[NC_INI_ERROR_FILE] = "", + [NC_INI_ERROR_EMPTY_KEY] = "Encountered empty key", + [NC_INI_ERROR_NOT_CLOSED] = "Expected closing \']\' for section", + [NC_INI_ERROR_NO_SECTION] = + "Encountered key=value mapping without a valid section", + [NC_INI_ERROR_INVALID_LINE] = "Expected key=value notation" }; + +const char *nc_ini_error(NcIniError error) +{ + int j = abs(error); + if (j < NC_INI_ERROR_FILE || j >= NC_INI_ERROR_MAX) { + return "[Unknown Error]"; + } + return _errors[j]; +} + +/** + * Strip leading whitespace from string (left) + */ +static char *lstrip(char *str, size_t len, size_t *out_len) +{ + size_t skip_len = 0; + + for (size_t i = 0; i < len; i++) { + if (str[i] == ' ' || str[i] == '\t') { + ++skip_len; + continue; + } + break; + } + *out_len = len - skip_len; + if (skip_len > 0) { + char *c = strdup(str + skip_len); + free(str); + return c; + } + return str + skip_len; +} + +/** + * Strip trailing whitespace from string (right) + */ +static char *rstrip(char *str, size_t len, size_t *out_len) +{ + size_t skip_len = 0; + + for (int i = len; i > 0; i--) { + if (str[i] == ' ' || str[i] == '\t') { + ++skip_len; + continue; + } else if (str[i] == '\0') { + continue; + } + break; + } + *out_len = len - skip_len; + if (len == skip_len) { + return str; + } + str[(len - skip_len)] = '\0'; + return str; +} + +/** + * Chew both ends of a null terminated string of whitespace + * If the left side is whitespace padded, this will always result + * in a new allocation due to fast-forwarding the pointer. Keep + * this in mind. + */ +static char *string_chew_terminated(char *inp) +{ + int skip_offset = 0; + int len = 0; + int end_len = 0; + bool count = true; + + if (!inp) { + return NULL; + } + + for (char *c = inp; *c; c++) { + if (count && (*c == ' ' || *c == '\t')) { + ++skip_offset; + continue; + } else { + count = false; + } + ++len; + } + for (int i = len; i > skip_offset; i--) { + if (inp[i] == ' ' || inp[i] == '\t') { + ++end_len; + continue; + } else if (inp[i] == '\0') { + continue; + } + break; + } + + if (end_len > 0) { + inp[(len - end_len)] = '\0'; + } + if (skip_offset > 0) { + char *c = strdup(inp + skip_offset); + free(inp); + return c; + } + return inp; +} + +/** + * Munch both ends of the string + */ +static char *string_strip(char *str, size_t len, size_t *out_len) +{ + size_t mylen = len; + + char *c = lstrip(str, len, &mylen); + c = rstrip(c, mylen, &mylen); + if (out_len) { + *out_len = mylen; + } + return c; +} + +NcHashmap *nc_ini_file_parse(const char *path) +{ + NcHashmap *ret = NULL; + int error_line = 0; + int r = 0; + + r = nc_ini_file_parse_full(path, &ret, &error_line); + if (r != 0) { + if (abs(r) == NC_INI_ERROR_FILE) { + fprintf(stderr, "[inifile] %s: %s\n", strerror(errno), path); + } else { + fprintf(stderr, + "[inifile] %s [L%d]: %s\n", + nc_ini_error(r), + error_line, + path); + } + return NULL; + } + return ret; +} + +int nc_ini_file_parse_full(const char *path, NcHashmap **out_map, int *error_line_number) +{ + autofree(FILE) *file = NULL; + char *buf = NULL; + ssize_t r = 0; + size_t sn = 0; + int line_count = 1; + char *current_section = NULL; + NcHashmap *root_map = NULL; + NcHashmap *section_map = NULL; + bool failed = false; + int err_ret = 0; + + file = fopen(path, "r"); + if (!file) { + return -(NC_INI_ERROR_FILE); + } + + if (!out_map) { + fprintf(stderr, "nc_ini_file_parse_full(): NcHashmap pointer invalid\n"); + return -1; + } + + root_map = nc_hashmap_new_full(nc_string_hash, + nc_string_compare, + free, + (nc_hash_free_func)nc_hashmap_free); + + while ((r = getline(&buf, &sn, file)) != -1) { + char *ch = NULL; + size_t str_len = r; + + /* Fix newline */ + if (buf[r - 1] == '\n') { + buf[r - 1] = '\0'; + --r; + } + str_len = r; + + buf = string_strip(buf, r, &str_len); + /* Empty lines are fine */ + if (streq(buf, "")) { + goto next; + } + + if (buf[0] == '[') { + /* Validate section start */ + if (buf[str_len - 1] != ']') { + /* Throw error */ + err_ret = NC_INI_ERROR_NOT_CLOSED; + goto fail; + } + /* Grab the section name, and "close" last section */ + buf[str_len - 1] = '\0'; + if (current_section) { + free(current_section); + } + current_section = strdup(buf + 1); + section_map = nc_hashmap_get(root_map, current_section); + if (!section_map) { + /* Create a new section dynamically */ + section_map = nc_hashmap_new_full(nc_string_hash, + nc_string_compare, + free, + free); + nc_hashmap_put(root_map, strdup(current_section), section_map); + } + goto next; + } else if (buf[0] == '#' || buf[0] == ';') { + /* Skip comment */ + goto next; + } + + /* Look for key = value */ + ch = strchr(buf, '='); + if (!ch) { + /* Throw error */ + err_ret = NC_INI_ERROR_INVALID_LINE; + goto fail; + } + + /* Can't have sectionless k->v */ + if (!current_section) { + err_ret = NC_INI_ERROR_NO_SECTION; + goto fail; + } + + int offset = ch - buf; + + /* Grab the key->value from this assignment line */ + char *value = strdup((buf + offset) + 1); + buf[offset] = '\0'; + char *key = strdup(buf); + key = string_chew_terminated(key); + value = string_chew_terminated(value); + + if (streq(key, "")) { + err_ret = NC_INI_ERROR_EMPTY_KEY; + free(key); + free(value); + goto fail; + } + + /* Ensure a section mapping exists */ + section_map = nc_hashmap_get(root_map, current_section); + if (!section_map) { + err_ret = 1; + fprintf(stderr, + "[inifile] Fatal! No section map for named " + "section: %s\n", + current_section); + abort(); + } + + /* Insert these guys into the map */ + if (!nc_hashmap_put(section_map, key, value)) { + err_ret = 1; + fprintf(stderr, "[inifile] Fatal! Out of memory\n"); + abort(); + } + /* Progression + cleanup */ + next: + if (buf) { + free(buf); + buf = NULL; + } + ++line_count; + sn = 0; + continue; + /* Parsing error, bail */ + fail: + failed = true; + if (error_line_number) { + *error_line_number = line_count; + } + break; + } + + if (buf) { + free(buf); + buf = NULL; + } + + if (current_section) { + free(current_section); + current_section = NULL; + } + + if (failed) { + if (root_map) { + nc_hashmap_free(root_map); + } + return -(err_ret); + } + *out_map = root_map; + return 0; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=8 tabstop=8 expandtab: + * :indentSize=8:tabSize=8:noTabs=true: + */ diff --git a/src/nica/inifile.h b/src/nica/inifile.h new file mode 100644 index 0000000..e25fe69 --- /dev/null +++ b/src/nica/inifile.h @@ -0,0 +1,112 @@ +/* + * This file is part of libnica. + * + * Copyright (C) 2016 Intel Corporation + * + * libnica 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 2.1 + * of the License, or (at your option) any later version. + */ + +#pragma once + +#include "hashmap.h" +#include "util.h" + +/** + * The Nica INI Parser focuses on simplicity and ease of use, therefore it + * currently only supports a read-only parse operation. + * + * The parse functions are used to return a _root_ @NcHashmap which contains + * name to section mappings. Each section is in turn an @NcHashmap with string + * to string mappings. + * + * Consider the following example: + * + * [Person] + * alive = true + * ; This person is alive + * + * The returned @NcHashmap will contain the key "Person", which corresponds to + * another @NcHashmap. This @NcHashmap will contain the key "alive", which has + * the value "true". Therefore we can access this variable using the @NcHashmap + * API: + * + * char *alive = nc_hashmap_get(nc_hashmap_get(ini, "Person"), "alive"); + * + * Conversely, we can use the API to determine whether a section or key is set: + * + * if (nc_hashmap_contains(ini, "Person")) + * + * As @NcHashmap returns NULL in nc_hashmap_get, you can happily pass NULL + * keys, allowing quick key fetches without checking if the section exists. + * + * In our INI file, lines beginning with ";" or "#" are considered as comments + * and are not processed. A line following the "key = value" notation is an + * _assignment_. "[Section]" is considered to be a section in the INI file, and + * all INI files must have at least one section. + * + * We do allow duplication section definitions, by following a merge policy. If + * a key is redefined then the original key->value mapping is freed and the new + * key->value mapping is inserted. + * + * The parser will return a correctly configured @NcHashmap, the only cleanup + * required is to call nc_hashmap_free on it. Alternatively, use the autofree + * helper, for a RAII approach: + * + * autofree(NcHashmap) *map = nc_ini_file_parse("myfile.ini"); + */ + +/** + * Errors that are reported when parsing an ini file + */ +typedef enum { + NC_INI_ERROR_MIN = 0, + NC_INI_ERROR_FILE, /**< File based error, check strerror(errno) */ + NC_INI_ERROR_EMPTY_KEY, /**< Empty key in an assignment line */ + NC_INI_ERROR_NOT_CLOSED, /**< Encountered section start that wasn't closed with a ']' */ + NC_INI_ERROR_NO_SECTION, /**< Key assignment with no defined sections */ + NC_INI_ERROR_INVALID_LINE, /**< Encountered an invalid line (syntax) */ + NC_INI_ERROR_MAX +} NcIniError; + +/** + * Convenience wrapper for nc_ini_file_parse_full, reports errors to stderrr + * and returns an NcHashmap if the parsing succeeded + */ +NcHashmap *nc_ini_file_parse(const char *path); + +/** + * Parse an INI file into a hash of hashes. + * + * @param path Path on the filesystem to parse, must be in INI syntax. + * @param out_map Pointer to store the resulting root NcHashmap in + * @param error_line_number If not null, then the erronous line number is + * stored. + * + * @return 0 if the call was succesful, or a negative integer. See nc_ini_error + */ +int nc_ini_file_parse_full(const char *path, NcHashmap **out_map, + int *error_line_number); + +/** + * Return a string representation for a given @NcIniError + * + * @param error Valid NcIniError code + * @return a static string owned by the implementation + */ +const char *nc_ini_error(NcIniError error); + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=8 tabstop=8 expandtab: + * :indentSize=8:tabSize=8:noTabs=true: + */ diff --git a/src/nica/util.h b/src/nica/util.h new file mode 100644 index 0000000..c882340 --- /dev/null +++ b/src/nica/util.h @@ -0,0 +1,53 @@ +/* + * This file is part of libnica. + * + * Copyright (C) 2016 Intel Corporation + * + * libnica 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 2.1 + * of the License, or (at your option) any later version. + */ + +#pragma once + +#define _GNU_SOURCE + +#include +#include +#include +#include + +#define DEF_AUTOFREE(N, C) \ + static inline void _autofree_func_##N(void *p) \ + { \ + if (p && *(N **)p) { \ + C(*(N **)p); \ + (*(void **)p) = NULL; \ + } \ + } + +#define autofree(N) __attribute__((cleanup(_autofree_func_##N))) N + +/** + * Dump any leaked file descriptors + */ +void nc_dump_file_descriptor_leaks(void); + +#define streq(x, y) strcmp(x, y) == 0 ? true : false + +DEF_AUTOFREE(char, free) +DEF_AUTOFREE(FILE, fclose) + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=8 tabstop=8 expandtab: + * :indentSize=8:tabSize=8:noTabs=true: + */