diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..25b050d98 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,37 @@ +# This file is here to make CLion users' lives better, but it is not complete. +# One cannot build sipnet from this file. But, it does let CLion do a much +# better job with code analysis. + +# As C files are added and/or removed, this file should be updated. + +cmake_minimum_required(VERSION 3.30) +project(sipnet C) + +set(CMAKE_C_STANDARD 23) + +include_directories(src) +include_directories(src/common) +include_directories(src/sipnet) +include_directories(tests) +include_directories(tests/sipnet/test_events_infrastructure) +include_directories(tests/sipnet/test_events_types) +include_directories(tests/sipnet/test_bugfixes) +include_directories(tests/sipnet/test_sipnet_infrastructure) +include_directories(tests/utils) + +add_compile_options(-Wall -Wpedantic -Werror) + +add_library(commonlib + src/common/spatialParams.c + src/common/util.c +) + +add_library(sipnetlib + src/sipnet/cli.c + src/sipnet/context.c + src/sipnet/events.c + src/sipnet/frontend.c + src/sipnet/outputItems.c + src/sipnet/runmean.c + src/sipnet/sipnet.c +) diff --git a/Makefile b/Makefile index 90d5daf20..b414837ec 100755 --- a/Makefile +++ b/Makefile @@ -7,11 +7,11 @@ LIB_DIR=./libs LDFLAGS=-L$(LIB_DIR) # Main executables -COMMON_CFILES:=util.c namelistInput.c modelParams.c +COMMON_CFILES:=util.c modelParams.c COMMON_CFILES:=$(addprefix src/common/, $(COMMON_CFILES)) COMMON_OFILES=$(COMMON_CFILES:.c=.o) -SIPNET_CFILES:=sipnet.c frontend.c runmean.c outputItems.c events.c +SIPNET_CFILES:=sipnet.c frontend.c runmean.c outputItems.c events.c context.c cli.c SIPNET_CFILES:=$(addprefix src/sipnet/, $(SIPNET_CFILES)) SIPNET_OFILES=$(SIPNET_CFILES:.c=.o) SIPNET_LIBS=-lsipnet_common diff --git a/docs/README.md b/docs/README.md index 1d3f7c04e..7044c6275 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,6 +4,26 @@ SIPNET (Simplified Photosynthesis and Evapotranspiration Model) is an ecosystem carbon and water dynamics. Originally developed for assimilation of eddy covariance flux data in forest ecosystems, current development is focused on representing carbon balance and GHG fluxes and agricultural management practices. +## Quick Start + +1. Clone the repository: + ```bash + git clone https://github.com/PecanProject/sipnet.git + cd sipnet + ``` +2. Build the SIPNET executable: + ```bash + make + ``` +3. Run a test simulation: + ```bash + ./src/sipnet -i tests/smoke/sipnet.in + ``` +4. Check the output: + ```bash + cat tests/smoke/niwot.out + ``` + ## Getting Started ### Installing diff --git a/src/common/exitCodes.h b/src/common/exitCodes.h index bcbfee428..60f402a1c 100644 --- a/src/common/exitCodes.h +++ b/src/common/exitCodes.h @@ -21,7 +21,8 @@ typedef enum { EXIT_CODE_UNKNOWN_EVENT_TYPE_OR_PARAM = 4, EXIT_CODE_INPUT_FILE_ERROR = 5, EXIT_CODE_FILE_OPEN_OR_READ_ERROR = 6, - EXIT_CODE_INTERNAL_ERROR = 7 + EXIT_CODE_INTERNAL_ERROR = 7, + EXIT_CODE_BAD_CLI_ARGUMENT = 8 } exit_code_t; #endif diff --git a/src/common/namelistInput.c b/src/common/namelistInput.c deleted file mode 100644 index 2937fc7b8..000000000 --- a/src/common/namelistInput.c +++ /dev/null @@ -1,292 +0,0 @@ -/* namelistInput: structure and functions to provide some of the functionality - provided by FORTRAN's NAMELISTs - - That is, read a bunch of input items from a file, where each item is given in - one of the forms: - name = value - name: value - name value - - Author: Bill Sacks - Creation date: 5/18/07 -*/ - -#include -#include -#include -#include "namelistInput.h" // includes definition of structures -#include "util.h" - -// Private/helper functions: not defined in namelistInput.h - -// Allocate space for a new namelistInputItem structure, return a pointer to it -// Name, type, ptr and maxlen will have the given values; nextItem will be null -// Note that if type is anything other than STRING_TYPE, maxlen is unused (but -// still must be supplied) -NamelistInputItem *newNamelistInputItem(char *name, int type, void *ptr, - int maxlen) { - NamelistInputItem *namelistInputItem; - - if (strlen(name) >= NAMELIST_INPUT_MAXNAME) { - printf("ERROR in newNamelistInputItem: parameter name '%s' exceeds maximum " - "length of %d\n", - name, NAMELIST_INPUT_MAXNAME); - exit(1); - } - - namelistInputItem = (NamelistInputItem *)malloc(sizeof(NamelistInputItem)); - - strcpy(namelistInputItem->name, name); - namelistInputItem->type = type; - namelistInputItem->ptr = ptr; - namelistInputItem->maxlen = maxlen; - namelistInputItem->wasRead = 0; - - namelistInputItem->nextItem = NULL; - - return namelistInputItem; -} - -// find input item with given name in namelistInputs list -// return a pointer to this input item -// if not found, return NULL -NamelistInputItem *locateNamelistInputItem(NamelistInputs *namelistInputs, - char *name) { - NamelistInputItem *namelistInputItem; - - namelistInputItem = namelistInputs->head; - - while ((namelistInputItem != NULL) && - (strcmpIgnoreCase(namelistInputItem->name, name) != 0)) { - namelistInputItem = namelistInputItem->nextItem; - } - - return namelistInputItem; -} - -/*************************************************/ - -// Public functions: defined in namelistInput.h - -// allocate space for a new namelistInputs structure, return a pointer to it -NamelistInputs *newNamelistInputs(void) { - NamelistInputs *namelistInputs; - - namelistInputs = (NamelistInputs *)malloc(sizeof(NamelistInputs)); - - namelistInputs->head = newNamelistInputItem("", -1, NULL, 0); // we'll keep a - // dummy item - // at the head - // of the list - namelistInputs->tail = namelistInputs->head; - namelistInputs->count = 0; - - return namelistInputs; -} - -/* Add a new namelistInputItem to the end of the list given by namelistInputs - strlen(name) must be < NAMELIST_INPUT_MAXNAME - type must be one of the types given by INPUT_TYPES - ptr must be a pointer to the variable holding this parameter value - maxlen is ignored if type is anything other than STRING_TYPE, but it still - must be supplied - - it specifies the maximum string length of the variable pointed to by ptr - (including the trailing '\0') -*/ -void addNamelistInputItem(NamelistInputs *namelistInputs, char *name, int type, - void *ptr, int maxlen) { - NamelistInputItem *namelistInputItem; - - namelistInputItem = newNamelistInputItem(name, type, ptr, maxlen); - namelistInputs->tail->nextItem = namelistInputItem; - namelistInputs->tail = namelistInputItem; - namelistInputs->count++; -} - -/* Read all namelist inputs from file given by fileName, put them in the - locations pointed to by each inputs' ptr variable - - Structure of input file: Each line contains: - name value - where the following are valid separators: space, tab, =, : - - A value of 'none' for a string type is translated into the empty string - - The order of input items in the input file does not matter - - ! is a comment character: anything after a ! on a line is ignored - */ -void readNamelistInputs(NamelistInputs *namelistInputs, const char *fileName) { - const char *SEPARATORS = " \t=:"; // characters that can separate names from - // values in input file - const char *COMMENT_CHARS = "!"; // comment characters (ignore everything - // after this on a line) - const char *EMPTY_STRING = "none"; // if this is the value of a string type, - // we take it to mean the empty string - - FILE *infile; - - char line[1024]; - char allSeparators[16]; - char *inputName; // name of input item just read in - char *inputValue; // value of input item just read in, as a string - char *errc = ""; - int isComment; - NamelistInputItem *namelistInputItem; - - strcpy(allSeparators, SEPARATORS); - strcat(allSeparators, "\n\r"); - infile = openFile(fileName, "r"); - - while (fgets(line, sizeof(line), infile) != NULL) { // while not EOF or error - // remove trailing comments: - isComment = stripComment(line, COMMENT_CHARS); - - if (!isComment) { // if this isn't just a comment line or blank line - // tokenize line: - inputName = strtok(line, SEPARATORS); // make inputName point to first - // token - inputValue = strtok(NULL, allSeparators); // make inputValue point to - // next token (e.g. after the - // '=') - - // printf("%s: <%s>\n", inputName, inputValue); - - namelistInputItem = locateNamelistInputItem(namelistInputs, inputName); - if (namelistInputItem == NULL) { - printf("WARNING: ignoring unknown parameter %s in %s\n", inputName, - fileName); - continue; - } - // otherwise, we have found the item with this name - - if (inputValue == NULL) { - printf( - "ERROR in readNamelistInputs: No value given for input item %s\n", - inputName); - printf("Please fix %s and re-run\n", fileName); - exit(1); - } - - switch (namelistInputItem->type) { - case INT_TYPE: - *(int *)(namelistInputItem->ptr) = strtol(inputValue, &errc, 0); - if (strlen(errc) > 0) { // invalid character(s) in input string - printf("ERROR in readNamelistInputs: Invalid value for %s: %s\n", - inputName, inputValue); - printf("Please fix %s and re-run\n", fileName); - // printf("%d <%s>\n", (int)strlen(errc), errc); - exit(1); - } - break; - case LONG_TYPE: - *(long *)(namelistInputItem->ptr) = strtol(inputValue, &errc, 0); - if (strlen(errc) > 0) { // invalid character(s) in input string - printf("ERROR in readNamelistInputs: Invalid value for %s: %s\n", - inputName, inputValue); - printf("Please fix %s and re-run\n", fileName); - // printf("%d <%s>\n", (int)strlen(errc), errc); - exit(1); - } - break; - case DOUBLE_TYPE: - *(double *)(namelistInputItem->ptr) = strtod(inputValue, &errc); - if (strlen(errc) > 0) { // invalid character(s) in input string - printf("ERROR in readNamelistInputs: Invalid value for %s: %s\n", - inputName, inputValue); - printf("Please fix %s and re-run\n", fileName); - // printf("%d <%s>\n", (int)strlen(errc), errc); - exit(1); - } - break; - case STRING_TYPE: - if (strcmp(inputValue, EMPTY_STRING) == 0) { - // if the value is specified as the value which signifies the empty - // string - strcpy((char *)(namelistInputItem->ptr), ""); - } else { - if (strlen(inputValue) >= namelistInputItem->maxlen) { - printf("ERROR in readNamelistInputs: value '%s' exceeds maximum " - "length for %s (%d)\n", - inputValue, inputName, namelistInputItem->maxlen); - printf( - "Please fix %s, or change the maximum length, and re-run\n", - fileName); - exit(1); - } - strcpy((char *)(namelistInputItem->ptr), inputValue); - } - break; - default: - printf("ERROR in readNamelistInputs: Unrecognized type for %s: %d\n", - inputName, namelistInputItem->type); - exit(1); - } - - namelistInputItem->wasRead = 1; - - } // if (!isComment) - } // while not EOF or error - - fclose(infile); -} - -/* If the input item with the given name wasn't read, - then kill the program and print an error message - */ -void dieIfNotRead(NamelistInputs *namelistInputs, char *name) { - NamelistInputItem *namelistInputItem; - - namelistInputItem = locateNamelistInputItem(namelistInputs, name); - if (namelistInputItem == NULL) { - printf("ERROR in dieIfNotRead: Can't find input item '%s'\n", name); - exit(1); - } - - if (!(namelistInputItem->wasRead)) { - printf("ERROR: '%s' must be present in input file\n", name); - exit(1); - } -} - -/* For string types only: - Check to make sure the given item: - (1) was read from the input file - (2) was set to a non-empty value (i.e. wasn't set to 'none') - Kill the program and print an error message if either of these conditions are - not met - */ -void dieIfNotSet(NamelistInputs *namelistInputs, char *name) { - NamelistInputItem *namelistInputItem; - - namelistInputItem = locateNamelistInputItem(namelistInputs, name); - if (namelistInputItem == NULL) { - printf("ERROR in dieIfNotSet: Can't find input item '%s'\n", name); - exit(1); - } - if (namelistInputItem->type != STRING_TYPE) { - printf("ERROR in dieIfNotSet: %s not of string_type\n", name); - printf(" Use dieIfNotRead instead\n"); - exit(1); - } - - dieIfNotRead(namelistInputs, name); - if (strcmp((char *)namelistInputItem->ptr, "") == 0) { - printf("ERROR: '%s' must not be set to 'none' or the empty string\n", name); - exit(1); - } -} - -// free up space used by namelistInputs -void deleteNamelistInputs(NamelistInputs *namelistInputs) { - NamelistInputItem *curr, *temp; - - curr = namelistInputs->head; - while (curr != NULL) { - temp = curr; - curr = curr->nextItem; - free(temp); - } - - free(namelistInputs); -} diff --git a/src/common/namelistInput.h b/src/common/namelistInput.h deleted file mode 100644 index 22e0267d3..000000000 --- a/src/common/namelistInput.h +++ /dev/null @@ -1,91 +0,0 @@ -// header file for namelistInput.c -// includes definitions of NamelistInputItem, namelistInputs structures - -#ifndef NAMELIST_INPUT_H -#define NAMELIST_INPUT_H - -// maximum length of a parameter name, including the trailing '\0' -#define NAMELIST_INPUT_MAXNAME 64 - -// define named constants for the different possible input types -enum INPUT_TYPES { INT_TYPE, LONG_TYPE, DOUBLE_TYPE, STRING_TYPE }; - -// structure to hold a namelist input item, and a pointer to the next (for a -// linked list) -typedef struct NamelistInputItemStruct { - char name[NAMELIST_INPUT_MAXNAME]; // name of parameter (must match name - // given in input file) - int type; // type of parameter (one of INPUT_TYPES) - void *ptr; /* pointer to the variable holding this parameter - (in the case of a string, ptr will be of type *char) */ - int maxlen; /* for STRING_TYPE, holds the maximum string length (including the - trailing '\0'); ignored for other types */ - int wasRead; // 1 if this input item was read from the input file, 0 if not - - struct NamelistInputItemStruct *nextItem; -} NamelistInputItem; - -// structure to hold a bunch of NamelistInputItems -// implemented as a linked list -typedef struct NamelistInputsStruct { - NamelistInputItem *head; - NamelistInputItem *tail; - - int count; // number of items in the list, not counting the dummy item at the - // head -} NamelistInputs; - -// allocate space for a new namelistInputs structure, return a pointer to it -NamelistInputs *newNamelistInputs(void); - -/* Add a new namelistInputItem to the end of the list given by namelistInputs - strlen(name) must be < NAMELIST_INPUT_MAXNAME - type must be one of the types given by INPUT_TYPES - ptr must be a pointer to the variable holding this parameter value - maxlen is ignored if type is anything other than STRING_TYPE, but it still - must be supplied - - it specifies the maximum string length of the variable pointed to by ptr - (including the trailing '\0') -*/ -void addNamelistInputItem(NamelistInputs *namelistInputs, char *name, int type, - void *ptr, int maxlen); - -/* Read all namelist inputs from file given by fileName, put them in the - locations pointed to by each inputs' ptr variable - - Structure of input file: Each line contains: - name value - where the following are valid separators: space, tab, =, : - - A value of 'none' for a string type is translated into the empty string - - The order of input items in the input file does not matter - - ! is a comment character: anything after a ! on a line is ignored - */ -void readNamelistInputs(NamelistInputs *namelistInputs, const char *fileName); - -/* If the input item with the given name wasn't read, - then kill the program and print an error message - */ -void dieIfNotRead(NamelistInputs *namelistInputs, char *name); - -/* For string types only: - Check to make sure the given item: - (1) was read from the input file - (2) was set to a non-empty value (i.e. wasn't set to 'none') - Kill the program and print an error message if either of these conditions are - not met - */ -void dieIfNotSet(NamelistInputs *namelistInputs, char *name); - -// free up space used by namelistInputs -void deleteNamelistInputs(NamelistInputs *namelistInputs); - -// find input item with given name in namelistInputs list -// return a pointer to this input item -// if not found, return NULL -NamelistInputItem *locateNamelistInputItem(NamelistInputs *namelistInputs, - char *name); - -#endif diff --git a/src/common/uthash.h b/src/common/uthash.h new file mode 100644 index 000000000..229389fd6 --- /dev/null +++ b/src/common/uthash.h @@ -0,0 +1,1216 @@ +/* +Copyright (c) 2003-2025, Troy D. Hanson https://troydhanson.github.io/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#define UTHASH_VERSION 2.3.0 + +#include /* memcmp, memset, strlen */ +#include /* ptrdiff_t */ +#include /* exit */ + +#if defined(HASH_NO_STDINT) && HASH_NO_STDINT +/* The user doesn't have , and must figure out their own way + to provide definitions for uint8_t and uint32_t. */ +#else +#include /* uint8_t, uint32_t */ +#endif + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if !defined(DECLTYPE) && !defined(NO_DECLTYPE) +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#endif +#elif defined(__MCST__) /* Elbrus C Compiler */ +#define DECLTYPE(x) (__typeof(x)) +#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || \ + defined(__WATCOMC__) +#define NO_DECLTYPE +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE(x) +#define DECLTYPE_ASSIGN(dst, src) \ + do { \ + char **_da_dst = (char **)(&(dst)); \ + *_da_dst = (char *)(src); \ + } while (0) +#else +#define DECLTYPE_ASSIGN(dst, src) \ + do { \ + (dst) = DECLTYPE(dst)(src); \ + } while (0) +#endif + +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr, sz) free(ptr) /* free fcn */ +#endif +#ifndef uthash_bzero +#define uthash_bzero(a, n) memset(a, '\0', n) +#endif +#ifndef uthash_strlen +#define uthash_strlen(s) strlen(s) +#endif + +#ifndef HASH_FUNCTION +#define HASH_FUNCTION(keyptr, keylen, hashv) HASH_JEN(keyptr, keylen, hashv) +#endif + +#ifndef HASH_KEYCMP +#define HASH_KEYCMP(a, b, n) memcmp(a, b, n) +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +#ifndef HASH_NONFATAL_OOM +#define HASH_NONFATAL_OOM 0 +#endif + +#if HASH_NONFATAL_OOM +/* malloc failures can be recovered from */ + +#ifndef uthash_nonfatal_oom +#define uthash_nonfatal_oom(obj) \ + do { \ + } while (0) /* non-fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) \ + do { \ + (oomed) = 1; \ + } while (0) +#define IF_HASH_NONFATAL_OOM(x) x + +#else +/* malloc failures result in lost memory, hash tables are unusable */ + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") +#define IF_HASH_NONFATAL_OOM(x) + +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 \ + 5U /* lg2 of initial number of buckets \ + */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhp */ +#define ELMT_FROM_HH(tbl, hhp) ((void *)(((char *)(hhp)) - ((tbl)->hho))) +/* calculate the hash handle from element address elp */ +#define HH_FROM_ELMT(tbl, elp) \ + ((UT_hash_handle *)(void *)(((char *)(elp)) + ((tbl)->hho))) + +#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ + do { \ + struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ + unsigned _hd_bkt; \ + HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + (head)->hh.tbl->buckets[_hd_bkt].count++; \ + _hd_hh_item->hh_next = NULL; \ + _hd_hh_item->hh_prev = NULL; \ + } while (0) + +#define HASH_VALUE(keyptr, keylen, hashv) \ + do { \ + HASH_FUNCTION(keyptr, keylen, hashv); \ + } while (0) + +#define HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, hashval, out) \ + do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_bkt; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, hashval)) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[_hf_bkt], \ + keyptr, keylen, hashval, out); \ + } \ + } \ + } while (0) + +#define HASH_FIND(hh, head, keyptr, keylen, out) \ + do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_hashv; \ + HASH_VALUE(keyptr, keylen, _hf_hashv); \ + HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ + } \ + } while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN \ + (HASH_BLOOM_BITLEN / 8UL) + (((HASH_BLOOM_BITLEN % 8UL) != 0UL) ? 1UL : 0UL) +#define HASH_BLOOM_MAKE(tbl, oomed) \ + do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t *)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!(tbl)->bloom_bv) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ + } \ + } while (0) + +#define HASH_BLOOM_FREE(tbl) \ + do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + } while (0) + +#define HASH_BLOOM_BITSET(bv, idx) (bv[(idx) / 8U] |= (1U << ((idx) % 8U))) +#define HASH_BLOOM_BITTEST(bv, idx) \ + ((bv[(idx) / 8U] & (1U << ((idx) % 8U))) != 0) + +#define HASH_BLOOM_ADD(tbl, hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, \ + ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#define HASH_BLOOM_TEST(tbl, hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, \ + ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#else +#define HASH_BLOOM_MAKE(tbl, oomed) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl, hashv) +#define HASH_BLOOM_TEST(tbl, hashv) 1 +#define HASH_BLOOM_BYTELEN 0U +#endif + +#define HASH_MAKE_TABLE(hh, head, oomed) \ + do { \ + (head)->hh.tbl = (UT_hash_table *)uthash_malloc(sizeof(UT_hash_table)); \ + if (!(head)->hh.tbl) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char *)(&(head)->hh) - (char *)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket *)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ + if (!(head)->hh.tbl->buckets) { \ + HASH_RECORD_OOM(oomed); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } else { \ + uthash_bzero((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * \ + sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ + IF_HASH_NONFATAL_OOM(if (oomed) { \ + uthash_free((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * \ + sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + }) \ + } \ + } \ + } while (0) + +#define HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, \ + hashval, add, replaced, cmpfcn) \ + do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, \ + replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), \ + keylen_in, hashval, add, cmpfcn); \ + } while (0) + +#define HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, hashval, add, \ + replaced) \ + do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, \ + replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, \ + hashval, add); \ + } while (0) + +#define HASH_REPLACE(hh, head, fieldname, keylen_in, add, replaced) \ + do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, \ + replaced); \ + } while (0) + +#define HASH_REPLACE_INORDER(hh, head, fieldname, keylen_in, add, replaced, \ + cmpfcn) \ + do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, \ + _hr_hashv, add, replaced, cmpfcn); \ + } while (0) + +#define HASH_APPEND_LIST(hh, head, add) \ + do { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail->next = (add); \ + (head)->hh.tbl->tail = &((add)->hh); \ + } while (0) + +#define HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn) \ + do { \ + do { \ + if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ + break; \ + } \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ + } while (0) + +#ifdef NO_DECLTYPE +#undef HASH_AKBI_INNER_LOOP +#define HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn) \ + do { \ + char *_hs_saved_head = (char *)(head); \ + do { \ + DECLTYPE_ASSIGN(head, _hs_iter); \ + if (cmpfcn(head, add) > 0) { \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + break; \ + } \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ + } while (0) +#endif + +#if HASH_NONFATAL_OOM + +#define HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, oomed) \ + do { \ + if (!(oomed)) { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, \ + oomed); \ + if (oomed) { \ + HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ + HASH_DELETE_HH(hh, head, &(add)->hh); \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } else { \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } \ + } else { \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } \ + } while (0) + +#else + +#define HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, oomed) \ + do { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } while (0) + +#endif + +#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, \ + hashval, add, cmpfcn) \ + do { \ + IF_HASH_NONFATAL_OOM(int _ha_oomed = 0;) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char *)(keyptr); \ + (add)->hh.keylen = (unsigned)(keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM(if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( \ + }) \ + } else { \ + void *_hs_iter = (head); \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ + if (_hs_iter) { \ + (add)->hh.next = _hs_iter; \ + if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ + HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ + } else { \ + (head) = (add); \ + } \ + HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ + } else { \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ + } while (0) + +#define HASH_ADD_KEYPTR_INORDER(hh, head, keyptr, keylen_in, add, cmpfcn) \ + do { \ + unsigned _hs_hashv; \ + HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, \ + _hs_hashv, add, cmpfcn); \ + } while (0) + +#define HASH_ADD_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, hashval, \ + add, cmpfcn) \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), \ + keylen_in, hashval, add, cmpfcn) + +#define HASH_ADD_INORDER(hh, head, fieldname, keylen_in, add, cmpfcn) \ + HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) + +#define HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, hashval, add) \ + do { \ + IF_HASH_NONFATAL_OOM(int _ha_oomed = 0;) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (const void *)(keyptr); \ + (add)->hh.keylen = (unsigned)(keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM(if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( \ + }) \ + } else { \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ + } while (0) + +#define HASH_ADD_KEYPTR(hh, head, keyptr, keylen_in, add) \ + do { \ + unsigned _ha_hashv; \ + HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ + } while (0) + +#define HASH_ADD_BYHASHVALUE(hh, head, fieldname, keylen_in, hashval, add) \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, \ + hashval, add) + +#define HASH_ADD(hh, head, fieldname, keylen_in, add) \ + HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) + +#define HASH_TO_BKT(hashv, num_bkts, bkt) \ + do { \ + bkt = ((hashv) & ((num_bkts) - 1U)); \ + } while (0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh, head, delptr) HASH_DELETE_HH(hh, head, &(delptr)->hh) + +#define HASH_DELETE_HH(hh, head, delptrhh) \ + do { \ + const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ + if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, (head)->hh.tbl->num_buckets * \ + sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } else { \ + unsigned _hd_bkt; \ + if (_hd_hh_del == (head)->hh.tbl->tail) { \ + (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ + } \ + if (_hd_hh_del->prev != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = \ + _hd_hh_del->next; \ + } else { \ + DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ + } \ + if (_hd_hh_del->next != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = \ + _hd_hh_del->prev; \ + } \ + HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ + } while (0) + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head, findstr, out) \ + do { \ + unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ + HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ + } while (0) +#define HASH_ADD_STR(head, strfield, add) \ + do { \ + unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ + } while (0) +#define HASH_REPLACE_STR(head, strfield, add, replaced) \ + do { \ + unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ + } while (0) +#define HASH_FIND_INT(head, findint, out) \ + HASH_FIND(hh, head, findint, sizeof(int), out) +#define HASH_ADD_INT(head, intfield, add) \ + HASH_ADD(hh, head, intfield, sizeof(int), add) +#define HASH_REPLACE_INT(head, intfield, add, replaced) \ + HASH_REPLACE(hh, head, intfield, sizeof(int), add, replaced) +#define HASH_FIND_PTR(head, findptr, out) \ + HASH_FIND(hh, head, findptr, sizeof(void *), out) +#define HASH_ADD_PTR(head, ptrfield, add) \ + HASH_ADD(hh, head, ptrfield, sizeof(void *), add) +#define HASH_REPLACE_PTR(head, ptrfield, add, replaced) \ + HASH_REPLACE(hh, head, ptrfield, sizeof(void *), add, replaced) +#define HASH_DEL(head, delptr) HASH_DELETE(hh, head, delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is + * defined. This is for uthash developer only; it compiles away if HASH_DEBUG + * isn't defined. + */ +#ifdef HASH_DEBUG +#include /* fprintf, stderr */ +#define HASH_OOPS(...) \ + do { \ + fprintf(stderr, __VA_ARGS__); \ + exit(-1); \ + } while (0) +#define HASH_FSCK(hh, head, where) \ + do { \ + struct UT_hash_handle *_thh; \ + if (head) { \ + unsigned _bkt_i; \ + unsigned _count = 0; \ + char *_prev; \ + for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ + unsigned _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char *)(_thh->hh_prev)) { \ + HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", (where), \ + (void *)_thh->hh_prev, (void *)_prev); \ + } \ + _bkt_count++; \ + _prev = (char *)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("%s: invalid bucket count %u, actual %u\n", (where), \ + (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid hh item count %u, actual %u\n", (where), \ + (head)->hh.tbl->num_items, _count); \ + } \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev != (char *)_thh->prev) { \ + HASH_OOPS("%s: invalid prev %p, actual %p\n", (where), \ + (void *)_thh->prev, (void *)_prev); \ + } \ + _prev = (char *)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid app item count %u, actual %u\n", (where), \ + (head)->hh.tbl->num_items, _count); \ + } \ + } \ + } while (0) +#else +#define HASH_FSCK(hh, head, where) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh, head, keyptr, fieldlen) \ + do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ + } while (0) +#else +#define HASH_EMIT_KEY(hh, head, keyptr, fieldlen) +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. + */ +#define HASH_BER(key, keylen, hashv) \ + do { \ + unsigned _hb_keylen = (unsigned)keylen; \ + const unsigned char *_hb_key = (const unsigned char *)(key); \ + (hashv) = 0; \ + while (_hb_keylen-- != 0U) { \ + (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ + } \ + } while (0) + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx + * (archive link: https://archive.is/Ivcan ) + */ +#define HASH_SAX(key, keylen, hashv) \ + do { \ + unsigned _sx_i; \ + const unsigned char *_hs_key = (const unsigned char *)(key); \ + hashv = 0; \ + for (_sx_i = 0; _sx_i < keylen; _sx_i++) { \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + } \ + } while (0) +/* FNV-1a variation */ +#define HASH_FNV(key, keylen, hashv) \ + do { \ + unsigned _fn_i; \ + const unsigned char *_hf_key = (const unsigned char *)(key); \ + (hashv) = 2166136261U; \ + for (_fn_i = 0; _fn_i < keylen; _fn_i++) { \ + hashv = hashv ^ _hf_key[_fn_i]; \ + hashv = hashv * 16777619U; \ + } \ + } while (0) + +#define HASH_OAT(key, keylen, hashv) \ + do { \ + unsigned _ho_i; \ + const unsigned char *_ho_key = (const unsigned char *)(key); \ + hashv = 0; \ + for (_ho_i = 0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ + } while (0) + +#define HASH_JEN_MIX(a, b, c) \ + do { \ + a -= b; \ + a -= c; \ + a ^= (c >> 13); \ + b -= c; \ + b -= a; \ + b ^= (a << 8); \ + c -= a; \ + c -= b; \ + c ^= (b >> 13); \ + a -= b; \ + a -= c; \ + a ^= (c >> 12); \ + b -= c; \ + b -= a; \ + b ^= (a << 16); \ + c -= a; \ + c -= b; \ + c ^= (b >> 5); \ + a -= b; \ + a -= c; \ + a ^= (c >> 3); \ + b -= c; \ + b -= a; \ + b ^= (a << 10); \ + c -= a; \ + c -= b; \ + c ^= (b >> 15); \ + } while (0) + +#define HASH_JEN(key, keylen, hashv) \ + do { \ + unsigned _hj_i, _hj_j, _hj_k; \ + unsigned const char *_hj_key = (unsigned const char *)(key); \ + hashv = 0xfeedbeefu; \ + _hj_i = _hj_j = 0x9e3779b9u; \ + _hj_k = (unsigned)(keylen); \ + while (_hj_k >= 12U) { \ + _hj_i += (_hj_key[0] + ((unsigned)_hj_key[1] << 8) + \ + ((unsigned)_hj_key[2] << 16) + ((unsigned)_hj_key[3] << 24)); \ + _hj_j += (_hj_key[4] + ((unsigned)_hj_key[5] << 8) + \ + ((unsigned)_hj_key[6] << 16) + ((unsigned)_hj_key[7] << 24)); \ + hashv += \ + (_hj_key[8] + ((unsigned)_hj_key[9] << 8) + \ + ((unsigned)_hj_key[10] << 16) + ((unsigned)_hj_key[11] << 24)); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12U; \ + } \ + hashv += (unsigned)(keylen); \ + switch (_hj_k) { \ + case 11: \ + hashv += ((unsigned)_hj_key[10] << 24); /* FALLTHROUGH */ \ + case 10: \ + hashv += ((unsigned)_hj_key[9] << 16); /* FALLTHROUGH */ \ + case 9: \ + hashv += ((unsigned)_hj_key[8] << 8); /* FALLTHROUGH */ \ + case 8: \ + _hj_j += ((unsigned)_hj_key[7] << 24); /* FALLTHROUGH */ \ + case 7: \ + _hj_j += ((unsigned)_hj_key[6] << 16); /* FALLTHROUGH */ \ + case 6: \ + _hj_j += ((unsigned)_hj_key[5] << 8); /* FALLTHROUGH */ \ + case 5: \ + _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ + case 4: \ + _hj_i += ((unsigned)_hj_key[3] << 24); /* FALLTHROUGH */ \ + case 3: \ + _hj_i += ((unsigned)_hj_key[2] << 16); /* FALLTHROUGH */ \ + case 2: \ + _hj_i += ((unsigned)_hj_key[1] << 8); /* FALLTHROUGH */ \ + case 1: \ + _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ + default:; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + } while (0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) || \ + defined(_MSC_VER) || defined(__BORLANDC__) || defined(__TURBOC__) +#define get16bits(d) (*((const uint16_t *)(d))) +#endif + +#if !defined(get16bits) +#define get16bits(d) \ + ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) + \ + (uint32_t)(((const uint8_t *)(d))[0])) +#endif +#define HASH_SFH(key, keylen, hashv) \ + do { \ + unsigned const char *_sfh_key = (unsigned const char *)(key); \ + uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ + \ + unsigned _sfh_rem = _sfh_len & 3U; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabeu; \ + \ + /* Main loop */ \ + for (; _sfh_len > 0U; _sfh_len--) { \ + hashv += get16bits(_sfh_key); \ + _sfh_tmp = ((uint32_t)(get16bits(_sfh_key + 2)) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2U * sizeof(uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: \ + hashv += get16bits(_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof(uint16_t)]) << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: \ + hashv += get16bits(_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: \ + hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + break; \ + default:; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ + } while (0) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl, hh, head, keyptr, keylen_in, hashval, out) \ + do { \ + if ((head).hh_head != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ + } else { \ + (out) = NULL; \ + } \ + while ((out) != NULL) { \ + if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ + if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ + break; \ + } \ + } \ + if ((out)->hh.hh_next != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ + } else { \ + (out) = NULL; \ + } \ + } \ + } while (0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head, hh, addhh, oomed) \ + do { \ + UT_hash_bucket *_ha_head = &(head); \ + _ha_head->count++; \ + (addhh)->hh_next = _ha_head->hh_head; \ + (addhh)->hh_prev = NULL; \ + if (_ha_head->hh_head != NULL) { \ + _ha_head->hh_head->hh_prev = (addhh); \ + } \ + _ha_head->hh_head = (addhh); \ + if ((_ha_head->count >= \ + ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) && \ + !(addhh)->tbl->noexpand) { \ + HASH_EXPAND_BUCKETS(addhh, (addhh)->tbl, oomed); \ + IF_HASH_NONFATAL_OOM(if (oomed) { HASH_DEL_IN_BKT(head, addhh); }) \ + } \ + } while (0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(head, delhh) \ + do { \ + UT_hash_bucket *_hd_head = &(head); \ + _hd_head->count--; \ + if (_hd_head->hh_head == (delhh)) { \ + _hd_head->hh_head = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_prev) { \ + (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_next) { \ + (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ + } \ + } while (0) + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(hh, tbl, oomed) \ + do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket *)uthash_malloc( \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + if (!_he_new_buckets) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero(_he_new_buckets, \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + (tbl)->ideal_chain_maxlen = \ + ((tbl)->num_items >> ((tbl)->log2_num_buckets + 1U)) + \ + ((((tbl)->num_items & (((tbl)->num_buckets * 2U) - 1U)) != 0U) \ + ? 1U \ + : 0U); \ + (tbl)->nonideal_items = 0; \ + for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ + _he_thh = (tbl)->buckets[_he_bkt_i].hh_head; \ + while (_he_thh != NULL) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[_he_bkt]); \ + if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ + (tbl)->nonideal_items++; \ + if (_he_newbkt->count > \ + _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ + _he_newbkt->expand_mult++; \ + } \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head != NULL) { \ + _he_newbkt->hh_head->hh_prev = _he_thh; \ + } \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free((tbl)->buckets, \ + (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + (tbl)->num_buckets *= 2U; \ + (tbl)->log2_num_buckets++; \ + (tbl)->buckets = _he_new_buckets; \ + (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) \ + ? ((tbl)->ineff_expands + 1U) \ + : 0U; \ + if ((tbl)->ineff_expands > 1U) { \ + (tbl)->noexpand = 1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ + } \ + } while (0) + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head, cmpfcn) HASH_SRT(hh, head, cmpfcn) +#define HASH_SRT(hh, head, cmpfcn) \ + do { \ + unsigned _hs_i; \ + unsigned _hs_looping, _hs_nmerges, _hs_insize, _hs_psize, _hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head != NULL) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping != 0U) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p != NULL) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ + _hs_psize++; \ + _hs_q = ((_hs_q->next != NULL) \ + ? HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) \ + : NULL); \ + if (_hs_q == NULL) { \ + break; \ + } \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize != 0U) || \ + ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ + if (_hs_psize == 0U) { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) \ + ? HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) \ + : NULL); \ + _hs_qsize--; \ + } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) \ + ? HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) \ + : NULL); \ + } \ + _hs_psize--; \ + } else if ((cmpfcn(DECLTYPE(head)( \ + ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, \ + _hs_q)))) <= 0) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) \ + ? HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) \ + : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) \ + ? HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) \ + : NULL); \ + _hs_qsize--; \ + } \ + if (_hs_tail != NULL) { \ + _hs_tail->next = \ + ((_hs_e != NULL) ? ELMT_FROM_HH((head)->hh.tbl, _hs_e) \ + : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e != NULL) { \ + _hs_e->prev = \ + ((_hs_tail != NULL) ? ELMT_FROM_HH((head)->hh.tbl, _hs_tail) \ + : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail != NULL) { \ + _hs_tail->next = NULL; \ + } \ + if (_hs_nmerges <= 1U) { \ + _hs_looping = 0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2U; \ + } \ + HASH_FSCK(hh, head, "HASH_SRT"); \ + } \ + } while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ + do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt = NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh = NULL; \ + ptrdiff_t _dst_hho = ((char *)(&(dst)->hh_dst) - (char *)(dst)); \ + if ((src) != NULL) { \ + for (_src_bkt = 0; _src_bkt < (src)->hh_src.tbl->num_buckets; \ + _src_bkt++) { \ + for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh != NULL; _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + IF_HASH_NONFATAL_OOM(int _hs_oomed = 0;) \ + _dst_hh = (UT_hash_handle *)(void *)(((char *)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh != NULL) { \ + _last_elt_hh->next = _elt; \ + } \ + if ((dst) == NULL) { \ + DECLTYPE_ASSIGN(dst, _elt); \ + HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ + IF_HASH_NONFATAL_OOM(if (_hs_oomed) { \ + uthash_nonfatal_oom(_elt); \ + (dst) = NULL; \ + continue; \ + }) \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, \ + _hs_oomed); \ + (dst)->hh_dst.tbl->num_items++; \ + IF_HASH_NONFATAL_OOM(if (_hs_oomed) { \ + HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ + HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ + _dst_hh->tbl = NULL; \ + uthash_nonfatal_oom(_elt); \ + continue; \ + }) \ + HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ + } while (0) + +#define HASH_CLEAR(hh, head) \ + do { \ + if ((head) != NULL) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, (head)->hh.tbl->num_buckets * \ + sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } \ + } while (0) + +#define HASH_OVERHEAD(hh, head) \ + (((head) != NULL) \ + ? ((size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + sizeof(UT_hash_table) + (HASH_BLOOM_BYTELEN))) \ + : 0U) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh, head, el, tmp) \ + for (((el) = (head)), \ + ((*(char **)(&(tmp))) = \ + (char *)((head != NULL) ? (head)->hh.next : NULL)); \ + (el) != NULL; \ + ((el) = (tmp)), ((*(char **)(&(tmp))) = \ + (char *)((tmp != NULL) ? (tmp)->hh.next : NULL))) +#else +#define HASH_ITER(hh, head, el, tmp) \ + for (((el) = (head)), \ + ((tmp) = DECLTYPE(el)((head != NULL) ? (head)->hh.next : NULL)); \ + (el) != NULL; \ + ((el) = (tmp)), \ + ((tmp) = DECLTYPE(el)((tmp != NULL) ? (tmp)->hh.next : NULL))) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh, head) +#define HASH_CNT(hh, head) ((head != NULL) ? ((head)->hh.tbl->num_items) : 0U) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1u +#define HASH_BLOOM_SIGNATURE 0xb12220f2u + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + uint8_t bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + const void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ diff --git a/src/common/util.c b/src/common/util.c index 149ea444a..fb92606de 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "exitCodes.h" #include "util.h" @@ -27,7 +28,9 @@ FILE *openFile(const char *name, const char *mode) { FILE *f; if ((f = fopen(name, mode)) == NULL) { - printf("Error opening %s for %s\n", name, mode); + const char *mode_word = + (!strcmp(mode, "r") || !strcmp(mode, "rb")) ? "reading" : "writing"; + fprintf(stderr, "Error %s '%s': %s\n", mode_word, name, strerror(errno)); exit(EXIT_CODE_FILE_OPEN_OR_READ_ERROR); } diff --git a/src/sipnet/cli.c b/src/sipnet/cli.c new file mode 100644 index 000000000..9ca5fa044 --- /dev/null +++ b/src/sipnet/cli.c @@ -0,0 +1,94 @@ +#include "cli.h" + +#include "common/exitCodes.h" + +#include "version.h" + +// The struct 'option' is defined in getopt.h, and is expected by getopt_long() +struct option long_options[] = { + // These options set a flag (and they need to be at the top here) + // name has_arg flag val + {"print_header", no_argument, &ctx.tmpFlag, 1}, + {"no_print_header", no_argument, &ctx.tmpFlag, 0}, + {"dump_config", no_argument, &ctx.tmpFlag, 1}, + {"no_dump_config", no_argument, &ctx.tmpFlag, 0}, + // These options don’t set a flag. We distinguish them by their indices + // name has_arg flag val + {"input_file", required_argument, 0, 'i'}, + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'v'}, + {0, 0, 0, 0}}; + +// See cli.h +#define NUM_FLAG_OPTIONS 4 +char *argNameMap[NUM_FLAG_OPTIONS] = { + // Must follow same order as long_options above; only need flag opts here + // Gives corresponding name in Context struct + "printHeader", "printHeader", "dumpConfig", "dumpConfig"}; + +// Print the help message when requested +void usage(char *progName) { + // clang-format off + printf("Usage: %s [OPTIONS]", progName); + printf("\n"); + printf("Run SIPNET model for one site with configured options.\n"); + printf("\n"); + printf("Options: (defaults are shown in parens at end)\n"); + printf(" -i, --input_file Name of input config file (sipnet.in)\n"); + printf("\n"); + printf("Flag options: (prepend flag with 'no_' to force off, eg '--no_print_header')\n"); + printf("\n"); + printf(" --dump_config Print final config to .config (0)\n"); + printf(" --print_header Whether to print header row in output files (1)\n"); + printf("\n"); + printf("Info options:\n"); + printf(" -h, --help Print this message and exit\n"); + printf(" -v, --version Print version information and exit\n"); + printf("\n"); + printf("Configuration options are read from . Other options specified on the command\n"); + printf("line override settings from that file.\n"); + printf("\n"); + //printf("\n"); + //printf("\n"); + // clang-format on +} + +// Print the version when requested +void version(void) { printf("SIPNET version %s\n", VERSION_STRING); } + +// Parses command-line options using getopt_long +void parseCommandLineArgs(int argc, char *argv[]) { + /* getopt_long stores the option index here. */ + int longIndex = 0; + int shortIndex; + // get command-line arguments: + while ((shortIndex = getopt_long(argc, argv, "hi:v", long_options, + &longIndex)) != -1) { + + switch (shortIndex) { + case 0: + // long form option, flag == 0 + updateIntContext(argNameMap[longIndex], ctx.tmpFlag, CTX_COMMAND_LINE); + break; + case 'h': + usage(argv[0]); + exit(EXIT_CODE_SUCCESS); + case 'i': + if (strlen(optarg) >= FILENAME_MAXLEN) { + printf("ERROR: input filename %s exceeds maximum length of %d\n", + optarg, FILENAME_MAXLEN); + printf("Either change the name or increase INPUT_MAXNAME in " + "frontend.c\n"); + exit(1); + } + updateCharContext("inputFile", optarg, CTX_COMMAND_LINE); + break; + case 'v': + version(); + exit(EXIT_CODE_SUCCESS); + default: + usage(argv[0]); + exit(EXIT_CODE_BAD_CLI_ARGUMENT); + } + } +} diff --git a/src/sipnet/cli.h b/src/sipnet/cli.h new file mode 100644 index 000000000..e95be9c4d --- /dev/null +++ b/src/sipnet/cli.h @@ -0,0 +1,31 @@ +#ifndef SIPNET_CLI_H +#define SIPNET_CLI_H + +#include +#include + +#include "context.h" + +// This file encapsulates management of the cli - that is, parsing the command +// line options, and managing what those options are + +// The struct 'option' is defined in getopt.h, and is expected by getopt_long() +extern struct option long_options[]; + +// The run-time option names do not match their corresponding fields in Context, +// so we need a way to get from one to the other. There is a check in +// initContext() that verifies that these all work. The #define here is for +// that check. +#define NUM_FLAG_OPTIONS 4 +extern char *argNameMap[NUM_FLAG_OPTIONS]; + +// Print the help message when requested +void usage(char *progName); + +// Print the version when requested +void version(void); + +// Parses command-line options using getopt_long +void parseCommandLineArgs(int argc, char *argv[]); + +#endif // SIPNET_CLI_H diff --git a/src/sipnet/context.c b/src/sipnet/context.c new file mode 100644 index 000000000..515a90bc6 --- /dev/null +++ b/src/sipnet/context.c @@ -0,0 +1,189 @@ +#include "context.h" + +#include +#include +#include +#include + +#include "common/exitCodes.h" +#include "cli.h" + +#define DEFAULT_INPUT_FILE "sipnet.in" +#define RUN_TYPE_STANDARD "standard" + +struct Context ctx; + +// Temp space for name-to-key conversions +static char keyName[CONTEXT_CHAR_MAXLEN]; + +// Default values for all context fields +void initContext(void) { + // Init hash map to NULL before adding anything to it + ctx.metaMap = NULL; + + // Init the params + CREATE_CHAR_CONTEXT(inputFile, "INPUT_FILE", DEFAULT_INPUT_FILE, CTX_DEFAULT); + CREATE_CHAR_CONTEXT(runType, "RUN_TYPE", RUN_TYPE_STANDARD, CTX_DEFAULT); + CREATE_CHAR_CONTEXT(fileName, "FILENAME", "", CTX_DEFAULT); + CREATE_INT_CONTEXT(location, "LOCATION", 0, CTX_DEFAULT); + CREATE_INT_CONTEXT(doMainOutput, "DO_MAIN_OUTPUT", 1, CTX_DEFAULT); + CREATE_INT_CONTEXT(doSingleOutputs, "DO_SINGLE_OUTPUT", 0, CTX_DEFAULT); + CREATE_INT_CONTEXT(printHeader, "PRINT_HEADER", 1, CTX_DEFAULT); + CREATE_CHAR_CONTEXT(paramFile, "PARAM_FILE", "", CTX_DEFAULT); + CREATE_CHAR_CONTEXT(climFile, "CLIM_FILE", "", CTX_DEFAULT); + CREATE_CHAR_CONTEXT(outFile, "OUT_FILE", "", CTX_DEFAULT); + CREATE_INT_CONTEXT(dumpConfig, "DUMP_CONFIG", 0, CTX_DEFAULT); + CREATE_CHAR_CONTEXT(outConfigFile, "OUT_CONFIG_FILE", "", CTX_DEFAULT); + + // Safety check: make sure the elements of the cli argNameMap are actually + // valid members - that is, that we can successfully find a metadata for each + // one. This protects against changing a field name here and forgetting to + // update that map. + for (int ind = 0; ind < NUM_FLAG_OPTIONS; ++ind) { + struct context_metadata *s = getContextMetadata(argNameMap[ind]); + if (s == NULL) { + printf("Internal error: cli param mismatch with Context\n"); + exit(EXIT_CODE_INTERNAL_ERROR); + } + } +} + +// With all the different permutations of spellings for config params, lets +// strip names down by removing chars like dashes and underscores, and +// converting to lowercase. This will allow different versions to still find +// the relevant metadata, while retaining uniqueness. +void nameToKey(const char *name) { + int keyInd = 0; + // Drop all non-alphanumeric chars (eg '-', '_'), and convert to lowercase + for (int i = 0; name[i]; i++) { + if (isalnum(name[i])) { + keyName[keyInd] = tolower(name[i]); // NOLINT + ++keyInd; + } + } + keyName[keyInd] = '\0'; +} + +context_source_t getContextSource(const char *name) { + return getContextMetadata(name)->source; +} + +struct context_metadata *getContextMetadata(const char *name) { + struct context_metadata *s; + nameToKey(name); + HASH_FIND_STR(ctx.metaMap, keyName, s); + if (s == NULL) { + printf("Internal error: context metadata for param %s not found\n", name); + exit(EXIT_CODE_INTERNAL_ERROR); + } + return s; +} + +void createContextMetadata(const char *name, const char *printName, + context_source_t source, context_type_t type, + void *value) { + struct context_metadata *s; + nameToKey(name); + HASH_FIND_STR(ctx.metaMap, keyName, s); + if (s == NULL) { + s = (struct context_metadata *)malloc(sizeof *s); + strcpy(s->keyName, keyName); + HASH_ADD_STR(ctx.metaMap, keyName, s); + } else { + printf("Internal error: attempt to recreate context param %s\n", name); + exit(EXIT_CODE_INTERNAL_ERROR); + } + strcpy(s->printName, printName); + s->source = source; + s->type = type; + s->value = value; +} + +void updateIntContext(const char *name, int value, context_source_t source) { + struct context_metadata *s; + nameToKey(name); + HASH_FIND_STR(ctx.metaMap, keyName, s); /* name already in the hash? */ + if (s == NULL) { + printf("Internal error: no context param %s found\n", name); + exit(EXIT_CODE_INTERNAL_ERROR); + } + if (hasSourcePrecedence(s, source)) { + *(int *)(s->value) = value; + s->source = source; + } +} + +void updateCharContext(const char *name, const char *value, + context_source_t source) { + struct context_metadata *s; + nameToKey(name); + HASH_FIND_STR(ctx.metaMap, keyName, s); /* name already in the hash? */ + if (s == NULL) { + printf("Internal error: no context param %s found\n", name); + exit(EXIT_CODE_INTERNAL_ERROR); + } + if (hasSourcePrecedence(s, source)) { + strncpy((char *)s->value, value, CONTEXT_CHAR_MAXLEN); + s->source = source; + } +} + +int hasSourcePrecedence(struct context_metadata *s, + context_source_t newSource) { + // If newSource is greater (or equal) to old source, then it's good + return (s->source < newSource); +} + +// Get a printable version of source enum for dumpConfig() +char *getContextSourceString(context_source_t src) { + switch (src) { + case CTX_DEFAULT: + return "DEFAULT"; + case CTX_CONTEXT_FILE: + return "INPUT_FILE"; + case CTX_COMMAND_LINE: + return "COMMAND_LINE"; + case CTX_CALCULATED: + return "CALCULATED"; + default: + return "UNKNOWN"; + } +} + +// Sort function for printConfig +int by_name(const struct context_metadata *a, + const struct context_metadata *b) { + return strcmp(a->keyName, b->keyName); +} + +void printConfig(FILE *outFile) { + // Sort alphabetically for consistency + HASH_SORT(ctx.metaMap, by_name); // NOLINT + + struct context_metadata *s; + + // Header + char timestamp[100]; + time_t current_time; + time(¤t_time); + struct tm *utc_time = gmtime(¤t_time); + strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S UTC", utc_time); + fprintf(outFile, "Final config for SIPNET run at %s\n", timestamp); + fprintf(outFile, "%20s %20s %30s\n", "Name", "Source", "Value"); + + // Config + for (s = ctx.metaMap; s != NULL; + s = (struct context_metadata *)(s->hh.next)) { + if (s->type == CTX_INT) { + fprintf(outFile, "%20s %20s %30d\n", s->printName, + getContextSourceString(s->source), *(int *)s->value); + } else if (s->type == CTX_CHAR) { + fprintf(outFile, "%20s %20s %30s\n", s->printName, + getContextSourceString(s->source), (char *)s->value); + } else { + // The height of paranoia + printf("Internal error, unknown found for context param\n"); + exit(EXIT_CODE_INTERNAL_ERROR); + } + } +} diff --git a/src/sipnet/context.h b/src/sipnet/context.h new file mode 100644 index 000000000..0bc4fb627 --- /dev/null +++ b/src/sipnet/context.h @@ -0,0 +1,99 @@ +// Definition of global context struct +// +// Used to encapsulate program options defined in context file and command +// options +#ifndef CONTEXT_H +#define CONTEXT_H + +#define CONTEXT_CHAR_MAXLEN 256 +// For convenience +#define FILENAME_MAXLEN CONTEXT_CHAR_MAXLEN + +#include + +#include "common/uthash.h" + +typedef enum ContextSource { + // These are in order of precedence, higher value wins. CALCULATED should be + // orthogonal to CONTEXT_FILE and COMMAND_LINE. + CTX_DEFAULT = 0, + CTX_CONTEXT_FILE = 1, + CTX_COMMAND_LINE = 2, + CTX_CALCULATED = 3 +} context_source_t; + +typedef enum ContextType { CTX_INT = 0, CTX_CHAR = 1 } context_type_t; + +struct context_metadata { + char keyName[CONTEXT_CHAR_MAXLEN]; // hash key + char printName[CONTEXT_CHAR_MAXLEN]; + context_source_t source; + context_type_t type; + void *value; // pointer to the member of Context that holds the value + UT_hash_handle hh; // makes this structure hashable +}; + +struct Context { + char inputFile[CONTEXT_CHAR_MAXLEN]; + char runType[CONTEXT_CHAR_MAXLEN]; + char fileName[CONTEXT_CHAR_MAXLEN]; + int location; + int doMainOutput; + int doSingleOutputs; + int printHeader; + char paramFile[CONTEXT_CHAR_MAXLEN]; + char climFile[CONTEXT_CHAR_MAXLEN]; + char outFile[CONTEXT_CHAR_MAXLEN]; + int dumpConfig; + char outConfigFile[CONTEXT_CHAR_MAXLEN]; + + // Temp space for handling command line flag args; we do not write directly + // the params since we want to do a precedence check first. If the new source + // has higher precedence than the current, we will copy this value to the + // respective param. + int tmpFlag; + + // Hash map storing metadata for context values + struct context_metadata *metaMap; +}; + +// The one and only Context struct +extern struct Context ctx; + +/*! + * Initialize the global context struct with default values + */ +void initContext(void); + +context_source_t getContextSource(const char *name); + +struct context_metadata *getContextMetadata(const char *name); + +void createContextMetadata(const char *name, const char *printName, + context_source_t source, context_type_t type, + void *value); + +void updateIntContext(const char *name, int value, context_source_t source); + +void updateCharContext(const char *name, const char *value, + context_source_t source); + +int hasSourcePrecedence(struct context_metadata *s, context_source_t newSource); + +char *getContextSourceString(context_source_t src); + +void printConfig(FILE *outFile); + +#define CREATE_INT_CONTEXT(name, printName, value, source) \ + do { \ + ctx.name = value; \ + createContextMetadata(#name, printName, source, CTX_INT, &ctx.name); \ + } while (0) + +#define CREATE_CHAR_CONTEXT(name, printName, value, source) \ + do { \ + strncpy(ctx.name, value, CONTEXT_CHAR_MAXLEN); \ + createContextMetadata(#name, printName, source, CTX_CHAR, ctx.name); \ + } while (0) + +#endif // CONTEXT_H diff --git a/src/sipnet/frontend.c b/src/sipnet/frontend.c index 4c90a2282..228ad1ee0 100644 --- a/src/sipnet/frontend.c +++ b/src/sipnet/frontend.c @@ -7,63 +7,116 @@ #include #include #include -#include // for command-line arguments #include "common/exitCodes.h" -#include "common/namelistInput.h" #include "common/modelParams.h" #include "common/util.h" +#include "cli.h" +#include "context.h" #include "events.h" #include "sipnet.h" #include "outputItems.h" #include "modelStructures.h" -// important constants - default values: - -#define FILE_MAXNAME 256 -#define RUNTYPE_MAXNAME 16 -#define INPUT_MAXNAME 64 -#define INPUT_FILE "sipnet.in" -#define DO_MAIN_OUTPUT 1 -#define DO_SINGLE_OUTPUTS 0 - -void checkRuntype(NamelistInputs *namelist, const char *inputFile) { - NamelistInputItem *inputItem = locateNamelistInputItem(namelist, "RUNTYPE"); - if (inputItem == NULL) { - // Well, something really broke - printf("ERROR: did not find NameListInputItem RUNTYPE\n"); - exit(EXIT_CODE_INTERNAL_ERROR); - } - if (!(inputItem->wasRead)) { - strcpy((char *)inputItem->ptr, "standard"); - return; - } - if (strcmpIgnoreCase((char *)inputItem->ptr, "standard") != 0) { +void checkRuntype(void) { + if (strcmpIgnoreCase(ctx.runType, "standard") != 0) { // Make sure this is not an old config with a different RUNTYPE set printf("SIPNET only supports the standard runtype mode; other options are " "obsolete and were last supported in v1.3.0\n"); - printf("Please fix %s and re-run\n", inputFile); + printf("Please fix %s and re-run\n", ctx.inputFile); exit(EXIT_CODE_BAD_PARAMETER_VALUE); } } -void usage(char *progName) { - printf("Usage: %s [-h] [-i inputFile]\n", progName); - printf("[-h] : Print this usage message and exit\n"); - printf("[-i inputFile]: Use given input file to configure the run\n"); - printf("\tDefault: %s\n", INPUT_FILE); +void readInputFile(const char *fileName) { + const char *SEPARATORS = " \t=:"; // characters that can separate names from + // values in input file + const char *COMMENT_CHARS = "!"; // comment characters (ignore everything + // after this on a line) + const char *EMPTY_STRING = "none"; // if this is the value of a string type, + // we take it to mean the empty string + + FILE *infile; + + char line[1024]; + char allSeparators[16]; + char *inputName; // name of input item just read in + char *inputValue; // value of input item just read in, as a string + char *errc = ""; + int isComment; + + strcpy(allSeparators, SEPARATORS); + strcat(allSeparators, "\n\r"); + infile = openFile(fileName, "r"); + + while (fgets(line, sizeof(line), infile) != NULL) { // while not EOF or error + // remove trailing comments: + isComment = stripComment(line, COMMENT_CHARS); + + if (!isComment) { // if this isn't just a comment line or blank line + // tokenize line: + inputName = strtok(line, SEPARATORS); // make inputName point to first + // token + inputValue = strtok(NULL, allSeparators); // make inputValue point to + // next token (e.g. after the + // '=') + struct context_metadata *ctx_meta = getContextMetadata(inputName); + if (ctx_meta == NULL) { + printf("Warning: ignoring input file parameter %s\n", inputName); + continue; + } + + if (inputValue == NULL) { + printf("Error in input file: No value given for input item %s\n", + inputName); + printf("Please fix %s and re-run\n", fileName); + exit(EXIT_CODE_BAD_PARAMETER_VALUE); + } + + switch (ctx_meta->type) { + case CTX_INT: { + int intVal = strtol(inputValue, &errc, 0); // NOLINT + if (strlen(errc) > 0) { // invalid character(s) in input string + printf("ERROR in input file: Invalid value for %s: %s\n", inputName, + inputValue); + printf("Please fix %s and re-run\n", fileName); + exit(EXIT_CODE_BAD_PARAMETER_VALUE); + } + updateIntContext(inputName, intVal, CTX_CONTEXT_FILE); + } break; + case CTX_CHAR: { + if (strcmp(inputValue, EMPTY_STRING) == 0) { + // if the value is specified as the value which signifies the + // empty string + updateCharContext(inputName, "", CTX_CONTEXT_FILE); + } else { + if (strlen(inputValue) >= CONTEXT_CHAR_MAXLEN) { + printf("ERROR in input file: value '%s' exceeds maximum " + "length for %s (%d)\n", + inputValue, inputName, CONTEXT_CHAR_MAXLEN); + printf( + "Please fix %s, or change the maximum length, and re-run\n", + fileName); + exit(EXIT_CODE_BAD_PARAMETER_VALUE); + } + updateCharContext(inputName, inputValue, CTX_CONTEXT_FILE); + } + } break; + default: + printf("ERROR in readInputFile: Unrecognized type for %s: %d\n", + inputName, ctx_meta->type); + exit(EXIT_CODE_INTERNAL_ERROR); + } + } // if (!isComment) + } // while not EOF or error + + fclose(infile); } int main(int argc, char *argv[]) { - char inputFile[INPUT_MAXNAME] = INPUT_FILE; - NamelistInputs *namelistInputs; - - // for compatibility - int loc; - FILE *out; - int option; // reading in optional arguments + FILE *out, *outConfig; ModelParams *modelParams; // the parameters used in the model (possibly // spatially-varying) @@ -71,92 +124,85 @@ int main(int argc, char *argv[]) { // single-variable files (if doSingleOutputs is // true) - char runtype[RUNTYPE_MAXNAME]; - - int doMainOutput = DO_MAIN_OUTPUT; // do we do main outputting of all - // variables? - int doSingleOutputs = DO_SINGLE_OUTPUTS; // do we do extra outputting of - // single-variable files? - int printHeader = HEADER; - - char fileName[FILE_MAXNAME]; - char outFile[FILE_MAXNAME + 24]; - char paramFile[FILE_MAXNAME + 24], climFile[FILE_MAXNAME + 24]; - - // get command-line arguments: - while ((option = getopt(argc, argv, "hi:")) != -1) { - // we have another optional argument - switch (option) { - case 'h': - usage(argv[0]); - exit(1); - case 'i': - if (strlen(optarg) >= INPUT_MAXNAME) { - printf("ERROR: input filename %s exceeds maximum length of %d\n", - optarg, INPUT_MAXNAME); - printf("Either change the name or increase INPUT_MAXNAME in " - "frontend.c\n"); - exit(1); - } - strcpy(inputFile, optarg); - break; - default: - usage(argv[0]); - exit(1); - } - } + // char fileName[FILENAME_MAXLEN - 8]; + char outFile[FILENAME_MAXLEN], outConfigFile[FILENAME_MAXLEN]; + char paramFile[FILENAME_MAXLEN], climFile[FILENAME_MAXLEN]; + + // 1. Initialize Context with default values + initContext(); - // clang-format off - // setup namelist input: - namelistInputs = newNamelistInputs(); - addNamelistInputItem(namelistInputs, "RUNTYPE", STRING_TYPE, runtype, RUNTYPE_MAXNAME); - addNamelistInputItem(namelistInputs, "FILENAME", STRING_TYPE, fileName, FILE_MAXNAME); - addNamelistInputItem(namelistInputs, "LOCATION", INT_TYPE, &loc, 0); - addNamelistInputItem(namelistInputs, "DO_MAIN_OUTPUT", INT_TYPE, &doMainOutput, 0); - addNamelistInputItem(namelistInputs, "DO_SINGLE_OUTPUTS", INT_TYPE, &doSingleOutputs, 0); - addNamelistInputItem(namelistInputs, "PRINT_HEADER", INT_TYPE, &printHeader,0); - // clang-format on + // 2. Parse command line args + parseCommandLineArgs(argc, argv); + // 3. Read input config file + // Note: command-line args have precedence // read from input file: - readNamelistInputs(namelistInputs, inputFile); + readInputFile(ctx.inputFile); - // Make sure FILENAME is set; everything else is optional (not necessary or - // has a default) - dieIfNotSet(namelistInputs, "FILENAME"); + // 4. Run some checks + // Make sure FILENAME is set and well-sized; everything else is optional (not + // necessary or has a default) + if (strcmp(ctx.fileName, "") == 0) { + printf("Error: fileName must be set for SIPNET to run\n"); + exit(EXIT_CODE_BAD_PARAMETER_VALUE); + } + if (strlen(ctx.fileName) > FILENAME_MAXLEN - 10) { + // We need room to append .clim, .param, etc + printf("Error: fileName is too long; max length is %d characters\n", + FILENAME_MAXLEN - 10); + exit(EXIT_CODE_BAD_PARAMETER_VALUE); + } // Handle RUNTYPE as an obsolete param; if it isn't set, consider it to be // set to "standard"; and make sure it is that if set - checkRuntype(namelistInputs, inputFile); + checkRuntype(); - strcpy(paramFile, fileName); + // 5. Set calculated filenames + strcpy(paramFile, ctx.fileName); strcat(paramFile, ".param"); - strcpy(climFile, fileName); + updateCharContext("paramFile", paramFile, CTX_CALCULATED); + strcpy(climFile, ctx.fileName); strcat(climFile, ".clim"); + updateCharContext("climFile", climFile, CTX_CALCULATED); + if (ctx.doMainOutput) { + strcpy(outFile, ctx.fileName); + strcat(outFile, ".out"); + updateCharContext("outFile", outFile, CTX_CALCULATED); + out = openFile(outFile, "w"); + } else { + out = NULL; + } + // Lastly - do after all other config processing + if (ctx.dumpConfig) { + strcpy(outConfigFile, ctx.fileName); + strcat(outConfigFile, ".config"); + updateCharContext("outConfigFile", outConfigFile, CTX_CALCULATED); + outConfig = openFile(outConfigFile, "w"); + printConfig(outConfig); + fclose(outConfig); + } else { + outConfig = NULL; + } + + // 6. Initialize model, events, outputItems initModel(&modelParams, paramFile, climFile); #if EVENT_HANDLER - initEvents(EVENT_IN_FILE, printHeader); + initEvents(EVENT_IN_FILE, ctx.printHeader); #endif - if (doSingleOutputs) { - outputItems = newOutputItems(fileName, ' '); + if (ctx.doSingleOutputs) { + outputItems = newOutputItems(ctx.fileName, ' '); setupOutputItems(outputItems); } else { outputItems = NULL; } - // Do the run! - if (doMainOutput) { - strcpy(outFile, fileName); - strcat(outFile, ".out"); - out = openFile(outFile, "w"); - } else { - out = NULL; - } - - runModelOutput(out, outputItems, printHeader); + // 7. Do the run! + runModelOutput(out, outputItems, ctx.printHeader); - if (doMainOutput) { + // 8. Cleanup + if (ctx.doMainOutput) { fclose(out); } @@ -165,5 +211,5 @@ int main(int argc, char *argv[]) { deleteOutputItems(outputItems); } - return 0; + return EXIT_CODE_SUCCESS; } diff --git a/src/sipnet/version.h b/src/sipnet/version.h new file mode 100644 index 000000000..26d7e6482 --- /dev/null +++ b/src/sipnet/version.h @@ -0,0 +1,6 @@ +#ifndef SIPNET_VERSION_H +#define SIPNET_VERSION_H + +#define VERSION_STRING "2.0.0" + +#endif // SIPNET_VERSION_H