From 762fe5ebaf105b24a38312cb3c116ddc5b65d0f4 Mon Sep 17 00:00:00 2001 From: kanoi Date: Fri, 13 Jun 2014 19:32:37 +1000 Subject: [PATCH] ckdb v0.0 --- configure.ac | 3 + src/Makefile.am | 5 +- src/ckdb.c | 2101 +++++++++++++++++++++++++++++++++++++++++++++++ src/klist.c | 380 +++++++++ src/klist.h | 93 +++ src/ktree.c | 872 ++++++++++++++++++++ src/ktree.h | 44 + 7 files changed, 3497 insertions(+), 1 deletion(-) create mode 100644 src/ckdb.c create mode 100644 src/klist.c create mode 100644 src/klist.h create mode 100644 src/ktree.c create mode 100644 src/ktree.h diff --git a/configure.ac b/configure.ac index 687fd882..4e708cc3 100644 --- a/configure.ac +++ b/configure.ac @@ -41,6 +41,7 @@ AC_CHECK_HEADERS(stdint.h netinet/in.h netinet/tcp.h sys/ioctl.h) PTHREAD_LIBS="-lpthread" MATH_LIBS="-lm" RT_LIBS="-lrt" +PQ_LIBS="-lpq" AC_CONFIG_SUBDIRS([src/jansson-2.6]) JANSSON_LIBS="jansson-2.6/src/.libs/libjansson.a" @@ -48,6 +49,7 @@ JANSSON_LIBS="jansson-2.6/src/.libs/libjansson.a" AC_SUBST(PTHREAD_LIBS) AC_SUBST(MATH_LIBS) AC_SUBST(RT_LIBS) +AC_SUBST(PQ_LIBS) AC_SUBST(JANSSON_LIBS) AC_OUTPUT([Makefile] [src/Makefile]) @@ -58,6 +60,7 @@ echo " CPPFLAGS.............: $CPPFLAGS" echo " CFLAGS...............: $CFLAGS" echo " LDFLAGS..............: $LDFLAGS" echo " LDADD................: $PTHREAD_LIBS $MATH_LIBS $RT_LIBS $JANSSON_LIBS" +echo " db LDADD.............: $PQ_LIBS" echo echo "Installation...........: make install (as root if needed, with 'su' or 'sudo')" echo " prefix...............: $prefix" diff --git a/src/Makefile.am b/src/Makefile.am index 3cce0b8c..a19fd2cc 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -6,7 +6,7 @@ lib_LTLIBRARIES = libckpool.la libckpool_la_SOURCES = libckpool.c libckpool.h sha2.c sha2.h libckpool_la_LIBADD = @PTHREAD_LIBS@ @MATH_LIBS@ @RT_LIBS@ -bin_PROGRAMS = ckpool ckpmsg +bin_PROGRAMS = ckpool ckpmsg ckdb ckpool_SOURCES = ckpool.c ckpool.h generator.c generator.h bitcoin.c bitcoin.h \ stratifier.c stratifier.h connector.c connector.h uthash.h \ utlist.h @@ -14,3 +14,6 @@ ckpool_LDADD = libckpool.la @JANSSON_LIBS@ ckpmsg_SOURCES = ckpmsg.c ckpmsg_LDADD = libckpool.la @JANSSON_LIBS@ + +ckdb_SOURCES = ckdb.c klist.c ktree.c klist.h ktree.h +ckdb_LDADD = libckpool.la @JANSSON_LIBS@ @PQ_LIBS@ diff --git a/src/ckdb.c b/src/ckdb.c new file mode 100644 index 00000000..ff60756c --- /dev/null +++ b/src/ckdb.c @@ -0,0 +1,2101 @@ +/* + * Copyright 2003-2014 Andrew Smith + * Copyright 2014 Con Kolivas + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ckpool.h" +#include "libckpool.h" + +#include "klist.h" +#include "ktree.h" + +static char *db_user; +static char *db_pass; + +// size limit on the command string +#define ID_SIZ 31 + +#define TXT_BIG 256 +#define TXT_MED 128 +#define TXT_SML 64 +#define TXT_FLAG 1 + +#define FLDSEP 0x02 + +// Ensure long long and int64_t are both 8 bytes (and thus also the same) +#define ASSERT1(condition) __maybe_unused static char sizeof_longlong_must_be_8[(condition)?1:-1] +#define ASSERT2(condition) __maybe_unused static char sizeof_int64_t_must_be_8[(condition)?1:-1] +ASSERT1(sizeof(long long) == 8); +ASSERT2(sizeof(int64_t) == 8); + +#define PGLOG(__LOG, __str, __rescode, __conn) do { \ + char *__ptr, *__buf = strdup(PQerrorMessage(__conn)); \ + __ptr = __buf + strlen(__buf) - 1; \ + while (__ptr >= __buf && (*__ptr == '\n' || *__ptr == '\r')) \ + *(__ptr--) = '\0'; \ + while (--__ptr >= __buf) \ + if (*__ptr == '\n' || *__ptr == '\r' || *__ptr == '\t') \ + *__ptr = ' '; \ + __LOG("%s(): %s failed (%d) '%s'", __func__, \ + __str, (int)rescode, __buf); \ + free(__buf); \ + } while (0) + +#define PGLOGERR(_str, _rescode, _conn) PGLOG(LOGERR, _str, _rescode, _conn) + +/* N.B. STRNCPY() truncates, whereas txt_to_str() aborts ckdb if src > trg + * If copying data from the DB, code should always use txt_to_str() since + * data should never be lost/truncated if it came from the DB - + * that simply implies a code bug or a database change that must be fixed */ +#define STRNCPY(trg, src) do { \ + strncpy((char *)(trg), (char *)(src), sizeof(trg)); \ + trg[sizeof(trg) - 1] = '\0'; \ + } while (0) + +#define STRNCPYSIZ(trg, src, siz) do { \ + strncpy((char *)(trg), (char *)(src), siz); \ + trg[siz - 1] = '\0'; \ + } while (0) + +#define APPEND_REALLOC(_dst, _dstoff, _dstsiz, _src) do { \ + size_t _srclen = strlen(_src); \ + if ((_dstoff) + _srclen >= (_dstsiz)) { \ + _dstsiz += 1024; \ + _dst = realloc(_dst, _dstsiz); \ + if (!(_dst)) \ + quithere(1, "realloc (%d) OOM", (int)_dstsiz); \ + } \ + strcpy((_dst)+(_dstoff), _src); \ + _dstoff += _srclen; \ + } while(0) + +enum data_type { + TYPE_STR, + TYPE_BIGINT, + TYPE_INT, + TYPE_TV, + TYPE_BLOB +}; + +#define TXT_TO_STR(__nam, __fld, __data) txt_to_str(__nam, __fld, (__data), sizeof(__data)) +#define TXT_TO_BIGINT(__nam, __fld, __data) txt_to_bigint(__nam, __fld, &(__data), sizeof(__data)) +#define TXT_TO_INT(__nam, __fld, __data) txt_to_int(__nam, __fld, &(__data), sizeof(__data)) +#define TXT_TO_TV(__nam, __fld, __data) txt_to_tv(__nam, __fld, &(__data), sizeof(__data)) +#define TXT_TO_BLOB(__nam, __fld, __data) txt_to_blob(__nam, __fld, __data) + +#define PQ_GET_FLD(__res, __row, __name, __fld, __ok) do { \ + int __col = PQfnumber(__res, __name); \ + if (__col == -1) { \ + LOGERR("%s(): Unknown field '%s' row %d", __func__, __name, __row); \ + __ok = false; \ + } else \ + __fld = PQgetvalue(__res, __row, __col); \ + } while (0) + +// HISTORY FIELDS +#define HISTORYDATECONTROL ",createdate,createby,createcode,createinet,expirydate" +#define HISTORYDATECOUNT 5 + +#define HISTORYDATEFLDS(_res, _row, _data, _ok) do { \ + char *_fld; \ + PQ_GET_FLD(_res, _row, "createdate", _fld, _ok); \ + if (!_ok) \ + break; \ + TXT_TO_TV("createdate", _fld, (_data)->createdate); \ + PQ_GET_FLD(_res, _row, "createby", _fld, _ok); \ + if (!_ok) \ + break; \ + TXT_TO_STR("createby", _fld, (_data)->createby); \ + PQ_GET_FLD(_res, _row, "createcode", _fld, _ok); \ + if (!_ok) \ + break; \ + TXT_TO_STR("createcode", _fld, (_data)->createcode); \ + PQ_GET_FLD(_res, _row, "createinet", _fld, _ok); \ + if (!_ok) \ + break; \ + TXT_TO_STR("createinet", _fld, (_data)->createinet); \ + PQ_GET_FLD(_res, _row, "expirydate", _fld, _ok); \ + if (!_ok) \ + break; \ + TXT_TO_TV("expirydate", _fld, (_data)->expirydate); \ + } while (0) + +#define HISTORYDATECONTROLFIELDS \ + tv_t createdate; \ + char createby[TXT_SML+1]; \ + char createcode[TXT_MED+1]; \ + char createinet[TXT_MED+1]; \ + tv_t expirydate + +#define HISTORYDATEPARAMS(_params, _his_pos, _row) do { \ + _params[_his_pos++] = tv_to_buf(&(_row->createdate), NULL, 0); \ + _params[_his_pos++] = str_to_buf(_row->createby, NULL, 0); \ + _params[_his_pos++] = str_to_buf(_row->createcode, NULL, 0); \ + _params[_his_pos++] = str_to_buf(_row->createinet, NULL, 0); \ + _params[_his_pos++] = tv_to_buf(&(_row->expirydate), NULL, 0); \ + } while (0) + +// 6-Jun-6666 06:06:06+00 +#define DEFAULT_EXPIRY 148204965966L +// 1-Jun-6666 00:00:00+00 +#define COMPARE_EXPIRY 148204512000L + +static const tv_t default_expiry = { DEFAULT_EXPIRY, 0L }; + +#define HISTORYDATEINIT(_row, _by, _code, _inet) do { \ + setnow(&(_row->createdate)); \ + STRNCPY(_row->createby, _by); \ + STRNCPY(_row->createcode, _code); \ + STRNCPY(_row->createinet, _inet); \ + _row->expirydate.tv_sec = default_expiry.tv_sec; \ + _row->expirydate.tv_usec = default_expiry.tv_usec; \ + } while (0) + +// MODIFY FIELDS +#define MODIFYDATECONTROL ",createdate,createby,createcode,createinet" \ + ",modifydate,modifyby,modifycode,modifyinet" +#define MODIFYDATECOUNT 8 + +#define MODIFYDATEFLDS(_res, _row, _data, _ok) do { \ + char *_fld; \ + PQ_GET_FLD(_res, _row, "createdate", _fld, _ok); \ + if (!_ok) \ + break; \ + TXT_TO_TV("createdate", _fld, (_data)->createdate); \ + PQ_GET_FLD(_res, _row, "createby", _fld, _ok); \ + if (!_ok) \ + break; \ + TXT_TO_STR("createby", _fld, (_data)->createby); \ + PQ_GET_FLD(_res, _row, "createcode", _fld, _ok); \ + if (!_ok) \ + break; \ + TXT_TO_STR("createcode", _fld, (_data)->createcode); \ + PQ_GET_FLD(_res, _row, "createinet", _fld, _ok); \ + if (!_ok) \ + break; \ + TXT_TO_STR("createinet", _fld, (_data)->createinet); \ + PQ_GET_FLD(_res, _row, "modifydate", _fld, _ok); \ + if (!_ok) \ + break; \ + TXT_TO_TV("modifydate", _fld, (_data)->modifydate); \ + PQ_GET_FLD(_res, _row, "modifyby", _fld, _ok); \ + if (!_ok) \ + break; \ + TXT_TO_STR("modifyby", _fld, (_data)->modifyby); \ + PQ_GET_FLD(_res, _row, "modifycode", _fld, _ok); \ + if (!_ok) \ + break; \ + TXT_TO_STR("modifycode", _fld, (_data)->modifycode); \ + PQ_GET_FLD(_res, _row, "modifyinet", _fld, _ok); \ + if (!_ok) \ + break; \ + TXT_TO_STR("modifyinet", _fld, (_data)->modifyinet); \ + } while (0) + +#define MODIFYDATECONTROLFIELDS \ + tv_t createdate; \ + char createby[TXT_SML+1]; \ + char createcode[TXT_MED+1]; \ + char createinet[TXT_MED+1]; \ + tv_t modifydate; \ + char modifyby[TXT_SML+1]; \ + char modifycode[TXT_MED+1]; \ + char modifyinet[TXT_MED+1] + +#define MODIFYDATEPARAMS(_params, _mod_pos, _row) do { \ + _params[_mod_pos++] = tv_to_buf(&(_row->createdate), NULL, 0); \ + _params[_mod_pos++] = str_to_buf(_row->createby, NULL, 0); \ + _params[_mod_pos++] = str_to_buf(_row->createcode, NULL, 0); \ + _params[_mod_pos++] = str_to_buf(_row->createinet, NULL, 0); \ + _params[_mod_pos++] = tv_to_buf(&(_row->modifydate), NULL, 0); \ + _params[_mod_pos++] = str_to_buf(_row->modifyby, NULL, 0); \ + _params[_mod_pos++] = str_to_buf(_row->modifycode, NULL, 0); \ + _params[_mod_pos++] = str_to_buf(_row->modifyinet, NULL, 0); \ + } while (0) + +#define MODIFYDATEINIT(_row, _by, _code, _inet) do { \ + setnow(&(_row->createdate)); \ + STRNCPY(_row->createby, _by); \ + STRNCPY(_row->createcode, _code); \ + STRNCPY(_row->createinet, _inet); \ + _row->modifydate.tv_sec = 0; \ + _row->modifydate.tv_usec = 0; \ + _row->modifyby[0] = '\0'; \ + _row->modifycode[0] = '\0'; \ + _row->modifyinet[0] = '\0'; \ + } while (0) + +// For easy parameter constant strings +#define PQPARAM1 "$1" +#define PQPARAM2 "$1,$2" +#define PQPARAM3 "$1,$2,$3" +#define PQPARAM4 "$1,$2,$3,$4" +#define PQPARAM5 "$1,$2,$3,$4,$5" +#define PQPARAM6 "$1,$2,$3,$4,$5,$6" +#define PQPARAM7 "$1,$2,$3,$4,$5,$6,$7" +#define PQPARAM8 "$1,$2,$3,$4,$5,$6,$7,$8" +#define PQPARAM9 "$1,$2,$3,$4,$5,$6,$7,$8,$9" +#define PQPARAM10 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10" +#define PQPARAM11 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11" +#define PQPARAM12 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12" +#define PQPARAM13 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13" +#define PQPARAM14 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14" +#define PQPARAM15 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15" +#define PQPARAM16 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16" +#define PQPARAM17 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17" + +#define PARCHK(_par, _params) do { \ + if (_par != (int)(sizeof(_params)/sizeof(_params[0]))) { \ + quithere(1, "params[] usage (%d) != size (%d)", \ + _par, (int)(sizeof(_params)/sizeof(_params[0]))); \ + } \ + } while (0) + + +static const char *userpatt = "^[!-~]*$"; // no spaces +static const char *mailpatt = "^[A-Za-z0-9_-][A-Za-z0-9_\\.-]*@[A-Za-z0-9][A-Za-z0-9\\.]*[A-Za-z0-9]$"; +static const char *idpatt = "^[_A-Za-z][_A-Za-z0-9]*$"; +static const char *intpatt = "^[0-9][0-9]*$"; +static const char *hashpatt = "^[A-Fa-f0-9]*$"; + +// TRANSFER +#define NAME_SIZE 63 +#define VALUE_SIZE 1023 +typedef struct transfer { + char name[NAME_SIZE+1]; + char value[VALUE_SIZE+1]; +} TRANSFER; + +#define ALLOC_TRANSFER 1024 +#define LIMIT_TRANSFER 0 +#define DATA_TRANSFER(_item) ((TRANSFER *)(_item->data)) + +static K_TREE *transfer_root; +static K_LIST *transfer_list; +static K_STORE *transfer_store; + +// USERS +typedef struct users { + int64_t userid; + char username[TXT_BIG+1]; + char emailaddress[TXT_BIG+1]; + tv_t joineddate; + char passwordhash[TXT_BIG+1]; + char secondaryuserid[TXT_SML+1]; + HISTORYDATECONTROLFIELDS; +} USERS; + +#define ALLOC_USERS 1024 +#define LIMIT_USERS 0 +#define DATA_USERS(_item) ((USERS *)(_item->data)) + +static K_TREE *users_root; +static K_LIST *users_list; +static K_STORE *users_store; + +// WORKERS +typedef struct workers { + int64_t workerid; + int64_t userid; + char workername[TXT_SML+1]; + int32_t difficultydefault; + char idlenotificationenabled[TXT_FLAG+1]; + int32_t idlenotificationtime; + HISTORYDATECONTROLFIELDS; +} WORKERS; + +#define ALLOC_WORKERS 1024 +#define LIMIT_WORKERS 0 +#define DATA_WORKERS(_item) ((WORKERS *)(_item->data)) + +static K_TREE *workers_root; +static K_LIST *workers_list; +static K_STORE *workers_store; + +/* unused yet +// PAYMENTADDRESSES +typedef struct paymentaddresses { + int64_t paymentaddressid; + int64_t userid; + char payaddress[TXT_BIG+1]; + int32_t payratio; + HISTORYDATECONTROLFIELDS; +} PAYMENTADDRESSES; + +#define ALLOC_PAYMENTADDRESSES 1024 +#define LIMIT_PAYMENTADDRESSES 0 +#define DATA_PAYMENTADDRESSES(_item) ((PAYMENTADDRESSES *)(_item->data)) + +static K_TREE *paymentaddresses_root; +static K_LIST *paymentaddresses_list; +static K_STORE *paymentaddresses_store; +*/ + +// PAYMENTS +typedef struct payments { + int64_t paymentid; + int64_t userid; + tv_t paydate; + char payaddress[TXT_BIG+1]; + char originaltxn[TXT_BIG+1]; + int64_t amount; + char committxn[TXT_BIG+1]; + char commitblockhash[TXT_BIG+1]; + HISTORYDATECONTROLFIELDS; +} PAYMENTS; + +#define ALLOC_PAYMENTS 1024 +#define LIMIT_PAYMENTS 0 +#define DATA_PAYMENTS(_item) ((PAYMENTS *)(_item->data)) + +static K_TREE *payments_root; +static K_LIST *payments_list; +static K_STORE *payments_store; + +/* unused yet +// ACCOUNTBALANCE +typedef struct accountbalance { + int64_t userid; + int64_t confirmedpaid; + int64_t confirmedunpaid; + int64_t pendingconfirm; + int32_t heightupdate; + HISTORYDATECONTROLFIELDS; +} ACCOUNTBALANCE; + +#define ALLOC_ACCOUNTBALANCE 1024 +#define LIMIT_ACCOUNTBALANCE 0 +#define DATA_ACCOUNTBALANCE(_item) ((ACCOUNTBALANCE *)(_item->data)) + +static K_TREE *accountbalance_root; +static K_LIST *accountbalance_list; +static K_STORE *accountbalance_store; +*/ + +// IDCONTROL +typedef struct idcontrol { + char idname[TXT_SML+1]; + int64_t lastid; + MODIFYDATECONTROLFIELDS; +} IDCONTROL; + +#define ALLOC_IDCONTROL 16 +#define LIMIT_IDCONTROL 0 +#define DATA_IDCONTROL(_item) ((IDCONTROL *)(_item->data)) + +// These are only used for db access - not stored in memory +//static K_TREE *idcontrol_root; +static K_LIST *idcontrol_list; +static K_STORE *idcontrol_store; + +/* unused yet +// OPTIONCONTROL +typedef struct optioncontrol { + char optionname[TXT_SML+1]; + char optionvalue[TXT_MED+1]; + tv_t activationdate; + int32_t activationheight; + HISTORYDATECONTROLFIELDS; +} OPTIONCONTROL; + +#define ALLOC_OPTIONCONTROL 64 +#define LIMIT_OPTIONCONTROL 0 +#define DATA_OPTIONCONTROL(_item) ((OPTIONCONTROL *)(_item->data)) + +static K_TREE *optioncontrol_root; +static K_LIST *optioncontrol_list; +static K_STORE *optioncontrol_store; + +// TODO: aging/discarding workinfo,shares +// WORKINFO id.workinfo.json={...} +typedef struct workinfo { + int64_t workinfoid; + char poolinstance[TXT_BIG+1]; + char *transactiontree; + char prevhash[TXT_BIG+1]; + char coinbase1[TXT_BIG+1]; + char coinbase2[TXT_BIG+1]; + char version[TXT_SML+1]; + char bits[TXT_SML+1]; + char ntime[TXT_SML+1]; + HISTORYDATECONTROLFIELDS; +} WORKINFO; + +// ~10 hrs +#define ALLOC_WORKINFO 1400 +#define LIMIT_WORKINFO 0 +#define DATA_WORKINFO(_item) ((WORKINFO *)(_item->data)) + +static K_TREE *workinfo_root; +static K_LIST *workinfo_list; +static K_STORE *workinfo_store; + +// SHARES id.sharelog.json={...} +typedef struct shares { + int64_t workinfoid; + int64_t userid; + char workername[TXT_SML+1]; + int32_t clientid; + char enonce1[TXT_SML+1]; + char nonce2[TXT_BIG+1]; + char nonce[TXT_SML+1]; + char secondaryuserid[TXT_SML+1]; + HISTORYDATECONTROLFIELDS; +} SHARES; + +#define ALLOC_SHARES 10000 +#define LIMIT_SHARES 0 +#define DATA_SHARES(_item) ((SHARES *)(_item->data)) + +static K_TREE *shares_root; +static K_LIST *shares_list; +static K_STORE *shares_store; + +// SHARESUMMARY +typedef struct sharesummary { + int64_t userid; + char workername[TXT_SML+1]; + int64_t workinfoid; + int64_t diff_acc; + int64_t diff_sta; + int64_t diff_dup; + int64_t diff_low; + int64_t diff_rej; + int64_t share_acc; + int64_t share_sta; + int64_t share_dup; + int64_t share_low; + int64_t share_rej; + tv_t first_share; + tv_t last_share; + char complete[TXT_FLAG+1]; + MODIFYDATECONTROLFIELDS; +} SHARESUMMARY; + +#define ALLOC_SHARESUMMARY 10000 +#define LIMIT_SHARESUMMARY 0 +#define DATA_SHARESUMMARY(_item) ((SHARESUMMARY *)(_item->data)) + +static K_TREE *sharesummary_root; +static K_LIST *sharesummary_list; +static K_STORE *sharesummary_store; + +// BLOCKSUMMARY +typedef struct blocksummary { + int32_t height; + char blockhash[TXT_BIG+1]; + int64_t userid; + char workername[TXT_SML+1]; + int64_t diff_acc; + int64_t diff_sta; + int64_t diff_dup; + int64_t diff_low; + int64_t diff_rej; + int64_t share_acc; + int64_t share_sta; + int64_t share_dup; + int64_t share_low; + int64_t share_rej; + tv_t first_share; + tv_t last_share; + char complete[TXT_FLAG+1]; + MODIFYDATECONTROLFIELDS; +} BLOCKSUMMARY; + +#define ALLOC_BLOCKSUMMARY 10000 +#define LIMIT_BLOCKSUMMARY 0 +#define DATA_BLOCKSUMMARY(_item) ((BLOCKSUMMARY *)(_item->data)) + +static K_TREE *blocksummary_root; +static K_LIST *blocksummary_list; +static K_STORE *blocksummary_store; + +// BLOCKS +typedef struct blocks { + int32_t height; + char blockhash[TXT_BIG+1]; + int64_t workinfoid; + int64_t userid; + char workername[TXT_SML+1]; + int32_t clientid; + char enonce1[TXT_SML+1]; + char nonce2[TXT_BIG+1]; + char nonce[TXT_SML+1]; + char confirmed[TXT_FLAG+1]; + HISTORYDATECONTROLFIELDS; +} BLOCKS; + +#define ALLOC_BLOCKS 10000 +#define LIMIT_BLOCKS 0 +#define DATA_BLOCKS ((BLOCKS *)(_item->data)) + +static K_TREE *blocks_root; +static K_LIST *blocks_list; +static K_STORE *blocks_store; + +// MININGPAYOUTS +typedef struct miningpayouts { + int64_t miningpayoutid; + int64_t userid; + int32_t height; + char blockhash[TXT_BIG+1]; + int64_t amount; + HISTORYDATECONTROLFIELDS; +} MININGPAYOUTS; + +#define ALLOC_MININGPAYOUTS 1000 +#define LIMIT_MININGPAYOUTS 0 +#define DATA_MININGPAYOUTS(_item) ((MININGPAYOUTS *)(_item->data)) + +static K_TREE *miningpayouts_root; +static K_LIST *miningpayouts_list; +static K_STORE *miningpayouts_store; + +// EVENTLOG +typedef struct eventlog { + int64_t eventlogid; + char eventlogcode[TXT_SML+1]; + char *eventlogdescription; + HISTORYDATECONTROLFIELDS; +} EVENTLOG; + +#define ALLOC_EVENTLOG 100 +#define LIMIT_EVENTLOG 0 +#define DATA_EVENTLOG(_item) ((EVENTLOG *)(_item->data)) + +static K_TREE *eventlog_root; +static K_LIST *eventlog_list; +static K_STORE *eventlog_store; + +// AUTHS +typedef struct auths { + int64_t authid; + int64_t userid; + char workername[TXT_SML+1]; + int32_t clientid; + char enonce1[TXT_SML+1]; + char useragent[TXT_BIG+1]; + HISTORYDATECONTROLFIELDS; +} AUTHS; + +#define ALLOC_AUTHS 1000 +#define LIMIT_AUTHS 0 +#define DATA_AUTHS(_item) ((AUTHS *)(_item->data)) + +static K_TREE *auths_root; +static K_LIST *auths_list; +static K_STORE *auths_store; + +// POOLSTATS +// TODO: not in DB yet - design incomplete +// poll pool(s) every 10min? +typedef struct poolstats { + char poolinstance[TXT_BIG+1]; + int32_t users; + int32_t workers; + int64_t hashrate; // ... etc + int64_t hashrate5m; + int64_t hashrate1hr; + int64_t hashrate24hr; + HISTORYDATECONTROLFIELDS; +} POOLSTATS; + +#define ALLOC_POOLSTATS 10000 +#define LIMIT_POOLSTATS 0 +#define DATA_POOLSTATS(_item) ((POOLSTATS *)(_item->data)) + +static K_TREE *poolstats_root; +static K_LIST *poolstats_list; +static K_STORE *poolstats_store; +*/ + +static void setnow(tv_t *now) +{ + ts_t spec; + spec.tv_sec = 0; + spec.tv_nsec = 0; + clock_gettime(CLOCK_REALTIME, &spec); + now->tv_sec = spec.tv_sec; + now->tv_usec = spec.tv_nsec / 1000; +} + +static void txt_to_data(enum data_type typ, char *nam, char *fld, void *data, size_t siz) +{ + char *tmp; + + switch (typ) { + case TYPE_STR: + // A database field being bigger than local storage is a fatal error + if (siz < (strlen(fld)+1)) { + quithere(1, "Field %s structure size %d is smaller than db %d", + nam, (int)siz, (int)strlen(fld)+1); + } + strcpy((char *)data, fld); + break; + case TYPE_BIGINT: + if (siz != sizeof(int64_t)) { + quithere(1, "Field %s bigint incorrect structure size %d - should be %d", + nam, (int)siz, (int)sizeof(int64_t)); + } + *((long long *)data) = atoll(fld); + break; + case TYPE_INT: + if (siz != sizeof(int32_t)) { + quithere(1, "Field %s int incorrect structure size %d - should be %d", + nam, (int)siz, (int)sizeof(int32_t)); + } + *((int32_t *)data) = atoi(fld); + break; + case TYPE_TV: + if (siz != sizeof(tv_t)) { + quithere(1, "Field %s timeval incorrect structure size %d - should be %d", + nam, (int)siz, (int)sizeof(tv_t)); + } + unsigned int yyyy, mm, dd, HH, MM, SS, uS = 0, tz; + struct tm tm; + time_t tim; + int n; + n = sscanf(fld, "%u-%u-%u %u:%u:%u+%u", + &yyyy, &mm, &dd, &HH, &MM, &SS, &tz); + if (n != 7) { + // allow uS in case data was created elsewhere + n = sscanf(fld, "%u-%u-%u %u:%u:%u.%u+%u", + &yyyy, &mm, &dd, &HH, &MM, &SS, &uS, &tz); + if (n != 8) { + quithere(1, "Field %s timeval unhandled date '%s' (%d)", + nam, fld, n); + } + } + tm.tm_sec = (int)SS; + tm.tm_min = (int)MM; + tm.tm_hour = (int)HH; + tm.tm_mday = (int)dd; + tm.tm_mon = (int)mm - 1; + tm.tm_year = (int)yyyy - 1900; + tm.tm_isdst = -1; + tim = mktime(&tm); + // Fix TZ offsets errors + if (tim > COMPARE_EXPIRY) { + ((tv_t *)data)->tv_sec = default_expiry.tv_sec; + ((tv_t *)data)->tv_usec = default_expiry.tv_usec; + } else { + ((tv_t *)data)->tv_sec = tim; + ((tv_t *)data)->tv_usec = uS; + } + break; + case TYPE_BLOB: + tmp = strdup(fld); + if (!tmp) + quithere(1, "Field %s (%d) OOM", nam, (int)strlen(fld)); + *((char **)data) = tmp; + break; + default: + quithere(1, "Unknown field %s (%d) to convert", nam, (int)typ); + break; + } +} + +// N.B. STRNCPY* macros truncate, whereas this aborts ckdb if src > trg +static void txt_to_str(char *nam, char *fld, char data[], size_t siz) +{ + txt_to_data(TYPE_STR, nam, fld, (void *)data, siz); +} + +static void txt_to_bigint(char *nam, char *fld, int64_t *data, size_t siz) +{ + txt_to_data(TYPE_BIGINT, nam, fld, (void *)data, siz); +} + +static void txt_to_int(char *nam, char *fld, int32_t *data, size_t siz) +{ + txt_to_data(TYPE_INT, nam, fld, (void *)data, siz); +} + +static void txt_to_tv(char *nam, char *fld, tv_t *data, size_t siz) +{ + txt_to_data(TYPE_TV, nam, fld, (void *)data, siz); +} + +/* unused yet +static void txt_to_blob(char *nam, char *fld, char *data) +{ + txt_to_data(TYPE_BLOB, nam, fld, (void *)(&data), 0); +} +*/ + +static char *data_to_buf(enum data_type typ, void *data, char *buf, size_t siz) +{ + struct tm tm; + char *buf2; + + if (!buf) { + switch (typ) { + case TYPE_STR: + case TYPE_BLOB: + siz = strlen((char *)data) + 1; + break; + case TYPE_BIGINT: + case TYPE_INT: + case TYPE_TV: + siz = 64; // More than big enough + break; + default: + quithere(1, "Unknown field (%d) to convert", (int)typ); + break; + } + + buf = malloc(siz); + if (!buf) + quithere(1, "OOM (%d)", (int)siz); + } + + switch (typ) { + case TYPE_STR: + case TYPE_BLOB: + snprintf(buf, siz, "%s", (char *)data); + break; + case TYPE_BIGINT: + snprintf(buf, siz, "%"PRId64, *((uint64_t *)data)); + break; + case TYPE_INT: + snprintf(buf, siz, "%"PRId32, *((uint32_t *)data)); + break; + case TYPE_TV: + buf2 = malloc(siz); + if (!buf2) + quithere(1, "OOM (%d)", (int)siz); + localtime_r(&(((struct timeval *)data)->tv_sec), &tm); + strftime(buf2, siz, "%Y-%m-%d %H:%M:%S", &tm); + snprintf(buf, siz, "%s.%06ld", buf2, + (((struct timeval *)data)->tv_usec)); + free(buf2); + break; + } + + return buf; +} + +static char *str_to_buf(char data[], char *buf, size_t siz) +{ + return data_to_buf(TYPE_STR, (void *)data, buf, siz); +} + +static char *bigint_to_buf(int64_t data, char *buf, size_t siz) +{ + return data_to_buf(TYPE_BIGINT, (void *)(&data), buf, siz); +} + +/* unused yet +static char *int_to_buf(int32_t data, char *buf, size_t siz) +{ + return data_to_buf(TYPE_INT, (void *)(&data), buf, siz); +} +*/ + +static char *tv_to_buf(tv_t *data, char *buf, size_t siz) +{ + return data_to_buf(TYPE_TV, (void *)data, buf, siz); +} + +/* unused yet +static char *blob_to_buf(char *data, char *buf, size_t siz) +{ + return data_to_buf(TYPE_BLOB, (void *)data, buf, siz); +} +*/ + +static PGconn *dbconnect() +{ + char conninfo[128]; + PGconn *conn; + + snprintf(conninfo, sizeof(conninfo), "host=127.0.0.1 dbname=ckdb user=%s", db_user); + + conn = PQconnectdb(conninfo); + if (PQstatus(conn) != CONNECTION_OK) + quithere(1, "ERR: Failed to connect to db '%s'", PQerrorMessage(conn)); + + return conn; +} + +static int64_t nextid(PGconn *conn, char *idname, int64_t increment, char *by, char *code, char *inet) +{ + ExecStatusType rescode; + PGresult *res; + char qry[1024]; + char *params[5]; + int par; + int64_t lastid; + char *field; + tv_t now; + bool ok; + int n; + + lastid = 0; + + snprintf(qry, sizeof(qry), "select lastid from idcontrol " + "where idname='%s' for update", + idname); + + res = PQexec(conn, qry); + rescode = PQresultStatus(res); + if (rescode != PGRES_TUPLES_OK) { + PGLOGERR("Select", rescode, conn); + goto cleanup; + } + + n = PQnfields(res); + if (n != 1) { + LOGERR("%s(): Invalid field count - should be %d, but is %d", + __func__, 1, n); + goto cleanup; + } + + n = PQntuples(res); + if (n < 1) { + LOGERR("%s(): No matching idname='%s'", __func__, idname); + goto cleanup; + } + + ok = true; + PQ_GET_FLD(res, 0, "lastid", field, ok); + if (!ok) + goto cleanup; + TXT_TO_BIGINT("lastid", field, lastid); + + PQclear(res); + + lastid += increment; + snprintf(qry, sizeof(qry), "update idcontrol set " + "lastid=$1, modifydate=$2, modifyby=$3, " + "modifycode=$4, modifyinet=$5 " + "where idname='%s'", + idname); + + setnow(&now); + + par = 0; + params[par++] = bigint_to_buf(lastid, NULL, 0); + params[par++] = tv_to_buf(&now, NULL, 0); + params[par++] = str_to_buf(by, NULL, 0); + params[par++] = str_to_buf(code, NULL, 0); + params[par++] = str_to_buf(inet, NULL, 0); + PARCHK(par, params); + + res = PQexecParams(conn, qry, par, NULL, (const char **)params, NULL, NULL, 0); + rescode = PQresultStatus(res); + if (rescode != PGRES_COMMAND_OK) { + PGLOGERR("Update", rescode, conn); + lastid = 0; + } + + for (n = 0; n < par; n++) + free(params[n]); +cleanup: + PQclear(res); + return lastid; +} + +// order by username asc,expirydate desc +static double users_cmp(K_ITEM *a, K_ITEM *b) +{ + double c = strcmp(DATA_USERS(a)->username, DATA_USERS(b)->username); + if (c == 0.0) + c = tvdiff(&(DATA_USERS(b)->expirydate), &(DATA_USERS(a)->expirydate)); + return c; +} + +static bool users_add(PGconn *conn, char *username, char *emailaddress, char *passwordhash, char *by, char *code, char *inet) +{ + ExecStatusType rescode; + PGresult *res; + K_ITEM *item; + int n; + USERS *row; + char *ins; + char tohash[64]; + uint64_t hash; + __maybe_unused uint64_t tmp; + bool ok = false; + char *params[11]; + int par; + + LOGDEBUG("%s(): add", __func__); + + K_WLOCK(users_list); + item = k_unlink_head(users_list); + K_WUNLOCK(users_list); + + row = DATA_USERS(item); + + row->userid = nextid(conn, "userid", (int64_t)(666 + (rand() % 334)), by, code, inet); + if (row->userid == 0) + goto unitem; + + STRNCPY(row->username, username); + STRNCPY(row->emailaddress, emailaddress); + STRNCPY(row->passwordhash, passwordhash); + + snprintf(tohash, sizeof(tohash), "%s&#%s", username, emailaddress); + HASH_BER(tohash, strlen(tohash), 1, hash, tmp); + __bin2hex(row->secondaryuserid, (void *)(&hash), sizeof(hash)); + + HISTORYDATEINIT(row, by, code, inet); + + // copy createdate + row->joineddate.tv_sec = row->createdate.tv_sec; + row->joineddate.tv_usec = row->createdate.tv_usec; + + par = 0; + params[par++] = bigint_to_buf(row->userid, NULL, 0); + params[par++] = str_to_buf(row->username, NULL, 0); + params[par++] = str_to_buf(row->emailaddress, NULL, 0); + params[par++] = tv_to_buf(&(row->joineddate), NULL, 0); + params[par++] = str_to_buf(row->passwordhash, NULL, 0); + params[par++] = str_to_buf(row->secondaryuserid, NULL, 0); + HISTORYDATEPARAMS(params, par, row); + PARCHK(par, params); + + ins = "insert into users " + "(userid,username,emailaddress,joineddate,passwordhash," + "secondaryuserid" + HISTORYDATECONTROL ") values (" PQPARAM11 ")"; + + res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0); + rescode = PQresultStatus(res); + if (rescode != PGRES_COMMAND_OK) { + PGLOGERR("Insert", rescode, conn); + goto unparam; + } + + ok = true; +unparam: + PQclear(res); + for (n = 0; n < par; n++) + free(params[n]); +unitem: + K_WLOCK(users_list); + if (!ok) + k_add_head(users_list, item); + else { + users_root = add_to_ktree(users_root, item, users_cmp); + k_add_head(users_store, item); + } + K_WUNLOCK(users_list); + + return ok; +} + +static bool users_fill(PGconn *conn) +{ + ExecStatusType rescode; + PGresult *res; + K_ITEM *item; + int n, i; + USERS *row; + char *field; + char *sel; + int fields = 6; + bool ok; + + LOGDEBUG("%s(): select", __func__); + + sel = "select " + "userid,username,emailaddress,joineddate,passwordhash," + "secondaryuserid" + HISTORYDATECONTROL + " from users"; + res = PQexec(conn, sel); + rescode = PQresultStatus(res); + if (rescode != PGRES_TUPLES_OK) { + PGLOGERR("Select", rescode, conn); + PQclear(res); + return false; + } + + n = PQnfields(res); + if (n != (fields + HISTORYDATECOUNT)) { + LOGERR("%s(): Invalid field count - should be %d, but is %d", + __func__, fields + HISTORYDATECOUNT, n); + PQclear(res); + return false; + } + + n = PQntuples(res); + LOGDEBUG("%s(): tree build count %d", __func__, n); + ok = true; + K_WLOCK(users_list); + for (i = 0; i < n; i++) { + item = k_unlink_head(users_list); + row = DATA_USERS(item); + + PQ_GET_FLD(res, i, "userid", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("userid", field, row->userid); + + PQ_GET_FLD(res, i, "username", field, ok); + if (!ok) + break; + TXT_TO_STR("username", field, row->username); + + PQ_GET_FLD(res, i, "emailaddress", field, ok); + if (!ok) + break; + TXT_TO_STR("emailaddress", field, row->emailaddress); + + PQ_GET_FLD(res, i, "joineddate", field, ok); + if (!ok) + break; + TXT_TO_TV("joineddate", field, row->joineddate); + + PQ_GET_FLD(res, i, "passwordhash", field, ok); + if (!ok) + break; + TXT_TO_STR("passwordhash", field, row->passwordhash); + + PQ_GET_FLD(res, i, "secondaryuserid", field, ok); + if (!ok) + break; + TXT_TO_STR("secondaryuserid", field, row->secondaryuserid); + + HISTORYDATEFLDS(res, i, row, ok); + if (!ok) + break; + + users_root = add_to_ktree(users_root, item, users_cmp); + k_add_head(users_store, item); + } + if (!ok) + k_add_head(users_list, item); + + K_WUNLOCK(users_list); + PQclear(res); + + if (ok) + LOGDEBUG("%s(): built", __func__); + + return true; +} + +void users_reload() +{ + PGconn *conn = dbconnect(); + + K_WLOCK(users_list); + users_root = free_ktree(users_root, NULL); + k_list_transfer_to_head(users_store, users_list); + K_WUNLOCK(users_list); + + users_fill(conn); + + PQfinish(conn); +} + +// order by userid asc,workername asc,expirydate desc +static double workers_cmp(K_ITEM *a, K_ITEM *b) +{ + double c = (double)(DATA_WORKERS(a)->userid) - (double)(DATA_WORKERS(b)->userid); + if (c == 0.0) { + c = strcmp(DATA_WORKERS(a)->workername, DATA_WORKERS(b)->workername); + if (c == 0.0) + c = tvdiff(&(DATA_WORKERS(b)->expirydate), &(DATA_WORKERS(a)->expirydate)); + } + return c; +} + +static bool workers_fill(PGconn *conn) +{ + ExecStatusType rescode; + PGresult *res; + K_ITEM *item; + int n, i; + WORKERS *row; + char *field; + char *sel; + int fields = 6; + bool ok; + + LOGDEBUG("%s(): select", __func__); + + sel = "select " + "userid,workername,difficultydefault," + "idlenotificationenabled,idlenotificationtime" + HISTORYDATECONTROL + ",workerid from workers"; + res = PQexec(conn, sel); + rescode = PQresultStatus(res); + if (rescode != PGRES_TUPLES_OK) { + PGLOGERR("Select", rescode, conn); + PQclear(res); + return false; + } + + n = PQnfields(res); + if (n != (fields + HISTORYDATECOUNT)) { + LOGERR("%s(): Invalid field count - should be %d, but is %d", + __func__, fields + HISTORYDATECOUNT, n); + PQclear(res); + return false; + } + + n = PQntuples(res); + LOGDEBUG("%s(): tree build count %d", __func__, n); + ok = true; + K_WLOCK(workers_list); + for (i = 0; i < n; i++) { + item = k_unlink_head(workers_list); + row = DATA_WORKERS(item); + + PQ_GET_FLD(res, i, "userid", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("userid", field, row->userid); + + PQ_GET_FLD(res, i, "workername", field, ok); + if (!ok) + break; + TXT_TO_STR("workername", field, row->workername); + + PQ_GET_FLD(res, i, "difficultydefault", field, ok); + if (!ok) + break; + TXT_TO_INT("difficultydefault", field, row->difficultydefault); + + PQ_GET_FLD(res, i, "idlenotificationenabled", field, ok); + if (!ok) + break; + TXT_TO_STR("idlenotificationenabled", field, row->idlenotificationenabled); + + PQ_GET_FLD(res, i, "idlenotificationtime", field, ok); + if (!ok) + break; + TXT_TO_INT("idlenotificationtime", field, row->idlenotificationtime); + + HISTORYDATEFLDS(res, i, row, ok); + if (!ok) + break; + + PQ_GET_FLD(res, i, "workerid", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("workerid", field, row->workerid); + + workers_root = add_to_ktree(workers_root, item, workers_cmp); + k_add_head(workers_store, item); + } + if (!ok) + k_add_head(workers_list, item); + + K_WUNLOCK(workers_list); + PQclear(res); + + if (ok) + LOGDEBUG("%s(): built", __func__); + + return true; +} + +void workers_reload() +{ + PGconn *conn = dbconnect(); + + K_WLOCK(workers_list); + workers_root = free_ktree(workers_root, NULL); + k_list_transfer_to_head(workers_store, workers_list); + K_WUNLOCK(workers_list); + + workers_fill(conn); + + PQfinish(conn); +} + +// order by userid asc,paydate asc,payaddress asc +static double payments_cmp(K_ITEM *a, K_ITEM *b) +{ + double c = (double)(DATA_PAYMENTS(a)->userid) - (double)(DATA_PAYMENTS(b)->userid); + if (c == 0.0) { + c = tvdiff(&(DATA_PAYMENTS(a)->paydate), &(DATA_PAYMENTS(b)->paydate)); + if (c == 0.0) + c = strcmp(DATA_PAYMENTS(a)->payaddress, DATA_PAYMENTS(b)->payaddress); + } + return c; +} + +static bool payments_fill(PGconn *conn) +{ + ExecStatusType rescode; + PGresult *res; + K_ITEM *item; + int n, i; + PAYMENTS *row; + char *params[1]; + int par; + char *field; + char *sel; + int fields = 8; + bool ok; + + LOGDEBUG("%s(): select", __func__); + + // TODO: handle selecting a subset, eg 20 per web page + sel = "select " + "userid,paydate,payaddress,originaltxn,amount,committxn,commitblockhash" + HISTORYDATECONTROL + ",paymentid from payments where expirydate=$1"; + par = 0; + params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0); + PARCHK(par, params); + res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0); + rescode = PQresultStatus(res); + if (rescode != PGRES_TUPLES_OK) { + PGLOGERR("Select", rescode, conn); + PQclear(res); + return false; + } + + n = PQnfields(res); + if (n != (fields + HISTORYDATECOUNT)) { + LOGERR("%s(): Invalid field count - should be %d, but is %d", + __func__, fields + HISTORYDATECOUNT, n); + PQclear(res); + return false; + } + + n = PQntuples(res); + LOGDEBUG("%s(): tree build count %d", __func__, n); + ok = true; + K_WLOCK(payments_list); + for (i = 0; i < n; i++) { + item = k_unlink_head(payments_list); + row = DATA_PAYMENTS(item); + + PQ_GET_FLD(res, i, "userid", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("userid", field, row->userid); + + PQ_GET_FLD(res, i, "paydate", field, ok); + if (!ok) + break; + TXT_TO_TV("paydate", field, row->paydate); + + PQ_GET_FLD(res, i, "payaddress", field, ok); + if (!ok) + break; + TXT_TO_STR("payaddress", field, row->payaddress); + + PQ_GET_FLD(res, i, "originaltxn", field, ok); + if (!ok) + break; + TXT_TO_STR("originaltxn", field, row->originaltxn); + + PQ_GET_FLD(res, i, "amount", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("amount", field, row->amount); + + PQ_GET_FLD(res, i, "committxn", field, ok); + if (!ok) + break; + TXT_TO_STR("committxn", field, row->committxn); + + PQ_GET_FLD(res, i, "commitblockhash", field, ok); + if (!ok) + break; + TXT_TO_STR("commitblockhash", field, row->commitblockhash); + + HISTORYDATEFLDS(res, i, row, ok); + if (!ok) + break; + + PQ_GET_FLD(res, i, "paymentid", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("paymentid", field, row->paymentid); + + payments_root = add_to_ktree(payments_root, item, payments_cmp); + k_add_head(payments_store, item); + } + if (!ok) + k_add_head(payments_list, item); + + K_WUNLOCK(payments_list); + PQclear(res); + + if (ok) + LOGDEBUG("%s(): built", __func__); + + return true; +} + +void payments_reload() +{ + PGconn *conn = dbconnect(); + + K_WLOCK(payments_list); + payments_root = free_ktree(payments_root, NULL); + k_list_transfer_to_head(payments_store, payments_list); + K_WUNLOCK(payments_list); + + payments_fill(conn); + + PQfinish(conn); +} + +static void getdata() +{ + PGconn *conn = dbconnect(); + + users_fill(conn); + workers_fill(conn); + payments_fill(conn); + + PQfinish(conn); +} + +static PGconn *dbquit(PGconn *conn) +{ + if (conn != NULL) + PQfinish(conn); + return NULL; +} + +/* Open the file in path, check if there is a pid in there that still exists + * and if not, write the pid into that file. */ +static bool write_pid(ckpool_t *ckp, const char *path, pid_t pid) +{ + struct stat statbuf; + FILE *fp; + int ret; + + if (!stat(path, &statbuf)) { + int oldpid; + + LOGWARNING("File %s exists", path); + fp = fopen(path, "r"); + if (!fp) { + LOGEMERG("Failed to open file %s", path); + return false; + } + ret = fscanf(fp, "%d", &oldpid); + fclose(fp); + if (ret == 1 && !(kill(oldpid, 0))) { + if (!ckp->killold) { + LOGEMERG("Process %s pid %d still exists, start ckpool with -k if you wish to kill it", + path, oldpid); + return false; + } + if (kill(oldpid, 9)) { + LOGEMERG("Unable to kill old process %s pid %d", path, oldpid); + return false; + } + LOGWARNING("Killing off old process %s pid %d", path, oldpid); + } + } + fp = fopen(path, "w"); + if (!fp) { + LOGERR("Failed to open file %s", path); + return false; + } + fprintf(fp, "%d", pid); + fclose(fp); + + return true; +} + +static void create_process_unixsock(proc_instance_t *pi) +{ + unixsock_t *us = &pi->us; + + us->path = strdup(pi->ckp->socket_dir); + realloc_strcat(&us->path, pi->sockname); + LOGDEBUG("Opening %s", us->path); + us->sockd = open_unix_server(us->path); + if (unlikely(us->sockd < 0)) + quit(1, "Failed to open %s socket", pi->sockname); +} + +static void write_namepid(proc_instance_t *pi) +{ + char s[256]; + + pi->pid = getpid(); + sprintf(s, "%s%s.pid", pi->ckp->socket_dir, pi->processname); + if (!write_pid(pi->ckp, s, pi->pid)) + quit(1, "Failed to write %s pid %d", pi->processname, pi->pid); +} + +static void rm_namepid(proc_instance_t *pi) +{ + char s[256]; + + sprintf(s, "%s%s.pid", pi->ckp->socket_dir, pi->processname); + unlink(s); + +} + +static void clean_up(ckpool_t *ckp) +{ + rm_namepid(&ckp->main); + dealloc(ckp->socket_dir); + fclose(ckp->logfp); +} + +static void setup_data() +{ + transfer_list = k_new_list("Transfer", sizeof(TRANSFER), ALLOC_TRANSFER, LIMIT_TRANSFER, true); + transfer_store = k_new_store(transfer_list); + transfer_root = new_ktree(); + + users_list = k_new_list("Users", sizeof(USERS), ALLOC_USERS, LIMIT_USERS, true); + users_store = k_new_store(users_list); + users_root = new_ktree(); + + workers_list = k_new_list("Workers", sizeof(WORKERS), ALLOC_WORKERS, LIMIT_WORKERS, true); + workers_store = k_new_store(workers_list); + workers_root = new_ktree(); + + payments_list = k_new_list("Payments", sizeof(PAYMENTS), ALLOC_PAYMENTS, LIMIT_PAYMENTS, true); + payments_store = k_new_store(payments_list); + payments_root = new_ktree(); + + idcontrol_list = k_new_list("IDControl", sizeof(IDCONTROL), ALLOC_IDCONTROL, LIMIT_IDCONTROL, true); + idcontrol_store = k_new_store(idcontrol_list); + + getdata(); +} + +static double transfer_cmp(K_ITEM *a, K_ITEM *b) +{ + double c = strcmp(DATA_TRANSFER(a)->name, DATA_TRANSFER(b)->name); + return c; +} + +static K_ITEM *find_transfer(char *name) +{ + TRANSFER transfer; + K_TREE_CTX ctx; + K_ITEM look; + + STRNCPY(transfer.name, name); + look.data = (void *)(&transfer); + return find_in_ktree(transfer_root, &look, transfer_cmp, &ctx); +} + +static K_ITEM *require_name(char *name, int len, char *patt, char *reply, size_t siz) +{ + K_ITEM *item; + char *value; + regex_t re; + int ret; + + item = find_transfer(name); + if (!item) { + snprintf(reply, siz, "failed.missing %s", name); + return NULL; + } + + value = DATA_TRANSFER(item)->value; + if (!*value || (int)strlen(value) < len) { + snprintf(reply, siz, "failed.short %s", name); + return NULL; + } + + if (patt) { + if (regcomp(&re, patt, REG_NOSUB) != 0) { + snprintf(reply, siz, "failed.REC %s", name); + return NULL; + } + + ret = regexec(&re, value, (size_t)0, NULL, 0); + regfree(&re); + + if (ret != 0) { + snprintf(reply, siz, "failed.invalid %s", name); + return NULL; + } + } + + return item; +} + +static char *cmd_adduser(char *id, char *by, char *code, char *inet) +{ + char reply[1024] = ""; + size_t siz = sizeof(reply); + + K_ITEM *i_username, *i_emailaddress, *i_passwordhash; + PGconn *conn; + bool ok; + + i_username = require_name("username", 3, (char *)userpatt, reply, siz); + if (!i_username) + return strdup(reply); + + i_emailaddress = require_name("emailaddress", 7, (char *)mailpatt, reply, siz); + if (!i_emailaddress) + return strdup(reply); + + i_passwordhash = require_name("passwordhash", 64, (char *)hashpatt, reply, siz); + if (!i_passwordhash) + return strdup(reply); + + conn = dbconnect(); + ok = users_add(conn, DATA_TRANSFER(i_username)->value, + DATA_TRANSFER(i_emailaddress)->value, + DATA_TRANSFER(i_passwordhash)->value, + by, code, inet); + PQfinish(conn); + + if (!ok) { + STRNCPY(reply, "failed.DBE"); + return strdup(reply); + } + + LOGDEBUG("%s.added.%s", id, DATA_TRANSFER(i_username)->value); + snprintf(reply, siz, "added.%s", DATA_TRANSFER(i_username)->value); + + return strdup(reply); +} + +static char *cmd_chkpass(char *id, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet) +{ + K_ITEM *i_username, *i_passwordhash, *look, *find; + K_TREE_CTX ctx[1]; + USERS *row; + char reply[1024] = ""; + size_t siz = sizeof(reply); + bool ok; + + i_username = require_name("username", 3, (char *)userpatt, reply, siz); + if (!i_username) + return strdup(reply); + + i_passwordhash = require_name("passwordhash", 64, (char *)hashpatt, reply, siz); + if (!i_passwordhash) + return strdup(reply); + + K_WLOCK(users_list); + look = k_unlink_head(users_list); + K_WUNLOCK(users_list); + + row = DATA_USERS(look); + + STRNCPY(row->username, DATA_TRANSFER(i_username)->value); + row->expirydate.tv_sec = default_expiry.tv_sec; + row->expirydate.tv_usec = default_expiry.tv_usec; + + find = find_in_ktree(users_root, look, users_cmp, ctx); + + if (!find) + ok = false; + else { + if (strcasecmp(DATA_TRANSFER(i_passwordhash)->value, DATA_USERS(find)->passwordhash) == 0) + ok = true; + else + ok = false; + } + + K_WLOCK(users_list); + k_add_head(users_list, look); + K_WUNLOCK(users_list); + + if (!ok) + return strdup("bad"); + + LOGDEBUG("%s.login.%s", id, DATA_TRANSFER(i_username)->value); + return strdup("ok"); +} + +static char *cmd_poolstats(char *id, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet) +{ + char reply[1024] = ""; + size_t siz = sizeof(reply); + + LOGDEBUG("%s.stats.ok", id); + printf("%s.stats", id); + snprintf(reply, siz, "stats.ok"); + return strdup(reply); +} + +static char *cmd_newid(char *id, char *by, char *code, char *inet) +{ + char reply[1024] = ""; + size_t siz = sizeof(reply); + K_ITEM *i_idname, *i_idvalue, *look; + IDCONTROL *row; + char *params[10]; + int par; + bool ok = false; + ExecStatusType rescode; + PGresult *res; + PGconn *conn; + char *ins; + int n; + + LOGDEBUG("%s(): add", __func__); + + i_idname = require_name("idname", 3, (char *)idpatt, reply, siz); + if (!i_idname) + return strdup(reply); + + i_idvalue = require_name("idvalue", 1, (char *)intpatt, reply, siz); + if (!i_idvalue) + return strdup(reply); + + K_WLOCK(idcontrol_list); + look = k_unlink_head(idcontrol_list); + K_WUNLOCK(idcontrol_list); + + row = DATA_IDCONTROL(look); + + STRNCPY(row->idname, DATA_TRANSFER(i_idname)->value); + TXT_TO_BIGINT("idvalue", DATA_TRANSFER(i_idvalue)->value, row->lastid); + MODIFYDATEINIT(row, by, code, inet); + + par = 0; + params[par++] = str_to_buf(row->idname, NULL, 0); + params[par++] = bigint_to_buf(row->lastid, NULL, 0); + MODIFYDATEPARAMS(params, par, row); + PARCHK(par, params); + + ins = "insert into idcontrol " + "(idname,lastid" MODIFYDATECONTROL ") values (" PQPARAM10 ")"; + + conn = dbconnect(); + + res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0); + rescode = PQresultStatus(res); + if (rescode != PGRES_COMMAND_OK) { + PGLOGERR("Insert", rescode, conn); + goto foil; + } + + ok = true; +foil: + PQclear(res); + PQfinish(conn); + for (n = 0; n < par; n++) + free(params[n]); + + K_WLOCK(idcontrol_list); + k_add_head(idcontrol_list, look); + K_WUNLOCK(idcontrol_list); + + if (!ok) { + snprintf(reply, siz, "failed.DBE"); + return strdup(reply); + } + + LOGDEBUG("%s.added.%s", id, DATA_TRANSFER(i_idname)->value); + snprintf(reply, siz, "added.%s", DATA_TRANSFER(i_idname)->value); + return strdup(reply); +} + +static char *cmd_payments(char *id, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet) +{ + K_ITEM *i_username, *ulook, *plook, *ufind, *pfind; + K_TREE_CTX ctx[1]; + USERS *urow; + PAYMENTS *prow; + char reply[1024] = ""; + char tmp[1024]; + size_t siz = sizeof(reply); + char *buf; + size_t len, off; + int rows; + + i_username = require_name("username", 3, (char *)userpatt, reply, siz); + if (!i_username) + return strdup(reply); + + K_WLOCK(users_list); + ulook = k_unlink_head(users_list); + K_WUNLOCK(users_list); + urow = DATA_USERS(ulook); + STRNCPY(urow->username, DATA_TRANSFER(i_username)->value); + urow->expirydate.tv_sec = default_expiry.tv_sec; + urow->expirydate.tv_usec = default_expiry.tv_usec; + ufind = find_in_ktree(users_root, ulook, users_cmp, ctx); + if (!ufind) { + K_WLOCK(users_list); + k_add_head(users_list, ulook); + K_WUNLOCK(users_list); + return strdup("bad"); + } + + K_WLOCK(payments_list); + plook = k_unlink_head(payments_list); + K_WUNLOCK(payments_list); + prow = DATA_PAYMENTS(plook); + prow->userid = DATA_USERS(ufind)->userid; + prow->paydate.tv_sec = 0; + prow->paydate.tv_usec = 0; + pfind = find_after_in_ktree(payments_root, plook, payments_cmp, ctx); + len = 1024; + buf = malloc(len); + if (!buf) + quithere(1, "malloc buf (%d) OOM", (int)len); + strcpy(buf, "ok."); + off = strlen(buf); + rows = 0; + while (pfind && DATA_PAYMENTS(pfind)->userid == DATA_USERS(ufind)->userid) { + tv_to_buf(&(DATA_PAYMENTS(pfind)->paydate), reply, sizeof(reply)); + snprintf(tmp, sizeof(tmp), "paydate%d=%s%c", rows, reply, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + + str_to_buf(DATA_PAYMENTS(pfind)->payaddress, reply, sizeof(reply)); + snprintf(tmp, sizeof(tmp), "payaddress%d=%s%c", rows, reply, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + + bigint_to_buf(DATA_PAYMENTS(pfind)->amount, reply, sizeof(reply)); + snprintf(tmp, sizeof(tmp), "amount%d=%s%c", rows, reply, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + + rows++; + pfind = next_in_ktree(ctx); + } + snprintf(tmp, sizeof(tmp), "rows=%d", rows); + APPEND_REALLOC(buf, off, len, tmp); + + K_WLOCK(users_list); + k_add_head(users_list, ulook); + K_WUNLOCK(users_list); + + K_WLOCK(payments_list); + k_add_head(payments_list, plook); + K_WUNLOCK(payments_list); + + LOGDEBUG("%s.payments.%s", id, DATA_TRANSFER(i_username)->value); + return buf; +} + +enum cmd_values { + CMD_UNSET, + CMD_REPLY, // Means something was wrong - send back reply + CMD_SHUTDOWN, + CMD_PING, + CMD_ADDUSER, + CMD_CHKPASS, + CMD_POOLSTAT, + CMD_NEWID, + CMD_PAYMENTS, + CMD_END +}; + +#define ACCESS_POOL "p" +#define ACCESS_SYSTEM "s" +#define ACCESS_WEB "w" +#define ACCESS_PROXY "x" + +static struct CMDS { + enum cmd_values cmd_val; + char *cmd_str; + char *(*func)(char *, char *, char *, char *); + char *access; +} cmds[] = { + { CMD_SHUTDOWN, "shutdown", NULL, ACCESS_SYSTEM }, + { CMD_PING, "ping", NULL, ACCESS_SYSTEM ACCESS_WEB }, +// { CMD_LOGSHARE, "logshares", cmd_logshares, ACCESS_POOL }, + { CMD_ADDUSER, "adduser", cmd_adduser, ACCESS_WEB }, + { CMD_CHKPASS, "chkpass", cmd_chkpass, ACCESS_WEB }, + { CMD_POOLSTAT, "poolstats", cmd_poolstats, ACCESS_WEB }, + { CMD_NEWID, "newid", cmd_newid, ACCESS_SYSTEM }, + { CMD_PAYMENTS, "payments", cmd_payments, ACCESS_WEB }, + { CMD_END, NULL, NULL, NULL } +}; + +static enum cmd_values breakdown(char *buf, int *which_cmds, char *id) +{ + K_TREE_CTX ctx[1]; + K_ITEM *item; + char *copy, *cmd, *data, *next, *eq; + + *which_cmds = CMD_UNSET; + copy = strdup(buf); + cmd = strchr(copy, '.'); + if (!cmd || !*cmd) { + STRNCPYSIZ(id, copy, ID_SIZ); + LOGINFO("Listener received invalid message: '%s'", buf); + free(copy); + return CMD_REPLY; + } + + *(cmd++) = '\0'; + STRNCPYSIZ(id, copy, ID_SIZ); + data = strchr(cmd, '.'); + if (data) + *(data++) = '\0'; + + for (*which_cmds = 0; cmds[*which_cmds].cmd_val != CMD_END; (*which_cmds)++) { + if (strcasecmp(cmd, cmds[*which_cmds].cmd_str) == 0) + break; + } + + if (cmds[*which_cmds].cmd_val == CMD_END) { + LOGINFO("Listener received unknown command: '%s'", buf); + free(copy); + return CMD_REPLY; + } + + next = data; + K_WLOCK(transfer_list); + while (next && *next) { + data = next; + next = strchr(data, 0x02); + if (next) + *(next++) = '\0'; + + eq = strchr(data, '='); + if (!eq) + eq = ""; + else + *(eq++) = '\0'; + + item = k_unlink_head(transfer_list); + STRNCPY(DATA_TRANSFER(item)->name, data); + STRNCPY(DATA_TRANSFER(item)->value, eq); + + if (find_in_ktree(transfer_root, item, transfer_cmp, ctx)) + k_add_head(transfer_list, item); + else { + transfer_root = add_to_ktree(transfer_root, item, transfer_cmp); + k_add_head(transfer_store, item); + } + } + K_WUNLOCK(transfer_list); + + free(copy); + return cmds[*which_cmds].cmd_val; +} + +// TODO: equivalent of api_allow +static void *listener(void *arg) +{ + proc_instance_t *pi = (proc_instance_t *)arg; + unixsock_t *us = &pi->us; + char *end, *ans, *rep, *buf = NULL; + char id[ID_SIZ+1], reply[1024+1]; + enum cmd_values cmd; + int sockd, which_cmds; + size_t siz; + tv_t now; + + rename_proc(pi->sockname); + + setup_data(); + + while (true) { + dealloc(buf); + sockd = accept(us->sockd, NULL, NULL); + if (sockd < 0) { + LOGERR("Failed to accept on socket in listener"); + break; + } + + cmd = CMD_UNSET; + + buf = recv_unix_msg(sockd); + // Once we've read the message + setnow(&now); + if (buf) { + end = buf + strlen(buf) - 1; + // strip trailing \n and \r + while (end >= buf && (*end == '\n' || *end == '\r')) + *(end--) = '\0'; + } + if (!buf || !*buf) { + // An empty message wont get a reply + if (!buf) + LOGWARNING("Failed to get message in listener"); + else + LOGWARNING("Empty message in listener"); + } else { + cmd = breakdown(buf, &which_cmds, id); + switch (cmd) { + case CMD_REPLY: + snprintf(reply, sizeof(reply), "%s.%ld.?", id, now.tv_sec); + send_unix_msg(sockd, reply); + break; + case CMD_SHUTDOWN: + LOGWARNING("Listener received shutdown message, terminating ckdb"); + snprintf(reply, sizeof(reply), "%s.%d.exiting", id, (int)now.tv_sec); + send_unix_msg(sockd, reply); + break; + case CMD_PING: + LOGDEBUG("Listener received ping request"); + snprintf(reply, sizeof(reply), "%s.%ld.pong", id, now.tv_sec); + send_unix_msg(sockd, reply); + break; + default: + // TODO: optionally get by/inet from transfer? + ans = cmds[which_cmds].func(id, (char *)"code", + (char *)__func__, + (char *)"127.0.0.1"); + + siz = strlen(ans) + strlen(id) + 32; + rep = malloc(siz); + snprintf(rep, siz, "%s.%ld.%s", id, now.tv_sec, ans); + free(ans); + ans = NULL; + send_unix_msg(sockd, rep); + free(rep); + rep = NULL; + break; + } + } + close(sockd); + + if (cmd == CMD_SHUTDOWN) + break; + + K_WLOCK(transfer_list); + transfer_root = free_ktree(transfer_root, NULL); + k_list_transfer_to_head(transfer_store, transfer_list); + K_WUNLOCK(transfer_list); + } + + dealloc(buf); + close_unix_socket(us->sockd, us->path); + return NULL; +} + +int main(int argc, char **argv) +{ + struct sigaction handler; + char buf[512]; + ckpool_t ckp; + int c, ret; + char *kill; + + memset(&ckp, 0, sizeof(ckp)); + ckp.loglevel = LOG_NOTICE; + + while ((c = getopt(argc, argv, "c:kl:n:p:s:u:")) != -1) { + switch(c) { + case 'c': + ckp.config = optarg; + break; + case 'k': + ckp.killold = true; + break; + case 'n': + ckp.name = strdup(optarg); + break; + case 'l': + ckp.loglevel = atoi(optarg); + if (ckp.loglevel < LOG_EMERG || ckp.loglevel > LOG_DEBUG) { + quit(1, "Invalid loglevel (range %d - %d): %d", + LOG_EMERG, LOG_DEBUG, ckp.loglevel); + } + break; + case 's': + ckp.socket_dir = strdup(optarg); + break; + case 'u': + db_user = strdup(optarg); + kill = optarg; + while (*kill) + *(kill++) = ' '; + break; + case 'p': + db_pass = strdup(optarg); + kill = optarg; + if (*kill) + *(kill++) = ' '; + while (*kill) + *(kill++) = '\0'; + break; + } + } +// if (!db_pass) +// zzz + if (!db_user) + db_user = "postgres"; + if (!ckp.name) + ckp.name = "ckdb"; + snprintf(buf, 15, "%s", ckp.name); + prctl(PR_SET_NAME, buf, 0, 0, 0); + memset(buf, 0, 15); + + if (!ckp.config) { + ckp.config = strdup(ckp.name); + realloc_strcat(&ckp.config, ".conf"); + } + + if (!ckp.socket_dir) { +// ckp.socket_dir = strdup("/tmp/"); + ckp.socket_dir = strdup("/opt/"); + realloc_strcat(&ckp.socket_dir, ckp.name); + } + trail_slash(&ckp.socket_dir); + + /* Ignore sigpipe */ + signal(SIGPIPE, SIG_IGN); + + ret = mkdir(ckp.socket_dir, 0700); + if (ret && errno != EEXIST) + quit(1, "Failed to make directory %s", ckp.socket_dir); + +// parse_config(&ckp); + + if (!ckp.logdir) + ckp.logdir = strdup("logs"); + + /* Create the log directory */ + trail_slash(&ckp.logdir); + ret = mkdir(ckp.logdir, 0700); + if (ret && errno != EEXIST) + quit(1, "Failed to make log directory %s", ckp.logdir); + + /* Create the logfile */ + sprintf(buf, "%s%s.log", ckp.logdir, ckp.name); + ckp.logfp = fopen(buf, "a"); + if (!ckp.logfp) + quit(1, "Failed to open log file %s", buf); + ckp.logfd = fileno(ckp.logfp); + + ckp.main.ckp = &ckp; + ckp.main.processname = strdup("main"); + ckp.main.sockname = strdup("listener"); + write_namepid(&ckp.main); + create_process_unixsock(&ckp.main); + + srand((unsigned int)time(NULL)); + create_pthread(&ckp.pth_listener, listener, &ckp.main); + + handler.sa_flags = 0; + sigemptyset(&handler.sa_mask); + sigaction(SIGTERM, &handler, NULL); + sigaction(SIGINT, &handler, NULL); + + /* Shutdown from here if the listener is sent a shutdown message */ + join_pthread(ckp.pth_listener); + + clean_up(&ckp); + + return 0; +} diff --git a/src/klist.c b/src/klist.c new file mode 100644 index 00000000..adca979e --- /dev/null +++ b/src/klist.c @@ -0,0 +1,380 @@ +/* + * Copyright 2013-2014 Andrew Smith - BlackArrow Ltd + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "klist.h" + +static void k_alloc_items(K_LIST *list, KLIST_FFL_ARGS) +{ + K_ITEM *item; + int allocate, i; + + if (list->is_store) { + quithere(1, "List %s store can't %s()" KLIST_FFL, + list->name, __func__, KLIST_FFL_PASS); + } + + if (list->limit > 0 && list->total >= list->limit) + return; + + allocate = list->allocate; + if (list->limit > 0 && (list->total + allocate) > list->limit) + allocate = list->limit - list->total; + + list->item_mem_count++; + if (!(list->item_memory = realloc(list->item_memory, + list->item_mem_count * sizeof(*(list->item_memory))))) { + quithere(1, "List %s item_memory failed to realloc count=%d", + list->name, list->item_mem_count); + } + item = calloc(allocate, sizeof(*item)); + if (!item) { + quithere(1, "List %s failed to calloc %d new items - total was %d, limit was %d", + list->name, allocate, list->total, list->limit); + } + list->item_memory[list->item_mem_count - 1] = (void *)item; + + list->total += allocate; + list->count = allocate; + list->count_up = allocate; + + item[0].name = list->name; + item[0].prev = NULL; + item[0].next = &(item[1]); + for (i = 1; i < allocate-1; i++) { + item[i].name = list->name; + item[i].prev = &item[i-1]; + item[i].next = &item[i+1]; + } + item[allocate-1].name = list->name; + item[allocate-1].prev = &(item[allocate-2]); + item[allocate-1].next = NULL; + + list->head = item; + if (list->do_tail) + list->tail = &(item[allocate-1]); + + item = list->head; + while (item) { + list->data_mem_count++; + if (!(list->data_memory = realloc(list->data_memory, + list->data_mem_count * + sizeof(*(list->data_memory))))) { + quithere(1, "List %s data_memory failed to realloc count=%d", + list->name, list->data_mem_count); + } + item->data = calloc(1, list->siz); + if (!(item->data)) + quithere(1, "List %s failed to calloc item data", list->name); + list->data_memory[list->data_mem_count - 1] = (void *)(item->data); + item = item->next; + } +} + +K_STORE *k_new_store(K_LIST *list) +{ + K_STORE *store; + + store = calloc(1, sizeof(*store)); + if (!store) + quithere(1, "Failed to calloc store for %s", list->name); + + store->is_store = true; + store->lock = list->lock; + store->name = list->name; + store->do_tail = list->do_tail; + + return store; +} + +K_LIST *_k_new_list(const char *name, size_t siz, int allocate, int limit, bool do_tail, KLIST_FFL_ARGS) +{ + K_LIST *list; + + if (allocate < 1) + quithere(1, "Invalid new list %s with allocate %d must be > 0", name, allocate); + + if (limit < 0) + quithere(1, "Invalid new list %s with limit %d must be >= 0", name, limit); + + list = calloc(1, sizeof(*list)); + if (!list) + quithere(1, "Failed to calloc list %s", name); + + list->is_store = false; + + list->lock = calloc(1, sizeof(*(list->lock))); + if (!(list->lock)) + quithere(1, "Failed to calloc lock for list %s", name); + + cklock_init(list->lock); + + list->name = name; + list->siz = siz; + list->allocate = allocate; + list->limit = limit; + list->do_tail = do_tail; + + k_alloc_items(list, KLIST_FFL_PASS); + + return list; +} + +/* + * Unlink and return the head of the list + * If the list is empty: + * 1) If it's a store - return NULL + * 2) alloc a new list and return the head - + * which is NULL if the list limit has been reached + */ +K_ITEM *_k_unlink_head(K_LIST *list, KLIST_FFL_ARGS) +{ + K_ITEM *item; + + if (!(list->head) && !(list->is_store)) + k_alloc_items(list, KLIST_FFL_PASS); + + if (!(list->head)) + return NULL; + + item = list->head; + list->head = item->next; + if (list->head) + list->head->prev = NULL; + else { + if (list->do_tail) + list->tail = NULL; + } + + item->prev = item->next = NULL; + + list->count--; + + return item; +} + +// Zeros the head returned +K_ITEM *_k_unlink_head_zero(K_LIST *list, KLIST_FFL_ARGS) +{ + K_ITEM *item; + + item = _k_unlink_head(list, KLIST_FFL_PASS); + + if (item) + memset(item->data, 0, list->siz); + + return item; +} + +// Returns NULL if empty +K_ITEM *_k_unlink_tail(K_LIST *list, KLIST_FFL_ARGS) +{ + K_ITEM *item; + + if (!(list->do_tail)) { + quithere(1, "List %s can't %s() - do_tail is false" KLIST_FFL, + list->name, __func__, KLIST_FFL_PASS); + } + + if (!(list->tail)) + return NULL; + + item = list->tail; + list->tail = item->prev; + if (list->tail) + list->tail->next = NULL; + else + list->head = NULL; + + item->prev = item->next = NULL; + + list->count--; + + return item; +} + +void _k_add_head(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS) +{ + if (item->name != list->name) { + quithere(1, "List %s can't %s() a %s item" KLIST_FFL, + list->name, __func__, item->name, KLIST_FFL_PASS); + } + + item->prev = NULL; + item->next = list->head; + if (list->head) + list->head->prev = item; + + list->head = item; + + if (list->do_tail) { + if (!(list->tail)) + list->tail = item; + } + + list->count++; + list->count_up++; +} + +/* slows it down (of course) - only for debugging +void _k_free_head(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS) +{ + memset(item->data, 0xff, list->siz); + _k_add_head(list, item, KLIST_FFL_PASS); +} +*/ + +void _k_add_tail(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS) +{ + if (item->name != list->name) { + quithere(1, "List %s can't %s() a %s item" KLIST_FFL, + list->name, __func__, item->name, KLIST_FFL_PASS); + } + + if (!(list->do_tail)) { + quithere(1, "List %s can't %s() - do_tail is false" KLIST_FFL, + list->name, __func__, KLIST_FFL_PASS); + } + + item->prev = list->tail; + item->next = NULL; + if (list->tail) + list->tail->next = item; + + list->tail = item; + + if (!(list->head)) + list->head = item; + + list->count++; + list->count_up++; +} + +void _k_unlink_item(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS) +{ + if (item->name != list->name) { + quithere(1, "List %s can't %s() a %s item" KLIST_FFL, + list->name, __func__, item->name, KLIST_FFL_PASS); + } + + if (item->prev) + item->prev->next = item->next; + + if (item->next) + item->next->prev = item->prev; + + if (list->head == item) + list->head = item->next; + + if (list->do_tail) { + if (list->tail == item) + list->tail = item->prev; + } + + item->prev = item->next = NULL; + + list->count--; +} + +void _k_list_transfer_to_head(K_LIST *from, K_LIST *to, KLIST_FFL_ARGS) +{ + if (from->name != to->name) { + quithere(1, "List %s can't %s() to a %s list" KLIST_FFL, + from->name, __func__, to->name, KLIST_FFL_PASS); + } + + if (!(from->do_tail)) { + quithere(1, "List %s can't %s() - do_tail is false" KLIST_FFL, + from->name, __func__, KLIST_FFL_PASS); + } + + if (!(from->head)) + return; + + if (to->head) + to->head->prev = from->tail; + else + to->tail = from->tail; + + from->tail->next = to->head; + to->head = from->head; + + from->head = from->tail = NULL; + to->count += from->count; + from->count = 0; + to->count_up += from->count_up; + from->count_up = 0; +} + +void _k_list_transfer_to_tail(K_LIST *from, K_LIST *to, KLIST_FFL_ARGS) +{ + if (from->name != to->name) { + quithere(1, "List %s can't %s() to a %s list" KLIST_FFL, + from->name, __func__, to->name, KLIST_FFL_PASS); + } + + if (!(from->do_tail)) { + quithere(1, "List %s can't %s() - do_tail is false" KLIST_FFL, + from->name, __func__, KLIST_FFL_PASS); + } + + if (!(from->head)) + return; + + if (to->tail) + to->tail->next = from->head; + else + to->head = from->head; + + from->head->prev = to->tail; + to->tail = from->tail; + + from->head = from->tail = NULL; + to->count += from->count; + from->count = 0; + to->count_up += from->count_up; + from->count_up = 0; +} + +K_LIST *_k_free_list(K_LIST *list, KLIST_FFL_ARGS) +{ + int i; + + if (list->is_store) { + quithere(1, "List %s can't %s() a store" KLIST_FFL, + list->name, __func__, KLIST_FFL_PASS); + } + + for (i = 0; i < list->item_mem_count; i++) + free(list->item_memory[i]); + free(list->item_memory); + + for (i = 0; i < list->data_mem_count; i++) + free(list->data_memory[i]); + free(list->data_memory); + + cklock_destroy(list->lock); + + free(list->lock); + + free(list); + + return NULL; +} + +K_STORE *_k_free_store(K_STORE *store, KLIST_FFL_ARGS) +{ + if (!(store->is_store)) { + quithere(1, "Store %s can't %s() the list" KLIST_FFL, + store->name, __func__, KLIST_FFL_PASS); + } + + free(store); + + return NULL; +} diff --git a/src/klist.h b/src/klist.h new file mode 100644 index 00000000..dcebda3a --- /dev/null +++ b/src/klist.h @@ -0,0 +1,93 @@ +/* + * Copyright 2013-2014 Andrew Smith - BlackArrow Ltd + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef KLIST_H +#define KLIST_H + +#include "libckpool.h" + +#define quithere(status, fmt, ...) \ + quitfrom(status, __FILE__, __func__, __LINE__, fmt, ##__VA_ARGS__) + +#define KLIST_FFL " - from %s %s() line %d" +#define KLIST_FFL_HERE __FILE__, __func__, __LINE__ +#define KLIST_FFL_PASS file, func, line +#define KLIST_FFL_ARGS __maybe_unused const char *file, \ + __maybe_unused const char *func, \ + __maybe_unused const int line + +typedef struct k_item { + const char *name; + struct k_item *prev; + struct k_item *next; + void *data; +} K_ITEM; + +typedef struct k_list { + const char *name; + bool is_store; + cklock_t *lock; + struct k_item *head; + struct k_item *tail; + size_t siz; // item data size + int total; // total allocated + int count; // in this list + int count_up; // incremented every time one is added + int allocate; // number to intially allocate and each time we run out + int limit; // total limit - 0 means unlimited + bool do_tail; // track the tail? + int item_mem_count; // how many item memory buffers have been allocated + void **item_memory; // allocated item memory buffers + int data_mem_count; // how many item data memory buffers have been allocated + void **data_memory; // allocated item data memory buffers +} K_LIST; + +/* + * K_STORE is for a list of items taken from a K_LIST + * The restriction is, a K_STORE must not allocate new items, + * only the K_LIST should do that + * i.e. all K_STORE items came from a K_LIST + */ +#define K_STORE K_LIST + +/* + * N.B. all locking is done in the code using the K_*LOCK macros + */ +#define K_WLOCK(_list) ck_wlock(_list->lock) +#define K_WUNLOCK(_list) ck_wunlock(_list->lock) +#define K_RLOCK(_list) ck_rlock(_list->lock) +#define K_RUNLOCK(_list) ck_runlock(_list->lock) + +extern K_STORE *k_new_store(K_LIST *list); +extern K_LIST *_k_new_list(const char *name, size_t siz, int allocate, int limit, bool do_tail, KLIST_FFL_ARGS); +#define k_new_list(_name, _siz, _allocate, _limit, _do_tail) _k_new_list(_name, _siz, _allocate, _limit, _do_tail, KLIST_FFL_HERE) +extern K_ITEM *_k_unlink_head(K_LIST *list, KLIST_FFL_ARGS); +#define k_unlink_head(_list) _k_unlink_head(_list, KLIST_FFL_HERE) +extern K_ITEM *_k_unlink_head_zero(K_LIST *list, KLIST_FFL_ARGS); +#define k_unlink_head_zero(_list) _k_unlink_head_zero(_list, KLIST_FFL_HERE) +extern K_ITEM *_k_unlink_tail(K_LIST *list, KLIST_FFL_ARGS); +#define k_unlink_tail(_list) _k_unlink_tail(_list, KLIST_FFL_HERE) +extern void _k_add_head(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS); +#define k_add_head(_list, _item) _k_add_head(_list, _item, KLIST_FFL_HERE) +// extern void k_free_head(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS); +#define k_free_head(__list, __item) _k_add_head(__list, __item, KLIST_FFL_HERE) +extern void _k_add_tail(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS); +#define k_add_tail(_list, _item) _k_add_tail(_list, _item, KLIST_FFL_HERE) +extern void _k_unlink_item(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS); +#define k_unlink_item(_list, _item) _k_unlink_item(_list, _item, KLIST_FFL_HERE) +void _k_list_transfer_to_head(K_LIST *from, K_LIST *to, KLIST_FFL_ARGS); +#define k_list_transfer_to_head(_from, _to) _k_list_transfer_to_head(_from, _to, KLIST_FFL_HERE) +void _k_list_transfer_to_tail(K_LIST *from, K_LIST *to, KLIST_FFL_ARGS); +#define k_list_transfer_to_tail(_from, _to) _k_list_transfer_to_tail(_from, _to, KLIST_FFL_HERE) +extern K_LIST *_k_free_list(K_LIST *list, KLIST_FFL_ARGS); +#define k_free_list(_list) _k_free_list(_list, KLIST_FFL_HERE) +extern K_STORE *_k_free_store(K_STORE *store, KLIST_FFL_ARGS); +#define k_free_store(_store) _k_free_store(_store, KLIST_FFL_HERE) + +#endif diff --git a/src/ktree.c b/src/ktree.c new file mode 100644 index 00000000..f6952705 --- /dev/null +++ b/src/ktree.c @@ -0,0 +1,872 @@ +/* + * Copyright 2003-2014 Andrew Smith + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "ktree.h" + +static const int dbg = 0; +#define DBG if (dbg != 0) printf + +#define FAIL(fmt, ...) do \ + { \ + quithere(1, fmt, ##__VA_ARGS__); \ + } while (0); + +#define RED_RED true +#define RED_BLACK false + +#define Yo true +#define No false + +static K_TREE nil[1] = { Yo, RED_BLACK, NULL, NULL, NULL, NULL, 0 }; + +K_TREE *new_ktree() +{ + K_TREE *ktree = (K_TREE *)malloc(sizeof(*ktree)); + + if (ktree == NULL) + FAIL("%s", "%K_TREE-F-OOM"); + + ktree->isNil = Yo; + ktree->red = RED_BLACK; + ktree->parent = nil; + ktree->left = nil; + ktree->right = nil; + ktree->data = NULL; + ktree->test = 0; + + return ktree; +} + +static K_TREE *new_data(K_ITEM *data) +{ + K_TREE *ktree = (K_TREE *)malloc(sizeof(*ktree)); + + if (ktree == NULL) + FAIL("%s", "%K_TREE-F-OOM"); + + ktree->isNil = No; + ktree->red = RED_RED; + ktree->parent = nil; + ktree->left = nil; + ktree->right = nil; + ktree->data = data; + ktree->test = 0; + + return ktree; +} + +static int bCount = 0; +static long bTestValue = 0; +static long nilTestValue = 0; +static long lrpTestValue = 0; + +static long testValue = 1231230L; + +static long getTestValue() +{ + return ++testValue; +} + +static void show_ktree(K_TREE *root, char *path, int pos, char *(*dsp_funct)(K_ITEM *)) +{ + char col; + + if (root->isNil == Yo) + return; + + if (root->left->isNil == No) + { + path[pos] = 'L'; + path[pos+1] = '\0'; + show_ktree(root->left, path, pos+1, dsp_funct); + } + + path[pos] = '\0'; + + switch(root->red) + { + case RED_RED: + col = 'R'; + break; + case RED_BLACK: + col = 'B'; + break; + } + + printf(" %c %s=%s\n", col, path, dsp_funct(root->data)); + + if (root->right->isNil == No) + { + path[pos] = 'R'; + path[pos+1] = '\0'; + show_ktree(root->right, path, pos+1, dsp_funct); + } +} + +void dump_ktree(K_TREE *root, char *(*dsp_funct)(K_ITEM *)) +{ + char buf[42424]; + + printf("dump:\n"); + if (root->isNil == No) + { + buf[0] = 'T'; + buf[1] = '\0'; + show_ktree(root, buf, 1, dsp_funct); + } + else + printf(" Empty ktree\n"); +} + +static int nilTest(K_TREE *node, char *msg, int depth, int count, K_TREE *nil2) +{ + if (node->isNil == Yo || node == nil2) + { + if (node == nil2 && node->isNil == No) + FAIL("%%K_TREE-F-NIL2NOTNIL '%s' depth=%d count=%d", msg, depth, count); + + if (node != nil && node != nil2) + FAIL("%%K_TREE-F-NOTNILNIL2 '%s' depth=%d count=%d", msg, depth, count); + + if (node->red == RED_RED) + FAIL("%%K_TREE-F-NIRED '%s' depth=%d count=%d", msg, depth, count); + + if (node != nil2 && node->parent != NULL) + FAIL("%%K_TREE-F-NILPARENT '%s' depth=%d count=%d", msg, depth, count); + + if (node != nil2 && node->left != NULL) + FAIL("%%K_TREE-F-NILLEFT '%s' depth=%d count=%d", msg, depth, count); + + if (node == nil2 && node->left != nil) + FAIL("%%K_TREE-F-NIL2LEFT '%s' depth=%d count=%d", msg, depth, count); + + if (node != nil2 && node->right != NULL) + FAIL("%%K_TREE-F-NILRIGHT '%s' depth=%d count=%d", msg, depth, count); + + if (node == nil2 && node->right != nil) + FAIL("%%K_TREE-F-NIL2RIGHT '%s' depth=%d count=%d", msg, depth, count); + + if (node->data != NULL) + FAIL("%%K_TREE-F-NIDATA '%s' depth=%d count=%d", msg, depth, count); + } + else + { + count++; + + if (node->parent == NULL) + FAIL("%%K_TREE-F-NOPAR '%s' depth=%d count=%d", msg, depth, count); + + if (node->left == NULL) + FAIL("%%K_TREE-F-NOLEFT '%s' depth=%d count=%d", msg, depth, count); + + if (node->right == NULL) + FAIL("%%K_TREE-F-NORIGHT '%s' depth=%d count=%d", msg, depth, count); + + if (node->data == NULL) + FAIL("%%K_TREE-F-NODATA '%s' depth=%d count=%d", msg, depth, count); + + if (node->test != nilTestValue) + node->test = nilTestValue; + else + FAIL("%%K_TREE-F-NILTESTVALUE '%s' depth=%d count=%d", msg, depth, count); + + count = nilTest(node->left, msg, depth+1, count, nil2); + count = nilTest(node->right, msg, depth+1, count, nil2); + } + + return(count); +} + +static void bTest(K_TREE *root, K_TREE *cur, char *msg, int count) +{ + if (cur->red != RED_RED) + count++; + else + { + if (cur->left->red == RED_RED) + FAIL("%%K_TREE-F-CURLR '%s' count=%d", msg, count); + + if (cur->right->red == RED_RED) + FAIL("%%K_TREE-F-CURRR '%s' count=%d", msg, count); + } + + if (cur->isNil == Yo) + { + if (bCount == 0) + bCount = count; + + if (count != bCount) + FAIL("%%K_TREE-F-BCOUNT '%s' count=%d bCount=%d", msg, count, bCount); + } + else + { + if (cur->test != bTestValue) + cur->test = bTestValue; + else + FAIL("%%K_TREE-F-BTESTVALUE '%s' count=%d", msg, count); + + bTest(root, cur->left, msg, count); + bTest(root, cur->right, msg, count); + } +} + +static void bTestInit(K_TREE *root, char *msg) +{ + bCount = 0; + bTestValue = getTestValue(); + bTest(root, root, msg, 0); +} + +static void lrpTest(K_TREE *top, char *msg) +{ + if (top->test != lrpTestValue) + top->test = lrpTestValue; + else + FAIL("%%K_TREE-F-LRPTESTVALUE '%s'", msg); + + if (top->left->isNil == No) + { + if (top->left->parent != top) + FAIL("%%K_TREE-F-LRPTESTL '%s'", msg); + + lrpTest(top->left, msg); + } + + if (top->right->isNil == No) + { + if (top->right->parent != top) + FAIL("%%K_TREE-F-LRPTESTR '%s'", msg); + + lrpTest(top->right, msg); + } +} + +static __maybe_unused void check_ktree(K_TREE *root, char *msg, K_TREE *nil2, int debugNil, int debugLRP, int debugColor) +{ + if (root->isNil == Yo) + return; + + if (debugNil) + { + nilTestValue = getTestValue(); + nilTest(root, msg, 1, 0, nil2); + } + + if (debugLRP && root->isNil == No) + { + lrpTestValue = getTestValue(); + lrpTest(root, msg); + } + + if (debugColor && root->isNil == No) + bTestInit(root, msg); +} + +K_ITEM *first_in_ktree(K_TREE *root, K_TREE_CTX *ctx) +{ + if (root->isNil == No) + { + while (root->left->isNil == No) + root = root->left; + + *ctx = root; + return(root->data); + } + + *ctx = NULL; + return(NULL); +} + +K_ITEM *last_in_ktree(K_TREE *root, K_TREE_CTX *ctx) +{ + if (root->isNil == No) + { + while (root->right->isNil == No) + root = root->right; + + *ctx = root; + return(root->data); + } + + *ctx = NULL; + return(NULL); +} + +K_ITEM *next_in_ktree(K_TREE_CTX *ctx) +{ + K_TREE *parent; + K_TREE *ktree = (K_TREE *)(*ctx); + + if (ktree->isNil == No) + { + if (ktree->right->isNil == No) + return(first_in_ktree(ktree->right, ctx)); + else + { + parent = ktree->parent; + while (parent->isNil == No && ktree == parent->right) + { + ktree = parent; + parent = parent->parent; + } + if (parent->isNil == No) + { + *ctx = parent; + return(parent->data); + } + } + } + + *ctx = NULL; + return(NULL); +} + +K_ITEM *prev_in_ktree(K_TREE_CTX *ctx) +{ + K_TREE *parent; + K_TREE *ktree = (K_TREE *)(*ctx); + + if (ktree->isNil == No) + { + if (ktree->left->isNil == No) + return(last_in_ktree(ktree->left, ctx)); + else + { + parent = ktree->parent; + while (parent->isNil == No && ktree == parent->left) + { + ktree = parent; + parent = parent->parent; + } + if (parent->isNil == No) + { + *ctx = parent; + return(parent->data); + } + } + } + + *ctx = NULL; + return(NULL); +} + +static K_TREE *left_rotate(K_TREE *root, K_TREE *about) +{ + K_TREE *rotate; + + rotate = about->right; + about->right = rotate->left; + + if (rotate->left->isNil == No) + rotate->left->parent = about; + + rotate->parent = about->parent; + + if (about->parent->isNil == Yo) + root = rotate; + else + { + if (about == about->parent->left) + about->parent->left = rotate; + else + about->parent->right = rotate; + } + + rotate->left = about; + about->parent = rotate; + + return(root); +} + +static K_TREE *right_rotate(K_TREE *root, K_TREE *about) +{ + K_TREE *rotate; + + rotate = about->left; + about->left = rotate->right; + + if (rotate->right->isNil == No) + rotate->right->parent = about; + + rotate->parent = about->parent; + + if (about->parent->isNil == Yo) + root = rotate; + else + if (about == about->parent->right) + about->parent->right = rotate; + else + about->parent->left = rotate; + + rotate->right = about; + about->parent = rotate; + + return(root); +} + +K_TREE *add_to_ktree(K_TREE *root, K_ITEM *data, double (*cmp_funct)(K_ITEM *, K_ITEM *)) +{ + K_TREE *ktree; + K_TREE *x, *y; + K_TREE *pp; + double cmp; + + if (root == NULL) + FAIL("%s", "%K_TREE-F-ADDNULL add ktree is NULL"); + +//check_ktree(root, ">add", NULL, 1, 1, 1); + + if (root->parent != nil && root->parent != NULL) + FAIL("%s", "%K_TREE-F-ADDROOT add root isn't the root"); + + ktree = new_data(data); + + if (root->isNil == Yo) + { + if (root != nil) + free(root); + + root = ktree; + } + else + { + x = root; + y = nil; + while (x->isNil == No) + { + y = x; + if ((cmp = (*cmp_funct)(ktree->data, x->data)) < 0.0) + x = x->left; + else + x = x->right; + } + ktree->parent = y; + if (cmp < 0.0) + y->left = ktree; + else + y->right = ktree; + + x = ktree; + while (x != root && x->parent->red == RED_RED) + { + pp = x->parent->parent; + if (x->parent == pp->left) + { + y = pp->right; + if (y->red == RED_RED) + { + x->parent->red = RED_BLACK; + y->red = RED_BLACK; + pp->red = RED_RED; + x = pp; + } + else + { + if (x == x->parent->right) + { + x = x->parent; + root = left_rotate(root, x); + pp = x->parent->parent; + } + x->parent->red = RED_BLACK; + pp->red = RED_RED; + root = right_rotate(root, pp); + } + } + else + { + y = pp->left; + if (y->red == RED_RED) + { + x->parent->red = RED_BLACK; + y->red = RED_BLACK; + pp->red = RED_RED; + x = pp; + } + else + { + if (x == x->parent->left) + { + x = x->parent; + root = right_rotate(root, x); + pp = x->parent->parent; + } + x->parent->red = RED_BLACK; + pp->red = RED_RED; + root = left_rotate(root, pp); + } + } + } + } + root->red = RED_BLACK; + +//check_ktree(root, "isNil == No && cmp != 0.0) + { + if ((cmp = (*cmp_funct)(ktree->data, data))) + { + if (cmp > 0.0) + ktree = ktree->left; + else + ktree = ktree->right; + } + } + + if (ktree->isNil == No) + { + *ctx = ktree; + return(ktree->data); + } + else + { + *ctx = NULL; + return(NULL); + } +} + +K_ITEM *find_after_in_ktree(K_TREE *ktree, K_ITEM *data, double (*cmp_funct)(K_ITEM *, K_ITEM *), K_TREE_CTX *ctx) +{ + K_TREE *prev = NULL; + double cmp = -1.0, prevcmp = -1; + + while (ktree->isNil == No && cmp != 0.0) + { + if ((cmp = (*cmp_funct)(ktree->data, data))) + { + prev = ktree; + prevcmp = cmp; + if (cmp > 0.0) + ktree = ktree->left; + else + ktree = ktree->right; + } + } + + if (ktree->isNil == No) + { + *ctx = ktree; + return next_in_ktree(ctx); + } + else + { + if (prev) + { + if (prevcmp > 0.0) + { + *ctx = prev; + return(prev->data); + } + + *ctx = prev; + return next_in_ktree(ctx); + } + + *ctx = NULL; + return(NULL); + } +} + +static K_TREE *removeFixup(K_TREE *root, K_TREE *fix) +{ + K_TREE *w = NULL; + + while (fix != root && fix->red != RED_RED) + { + if (fix == fix->parent->left) + { + w = fix->parent->right; + if (w->red == RED_RED) + { + w->red = RED_BLACK; + fix->parent->red = RED_RED; + root = left_rotate(root, fix->parent); + w = fix->parent->right; + } + + if (w->left->red != RED_RED && w->right->red != RED_RED) + { + w->red = RED_RED; + fix = fix->parent; + } + else + { + if (w->right->red != RED_RED) + { + w->left->red = RED_BLACK; + w->red = RED_RED; + root = right_rotate(root, w); + w = fix->parent->right; + } + + w->red = fix->parent->red; + fix->parent->red = RED_BLACK; + w->right->red = RED_BLACK; + root = left_rotate(root, fix->parent); + fix = root; + } + } + else + { + w = fix->parent->left; + if (w->red == RED_RED) + { + w->red = RED_BLACK; + fix->parent->red = RED_RED; + root = right_rotate(root, fix->parent); + w = fix->parent->left; + } + + if (w->right->red != RED_RED && w->left->red != RED_RED) + { + w->red = RED_RED; + fix = fix->parent; + } + else + { + if (w->left->red != RED_RED) + { + w->right->red = RED_BLACK; + w->red = RED_RED; + root = left_rotate(root, w); + w = fix->parent->left; + } + + w->red = fix->parent->red; + fix->parent->red = RED_BLACK; + w->left->red = RED_BLACK; + root = right_rotate(root, fix->parent); + fix = root; + } + } + } + + fix->red = RED_BLACK; + + return root; +} + +// Does this work OK when you remove the last element in the ktree? +// It should return the root as 'nil' +K_TREE *remove_from_ktree(K_TREE *root, K_ITEM *data, double (*cmp_funct)(K_ITEM *, K_ITEM *), K_TREE_CTX *ctx) +{ + K_TREE_CTX tmpctx[1]; + K_TREE *found; + K_ITEM *fdata; + K_TREE *x, *y, *nil2; + // double cmp; + int yred; + +//check_ktree(root, ">remove", NULL, 1, 1, 1); + + if (root == NULL) + FAIL("%s", "%K_TREE-F-REMNULL remove ktree is NULL"); + + if (root->isNil == Yo) + { + *ctx = NULL; + return(root); + } + + if (root->parent->isNil == No) + FAIL("%s", "%K_TREE-F-REMROOT remove root isn't the root"); + + fdata = find_in_ktree(root, data, cmp_funct, ctx); + + if (fdata == NULL) + return(root); + + if (cmp_funct(fdata, data) != 0.0) + FAIL("%s", "%K_TREE-F-BADFIND cmp(found, remove) != 0"); + + found = *ctx; + + x = nil; + y = nil; + + if (found->left->isNil == Yo || found->right->isNil == Yo) + y = found; + else + { + *tmpctx = found; + next_in_ktree(tmpctx); + y = *tmpctx; + } + + yred = y->red; + + if (y->left->isNil == No && y->right->isNil == No) + FAIL("%s", "%K_TREE-F-REMBADY remove error"); + + if (y->left->isNil == Yo) + x = y->right; + else + x = y->left; + + if (x != nil) + nil2 = NULL; + else + { + nil2 = new_ktree(); + x = nil2; + } + + x->parent = y->parent; + + if (x->parent->isNil == Yo) + root = x; + else + { + if (x->parent->left == y) + x->parent->left = x; + else + x->parent->right = x; + } + + if (y != found) + { + if (root == found) + root = y; + + if (x == found) + x = y; + + y->red = found->red; + y->parent = found->parent; + + if (y->parent->isNil == No) + { + if (y->parent->left == found) + y->parent->left = y; + else + y->parent->right = y; + } + + y->left = found->left; + if (y->left->isNil == No || y->left == nil2) + y->left->parent = y; + + y->right = found->right; + if (y->right->isNil == No || y->right == nil2) + y->right->parent = y; + } + + if (yred != RED_RED) + root = removeFixup(root, x); + + if (nil2 != NULL) + { + if (nil2->parent->isNil == No && nil2->parent->left == nil2) + nil2->parent->left = nil; + + if (nil2->parent->isNil == No && nil2->parent->right == nil2) + nil2->parent->right = nil; + + if (root == nil2) + root = nil; + +/* +if (dbg != 0) +{ + if (nil2->left != nil) + { +DBG("@remove nil2->left wasn't nil!!!\n"); + } + if (nil2->right != nil) + { +DBG("@remove nil2->right wasn't nil!!!\n"); + } + cmp = 0.0; + fdata = first_in_ktree(root, tmpctx);; + while (fdata != NULL) + { + cmp++; + x = *tmpctx; + if (x == nil2) + { +DBG("@remove found nil2 in ktree %f!!!\n", cmp); + } + else + if (x->left == nil2) + { +DBG("@remove found nil2 in ktree(left) %f!!!\n", cmp); + } + else + if (x->right == nil2) + { +DBG("@remove found nil2 in ktree(right) %f!!!\n", cmp); + } + + fdata = next_in_ktree(tmpctx);; + } +} +*/ + free(nil2); + } + +/* +if (dbg != 0) +{ + cmp = 0.0 + fdata = first_in_ktree(root, tmpctx);; + while (fdata != NULL) + { + if (cmp_funct(fdata, root->data) < 0) + cmp--; + else + cmp++; + + fdata = next_in_ktree(tmpctx);; + } + if (cmp < -10.0 || cmp > 10.0) + { +DBG("@remove after balance=%f :(\n", cmp); + } +} +*/ + + +//check_ktree(root, "data != NULL && free_funct) + (*free_funct)(ktree->data); + + free_ktree_sub(ktree->left, free_funct); + free_ktree_sub(ktree->right, free_funct); + + free(ktree); + } +} + +K_TREE *free_ktree(K_TREE *ktree, void (*free_funct)(void *)) +{ + if (ktree == NULL) + FAIL("%s", "%K_TREE-F-FREENULL free NULL ktree"); + + if (ktree->parent != NULL && ktree->parent != nil) + FAIL("%s", "%K_TREE-F-FREENOTROOT free ktree not root"); + + free_ktree_sub(ktree, free_funct); + + return(nil); +} diff --git a/src/ktree.h b/src/ktree.h new file mode 100644 index 00000000..3fa19fba --- /dev/null +++ b/src/ktree.h @@ -0,0 +1,44 @@ +/* + * Copyright 2003-2014 Andrew Smith + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef ___KTREE_H +#define ___KTREE_H + +#include "klist.h" +#include "libckpool.h" + +#define quithere(status, fmt, ...) \ + quitfrom(status, __FILE__, __func__, __LINE__, fmt, ##__VA_ARGS__) + +typedef struct ktree +{ + bool isNil; + bool red; + struct ktree *parent; + struct ktree *left; + struct ktree *right; + K_ITEM *data; + long test; +} K_TREE; + +typedef void *K_TREE_CTX; + +extern K_TREE *new_ktree(); +extern void dump_ktree(K_TREE *root, char *(*dsp_funct)(K_ITEM *)); +extern K_ITEM *first_in_ktree(K_TREE *root, K_TREE_CTX *ctx); +extern K_ITEM *last_in_ktree(K_TREE *root, K_TREE_CTX *ctx); +extern K_ITEM *next_in_ktree(K_TREE_CTX *ctx); +extern K_ITEM *prev_in_ktree(K_TREE_CTX *ctx); +extern K_TREE *add_to_ktree(K_TREE *root, K_ITEM *data, double (*cmp_func)(K_ITEM *, K_ITEM *)); +extern K_ITEM *find_in_ktree(K_TREE *root, K_ITEM *data, double (*cmp_funct)(K_ITEM *, K_ITEM *), K_TREE_CTX *ctx); +extern K_ITEM *find_after_in_ktree(K_TREE *ktree, K_ITEM *data, double (*cmp_funct)(K_ITEM *, K_ITEM *), K_TREE_CTX *ctx); +extern K_TREE *remove_from_ktree(K_TREE *root, K_ITEM *data, double (*cmp_funct)(K_ITEM *, K_ITEM *), K_TREE_CTX *ctx); +extern K_TREE *free_ktree(K_TREE *root, void (*free_funct)(void *)); + +#endif