diff --git a/src/ckdb.c b/src/ckdb.c index 8105d607..43a94834 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.302" +#define CKDB_VERSION DB_VERSION"-0.303" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ @@ -802,6 +802,7 @@ enum cmd_values { CMD_HOMEPAGE, CMD_GETATTS, CMD_SETATTS, + CMD_EXPATTS, CMD_DSP, CMD_STATS, CMD_PPLNS, @@ -3343,6 +3344,74 @@ unitem: return NULL; } +static bool useratts_item_expire(PGconn *conn, K_ITEM *ua_item, tv_t *cd) +{ + ExecStatusType rescode; + bool conned = false; + K_TREE_CTX ctx[1]; + PGresult *res; + K_ITEM *item; + USERATTS *useratts; + char *upd; + bool ok = false; + char *params[4 + HISTORYDATECOUNT]; + int n, par = 0; + + LOGDEBUG("%s(): add", __func__); + + DATA_USERATTS(useratts, ua_item); + + /* This is pointless if ua_item is part of the tree, however, + * it allows for if ua_item isn't already part of the tree */ + K_RLOCK(useratts_free); + item = find_useratts(useratts->userid, useratts->attname); + K_RUNLOCK(useratts_free); + + if (item) { + DATA_USERATTS(useratts, item); + + if (!conn) { + conn = dbconnect(); + conned = true; + } + + upd = "update useratts set expirydate=$1 where userid=$2 and " + "attname=$3 and expirydate=$4"; + par = 0; + params[par++] = tv_to_buf(cd, NULL, 0); + params[par++] = bigint_to_buf(useratts->userid, NULL, 0); + params[par++] = str_to_buf(useratts->attname, NULL, 0); + params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); + PARCHKVAL(par, 4, params); + + res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); + rescode = PQresultStatus(res); + if (!PGOK(rescode)) { + PGLOGERR("Update", rescode, conn); + goto unparam; + } + } + ok = true; +unparam: + if (par) { + PQclear(res); + if (conned) + PQfinish(conn); + for (n = 0; n < par; n++) + free(params[n]); + } + + K_WLOCK(useratts_free); + if (ok && item) { + useratts_root = remove_from_ktree(useratts_root, item, cmp_useratts, ctx); + copy_tv(&(useratts->expirydate), cd); + useratts_root = add_to_ktree(useratts_root, item, cmp_useratts); + } + K_WUNLOCK(useratts_free); + + return ok; +} + static bool useratts_fill(PGconn *conn) { ExecStatusType rescode; @@ -10261,6 +10330,15 @@ static char *cmd_homepage(__maybe_unused PGconn *conn, char *cmd, char *id, return buf; } +/* Return the list of useratts for the given username=value + * Format is attlist=attname.element,attname.element,... + * Replies will be attname.element=value + * The 2 date fields, date and date2, have a secondary element name + * dateexp and date2exp + * This will return Y or N depending upon if the date has expired as: + * attname.dateexp=N (or Y) and attname.date2exp=N (or Y) + * Expired means the date is <= now + */ static char *cmd_getatts(__maybe_unused PGconn *conn, char *cmd, char *id, tv_t *now, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet, @@ -10274,7 +10352,7 @@ static char *cmd_getatts(__maybe_unused PGconn *conn, char *cmd, char *id, USERS *users; char *reason = NULL; char *answer = NULL; - char *ptr, *comma, *dot; + char *attlist = NULL, *ptr, *comma, *dot; size_t len, off; bool first; @@ -10302,7 +10380,7 @@ static char *cmd_getatts(__maybe_unused PGconn *conn, char *cmd, char *id, } APPEND_REALLOC_INIT(answer, off, len); - ptr = strdup(transfer_data(i_attlist)); + attlist = ptr = strdup(transfer_data(i_attlist)); first = true; while (ptr && *ptr) { comma = strchr(ptr, ','); @@ -10310,7 +10388,6 @@ static char *cmd_getatts(__maybe_unused PGconn *conn, char *cmd, char *id, *(comma++) = '\0'; dot = strchr(ptr, '.'); if (!dot) { - free(answer); reason = "Missing element"; goto nuts; } @@ -10346,7 +10423,7 @@ static char *cmd_getatts(__maybe_unused PGconn *conn, char *cmd, char *id, sizeof(num_buf)); ans = ctv_buf; } else if (strcmp(dot, "dateexp") == 0) { - // Y/N if date is before now (not expired) + // Y/N if date is <= now (expired) if (tv_newer(&(useratts->attdate), now)) ans = TRUE_STR; else @@ -10357,13 +10434,12 @@ static char *cmd_getatts(__maybe_unused PGconn *conn, char *cmd, char *id, sizeof(num_buf)); ans = ctv_buf; } else if (strcmp(dot, "date2exp") == 0) { - // Y/N if date2 is before now (not expired) + // Y/N if date2 is <= now (expired) if (tv_newer(&(useratts->attdate2), now)) ans = TRUE_STR; else ans = FALSE_STR; } else { - free(answer); reason = "Unknown element"; goto nuts; } @@ -10377,7 +10453,12 @@ static char *cmd_getatts(__maybe_unused PGconn *conn, char *cmd, char *id, } } nuts: + if (attlist) + free(attlist); + if (reason) { + if (answer) + free(answer); snprintf(reply, siz, "ERR.%s", reason); LOGERR("%s.%s.%s", cmd, id, reply); return strdup(reply); @@ -10403,6 +10484,21 @@ static void att_to_date(tv_t *date, char *data, tv_t *now) } } +/* Store useratts in the DB for the given username=value + * Format is 1 or more: ua_attname.element=value + * i.e. each starts with the constant "ua_" + * attname cannot contain Tab . or = + * element is per the coded list below, which also cannot contain Tab . or = + * Any matching useratts attnames found currently in the DB are expired + * Transfer will sort them so that any of the same attname + * will be next to each other + * thus will combine multiple elements for the same attname + * into one single useratts record (as is mandatory) + * The 2 date fields date and date2 require either epoch values 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 att_to_date() above + * */ static char *cmd_setatts(PGconn *conn, char *cmd, char *id, tv_t *now, char *by, char *code, char *inet, __maybe_unused tv_t *notcd, K_TREE *trf_root) @@ -10421,6 +10517,7 @@ static char *cmd_setatts(PGconn *conn, char *cmd, char *id, char *reason = NULL; char *dot, *data; bool begun = false; + int set = 0, db = 0; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); @@ -10439,12 +10536,6 @@ static char *cmd_setatts(PGconn *conn, char *cmd, char *id, goto bats; } else { DATA_USERS(users, u_item); - /* format is: ua_attname.element=value - * i.e. eash starts with the constant "ua_" - * transfer will sort them so that any of the same attname - * will be next to each other - * thus can combine multiple elements for the same attname - * into one single useratts record (as is mandatory) */ t_item = first_in_ktree(trf_root, ctx); while (t_item) { DATA_TRANSFER(transfer, t_item); @@ -10475,9 +10566,10 @@ static char *cmd_setatts(PGconn *conn, char *cmd, char *id, } begun = true; } - if (useratts_item_add(conn, ua_item, now, begun)) + if (useratts_item_add(conn, ua_item, now, begun)) { ua_item = NULL; - else { + db++; + } else { res = PQexec(conn, "Rollback", CKPQ_WRITE); PQclear(res); reason = "DBERR"; @@ -10495,18 +10587,25 @@ static char *cmd_setatts(PGconn *conn, char *cmd, char *id, HISTORYDATEINIT(useratts, now, by, code, inet); HISTORYDATETRANSFER(trf_root, useratts); } + // List of valid element names for storage if (strcmp(dot, "str") == 0) { STRNCPY(useratts->attstr, data); + set++; } else if (strcmp(dot, "str2") == 0) { STRNCPY(useratts->attstr2, data); + set++; } else if (strcmp(dot, "num") == 0) { TXT_TO_BIGINT("num", data, useratts->attnum); + set++; } else if (strcmp(dot, "num2") == 0) { TXT_TO_BIGINT("num2", data, useratts->attnum2); + set++; } else if (strcmp(dot, "date") == 0) { att_to_date(&(useratts->attdate), data, now); + set++; } else if (strcmp(dot, "date2") == 0) { att_to_date(&(useratts->attdate2), data, now); + set++; } else { reason = "Unknown element"; goto bats; @@ -10537,6 +10636,7 @@ static char *cmd_setatts(PGconn *conn, char *cmd, char *id, reason = "DBERR"; goto bats; } + db++; res = PQexec(conn, "Commit", CKPQ_WRITE); PQclear(res); } @@ -10554,11 +10654,94 @@ bats: LOGERR("%s.%s.%s", cmd, id, reply); return strdup(reply); } - snprintf(reply, siz, "ok.set"); + snprintf(reply, siz, "ok.set %d,%d", db, set); LOGDEBUG("%s.%s", id, reply); return strdup(reply); } +/* Expire the list of useratts for the given username=value + * Format is attlist=attname,attname,... + * Each matching DB attname record will have it's expirydate set to now + * thus an attempt to access it with getatts will not find it and + * return nothing for that attname + */ +static char *cmd_expatts(__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_username, *i_attlist, *u_item, *ua_item; + char reply[1024] = ""; + size_t siz = sizeof(reply); + USERATTS *useratts; + USERS *users; + char *reason = NULL; + char *attlist, *ptr, *comma; + int db = 0, mis = 0; + + LOGDEBUG("%s(): cmd '%s'", __func__, cmd); + + i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz); + if (!i_username) { + reason = "Missing username"; + goto rats; + } + + K_RLOCK(users_free); + u_item = find_users(transfer_data(i_username)); + K_RUNLOCK(users_free); + + if (!u_item) { + reason = "Unknown user"; + goto rats; + } else { + DATA_USERS(users, u_item); + i_attlist = require_name(trf_root, "attlist", 1, NULL, reply, siz); + if (!i_attlist) { + reason = "Missing attlist"; + goto rats; + } + + attlist = ptr = strdup(transfer_data(i_attlist)); + while (ptr && *ptr) { + comma = strchr(ptr, ','); + if (comma) + *(comma++) = '\0'; + K_RLOCK(useratts_free); + ua_item = find_useratts(users->userid, ptr); + K_RUNLOCK(useratts_free); + if (!ua_item) + mis++; + else { + DATA_USERATTS(useratts, ua_item); + HISTORYDATEINIT(useratts, now, by, code, inet); + HISTORYDATETRANSFER(trf_root, useratts); + /* Since we are expiring records, don't bother + * with combining them all into a single + * transaction and don't abort on error + * Thus if an error is returned, retry would be + * necessary, but some may also have been + * expired successfully */ + if (!useratts_item_expire(conn, ua_item, now)) + reason = "DBERR"; + else + db++; + } + ptr = comma; + } + free(attlist); + } +rats: + if (reason) { + snprintf(reply, siz, "ERR.%s", reason); + LOGERR("%s.%s.%s", cmd, id, reply); + return strdup(reply); + } + snprintf(reply, siz, "ok.exp %d,%d", db, mis); + LOGDEBUG("%s.%s.%s", cmd, id, reply); + return strdup(reply); +} + // order by userid asc static cmp_t cmp_mu(K_ITEM *a, K_ITEM *b) { @@ -11143,6 +11326,7 @@ static struct CMDS { { CMD_HOMEPAGE, "homepage", false, false, cmd_homepage, ACCESS_WEB }, { 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_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 }, @@ -11994,6 +12178,7 @@ static void *socketer(__maybe_unused void *arg) case CMD_USERSET: case CMD_GETATTS: case CMD_SETATTS: + case CMD_EXPATTS: case CMD_BLOCKLIST: case CMD_NEWID: case CMD_STATS: @@ -12230,6 +12415,7 @@ static bool reload_line(PGconn *conn, char *filename, uint64_t count, char *buf) case CMD_HOMEPAGE: case CMD_GETATTS: case CMD_SETATTS: + case CMD_EXPATTS: case CMD_DSP: case CMD_STATS: case CMD_PPLNS: