From 48ffb1d1094e01a85ba3459044d1e3db4e5da27e Mon Sep 17 00:00:00 2001 From: kanoi Date: Tue, 16 Sep 2014 22:14:22 +1000 Subject: [PATCH] ckdb - optioncontrol table and commands - getopts/setopts --- src/ckdb.c | 675 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 649 insertions(+), 26 deletions(-) diff --git a/src/ckdb.c b/src/ckdb.c index 43a94834..31db9f31 100644 --- a/src/ckdb.c +++ b/src/ckdb.c @@ -49,7 +49,7 @@ #define DB_VLOCK "1" #define DB_VERSION "0.9.2" -#define CKDB_VERSION DB_VERSION"-0.303" +#define CKDB_VERSION DB_VERSION"-0.305" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ @@ -236,6 +236,7 @@ static LOADSTATUS dbstatus; // Share stats since last block typedef struct poolstatus { int64_t workinfoid; // Last block + int32_t height; double diffacc; double diffinv; // Non-acc double shareacc; @@ -314,6 +315,8 @@ static char *pqerrmsg(PGconn *conn) { char *ptr, *buf = strdup(PQerrorMessage(conn)); + if (!buf) + quithere(1, "malloc OOM"); ptr = buf + strlen(buf) - 1; while (ptr >= buf && (*ptr == '\n' || *ptr == '\r')) *(ptr--) = '\0'; @@ -388,7 +391,7 @@ enum data_type { #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_CTV(__nam, __fld, __data) txt_to_ctv(__nam, __fld, &(__data), sizeof(__data)) -#define TXT_TO_BLOB(__nam, __fld, __data) txt_to_blob(__nam, __fld, __data) +#define TXT_TO_BLOB(__nam, __fld, __data) txt_to_blob(__nam, __fld, &(__data)) #define TXT_TO_DOUBLE(__nam, __fld, __data) txt_to_double(__nam, __fld, &(__data), sizeof(__data)) #define PQ_GET_FLD(__res, __row, __name, __fld, __ok) do { \ @@ -803,6 +806,8 @@ enum cmd_values { CMD_GETATTS, CMD_SETATTS, CMD_EXPATTS, + CMD_GETOPTS, + CMD_SETOPTS, CMD_DSP, CMD_STATS, CMD_PPLNS, @@ -1135,7 +1140,6 @@ typedef struct idcontrol { static K_LIST *idcontrol_free; static K_STORE *idcontrol_store; -/* unused yet // OPTIONCONTROL typedef struct optioncontrol { char optionname[TXT_SML+1]; @@ -1149,11 +1153,14 @@ typedef struct optioncontrol { #define LIMIT_OPTIONCONTROL 0 #define INIT_OPTIONCONTROL(_item) INIT_GENERIC(_item, optioncontrol) #define DATA_OPTIONCONTROL(_var, _item) DATA_GENERIC(_var, _item, optioncontrol, true) +#define DATA_OPTIONCONTROL_NULL(_var, _item) DATA_GENERIC(_var, _item, optioncontrol, false) + +// Value it must default to (to work properly) +#define OPTIONCONTROL_HEIGHT 1 static K_TREE *optioncontrol_root; static K_LIST *optioncontrol_free; static K_STORE *optioncontrol_store; -*/ // TODO: discarding workinfo,shares // WORKINFO workinfo.id.json={...} @@ -1535,6 +1542,8 @@ static K_STORE *userstats_summ; ((_old)->tv_sec < (_new)->tv_sec)) #define tv_equal(_a, _b) (((_a)->tv_sec == (_b)->tv_sec) && \ ((_a)->tv_usec == (_b)->tv_usec)) +// newer OR equal +#define tv_newer_eq(_old, _new) (!(tv_newer(_new, _old))) // WORKERSTATUS from various incoming data typedef struct workerstatus { @@ -1716,9 +1725,9 @@ static void _txt_to_ctv(char *nam, char *fld, tv_t *data, size_t siz, WHERE_FFL_ _txt_to_data(TYPE_CTV, nam, fld, (void *)data, siz, WHERE_FFL_PASS); } -static void _txt_to_blob(char *nam, char *fld, char *data, WHERE_FFL_ARGS) +static void _txt_to_blob(char *nam, char *fld, char **data, WHERE_FFL_ARGS) { - _txt_to_data(TYPE_BLOB, nam, fld, (void *)(&data), 0, WHERE_FFL_PASS); + _txt_to_data(TYPE_BLOB, nam, fld, (void *)data, 0, WHERE_FFL_PASS); } static void _txt_to_double(char *nam, char *fld, double *data, size_t siz, WHERE_FFL_ARGS) @@ -1760,7 +1769,7 @@ static char *_data_to_buf(enum data_type typ, void *data, char *buf, size_t siz, buf = malloc(siz); if (!buf) - quithere(1, "OOM (%d)" WHERE_FFL, (int)siz, WHERE_FFL_PASS); + quithere(1, "(%d) OOM" WHERE_FFL, (int)siz, WHERE_FFL_PASS); } switch (typ) { @@ -1896,6 +1905,8 @@ static void log_queue_message(char *msg) lq_item = k_unlink_head(logqueue_free); DATA_LOGQUEUE(lq, lq_item); lq->msg = strdup(msg); + if (!(lq->msg)) + quithere(1, "malloc (%d) OOM", (int)strlen(msg)); k_add_tail(logqueue_store, lq_item); K_WUNLOCK(logqueue_free); } @@ -3164,7 +3175,7 @@ static bool useratts_item_add(PGconn *conn, K_ITEM *ua_item, tv_t *cd, bool begu char *upd, *ins; bool ok = false; char *params[9 + HISTORYDATECOUNT]; - int n, par = 0; + int n, par; LOGDEBUG("%s(): add", __func__); @@ -3178,6 +3189,7 @@ static bool useratts_item_add(PGconn *conn, K_ITEM *ua_item, tv_t *cd, bool begu /* N.B. the values of the old ua_item record, if it exists, * are completely ignored i.e. you must provide all values required */ + par = 0; if (!conn) { conn = dbconnect(); conned = true; @@ -3187,11 +3199,11 @@ static bool useratts_item_add(PGconn *conn, K_ITEM *ua_item, tv_t *cd, bool begu // Beginning of a write txn res = PQexec(conn, "Begin", CKPQ_WRITE); rescode = PQresultStatus(res); + PQclear(res); if (!PGOK(rescode)) { PGLOGERR("Begin", rescode, conn); goto unparam; } - PQclear(res); } if (old_item) { @@ -3206,6 +3218,7 @@ static bool useratts_item_add(PGconn *conn, K_ITEM *ua_item, tv_t *cd, bool begu res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); rescode = PQresultStatus(res); + PQclear(res); if (!PGOK(rescode)) { PGLOGERR("Update", rescode, conn); goto unparam; @@ -3235,22 +3248,23 @@ static bool useratts_item_add(PGconn *conn, K_ITEM *ua_item, tv_t *cd, bool begu res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); rescode = PQresultStatus(res); + PQclear(res); + if (!PGOK(rescode)) { + PGLOGERR("Insert", rescode, conn); + goto rollback; + } + + ok = true; +rollback: if (!begun) { - PQclear(res); - if (!PGOK(rescode)) { - PGLOGERR("Insert", rescode, conn); + if (ok) + res = PQexec(conn, "Commit", CKPQ_WRITE); + else res = PQexec(conn, "Rollback", CKPQ_WRITE); - goto unparam; - } - res = PQexec(conn, "Commit", CKPQ_WRITE); - ok = true; - } else { - if (PGOK(rescode)) - ok = true; + PQclear(res); } unparam: - PQclear(res); if (conned) PQfinish(conn); for (n = 0; n < par; n++) @@ -3258,6 +3272,7 @@ unparam: K_WLOCK(useratts_free); if (ok) { + // Update it if (old_item) { useratts_root = remove_from_ktree(useratts_root, old_item, cmp_useratts, ctx); copy_tv(&(old_useratts->expirydate), cd); @@ -3333,10 +3348,11 @@ static __maybe_unused K_ITEM *useratts_add(PGconn *conn, char *username, char *a ok = useratts_item_add(conn, item, cd, begun); unitem: - K_WLOCK(useratts_free); - if (!ok) + if (!ok) { + K_WLOCK(useratts_free); k_add_head(useratts_free, item); - K_WUNLOCK(useratts_free); + K_WUNLOCK(useratts_free); + } if (ok) return item; @@ -4407,6 +4423,350 @@ void payments_reload() PQfinish(conn); } +// order by optionname asc,activationdate asc,activationheight asc,expirydate desc +static cmp_t cmp_optioncontrol(K_ITEM *a, K_ITEM *b) +{ + OPTIONCONTROL *oca, *ocb; + DATA_OPTIONCONTROL(oca, a); + DATA_OPTIONCONTROL(ocb, b); + cmp_t c = CMP_STR(oca->optionname, ocb->optionname); + if (c == 0) { + c = CMP_TV(oca->activationdate, ocb->activationdate); + if (c == 0) { + c = CMP_INT(oca->activationheight, ocb->activationheight); + if (c == 0) + c = CMP_TV(ocb->expirydate, oca->expirydate); + } + } + return c; +} + +// Must be R or W locked before call +static K_ITEM *find_optioncontrol(char *optionname, tv_t *now) +{ + OPTIONCONTROL optioncontrol, *oc, *ocbest; + K_TREE_CTX ctx[1]; + K_ITEM look, *item, *best; + + /* Step through all records having optionaname and check: + * 1) activationdate is <= now + * and + * 2) height <= current + * Remember the active record with the newest activationdate + * If two records have the same activation date, then + * remember the active record with the highest height + * In optioncontrol_add(), when not specified, + * the default activation date is DATE_BEGIN + * and the default height is 1 (OPTIONCONTROL_HEIGHT) + * Thus if records have both values set, then + * activationdate will determine the newests record + * To have activationheight decide selection, + * create all records with only activationheight and then + * activationdate will all be the default value and not + * decide the outcome */ + STRNCPY(optioncontrol.optionname, optionname); + optioncontrol.activationdate.tv_sec = 0L; + optioncontrol.activationdate.tv_usec = 0L; + optioncontrol.activationheight = OPTIONCONTROL_HEIGHT - 1; + optioncontrol.expirydate.tv_sec = default_expiry.tv_sec; + optioncontrol.expirydate.tv_usec = default_expiry.tv_usec; + + INIT_OPTIONCONTROL(&look); + look.data = (void *)(&optioncontrol); + item = find_after_in_ktree(optioncontrol_root, &look, cmp_optioncontrol, ctx); + ocbest = NULL; + best = NULL; + while (item) { + DATA_OPTIONCONTROL(oc, item); + // Ordered first by optionname + if (strcmp(oc->optionname, optionname) != 0) + break; + + // Is oc active? + if (CURRENT(&(oc->expirydate)) && + oc->activationheight <= pool.height && + tv_newer_eq(&(oc->activationdate), now)) { + // Is oc newer than ocbest? + if (!ocbest || + tv_newer(&(ocbest->activationdate), &(oc->activationdate)) || + (tv_equal(&(ocbest->activationdate), &(oc->activationdate)) && + ocbest->activationheight < oc->activationheight)) { + ocbest = oc; + best = item; + } + } + item = next_in_ktree(ctx); + } + return best; +} + +static K_ITEM *optioncontrol_item_add(PGconn *conn, K_ITEM *oc_item, tv_t *cd, bool begun) +{ + ExecStatusType rescode; + bool conned = false; + K_TREE_CTX ctx[1]; + PGresult *res; + K_ITEM *old_item, look; + int n; + OPTIONCONTROL *row; + char *upd, *ins; + bool ok = false; + char *params[4 + HISTORYDATECOUNT]; + int par; + + LOGDEBUG("%s(): add", __func__); + + DATA_OPTIONCONTROL(row, oc_item); + + INIT_OPTIONCONTROL(&look); + look.data = (void *)row; + K_RLOCK(optioncontrol_free); + old_item = find_in_ktree(optioncontrol_root, &look, cmp_optioncontrol, ctx); + K_RUNLOCK(optioncontrol_free); + + par = 0; + if (!conn) { + conn = dbconnect(); + conned = true; + } + + if (!begun) { + res = PQexec(conn, "Begin", CKPQ_WRITE); + rescode = PQresultStatus(res); + PQclear(res); + if (!PGOK(rescode)) { + PGLOGERR("Begin", rescode, conn); + goto nostart; + } + } + + if (old_item) { + upd = "update optioncontrol " + "set expirydate=$1 where optionname=$2 and " + "activationdate=$3 and activationheight=$4 and " + "expirydate=$5"; + + par = 0; + params[par++] = tv_to_buf(cd, NULL, 0); + params[par++] = str_to_buf(row->optionname, NULL, 0); + params[par++] = tv_to_buf(&(row->activationdate), NULL, 0); + params[par++] = int_to_buf(row->activationheight, NULL, 0); + params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); + PARCHKVAL(par, 5, params); + + res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); + rescode = PQresultStatus(res); + PQclear(res); + if (!PGOK(rescode)) { + PGLOGERR("Update", rescode, conn); + goto rollback; + } + + for (n = 0; n < par; n++) + free(params[n]); + } + + par = 0; + params[par++] = str_to_buf(row->optionname, NULL, 0); + params[par++] = str_to_buf(row->optionvalue, NULL, 0); + params[par++] = tv_to_buf(&(row->activationdate), NULL, 0); + params[par++] = int_to_buf(row->activationheight, NULL, 0); + HISTORYDATEPARAMS(params, par, row); + PARCHK(par, params); + + ins = "insert into optioncontrol " + "(optionname,optionvalue,activationdate,activationheight" + HISTORYDATECONTROL ") values (" PQPARAM9 ")"; + + res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); + rescode = PQresultStatus(res); + PQclear(res); + if (!PGOK(rescode)) { + PGLOGERR("Insert", rescode, conn); + goto rollback; + } + + ok = true; +rollback: + if (!begun) { + if (ok) + res = PQexec(conn, "Commit", CKPQ_WRITE); + else + res = PQexec(conn, "Rollback", CKPQ_WRITE); + + PQclear(res); + } +nostart: + if (conned) + PQfinish(conn); + for (n = 0; n < par; n++) + free(params[n]); + + K_WLOCK(optioncontrol_free); + if (!ok) + k_add_head(optioncontrol_free, oc_item); + else { + // Discard it + if (old_item) { + optioncontrol_root = remove_from_ktree(optioncontrol_root, old_item, + cmp_optioncontrol, ctx); + k_add_head(optioncontrol_free, old_item); + } + optioncontrol_root = add_to_ktree(optioncontrol_root, oc_item, cmp_optioncontrol); + k_add_head(optioncontrol_store, oc_item); + } + K_WUNLOCK(optioncontrol_free); + + if (ok) + return oc_item; + else + return NULL; +} + +static __maybe_unused K_ITEM *optioncontrol_add(PGconn *conn, char *optionname, char *optionvalue, + char *activationdate, char *activationheight, + char *by, char *code, char *inet, tv_t *cd, + K_TREE *trf_root, bool begun) +{ + K_ITEM *item; + OPTIONCONTROL *row; + bool ok = false; + + LOGDEBUG("%s(): add", __func__); + + K_WLOCK(optioncontrol_free); + item = k_unlink_head(optioncontrol_free); + K_WUNLOCK(optioncontrol_free); + + DATA_OPTIONCONTROL(row, item); + + STRNCPY(row->optionname, optionname); + row->optionvalue = strdup(optionvalue); + if (!(row->optionvalue)) + quithere(1, "malloc (%d) OOM", (int)strlen(optionvalue)); + if (activationdate && *activationdate) { + TXT_TO_CTV("activationdate", activationdate, + row->activationdate); + } else + copy_tv(&(row->activationdate), &date_begin); + if (activationheight && *activationheight) { + TXT_TO_INT("activationheight", activationheight, + row->activationheight); + } else + row->activationheight = 1; + + HISTORYDATEINIT(row, cd, by, code, inet); + HISTORYDATETRANSFER(trf_root, row); + + ok = optioncontrol_item_add(conn, item, cd, begun); + + if (!ok) { + free(row->optionvalue); + K_WLOCK(optioncontrol_free); + k_add_head(optioncontrol_free, item); + K_WUNLOCK(optioncontrol_free); + } + + if (ok) + return item; + else + return NULL; +} + +static bool optioncontrol_fill(PGconn *conn) +{ + ExecStatusType rescode; + PGresult *res; + K_ITEM *item; + int n, i; + OPTIONCONTROL *row; + char *params[1]; + int par; + char *field; + char *sel; + int fields = 4; + bool ok; + + LOGDEBUG("%s(): select", __func__); + + // No need to keep old versions in ram for now ... + sel = "select " + "optionname,optionvalue,activationdate,activationheight" + HISTORYDATECONTROL + " from optioncontrol 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, CKPQ_READ); + rescode = PQresultStatus(res); + if (!PGOK(rescode)) { + 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(optioncontrol_free); + for (i = 0; i < n; i++) { + item = k_unlink_head(optioncontrol_free); + DATA_OPTIONCONTROL(row, item); + + if (everyone_die) { + ok = false; + break; + } + + PQ_GET_FLD(res, i, "optionname", field, ok); + if (!ok) + break; + TXT_TO_STR("optionname", field, row->optionname); + + PQ_GET_FLD(res, i, "optionvalue", field, ok); + if (!ok) + break; + TXT_TO_BLOB("optionvalue", field, row->optionvalue); + + PQ_GET_FLD(res, i, "activationdate", field, ok); + if (!ok) + break; + TXT_TO_TV("activationdate", field, row->activationdate); + + PQ_GET_FLD(res, i, "activationheight", field, ok); + if (!ok) + break; + TXT_TO_INT("activationheight", field, row->activationheight); + + HISTORYDATEFLDS(res, i, row, ok); + if (!ok) + break; + + optioncontrol_root = add_to_ktree(optioncontrol_root, item, cmp_optioncontrol); + k_add_head(optioncontrol_store, item); + } + if (!ok) + k_add_head(optioncontrol_free, item); + + K_WUNLOCK(optioncontrol_free); + PQclear(res); + + if (ok) { + LOGDEBUG("%s(): built", __func__); + LOGWARNING("%s(): loaded %d optioncontrol records", __func__, n); + } + + return ok; +} + // order by workinfoid asc,expirydate asc static cmp_t cmp_workinfo(K_ITEM *a, K_ITEM *b) { @@ -4513,7 +4873,11 @@ static int64_t workinfo_add(PGconn *conn, char *workinfoidstr, char *poolinstanc TXT_TO_BIGINT("workinfoid", workinfoidstr, row->workinfoid); STRNCPY(row->poolinstance, poolinstance); row->transactiontree = strdup(transactiontree); + if (!(row->transactiontree)) + quithere(1, "malloc (%d) OOM", (int)strlen(transactiontree)); row->merklehash = strdup(merklehash); + if (!(row->merklehash)) + quithere(1, "malloc (%d) OOM", (int)strlen(merklehash)); STRNCPY(row->prevhash, prevhash); STRNCPY(row->coinbase1, coinbase1); STRNCPY(row->coinbase2, coinbase2); @@ -6565,6 +6929,7 @@ flail: pool.diffacc, est, pct, cd_buf); if (pool.workinfoid < row->workinfoid) { pool.workinfoid = row->workinfoid; + pool.height = row->height; zero_on_new_block(); } break; @@ -6728,8 +7093,10 @@ static bool blocks_fill(PGconn *conn) if (tv_newer(&(dbstatus.newest_createdate_blocks), &(row->createdate))) copy_tv(&(dbstatus.newest_createdate_blocks), &(row->createdate)); - if (pool.workinfoid < row->workinfoid) + if (pool.workinfoid < row->workinfoid) { pool.workinfoid = row->workinfoid; + pool.height = row->height; + } } if (!ok) k_add_head(blocks_free, item); @@ -8052,6 +8419,8 @@ static bool check_db_version(PGconn *conn) /* Load tables required to support auths,adduser,chkpass and newid * N.B. idcontrol is DB internal so is always ready + * OptionControl is loaded first in case it is needed by other loads + * (though not yet) */ static bool getdata1() { @@ -8060,6 +8429,8 @@ static bool getdata1() if (!(ok = check_db_version(conn))) goto matane; + if (!(ok = optioncontrol_fill(conn))) + goto matane; if (!(ok = users_fill(conn))) goto matane; if (!(ok = workers_fill(conn))) @@ -8293,6 +8664,12 @@ static void alloc_storage() useratts_store = k_new_store(useratts_free); useratts_root = new_ktree(); + optioncontrol_free = k_new_list("OptionControl", sizeof(OPTIONCONTROL), + ALLOC_OPTIONCONTROL, + LIMIT_OPTIONCONTROL, true); + optioncontrol_store = k_new_store(optioncontrol_free); + optioncontrol_root = new_ktree(); + workers_free = k_new_list("Workers", sizeof(WORKERS), ALLOC_WORKERS, LIMIT_WORKERS, true); workers_store = k_new_store(workers_free); @@ -10511,7 +10888,7 @@ static char *cmd_setatts(PGconn *conn, char *cmd, char *id, char reply[1024] = ""; size_t siz = sizeof(reply); TRANSFER *transfer; - USERATTS *useratts; + USERATTS *useratts = NULL; USERS *users; char attname[sizeof(useratts->attname)*2]; char *reason = NULL; @@ -10742,6 +11119,239 @@ rats: return strdup(reply); } +/* Return the list of optioncontrols + * Format is optlist=optionname,optionname,optionname,... + * Replies will be optionname=value + * Any optionnames not in the DB or not yet active will be missing + */ +static char *cmd_getopts(__maybe_unused PGconn *conn, char *cmd, char *id, + tv_t *now, __maybe_unused char *by, + __maybe_unused char *code, __maybe_unused char *inet, + __maybe_unused tv_t *notcd, K_TREE *trf_root) +{ + K_ITEM *i_optlist, *oc_item; + char reply[1024] = ""; + size_t siz = sizeof(reply); + char tmp[1024]; + OPTIONCONTROL *optioncontrol; + char *reason = NULL; + char *answer = NULL; + char *optlist = NULL, *ptr, *comma; + size_t len, off; + bool first; + + LOGDEBUG("%s(): cmd '%s'", __func__, cmd); + + i_optlist = require_name(trf_root, "optlist", 1, NULL, reply, siz); + if (!i_optlist) { + reason = "Missing optlist"; + goto ruts; + } + + APPEND_REALLOC_INIT(answer, off, len); + optlist = ptr = strdup(transfer_data(i_optlist)); + first = true; + while (ptr && *ptr) { + comma = strchr(ptr, ','); + if (comma) + *(comma++) = '\0'; + K_RLOCK(optioncontrol_free); + oc_item = find_optioncontrol(ptr, now); + K_RUNLOCK(optioncontrol_free); + /* web code must check the existance of the optionname + * in the reply since it will be missing if it doesn't + * exist in the DB */ + if (oc_item) { + DATA_OPTIONCONTROL(optioncontrol, oc_item); + snprintf(tmp, sizeof(tmp), "%s%s=%s", + first ? EMPTY : FLDSEPSTR, + optioncontrol->optionname, + optioncontrol->optionvalue); + APPEND_REALLOC(answer, off, len, tmp); + first = false; + } + ptr = comma; + } +ruts: + if (optlist) + free(optlist); + + if (reason) { + if (answer) + free(answer); + snprintf(reply, siz, "ERR.%s", reason); + LOGERR("%s.%s.%s", cmd, id, reply); + return strdup(reply); + } + snprintf(reply, siz, "ok.%s", answer); + LOGDEBUG("%s.%s", id, answer); + free(answer); + return strdup(reply); +} + +// This is the same as att_set_date() for now +#define opt_set_date(_date, _data, _now) att_set_date(_date, _data, _now) + +/* Store optioncontrols in the DB + * Format is 1 or more: oc_optionname.fld=value + * i.e. each starts with the constant "oc_" + * optionname cannot contain Tab . or = + * fld is one of the 3: value, date, height + * value must exist + * None, one or both of date and height can exist + * If a matching optioncontrol (same name, date and height) exists, + * it will have it's expiry date set to now and be replaced with the new value + * The date field requires either an epoch sec,usec + * (usec is optional and defaults to 0) or one of: now or now+NNN + * now is the current epoch value and now+NNN is the epoch + NNN seconds + * See opt_set_date() above */ +static char *cmd_setopts(PGconn *conn, char *cmd, char *id, + tv_t *now, char *by, char *code, char *inet, + __maybe_unused tv_t *notcd, K_TREE *trf_root) +{ + ExecStatusType rescode; + PGresult *res; + bool conned = false; + K_ITEM *t_item, *oc_item = NULL; + K_TREE_CTX ctx[1]; + char reply[1024] = ""; + size_t siz = sizeof(reply); + TRANSFER *transfer; + OPTIONCONTROL *optioncontrol; + char optionname[sizeof(optioncontrol->optionname)*2]; + char *reason = NULL; + char *dot, *data; + bool begun = false, gotvalue = false; + int db = 0; + + LOGDEBUG("%s(): cmd '%s'", __func__, cmd); + + t_item = first_in_ktree(trf_root, ctx); + while (t_item) { + DATA_TRANSFER(transfer, t_item); + if (strncmp(transfer->name, "oc_", 3) == 0) { + data = transfer_data(t_item); + STRNCPY(optionname, transfer->name + 3); + dot = strchr(optionname, '.'); + if (!dot) { + reason = "Missing field"; + goto rollback; + } + *(dot++) = '\0'; + // If we already had a different one, save it to the DB + if (oc_item && strcmp(optioncontrol->optionname, optionname) != 0) { + if (!gotvalue) { + reason = "Missing value"; + goto rollback; + } + if (conn == NULL) { + conn = dbconnect(); + conned = true; + } + if (!begun) { + // Beginning of a write txn + res = PQexec(conn, "Begin", CKPQ_WRITE); + rescode = PQresultStatus(res); + PQclear(res); + if (!PGOK(rescode)) { + PGLOGERR("Begin", rescode, conn); + reason = "DBERR"; + goto rollback; + } + begun = true; + } + if (optioncontrol_item_add(conn, oc_item, now, begun)) { + oc_item = NULL; + db++; + } else { + reason = "DBERR"; + goto rollback; + } + } + if (!oc_item) { + K_RLOCK(optioncontrol_free); + oc_item = k_unlink_head(optioncontrol_free); + K_RUNLOCK(optioncontrol_free); + DATA_OPTIONCONTROL(optioncontrol, oc_item); + bzero(optioncontrol, sizeof(*optioncontrol)); + STRNCPY(optioncontrol->optionname, optionname); + optioncontrol->activationheight = OPTIONCONTROL_HEIGHT; + HISTORYDATEINIT(optioncontrol, now, by, code, inet); + HISTORYDATETRANSFER(trf_root, optioncontrol); + gotvalue = false; + } + if (strcmp(dot, "value") == 0) { + optioncontrol->optionvalue = strdup(data); + if (!(optioncontrol->optionvalue)) + quithere(1, "malloc (%d) OOM", (int)strlen(data)); + gotvalue = true; + } else if (strcmp(dot, "date") == 0) { + att_to_date(&(optioncontrol->activationdate), data, now); + } else if (strcmp(dot, "height") == 0) { + TXT_TO_INT("height", data, optioncontrol->activationheight); + } else { + reason = "Unknown field"; + goto rollback; + } + } + t_item = next_in_ktree(ctx); + } + if (oc_item) { + if (!gotvalue) { + reason = "Missing value"; + goto rollback; + } + if (conn == NULL) { + conn = dbconnect(); + conned = true; + } + if (!begun) { + // Beginning of a write txn + res = PQexec(conn, "Begin", CKPQ_WRITE); + rescode = PQresultStatus(res); + PQclear(res); + if (!PGOK(rescode)) { + PGLOGERR("Begin", rescode, conn); + reason = "DBERR"; + goto rollback; + } + begun = true; + } + if (!optioncontrol_item_add(conn, oc_item, now, begun)) { + reason = "DBERR"; + goto rollback; + } + db++; + } +rollback: + if (begun) { + if (reason) + res = PQexec(conn, "Rollback", CKPQ_WRITE); + else + res = PQexec(conn, "Commit", CKPQ_WRITE); + + PQclear(res); + } + + if (conned) + PQfinish(conn); + if (reason) { + if (oc_item) { + if (optioncontrol->optionvalue) + free(optioncontrol->optionvalue); + K_WLOCK(optioncontrol_free); + k_add_head(optioncontrol_free, oc_item); + K_WUNLOCK(optioncontrol_free); + } + snprintf(reply, siz, "ERR.%s", reason); + LOGERR("%s.%s.%s", cmd, id, reply); + return strdup(reply); + } + snprintf(reply, siz, "ok.set %d", db); + LOGDEBUG("%s.%s", id, reply); + return strdup(reply); +} + // order by userid asc static cmp_t cmp_mu(K_ITEM *a, K_ITEM *b) { @@ -11327,6 +11937,8 @@ static struct CMDS { { CMD_GETATTS, "getatts", false, false, cmd_getatts, ACCESS_WEB }, { CMD_SETATTS, "setatts", false, false, cmd_setatts, ACCESS_WEB }, { CMD_EXPATTS, "expatts", false, false, cmd_expatts, ACCESS_WEB }, + { CMD_GETOPTS, "getopts", false, false, cmd_getopts, ACCESS_WEB }, + { CMD_SETOPTS, "setopts", false, false, cmd_setopts, ACCESS_WEB }, { CMD_DSP, "dsp", false, false, cmd_dsp, ACCESS_SYSTEM }, { CMD_STATS, "stats", true, false, cmd_stats, ACCESS_SYSTEM }, { CMD_PPLNS, "pplns", false, false, cmd_pplns, ACCESS_SYSTEM }, @@ -11996,6 +12608,7 @@ static void *socketer(__maybe_unused void *arg) char *last_userset = NULL, *reply_userset = NULL; char *last_newid = NULL, *reply_newid = NULL; char *last_setatts = NULL, *reply_setatts = NULL; + char *last_setopts = NULL, *reply_setopts = NULL; char *last_web = NULL, *reply_web = NULL; char *reply_last, duptype[CMD_SIZ+1]; enum cmd_values cmdnum; @@ -12090,6 +12703,9 @@ static void *socketer(__maybe_unused void *arg) } else if (last_setatts && strcmp(last_setatts, buf) == 0) { reply_last = reply_setatts; dup = true; + } else if (last_setopts && strcmp(last_setopts, buf) == 0) { + reply_last = reply_setopts; + dup = true; } else if (last_web && strcmp(last_web, buf) == 0) { reply_last = reply_web; dup = true; @@ -12179,6 +12795,8 @@ static void *socketer(__maybe_unused void *arg) case CMD_GETATTS: case CMD_SETATTS: case CMD_EXPATTS: + case CMD_GETOPTS: + case CMD_SETOPTS: case CMD_BLOCKLIST: case CMD_NEWID: case CMD_STATS: @@ -12218,6 +12836,9 @@ static void *socketer(__maybe_unused void *arg) case CMD_SETATTS: STORELASTREPLY(setatts); break; + case CMD_SETOPTS: + STORELASTREPLY(setopts); + break; // The rest default: free(rep); @@ -12416,6 +13037,8 @@ static bool reload_line(PGconn *conn, char *filename, uint64_t count, char *buf) case CMD_GETATTS: case CMD_SETATTS: case CMD_EXPATTS: + case CMD_GETOPTS: + case CMD_SETOPTS: case CMD_DSP: case CMD_STATS: case CMD_PPLNS: @@ -12490,7 +13113,7 @@ static bool reload_from(tv_t *start) reload_buf = malloc(MAX_READ); if (!reload_buf) - quithere(1, "OOM"); + quithere(1, "(%d) OOM", MAX_READ); reloading = true;