You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6152 lines
158 KiB
6152 lines
158 KiB
/* |
|
* Copyright 1995-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 "ckdb.h" |
|
|
|
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'; |
|
while (--ptr >= buf) { |
|
if (*ptr == '\n' || *ptr == '\r' || *ptr == '\t') |
|
*ptr = ' '; |
|
} |
|
return buf; |
|
} |
|
|
|
#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 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 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) |
|
|
|
// MODIFY FIELDS |
|
#define MODIFYDATEFLDPOINTERS(_list, _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; \ |
|
SET_CREATEBY(_list, (_data)->createby, _fld); \ |
|
PQ_GET_FLD(_res, _row, "createcode", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
SET_CREATECODE(_list, (_data)->createcode, _fld); \ |
|
PQ_GET_FLD(_res, _row, "createinet", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
SET_CREATEINET(_list, (_data)->createinet, _fld); \ |
|
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; \ |
|
SET_MODIFYBY(_list, (_data)->modifyby, _fld); \ |
|
PQ_GET_FLD(_res, _row, "modifycode", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
SET_MODIFYCODE(_list, (_data)->modifycode, _fld); \ |
|
PQ_GET_FLD(_res, _row, "modifyinet", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
SET_MODIFYINET(_list, (_data)->modifyinet, _fld); \ |
|
} while (0) |
|
|
|
#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 MODIFYUPDATEPARAMS(_params, _mod_pos, _row) do { \ |
|
_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) |
|
|
|
// SIMPLE FIELDS |
|
#define SIMPLEDATEFLDS(_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); \ |
|
} while (0) |
|
|
|
#define SIMPLEDATEPARAMS(_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); \ |
|
} 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 PQPARAM8 ",$9" |
|
#define PQPARAM10 PQPARAM8 ",$9,$10" |
|
#define PQPARAM11 PQPARAM8 ",$9,$10,$11" |
|
#define PQPARAM12 PQPARAM8 ",$9,$10,$11,$12" |
|
#define PQPARAM13 PQPARAM8 ",$9,$10,$11,$12,$13" |
|
#define PQPARAM14 PQPARAM8 ",$9,$10,$11,$12,$13,$14" |
|
#define PQPARAM15 PQPARAM8 ",$9,$10,$11,$12,$13,$14,$15" |
|
#define PQPARAM16 PQPARAM8 ",$9,$10,$11,$12,$13,$14,$15,$16" |
|
#define PQPARAM22 PQPARAM16 ",$17,$18,$19,$20,$21,$22" |
|
#define PQPARAM27 PQPARAM22 ",$23,$24,$25,$26,$27" |
|
|
|
#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) |
|
|
|
#define PARCHKVAL(_par, _val, _params) do { \ |
|
if (_par != _val) { \ |
|
quithere(1, "params[] usage (%d) != expected (%d)", \ |
|
_par, _val); \ |
|
} \ |
|
if (_val > (int)(sizeof(_params)/sizeof(_params[0]))) { \ |
|
quithere(1, "params[] usage (%d) > size (%d)", \ |
|
_val, (int)(sizeof(_params)/sizeof(_params[0]))); \ |
|
} \ |
|
} while (0) |
|
|
|
#undef PQexec |
|
#undef PQexecParams |
|
|
|
// Bug check to ensure no unexpected write txns occur |
|
PGresult *_CKPQexec(PGconn *conn, const char *qry, bool isread, WHERE_FFL_ARGS) |
|
{ |
|
// It would slow it down, but could check qry for insert/update/... |
|
if (!isread && confirm_sharesummary) |
|
quitfrom(1, file, func, line, "BUG: write txn during confirm"); |
|
|
|
return PQexec(conn, qry); |
|
} |
|
|
|
PGresult *_CKPQexecParams(PGconn *conn, const char *qry, |
|
int nParams, |
|
const Oid *paramTypes, |
|
const char *const * paramValues, |
|
const int *paramLengths, |
|
const int *paramFormats, |
|
int resultFormat, |
|
bool isread, WHERE_FFL_ARGS) |
|
{ |
|
// It would slow it down, but could check qry for insert/update/... |
|
if (!isread && confirm_sharesummary) |
|
quitfrom(1, file, func, line, "BUG: write txn during confirm"); |
|
|
|
return PQexecParams(conn, qry, nParams, paramTypes, paramValues, paramLengths, |
|
paramFormats, resultFormat); |
|
} |
|
|
|
#define PQexec CKPQexec |
|
#define PQexecParams CKPQexecParams |
|
|
|
int64_t nextid(PGconn *conn, char *idname, int64_t increment, |
|
tv_t *cd, char *by, char *code, char *inet) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
char qry[1024]; |
|
char *params[5]; |
|
int n, par = 0; |
|
int64_t lastid; |
|
char *field; |
|
bool ok; |
|
|
|
lastid = 0; |
|
|
|
snprintf(qry, sizeof(qry), "select lastid from idcontrol " |
|
"where idname='%s' for update", |
|
idname); |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
res = PQexec(conn, qry, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
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); |
|
|
|
par = 0; |
|
params[par++] = bigint_to_buf(lastid, NULL, 0); |
|
params[par++] = tv_to_buf(cd, 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, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Update", rescode, conn); |
|
lastid = 0; |
|
} |
|
|
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
cleanup: |
|
PQclear(res); |
|
if (conned) |
|
PQfinish(conn); |
|
return lastid; |
|
} |
|
|
|
// status was added to the end so type checking intercepts new mistakes |
|
bool users_update(PGconn *conn, K_ITEM *u_item, char *oldhash, |
|
char *newhash, char *email, char *by, char *code, |
|
char *inet, tv_t *cd, K_TREE *trf_root, char *status) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
K_ITEM *item; |
|
USERS *row, *users; |
|
char *upd, *ins; |
|
bool ok = false; |
|
char *params[6 + HISTORYDATECOUNT]; |
|
bool hash; |
|
int n, par = 0; |
|
|
|
LOGDEBUG("%s(): change", __func__); |
|
|
|
if (oldhash != NULL) |
|
hash = true; |
|
else |
|
hash = false; |
|
|
|
DATA_USERS(users, u_item); |
|
// i.e. if oldhash == EMPTY, just overwrite with new |
|
if (hash && oldhash != EMPTY && !check_hash(users, oldhash)) |
|
return false; |
|
|
|
K_WLOCK(users_free); |
|
item = k_unlink_head(users_free); |
|
K_WUNLOCK(users_free); |
|
|
|
DATA_USERS(row, item); |
|
memcpy(row, users, sizeof(*row)); |
|
|
|
// Update each one supplied |
|
if (hash) { |
|
// New salt each password change |
|
make_salt(row); |
|
password_hash(row->username, newhash, row->salt, |
|
row->passwordhash, sizeof(row->passwordhash)); |
|
} |
|
if (email) |
|
STRNCPY(row->emailaddress, email); |
|
if (status) |
|
STRNCPY(row->status, status); |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
upd = "update users set expirydate=$1 where userid=$2 and expirydate=$3"; |
|
par = 0; |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); |
|
PARCHKVAL(par, 3, params); |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
// 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; |
|
} |
|
|
|
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++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
// Copy them all in - at least one will be new |
|
params[par++] = str_to_buf(row->status, NULL, 0); |
|
params[par++] = str_to_buf(row->emailaddress, NULL, 0); |
|
params[par++] = str_to_buf(row->passwordhash, NULL, 0); |
|
// New salt for each password change (or recopy old) |
|
params[par++] = str_to_buf(row->salt, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHKVAL(par, 6 + HISTORYDATECOUNT, params); // 11 as per ins |
|
|
|
ins = "insert into users " |
|
"(userid,username,status,emailaddress,joineddate," |
|
"passwordhash,secondaryuserid,salt" |
|
HISTORYDATECONTROL ") select " |
|
"userid,username,$3,$4,joineddate," |
|
"$5,secondaryuserid,$6," |
|
"$7,$8,$9,$10,$11 from users where " |
|
"userid=$1 and expirydate=$2"; |
|
|
|
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 (ok) |
|
res = PQexec(conn, "Commit", CKPQ_WRITE); |
|
else |
|
res = PQexec(conn, "Rollback", CKPQ_WRITE); |
|
|
|
PQclear(res); |
|
unparam: |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
K_WLOCK(users_free); |
|
if (!ok) |
|
k_add_head(users_free, item); |
|
else { |
|
users_root = remove_from_ktree(users_root, u_item, cmp_users); |
|
userid_root = remove_from_ktree(userid_root, u_item, cmp_userid); |
|
copy_tv(&(users->expirydate), cd); |
|
users_root = add_to_ktree(users_root, u_item, cmp_users); |
|
userid_root = add_to_ktree(userid_root, u_item, cmp_userid); |
|
|
|
users_root = add_to_ktree(users_root, item, cmp_users); |
|
userid_root = add_to_ktree(userid_root, item, cmp_userid); |
|
k_add_head(users_store, item); |
|
} |
|
K_WUNLOCK(users_free); |
|
|
|
return ok; |
|
} |
|
|
|
K_ITEM *users_add(PGconn *conn, char *username, char *emailaddress, |
|
char *passwordhash, char *by, char *code, char *inet, |
|
tv_t *cd, K_TREE *trf_root) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
K_ITEM *item, *u_item; |
|
USERS *row, *users; |
|
char *ins; |
|
char tohash[64]; |
|
uint64_t hash; |
|
__maybe_unused uint64_t tmp; |
|
bool dup, ok = false; |
|
char *params[8 + HISTORYDATECOUNT]; |
|
int n, par = 0; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(users_free); |
|
item = k_unlink_head(users_free); |
|
K_WUNLOCK(users_free); |
|
|
|
DATA_USERS(row, item); |
|
|
|
STRNCPY(row->username, username); |
|
username_trim(row); |
|
|
|
dup = false; |
|
K_RLOCK(users_free); |
|
u_item = users_store->head; |
|
while (u_item) { |
|
DATA_USERS(users, u_item); |
|
if (strcmp(row->usertrim, users->usertrim) == 0) { |
|
dup = true; |
|
break; |
|
} |
|
u_item = u_item->next; |
|
} |
|
K_RUNLOCK(users_free); |
|
|
|
if (dup) |
|
goto unitem; |
|
|
|
row->userid = nextid(conn, "userid", (int64_t)(666 + (random() % 334)), |
|
cd, by, code, inet); |
|
if (row->userid == 0) |
|
goto unitem; |
|
|
|
row->status[0] = '\0'; |
|
STRNCPY(row->emailaddress, emailaddress); |
|
|
|
snprintf(tohash, sizeof(tohash), "%s&#%s", username, emailaddress); |
|
HASH_BER(tohash, strlen(tohash), 1, hash, tmp); |
|
__bin2hex(row->secondaryuserid, (void *)(&hash), sizeof(hash)); |
|
|
|
make_salt(row); |
|
if (passwordhash == EMPTY) { |
|
// Make it impossible to login for a BTC Address username |
|
row->passwordhash[0] = '\0'; |
|
} else { |
|
password_hash(row->username, passwordhash, row->salt, |
|
row->passwordhash, sizeof(row->passwordhash)); |
|
} |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
// 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->status, 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); |
|
params[par++] = str_to_buf(row->salt, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into users " |
|
"(userid,username,status,emailaddress,joineddate,passwordhash," |
|
"secondaryuserid,salt" |
|
HISTORYDATECONTROL ") values (" PQPARAM13 ")"; |
|
|
|
if (!conn) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
ok = true; |
|
unparam: |
|
PQclear(res); |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
unitem: |
|
K_WLOCK(users_free); |
|
if (!ok) |
|
k_add_head(users_free, item); |
|
else { |
|
users_root = add_to_ktree(users_root, item, cmp_users); |
|
userid_root = add_to_ktree(userid_root, item, cmp_userid); |
|
k_add_head(users_store, item); |
|
} |
|
K_WUNLOCK(users_free); |
|
|
|
if (ok) |
|
return item; |
|
else |
|
return NULL; |
|
} |
|
|
|
bool users_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
USERS *row; |
|
char *field; |
|
char *sel; |
|
int fields = 8; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
sel = "select " |
|
"userid,username,status,emailaddress,joineddate," |
|
"passwordhash,secondaryuserid,salt" |
|
HISTORYDATECONTROL |
|
" from users"; |
|
res = PQexec(conn, sel, 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(users_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(users_free); |
|
DATA_USERS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
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, "status", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("status", field, row->status); |
|
|
|
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); |
|
|
|
PQ_GET_FLD(res, i, "salt", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("salt", field, row->salt); |
|
|
|
HISTORYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
username_trim(row); |
|
|
|
users_root = add_to_ktree(users_root, item, cmp_users); |
|
userid_root = add_to_ktree(userid_root, item, cmp_userid); |
|
k_add_head(users_store, item); |
|
} |
|
if (!ok) |
|
k_add_head(users_free, item); |
|
|
|
K_WUNLOCK(users_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d users records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
bool useratts_item_add(PGconn *conn, K_ITEM *ua_item, tv_t *cd, bool begun) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
K_ITEM *old_item; |
|
USERATTS *old_useratts, *useratts; |
|
char *upd, *ins; |
|
bool ok = false; |
|
char *params[9 + HISTORYDATECOUNT]; |
|
int n, par = 0; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
DATA_USERATTS(useratts, ua_item); |
|
|
|
K_RLOCK(useratts_free); |
|
old_item = find_useratts(useratts->userid, useratts->attname); |
|
K_RUNLOCK(useratts_free); |
|
DATA_USERATTS_NULL(old_useratts, old_item); |
|
|
|
/* N.B. the values of the old ua_item record, if it exists, |
|
* are completely ignored i.e. you must provide all values required */ |
|
|
|
if (!conn) { |
|
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); |
|
goto unparam; |
|
} |
|
} |
|
|
|
if (old_item) { |
|
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(old_useratts->userid, NULL, 0); |
|
params[par++] = str_to_buf(old_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); |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Update", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
} |
|
|
|
par = 0; |
|
params[par++] = bigint_to_buf(useratts->userid, NULL, 0); |
|
params[par++] = str_to_buf(useratts->attname, NULL, 0); |
|
params[par++] = str_to_buf(useratts->status, NULL, 0); |
|
params[par++] = str_to_buf(useratts->attstr, NULL, 0); |
|
params[par++] = str_to_buf(useratts->attstr2, NULL, 0); |
|
params[par++] = bigint_to_buf(useratts->attnum, NULL, 0); |
|
params[par++] = bigint_to_buf(useratts->attnum2, NULL, 0); |
|
params[par++] = tv_to_buf(&(useratts->attdate), NULL, 0); |
|
params[par++] = tv_to_buf(&(useratts->attdate2), NULL, 0); |
|
HISTORYDATEPARAMS(params, par, useratts); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into useratts " |
|
"(userid,attname,status,attstr,attstr2,attnum,attnum2," |
|
"attdate,attdate2" |
|
HISTORYDATECONTROL ") values (" PQPARAM14 ")"; |
|
|
|
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); |
|
} |
|
unparam: |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
K_WLOCK(useratts_free); |
|
if (ok) { |
|
// Update it |
|
if (old_item) { |
|
useratts_root = remove_from_ktree(useratts_root, old_item, cmp_useratts); |
|
copy_tv(&(old_useratts->expirydate), cd); |
|
useratts_root = add_to_ktree(useratts_root, old_item, cmp_useratts); |
|
} |
|
useratts_root = add_to_ktree(useratts_root, ua_item, cmp_useratts); |
|
k_add_head(useratts_store, ua_item); |
|
} |
|
K_WUNLOCK(useratts_free); |
|
|
|
return ok; |
|
} |
|
|
|
K_ITEM *useratts_add(PGconn *conn, char *username, char *attname, |
|
char *status, char *attstr, char *attstr2, |
|
char *attnum, char *attnum2, char *attdate, |
|
char *attdate2, char *by, char *code, |
|
char *inet, tv_t *cd, K_TREE *trf_root, |
|
bool begun) |
|
{ |
|
K_ITEM *item, *u_item; |
|
USERATTS *row; |
|
USERS *users; |
|
bool ok = false; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(useratts_free); |
|
item = k_unlink_head(useratts_free); |
|
K_WUNLOCK(useratts_free); |
|
DATA_USERATTS(row, item); |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(username); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) { |
|
char *txt; |
|
LOGERR("%s(): unknown user '%s'", |
|
__func__, |
|
txt = safe_text(username)); |
|
free(txt); |
|
goto unitem; |
|
} |
|
DATA_USERS(users, u_item); |
|
|
|
row->userid = users->userid; |
|
STRNCPY(row->attname, attname); |
|
if (status == NULL) |
|
row->status[0] = '\0'; |
|
else |
|
STRNCPY(row->status, status); |
|
if (attstr == NULL) |
|
row->attstr[0] = '\0'; |
|
else |
|
STRNCPY(row->attstr, attstr); |
|
if (attstr2 == NULL) |
|
row->attstr2[0] = '\0'; |
|
else |
|
STRNCPY(row->attstr2, attstr2); |
|
if (attnum == NULL || attnum[0] == '\0') |
|
row->attnum = 0; |
|
else |
|
TXT_TO_BIGINT("attnum", attnum, row->attnum); |
|
if (attnum2 == NULL || attnum2[0] == '\0') |
|
row->attnum2 = 0; |
|
else |
|
TXT_TO_BIGINT("attnum2", attnum2, row->attnum2); |
|
if (attdate == NULL || attdate[0] == '\0') |
|
row->attdate.tv_sec = row->attdate.tv_usec = 0L; |
|
else |
|
TXT_TO_TV("attdate", attdate, row->attdate); |
|
if (attdate2 == NULL || attdate2[0] == '\0') |
|
row->attdate2.tv_sec = row->attdate2.tv_usec = 0L; |
|
else |
|
TXT_TO_TV("attdate2", attdate2, row->attdate2); |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
ok = useratts_item_add(conn, item, cd, begun); |
|
unitem: |
|
if (!ok) { |
|
K_WLOCK(useratts_free); |
|
k_add_head(useratts_free, item); |
|
K_WUNLOCK(useratts_free); |
|
} |
|
|
|
if (ok) |
|
return item; |
|
else |
|
return NULL; |
|
} |
|
|
|
bool useratts_item_expire(PGconn *conn, K_ITEM *ua_item, tv_t *cd) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
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); |
|
copy_tv(&(useratts->expirydate), cd); |
|
useratts_root = add_to_ktree(useratts_root, item, cmp_useratts); |
|
} |
|
K_WUNLOCK(useratts_free); |
|
|
|
return ok; |
|
} |
|
|
|
bool useratts_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
USERATTS *row; |
|
char *field; |
|
char *sel; |
|
int fields = 9; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
sel = "select " |
|
"userid,attname,status,attstr,attstr2,attnum,attnum2" |
|
",attdate,attdate2" |
|
HISTORYDATECONTROL |
|
" from useratts"; |
|
res = PQexec(conn, sel, 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(useratts_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(useratts_free); |
|
DATA_USERATTS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
PQ_GET_FLD(res, i, "userid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("userid", field, row->userid); |
|
|
|
PQ_GET_FLD(res, i, "attname", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("attname", field, row->attname); |
|
|
|
PQ_GET_FLD(res, i, "status", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("status", field, row->status); |
|
|
|
PQ_GET_FLD(res, i, "attstr", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("attstr", field, row->attstr); |
|
|
|
PQ_GET_FLD(res, i, "attstr2", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("attstr2", field, row->attstr2); |
|
|
|
PQ_GET_FLD(res, i, "attnum", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("attnum", field, row->attnum); |
|
|
|
PQ_GET_FLD(res, i, "attnum2", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("attnum2", field, row->attnum2); |
|
|
|
PQ_GET_FLD(res, i, "attdate", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_TV("attdate", field, row->attdate); |
|
|
|
PQ_GET_FLD(res, i, "attdate2", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_TV("attdate2", field, row->attdate2); |
|
|
|
HISTORYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
useratts_root = add_to_ktree(useratts_root, item, cmp_useratts); |
|
k_add_head(useratts_store, item); |
|
} |
|
if (!ok) |
|
k_add_head(useratts_free, item); |
|
|
|
K_WUNLOCK(useratts_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d useratts records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
K_ITEM *workers_add(PGconn *conn, int64_t userid, char *workername, |
|
char *difficultydefault, char *idlenotificationenabled, |
|
char *idlenotificationtime, char *by, |
|
char *code, char *inet, tv_t *cd, K_TREE *trf_root) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
K_ITEM *item, *ret = NULL; |
|
WORKERS *row; |
|
char *ins; |
|
char *params[6 + HISTORYDATECOUNT]; |
|
int n, par = 0; |
|
int32_t diffdef; |
|
int32_t nottime; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(workers_free); |
|
item = k_unlink_head(workers_free); |
|
K_WUNLOCK(workers_free); |
|
|
|
DATA_WORKERS(row, item); |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
bzero(row, sizeof(*row)); |
|
row->workerid = nextid(conn, "workerid", (int64_t)1, cd, by, code, inet); |
|
if (row->workerid == 0) |
|
goto unitem; |
|
|
|
row->userid = userid; |
|
STRNCPY(row->workername, workername); |
|
if (difficultydefault && *difficultydefault) { |
|
diffdef = atoi(difficultydefault); |
|
// If out of the range, set it in the range |
|
if (diffdef != DIFFICULTYDEFAULT_DEF) { |
|
if (diffdef < DIFFICULTYDEFAULT_MIN) |
|
diffdef = DIFFICULTYDEFAULT_MIN; |
|
if (diffdef > DIFFICULTYDEFAULT_MAX) |
|
diffdef = DIFFICULTYDEFAULT_MAX; |
|
} |
|
row->difficultydefault = diffdef; |
|
} else |
|
row->difficultydefault = DIFFICULTYDEFAULT_DEF; |
|
|
|
row->idlenotificationenabled[1] = '\0'; |
|
if (idlenotificationenabled && *idlenotificationenabled) { |
|
if (tolower(*idlenotificationenabled) == IDLENOTIFICATIONENABLED[0]) |
|
row->idlenotificationenabled[0] = IDLENOTIFICATIONENABLED[0]; |
|
else |
|
row->idlenotificationenabled[0] = IDLENOTIFICATIONDISABLED[0]; |
|
} else |
|
row->idlenotificationenabled[0] = IDLENOTIFICATIONENABLED_DEF[0]; |
|
|
|
if (idlenotificationtime && *idlenotificationtime) { |
|
nottime = atoi(idlenotificationtime); |
|
if (nottime != IDLENOTIFICATIONTIME_DEF) { |
|
// If out of the range, set to default |
|
if (nottime < IDLENOTIFICATIONTIME_MIN || |
|
nottime > IDLENOTIFICATIONTIME_MAX) |
|
nottime = IDLENOTIFICATIONTIME_DEF; |
|
} |
|
row->idlenotificationtime = nottime; |
|
} else |
|
row->idlenotificationtime = IDLENOTIFICATIONTIME_DEF; |
|
|
|
// Default is disabled |
|
if (row->idlenotificationtime == IDLENOTIFICATIONTIME_DEF) |
|
row->idlenotificationenabled[0] = IDLENOTIFICATIONDISABLED[0]; |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
par = 0; |
|
params[par++] = bigint_to_buf(row->workerid, NULL, 0); |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->workername, NULL, 0); |
|
params[par++] = int_to_buf(row->difficultydefault, NULL, 0); |
|
params[par++] = str_to_buf(row->idlenotificationenabled, NULL, 0); |
|
params[par++] = int_to_buf(row->idlenotificationtime, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into workers " |
|
"(workerid,userid,workername,difficultydefault," |
|
"idlenotificationenabled,idlenotificationtime" |
|
HISTORYDATECONTROL ") values (" PQPARAM11 ")"; |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
ret = item; |
|
unparam: |
|
PQclear(res); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
unitem: |
|
if (conned) |
|
PQfinish(conn); |
|
K_WLOCK(workers_free); |
|
if (!ret) |
|
k_add_head(workers_free, item); |
|
else { |
|
workers_root = add_to_ktree(workers_root, item, cmp_workers); |
|
k_add_head(workers_store, item); |
|
// Ensure there is a matching workerstatus |
|
find_create_workerstatus(userid, workername, |
|
__FILE__, __func__, __LINE__); |
|
} |
|
K_WUNLOCK(workers_free); |
|
|
|
return ret; |
|
} |
|
|
|
/* The assumption is that the worker already exists in the DB |
|
* and in the RAM tables and the item passed is already in the tree |
|
* Since there is no change to the key, there's no tree reorg required |
|
* check = false means just update it, ignore the passed char* vars */ |
|
bool workers_update(PGconn *conn, K_ITEM *item, char *difficultydefault, |
|
char *idlenotificationenabled, |
|
char *idlenotificationtime, char *by, char *code, |
|
char *inet, tv_t *cd, K_TREE *trf_root, bool check) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
WORKERS *row; |
|
char *upd, *ins; |
|
bool ok = false; |
|
char *params[6 + HISTORYDATECOUNT]; |
|
int n, par = 0; |
|
int32_t diffdef; |
|
char idlenot; |
|
int32_t nottime; |
|
|
|
LOGDEBUG("%s(): update", __func__); |
|
|
|
DATA_WORKERS(row, item); |
|
|
|
if (check) { |
|
if (difficultydefault && *difficultydefault) { |
|
diffdef = atoi(difficultydefault); |
|
if (diffdef < DIFFICULTYDEFAULT_MIN) |
|
diffdef = row->difficultydefault; |
|
if (diffdef > DIFFICULTYDEFAULT_MAX) |
|
diffdef = row->difficultydefault; |
|
} else |
|
diffdef = row->difficultydefault; |
|
|
|
if (idlenotificationenabled && *idlenotificationenabled) { |
|
if (tolower(*idlenotificationenabled) == IDLENOTIFICATIONENABLED[0]) |
|
idlenot = IDLENOTIFICATIONENABLED[0]; |
|
else |
|
idlenot = IDLENOTIFICATIONDISABLED[0]; |
|
} else |
|
idlenot = row->idlenotificationenabled[0]; |
|
|
|
if (idlenotificationtime && *idlenotificationtime) { |
|
nottime = atoi(idlenotificationtime); |
|
if (nottime < IDLENOTIFICATIONTIME_MIN) |
|
nottime = row->idlenotificationtime; |
|
if (nottime > IDLENOTIFICATIONTIME_MAX) |
|
nottime = row->idlenotificationtime; |
|
} else |
|
nottime = row->idlenotificationtime; |
|
|
|
if (diffdef == row->difficultydefault && |
|
idlenot == row->idlenotificationenabled[0] && |
|
nottime == row->idlenotificationtime) { |
|
ok = true; |
|
goto early; |
|
} |
|
|
|
row->difficultydefault = diffdef; |
|
row->idlenotificationenabled[0] = idlenot; |
|
row->idlenotificationenabled[1] = '\0'; |
|
row->idlenotificationtime = nottime; |
|
} |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
upd = "update workers set expirydate=$1 where workerid=$2 and expirydate=$3"; |
|
par = 0; |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
params[par++] = bigint_to_buf(row->workerid, NULL, 0); |
|
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); |
|
PARCHKVAL(par, 3, params); |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
res = PQexec(conn, "Begin", CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Begin", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
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]); |
|
|
|
ins = "insert into workers " |
|
"(workerid,userid,workername,difficultydefault," |
|
"idlenotificationenabled,idlenotificationtime" |
|
HISTORYDATECONTROL ") values (" PQPARAM11 ")"; |
|
|
|
par = 0; |
|
params[par++] = bigint_to_buf(row->workerid, NULL, 0); |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->workername, NULL, 0); |
|
params[par++] = int_to_buf(row->difficultydefault, NULL, 0); |
|
params[par++] = str_to_buf(row->idlenotificationenabled, NULL, 0); |
|
params[par++] = int_to_buf(row->idlenotificationtime, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
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 (ok) |
|
res = PQexec(conn, "Commit", CKPQ_WRITE); |
|
else |
|
res = PQexec(conn, "Rollback", CKPQ_WRITE); |
|
|
|
PQclear(res); |
|
unparam: |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
early: |
|
return ok; |
|
} |
|
|
|
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, 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(workers_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(workers_free); |
|
DATA_WORKERS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
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, cmp_workers); |
|
k_add_head(workers_store, item); |
|
|
|
/* Make sure a workerstatus exists for each worker |
|
* This is to ensure that code can use the workerstatus tree |
|
* to reference other tables and not miss workers in the |
|
* other tables */ |
|
find_create_workerstatus(row->userid, row->workername, |
|
__FILE__, __func__, __LINE__); |
|
} |
|
if (!ok) |
|
k_add_head(workers_free, item); |
|
|
|
K_WUNLOCK(workers_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d workers records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
/* Whatever the current paymentaddresses are, replace them with the list |
|
* in pa_store |
|
* Code allows for zero, one or more current payment address */ |
|
bool paymentaddresses_set(PGconn *conn, int64_t userid, K_STORE *pa_store, |
|
char *by, char *code, char *inet, tv_t *cd, |
|
K_TREE *trf_root) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM *item, *match, *next, *prev; |
|
PAYMENTADDRESSES *row, *pa; |
|
char *upd = NULL, *ins; |
|
size_t len, off; |
|
bool ok = false, first, locked = false; |
|
char *params[1002]; // Limit of 999 addresses per user |
|
char tmp[1024]; |
|
int n, par = 0, count, matches; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
// Quick early abort |
|
if (pa_store->count > 999) |
|
return false; |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
/* This means the nextid updates will rollback on an error, but also |
|
* means that it will lock the nextid record for the whole update */ |
|
res = PQexec(conn, "Begin", CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Begin", rescode, conn); |
|
goto unparam; |
|
} |
|
PQclear(res); |
|
|
|
// First step - DB expire all the old/changed records in RAM |
|
LOGDEBUG("%s(): Step 1 userid=%"PRId64, __func__, userid); |
|
count = matches = 0; |
|
APPEND_REALLOC_INIT(upd, off, len); |
|
APPEND_REALLOC(upd, off, len, |
|
"update paymentaddresses set expirydate=$1 where " |
|
"userid=$2 and expirydate=$3 and payaddress in ("); |
|
par = 0; |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
params[par++] = bigint_to_buf(userid, NULL, 0); |
|
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); |
|
|
|
/* Since we are merging the changes in rather than just |
|
* replacing the db contents, lock the data for the duration |
|
* of the update to ensure nothing else changes it */ |
|
K_WLOCK(paymentaddresses_free); |
|
locked = true; |
|
|
|
first = true; |
|
item = find_paymentaddresses(userid, ctx); |
|
DATA_PAYMENTADDRESSES_NULL(row, item); |
|
while (item && CURRENT(&(row->expirydate)) && row->userid == userid) { |
|
/* This is only possible if the DB was directly updated with |
|
* more than 999 records then reloaded (or a code bug) */ |
|
if (++count > 999) |
|
break; |
|
|
|
// Find the RAM record in pa_store |
|
match = pa_store->head; |
|
while (match) { |
|
DATA_PAYMENTADDRESSES(pa, match); |
|
if (strcmp(pa->payaddress, row->payaddress) == 0 && |
|
pa->payratio == row->payratio) { |
|
pa->match = true; // Don't store it |
|
matches++; |
|
break; |
|
} |
|
match = match->next; |
|
} |
|
if (!match) { |
|
// No matching replacement, so expire 'row' |
|
params[par++] = str_to_buf(row->payaddress, NULL, 0); |
|
if (!first) |
|
APPEND_REALLOC(upd, off, len, ","); |
|
first = false; |
|
snprintf(tmp, sizeof(tmp), "$%d", par); |
|
APPEND_REALLOC(upd, off, len, tmp); |
|
} |
|
item = prev_in_ktree(ctx); |
|
DATA_PAYMENTADDRESSES_NULL(row, item); |
|
} |
|
LOGDEBUG("%s(): Step 1 par=%d count=%d matches=%d first=%s", __func__, |
|
par, count, matches, first ? "true" : "false"); |
|
// Too many, or none need expiring = don't do the update |
|
if (count > 999 || first == true) { |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
par = 0; |
|
// Too many |
|
if (count > 999) |
|
goto rollback; |
|
} else { |
|
APPEND_REALLOC(upd, off, len, ")"); |
|
PARCHKVAL(par, par, 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; |
|
} |
|
|
|
LOGDEBUG("%s(): Step 1 expired %d", __func__, par-3); |
|
|
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
par = 0; |
|
} |
|
|
|
// Second step - add the non-matching records to the DB |
|
LOGDEBUG("%s(): Step 2", __func__); |
|
ins = "insert into paymentaddresses " |
|
"(paymentaddressid,userid,payaddress,payratio" |
|
HISTORYDATECONTROL ") values (" PQPARAM9 ")"; |
|
|
|
count = 0; |
|
match = pa_store->head; |
|
while (match) { |
|
DATA_PAYMENTADDRESSES(row, match); |
|
if (!row->match) { |
|
row->paymentaddressid = nextid(conn, "paymentaddressid", 1, |
|
cd, by, code, inet); |
|
if (row->paymentaddressid == 0) |
|
goto rollback; |
|
|
|
row->userid = userid; |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
par = 0; |
|
params[par++] = bigint_to_buf(row->paymentaddressid, NULL, 0); |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->payaddress, NULL, 0); |
|
params[par++] = int_to_buf(row->payratio, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHKVAL(par, 9, params); // As per PQPARAM9 above |
|
|
|
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; |
|
} |
|
|
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
par = 0; |
|
|
|
count++; |
|
} |
|
match = match->next; |
|
} |
|
LOGDEBUG("%s(): Step 2 inserted %d", __func__, count); |
|
|
|
ok = true; |
|
rollback: |
|
if (ok) |
|
res = PQexec(conn, "Commit", CKPQ_WRITE); |
|
else |
|
res = PQexec(conn, "Rollback", CKPQ_WRITE); |
|
|
|
PQclear(res); |
|
unparam: |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
FREENULL(upd); |
|
// Third step - do step 1 and 2 to the RAM version of the DB |
|
LOGDEBUG("%s(): Step 3, ok=%s", __func__, ok ? "true" : "false"); |
|
matches = count = n = 0; |
|
if (ok) { |
|
// Change the expiry on all records that we expired in the DB |
|
item = find_paymentaddresses(userid, ctx); |
|
DATA_PAYMENTADDRESSES_NULL(row, item); |
|
while (item && CURRENT(&(row->expirydate)) && row->userid == userid) { |
|
prev = prev_in_ktree(ctx); |
|
// Find the RAM record in pa_store |
|
match = pa_store->head; |
|
while (match) { |
|
DATA_PAYMENTADDRESSES(pa, match); |
|
if (strcmp(pa->payaddress, row->payaddress) == 0 && |
|
pa->payratio == row->payratio) { |
|
break; |
|
} |
|
match = match->next; |
|
} |
|
if (match) |
|
matches++; |
|
else { |
|
// It wasn't a match, thus it was expired |
|
n++; |
|
paymentaddresses_root = remove_from_ktree(paymentaddresses_root, item, |
|
cmp_paymentaddresses); |
|
copy_tv(&(row->expirydate), cd); |
|
paymentaddresses_root = add_to_ktree(paymentaddresses_root, item, |
|
cmp_paymentaddresses); |
|
} |
|
item = prev; |
|
DATA_PAYMENTADDRESSES_NULL(row, item); |
|
} |
|
|
|
// Add in all the non-matching ps_store |
|
match = pa_store->head; |
|
while (match) { |
|
next = match->next; |
|
DATA_PAYMENTADDRESSES(pa, match); |
|
if (!pa->match) { |
|
paymentaddresses_root = add_to_ktree(paymentaddresses_root, match, |
|
cmp_paymentaddresses); |
|
k_unlink_item(pa_store, match); |
|
k_add_head(paymentaddresses_store, match); |
|
count++; |
|
} |
|
match = next; |
|
} |
|
} |
|
if (locked) |
|
K_WUNLOCK(paymentaddresses_free); |
|
|
|
LOGDEBUG("%s(): Step 3, untouched %d expired %d added %d", __func__, matches, n, count); |
|
|
|
// Calling function must clean up anything left in pa_store |
|
return ok; |
|
} |
|
|
|
bool paymentaddresses_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
PAYMENTADDRESSES *row; |
|
int n, i; |
|
char *field; |
|
char *sel; |
|
int fields = 4; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
sel = "select " |
|
"paymentaddressid,userid,payaddress,payratio" |
|
HISTORYDATECONTROL |
|
" from paymentaddresses"; |
|
res = PQexec(conn, sel, 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(paymentaddresses_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(paymentaddresses_free); |
|
DATA_PAYMENTADDRESSES(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
PQ_GET_FLD(res, i, "paymentaddressid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("paymentaddressid", field, row->paymentaddressid); |
|
|
|
PQ_GET_FLD(res, i, "userid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("userid", field, row->userid); |
|
|
|
PQ_GET_FLD(res, i, "payaddress", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("payaddress", field, row->payaddress); |
|
|
|
PQ_GET_FLD(res, i, "payratio", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_INT("payratio", field, row->payratio); |
|
|
|
HISTORYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
paymentaddresses_root = add_to_ktree(paymentaddresses_root, item, cmp_paymentaddresses); |
|
k_add_head(paymentaddresses_store, item); |
|
} |
|
if (!ok) |
|
k_add_head(paymentaddresses_free, item); |
|
|
|
K_WUNLOCK(paymentaddresses_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d paymentaddresses records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
bool payments_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
PAYMENTS *row; |
|
char *params[1]; |
|
int n, i, par = 0; |
|
char *field; |
|
char *sel; |
|
int fields = 8; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
// TODO: handle selecting a subset, eg 20 per web page (in blocklist also) |
|
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, 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(payments_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(payments_free); |
|
DATA_PAYMENTS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
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, cmp_payments); |
|
k_add_head(payments_store, item); |
|
} |
|
if (!ok) |
|
k_add_head(payments_free, item); |
|
|
|
K_WUNLOCK(payments_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d payments records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
bool idcontrol_add(PGconn *conn, char *idname, char *idvalue, char *by, |
|
char *code, char *inet, tv_t *cd, |
|
__maybe_unused K_TREE *trf_root) |
|
{ |
|
K_ITEM *look; |
|
IDCONTROL *row; |
|
char *params[2 + MODIFYDATECOUNT]; |
|
int n, par = 0; |
|
bool ok = false; |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
char *ins; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(idcontrol_free); |
|
look = k_unlink_head(idcontrol_free); |
|
K_WUNLOCK(idcontrol_free); |
|
|
|
DATA_IDCONTROL(row, look); |
|
|
|
STRNCPY(row->idname, idname); |
|
TXT_TO_BIGINT("idvalue", idvalue, row->lastid); |
|
MODIFYDATEINIT(row, cd, 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 ")"; |
|
|
|
if (!conn) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
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 foil; |
|
} |
|
|
|
ok = true; |
|
foil: |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
K_WLOCK(idcontrol_free); |
|
k_add_head(idcontrol_free, look); |
|
K_WUNLOCK(idcontrol_free); |
|
|
|
return ok; |
|
} |
|
|
|
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; |
|
OPTIONCONTROL *row, *optioncontrol; |
|
char *upd, *ins; |
|
bool ok = false; |
|
char *params[4 + HISTORYDATECOUNT]; |
|
int n, par = 0; |
|
|
|
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); |
|
|
|
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) { |
|
// Cleanup item passed in |
|
FREENULL(row->optionvalue); |
|
k_add_head(optioncontrol_free, oc_item); |
|
} else { |
|
// Discard old |
|
if (old_item) { |
|
DATA_OPTIONCONTROL(optioncontrol, old_item); |
|
optioncontrol_root = remove_from_ktree(optioncontrol_root, old_item, |
|
cmp_optioncontrol); |
|
FREENULL(optioncontrol->optionvalue); |
|
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; |
|
} |
|
|
|
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; |
|
|
|
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); |
|
|
|
return optioncontrol_item_add(conn, item, cd, begun); |
|
} |
|
|
|
bool optioncontrol_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
OPTIONCONTROL *row; |
|
char *params[1]; |
|
int n, i, par = 0; |
|
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) { |
|
FREENULL(row->optionvalue); |
|
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; |
|
} |
|
|
|
int64_t workinfo_add(PGconn *conn, char *workinfoidstr, char *poolinstance, |
|
char *transactiontree, char *merklehash, char *prevhash, |
|
char *coinbase1, char *coinbase2, char *version, |
|
char *bits, char *ntime, char *reward, char *by, |
|
char *code, char *inet, tv_t *cd, bool igndup, |
|
K_TREE *trf_root) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
K_TREE_CTX ctx[1]; |
|
PGresult *res; |
|
K_ITEM *item; |
|
char cd_buf[DATE_BUFSIZ]; |
|
char ndiffbin[TXT_SML+1]; |
|
int64_t workinfoid = -1; |
|
WORKINFO *row; |
|
char *ins; |
|
char *params[11 + HISTORYDATECOUNT]; |
|
int n, par = 0; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(workinfo_free); |
|
item = k_unlink_head(workinfo_free); |
|
K_WUNLOCK(workinfo_free); |
|
|
|
DATA_WORKINFO(row, item); |
|
|
|
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); |
|
STRNCPY(row->version, version); |
|
STRNCPY(row->bits, bits); |
|
STRNCPY(row->ntime, ntime); |
|
TXT_TO_BIGINT("reward", reward, row->reward); |
|
pool.reward = row->reward; |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
K_WLOCK(workinfo_free); |
|
if (find_in_ktree(workinfo_root, item, cmp_workinfo, ctx)) { |
|
FREENULL(row->transactiontree); |
|
FREENULL(row->merklehash); |
|
workinfoid = row->workinfoid; |
|
k_add_head(workinfo_free, item); |
|
K_WUNLOCK(workinfo_free); |
|
|
|
if (!igndup) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s(): Duplicate workinfo ignored %s/%s/%s", |
|
__func__, workinfoidstr, poolinstance, cd_buf); |
|
} |
|
|
|
return workinfoid; |
|
} |
|
K_WUNLOCK(workinfo_free); |
|
|
|
if (!confirm_sharesummary) { |
|
par = 0; |
|
params[par++] = bigint_to_buf(row->workinfoid, NULL, 0); |
|
params[par++] = str_to_buf(row->poolinstance, NULL, 0); |
|
params[par++] = str_to_buf(row->transactiontree, NULL, 0); |
|
params[par++] = str_to_buf(row->merklehash, NULL, 0); |
|
params[par++] = str_to_buf(row->prevhash, NULL, 0); |
|
params[par++] = str_to_buf(row->coinbase1, NULL, 0); |
|
params[par++] = str_to_buf(row->coinbase2, NULL, 0); |
|
params[par++] = str_to_buf(row->version, NULL, 0); |
|
params[par++] = str_to_buf(row->bits, NULL, 0); |
|
params[par++] = str_to_buf(row->ntime, NULL, 0); |
|
params[par++] = bigint_to_buf(row->reward, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into workinfo " |
|
"(workinfoid,poolinstance,transactiontree,merklehash," |
|
"prevhash,coinbase1,coinbase2,version,bits,ntime,reward" |
|
HISTORYDATECONTROL ") values (" PQPARAM16 ")"; |
|
|
|
if (!conn) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto unparam; |
|
} |
|
} |
|
|
|
workinfoid = row->workinfoid; |
|
|
|
unparam: |
|
if (par) { |
|
PQclear(res); |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
} |
|
|
|
K_WLOCK(workinfo_free); |
|
if (workinfoid == -1) { |
|
FREENULL(row->transactiontree); |
|
FREENULL(row->merklehash); |
|
k_add_head(workinfo_free, item); |
|
} else { |
|
if (row->transactiontree && *(row->transactiontree)) { |
|
// Not currently needed in RAM |
|
free(row->transactiontree); |
|
row->transactiontree = strdup(EMPTY); |
|
} |
|
|
|
hex2bin(ndiffbin, row->bits, 4); |
|
current_ndiff = diff_from_nbits(ndiffbin); |
|
|
|
workinfo_root = add_to_ktree(workinfo_root, item, cmp_workinfo); |
|
k_add_head(workinfo_store, item); |
|
|
|
// Remember the bc = 'cd' when the height changes |
|
if (workinfo_current) { |
|
WORKINFO *wic; |
|
DATA_WORKINFO(wic, workinfo_current); |
|
if (cmp_height(wic->coinbase1, row->coinbase1) != 0) |
|
copy_tv(&last_bc, cd); |
|
} |
|
|
|
workinfo_current = item; |
|
} |
|
K_WUNLOCK(workinfo_free); |
|
|
|
return workinfoid; |
|
} |
|
|
|
bool workinfo_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
WORKINFO *row; |
|
char *params[3]; |
|
int n, i, par = 0; |
|
char *field; |
|
char *sel = NULL; |
|
size_t len, off; |
|
int fields = 10; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
// TODO: select the data based on sharesummary since old data isn't needed |
|
// however, the ageing rules for workinfo will decide that also |
|
// keep the last block + current? Rules will depend on payout scheme also |
|
|
|
APPEND_REALLOC_INIT(sel, off, len); |
|
APPEND_REALLOC(sel, off, len, |
|
"select " |
|
// "workinfoid,poolinstance,transactiontree,merklehash,prevhash," |
|
"workinfoid,poolinstance,merklehash,prevhash," |
|
"coinbase1,coinbase2,version,bits,ntime,reward" |
|
HISTORYDATECONTROL |
|
" from workinfo where expirydate=$1 and" |
|
" ((workinfoid>=$2 and workinfoid<=$3)"); |
|
|
|
// If we aren't loading the full range, ensure the necessary ones are loaded |
|
if ((!dbload_only_sharesummary && dbload_workinfoid_start != -1) || |
|
dbload_workinfoid_finish != MAXID) { |
|
APPEND_REALLOC(sel, off, len, |
|
// we need all blocks workinfoids |
|
" or workinfoid in (select workinfoid from blocks)" |
|
// we need all marks workinfoids |
|
" or workinfoid in (select workinfoid from marks)" |
|
// we need all workmarkers workinfoids (start and end) |
|
" or workinfoid in (select workinfoidstart from workmarkers)" |
|
" or workinfoid in (select workinfoidend from workmarkers)"); |
|
} |
|
APPEND_REALLOC(sel, off, len, ")"); |
|
|
|
par = 0; |
|
params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0); |
|
if (dbload_only_sharesummary) |
|
params[par++] = bigint_to_buf(-1, NULL, 0); |
|
else |
|
params[par++] = bigint_to_buf(dbload_workinfoid_start, NULL, 0); |
|
params[par++] = bigint_to_buf(dbload_workinfoid_finish, 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(workinfo_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(workinfo_free); |
|
DATA_WORKINFO(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
PQ_GET_FLD(res, i, "workinfoid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("workinfoid", field, row->workinfoid); |
|
|
|
PQ_GET_FLD(res, i, "poolinstance", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("poolinstance", field, row->poolinstance); |
|
|
|
/* Not currently needed in RAM |
|
PQ_GET_FLD(res, i, "transactiontree", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BLOB("transactiontree", field, row->transactiontree); |
|
*/ |
|
row->transactiontree = strdup(EMPTY); |
|
|
|
PQ_GET_FLD(res, i, "merklehash", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BLOB("merklehash", field, row->merklehash); |
|
|
|
PQ_GET_FLD(res, i, "prevhash", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("prevhash", field, row->prevhash); |
|
|
|
PQ_GET_FLD(res, i, "coinbase1", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("coinbase1", field, row->coinbase1); |
|
|
|
PQ_GET_FLD(res, i, "coinbase2", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("coinbase2", field, row->coinbase2); |
|
|
|
PQ_GET_FLD(res, i, "version", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("version", field, row->version); |
|
|
|
PQ_GET_FLD(res, i, "bits", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("bits", field, row->bits); |
|
|
|
PQ_GET_FLD(res, i, "ntime", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("ntime", field, row->ntime); |
|
|
|
PQ_GET_FLD(res, i, "reward", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("reward", field, row->reward); |
|
pool.reward = row->reward; |
|
|
|
HISTORYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
workinfo_root = add_to_ktree(workinfo_root, item, cmp_workinfo); |
|
if (!confirm_sharesummary) |
|
workinfo_height_root = add_to_ktree(workinfo_height_root, item, cmp_workinfo_height); |
|
k_add_head(workinfo_store, item); |
|
|
|
if (tv_newer(&(dbstatus.newest_createdate_workinfo), &(row->createdate))) |
|
copy_tv(&(dbstatus.newest_createdate_workinfo), &(row->createdate)); |
|
|
|
tick(); |
|
} |
|
if (!ok) |
|
k_add_head(workinfo_free, item); |
|
|
|
K_WUNLOCK(workinfo_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d workinfo records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
// Memory (and log file) only |
|
bool shares_add(PGconn *conn, char *workinfoid, char *username, char *workername, |
|
char *clientid, char *errn, char *enonce1, char *nonce2, |
|
char *nonce, char *diff, char *sdiff, char *secondaryuserid, |
|
char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *s_item, *u_item, *wi_item, *w_item, *wm_item, *ss_item; |
|
char cd_buf[DATE_BUFSIZ]; |
|
SHARESUMMARY *sharesummary; |
|
SHARES *shares; |
|
USERS *users; |
|
bool ok = false; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(shares_free); |
|
s_item = k_unlink_head(shares_free); |
|
K_WUNLOCK(shares_free); |
|
|
|
DATA_SHARES(shares, s_item); |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(username); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) { |
|
char *txt; |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s() %s/%ld,%ld %.19s no user! Share discarded!", |
|
__func__, txt = safe_text(username), |
|
cd->tv_sec, cd->tv_usec, cd_buf); |
|
free(txt); |
|
goto unitem; |
|
} |
|
DATA_USERS(users, u_item); |
|
|
|
shares->userid = users->userid; |
|
|
|
TXT_TO_BIGINT("workinfoid", workinfoid, shares->workinfoid); |
|
STRNCPY(shares->workername, workername); |
|
TXT_TO_INT("clientid", clientid, shares->clientid); |
|
TXT_TO_INT("errn", errn, shares->errn); |
|
STRNCPY(shares->enonce1, enonce1); |
|
STRNCPY(shares->nonce2, nonce2); |
|
STRNCPY(shares->nonce, nonce); |
|
TXT_TO_DOUBLE("diff", diff, shares->diff); |
|
TXT_TO_DOUBLE("sdiff", sdiff, shares->sdiff); |
|
STRNCPY(shares->secondaryuserid, secondaryuserid); |
|
|
|
if (!(*secondaryuserid)) { |
|
STRNCPY(shares->secondaryuserid, users->secondaryuserid); |
|
if (!tv_newer(&missing_secuser_min, cd) || |
|
!tv_newer(cd, &missing_secuser_max)) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s() %s/%ld,%ld %.19s missing secondaryuserid! " |
|
"Share corrected", |
|
__func__, username, |
|
cd->tv_sec, cd->tv_usec, cd_buf); |
|
} |
|
} |
|
|
|
HISTORYDATEINIT(shares, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, shares); |
|
|
|
wi_item = find_workinfo(shares->workinfoid, NULL); |
|
if (!wi_item) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
// TODO: store it for a few workinfoid changes |
|
LOGERR("%s() %"PRId64"/%s/%ld,%ld %.19s no workinfo! Share discarded!", |
|
__func__, shares->workinfoid, workername, |
|
cd->tv_sec, cd->tv_usec, cd_buf); |
|
goto unitem; |
|
} |
|
|
|
w_item = new_default_worker(conn, false, shares->userid, shares->workername, |
|
by, code, inet, cd, trf_root); |
|
if (!w_item) |
|
goto unitem; |
|
|
|
if (reloading && !confirm_sharesummary) { |
|
// We only need to know if the workmarker is processed |
|
wm_item = find_workmarkers(shares->workinfoid, false, |
|
MARKER_PROCESSED); |
|
if (wm_item) { |
|
K_WLOCK(shares_free); |
|
k_add_head(shares_free, s_item); |
|
K_WUNLOCK(shares_free); |
|
return true; |
|
} |
|
ss_item = find_sharesummary(shares->userid, shares->workername, shares->workinfoid); |
|
if (ss_item) { |
|
DATA_SHARESUMMARY(sharesummary, ss_item); |
|
if (sharesummary->complete[0] != SUMMARY_NEW) { |
|
K_WLOCK(shares_free); |
|
k_add_head(shares_free, s_item); |
|
K_WUNLOCK(shares_free); |
|
return true; |
|
} |
|
|
|
if (!sharesummary->reset) { |
|
zero_sharesummary(sharesummary, cd, shares->diff); |
|
sharesummary->reset = true; |
|
} |
|
} |
|
} |
|
|
|
if (!confirm_sharesummary) |
|
workerstatus_update(NULL, shares, NULL); |
|
|
|
sharesummary_update(conn, shares, NULL, NULL, by, code, inet, cd); |
|
|
|
ok = true; |
|
unitem: |
|
K_WLOCK(shares_free); |
|
if (!ok) |
|
k_add_head(shares_free, s_item); |
|
else { |
|
shares_root = add_to_ktree(shares_root, s_item, cmp_shares); |
|
k_add_head(shares_store, s_item); |
|
} |
|
K_WUNLOCK(shares_free); |
|
|
|
return ok; |
|
} |
|
|
|
// Memory (and log file) only |
|
// TODO: handle shareerrors that appear after a workinfoid is aged or doesn't exist? |
|
bool shareerrors_add(PGconn *conn, char *workinfoid, char *username, |
|
char *workername, char *clientid, char *errn, |
|
char *error, char *secondaryuserid, char *by, |
|
char *code, char *inet, tv_t *cd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *s_item, *u_item, *wi_item, *w_item, *wm_item, *ss_item; |
|
char cd_buf[DATE_BUFSIZ]; |
|
SHARESUMMARY *sharesummary; |
|
SHAREERRORS *shareerrors; |
|
USERS *users; |
|
bool ok = false; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(shareerrors_free); |
|
s_item = k_unlink_head(shareerrors_free); |
|
K_WUNLOCK(shareerrors_free); |
|
|
|
DATA_SHAREERRORS(shareerrors, s_item); |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(username); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) { |
|
char *txt; |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s() %s/%ld,%ld %.19s no user! Shareerror discarded!", |
|
__func__, txt = safe_text(username), |
|
cd->tv_sec, cd->tv_usec, cd_buf); |
|
free(txt); |
|
goto unitem; |
|
} |
|
DATA_USERS(users, u_item); |
|
|
|
shareerrors->userid = users->userid; |
|
|
|
TXT_TO_BIGINT("workinfoid", workinfoid, shareerrors->workinfoid); |
|
STRNCPY(shareerrors->workername, workername); |
|
TXT_TO_INT("clientid", clientid, shareerrors->clientid); |
|
TXT_TO_INT("errn", errn, shareerrors->errn); |
|
STRNCPY(shareerrors->error, error); |
|
STRNCPY(shareerrors->secondaryuserid, secondaryuserid); |
|
|
|
if (!(*secondaryuserid)) { |
|
STRNCPY(shareerrors->secondaryuserid, users->secondaryuserid); |
|
if (!tv_newer(&missing_secuser_min, cd) || |
|
!tv_newer(cd, &missing_secuser_max)) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s() %s/%ld,%ld %.19s missing secondaryuserid! " |
|
"Sharerror corrected", |
|
__func__, username, |
|
cd->tv_sec, cd->tv_usec, cd_buf); |
|
} |
|
} |
|
|
|
HISTORYDATEINIT(shareerrors, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, shareerrors); |
|
|
|
wi_item = find_workinfo(shareerrors->workinfoid, NULL); |
|
if (!wi_item) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s() %"PRId64"/%s/%ld,%ld %.19s no workinfo! Shareerror discarded!", |
|
__func__, shareerrors->workinfoid, workername, |
|
cd->tv_sec, cd->tv_usec, cd_buf); |
|
goto unitem; |
|
} |
|
|
|
w_item = new_default_worker(NULL, false, shareerrors->userid, shareerrors->workername, |
|
by, code, inet, cd, trf_root); |
|
if (!w_item) |
|
goto unitem; |
|
|
|
if (reloading && !confirm_sharesummary) { |
|
// We only need to know if the workmarker is processed |
|
wm_item = find_workmarkers(shareerrors->workinfoid, false, |
|
MARKER_PROCESSED); |
|
if (wm_item) { |
|
K_WLOCK(shareerrors_free); |
|
k_add_head(shareerrors_free, s_item); |
|
K_WUNLOCK(shareerrors_free); |
|
return true; |
|
} |
|
ss_item = find_sharesummary(shareerrors->userid, shareerrors->workername, shareerrors->workinfoid); |
|
if (ss_item) { |
|
DATA_SHARESUMMARY(sharesummary, ss_item); |
|
if (sharesummary->complete[0] != SUMMARY_NEW) { |
|
K_WLOCK(shareerrors_free); |
|
k_add_head(shareerrors_free, s_item); |
|
K_WUNLOCK(shareerrors_free); |
|
return true; |
|
} |
|
|
|
if (!sharesummary->reset) { |
|
zero_sharesummary(sharesummary, cd, 0.0); |
|
sharesummary->reset = true; |
|
} |
|
} |
|
} |
|
|
|
sharesummary_update(conn, NULL, shareerrors, NULL, by, code, inet, cd); |
|
|
|
ok = true; |
|
unitem: |
|
K_WLOCK(shareerrors_free); |
|
if (!ok) |
|
k_add_head(shareerrors_free, s_item); |
|
else { |
|
shareerrors_root = add_to_ktree(shareerrors_root, s_item, cmp_shareerrors); |
|
k_add_head(shareerrors_store, s_item); |
|
} |
|
K_WUNLOCK(shareerrors_free); |
|
|
|
return ok; |
|
} |
|
|
|
bool shareerrors_fill() |
|
{ |
|
return true; |
|
} |
|
|
|
bool _sharesummary_update(PGconn *conn, SHARES *s_row, SHAREERRORS *e_row, K_ITEM *ss_item, |
|
char *by, char *code, char *inet, tv_t *cd, WHERE_FFL_ARGS) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res = NULL; |
|
WORKMARKERS *wm; |
|
SHARESUMMARY *row; |
|
K_ITEM *item, *wm_item; |
|
char *ins, *upd; |
|
bool ok = false, new; |
|
char *params[19 + MODIFYDATECOUNT]; |
|
int n, par = 0; |
|
int64_t userid, workinfoid; |
|
char *workername; |
|
tv_t *sharecreatedate; |
|
bool must_update = false, conned = false; |
|
double diff = 0; |
|
|
|
LOGDEBUG("%s(): update", __func__); |
|
|
|
if (ss_item) { |
|
if (s_row || e_row) { |
|
quithere(1, "ERR: only one of s_row, e_row and " |
|
"ss_item allowed" WHERE_FFL, |
|
WHERE_FFL_PASS); |
|
} |
|
new = false; |
|
item = ss_item; |
|
DATA_SHARESUMMARY(row, item); |
|
must_update = true; |
|
row->complete[0] = SUMMARY_COMPLETE; |
|
row->complete[1] = '\0'; |
|
} else { |
|
if (s_row) { |
|
if (e_row) { |
|
quithere(1, "ERR: only one of s_row, e_row " |
|
"(and ss_item) allowed" WHERE_FFL, |
|
WHERE_FFL_PASS); |
|
} |
|
userid = s_row->userid; |
|
workername = s_row->workername; |
|
workinfoid = s_row->workinfoid; |
|
diff = s_row->diff; |
|
sharecreatedate = &(s_row->createdate); |
|
} else { |
|
if (!e_row) { |
|
quithere(1, "ERR: all s_row, e_row and " |
|
"ss_item are NULL" WHERE_FFL, |
|
WHERE_FFL_PASS); |
|
} |
|
userid = e_row->userid; |
|
workername = e_row->workername; |
|
workinfoid = e_row->workinfoid; |
|
sharecreatedate = &(e_row->createdate); |
|
} |
|
|
|
K_RLOCK(workmarkers_free); |
|
wm_item = find_workmarkers(workinfoid, false, MARKER_PROCESSED); |
|
K_RUNLOCK(workmarkers_free); |
|
if (wm_item) { |
|
char *tmp; |
|
DATA_WORKMARKERS(wm, wm_item); |
|
LOGERR("%s(): attempt to update sharesummary " |
|
"with %s %"PRId64"/%"PRId64"/%s createdate %s" |
|
" but processed workmarkers %"PRId64" exists", |
|
__func__, s_row ? "shares" : "shareerrors", |
|
workinfoid, userid, workername, |
|
(tmp = ctv_to_buf(sharecreatedate, NULL, 0)), |
|
wm->markerid); |
|
free(tmp); |
|
return false; |
|
} |
|
|
|
K_RLOCK(sharesummary_free); |
|
item = find_sharesummary(userid, workername, workinfoid); |
|
K_RUNLOCK(sharesummary_free); |
|
if (item) { |
|
new = false; |
|
DATA_SHARESUMMARY(row, item); |
|
} else { |
|
new = true; |
|
K_WLOCK(sharesummary_free); |
|
item = k_unlink_head(sharesummary_free); |
|
K_WUNLOCK(sharesummary_free); |
|
DATA_SHARESUMMARY(row, item); |
|
row->userid = userid; |
|
row->workername = strdup(workername); |
|
LIST_MEM_ADD(sharesummary_free, row->workername); |
|
row->workinfoid = workinfoid; |
|
zero_sharesummary(row, sharecreatedate, diff); |
|
row->inserted = false; |
|
row->saveaged = false; |
|
} |
|
|
|
if (e_row) |
|
row->errorcount += 1; |
|
else { |
|
row->sharecount += 1; |
|
switch (s_row->errn) { |
|
case SE_NONE: |
|
row->diffacc += s_row->diff; |
|
row->shareacc++; |
|
break; |
|
case SE_STALE: |
|
row->diffsta += s_row->diff; |
|
row->sharesta++; |
|
break; |
|
case SE_DUPE: |
|
row->diffdup += s_row->diff; |
|
row->sharedup++; |
|
break; |
|
case SE_HIGH_DIFF: |
|
row->diffhi += s_row->diff; |
|
row->sharehi++; |
|
break; |
|
default: |
|
row->diffrej += s_row->diff; |
|
row->sharerej++; |
|
break; |
|
} |
|
} |
|
|
|
if (!new) { |
|
double td; |
|
td = tvdiff(sharecreatedate, &(row->firstshare)); |
|
// don't LOGERR '=' in case shares come from ckpool with the same timestamp |
|
if (td < 0.0) { |
|
char *tmp1, *tmp2; |
|
LOGERR("%s(): %s createdate (%s) is < summary firstshare (%s)", |
|
__func__, s_row ? "shares" : "shareerrors", |
|
(tmp1 = ctv_to_buf(sharecreatedate, NULL, 0)), |
|
(tmp2 = ctv_to_buf(&(row->firstshare), NULL, 0))); |
|
free(tmp2); |
|
free(tmp1); |
|
row->firstshare.tv_sec = sharecreatedate->tv_sec; |
|
row->firstshare.tv_usec = sharecreatedate->tv_usec; |
|
// Don't change lastdiffacc |
|
} |
|
td = tvdiff(sharecreatedate, &(row->lastshare)); |
|
// don't LOGERR '=' in case shares come from ckpool with the same timestamp |
|
if (td >= 0.0) { |
|
row->lastshare.tv_sec = sharecreatedate->tv_sec; |
|
row->lastshare.tv_usec = sharecreatedate->tv_usec; |
|
row->lastdiffacc = diff; |
|
} else { |
|
char *tmp1, *tmp2; |
|
LOGERR("%s(): %s createdate (%s) is < summary lastshare (%s)", |
|
__func__, s_row ? "shares" : "shareerrors", |
|
(tmp1 = ctv_to_buf(sharecreatedate, NULL, 0)), |
|
(tmp2 = ctv_to_buf(&(row->lastshare), NULL, 0))); |
|
free(tmp2); |
|
free(tmp1); |
|
} |
|
if (row->complete[0] != SUMMARY_NEW) { |
|
LOGDEBUG("%s(): updating sharesummary not '%c' %"PRId64"/%s/%"PRId64"/%s", |
|
__func__, SUMMARY_NEW, row->userid, row->workername, |
|
row->workinfoid, row->complete); |
|
} |
|
} |
|
} |
|
|
|
// During startup, don't save 'new' sharesummaries, to reduce DB I/O |
|
// ... and also during normal processing |
|
if (row->complete[0] == SUMMARY_NEW) |
|
goto startupskip; |
|
|
|
if (conn == NULL && !confirm_sharesummary) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
if (new || !(row->inserted)) { |
|
MODIFYDATEPOINTERS(sharesummary_free, row, cd, by, code, inet); |
|
|
|
if (!confirm_sharesummary) { |
|
par = 0; |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->workername, NULL, 0); |
|
params[par++] = bigint_to_buf(row->workinfoid, NULL, 0); |
|
params[par++] = double_to_buf(row->diffacc, NULL, 0); |
|
params[par++] = double_to_buf(row->diffsta, NULL, 0); |
|
params[par++] = double_to_buf(row->diffdup, NULL, 0); |
|
params[par++] = double_to_buf(row->diffhi, NULL, 0); |
|
params[par++] = double_to_buf(row->diffrej, NULL, 0); |
|
params[par++] = double_to_buf(row->shareacc, NULL, 0); |
|
params[par++] = double_to_buf(row->sharesta, NULL, 0); |
|
params[par++] = double_to_buf(row->sharedup, NULL, 0); |
|
params[par++] = double_to_buf(row->sharehi, NULL, 0); |
|
params[par++] = double_to_buf(row->sharerej, NULL, 0); |
|
params[par++] = bigint_to_buf(row->sharecount, NULL, 0); |
|
params[par++] = bigint_to_buf(row->errorcount, NULL, 0); |
|
params[par++] = tv_to_buf(&(row->firstshare), NULL, 0); |
|
params[par++] = tv_to_buf(&(row->lastshare), NULL, 0); |
|
params[par++] = double_to_buf(row->lastdiffacc, NULL, 0); |
|
params[par++] = str_to_buf(row->complete, NULL, 0); |
|
MODIFYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into sharesummary " |
|
"(userid,workername,workinfoid,diffacc,diffsta,diffdup,diffhi," |
|
"diffrej,shareacc,sharesta,sharedup,sharehi,sharerej," |
|
"sharecount,errorcount,firstshare,lastshare," |
|
"lastdiffacc,complete" |
|
MODIFYDATECONTROL ") values (" PQPARAM27 ")"; |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto unparam; |
|
} |
|
} |
|
|
|
row->countlastupdate = row->sharecount + row->errorcount; |
|
row->inserted = true; |
|
if (row->complete[0] == SUMMARY_COMPLETE) |
|
row->saveaged = true; |
|
} else { |
|
bool stats_update = false; |
|
|
|
MODIFYUPDATEPOINTERS(sharesummary_free, row, cd, by, code, inet); |
|
|
|
if ((row->countlastupdate + SHARESUMMARY_UPDATE_EVERY) < |
|
(row->sharecount + row->errorcount)) |
|
stats_update = true; |
|
|
|
if (must_update && row->countlastupdate < (row->sharecount + row->errorcount)) |
|
stats_update = true; |
|
|
|
if (stats_update) { |
|
if (!confirm_sharesummary) { |
|
par = 0; |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->workername, NULL, 0); |
|
params[par++] = bigint_to_buf(row->workinfoid, NULL, 0); |
|
params[par++] = double_to_buf(row->diffacc, NULL, 0); |
|
params[par++] = double_to_buf(row->diffsta, NULL, 0); |
|
params[par++] = double_to_buf(row->diffdup, NULL, 0); |
|
params[par++] = double_to_buf(row->diffhi, NULL, 0); |
|
params[par++] = double_to_buf(row->diffrej, NULL, 0); |
|
params[par++] = double_to_buf(row->shareacc, NULL, 0); |
|
params[par++] = double_to_buf(row->sharesta, NULL, 0); |
|
params[par++] = double_to_buf(row->sharedup, NULL, 0); |
|
params[par++] = double_to_buf(row->sharehi, NULL, 0); |
|
params[par++] = double_to_buf(row->sharerej, NULL, 0); |
|
params[par++] = tv_to_buf(&(row->firstshare), NULL, 0); |
|
params[par++] = tv_to_buf(&(row->lastshare), NULL, 0); |
|
params[par++] = bigint_to_buf(row->sharecount, NULL, 0); |
|
params[par++] = bigint_to_buf(row->errorcount, NULL, 0); |
|
params[par++] = double_to_buf(row->lastdiffacc, NULL, 0); |
|
params[par++] = str_to_buf(row->complete, NULL, 0); |
|
MODIFYUPDATEPARAMS(params, par, row); |
|
PARCHKVAL(par, 23, params); |
|
|
|
upd = "update sharesummary " |
|
"set diffacc=$4,diffsta=$5,diffdup=$6,diffhi=$7,diffrej=$8," |
|
"shareacc=$9,sharesta=$10,sharedup=$11,sharehi=$12," |
|
"sharerej=$13,firstshare=$14,lastshare=$15," |
|
"sharecount=$16,errorcount=$17,lastdiffacc=$18,complete=$19" |
|
",modifydate=$20,modifyby=$21,modifycode=$22,modifyinet=$23 " |
|
"where userid=$1 and workername=$2 and workinfoid=$3"; |
|
|
|
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; |
|
} |
|
} |
|
row->countlastupdate = row->sharecount + row->errorcount; |
|
if (row->complete[0] == SUMMARY_COMPLETE) |
|
row->saveaged = true; |
|
} else { |
|
if (!must_update) { |
|
ok = true; |
|
goto late; |
|
} else { |
|
if (!confirm_sharesummary) { |
|
par = 0; |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->workername, NULL, 0); |
|
params[par++] = bigint_to_buf(row->workinfoid, NULL, 0); |
|
params[par++] = str_to_buf(row->complete, NULL, 0); |
|
MODIFYUPDATEPARAMS(params, par, row); |
|
PARCHKVAL(par, 8, params); |
|
|
|
upd = "update sharesummary " |
|
"set complete=$4,modifydate=$5,modifyby=$6,modifycode=$7,modifyinet=$8 " |
|
"where userid=$1 and workername=$2 and workinfoid=$3"; |
|
|
|
res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("MustUpdate", rescode, conn); |
|
goto unparam; |
|
} |
|
} |
|
row->countlastupdate = row->sharecount + row->errorcount; |
|
if (row->complete[0] == SUMMARY_COMPLETE) |
|
row->saveaged = true; |
|
} |
|
} |
|
} |
|
startupskip: |
|
ok = true; |
|
unparam: |
|
if (par) { |
|
PQclear(res); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
} |
|
late: |
|
if (conned) |
|
PQfinish(conn); |
|
|
|
// We keep the new item no matter what 'ok' is, since it will be inserted later |
|
if (new) { |
|
K_WLOCK(sharesummary_free); |
|
sharesummary_root = add_to_ktree(sharesummary_root, item, cmp_sharesummary); |
|
sharesummary_workinfoid_root = add_to_ktree(sharesummary_workinfoid_root, |
|
item, |
|
cmp_sharesummary_workinfoid); |
|
k_add_head(sharesummary_store, item); |
|
K_WUNLOCK(sharesummary_free); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
bool sharesummary_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM *item, *m_item; |
|
int n, i, par = 0; |
|
SHARESUMMARY *row; |
|
MARKS *marks; |
|
char *params[2]; |
|
char *field; |
|
char *sel; |
|
int fields = 19; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
/* Load needs to go back to the last marks workinfoid(+1) |
|
* If it is later than that, we can't create markersummaries |
|
* since some of the required data is missing - |
|
* thus we also can't make the shift markersummaries */ |
|
m_item = last_in_ktree(marks_root, ctx); |
|
if (!m_item) { |
|
if (dbload_workinfoid_start != -1) { |
|
sharesummary_marks_limit = true; |
|
LOGWARNING("WARNING: dbload -w start used " |
|
"but there are no marks ..."); |
|
} |
|
} else { |
|
DATA_MARKS(marks, m_item); |
|
if (dbload_workinfoid_start > marks->workinfoid) { |
|
sharesummary_marks_limit = true; |
|
LOGWARNING("WARNING: dbload -w start %"PRId64 |
|
" is after the last mark %"PRId64" ...", |
|
dbload_workinfoid_start, |
|
marks->workinfoid); |
|
} |
|
} |
|
if (sharesummary_marks_limit) { |
|
LOGWARNING("WARNING: ... markersummaries cannot be created " |
|
"and pplns calculations may be wrong"); |
|
} |
|
|
|
// TODO: limit how far back |
|
sel = "select " |
|
"userid,workername,workinfoid,diffacc,diffsta,diffdup,diffhi," |
|
"diffrej,shareacc,sharesta,sharedup,sharehi,sharerej," |
|
"sharecount,errorcount,firstshare,lastshare," |
|
"lastdiffacc,complete" |
|
MODIFYDATECONTROL |
|
" from sharesummary where workinfoid>=$1 and workinfoid<=$2"; |
|
par = 0; |
|
params[par++] = bigint_to_buf(dbload_workinfoid_start, NULL, 0); |
|
params[par++] = bigint_to_buf(dbload_workinfoid_finish, 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 + MODIFYDATECOUNT)) { |
|
LOGERR("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, fields + MODIFYDATECOUNT, n); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQntuples(res); |
|
LOGDEBUG("%s(): tree build count %d", __func__, n); |
|
ok = true; |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(sharesummary_free); |
|
DATA_SHARESUMMARY(row, item); |
|
row->workername = NULL; |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
row->inserted = true; |
|
|
|
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; |
|
row->workername = strdup(field); |
|
LIST_MEM_ADD(sharesummary_free, row->workername); |
|
|
|
PQ_GET_FLD(res, i, "workinfoid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("workinfoid", field, row->workinfoid); |
|
|
|
PQ_GET_FLD(res, i, "diffacc", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffacc", field, row->diffacc); |
|
|
|
PQ_GET_FLD(res, i, "diffsta", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffsta", field, row->diffsta); |
|
|
|
PQ_GET_FLD(res, i, "diffdup", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffdup", field, row->diffdup); |
|
|
|
PQ_GET_FLD(res, i, "diffhi", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffhi", field, row->diffhi); |
|
|
|
PQ_GET_FLD(res, i, "diffrej", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffrej", field, row->diffrej); |
|
|
|
PQ_GET_FLD(res, i, "shareacc", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("shareacc", field, row->shareacc); |
|
|
|
PQ_GET_FLD(res, i, "sharesta", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("sharesta", field, row->sharesta); |
|
|
|
PQ_GET_FLD(res, i, "sharedup", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("sharedup", field, row->sharedup); |
|
|
|
PQ_GET_FLD(res, i, "sharehi", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("sharehi", field, row->sharehi); |
|
|
|
PQ_GET_FLD(res, i, "sharerej", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("sharerej", field, row->sharerej); |
|
|
|
PQ_GET_FLD(res, i, "sharecount", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("sharecount", field, row->sharecount); |
|
|
|
PQ_GET_FLD(res, i, "errorcount", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("errorcount", field, row->errorcount); |
|
|
|
row->countlastupdate = row->sharecount + row->errorcount; |
|
|
|
PQ_GET_FLD(res, i, "firstshare", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_TV("firstshare", field, row->firstshare); |
|
|
|
PQ_GET_FLD(res, i, "lastshare", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_TV("lastshare", field, row->lastshare); |
|
|
|
PQ_GET_FLD(res, i, "lastdiffacc", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("lastdiffacc", field, row->lastdiffacc); |
|
|
|
PQ_GET_FLD(res, i, "complete", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("complete", field, row->complete); |
|
|
|
MODIFYDATEFLDPOINTERS(sharesummary_free, res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
sharesummary_root = add_to_ktree(sharesummary_root, item, cmp_sharesummary); |
|
sharesummary_workinfoid_root = add_to_ktree(sharesummary_workinfoid_root, item, cmp_sharesummary_workinfoid); |
|
k_add_head(sharesummary_store, item); |
|
|
|
// A share summary is shares in a single workinfo, at all 3 levels n,a,y |
|
if (tolower(row->complete[0]) == SUMMARY_NEW) { |
|
if (dbstatus.oldest_sharesummary_firstshare_n.tv_sec == 0 || |
|
!tv_newer(&(dbstatus.oldest_sharesummary_firstshare_n), &(row->firstshare))) { |
|
copy_tv(&(dbstatus.oldest_sharesummary_firstshare_n), &(row->firstshare)); |
|
dbstatus.oldest_workinfoid_n = row->workinfoid; |
|
} |
|
} else { |
|
if (tv_newer(&(dbstatus.newest_sharesummary_firstshare_ay), &(row->firstshare))) |
|
copy_tv(&(dbstatus.newest_sharesummary_firstshare_ay), &(row->firstshare)); |
|
if (tolower(row->complete[0]) == SUMMARY_COMPLETE) { |
|
if (dbstatus.oldest_sharesummary_firstshare_a.tv_sec == 0 || |
|
!tv_newer(&(dbstatus.oldest_sharesummary_firstshare_a), &(row->firstshare))) { |
|
copy_tv(&(dbstatus.oldest_sharesummary_firstshare_a), &(row->firstshare)); |
|
dbstatus.oldest_workinfoid_a = row->workinfoid; |
|
} |
|
if (tv_newer(&(dbstatus.newest_sharesummary_firstshare_a), &(row->firstshare))) { |
|
copy_tv(&(dbstatus.newest_sharesummary_firstshare_a), &(row->firstshare)); |
|
dbstatus.newest_workinfoid_a = row->workinfoid; |
|
} |
|
} else /* SUMMARY_CONFIRM */ { |
|
if (tv_newer(&(dbstatus.newest_sharesummary_firstshare_y), &(row->firstshare))) { |
|
copy_tv(&(dbstatus.newest_sharesummary_firstshare_y), &(row->firstshare)); |
|
dbstatus.newest_workinfoid_y = row->workinfoid; |
|
} |
|
} |
|
} |
|
|
|
tick(); |
|
} |
|
if (!ok) { |
|
DATA_SHARESUMMARY(row, item); |
|
if (row->workername) { |
|
LIST_MEM_SUB(sharesummary_free, row->workername); |
|
FREENULL(row->workername); |
|
} |
|
k_add_head(sharesummary_free, item); |
|
} |
|
|
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d sharesummary records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
bool blocks_stats(PGconn *conn, int32_t height, char *blockhash, |
|
double diffacc, double diffinv, double shareacc, |
|
double shareinv, int64_t elapsed, |
|
char *by, char *code, char *inet, tv_t *cd) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res = NULL; |
|
K_ITEM *b_item, *old_b_item; |
|
BLOCKS *row, *oldblocks; |
|
char hash_dsp[16+1]; |
|
char *upd, *ins; |
|
char *params[8 + HISTORYDATECOUNT]; |
|
bool ok = false, update_old = false; |
|
int n, par = 0; |
|
|
|
LOGDEBUG("%s(): confirm", __func__); |
|
|
|
dsp_hash(blockhash, hash_dsp, sizeof(hash_dsp)); |
|
|
|
K_RLOCK(blocks_free); |
|
old_b_item = find_blocks(height, blockhash); |
|
K_RUNLOCK(blocks_free); |
|
|
|
if (!old_b_item) { |
|
LOGERR("%s(): Non-existent Block: %d/...%s", |
|
__func__, height, hash_dsp); |
|
return false; |
|
} |
|
|
|
DATA_BLOCKS(oldblocks, old_b_item); |
|
|
|
K_WLOCK(blocks_free); |
|
b_item = k_unlink_head(blocks_free); |
|
K_WUNLOCK(blocks_free); |
|
|
|
DATA_BLOCKS(row, b_item); |
|
memcpy(row, oldblocks, sizeof(*row)); |
|
row->diffacc = diffacc; |
|
row->diffinv = diffinv; |
|
row->shareacc = shareacc; |
|
row->shareinv = shareinv; |
|
row->elapsed = elapsed; |
|
row->statsconfirmed[0] = BLOCKS_STATSCONFIRMED; |
|
row->statsconfirmed[1] = '\0'; |
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
|
|
upd = "update blocks set expirydate=$1 where blockhash=$2 and expirydate=$3"; |
|
par = 0; |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
params[par++] = str_to_buf(row->blockhash, NULL, 0); |
|
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); |
|
PARCHKVAL(par, 3, params); |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
res = PQexec(conn, "Begin", CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Begin", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
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; |
|
} |
|
|
|
update_old = true; |
|
|
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
par = 0; |
|
params[par++] = str_to_buf(row->blockhash, NULL, 0); |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
params[par++] = double_to_buf(row->diffacc, NULL, 0); |
|
params[par++] = double_to_buf(row->diffinv, NULL, 0); |
|
params[par++] = double_to_buf(row->shareacc, NULL, 0); |
|
params[par++] = double_to_buf(row->shareinv, NULL, 0); |
|
params[par++] = bigint_to_buf(row->elapsed, NULL, 0); |
|
params[par++] = str_to_buf(row->statsconfirmed, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHKVAL(par, 8 + HISTORYDATECOUNT, params); // 13 as per ins |
|
|
|
ins = "insert into blocks " |
|
"(height,blockhash,workinfoid,userid,workername," |
|
"clientid,enonce1,nonce2,nonce,reward,confirmed," |
|
"diffacc,diffinv,shareacc,shareinv,elapsed," |
|
"statsconfirmed" |
|
HISTORYDATECONTROL ") select " |
|
"height,blockhash,workinfoid,userid,workername," |
|
"clientid,enonce1,nonce2,nonce,reward,confirmed," |
|
"$3,$4,$5,$6,$7,$8," |
|
"$9,$10,$11,$12,$13 from blocks where " |
|
"blockhash=$1 and expirydate=$2"; |
|
|
|
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 (ok) |
|
res = PQexec(conn, "Commit", CKPQ_WRITE); |
|
else |
|
res = PQexec(conn, "Rollback", CKPQ_WRITE); |
|
|
|
PQclear(res); |
|
unparam: |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
if (conned) |
|
PQfinish(conn); |
|
|
|
K_WLOCK(blocks_free); |
|
if (!ok) |
|
k_add_head(blocks_free, b_item); |
|
else { |
|
if (update_old) { |
|
blocks_root = remove_from_ktree(blocks_root, old_b_item, cmp_blocks); |
|
copy_tv(&(oldblocks->expirydate), cd); |
|
blocks_root = add_to_ktree(blocks_root, old_b_item, cmp_blocks); |
|
} |
|
blocks_root = add_to_ktree(blocks_root, b_item, cmp_blocks); |
|
k_add_head(blocks_store, b_item); |
|
} |
|
K_WUNLOCK(blocks_free); |
|
|
|
return ok; |
|
} |
|
|
|
bool blocks_add(PGconn *conn, char *height, char *blockhash, |
|
char *confirmed, char *workinfoid, char *username, |
|
char *workername, char *clientid, char *enonce1, |
|
char *nonce2, char *nonce, char *reward, |
|
char *by, char *code, char *inet, tv_t *cd, |
|
bool igndup, char *id, K_TREE *trf_root) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res = NULL; |
|
K_ITEM *b_item, *u_item, *old_b_item; |
|
char cd_buf[DATE_BUFSIZ]; |
|
char hash_dsp[16+1]; |
|
BLOCKS *row, *oldblocks; |
|
USERS *users; |
|
char *upd, *ins; |
|
char *params[17 + HISTORYDATECOUNT]; |
|
bool ok = false, update_old = false; |
|
int n, par = 0; |
|
char want = '?'; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(blocks_free); |
|
b_item = k_unlink_head(blocks_free); |
|
K_WUNLOCK(blocks_free); |
|
|
|
DATA_BLOCKS(row, b_item); |
|
|
|
TXT_TO_INT("height", height, row->height); |
|
STRNCPY(row->blockhash, blockhash); |
|
|
|
dsp_hash(blockhash, hash_dsp, sizeof(hash_dsp)); |
|
|
|
K_RLOCK(blocks_free); |
|
old_b_item = find_blocks(row->height, blockhash); |
|
K_RUNLOCK(blocks_free); |
|
DATA_BLOCKS_NULL(oldblocks, old_b_item); |
|
|
|
switch (confirmed[0]) { |
|
case BLOCKS_NEW: |
|
// None should exist - so must be a duplicate |
|
if (old_b_item) { |
|
K_WLOCK(blocks_free); |
|
k_add_head(blocks_free, b_item); |
|
K_WUNLOCK(blocks_free); |
|
if (!igndup) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s(): Duplicate (%s) blocks ignored, Status: " |
|
"%s, Block: %s/...%s/%s", |
|
__func__, |
|
blocks_confirmed(oldblocks->confirmed), |
|
blocks_confirmed(confirmed), |
|
height, hash_dsp, cd_buf); |
|
} |
|
return true; |
|
} |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(username); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) |
|
row->userid = KANO; |
|
else { |
|
DATA_USERS(users, u_item); |
|
row->userid = users->userid; |
|
} |
|
|
|
STRNCPY(row->confirmed, confirmed); |
|
TXT_TO_BIGINT("workinfoid", workinfoid, row->workinfoid); |
|
STRNCPY(row->workername, workername); |
|
TXT_TO_INT("clientid", clientid, row->clientid); |
|
STRNCPY(row->enonce1, enonce1); |
|
STRNCPY(row->nonce2, nonce2); |
|
STRNCPY(row->nonce, nonce); |
|
TXT_TO_BIGINT("reward", reward, row->reward); |
|
// Specify them |
|
row->diffacc = 0; |
|
row->diffinv = 0; |
|
row->shareacc = 0; |
|
row->shareinv = 0; |
|
row->elapsed = 0; |
|
STRNCPY(row->statsconfirmed, BLOCKS_STATSPENDING_STR); |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
par = 0; |
|
params[par++] = int_to_buf(row->height, NULL, 0); |
|
params[par++] = str_to_buf(row->blockhash, NULL, 0); |
|
params[par++] = bigint_to_buf(row->workinfoid, NULL, 0); |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->workername, NULL, 0); |
|
params[par++] = int_to_buf(row->clientid, NULL, 0); |
|
params[par++] = str_to_buf(row->enonce1, NULL, 0); |
|
params[par++] = str_to_buf(row->nonce2, NULL, 0); |
|
params[par++] = str_to_buf(row->nonce, NULL, 0); |
|
params[par++] = bigint_to_buf(row->reward, NULL, 0); |
|
params[par++] = str_to_buf(row->confirmed, NULL, 0); |
|
params[par++] = double_to_buf(row->diffacc, NULL, 0); |
|
params[par++] = double_to_buf(row->diffinv, NULL, 0); |
|
params[par++] = double_to_buf(row->shareacc, NULL, 0); |
|
params[par++] = double_to_buf(row->shareinv, NULL, 0); |
|
params[par++] = bigint_to_buf(row->elapsed, NULL, 0); |
|
params[par++] = str_to_buf(row->statsconfirmed, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into blocks " |
|
"(height,blockhash,workinfoid,userid,workername," |
|
"clientid,enonce1,nonce2,nonce,reward,confirmed," |
|
"diffacc,diffinv,shareacc,shareinv,elapsed," |
|
"statsconfirmed" |
|
HISTORYDATECONTROL ") values (" PQPARAM22 ")"; |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
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 unparam; |
|
} |
|
// We didn't use a Begin |
|
ok = true; |
|
goto unparam; |
|
break; |
|
case BLOCKS_ORPHAN: |
|
case BLOCKS_42: |
|
// These shouldn't be possible until startup completes |
|
if (!startup_complete) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s(): Status: %s invalid during startup. " |
|
"Ignored: Block: %s/...%s/%s", |
|
__func__, |
|
blocks_confirmed(confirmed), |
|
height, hash_dsp, cd_buf); |
|
goto flail; |
|
} |
|
want = BLOCKS_CONFIRM; |
|
case BLOCKS_CONFIRM: |
|
if (!old_b_item) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s(): Can't %s a non-existent Block: %s/...%s/%s", |
|
__func__, blocks_confirmed(confirmed), |
|
height, hash_dsp, cd_buf); |
|
goto flail; |
|
} |
|
if (confirmed[0] == BLOCKS_CONFIRM) |
|
want = BLOCKS_NEW; |
|
if (oldblocks->confirmed[0] != want) { |
|
// No mismatch messages during startup |
|
if (startup_complete) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s(): New Status: %s requires Status: %c. " |
|
"Ignored: Status: %s, Block: %s/...%s/%s", |
|
__func__, |
|
blocks_confirmed(confirmed), want, |
|
blocks_confirmed(oldblocks->confirmed), |
|
height, hash_dsp, cd_buf); |
|
} |
|
goto flail; |
|
} |
|
|
|
upd = "update blocks set expirydate=$1 where blockhash=$2 and expirydate=$3"; |
|
par = 0; |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
params[par++] = str_to_buf(row->blockhash, NULL, 0); |
|
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); |
|
PARCHKVAL(par, 3, params); |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
// New is mostly a copy of the old |
|
memcpy(row, oldblocks, sizeof(*row)); |
|
STRNCPY(row->confirmed, confirmed); |
|
if (confirmed[0] == BLOCKS_CONFIRM) { |
|
row->diffacc = pool.diffacc; |
|
row->diffinv = pool.diffinv; |
|
row->shareacc = pool.shareacc; |
|
row->shareinv = pool.shareinv; |
|
} |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
res = PQexec(conn, "Begin", CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Begin", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
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->blockhash, NULL, 0); |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
params[par++] = str_to_buf(row->confirmed, NULL, 0); |
|
|
|
if (confirmed[0] == BLOCKS_CONFIRM) { |
|
params[par++] = double_to_buf(row->diffacc, NULL, 0); |
|
params[par++] = double_to_buf(row->diffinv, NULL, 0); |
|
params[par++] = double_to_buf(row->shareacc, NULL, 0); |
|
params[par++] = double_to_buf(row->shareinv, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHKVAL(par, 7 + HISTORYDATECOUNT, params); // 12 as per ins |
|
|
|
ins = "insert into blocks " |
|
"(height,blockhash,workinfoid,userid,workername," |
|
"clientid,enonce1,nonce2,nonce,reward,confirmed," |
|
"diffacc,diffinv,shareacc,shareinv,elapsed," |
|
"statsconfirmed" |
|
HISTORYDATECONTROL ") select " |
|
"height,blockhash,workinfoid,userid,workername," |
|
"clientid,enonce1,nonce2,nonce,reward," |
|
"$3,$4,$5,$6,$7,elapsed,statsconfirmed," |
|
"$8,$9,$10,$11,$12 from blocks where " |
|
"blockhash=$1 and expirydate=$2"; |
|
} else { |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHKVAL(par, 3 + HISTORYDATECOUNT, params); // 8 as per ins |
|
|
|
ins = "insert into blocks " |
|
"(height,blockhash,workinfoid,userid,workername," |
|
"clientid,enonce1,nonce2,nonce,reward,confirmed," |
|
"diffacc,diffinv,shareacc,shareinv,elapsed," |
|
"statsconfirmed" |
|
HISTORYDATECONTROL ") select " |
|
"height,blockhash,workinfoid,userid,workername," |
|
"clientid,enonce1,nonce2,nonce,reward," |
|
"$3,diffacc,diffinv,shareacc,shareinv,elapsed," |
|
"statsconfirmed," |
|
"$4,$5,$6,$7,$8 from blocks where " |
|
"blockhash=$1 and expirydate=$2"; |
|
} |
|
|
|
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; |
|
} |
|
|
|
update_old = true; |
|
break; |
|
default: |
|
LOGERR("%s(): %s.failed.invalid confirm='%s'", |
|
__func__, id, confirmed); |
|
goto flail; |
|
} |
|
|
|
ok = true; |
|
rollback: |
|
if (ok) |
|
res = PQexec(conn, "Commit", CKPQ_WRITE); |
|
else |
|
res = PQexec(conn, "Rollback", CKPQ_WRITE); |
|
|
|
PQclear(res); |
|
unparam: |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
flail: |
|
if (conned) |
|
PQfinish(conn); |
|
|
|
K_WLOCK(blocks_free); |
|
if (!ok) |
|
k_add_head(blocks_free, b_item); |
|
else { |
|
if (update_old) { |
|
blocks_root = remove_from_ktree(blocks_root, old_b_item, cmp_blocks); |
|
copy_tv(&(oldblocks->expirydate), cd); |
|
blocks_root = add_to_ktree(blocks_root, old_b_item, cmp_blocks); |
|
} |
|
blocks_root = add_to_ktree(blocks_root, b_item, cmp_blocks); |
|
k_add_head(blocks_store, b_item); |
|
} |
|
K_WUNLOCK(blocks_free); |
|
|
|
if (ok) { |
|
char pct[16] = "?"; |
|
char est[16] = ""; |
|
K_ITEM *w_item; |
|
char tmp[256]; |
|
bool blk; |
|
|
|
switch (confirmed[0]) { |
|
case BLOCKS_NEW: |
|
blk = true; |
|
tv_to_buf(&(row->createdate), cd_buf, sizeof(cd_buf)); |
|
snprintf(tmp, sizeof(tmp), " UTC:%s", cd_buf); |
|
break; |
|
case BLOCKS_CONFIRM: |
|
blk = true; |
|
w_item = find_workinfo(row->workinfoid, NULL); |
|
if (w_item) { |
|
char wdiffbin[TXT_SML+1]; |
|
double wdiff; |
|
WORKINFO *workinfo; |
|
DATA_WORKINFO(workinfo, w_item); |
|
hex2bin(wdiffbin, workinfo->bits, 4); |
|
wdiff = diff_from_nbits(wdiffbin); |
|
if (wdiff > 0.0) { |
|
snprintf(pct, sizeof(pct), "%.2f", |
|
100.0 * pool.diffacc / wdiff); |
|
} |
|
} |
|
if (pool.diffacc >= 1000.0) { |
|
suffix_string(pool.diffacc, est, sizeof(est)-1, 0); |
|
strcat(est, " "); |
|
} |
|
tv_to_buf(&(row->createdate), cd_buf, sizeof(cd_buf)); |
|
snprintf(tmp, sizeof(tmp), |
|
" Reward: %f, Worker: %s, ShareEst: %.1f %s%s%% UTC:%s", |
|
BTC_TO_D(row->reward), |
|
row->workername, |
|
pool.diffacc, est, pct, cd_buf); |
|
if (pool.workinfoid < row->workinfoid) { |
|
pool.workinfoid = row->workinfoid; |
|
pool.height = row->height; |
|
zero_on_new_block(); |
|
} |
|
break; |
|
case BLOCKS_ORPHAN: |
|
case BLOCKS_42: |
|
default: |
|
blk = false; |
|
tmp[0] = '\0'; |
|
break; |
|
} |
|
|
|
LOGWARNING("%s(): %sStatus: %s, Block: %s/...%s%s", |
|
__func__, blk ? "BLOCK! " : "", |
|
blocks_confirmed(confirmed), |
|
height, hash_dsp, tmp); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
bool blocks_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
BLOCKS *row; |
|
char *field; |
|
char *sel; |
|
int fields = 17; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
sel = "select " |
|
"height,blockhash,workinfoid,userid,workername," |
|
"clientid,enonce1,nonce2,nonce,reward,confirmed," |
|
"diffacc,diffinv,shareacc,shareinv,elapsed,statsconfirmed" |
|
HISTORYDATECONTROL |
|
" from blocks"; |
|
res = PQexec(conn, sel, 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; |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(blocks_free); |
|
DATA_BLOCKS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
PQ_GET_FLD(res, i, "height", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_INT("height", field, row->height); |
|
|
|
PQ_GET_FLD(res, i, "blockhash", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("blockhash", field, row->blockhash); |
|
|
|
PQ_GET_FLD(res, i, "workinfoid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("workinfoid", field, row->workinfoid); |
|
|
|
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, "clientid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_INT("clientid", field, row->clientid); |
|
|
|
PQ_GET_FLD(res, i, "enonce1", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("enonce1", field, row->enonce1); |
|
|
|
PQ_GET_FLD(res, i, "nonce2", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("nonce2", field, row->nonce2); |
|
|
|
PQ_GET_FLD(res, i, "nonce", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("nonce", field, row->nonce); |
|
|
|
PQ_GET_FLD(res, i, "reward", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("reward", field, row->reward); |
|
|
|
PQ_GET_FLD(res, i, "confirmed", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("confirmed", field, row->confirmed); |
|
|
|
PQ_GET_FLD(res, i, "diffacc", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffacc", field, row->diffacc); |
|
|
|
PQ_GET_FLD(res, i, "diffinv", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffinv", field, row->diffinv); |
|
|
|
PQ_GET_FLD(res, i, "shareacc", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("shareacc", field, row->shareacc); |
|
|
|
PQ_GET_FLD(res, i, "shareinv", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("shareinv", field, row->shareinv); |
|
|
|
PQ_GET_FLD(res, i, "elapsed", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("elapsed", field, row->elapsed); |
|
|
|
PQ_GET_FLD(res, i, "statsconfirmed", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("statsconfirmed", field, row->statsconfirmed); |
|
|
|
HISTORYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
blocks_root = add_to_ktree(blocks_root, item, cmp_blocks); |
|
k_add_head(blocks_store, item); |
|
|
|
if (tv_newer(&(dbstatus.newest_createdate_blocks), &(row->createdate))) |
|
copy_tv(&(dbstatus.newest_createdate_blocks), &(row->createdate)); |
|
|
|
if (pool.workinfoid < row->workinfoid) { |
|
pool.workinfoid = row->workinfoid; |
|
pool.height = row->height; |
|
} |
|
} |
|
if (!ok) |
|
k_add_head(blocks_free, item); |
|
|
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d blocks records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
bool miningpayouts_add(PGconn *conn, char *username, char *height, |
|
char *blockhash, char *amount, char *by, |
|
char *code, char *inet, tv_t *cd, K_TREE *trf_root) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
K_ITEM *m_item, *u_item; |
|
bool ok = false; |
|
MININGPAYOUTS *row; |
|
USERS *users; |
|
char *ins; |
|
char *params[5 + HISTORYDATECOUNT]; |
|
int n, par = 0; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(miningpayouts_free); |
|
m_item = k_unlink_head(miningpayouts_free); |
|
K_WUNLOCK(miningpayouts_free); |
|
|
|
DATA_MININGPAYOUTS(row, m_item); |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
row->miningpayoutid = nextid(conn, "miningpayoutid", (int64_t)1, cd, by, code, inet); |
|
if (row->miningpayoutid == 0) |
|
goto unitem; |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(username); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) { |
|
char *txt; |
|
LOGERR("%s(): unknown user '%s'", |
|
__func__, |
|
txt = safe_text(username)); |
|
free(txt); |
|
goto unitem; |
|
} |
|
DATA_USERS(users, u_item); |
|
|
|
row->userid = users->userid; |
|
TXT_TO_INT("height", height, row->height); |
|
STRNCPY(row->blockhash, blockhash); |
|
TXT_TO_BIGINT("amount", amount, row->amount); |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
par = 0; |
|
params[par++] = bigint_to_buf(row->miningpayoutid, NULL, 0); |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = int_to_buf(row->height, NULL, 0); |
|
params[par++] = str_to_buf(row->blockhash, NULL, 0); |
|
params[par++] = bigint_to_buf(row->amount, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into miningpayouts " |
|
"(miningpayoutid,userid,height,blockhash,amount" |
|
HISTORYDATECONTROL ") values (" PQPARAM10 ")"; |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
ok = true; |
|
|
|
unparam: |
|
PQclear(res); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
unitem: |
|
if (conned) |
|
PQfinish(conn); |
|
K_WLOCK(miningpayouts_free); |
|
if (!ok) |
|
k_add_head(miningpayouts_free, m_item); |
|
else { |
|
miningpayouts_root = add_to_ktree(miningpayouts_root, m_item, cmp_miningpayouts); |
|
k_add_head(miningpayouts_store, m_item); |
|
} |
|
K_WUNLOCK(miningpayouts_free); |
|
|
|
return ok; |
|
} |
|
|
|
bool miningpayouts_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
MININGPAYOUTS *row; |
|
char *params[1]; |
|
int n, i, par = 0; |
|
char *field; |
|
char *sel; |
|
int fields = 5; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
sel = "select " |
|
"miningpayoutid,userid,height,blockhash,amount" |
|
HISTORYDATECONTROL |
|
" from miningpayouts 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(miningpayouts_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(miningpayouts_free); |
|
DATA_MININGPAYOUTS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
PQ_GET_FLD(res, i, "miningpayoutid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("miningpayoutid", field, row->miningpayoutid); |
|
|
|
PQ_GET_FLD(res, i, "userid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("userid", field, row->userid); |
|
|
|
PQ_GET_FLD(res, i, "height", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_INT("height", field, row->height); |
|
|
|
PQ_GET_FLD(res, i, "blockhash", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("blockhash", field, row->blockhash); |
|
|
|
PQ_GET_FLD(res, i, "amount", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("amount", field, row->amount); |
|
|
|
HISTORYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
miningpayouts_root = add_to_ktree(miningpayouts_root, item, cmp_miningpayouts); |
|
k_add_head(miningpayouts_store, item); |
|
|
|
tick(); |
|
} |
|
if (!ok) |
|
k_add_head(miningpayouts_free, item); |
|
|
|
K_WUNLOCK(miningpayouts_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d miningpayout records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
bool auths_add(PGconn *conn, char *poolinstance, char *username, |
|
char *workername, char *clientid, char *enonce1, |
|
char *useragent, char *preauth, char *by, char *code, |
|
char *inet, tv_t *cd, bool igndup, K_TREE *trf_root, |
|
bool addressuser, USERS **users, WORKERS **workers) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM *a_item, *u_item, *w_item; |
|
char cd_buf[DATE_BUFSIZ]; |
|
AUTHS *row; |
|
char *ins; |
|
char *params[8 + HISTORYDATECOUNT]; |
|
int n, par = 0; |
|
bool ok = false; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(auths_free); |
|
a_item = k_unlink_head(auths_free); |
|
K_WUNLOCK(auths_free); |
|
|
|
DATA_AUTHS(row, a_item); |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(username); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) { |
|
if (addressuser) { |
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
u_item = users_add(conn, username, EMPTY, EMPTY, |
|
by, code, inet, cd, trf_root); |
|
} else { |
|
char *txt; |
|
LOGDEBUG("%s(): unknown user '%s'", |
|
__func__, |
|
txt = safe_text(username)); |
|
free(txt); |
|
} |
|
if (!u_item) |
|
goto unitem; |
|
} |
|
DATA_USERS(*users, u_item); |
|
|
|
// Any status content means disallow mining |
|
if ((*users)->status[0]) |
|
goto unitem; |
|
|
|
STRNCPY(row->poolinstance, poolinstance); |
|
row->userid = (*users)->userid; |
|
// since update=false, a dup will be ok and do nothing when igndup=true |
|
w_item = new_worker(conn, false, row->userid, workername, |
|
DIFFICULTYDEFAULT_DEF_STR, |
|
IDLENOTIFICATIONENABLED_DEF, |
|
IDLENOTIFICATIONTIME_DEF_STR, |
|
by, code, inet, cd, trf_root); |
|
if (!w_item) |
|
goto unitem; |
|
|
|
DATA_WORKERS(*workers, w_item); |
|
STRNCPY(row->workername, workername); |
|
TXT_TO_INT("clientid", clientid, row->clientid); |
|
STRNCPY(row->enonce1, enonce1); |
|
STRNCPY(row->useragent, useragent); |
|
STRNCPY(row->preauth, preauth); |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
K_WLOCK(auths_free); |
|
if (find_in_ktree(auths_root, a_item, cmp_auths, ctx)) { |
|
k_add_head(auths_free, a_item); |
|
K_WUNLOCK(auths_free); |
|
|
|
if (conned) |
|
PQfinish(conn); |
|
|
|
if (!igndup) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s(): Duplicate auths ignored %s/%s/%s", |
|
__func__, poolinstance, workername, cd_buf); |
|
} |
|
|
|
/* Let them mine, that's what matters :) |
|
* though this would normally only be during a reload */ |
|
return true; |
|
} |
|
K_WUNLOCK(auths_free); |
|
|
|
// Update even if DB fails |
|
workerstatus_update(row, NULL, NULL); |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
row->authid = nextid(conn, "authid", (int64_t)1, cd, by, code, inet); |
|
if (row->authid == 0) |
|
goto unitem; |
|
|
|
par = 0; |
|
params[par++] = bigint_to_buf(row->authid, NULL, 0); |
|
params[par++] = str_to_buf(row->poolinstance, NULL, 0); |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->workername, NULL, 0); |
|
params[par++] = int_to_buf(row->clientid, NULL, 0); |
|
params[par++] = str_to_buf(row->enonce1, NULL, 0); |
|
params[par++] = str_to_buf(row->useragent, NULL, 0); |
|
params[par++] = str_to_buf(row->preauth, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into auths " |
|
"(authid,poolinstance,userid,workername,clientid,enonce1,useragent,preauth" |
|
HISTORYDATECONTROL ") values (" PQPARAM13 ")"; |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto unparam; |
|
} |
|
ok = true; |
|
unparam: |
|
PQclear(res); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
unitem: |
|
if (conned) |
|
PQfinish(conn); |
|
K_WLOCK(auths_free); |
|
if (!ok) |
|
k_add_head(auths_free, a_item); |
|
else { |
|
auths_root = add_to_ktree(auths_root, a_item, cmp_auths); |
|
k_add_head(auths_store, a_item); |
|
} |
|
K_WUNLOCK(auths_free); |
|
|
|
return ok; |
|
} |
|
|
|
bool auths_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
AUTHS *row; |
|
// char *params[1]; |
|
int n, i; |
|
// int par = 0; |
|
char *field; |
|
char *sel; |
|
int fields = 7 + 1; // +1 = 'best' |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
// TODO: add/update a (single) fake auth every ~10min or 10min after the last one? |
|
#if 0 |
|
sel = "select " |
|
"authid,userid,workername,clientid,enonce1,useragent,preauth" |
|
HISTORYDATECONTROL |
|
" from auths 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; |
|
} |
|
#endif |
|
|
|
// Only load the last record for each workername |
|
sel = "with last as (" |
|
"select authid,userid,workername,clientid,enonce1,useragent,preauth" |
|
HISTORYDATECONTROL |
|
",row_number() over(partition by userid,workername " |
|
"order by expirydate desc, createdate desc)" |
|
" as best from auths" |
|
") select * from last where best=1"; |
|
|
|
res = PQexec(conn, sel, 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(auths_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(auths_free); |
|
DATA_AUTHS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
PQ_GET_FLD(res, i, "authid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("authid", field, row->authid); |
|
|
|
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, "clientid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_INT("clientid", field, row->clientid); |
|
|
|
PQ_GET_FLD(res, i, "enonce1", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("enonce1", field, row->enonce1); |
|
|
|
PQ_GET_FLD(res, i, "useragent", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("useragent", field, row->useragent); |
|
|
|
PQ_GET_FLD(res, i, "preauth", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("preauth", field, row->preauth); |
|
|
|
HISTORYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
auths_root = add_to_ktree(auths_root, item, cmp_auths); |
|
k_add_head(auths_store, item); |
|
workerstatus_update(row, NULL, NULL); |
|
|
|
if (tv_newer(&(dbstatus.newest_createdate_auths), &(row->createdate))) |
|
copy_tv(&(dbstatus.newest_createdate_auths), &(row->createdate)); |
|
|
|
tick(); |
|
} |
|
if (!ok) |
|
k_add_head(auths_free, item); |
|
|
|
K_WUNLOCK(auths_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d auth records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
bool poolstats_add(PGconn *conn, bool store, char *poolinstance, |
|
char *elapsed, char *users, char *workers, |
|
char *hashrate, char *hashrate5m, |
|
char *hashrate1hr, char *hashrate24hr, |
|
char *by, char *code, char *inet, tv_t *cd, |
|
bool igndup, K_TREE *trf_root) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM *p_item; |
|
POOLSTATS *row; |
|
char *ins; |
|
char *params[8 + SIMPLEDATECOUNT]; |
|
int n, par = 0; |
|
bool ok = false; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(poolstats_free); |
|
p_item = k_unlink_head(poolstats_free); |
|
K_WUNLOCK(poolstats_free); |
|
|
|
DATA_POOLSTATS(row, p_item); |
|
|
|
row->stored = false; |
|
|
|
STRNCPY(row->poolinstance, poolinstance); |
|
TXT_TO_BIGINT("elapsed", elapsed, row->elapsed); |
|
TXT_TO_INT("users", users, row->users); |
|
TXT_TO_INT("workers", workers, row->workers); |
|
TXT_TO_DOUBLE("hashrate", hashrate, row->hashrate); |
|
TXT_TO_DOUBLE("hashrate5m", hashrate5m, row->hashrate5m); |
|
TXT_TO_DOUBLE("hashrate1hr", hashrate1hr, row->hashrate1hr); |
|
TXT_TO_DOUBLE("hashrate24hr", hashrate24hr, row->hashrate24hr); |
|
|
|
SIMPLEDATEINIT(row, cd, by, code, inet); |
|
SIMPLEDATETRANSFER(trf_root, row); |
|
|
|
if (igndup && find_in_ktree(poolstats_root, p_item, cmp_poolstats, ctx)) { |
|
K_WLOCK(poolstats_free); |
|
k_add_head(poolstats_free, p_item); |
|
K_WUNLOCK(poolstats_free); |
|
return true; |
|
} |
|
|
|
if (store) { |
|
par = 0; |
|
params[par++] = str_to_buf(row->poolinstance, NULL, 0); |
|
params[par++] = bigint_to_buf(row->elapsed, NULL, 0); |
|
params[par++] = int_to_buf(row->users, NULL, 0); |
|
params[par++] = int_to_buf(row->workers, NULL, 0); |
|
params[par++] = bigint_to_buf(row->hashrate, NULL, 0); |
|
params[par++] = bigint_to_buf(row->hashrate5m, NULL, 0); |
|
params[par++] = bigint_to_buf(row->hashrate1hr, NULL, 0); |
|
params[par++] = bigint_to_buf(row->hashrate24hr, NULL, 0); |
|
SIMPLEDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into poolstats " |
|
"(poolinstance,elapsed,users,workers,hashrate," |
|
"hashrate5m,hashrate1hr,hashrate24hr" |
|
SIMPLEDATECONTROL ") values (" PQPARAM12 ")"; |
|
|
|
if (!conn) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
row->stored = true; |
|
} |
|
|
|
ok = true; |
|
unparam: |
|
if (store) { |
|
PQclear(res); |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
} |
|
|
|
K_WLOCK(poolstats_free); |
|
if (!ok) |
|
k_add_head(poolstats_free, p_item); |
|
else { |
|
poolstats_root = add_to_ktree(poolstats_root, p_item, cmp_poolstats); |
|
k_add_head(poolstats_store, p_item); |
|
} |
|
K_WUNLOCK(poolstats_free); |
|
|
|
return ok; |
|
} |
|
|
|
// TODO: data selection - only require ? |
|
bool poolstats_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
struct tm tm; |
|
time_t now_t; |
|
char tzinfo[16], stamp[128]; |
|
POOLSTATS *row; |
|
char *field; |
|
char *sel = NULL; |
|
size_t len, off; |
|
int fields = 8; |
|
long minoff, hroff; |
|
char tzch; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
// Temoprarily ... load last 24hrs worth |
|
now_t = time(NULL); |
|
now_t -= 24 * 60 * 60; |
|
localtime_r(&now_t, &tm); |
|
minoff = tm.tm_gmtoff / 60; |
|
if (minoff < 0) { |
|
tzch = '-'; |
|
minoff *= -1; |
|
} else |
|
tzch = '+'; |
|
hroff = minoff / 60; |
|
if (minoff % 60) { |
|
snprintf(tzinfo, sizeof(tzinfo), |
|
"%c%02ld:%02ld", |
|
tzch, hroff, minoff % 60); |
|
} else { |
|
snprintf(tzinfo, sizeof(tzinfo), |
|
"%c%02ld", |
|
tzch, hroff); |
|
} |
|
snprintf(stamp, sizeof(stamp), |
|
"'%d-%02d-%02d %02d:%02d:%02d%s'", |
|
tm.tm_year + 1900, |
|
tm.tm_mon + 1, |
|
tm.tm_mday, |
|
tm.tm_hour, |
|
tm.tm_min, |
|
tm.tm_sec, |
|
tzinfo); |
|
|
|
APPEND_REALLOC_INIT(sel, off, len); |
|
APPEND_REALLOC(sel, off, len, |
|
"select " |
|
"poolinstance,elapsed,users,workers,hashrate," |
|
"hashrate5m,hashrate1hr,hashrate24hr" |
|
SIMPLEDATECONTROL |
|
" from poolstats where createdate>"); |
|
APPEND_REALLOC(sel, off, len, stamp); |
|
|
|
res = PQexec(conn, sel, CKPQ_READ); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Select", rescode, conn); |
|
PQclear(res); |
|
ok = false; |
|
goto clean; |
|
} |
|
|
|
n = PQnfields(res); |
|
if (n != (fields + SIMPLEDATECOUNT)) { |
|
LOGERR("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, fields + SIMPLEDATECOUNT, n); |
|
PQclear(res); |
|
ok = false; |
|
goto clean; |
|
} |
|
|
|
n = PQntuples(res); |
|
LOGDEBUG("%s(): tree build count %d", __func__, n); |
|
ok = true; |
|
K_WLOCK(poolstats_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(poolstats_free); |
|
DATA_POOLSTATS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
row->stored = true; |
|
|
|
PQ_GET_FLD(res, i, "poolinstance", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("poolinstance", field, row->poolinstance); |
|
|
|
PQ_GET_FLD(res, i, "elapsed", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("elapsed", field, row->elapsed); |
|
|
|
PQ_GET_FLD(res, i, "users", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_INT("users", field, row->users); |
|
|
|
PQ_GET_FLD(res, i, "workers", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_INT("workers", field, row->workers); |
|
|
|
PQ_GET_FLD(res, i, "hashrate", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("hashrate", field, row->hashrate); |
|
|
|
PQ_GET_FLD(res, i, "hashrate5m", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("hashrate5m", field, row->hashrate5m); |
|
|
|
PQ_GET_FLD(res, i, "hashrate1hr", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("hashrate1hr", field, row->hashrate1hr); |
|
|
|
PQ_GET_FLD(res, i, "hashrate24hr", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("hashrate24hr", field, row->hashrate24hr); |
|
|
|
SIMPLEDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
poolstats_root = add_to_ktree(poolstats_root, item, cmp_poolstats); |
|
k_add_head(poolstats_store, item); |
|
|
|
if (tv_newer(&(dbstatus.newest_createdate_poolstats), &(row->createdate))) |
|
copy_tv(&(dbstatus.newest_createdate_poolstats), &(row->createdate)); |
|
|
|
tick(); |
|
} |
|
if (!ok) |
|
k_add_head(poolstats_free, item); |
|
|
|
K_WUNLOCK(poolstats_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d poolstats records", __func__, n); |
|
} |
|
clean: |
|
free(sel); |
|
return ok; |
|
} |
|
|
|
bool userstats_add_db(PGconn *conn, USERSTATS *row) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
char *ins; |
|
bool ok = false; |
|
char *params[10 + SIMPLEDATECOUNT]; |
|
int n, par = 0; |
|
|
|
LOGDEBUG("%s(): store", __func__); |
|
|
|
par = 0; |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->workername, NULL, 0); |
|
params[par++] = bigint_to_buf(row->elapsed, NULL, 0); |
|
params[par++] = double_to_buf(row->hashrate, NULL, 0); |
|
params[par++] = double_to_buf(row->hashrate5m, NULL, 0); |
|
params[par++] = double_to_buf(row->hashrate1hr, NULL, 0); |
|
params[par++] = double_to_buf(row->hashrate24hr, NULL, 0); |
|
params[par++] = str_to_buf(row->summarylevel, NULL, 0); |
|
params[par++] = int_to_buf(row->summarycount, NULL, 0); |
|
params[par++] = tv_to_buf(&(row->statsdate), NULL, 0); |
|
SIMPLEDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into userstats " |
|
"(userid,workername,elapsed,hashrate,hashrate5m,hashrate1hr," |
|
"hashrate24hr,summarylevel,summarycount,statsdate" |
|
SIMPLEDATECONTROL ") values (" PQPARAM14 ")"; |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
ok = true; |
|
unparam: |
|
PQclear(res); |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
return ok; |
|
} |
|
|
|
// This is to RAM. The summariser calls the DB I/O functions for userstats |
|
bool userstats_add(char *poolinstance, char *elapsed, char *username, |
|
char *workername, char *hashrate, char *hashrate5m, |
|
char *hashrate1hr, char *hashrate24hr, bool idle, |
|
bool eos, char *by, char *code, char *inet, tv_t *cd, |
|
K_TREE *trf_root) |
|
{ |
|
K_ITEM *us_item, *u_item, *us_match, *us_next, look; |
|
tv_t eosdate; |
|
USERSTATS *row, cmp, *match, *next; |
|
USERS *users; |
|
K_TREE_CTX ctx[1]; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(userstats_free); |
|
us_item = k_unlink_head(userstats_free); |
|
K_WUNLOCK(userstats_free); |
|
|
|
DATA_USERSTATS(row, us_item); |
|
|
|
STRNCPY(row->poolinstance, poolinstance); |
|
TXT_TO_BIGINT("elapsed", elapsed, row->elapsed); |
|
K_RLOCK(users_free); |
|
u_item = find_users(username); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) { |
|
char *txt; |
|
LOGERR("%s(): unknown user '%s'", |
|
__func__, |
|
txt = safe_text(username)); |
|
free(txt); |
|
return false; |
|
} |
|
DATA_USERS(users, u_item); |
|
row->userid = users->userid; |
|
TXT_TO_STR("workername", workername, row->workername); |
|
TXT_TO_DOUBLE("hashrate", hashrate, row->hashrate); |
|
TXT_TO_DOUBLE("hashrate5m", hashrate5m, row->hashrate5m); |
|
TXT_TO_DOUBLE("hashrate1hr", hashrate1hr, row->hashrate1hr); |
|
TXT_TO_DOUBLE("hashrate24hr", hashrate24hr, row->hashrate24hr); |
|
row->idle = idle; |
|
row->summarylevel[0] = SUMMARY_NONE; |
|
row->summarylevel[1] = '\0'; |
|
row->summarycount = 1; |
|
SIMPLEDATEINIT(row, cd, by, code, inet); |
|
SIMPLEDATETRANSFER(trf_root, row); |
|
copy_tv(&(row->statsdate), &(row->createdate)); |
|
row->six = true; |
|
|
|
if (eos) { |
|
// Save it for end processing |
|
eosdate.tv_sec = row->createdate.tv_sec; |
|
eosdate.tv_usec = row->createdate.tv_usec; |
|
} |
|
|
|
// confirm_summaries() doesn't call this |
|
if (reloading) { |
|
memcpy(&cmp, row, sizeof(cmp)); |
|
INIT_USERSTATS(&look); |
|
look.data = (void *)(&cmp); |
|
// Just zero it to ensure the DB record is after it, not equal to it |
|
cmp.statsdate.tv_usec = 0; |
|
/* If there is a matching user+worker DB record summarising this row, |
|
* or a matching user+worker DB record next after this row, discard it */ |
|
us_match = find_after_in_ktree(userstats_workerstatus_root, &look, |
|
cmp_userstats_workerstatus, ctx); |
|
DATA_USERSTATS_NULL(match, us_match); |
|
if (us_match && |
|
match->userid == row->userid && |
|
strcmp(match->workername, row->workername) == 0 && |
|
match->summarylevel[0] != SUMMARY_NONE) { |
|
K_WLOCK(userstats_free); |
|
k_add_head(userstats_free, us_item); |
|
K_WUNLOCK(userstats_free); |
|
|
|
/* If this was an eos record and eos_store has data, |
|
* it means we need to process the eos_store */ |
|
if (eos && userstats_eos_store->count > 0) |
|
goto advancetogo; |
|
|
|
return true; |
|
} |
|
} |
|
|
|
workerstatus_update(NULL, NULL, row); |
|
|
|
/* group at full key: userid,createdate,poolinstance,workername |
|
i.e. ignore instance and group together down at workername */ |
|
us_match = userstats_eos_store->head; |
|
while (us_match && cmp_userstats(us_item, us_match) != 0.0) |
|
us_match = us_match->next; |
|
|
|
if (us_match) { |
|
DATA_USERSTATS(match, us_match); |
|
match->hashrate += row->hashrate; |
|
match->hashrate5m += row->hashrate5m; |
|
match->hashrate1hr += row->hashrate1hr; |
|
match->hashrate24hr += row->hashrate24hr; |
|
// Minimum elapsed of the data set |
|
if (match->elapsed > row->elapsed) |
|
match->elapsed = row->elapsed; |
|
// Unused |
|
K_WLOCK(userstats_free); |
|
k_add_head(userstats_free, us_item); |
|
K_WUNLOCK(userstats_free); |
|
} else { |
|
// New worker |
|
K_WLOCK(userstats_free); |
|
k_add_head(userstats_eos_store, us_item); |
|
K_WUNLOCK(userstats_free); |
|
} |
|
|
|
if (eos) { |
|
advancetogo: |
|
K_WLOCK(userstats_free); |
|
us_next = userstats_eos_store->head; |
|
while (us_next) { |
|
DATA_USERSTATS(next, us_next); |
|
if (tvdiff(&(next->createdate), &eosdate) != 0.0) { |
|
char date_buf[DATE_BUFSIZ]; |
|
LOGERR("userstats != eos '%s' discarded: %s/%"PRId64"/%s", |
|
tv_to_buf(&eosdate, date_buf, DATE_BUFSIZ), |
|
next->poolinstance, |
|
next->userid, |
|
next->workername); |
|
us_next = us_next->next; |
|
} else { |
|
us_match = us_next; |
|
us_next = us_match->next; |
|
k_unlink_item(userstats_eos_store, us_match); |
|
userstats_root = add_to_ktree(userstats_root, us_match, |
|
cmp_userstats); |
|
userstats_statsdate_root = add_to_ktree(userstats_statsdate_root, us_match, |
|
cmp_userstats_statsdate); |
|
if (!startup_complete) { |
|
userstats_workerstatus_root = add_to_ktree(userstats_workerstatus_root, us_match, |
|
cmp_userstats_workerstatus); |
|
} |
|
k_add_head(userstats_store, us_match); |
|
} |
|
} |
|
// Discard them |
|
if (userstats_eos_store->count > 0) |
|
k_list_transfer_to_head(userstats_eos_store, userstats_free); |
|
K_WUNLOCK(userstats_free); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
// This is to RAM. The summariser calls the DB I/O functions for userstats |
|
bool workerstats_add(char *poolinstance, char *elapsed, char *username, |
|
char *workername, char *hashrate, char *hashrate5m, |
|
char *hashrate1hr, char *hashrate24hr, bool idle, |
|
char *by, char *code, char *inet, tv_t *cd, |
|
K_TREE *trf_root) |
|
{ |
|
K_ITEM *us_item, *u_item, *us_match, look; |
|
USERSTATS *row, cmp, *match; |
|
USERS *users; |
|
K_TREE_CTX ctx[1]; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(userstats_free); |
|
us_item = k_unlink_head(userstats_free); |
|
K_WUNLOCK(userstats_free); |
|
|
|
DATA_USERSTATS(row, us_item); |
|
|
|
STRNCPY(row->poolinstance, poolinstance); |
|
TXT_TO_BIGINT("elapsed", elapsed, row->elapsed); |
|
K_RLOCK(users_free); |
|
u_item = find_users(username); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) { |
|
char *txt; |
|
LOGERR("%s(): unknown user '%s'", |
|
__func__, |
|
txt = safe_text(username)); |
|
free(txt); |
|
return false; |
|
} |
|
DATA_USERS(users, u_item); |
|
row->userid = users->userid; |
|
TXT_TO_STR("workername", workername, row->workername); |
|
TXT_TO_DOUBLE("hashrate", hashrate, row->hashrate); |
|
TXT_TO_DOUBLE("hashrate5m", hashrate5m, row->hashrate5m); |
|
TXT_TO_DOUBLE("hashrate1hr", hashrate1hr, row->hashrate1hr); |
|
TXT_TO_DOUBLE("hashrate24hr", hashrate24hr, row->hashrate24hr); |
|
row->idle = idle; |
|
row->summarylevel[0] = SUMMARY_NONE; |
|
row->summarylevel[1] = '\0'; |
|
row->summarycount = 1; |
|
SIMPLEDATEINIT(row, cd, by, code, inet); |
|
SIMPLEDATETRANSFER(trf_root, row); |
|
copy_tv(&(row->statsdate), &(row->createdate)); |
|
row->six = false; |
|
|
|
// confirm_summaries() doesn't call this |
|
if (reloading) { |
|
memcpy(&cmp, row, sizeof(cmp)); |
|
INIT_USERSTATS(&look); |
|
look.data = (void *)(&cmp); |
|
// Just zero it to ensure the DB record is after it, not equal to it |
|
cmp.statsdate.tv_usec = 0; |
|
/* If there is a matching user+worker DB record summarising this row, |
|
* or a matching user+worker DB record next after this row, discard it */ |
|
us_match = find_after_in_ktree(userstats_workerstatus_root, &look, |
|
cmp_userstats_workerstatus, ctx); |
|
DATA_USERSTATS_NULL(match, us_match); |
|
if (us_match && |
|
match->userid == row->userid && |
|
strcmp(match->workername, row->workername) == 0 && |
|
match->summarylevel[0] != SUMMARY_NONE) { |
|
K_WLOCK(userstats_free); |
|
k_add_head(userstats_free, us_item); |
|
K_WUNLOCK(userstats_free); |
|
return true; |
|
} |
|
} |
|
|
|
workerstatus_update(NULL, NULL, row); |
|
|
|
K_WLOCK(userstats_free); |
|
userstats_root = add_to_ktree(userstats_root, us_item, cmp_userstats); |
|
userstats_statsdate_root = add_to_ktree(userstats_statsdate_root, us_item, |
|
cmp_userstats_statsdate); |
|
if (!startup_complete) { |
|
userstats_workerstatus_root = add_to_ktree(userstats_workerstatus_root, |
|
us_item, |
|
cmp_userstats_workerstatus); |
|
} |
|
k_add_head(userstats_store, us_item); |
|
K_WUNLOCK(userstats_free); |
|
|
|
return true; |
|
} |
|
|
|
// TODO: data selection - only require ? |
|
bool userstats_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
struct tm tm; |
|
time_t now_t; |
|
char tzinfo[16], stamp[128]; |
|
USERSTATS *row; |
|
tv_t statsdate; |
|
char *field; |
|
char *sel = NULL; |
|
size_t len, off; |
|
int fields = 10; |
|
long minoff, hroff; |
|
char tzch; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
// Temoprarily ... load last 24hrs worth |
|
now_t = time(NULL); |
|
now_t -= 24 * 60 * 60; |
|
localtime_r(&now_t, &tm); |
|
minoff = tm.tm_gmtoff / 60; |
|
if (minoff < 0) { |
|
tzch = '-'; |
|
minoff *= -1; |
|
} else |
|
tzch = '+'; |
|
hroff = minoff / 60; |
|
if (minoff % 60) { |
|
snprintf(tzinfo, sizeof(tzinfo), |
|
"%c%02ld:%02ld", |
|
tzch, hroff, minoff % 60); |
|
} else { |
|
snprintf(tzinfo, sizeof(tzinfo), |
|
"%c%02ld", |
|
tzch, hroff); |
|
} |
|
snprintf(stamp, sizeof(stamp), |
|
"'%d-%02d-%02d %02d:%02d:%02d%s'", |
|
tm.tm_year + 1900, |
|
tm.tm_mon + 1, |
|
tm.tm_mday, |
|
tm.tm_hour, |
|
tm.tm_min, |
|
tm.tm_sec, |
|
tzinfo); |
|
|
|
APPEND_REALLOC_INIT(sel, off, len); |
|
APPEND_REALLOC(sel, off, len, |
|
"select " |
|
"userid,workername,elapsed,hashrate,hashrate5m," |
|
"hashrate1hr,hashrate24hr,summarylevel,summarycount," |
|
"statsdate" |
|
SIMPLEDATECONTROL |
|
" from userstats where statsdate>"); |
|
APPEND_REALLOC(sel, off, len, stamp); |
|
|
|
res = PQexec(conn, sel, CKPQ_READ); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Select", rescode, conn); |
|
PQclear(res); |
|
ok = false; |
|
goto clean; |
|
} |
|
|
|
n = PQnfields(res); |
|
if (n != (fields + SIMPLEDATECOUNT)) { |
|
LOGERR("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, fields + SIMPLEDATECOUNT, n); |
|
PQclear(res); |
|
ok = false; |
|
goto clean; |
|
} |
|
|
|
n = PQntuples(res); |
|
LOGDEBUG("%s(): tree build count %d", __func__, n); |
|
ok = true; |
|
K_WLOCK(userstats_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(userstats_free); |
|
DATA_USERSTATS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
// Not a DB field |
|
row->poolinstance[0] = '\0'; |
|
|
|
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, "elapsed", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("elapsed", field, row->elapsed); |
|
|
|
PQ_GET_FLD(res, i, "hashrate", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("hashrate", field, row->hashrate); |
|
|
|
PQ_GET_FLD(res, i, "hashrate5m", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("hashrate5m", field, row->hashrate5m); |
|
|
|
PQ_GET_FLD(res, i, "hashrate1hr", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("hashrate1hr", field, row->hashrate1hr); |
|
|
|
PQ_GET_FLD(res, i, "hashrate24hr", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("hashrate24hr", field, row->hashrate24hr); |
|
|
|
PQ_GET_FLD(res, i, "summarylevel", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("summarylevel", field, row->summarylevel); |
|
|
|
PQ_GET_FLD(res, i, "summarycount", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_INT("summarycount", field, row->summarycount); |
|
|
|
PQ_GET_FLD(res, i, "statsdate", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_TV("statsdate", field, row->statsdate); |
|
|
|
// From DB - 1hr means it must have been idle > 10m |
|
if (row->hashrate5m == 0.0 && row->hashrate1hr == 0.0) |
|
row->idle = true; |
|
else |
|
row->idle = false; |
|
|
|
SIMPLEDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
userstats_root = add_to_ktree(userstats_root, item, cmp_userstats); |
|
userstats_statsdate_root = add_to_ktree(userstats_statsdate_root, item, |
|
cmp_userstats_statsdate); |
|
userstats_workerstatus_root = add_to_ktree(userstats_workerstatus_root, item, |
|
cmp_userstats_workerstatus); |
|
k_add_head(userstats_store, item); |
|
|
|
workerstatus_update(NULL, NULL, row); |
|
if (userstats_starttimeband(row, &statsdate)) { |
|
if (tv_newer(&(dbstatus.newest_starttimeband_userstats), &statsdate)) |
|
copy_tv(&(dbstatus.newest_starttimeband_userstats), &statsdate); |
|
} |
|
|
|
tick(); |
|
} |
|
if (!ok) |
|
k_add_head(userstats_free, item); |
|
|
|
K_WUNLOCK(userstats_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d userstats records", __func__, n); |
|
} |
|
clean: |
|
free(sel); |
|
return ok; |
|
} |
|
|
|
bool markersummary_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
MARKERSUMMARY *row; |
|
char *field; |
|
char *sel; |
|
int fields = 18; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
// TODO: limit how far back |
|
sel = "select " |
|
"markerid,userid,workername,diffacc,diffsta,diffdup,diffhi," |
|
"diffrej,shareacc,sharesta,sharedup,sharehi,sharerej," |
|
"sharecount,errorcount,firstshare,lastshare," |
|
"lastdiffacc" |
|
MODIFYDATECONTROL |
|
" from markersummary"; |
|
res = PQexec(conn, sel, CKPQ_READ); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Select", rescode, conn); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQnfields(res); |
|
if (n != (fields + MODIFYDATECOUNT)) { |
|
LOGERR("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, fields + MODIFYDATECOUNT, n); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQntuples(res); |
|
LOGDEBUG("%s(): tree build count %d", __func__, n); |
|
ok = true; |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(markersummary_free); |
|
DATA_MARKERSUMMARY(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
PQ_GET_FLD(res, i, "markerid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("markerid", field, row->markerid); |
|
|
|
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_PTR("workername", field, row->workername); |
|
|
|
PQ_GET_FLD(res, i, "diffacc", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffacc", field, row->diffacc); |
|
|
|
PQ_GET_FLD(res, i, "diffsta", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffsta", field, row->diffsta); |
|
|
|
PQ_GET_FLD(res, i, "diffdup", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffdup", field, row->diffdup); |
|
|
|
PQ_GET_FLD(res, i, "diffhi", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffhi", field, row->diffhi); |
|
|
|
PQ_GET_FLD(res, i, "diffrej", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffrej", field, row->diffrej); |
|
|
|
PQ_GET_FLD(res, i, "shareacc", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("shareacc", field, row->shareacc); |
|
|
|
PQ_GET_FLD(res, i, "sharesta", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("sharesta", field, row->sharesta); |
|
|
|
PQ_GET_FLD(res, i, "sharedup", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("sharedup", field, row->sharedup); |
|
|
|
PQ_GET_FLD(res, i, "sharehi", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("sharehi", field, row->sharehi); |
|
|
|
PQ_GET_FLD(res, i, "sharerej", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("sharerej", field, row->sharerej); |
|
|
|
PQ_GET_FLD(res, i, "sharecount", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("sharecount", field, row->sharecount); |
|
|
|
PQ_GET_FLD(res, i, "errorcount", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("errorcount", field, row->errorcount); |
|
|
|
PQ_GET_FLD(res, i, "firstshare", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_TV("firstshare", field, row->firstshare); |
|
|
|
PQ_GET_FLD(res, i, "lastshare", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_TV("lastshare", field, row->lastshare); |
|
|
|
PQ_GET_FLD(res, i, "lastdiffacc", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("lastdiffacc", field, row->lastdiffacc); |
|
|
|
MODIFYDATEFLDPOINTERS(markersummary_free, res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
markersummary_root = add_to_ktree(markersummary_root, item, cmp_markersummary); |
|
markersummary_userid_root = add_to_ktree(markersummary_userid_root, item, cmp_markersummary_userid); |
|
k_add_head(markersummary_store, item); |
|
|
|
tick(); |
|
} |
|
if (!ok) |
|
k_add_head(markersummary_free, item); |
|
|
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d markersummary records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
/* Add means create a new one and expire the old one if it exists, |
|
* otherwise we only expire the old one if it exists |
|
* Add requires all db fields except markerid, however if markerid |
|
* is non-zero, it will be used instead of getting a new one |
|
* i.e. this effectively means updating a workmarker |
|
* !Add requires markerid or workinfoidend, only |
|
* workinfoidend is used if markerid is zero |
|
* N.B. if you expire a workmarker without creating a new one, |
|
* it's markerid is effectively cancelled, since creating a |
|
* new matching workmarker later, will get a new markerid, |
|
* since we only check for a CURRENT workmarkers |
|
* N.B. also, this returns success if !add and there is no matching |
|
* old workmarkers */ |
|
bool _workmarkers_process(PGconn *conn, bool add, int64_t markerid, |
|
char *poolinstance, int64_t workinfoidend, |
|
int64_t workinfoidstart, char *description, |
|
char *status, char *by, char *code, |
|
char *inet, tv_t *cd, K_TREE *trf_root, |
|
WHERE_FFL_ARGS) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res = NULL; |
|
K_ITEM *wm_item = NULL, *old_wm_item = NULL, *w_item; |
|
WORKMARKERS *row, *oldworkmarkers; |
|
char *upd, *ins; |
|
char *params[6 + HISTORYDATECOUNT]; |
|
bool ok = false, begun = false; |
|
int n, par = 0; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
if (markerid == 0) { |
|
K_RLOCK(workmarkers_free); |
|
old_wm_item = find_workmarkers(workinfoidend, true, '\0'); |
|
K_RUNLOCK(workmarkers_free); |
|
} else { |
|
K_RLOCK(workmarkers_free); |
|
old_wm_item = find_workmarkerid(markerid, true, '\0'); |
|
K_RUNLOCK(workmarkers_free); |
|
} |
|
if (old_wm_item) { |
|
DATA_WORKMARKERS(oldworkmarkers, old_wm_item); |
|
if (!conn) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
res = PQexec(conn, "Begin", CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Begin", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
begun = true; |
|
|
|
upd = "update workmarkers set expirydate=$1 where markerid=$2" |
|
" and expirydate=$3"; |
|
par = 0; |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
params[par++] = bigint_to_buf(oldworkmarkers->markerid, NULL, 0); |
|
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); |
|
PARCHKVAL(par, 3, 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; |
|
} |
|
|
|
if (add) { |
|
if (poolinstance == NULL || description == NULL || |
|
status == NULL) { |
|
LOGEMERG("%s(): NULL field(s) passed:%s%s%s" |
|
WHERE_FFL, __func__, |
|
poolinstance ? "" : " poolinstance", |
|
description ? "" : " description", |
|
status ? "" : " status", |
|
WHERE_FFL_PASS); |
|
goto rollback; |
|
} |
|
w_item = find_workinfo(workinfoidend, NULL); |
|
if (!w_item) |
|
goto rollback; |
|
w_item = find_workinfo(workinfoidstart, NULL); |
|
if (!w_item) |
|
goto rollback; |
|
K_WLOCK(workmarkers_free); |
|
wm_item = k_unlink_head(workmarkers_free); |
|
K_WUNLOCK(workmarkers_free); |
|
DATA_WORKMARKERS(row, wm_item); |
|
bzero(row, sizeof(*row)); |
|
|
|
if (conn == NULL) { |
|
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 unparam; |
|
} |
|
begun = true; |
|
} |
|
|
|
if (old_wm_item) |
|
row->markerid = oldworkmarkers->markerid; |
|
else { |
|
if (markerid != 0) |
|
row->markerid = markerid; |
|
else { |
|
row->markerid = nextid(conn, "markerid", 1, |
|
cd, by, code, inet); |
|
if (row->markerid == 0) |
|
goto rollback; |
|
} |
|
} |
|
|
|
row->poolinstance = strdup(poolinstance); |
|
LIST_MEM_ADD(workmarkers_free, poolinstance); |
|
row->workinfoidend = workinfoidend; |
|
row->workinfoidstart = workinfoidstart; |
|
row->description = strdup(description); |
|
LIST_MEM_ADD(workmarkers_free, description); |
|
STRNCPY(row->status, status); |
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
ins = "insert into workmarkers " |
|
"(markerid,poolinstance,workinfoidend,workinfoidstart," |
|
"description,status" |
|
HISTORYDATECONTROL ") values (" PQPARAM11 ")"; |
|
par = 0; |
|
params[par++] = bigint_to_buf(row->markerid, NULL, 0); |
|
params[par++] = str_to_buf(row->poolinstance, NULL, 0); |
|
params[par++] = bigint_to_buf(row->workinfoidend, NULL, 0); |
|
params[par++] = bigint_to_buf(row->workinfoidstart, NULL, 0); |
|
params[par++] = str_to_buf(row->description, NULL, 0); |
|
params[par++] = str_to_buf(row->status, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
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); |
|
} |
|
unparam: |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
if (conned) |
|
PQfinish(conn); |
|
|
|
K_WLOCK(workmarkers_free); |
|
if (!ok) { |
|
if (wm_item) { |
|
DATA_WORKMARKERS(row, wm_item); |
|
if (row->poolinstance) { |
|
if (row->poolinstance != EMPTY) { |
|
LIST_MEM_SUB(workmarkers_free, |
|
row->poolinstance); |
|
free(row->poolinstance); |
|
} |
|
row->poolinstance = NULL; |
|
} |
|
if (row->description) { |
|
if (row->description != EMPTY) { |
|
LIST_MEM_SUB(workmarkers_free, |
|
row->description); |
|
free(row->description); |
|
} |
|
row->description = NULL; |
|
} |
|
k_add_head(workmarkers_free, wm_item); |
|
} |
|
} |
|
else { |
|
if (old_wm_item) { |
|
workmarkers_root = remove_from_ktree(workmarkers_root, |
|
old_wm_item, |
|
cmp_workmarkers); |
|
copy_tv(&(oldworkmarkers->expirydate), cd); |
|
workmarkers_root = add_to_ktree(workmarkers_root, |
|
old_wm_item, |
|
cmp_workmarkers); |
|
} |
|
if (wm_item) { |
|
workmarkers_root = add_to_ktree(workmarkers_root, |
|
wm_item, |
|
cmp_workmarkers); |
|
k_add_head(workmarkers_store, wm_item); |
|
} |
|
} |
|
K_WUNLOCK(workmarkers_free); |
|
|
|
return ok; |
|
} |
|
|
|
bool workmarkers_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
WORKMARKERS *row; |
|
char *field; |
|
char *sel; |
|
int fields = 6; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
// TODO: limit how far back |
|
sel = "select " |
|
"markerid,poolinstance,workinfoidend,workinfoidstart," |
|
"description,status" |
|
HISTORYDATECONTROL |
|
" from workmarkers"; |
|
res = PQexec(conn, sel, 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; |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(workmarkers_free); |
|
DATA_WORKMARKERS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
PQ_GET_FLD(res, i, "markerid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("markerid", field, row->markerid); |
|
|
|
PQ_GET_FLD(res, i, "poolinstance", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_PTR("poolinstance", field, row->poolinstance); |
|
|
|
PQ_GET_FLD(res, i, "workinfoidend", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("workinfoidend", field, row->workinfoidend); |
|
|
|
PQ_GET_FLD(res, i, "workinfoidstart", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("workinfoidstart", field, row->workinfoidstart); |
|
|
|
PQ_GET_FLD(res, i, "description", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_PTR("description", field, row->description); |
|
|
|
PQ_GET_FLD(res, i, "status", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("status", field, row->status); |
|
|
|
HISTORYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
workmarkers_root = add_to_ktree(workmarkers_root, item, cmp_workmarkers); |
|
workmarkers_workinfoid_root = add_to_ktree(workmarkers_workinfoid_root, |
|
item, cmp_workmarkers_workinfoid); |
|
k_add_head(workmarkers_store, item); |
|
|
|
tick(); |
|
} |
|
if (!ok) |
|
k_add_head(workmarkers_free, item); |
|
|
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d workmarkers records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
/* Add means create a new one and expire the old one if it exists, |
|
* otherwise we only expire the old one if it exists |
|
* Add requires all db fields |
|
* !Add only requires the (poolinstance and) workinfoid db fields */ |
|
bool _marks_process(PGconn *conn, bool add, char *poolinstance, |
|
int64_t workinfoid, char *description, char *extra, |
|
char *marktype, char *status, char *by, char *code, |
|
char *inet, tv_t *cd, K_TREE *trf_root, WHERE_FFL_ARGS) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res = NULL; |
|
K_ITEM *m_item = NULL, *old_m_item = NULL, *w_item; |
|
MARKS *row, *oldmarks; |
|
char *upd, *ins; |
|
char *params[6 + HISTORYDATECOUNT]; |
|
bool ok = false, begun = false; |
|
int n, par = 0; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_RLOCK(marks_free); |
|
old_m_item = find_marks(workinfoid); |
|
K_RUNLOCK(marks_free); |
|
if (old_m_item) { |
|
DATA_MARKS(oldmarks, old_m_item); |
|
if (!conn) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
res = PQexec(conn, "Begin", CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Begin", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
begun = true; |
|
|
|
upd = "update marks set expirydate=$1 where workinfoid=$2" |
|
" and expirydate=$3"; |
|
par = 0; |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
params[par++] = bigint_to_buf(workinfoid, NULL, 0); |
|
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); |
|
PARCHKVAL(par, 3, 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; |
|
} |
|
|
|
if (add) { |
|
if (poolinstance == NULL || description == NULL || |
|
extra == NULL || marktype == NULL || status == NULL) { |
|
LOGEMERG("%s(): NULL field(s) passed:%s%s%s%s%s" |
|
WHERE_FFL, __func__, |
|
poolinstance ? "" : " poolinstance", |
|
description ? "" : " description", |
|
extra ? "" : " extra", |
|
marktype ? "" : " marktype", |
|
status ? "" : " status", |
|
WHERE_FFL_PASS); |
|
goto rollback; |
|
} |
|
w_item = find_workinfo(workinfoid, NULL); |
|
if (!w_item) |
|
goto rollback; |
|
K_WLOCK(marks_free); |
|
m_item = k_unlink_head(marks_free); |
|
K_WUNLOCK(marks_free); |
|
DATA_MARKS(row, m_item); |
|
bzero(row, sizeof(*row)); |
|
row->poolinstance = strdup(poolinstance); |
|
LIST_MEM_ADD(marks_free, poolinstance); |
|
row->workinfoid = workinfoid; |
|
row->description = strdup(description); |
|
LIST_MEM_ADD(marks_free, description); |
|
row->extra = strdup(extra); |
|
LIST_MEM_ADD(marks_free, extra); |
|
STRNCPY(row->marktype, marktype); |
|
STRNCPY(row->status, status); |
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
ins = "insert into marks " |
|
"(poolinstance,workinfoid,description,extra,marktype," |
|
"status" |
|
HISTORYDATECONTROL ") values (" PQPARAM11 ")"; |
|
par = 0; |
|
params[par++] = str_to_buf(row->poolinstance, NULL, 0); |
|
params[par++] = bigint_to_buf(workinfoid, NULL, 0); |
|
params[par++] = str_to_buf(row->description, NULL, 0); |
|
params[par++] = str_to_buf(row->extra, NULL, 0); |
|
params[par++] = str_to_buf(row->marktype, NULL, 0); |
|
params[par++] = str_to_buf(row->status, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
if (conn == NULL) { |
|
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 unparam; |
|
} |
|
begun = true; |
|
} |
|
|
|
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); |
|
} |
|
unparam: |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
if (conned) |
|
PQfinish(conn); |
|
|
|
K_WLOCK(marks_free); |
|
if (!ok) { |
|
if (m_item) { |
|
DATA_MARKS(row, m_item); |
|
if (row->poolinstance) { |
|
if (row->poolinstance != EMPTY) { |
|
LIST_MEM_SUB(marks_free, row->poolinstance); |
|
free(row->poolinstance); |
|
} |
|
row->poolinstance = NULL; |
|
} |
|
if (row->description) { |
|
if (row->description != EMPTY) { |
|
LIST_MEM_SUB(marks_free, row->description); |
|
free(row->description); |
|
} |
|
row->description = NULL; |
|
} |
|
if (row->extra) { |
|
if (row->extra != EMPTY) { |
|
LIST_MEM_SUB(marks_free, row->extra); |
|
free(row->extra); |
|
} |
|
row->extra = NULL; |
|
} |
|
k_add_head(marks_free, m_item); |
|
} |
|
} |
|
else { |
|
if (old_m_item) { |
|
marks_root = remove_from_ktree(marks_root, old_m_item, cmp_marks); |
|
copy_tv(&(oldmarks->expirydate), cd); |
|
marks_root = add_to_ktree(marks_root, old_m_item, cmp_marks); |
|
} |
|
if (m_item) { |
|
marks_root = add_to_ktree(marks_root, m_item, cmp_marks); |
|
k_add_head(marks_store, m_item); |
|
} |
|
} |
|
K_WUNLOCK(marks_free); |
|
|
|
return ok; |
|
} |
|
|
|
bool marks_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
MARKS *row; |
|
char *field; |
|
char *sel; |
|
int fields = 6; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
// TODO: limit how far back |
|
sel = "select " |
|
"poolinstance,workinfoid,description,extra,marktype,status" |
|
HISTORYDATECONTROL |
|
" from marks"; |
|
res = PQexec(conn, sel, 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; |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(marks_free); |
|
DATA_MARKS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
PQ_GET_FLD(res, i, "poolinstance", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_PTR("poolinstance", field, row->poolinstance); |
|
|
|
PQ_GET_FLD(res, i, "workinfoid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("workinfoid", field, row->workinfoid); |
|
|
|
PQ_GET_FLD(res, i, "description", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_PTR("description", field, row->description); |
|
|
|
PQ_GET_FLD(res, i, "extra", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_PTR("extra", field, row->extra); |
|
|
|
PQ_GET_FLD(res, i, "marktype", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("marktype", field, row->marktype); |
|
|
|
PQ_GET_FLD(res, i, "status", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("status", field, row->status); |
|
|
|
HISTORYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
marks_root = add_to_ktree(marks_root, item, cmp_marks); |
|
k_add_head(marks_store, item); |
|
|
|
tick(); |
|
} |
|
if (!ok) |
|
k_add_head(marks_free, item); |
|
|
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d marks records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
bool check_db_version(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
char *field; |
|
char *sel; |
|
char *pgv; |
|
int fields = 3; |
|
bool ok; |
|
int n; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
sel = "select version() as pgv,* from version;"; |
|
res = PQexec(conn, sel, CKPQ_READ); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGEMERG("Select", rescode, conn); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQnfields(res); |
|
if (n != fields) { |
|
LOGEMERG("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, fields, n); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQntuples(res); |
|
if (n != 1) { |
|
LOGEMERG("%s(): Invalid record count - should be %d, but is %d", |
|
__func__, 1, n); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
ok = true; |
|
PQ_GET_FLD(res, 0, "vlock", field, ok); |
|
if (!ok) { |
|
LOGEMERG("%s(): Missing field vlock", __func__); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
if (strcmp(field, DB_VLOCK)) { |
|
LOGEMERG("%s(): incorrect vlock '%s' - should be '%s'", |
|
__func__, field, DB_VLOCK); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
ok = true; |
|
PQ_GET_FLD(res, 0, "version", field, ok); |
|
if (!ok) { |
|
LOGEMERG("%s(): Missing field version", __func__); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
if (strcmp(field, DB_VERSION)) { |
|
LOGEMERG("%s(): incorrect version '%s' - should be '%s'", |
|
__func__, field, DB_VERSION); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
PQ_GET_FLD(res, 0, "pgv", field, ok); |
|
if (ok) |
|
pgv = strdup(field); |
|
else |
|
pgv = strdup("Failed to get postgresql version information"); |
|
|
|
PQclear(res); |
|
|
|
LOGWARNING("%s(): DB version (%s) correct (CKDB V%s)", |
|
__func__, DB_VERSION, CKDB_VERSION); |
|
LOGWARNING("%s(): %s", __func__, pgv); |
|
|
|
free(pgv); |
|
|
|
return true; |
|
} |
|
|
|
char *cmd_newid(PGconn *conn, char *cmd, char *id, tv_t *now, char *by, |
|
char *code, char *inet, __maybe_unused tv_t *cd, |
|
K_TREE *trf_root) |
|
{ |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
K_ITEM *i_idname, *i_idvalue, *look; |
|
IDCONTROL *row; |
|
char *params[2 + MODIFYDATECOUNT]; |
|
int n, par = 0; |
|
bool ok = false; |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
char *ins; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_idname = require_name(trf_root, "idname", 3, (char *)idpatt, reply, siz); |
|
if (!i_idname) |
|
return strdup(reply); |
|
|
|
i_idvalue = require_name(trf_root, "idvalue", 1, (char *)intpatt, reply, siz); |
|
if (!i_idvalue) |
|
return strdup(reply); |
|
|
|
K_WLOCK(idcontrol_free); |
|
look = k_unlink_head(idcontrol_free); |
|
K_WUNLOCK(idcontrol_free); |
|
|
|
DATA_IDCONTROL(row, look); |
|
|
|
STRNCPY(row->idname, transfer_data(i_idname)); |
|
TXT_TO_BIGINT("idvalue", transfer_data(i_idvalue), row->lastid); |
|
MODIFYDATEINIT(row, now, 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 ")"; |
|
|
|
if (!conn) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto foil; |
|
} |
|
|
|
ok = true; |
|
foil: |
|
PQclear(res); |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
K_WLOCK(idcontrol_free); |
|
k_add_head(idcontrol_free, look); |
|
K_WUNLOCK(idcontrol_free); |
|
|
|
if (!ok) { |
|
LOGERR("%s() %s.failed.DBE", __func__, id); |
|
return strdup("failed.DBE"); |
|
} |
|
LOGDEBUG("%s.ok.added %s %"PRId64, id, transfer_data(i_idname), row->lastid); |
|
snprintf(reply, siz, "ok.added %s %"PRId64, |
|
transfer_data(i_idname), row->lastid); |
|
return strdup(reply); |
|
}
|
|
|