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.
2073 lines
55 KiB
2073 lines
55 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" |
|
|
|
// Clear text printable version of txt up to first '\0' |
|
char *safe_text(char *txt) |
|
{ |
|
unsigned char *ptr = (unsigned char *)txt; |
|
size_t len; |
|
char *ret, *buf; |
|
|
|
if (!txt) { |
|
buf = strdup("(Null)"); |
|
if (!buf) |
|
quithere(1, "malloc OOM"); |
|
return buf; |
|
} |
|
|
|
// Allocate the maximum needed |
|
len = (strlen(txt)+1)*4+1; |
|
ret = buf = malloc(len); |
|
if (!buf) |
|
quithere(1, "malloc (%d) OOM", (int)len); |
|
while (*ptr) { |
|
if (*ptr >= ' ' && *ptr <= '~') |
|
*(buf++) = *(ptr++); |
|
else { |
|
snprintf(buf, 5, "0x%02x", *(ptr++)); |
|
buf += 4; |
|
} |
|
} |
|
strcpy(buf, "0x00"); |
|
return ret; |
|
} |
|
|
|
#define TRIM_IGNORE(ch) ((ch) == '_' || (ch) == '.' || (ch) == '-' || isspace(ch)) |
|
|
|
void username_trim(USERS *users) |
|
{ |
|
char *front, *trail; |
|
|
|
front = users->username; |
|
while (*front && TRIM_IGNORE(*front)) |
|
front++; |
|
|
|
STRNCPY(users->usertrim, front); |
|
|
|
front = users->usertrim; |
|
trail = front + strlen(front) - 1; |
|
while (trail >= front) { |
|
if (TRIM_IGNORE(*trail)) |
|
*(trail--) = '\0'; |
|
else |
|
break; |
|
} |
|
|
|
while (trail >= front) { |
|
*trail = tolower(*trail); |
|
trail--; |
|
} |
|
} |
|
|
|
void _txt_to_data(enum data_type typ, char *nam, char *fld, void *data, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
char *tmp; |
|
|
|
switch (typ) { |
|
case TYPE_STR: |
|
// A database field being bigger than local storage is a fatal error |
|
if (siz < (strlen(fld)+1)) { |
|
quithere(1, "Field %s structure size %d is smaller than db %d" WHERE_FFL, |
|
nam, (int)siz, (int)strlen(fld)+1, WHERE_FFL_PASS); |
|
} |
|
strcpy((char *)data, fld); |
|
break; |
|
case TYPE_BIGINT: |
|
if (siz != sizeof(int64_t)) { |
|
quithere(1, "Field %s bigint incorrect structure size %d - should be %d" |
|
WHERE_FFL, |
|
nam, (int)siz, (int)sizeof(int64_t), WHERE_FFL_PASS); |
|
} |
|
*((long long *)data) = atoll(fld); |
|
break; |
|
case TYPE_INT: |
|
if (siz != sizeof(int32_t)) { |
|
quithere(1, "Field %s int incorrect structure size %d - should be %d" |
|
WHERE_FFL, |
|
nam, (int)siz, (int)sizeof(int32_t), WHERE_FFL_PASS); |
|
} |
|
*((int32_t *)data) = atoi(fld); |
|
break; |
|
case TYPE_TV: |
|
if (siz != sizeof(tv_t)) { |
|
quithere(1, "Field %s tv_t incorrect structure size %d - should be %d" |
|
WHERE_FFL, |
|
nam, (int)siz, (int)sizeof(tv_t), WHERE_FFL_PASS); |
|
} |
|
unsigned int yyyy, mm, dd, HH, MM, SS, uS = 0, tz, tzm = 0; |
|
char pm[2]; |
|
struct tm tm; |
|
time_t tim; |
|
int n; |
|
// A timezone looks like: +10 or +09:30 or -05 etc |
|
n = sscanf(fld, "%u-%u-%u %u:%u:%u%1[+-]%u:%u", |
|
&yyyy, &mm, &dd, &HH, &MM, &SS, pm, &tz, &tzm); |
|
if (n < 8) { |
|
// allow uS |
|
n = sscanf(fld, "%u-%u-%u %u:%u:%u.%u%1[+-]%u:%u", |
|
&yyyy, &mm, &dd, &HH, &MM, &SS, &uS, pm, &tz, &tzm); |
|
if (n < 9) { |
|
quithere(1, "Field %s tv_t unhandled date '%s' (%d)" WHERE_FFL, |
|
nam, fld, n, WHERE_FFL_PASS); |
|
} |
|
|
|
if (n < 10) |
|
tzm = 0; |
|
} else if (n < 9) |
|
tzm = 0; |
|
tm.tm_sec = (int)SS; |
|
tm.tm_min = (int)MM; |
|
tm.tm_hour = (int)HH; |
|
tm.tm_mday = (int)dd; |
|
tm.tm_mon = (int)mm - 1; |
|
tm.tm_year = (int)yyyy - 1900; |
|
tm.tm_isdst = -1; |
|
tim = timegm(&tm); |
|
if (tim > COMPARE_EXPIRY) { |
|
((tv_t *)data)->tv_sec = default_expiry.tv_sec; |
|
((tv_t *)data)->tv_usec = default_expiry.tv_usec; |
|
} else { |
|
tz = tz * 60 + tzm; |
|
// time was converted ignoring tz - so correct it |
|
if (pm[0] == '-') |
|
tim += 60 * tz; |
|
else |
|
tim -= 60 * tz; |
|
((tv_t *)data)->tv_sec = tim; |
|
((tv_t *)data)->tv_usec = uS; |
|
} |
|
break; |
|
case TYPE_CTV: |
|
if (siz != sizeof(tv_t)) { |
|
quithere(1, "Field %s tv_t incorrect structure size %d - should be %d" |
|
WHERE_FFL, |
|
nam, (int)siz, (int)sizeof(tv_t), WHERE_FFL_PASS); |
|
} |
|
long sec, nsec; |
|
int c; |
|
// Caller test for tv_sec=0 for failure |
|
((tv_t *)data)->tv_sec = 0L; |
|
((tv_t *)data)->tv_usec = 0L; |
|
c = sscanf(fld, "%ld,%ld", &sec, &nsec); |
|
if (c > 0) { |
|
((tv_t *)data)->tv_sec = (time_t)sec; |
|
if (c > 1) |
|
((tv_t *)data)->tv_usec = (nsec + 500) / 1000; |
|
if (((tv_t *)data)->tv_sec >= COMPARE_EXPIRY) { |
|
((tv_t *)data)->tv_sec = default_expiry.tv_sec; |
|
((tv_t *)data)->tv_usec = default_expiry.tv_usec; |
|
} |
|
} |
|
break; |
|
case TYPE_BLOB: |
|
tmp = strdup(fld); |
|
if (!tmp) { |
|
quithere(1, "Field %s (%d) OOM" WHERE_FFL, |
|
nam, (int)strlen(fld), WHERE_FFL_PASS); |
|
} |
|
*((char **)data) = tmp; |
|
break; |
|
case TYPE_DOUBLE: |
|
if (siz != sizeof(double)) { |
|
quithere(1, "Field %s int incorrect structure size %d - should be %d" |
|
WHERE_FFL, |
|
nam, (int)siz, (int)sizeof(double), WHERE_FFL_PASS); |
|
} |
|
*((double *)data) = atof(fld); |
|
break; |
|
default: |
|
quithere(1, "Unknown field %s (%d) to convert" WHERE_FFL, |
|
nam, (int)typ, WHERE_FFL_PASS); |
|
break; |
|
} |
|
} |
|
|
|
// N.B. STRNCPY* macros truncate, whereas this aborts ckdb if src > trg |
|
void _txt_to_str(char *nam, char *fld, char data[], size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
_txt_to_data(TYPE_STR, nam, fld, (void *)data, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
void _txt_to_bigint(char *nam, char *fld, int64_t *data, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
_txt_to_data(TYPE_BIGINT, nam, fld, (void *)data, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
void _txt_to_int(char *nam, char *fld, int32_t *data, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
_txt_to_data(TYPE_INT, nam, fld, (void *)data, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
void _txt_to_tv(char *nam, char *fld, tv_t *data, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
_txt_to_data(TYPE_TV, nam, fld, (void *)data, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
// Convert msg S,nS to tv_t |
|
void _txt_to_ctv(char *nam, char *fld, tv_t *data, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
_txt_to_data(TYPE_CTV, nam, fld, (void *)data, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
void _txt_to_blob(char *nam, char *fld, char **data, WHERE_FFL_ARGS) |
|
{ |
|
_txt_to_data(TYPE_BLOB, nam, fld, (void *)data, 0, WHERE_FFL_PASS); |
|
} |
|
|
|
void _txt_to_double(char *nam, char *fld, double *data, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
_txt_to_data(TYPE_DOUBLE, nam, fld, (void *)data, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
char *_data_to_buf(enum data_type typ, void *data, char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
struct tm tm; |
|
|
|
if (!buf) { |
|
switch (typ) { |
|
case TYPE_STR: |
|
case TYPE_BLOB: |
|
siz = strlen((char *)data) + 1; |
|
break; |
|
case TYPE_BIGINT: |
|
siz = BIGINT_BUFSIZ; |
|
break; |
|
case TYPE_INT: |
|
siz = INT_BUFSIZ; |
|
break; |
|
case TYPE_TV: |
|
case TYPE_TVS: |
|
siz = DATE_BUFSIZ; |
|
break; |
|
case TYPE_CTV: |
|
siz = CDATE_BUFSIZ; |
|
break; |
|
case TYPE_DOUBLE: |
|
siz = DOUBLE_BUFSIZ; |
|
break; |
|
default: |
|
quithere(1, "Unknown field (%d) to convert" WHERE_FFL, |
|
(int)typ, WHERE_FFL_PASS); |
|
break; |
|
} |
|
|
|
buf = malloc(siz); |
|
if (!buf) |
|
quithere(1, "(%d) OOM" WHERE_FFL, (int)siz, WHERE_FFL_PASS); |
|
} |
|
|
|
switch (typ) { |
|
case TYPE_STR: |
|
case TYPE_BLOB: |
|
snprintf(buf, siz, "%s", (char *)data); |
|
break; |
|
case TYPE_BIGINT: |
|
snprintf(buf, siz, "%"PRId64, *((uint64_t *)data)); |
|
break; |
|
case TYPE_INT: |
|
snprintf(buf, siz, "%"PRId32, *((uint32_t *)data)); |
|
break; |
|
case TYPE_TV: |
|
gmtime_r(&(((tv_t *)data)->tv_sec), &tm); |
|
snprintf(buf, siz, "%d-%02d-%02d %02d:%02d:%02d.%06ld+00", |
|
tm.tm_year + 1900, |
|
tm.tm_mon + 1, |
|
tm.tm_mday, |
|
tm.tm_hour, |
|
tm.tm_min, |
|
tm.tm_sec, |
|
(((tv_t *)data)->tv_usec)); |
|
break; |
|
case TYPE_CTV: |
|
snprintf(buf, siz, "%ld,%ld", |
|
(((tv_t *)data)->tv_sec), |
|
(((tv_t *)data)->tv_usec)); |
|
break; |
|
case TYPE_TVS: |
|
snprintf(buf, siz, "%ld", (((tv_t *)data)->tv_sec)); |
|
break; |
|
case TYPE_DOUBLE: |
|
snprintf(buf, siz, "%f", *((double *)data)); |
|
break; |
|
} |
|
|
|
return buf; |
|
} |
|
|
|
char *_str_to_buf(char data[], char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
return _data_to_buf(TYPE_STR, (void *)data, buf, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
char *_bigint_to_buf(int64_t data, char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
return _data_to_buf(TYPE_BIGINT, (void *)(&data), buf, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
char *_int_to_buf(int32_t data, char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
return _data_to_buf(TYPE_INT, (void *)(&data), buf, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
char *_tv_to_buf(tv_t *data, char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
return _data_to_buf(TYPE_TV, (void *)data, buf, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
// Convert tv to S,uS |
|
char *_ctv_to_buf(tv_t *data, char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
return _data_to_buf(TYPE_CTV, (void *)data, buf, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
// Convert tv to seconds (ignore uS) |
|
char *_tvs_to_buf(tv_t *data, char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
return _data_to_buf(TYPE_TVS, (void *)data, buf, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
/* unused yet |
|
char *_blob_to_buf(char *data, char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
return _data_to_buf(TYPE_BLOB, (void *)data, buf, siz, WHERE_FFL_PASS); |
|
} |
|
*/ |
|
|
|
char *_double_to_buf(double data, char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
return _data_to_buf(TYPE_DOUBLE, (void *)(&data), buf, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
// For mutiple variable function calls that need the data |
|
char *_transfer_data(K_ITEM *item, WHERE_FFL_ARGS) |
|
{ |
|
TRANSFER *transfer; |
|
char *mvalue; |
|
|
|
if (!item) { |
|
quitfrom(1, file, func, line, |
|
"Attempt to use NULL transfer item"); |
|
} |
|
if (item->name != transfer_free->name) { |
|
quitfrom(1, file, func, line, |
|
"Attempt to cast item '%s' data as '%s'", |
|
item->name, |
|
transfer_free->name); |
|
} |
|
transfer = (TRANSFER *)(item->data); |
|
if (!transfer) { |
|
quitfrom(1, file, func, line, |
|
"Transfer item has NULL data"); |
|
} |
|
mvalue = transfer->mvalue; |
|
if (!mvalue) { |
|
/* N.B. name and svalue strings will have \0 termination |
|
* even if they are both corrupt, since mvalue is NULL */ |
|
quitfrom(1, file, func, line, |
|
"Transfer '%s' '%s' has NULL mvalue", |
|
transfer->name, transfer->svalue); |
|
} |
|
return mvalue; |
|
} |
|
|
|
void dsp_transfer(K_ITEM *item, FILE *stream) |
|
{ |
|
TRANSFER *t; |
|
|
|
if (!item) |
|
fprintf(stream, "%s() called with (null) item\n", __func__); |
|
else { |
|
DATA_TRANSFER(t, item); |
|
fprintf(stream, " name='%s' mvalue='%s' malloc=%c\n", |
|
t->name, t->mvalue, |
|
(t->svalue == t->mvalue) ? 'N' : 'Y'); |
|
} |
|
} |
|
|
|
// order by name asc |
|
cmp_t cmp_transfer(K_ITEM *a, K_ITEM *b) |
|
{ |
|
TRANSFER *ta, *tb; |
|
DATA_TRANSFER(ta, a); |
|
DATA_TRANSFER(tb, b); |
|
return CMP_STR(ta->name, tb->name); |
|
} |
|
|
|
K_ITEM *find_transfer(K_TREE *trf_root, char *name) |
|
{ |
|
TRANSFER transfer; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look; |
|
|
|
STRNCPY(transfer.name, name); |
|
INIT_TRANSFER(&look); |
|
look.data = (void *)(&transfer); |
|
return find_in_ktree(trf_root, &look, cmp_transfer, ctx); |
|
} |
|
|
|
K_ITEM *_optional_name(K_TREE *trf_root, char *name, int len, char *patt, |
|
char *reply, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
TRANSFER *trf; |
|
K_ITEM *item; |
|
char *mvalue; |
|
regex_t re; |
|
size_t dlen; |
|
int ret; |
|
|
|
reply[0] = '\0'; |
|
|
|
item = find_transfer(trf_root, name); |
|
if (!item) |
|
return NULL; |
|
|
|
DATA_TRANSFER(trf, item); |
|
mvalue = trf->mvalue; |
|
if (mvalue) |
|
dlen = strlen(mvalue); |
|
else |
|
dlen = 0; |
|
if (!mvalue || (int)dlen < len) { |
|
if (!mvalue) { |
|
LOGERR("%s(): field '%s' NULL (%d) from %s():%d", |
|
__func__, name, (int)dlen, len, func, line); |
|
} else |
|
snprintf(reply, siz, "failed.short %s", name); |
|
return NULL; |
|
} |
|
|
|
if (patt) { |
|
if (regcomp(&re, patt, REG_NOSUB) != 0) { |
|
snprintf(reply, siz, "failed.REG %s", name); |
|
return NULL; |
|
} |
|
|
|
ret = regexec(&re, mvalue, (size_t)0, NULL, 0); |
|
regfree(&re); |
|
|
|
if (ret != 0) { |
|
snprintf(reply, siz, "failed.invalid %s", name); |
|
return NULL; |
|
} |
|
} |
|
|
|
return item; |
|
} |
|
|
|
K_ITEM *_require_name(K_TREE *trf_root, char *name, int len, char *patt, |
|
char *reply, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
TRANSFER *trf; |
|
K_ITEM *item; |
|
char *mvalue; |
|
regex_t re; |
|
size_t dlen; |
|
int ret; |
|
|
|
reply[0] = '\0'; |
|
|
|
item = find_transfer(trf_root, name); |
|
if (!item) { |
|
LOGERR("%s(): failed, field '%s' missing from %s():%d", |
|
__func__, name, func, line); |
|
snprintf(reply, siz, "failed.missing %s", name); |
|
return NULL; |
|
} |
|
|
|
DATA_TRANSFER(trf, item); |
|
mvalue = trf->mvalue; |
|
if (mvalue) |
|
dlen = strlen(mvalue); |
|
else |
|
dlen = 0; |
|
if (!mvalue || (int)dlen < len) { |
|
LOGERR("%s(): failed, field '%s' short (%s%d<%d) from %s():%d", |
|
__func__, name, mvalue ? EMPTY : "null", |
|
(int)dlen, len, func, line); |
|
snprintf(reply, siz, "failed.short %s", name); |
|
return NULL; |
|
} |
|
|
|
if (patt) { |
|
if (regcomp(&re, patt, REG_NOSUB) != 0) { |
|
LOGERR("%s(): failed, field '%s' failed to" |
|
" compile patt from %s():%d", |
|
__func__, name, func, line); |
|
snprintf(reply, siz, "failed.REG %s", name); |
|
return NULL; |
|
} |
|
|
|
ret = regexec(&re, mvalue, (size_t)0, NULL, 0); |
|
regfree(&re); |
|
|
|
if (ret != 0) { |
|
LOGERR("%s(): failed, field '%s' invalid from %s():%d", |
|
__func__, name, func, line); |
|
snprintf(reply, siz, "failed.invalid %s", name); |
|
return NULL; |
|
} |
|
} |
|
|
|
return item; |
|
} |
|
|
|
// order by userid asc,workername asc |
|
cmp_t cmp_workerstatus(K_ITEM *a, K_ITEM *b) |
|
{ |
|
WORKERSTATUS *wa, *wb; |
|
DATA_WORKERSTATUS(wa, a); |
|
DATA_WORKERSTATUS(wb, b); |
|
cmp_t c = CMP_BIGINT(wa->userid, wb->userid); |
|
if (c == 0) |
|
c = CMP_STR(wa->workername, wb->workername); |
|
return c; |
|
} |
|
|
|
/* TODO: replace a lot of the code for all data types that codes finds, |
|
* each with specific functions for finding, to centralise the finds, |
|
* with passed ctx's */ |
|
K_ITEM *get_workerstatus(int64_t userid, char *workername) |
|
{ |
|
WORKERSTATUS workerstatus; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look, *find; |
|
|
|
workerstatus.userid = userid; |
|
STRNCPY(workerstatus.workername, workername); |
|
|
|
INIT_WORKERSTATUS(&look); |
|
look.data = (void *)(&workerstatus); |
|
K_RLOCK(workerstatus_free); |
|
find = find_in_ktree(workerstatus_root, &look, cmp_workerstatus, ctx); |
|
K_RUNLOCK(workerstatus_free); |
|
return find; |
|
} |
|
|
|
/* Worker loading/creation calls this with create = true |
|
* All others with create = false since the workerstatus should exist |
|
* Failure is a code bug and a reported error, but handled anyway |
|
* This has 2 sets of file/func/line to allow 2 levels of traceback |
|
*/ |
|
K_ITEM *_find_create_workerstatus(int64_t userid, char *workername, |
|
bool create, const char *file2, |
|
const char *func2, const int line2, |
|
WHERE_FFL_ARGS) |
|
{ |
|
WORKERSTATUS *row; |
|
K_ITEM *item; |
|
|
|
item = get_workerstatus(userid, workername); |
|
if (!item) { |
|
if (!create) { |
|
LOGEMERG("%s(): Missing workerstatus %"PRId64"/%s" |
|
WHERE_FFL WHERE_FFL, |
|
__func__, userid, workername, |
|
file2, func2, line2, WHERE_FFL_PASS); |
|
return NULL; |
|
} |
|
|
|
K_WLOCK(workerstatus_free); |
|
item = k_unlink_head(workerstatus_free); |
|
|
|
DATA_WORKERSTATUS(row, item); |
|
|
|
bzero(row, sizeof(*row)); |
|
row->userid = userid; |
|
STRNCPY(row->workername, workername); |
|
|
|
workerstatus_root = add_to_ktree(workerstatus_root, item, cmp_workerstatus); |
|
k_add_head(workerstatus_store, item); |
|
K_WUNLOCK(workerstatus_free); |
|
} |
|
return item; |
|
} |
|
|
|
/* All data is loaded, now update workerstatus fields |
|
TODO: combine set_block_share_counters() with this? */ |
|
void workerstatus_ready() |
|
{ |
|
K_TREE_CTX ws_ctx[1], us_ctx[1], ss_ctx[1]; |
|
K_ITEM *ws_item, us_look, ss_look, *us_item, *ss_item; |
|
USERSTATS lookuserstats, *userstats; |
|
SHARESUMMARY looksharesummary, *sharesummary; |
|
WORKERSTATUS *workerstatus; |
|
|
|
INIT_USERSTATS(&us_look); |
|
INIT_SHARESUMMARY(&ss_look); |
|
ws_item = first_in_ktree(workerstatus_root, ws_ctx); |
|
while (ws_item) { |
|
DATA_WORKERSTATUS(workerstatus, ws_item); |
|
lookuserstats.userid = workerstatus->userid; |
|
STRNCPY(lookuserstats.workername, workerstatus->workername); |
|
lookuserstats.statsdate.tv_sec = date_eot.tv_sec; |
|
lookuserstats.statsdate.tv_usec = date_eot.tv_usec; |
|
us_look.data = (void *)(&lookuserstats); |
|
us_item = find_before_in_ktree(userstats_workerstatus_root, &us_look, |
|
cmp_userstats_workerstatus, us_ctx); |
|
if (us_item) { |
|
DATA_USERSTATS(userstats, us_item); |
|
if (userstats->idle) { |
|
if (tv_newer(&(workerstatus->last_idle), |
|
&(userstats->statsdate))) { |
|
copy_tv(&(workerstatus->last_idle), |
|
&(userstats->statsdate)); |
|
} |
|
} else { |
|
if (tv_newer(&(workerstatus->last_stats), |
|
&(userstats->statsdate))) { |
|
copy_tv(&(workerstatus->last_stats), |
|
&(userstats->statsdate)); |
|
} |
|
} |
|
} |
|
|
|
looksharesummary.userid = workerstatus->userid; |
|
STRNCPY(looksharesummary.workername, workerstatus->workername); |
|
looksharesummary.workinfoid = MAXID; |
|
ss_look.data = (void *)(&looksharesummary); |
|
K_RLOCK(sharesummary_free); |
|
ss_item = find_before_in_ktree(sharesummary_root, &ss_look, |
|
cmp_sharesummary, ss_ctx); |
|
K_RUNLOCK(sharesummary_free); |
|
if (ss_item) { |
|
DATA_SHARESUMMARY(sharesummary, ss_item); |
|
if (tv_newer(&(workerstatus->last_share), |
|
&(sharesummary->lastshare))) { |
|
copy_tv(&(workerstatus->last_share), |
|
&(sharesummary->lastshare)); |
|
workerstatus->last_diff = |
|
sharesummary->lastdiffacc; |
|
} |
|
} |
|
|
|
ws_item = next_in_ktree(ws_ctx); |
|
} |
|
} |
|
|
|
void _workerstatus_update(AUTHS *auths, SHARES *shares, |
|
USERSTATS *userstats, WHERE_FFL_ARGS) |
|
{ |
|
WORKERSTATUS *row; |
|
K_ITEM *item; |
|
|
|
if (auths) { |
|
item = find_workerstatus(auths->userid, auths->workername, |
|
file, func, line); |
|
if (item) { |
|
DATA_WORKERSTATUS(row, item); |
|
if (tv_newer(&(row->last_auth), &(auths->createdate))) |
|
copy_tv(&(row->last_auth), &(auths->createdate)); |
|
} |
|
} |
|
|
|
if (startup_complete && shares) { |
|
if (shares->errn == SE_NONE) { |
|
pool.diffacc += shares->diff; |
|
pool.shareacc++; |
|
} else { |
|
pool.diffinv += shares->diff; |
|
pool.shareinv++; |
|
} |
|
item = find_workerstatus(shares->userid, shares->workername, |
|
file, func, line); |
|
if (item) { |
|
DATA_WORKERSTATUS(row, item); |
|
if (tv_newer(&(row->last_share), &(shares->createdate))) { |
|
copy_tv(&(row->last_share), &(shares->createdate)); |
|
row->last_diff = shares->diff; |
|
} |
|
switch (shares->errn) { |
|
case SE_NONE: |
|
row->diffacc += shares->diff; |
|
row->shareacc++; |
|
break; |
|
case SE_STALE: |
|
row->diffinv += shares->diff; |
|
row->shareinv++; |
|
row->diffsta += shares->diff; |
|
row->sharesta++; |
|
break; |
|
case SE_DUPE: |
|
row->diffinv += shares->diff; |
|
row->shareinv++; |
|
row->diffdup += shares->diff; |
|
row->sharedup++; |
|
break; |
|
case SE_HIGH_DIFF: |
|
row->diffinv += shares->diff; |
|
row->shareinv++; |
|
row->diffhi += shares->diff; |
|
row->sharehi++; |
|
break; |
|
default: |
|
row->diffinv += shares->diff; |
|
row->shareinv++; |
|
row->diffrej += shares->diff; |
|
row->sharerej++; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if (startup_complete && userstats) { |
|
item = find_workerstatus(userstats->userid, userstats->workername, |
|
file, func, line); |
|
if (item) { |
|
DATA_WORKERSTATUS(row, item); |
|
if (userstats->idle) { |
|
if (tv_newer(&(row->last_idle), &(userstats->statsdate))) |
|
copy_tv(&(row->last_idle), &(userstats->statsdate)); |
|
} else { |
|
if (tv_newer(&(row->last_stats), &(userstats->statsdate))) |
|
copy_tv(&(row->last_stats), &(userstats->statsdate)); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// default tree order by username asc,expirydate desc |
|
cmp_t cmp_users(K_ITEM *a, K_ITEM *b) |
|
{ |
|
USERS *ua, *ub; |
|
DATA_USERS(ua, a); |
|
DATA_USERS(ub, b); |
|
cmp_t c = CMP_STR(ua->username, ub->username); |
|
if (c == 0) |
|
c = CMP_TV(ub->expirydate, ua->expirydate); |
|
return c; |
|
} |
|
|
|
// order by userid asc,expirydate desc |
|
cmp_t cmp_userid(K_ITEM *a, K_ITEM *b) |
|
{ |
|
USERS *ua, *ub; |
|
DATA_USERS(ua, a); |
|
DATA_USERS(ub, b); |
|
cmp_t c = CMP_BIGINT(ua->userid, ub->userid); |
|
if (c == 0) |
|
c = CMP_TV(ub->expirydate, ua->expirydate); |
|
return c; |
|
} |
|
|
|
// Must be R or W locked before call |
|
K_ITEM *find_users(char *username) |
|
{ |
|
USERS users; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look; |
|
|
|
STRNCPY(users.username, username); |
|
users.expirydate.tv_sec = default_expiry.tv_sec; |
|
users.expirydate.tv_usec = default_expiry.tv_usec; |
|
|
|
INIT_USERS(&look); |
|
look.data = (void *)(&users); |
|
return find_in_ktree(users_root, &look, cmp_users, ctx); |
|
} |
|
|
|
// Must be R or W locked before call |
|
K_ITEM *find_userid(int64_t userid) |
|
{ |
|
USERS users; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look; |
|
|
|
users.userid = userid; |
|
users.expirydate.tv_sec = default_expiry.tv_sec; |
|
users.expirydate.tv_usec = default_expiry.tv_usec; |
|
|
|
INIT_USERS(&look); |
|
look.data = (void *)(&users); |
|
return find_in_ktree(userid_root, &look, cmp_userid, ctx); |
|
} |
|
|
|
// TODO: endian? (to avoid being all zeros?) |
|
void make_salt(USERS *users) |
|
{ |
|
long int r1, r2, r3, r4; |
|
|
|
r1 = random(); |
|
r2 = random(); |
|
r3 = random(); |
|
r4 = random(); |
|
|
|
__bin2hex(users->salt, (void *)(&r1), 4); |
|
__bin2hex(users->salt+8, (void *)(&r2), 4); |
|
__bin2hex(users->salt+16, (void *)(&r3), 4); |
|
__bin2hex(users->salt+24, (void *)(&r4), 4); |
|
} |
|
|
|
void password_hash(char *username, char *passwordhash, char *salt, char *result, size_t siz) |
|
{ |
|
char tohash[TXT_BIG+1]; |
|
char buf[TXT_BIG+1]; |
|
size_t len, tot; |
|
char why[1024]; |
|
|
|
if (siz < SHA256SIZHEX+1) { |
|
snprintf(why, sizeof(why), |
|
"target result too small (%d/%d)", |
|
(int)siz, SHA256SIZHEX+1); |
|
goto hashfail; |
|
} |
|
|
|
if (sizeof(buf) < SHA256SIZBIN) { |
|
snprintf(why, sizeof(why), |
|
"temporary target buf too small (%d/%d)", |
|
(int)sizeof(buf), SHA256SIZBIN); |
|
goto hashfail; |
|
} |
|
|
|
tot = len = strlen(passwordhash) / 2; |
|
if (len != SHA256SIZBIN) { |
|
snprintf(why, sizeof(why), |
|
"passwordhash wrong size (%d/%d)", |
|
(int)len, SHA256SIZBIN); |
|
goto hashfail; |
|
} |
|
if (len > sizeof(tohash)) { |
|
snprintf(why, sizeof(why), |
|
"temporary tohash too small (%d/%d)", |
|
(int)sizeof(tohash), (int)len); |
|
goto hashfail; |
|
} |
|
|
|
hex2bin(tohash, passwordhash, len); |
|
|
|
len = strlen(salt) / 2; |
|
if (len != SALTSIZBIN) { |
|
snprintf(why, sizeof(why), |
|
"salt wrong size (%d/%d)", |
|
(int)len, SALTSIZBIN); |
|
goto hashfail; |
|
} |
|
if ((tot + len) > sizeof(tohash)) { |
|
snprintf(why, sizeof(why), |
|
"passwordhash+salt too big (%d/%d)", |
|
(int)(tot + len), (int)sizeof(tohash)); |
|
goto hashfail; |
|
} |
|
|
|
hex2bin(tohash+tot, salt, len); |
|
tot += len; |
|
|
|
sha256((const unsigned char *)tohash, (unsigned int)tot, (unsigned char *)buf); |
|
|
|
__bin2hex(result, (void *)buf, SHA256SIZBIN); |
|
|
|
return; |
|
hashfail: |
|
LOGERR("%s() Failed to hash '%s' password: %s", |
|
__func__, username, why); |
|
result[0] = '\0'; |
|
} |
|
|
|
bool check_hash(USERS *users, char *passwordhash) |
|
{ |
|
char hex[SHA256SIZHEX+1]; |
|
|
|
if (*(users->salt)) { |
|
password_hash(users->username, passwordhash, users->salt, hex, sizeof(hex)); |
|
return (strcasecmp(hex, users->passwordhash) == 0); |
|
} else |
|
return (strcasecmp(passwordhash, users->passwordhash) == 0); |
|
} |
|
|
|
// default tree order by userid asc,attname asc,expirydate desc |
|
cmp_t cmp_useratts(K_ITEM *a, K_ITEM *b) |
|
{ |
|
USERATTS *ua, *ub; |
|
DATA_USERATTS(ua, a); |
|
DATA_USERATTS(ub, b); |
|
cmp_t c = CMP_BIGINT(ua->userid, ub->userid); |
|
if (c == 0) { |
|
c = CMP_STR(ua->attname, ub->attname); |
|
if (c == 0) |
|
c = CMP_TV(ub->expirydate, ua->expirydate); |
|
} |
|
return c; |
|
} |
|
|
|
// Must be R or W locked before call |
|
K_ITEM *find_useratts(int64_t userid, char *attname) |
|
{ |
|
USERATTS useratts; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look; |
|
|
|
useratts.userid = userid; |
|
STRNCPY(useratts.attname, attname); |
|
useratts.expirydate.tv_sec = default_expiry.tv_sec; |
|
useratts.expirydate.tv_usec = default_expiry.tv_usec; |
|
|
|
INIT_USERATTS(&look); |
|
look.data = (void *)(&useratts); |
|
return find_in_ktree(useratts_root, &look, cmp_useratts, ctx); |
|
} |
|
|
|
// order by userid asc,workername asc,expirydate desc |
|
cmp_t cmp_workers(K_ITEM *a, K_ITEM *b) |
|
{ |
|
WORKERS *wa, *wb; |
|
DATA_WORKERS(wa, a); |
|
DATA_WORKERS(wb, b); |
|
cmp_t c = CMP_BIGINT(wa->userid, wb->userid); |
|
if (c == 0) { |
|
c = CMP_STR(wa->workername, wb->workername); |
|
if (c == 0) |
|
c = CMP_TV(wb->expirydate, wa->expirydate); |
|
} |
|
return c; |
|
} |
|
|
|
K_ITEM *find_workers(int64_t userid, char *workername) |
|
{ |
|
WORKERS workers; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look; |
|
|
|
workers.userid = userid; |
|
STRNCPY(workers.workername, workername); |
|
workers.expirydate.tv_sec = default_expiry.tv_sec; |
|
workers.expirydate.tv_usec = default_expiry.tv_usec; |
|
|
|
INIT_WORKERS(&look); |
|
look.data = (void *)(&workers); |
|
return find_in_ktree(workers_root, &look, cmp_workers, ctx); |
|
} |
|
|
|
K_ITEM *new_worker(PGconn *conn, bool update, int64_t userid, char *workername, |
|
char *diffdef, char *idlenotificationenabled, |
|
char *idlenotificationtime, char *by, |
|
char *code, char *inet, tv_t *cd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *item; |
|
|
|
item = find_workers(userid, workername); |
|
if (item) { |
|
if (!confirm_sharesummary && update) { |
|
workers_update(conn, item, diffdef, idlenotificationenabled, |
|
idlenotificationtime, by, code, inet, cd, |
|
trf_root, true); |
|
} |
|
} else { |
|
if (confirm_sharesummary) { |
|
// Shouldn't be possible since the sharesummary is already aged |
|
LOGERR("%s() %"PRId64"/%s workername not found during confirm", |
|
__func__, userid, workername); |
|
return NULL; |
|
} |
|
|
|
// TODO: limit how many? |
|
item = workers_add(conn, userid, workername, diffdef, |
|
idlenotificationenabled, idlenotificationtime, |
|
by, code, inet, cd, trf_root); |
|
} |
|
return item; |
|
} |
|
|
|
K_ITEM *new_default_worker(PGconn *conn, bool update, int64_t userid, char *workername, |
|
char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root) |
|
{ |
|
return new_worker(conn, update, userid, workername, DIFFICULTYDEFAULT_DEF_STR, |
|
IDLENOTIFICATIONENABLED_DEF, IDLENOTIFICATIONTIME_DEF_STR, |
|
by, code, inet, cd, trf_root); |
|
} |
|
|
|
/* unused |
|
static K_ITEM *new_worker_find_user(PGconn *conn, bool update, char *username, |
|
char *workername, char *diffdef, |
|
char *idlenotificationenabled, |
|
char *idlenotificationtime, |
|
char *by, char *code, char *inet, |
|
tv_t *cd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *item; |
|
USERS *users; |
|
|
|
K_RLOCK(users_free); |
|
item = find_users(username); |
|
K_RUNLOCK(users_free); |
|
if (!item) |
|
return NULL; |
|
|
|
DATA_USERS(users, item); |
|
return new_worker(conn, update, users->userid, workername, diffdef, |
|
idlenotificationenabled, idlenotificationtime, |
|
by, code, inet, cd, trf_root); |
|
} |
|
*/ |
|
|
|
void dsp_paymentaddresses(K_ITEM *item, FILE *stream) |
|
{ |
|
char expirydate_buf[DATE_BUFSIZ], createdate_buf[DATE_BUFSIZ]; |
|
PAYMENTADDRESSES *pa; |
|
|
|
if (!item) |
|
fprintf(stream, "%s() called with (null) item\n", __func__); |
|
else { |
|
DATA_PAYMENTADDRESSES(pa, item); |
|
tv_to_buf(&(pa->expirydate), expirydate_buf, sizeof(expirydate_buf)); |
|
tv_to_buf(&(pa->createdate), createdate_buf, sizeof(createdate_buf)); |
|
fprintf(stream, " id=%"PRId64" userid=%"PRId64" addr='%s' " |
|
"ratio=%"PRId32" exp=%s cd=%s\n", |
|
pa->paymentaddressid, pa->userid, |
|
pa->payaddress, pa->payratio, |
|
expirydate_buf, createdate_buf); |
|
} |
|
} |
|
|
|
// order by userid asc,expirydate desc,payaddress asc |
|
cmp_t cmp_paymentaddresses(K_ITEM *a, K_ITEM *b) |
|
{ |
|
PAYMENTADDRESSES *pa, *pb; |
|
DATA_PAYMENTADDRESSES(pa, a); |
|
DATA_PAYMENTADDRESSES(pb, b); |
|
cmp_t c = CMP_BIGINT(pa->userid, pb->userid); |
|
if (c == 0) { |
|
c = CMP_TV(pb->expirydate, pa->expirydate); |
|
if (c == 0) |
|
c = CMP_STR(pa->payaddress, pb->payaddress); |
|
} |
|
return c; |
|
} |
|
|
|
// Only one for now ... |
|
K_ITEM *find_paymentaddresses(int64_t userid) |
|
{ |
|
PAYMENTADDRESSES paymentaddresses, *pa; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look, *item; |
|
|
|
paymentaddresses.userid = userid; |
|
paymentaddresses.payaddress[0] = '\0'; |
|
paymentaddresses.expirydate.tv_sec = DATE_S_EOT; |
|
|
|
INIT_PAYMENTADDRESSES(&look); |
|
look.data = (void *)(&paymentaddresses); |
|
item = find_after_in_ktree(paymentaddresses_root, &look, cmp_paymentaddresses, ctx); |
|
if (item) { |
|
DATA_PAYMENTADDRESSES(pa, item); |
|
if (pa->userid == userid && CURRENT(&(pa->expirydate))) |
|
return item; |
|
else |
|
return NULL; |
|
} else |
|
return NULL; |
|
} |
|
|
|
// order by userid asc,paydate asc,payaddress asc,expirydate desc |
|
cmp_t cmp_payments(K_ITEM *a, K_ITEM *b) |
|
{ |
|
PAYMENTS *pa, *pb; |
|
DATA_PAYMENTS(pa, a); |
|
DATA_PAYMENTS(pb, b); |
|
cmp_t c = CMP_BIGINT(pa->userid, pb->userid); |
|
if (c == 0) { |
|
c = CMP_TV(pa->paydate, pb->paydate); |
|
if (c == 0) { |
|
c = CMP_STR(pa->payaddress, pb->payaddress); |
|
if (c == 0) |
|
c = CMP_TV(pb->expirydate, pa->expirydate); |
|
} |
|
} |
|
return c; |
|
} |
|
|
|
// order by optionname asc,activationdate asc,activationheight asc,expirydate desc |
|
cmp_t cmp_optioncontrol(K_ITEM *a, K_ITEM *b) |
|
{ |
|
OPTIONCONTROL *oca, *ocb; |
|
DATA_OPTIONCONTROL(oca, a); |
|
DATA_OPTIONCONTROL(ocb, b); |
|
cmp_t c = CMP_STR(oca->optionname, ocb->optionname); |
|
if (c == 0) { |
|
c = CMP_TV(oca->activationdate, ocb->activationdate); |
|
if (c == 0) { |
|
c = CMP_INT(oca->activationheight, ocb->activationheight); |
|
if (c == 0) |
|
c = CMP_TV(ocb->expirydate, oca->expirydate); |
|
} |
|
} |
|
return c; |
|
} |
|
|
|
// Must be R or W locked before call |
|
K_ITEM *find_optioncontrol(char *optionname, tv_t *now) |
|
{ |
|
OPTIONCONTROL optioncontrol, *oc, *ocbest; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look, *item, *best; |
|
|
|
/* Step through all records having optionaname and check: |
|
* 1) activationdate is <= now |
|
* and |
|
* 2) height <= current |
|
* Remember the active record with the newest activationdate |
|
* If two records have the same activation date, then |
|
* remember the active record with the highest height |
|
* In optioncontrol_add(), when not specified, |
|
* the default activation date is DATE_BEGIN |
|
* and the default height is 1 (OPTIONCONTROL_HEIGHT) |
|
* Thus if records have both values set, then |
|
* activationdate will determine the newests record |
|
* To have activationheight decide selection, |
|
* create all records with only activationheight and then |
|
* activationdate will all be the default value and not |
|
* decide the outcome */ |
|
STRNCPY(optioncontrol.optionname, optionname); |
|
optioncontrol.activationdate.tv_sec = 0L; |
|
optioncontrol.activationdate.tv_usec = 0L; |
|
optioncontrol.activationheight = OPTIONCONTROL_HEIGHT - 1; |
|
optioncontrol.expirydate.tv_sec = default_expiry.tv_sec; |
|
optioncontrol.expirydate.tv_usec = default_expiry.tv_usec; |
|
|
|
INIT_OPTIONCONTROL(&look); |
|
look.data = (void *)(&optioncontrol); |
|
item = find_after_in_ktree(optioncontrol_root, &look, cmp_optioncontrol, ctx); |
|
ocbest = NULL; |
|
best = NULL; |
|
while (item) { |
|
DATA_OPTIONCONTROL(oc, item); |
|
// Ordered first by optionname |
|
if (strcmp(oc->optionname, optionname) != 0) |
|
break; |
|
|
|
// Is oc active? |
|
if (CURRENT(&(oc->expirydate)) && |
|
oc->activationheight <= pool.height && |
|
tv_newer_eq(&(oc->activationdate), now)) { |
|
// Is oc newer than ocbest? |
|
if (!ocbest || |
|
tv_newer(&(ocbest->activationdate), &(oc->activationdate)) || |
|
(tv_equal(&(ocbest->activationdate), &(oc->activationdate)) && |
|
ocbest->activationheight < oc->activationheight)) { |
|
ocbest = oc; |
|
best = item; |
|
} |
|
} |
|
item = next_in_ktree(ctx); |
|
} |
|
return best; |
|
} |
|
|
|
// order by workinfoid asc,expirydate asc |
|
cmp_t cmp_workinfo(K_ITEM *a, K_ITEM *b) |
|
{ |
|
WORKINFO *wa, *wb; |
|
DATA_WORKINFO(wa, a); |
|
DATA_WORKINFO(wb, b); |
|
cmp_t c = CMP_BIGINT(wa->workinfoid, wb->workinfoid); |
|
if (c == 0) |
|
c = CMP_TV(wa->expirydate, wb->expirydate); |
|
return c; |
|
} |
|
|
|
int32_t _coinbase1height(char *coinbase1, WHERE_FFL_ARGS) |
|
{ |
|
int32_t height = 0; |
|
uchar *cb1; |
|
int siz; |
|
|
|
cb1 = ((uchar *)coinbase1) + 84; |
|
siz = ((hex2bin_tbl[*cb1]) << 4) + (hex2bin_tbl[*(cb1+1)]); |
|
|
|
// limit to 4 for int32_t and since ... that should last a while :) |
|
if (siz < 1 || siz > 4) { |
|
LOGERR("%s(): Invalid coinbase1 block height size (%d)" |
|
" require: 1..4 (cb1 %s)" WHERE_FFL, |
|
__func__, siz, coinbase1, WHERE_FFL_PASS); |
|
return height; |
|
} |
|
|
|
siz *= 2; |
|
while (siz-- > 0) { |
|
height <<= 4; |
|
height += (int32_t)hex2bin_tbl[*(cb1+(siz^1)+2)]; |
|
} |
|
|
|
return height; |
|
} |
|
|
|
cmp_t _cmp_height(char *coinbase1a, char *coinbase1b, WHERE_FFL_ARGS) |
|
{ |
|
return CMP_INT(_coinbase1height(coinbase1a, WHERE_FFL_PASS), |
|
_coinbase1height(coinbase1b, WHERE_FFL_PASS)); |
|
} |
|
|
|
// order by height asc,createdate asc |
|
cmp_t cmp_workinfo_height(K_ITEM *a, K_ITEM *b) |
|
{ |
|
WORKINFO *wa, *wb; |
|
DATA_WORKINFO(wa, a); |
|
DATA_WORKINFO(wb, b); |
|
cmp_t c = cmp_height(wa->coinbase1, wb->coinbase1); |
|
if (c == 0) |
|
c = CMP_TV(wa->createdate, wb->createdate); |
|
return c; |
|
} |
|
|
|
K_ITEM *find_workinfo(int64_t workinfoid) |
|
{ |
|
WORKINFO workinfo; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look, *item; |
|
|
|
workinfo.workinfoid = workinfoid; |
|
workinfo.expirydate.tv_sec = default_expiry.tv_sec; |
|
workinfo.expirydate.tv_usec = default_expiry.tv_usec; |
|
|
|
INIT_WORKINFO(&look); |
|
look.data = (void *)(&workinfo); |
|
K_RLOCK(workinfo_free); |
|
item = find_in_ktree(workinfo_root, &look, cmp_workinfo, ctx); |
|
K_RUNLOCK(workinfo_free); |
|
return item; |
|
} |
|
|
|
bool workinfo_age(PGconn *conn, int64_t workinfoid, char *poolinstance, |
|
char *by, char *code, char *inet, tv_t *cd, |
|
tv_t *ss_first, tv_t *ss_last, int64_t *ss_count, |
|
int64_t *s_count, int64_t *s_diff) |
|
{ |
|
K_ITEM *wi_item, ss_look, *ss_item, s_look, *s_item, *tmp_item; |
|
K_TREE_CTX ss_ctx[1], s_ctx[1], tmp_ctx[1]; |
|
char cd_buf[DATE_BUFSIZ]; |
|
int64_t ss_tot, ss_already, ss_failed, shares_tot, shares_dumped; |
|
SHARESUMMARY looksharesummary, *sharesummary; |
|
WORKINFO *workinfo; |
|
SHARES lookshares, *shares; |
|
bool ok = false, conned = false, skipupdate; |
|
char error[1024]; |
|
|
|
LOGDEBUG("%s(): age", __func__); |
|
|
|
ss_first->tv_sec = ss_first->tv_usec = |
|
ss_last->tv_sec = ss_last->tv_usec = 0; |
|
*ss_count = *s_count = *s_diff = 0; |
|
|
|
wi_item = find_workinfo(workinfoid); |
|
if (!wi_item) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s() %"PRId64"/%s/%ld,%ld %.19s no workinfo! Age discarded!", |
|
__func__, workinfoid, poolinstance, |
|
cd->tv_sec, cd->tv_usec, cd_buf); |
|
goto bye; |
|
} |
|
|
|
DATA_WORKINFO(workinfo, wi_item); |
|
if (strcmp(poolinstance, workinfo->poolinstance) != 0) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s() %"PRId64"/%s/%ld,%ld %.19s Poolinstance changed " |
|
"(from %s)! Age discarded!", |
|
__func__, workinfoid, poolinstance, |
|
cd->tv_sec, cd->tv_usec, cd_buf, |
|
workinfo->poolinstance); |
|
goto bye; |
|
} |
|
|
|
INIT_SHARESUMMARY(&ss_look); |
|
INIT_SHARES(&s_look); |
|
|
|
// Find the first matching sharesummary |
|
looksharesummary.workinfoid = workinfoid; |
|
looksharesummary.userid = -1; |
|
looksharesummary.workername[0] = '\0'; |
|
|
|
ok = true; |
|
ss_tot = ss_already = ss_failed = shares_tot = shares_dumped = 0; |
|
ss_look.data = (void *)(&looksharesummary); |
|
K_RLOCK(sharesummary_free); |
|
ss_item = find_after_in_ktree(sharesummary_workinfoid_root, &ss_look, cmp_sharesummary_workinfoid, ss_ctx); |
|
K_RUNLOCK(sharesummary_free); |
|
DATA_SHARESUMMARY_NULL(sharesummary, ss_item); |
|
while (ss_item && sharesummary->workinfoid == workinfoid) { |
|
ss_tot++; |
|
error[0] = '\0'; |
|
skipupdate = false; |
|
/* Reloading during a confirm will not have any old data |
|
* so finding an aged sharesummary here is an error */ |
|
if (reloading) { |
|
if (sharesummary->complete[0] == SUMMARY_COMPLETE) { |
|
ss_already++; |
|
skipupdate = true; |
|
if (confirm_sharesummary) { |
|
LOGERR("%s(): Duplicate %s found during confirm %"PRId64"/%s/%"PRId64, |
|
__func__, __func__, |
|
sharesummary->userid, |
|
sharesummary->workername, |
|
sharesummary->workinfoid); |
|
} |
|
} |
|
} |
|
|
|
if (!skipupdate) { |
|
if (conn == NULL && !confirm_sharesummary) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
if (!sharesummary_update(conn, NULL, NULL, ss_item, by, code, inet, cd)) { |
|
ss_failed++; |
|
LOGERR("%s(): Failed to age share summary %"PRId64"/%s/%"PRId64, |
|
__func__, sharesummary->userid, |
|
sharesummary->workername, |
|
sharesummary->workinfoid); |
|
ok = false; |
|
} else { |
|
(*ss_count)++; |
|
*s_count += sharesummary->sharecount; |
|
*s_diff += sharesummary->diffacc; |
|
if (ss_first->tv_sec == 0 || |
|
!tv_newer(ss_first, &(sharesummary->firstshare))) |
|
copy_tv(ss_first, &(sharesummary->firstshare)); |
|
if (tv_newer(ss_last, &(sharesummary->lastshare))) |
|
copy_tv(ss_last, &(sharesummary->lastshare)); |
|
} |
|
} |
|
|
|
// Discard the shares either way |
|
lookshares.workinfoid = workinfoid; |
|
lookshares.userid = sharesummary->userid; |
|
strcpy(lookshares.workername, sharesummary->workername); |
|
lookshares.createdate.tv_sec = 0; |
|
lookshares.createdate.tv_usec = 0; |
|
|
|
s_look.data = (void *)(&lookshares); |
|
K_WLOCK(shares_free); |
|
s_item = find_after_in_ktree(shares_root, &s_look, cmp_shares, s_ctx); |
|
while (s_item) { |
|
DATA_SHARES(shares, s_item); |
|
if (shares->workinfoid != workinfoid || |
|
shares->userid != lookshares.userid || |
|
strcmp(shares->workername, lookshares.workername) != 0) |
|
break; |
|
|
|
shares_tot++; |
|
tmp_item = next_in_ktree(s_ctx); |
|
shares_root = remove_from_ktree(shares_root, s_item, cmp_shares, tmp_ctx); |
|
k_unlink_item(shares_store, s_item); |
|
if (reloading && skipupdate) |
|
shares_dumped++; |
|
if (reloading && skipupdate && !error[0]) { |
|
snprintf(error, sizeof(error), |
|
"reload found aged shares: %"PRId64"/%"PRId64"/%s", |
|
shares->workinfoid, |
|
shares->userid, |
|
shares->workername); |
|
} |
|
k_add_head(shares_free, s_item); |
|
s_item = tmp_item; |
|
} |
|
K_WUNLOCK(shares_free); |
|
K_RLOCK(sharesummary_free); |
|
ss_item = next_in_ktree(ss_ctx); |
|
K_RUNLOCK(sharesummary_free); |
|
DATA_SHARESUMMARY_NULL(sharesummary, ss_item); |
|
|
|
if (error[0]) |
|
LOGERR("%s(): %s", __func__, error); |
|
} |
|
|
|
if (conned) |
|
PQfinish(conn); |
|
|
|
if (ss_already || ss_failed || shares_dumped) { |
|
/* If all were already aged, and no shares |
|
* then we don't want a message */ |
|
if (!(ss_already == ss_tot && shares_tot == 0)) { |
|
LOGERR("%s(): Summary aging of %"PRId64"/%s sstotal=%"PRId64 |
|
" already=%"PRId64" failed=%"PRId64 |
|
", sharestotal=%"PRId64" dumped=%"PRId64, |
|
__func__, workinfoid, poolinstance, ss_tot, |
|
ss_already, ss_failed, shares_tot, |
|
shares_dumped); |
|
} |
|
} |
|
bye: |
|
return ok; |
|
} |
|
|
|
// order by workinfoid asc,userid asc,workername asc,createdate asc,nonce asc,expirydate desc |
|
cmp_t cmp_shares(K_ITEM *a, K_ITEM *b) |
|
{ |
|
SHARES *sa, *sb; |
|
DATA_SHARES(sa, a); |
|
DATA_SHARES(sb, b); |
|
cmp_t c = CMP_BIGINT(sa->workinfoid, sb->workinfoid); |
|
if (c == 0) { |
|
c = CMP_BIGINT(sa->userid, sb->userid); |
|
if (c == 0) { |
|
c = CMP_STR(sa->workername, sb->workername); |
|
if (c == 0) { |
|
c = CMP_TV(sa->createdate, sb->createdate); |
|
if (c == 0) { |
|
c = CMP_STR(sa->nonce, sb->nonce); |
|
if (c == 0) { |
|
c = CMP_TV(sb->expirydate, |
|
sa->expirydate); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
return c; |
|
} |
|
|
|
// order by workinfoid asc,userid asc,createdate asc,nonce asc,expirydate desc |
|
cmp_t cmp_shareerrors(K_ITEM *a, K_ITEM *b) |
|
{ |
|
SHAREERRORS *sa, *sb; |
|
DATA_SHAREERRORS(sa, a); |
|
DATA_SHAREERRORS(sb, b); |
|
cmp_t c = CMP_BIGINT(sa->workinfoid, sb->workinfoid); |
|
if (c == 0) { |
|
c = CMP_BIGINT(sa->userid, sb->userid); |
|
if (c == 0) { |
|
c = CMP_TV(sa->createdate, sb->createdate); |
|
if (c == 0) |
|
c = CMP_TV(sb->expirydate, sa->expirydate); |
|
} |
|
} |
|
return c; |
|
} |
|
|
|
void dsp_sharesummary(K_ITEM *item, FILE *stream) |
|
{ |
|
char createdate_buf[DATE_BUFSIZ]; |
|
SHARESUMMARY *s; |
|
|
|
if (!item) |
|
fprintf(stream, "%s() called with (null) item\n", __func__); |
|
else { |
|
DATA_SHARESUMMARY(s, item); |
|
tv_to_buf(&(s->createdate), createdate_buf, sizeof(createdate_buf)); |
|
fprintf(stream, " uid=%"PRId64" wn='%s' wid=%"PRId64" " |
|
"da=%f ds=%f ss=%f c='%s' cd=%s\n", |
|
s->userid, s->workername, s->workinfoid, |
|
s->diffacc, s->diffsta, s->sharesta, |
|
s->complete, createdate_buf); |
|
} |
|
} |
|
|
|
// default tree order by userid asc,workername asc,workinfoid asc for reporting |
|
cmp_t cmp_sharesummary(K_ITEM *a, K_ITEM *b) |
|
{ |
|
SHARESUMMARY *sa, *sb; |
|
DATA_SHARESUMMARY(sa, a); |
|
DATA_SHARESUMMARY(sb, b); |
|
cmp_t c = CMP_BIGINT(sa->userid, sb->userid); |
|
if (c == 0) { |
|
c = CMP_STR(sa->workername, sb->workername); |
|
if (c == 0) |
|
c = CMP_BIGINT(sa->workinfoid, sb->workinfoid); |
|
} |
|
return c; |
|
} |
|
|
|
// order by workinfoid asc,userid asc,workername asc for flagging complete |
|
cmp_t cmp_sharesummary_workinfoid(K_ITEM *a, K_ITEM *b) |
|
{ |
|
SHARESUMMARY *sa, *sb; |
|
DATA_SHARESUMMARY(sa, a); |
|
DATA_SHARESUMMARY(sb, b); |
|
cmp_t c = CMP_BIGINT(sa->workinfoid, sb->workinfoid); |
|
if (c == 0) { |
|
c = CMP_BIGINT(sa->userid, sb->userid); |
|
if (c == 0) |
|
c = CMP_STR(sa->workername, sb->workername); |
|
} |
|
return c; |
|
} |
|
|
|
void zero_sharesummary(SHARESUMMARY *row, tv_t *cd, double diff) |
|
{ |
|
row->diffacc = row->diffsta = row->diffdup = row->diffhi = |
|
row->diffrej = row->shareacc = row->sharesta = row->sharedup = |
|
row->sharehi = row->sharerej = 0.0; |
|
row->sharecount = row->errorcount = row->countlastupdate = 0; |
|
row->reset = false; |
|
row->firstshare.tv_sec = cd->tv_sec; |
|
row->firstshare.tv_usec = cd->tv_usec; |
|
row->lastshare.tv_sec = row->firstshare.tv_sec; |
|
row->lastshare.tv_usec = row->firstshare.tv_usec; |
|
row->lastdiffacc = diff; |
|
row->complete[0] = SUMMARY_NEW; |
|
row->complete[1] = '\0'; |
|
} |
|
|
|
K_ITEM *find_sharesummary(int64_t userid, char *workername, int64_t workinfoid) |
|
{ |
|
SHARESUMMARY sharesummary; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look; |
|
|
|
sharesummary.userid = userid; |
|
STRNCPY(sharesummary.workername, workername); |
|
sharesummary.workinfoid = workinfoid; |
|
|
|
INIT_SHARESUMMARY(&look); |
|
look.data = (void *)(&sharesummary); |
|
return find_in_ktree(sharesummary_root, &look, cmp_sharesummary, ctx); |
|
} |
|
|
|
void auto_age_older(PGconn *conn, int64_t workinfoid, char *poolinstance, |
|
char *by, char *code, char *inet, tv_t *cd) |
|
{ |
|
static int64_t last_attempted_id = -1; |
|
static int64_t prev_found = 0; |
|
static int repeat; |
|
|
|
char min_buf[DATE_BUFSIZ], max_buf[DATE_BUFSIZ]; |
|
int64_t ss_count_tot, s_count_tot, s_diff_tot; |
|
int64_t ss_count, s_count, s_diff; |
|
tv_t ss_first_min, ss_last_max; |
|
tv_t ss_first, ss_last; |
|
int32_t wid_count; |
|
SHARESUMMARY looksharesummary, *sharesummary; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look, *ss_item; |
|
int64_t age_id, do_id, to_id; |
|
bool ok, found; |
|
|
|
LOGDEBUG("%s(): workinfoid=%"PRId64" prev=%"PRId64, __func__, workinfoid, prev_found); |
|
|
|
age_id = prev_found; |
|
|
|
// Find the oldest 'unaged' sharesummary < workinfoid and >= prev_found |
|
looksharesummary.workinfoid = prev_found; |
|
looksharesummary.userid = -1; |
|
looksharesummary.workername[0] = '\0'; |
|
INIT_SHARESUMMARY(&look); |
|
look.data = (void *)(&looksharesummary); |
|
|
|
K_RLOCK(sharesummary_free); |
|
ss_item = find_after_in_ktree(sharesummary_workinfoid_root, &look, |
|
cmp_sharesummary_workinfoid, ctx); |
|
DATA_SHARESUMMARY_NULL(sharesummary, ss_item); |
|
|
|
ss_first_min.tv_sec = ss_first_min.tv_usec = |
|
ss_last_max.tv_sec = ss_last_max.tv_usec = 0; |
|
ss_count_tot = s_count_tot = s_diff_tot = 0; |
|
|
|
found = false; |
|
while (ss_item && sharesummary->workinfoid < workinfoid) { |
|
if (sharesummary->complete[0] == SUMMARY_NEW) { |
|
age_id = sharesummary->workinfoid; |
|
prev_found = age_id; |
|
found = true; |
|
break; |
|
} |
|
ss_item = next_in_ktree(ctx); |
|
DATA_SHARESUMMARY_NULL(sharesummary, ss_item); |
|
} |
|
K_RUNLOCK(sharesummary_free); |
|
|
|
LOGDEBUG("%s(): age_id=%"PRId64" found=%d", __func__, age_id, found); |
|
// Don't repeat searching old items to avoid accessing their ram |
|
if (!found) |
|
prev_found = workinfoid; |
|
else { |
|
/* Process all the consecutive sharesummaries that's aren't aged |
|
* This way we find each oldest 'batch' of sharesummaries that have |
|
* been missed and can report the range of data that was aged, |
|
* which would normally just be an approx 10min set of workinfoids |
|
* from the last time ckpool stopped |
|
* Each next group of unaged sharesummaries following this, will be |
|
* picked up by each next aging */ |
|
wid_count = 0; |
|
do_id = age_id; |
|
to_id = 0; |
|
do { |
|
ok = workinfo_age(conn, do_id, poolinstance, |
|
by, code, inet, cd, |
|
&ss_first, &ss_last, |
|
&ss_count, &s_count, &s_diff); |
|
|
|
ss_count_tot += ss_count; |
|
s_count_tot += s_count; |
|
s_diff_tot += s_diff; |
|
if (ss_first_min.tv_sec == 0 || !tv_newer(&ss_first_min, &ss_first)) |
|
copy_tv(&ss_first_min, &ss_first); |
|
if (tv_newer(&ss_last_max, &ss_last)) |
|
copy_tv(&ss_last_max, &ss_last); |
|
|
|
if (!ok) |
|
break; |
|
|
|
to_id = do_id; |
|
wid_count++; |
|
K_RLOCK(sharesummary_free); |
|
while (ss_item && sharesummary->workinfoid == to_id) { |
|
ss_item = next_in_ktree(ctx); |
|
DATA_SHARESUMMARY_NULL(sharesummary, ss_item); |
|
} |
|
K_RUNLOCK(sharesummary_free); |
|
|
|
if (ss_item) { |
|
do_id = sharesummary->workinfoid; |
|
if (do_id >= workinfoid) |
|
break; |
|
if (sharesummary->complete[0] != SUMMARY_NEW) |
|
break; |
|
} |
|
} while (ss_item); |
|
if (to_id == 0) { |
|
if (last_attempted_id != age_id || ++repeat >= 10) { |
|
// Approx once every 5min since workinfo defaults to ~30s |
|
LOGWARNING("%s() Auto-age failed to age %"PRId64, |
|
__func__, age_id); |
|
last_attempted_id = age_id; |
|
repeat = 0; |
|
} |
|
} else { |
|
char idrange[64]; |
|
char sharerange[256]; |
|
if (to_id != age_id) { |
|
snprintf(idrange, sizeof(idrange), |
|
"from %"PRId64" to %"PRId64, |
|
age_id, to_id); |
|
} else { |
|
snprintf(idrange, sizeof(idrange), |
|
"%"PRId64, age_id); |
|
} |
|
tv_to_buf(&ss_first_min, min_buf, sizeof(min_buf)); |
|
if (tv_equal(&ss_first_min, &ss_last_max)) { |
|
snprintf(sharerange, sizeof(sharerange), |
|
"share date %s", min_buf); |
|
} else { |
|
tv_to_buf(&ss_last_max, max_buf, sizeof(max_buf)); |
|
snprintf(sharerange, sizeof(sharerange), |
|
"share dates %s to %s", |
|
min_buf, max_buf); |
|
} |
|
LOGWARNING("%s() Auto-aged %"PRId64"(%"PRId64") " |
|
"share%s %d sharesummar%s %d workinfoid%s " |
|
"%s %s", |
|
__func__, |
|
s_count_tot, s_diff_tot, |
|
(s_count_tot == 1) ? "" : "s", |
|
ss_count_tot, |
|
(ss_count_tot == 1) ? "y" : "ies", |
|
wid_count, |
|
(wid_count == 1) ? "" : "s", |
|
idrange, sharerange); |
|
} |
|
} |
|
} |
|
|
|
void _dbhash2btchash(char *hash, char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
size_t len; |
|
int i, j; |
|
|
|
// code bug |
|
if (siz < (SHA256SIZHEX + 1)) { |
|
quitfrom(1, file, func, line, |
|
"%s() passed buf too small %d (%d)", |
|
__func__, (int)siz, SHA256SIZHEX+1); |
|
} |
|
|
|
len = strlen(hash); |
|
// code bug - check this before calling |
|
if (len != SHA256SIZHEX) { |
|
quitfrom(1, file, func, line, |
|
"%s() invalid hash passed - size %d (%d)", |
|
__func__, (int)len, SHA256SIZHEX); |
|
} |
|
|
|
for (i = 0; i < SHA256SIZHEX; i++) { |
|
j = SHA256SIZHEX - 8 - (i & 0xfff8) + (i % 8); |
|
buf[i] = hash[j]; |
|
} |
|
buf[SHA256SIZHEX] = '\0'; |
|
} |
|
|
|
void _dsp_hash(char *hash, char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
char tmp[SHA256SIZHEX+1]; |
|
char *ptr; |
|
|
|
_dbhash2btchash(hash, tmp, sizeof(tmp), file, func, line); |
|
ptr = tmp; |
|
while (*ptr && *ptr == '0') |
|
ptr++; |
|
ptr -= 4; |
|
if (ptr < tmp) |
|
ptr = tmp; |
|
STRNCPYSIZ(buf, ptr, siz); |
|
} |
|
|
|
void dsp_blocks(K_ITEM *item, FILE *stream) |
|
{ |
|
char createdate_buf[DATE_BUFSIZ], expirydate_buf[DATE_BUFSIZ]; |
|
BLOCKS *b = NULL; |
|
char hash_dsp[16+1]; |
|
|
|
if (!item) |
|
fprintf(stream, "%s() called with (null) item\n", __func__); |
|
else { |
|
DATA_BLOCKS(b, item); |
|
dsp_hash(b->blockhash, hash_dsp, sizeof(hash_dsp)); |
|
tv_to_buf(&(b->createdate), createdate_buf, sizeof(createdate_buf)); |
|
tv_to_buf(&(b->expirydate), expirydate_buf, sizeof(expirydate_buf)); |
|
fprintf(stream, " hi=%d hash='%.16s' conf=%s uid=%"PRId64 |
|
" w='%s' sconf=%s cd=%s ed=%s\n", |
|
b->height, hash_dsp, b->confirmed, b->userid, |
|
b->workername, b->statsconfirmed, |
|
createdate_buf, expirydate_buf); |
|
} |
|
} |
|
|
|
// order by height asc,blockhash asc,expirydate desc |
|
cmp_t cmp_blocks(K_ITEM *a, K_ITEM *b) |
|
{ |
|
BLOCKS *ba, *bb; |
|
DATA_BLOCKS(ba, a); |
|
DATA_BLOCKS(bb, b); |
|
cmp_t c = CMP_INT(ba->height, bb->height); |
|
if (c == 0) { |
|
c = CMP_STR(ba->blockhash, bb->blockhash); |
|
if (c == 0) |
|
c = CMP_TV(bb->expirydate, ba->expirydate); |
|
} |
|
return c; |
|
} |
|
|
|
/* TODO: and make sure all block searches use these |
|
* or add new ones as required here */ |
|
|
|
// Must be R or W locked before call - gets current status (default_expiry) |
|
K_ITEM *find_blocks(int32_t height, char *blockhash) |
|
{ |
|
BLOCKS blocks; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look; |
|
|
|
blocks.height = height; |
|
STRNCPY(blocks.blockhash, blockhash); |
|
blocks.expirydate.tv_sec = default_expiry.tv_sec; |
|
blocks.expirydate.tv_usec = default_expiry.tv_usec; |
|
|
|
INIT_BLOCKS(&look); |
|
look.data = (void *)(&blocks); |
|
return find_in_ktree(blocks_root, &look, cmp_blocks, ctx); |
|
} |
|
|
|
// Must be R or W locked before call |
|
K_ITEM *find_prev_blocks(int32_t height) |
|
{ |
|
BLOCKS lookblocks, *blocks; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look, *b_item; |
|
|
|
/* TODO: For self orphaned (if that ever happens) |
|
* this will find based on blockhash order if it has two, |
|
* not NEW, blocks, which might not find the right one */ |
|
lookblocks.height = height; |
|
lookblocks.blockhash[0] = '\0'; |
|
lookblocks.expirydate.tv_sec = 0L; |
|
lookblocks.expirydate.tv_usec = 0L; |
|
|
|
INIT_BLOCKS(&look); |
|
look.data = (void *)(&lookblocks); |
|
b_item = find_before_in_ktree(blocks_root, &look, cmp_blocks, ctx); |
|
while (b_item) { |
|
DATA_BLOCKS(blocks, b_item); |
|
if (blocks->confirmed[0] != BLOCKS_NEW && |
|
CURRENT(&(blocks->expirydate))) |
|
return b_item; |
|
b_item = prev_in_ktree(ctx); |
|
} |
|
return NULL; |
|
} |
|
|
|
const char *blocks_confirmed(char *confirmed) |
|
{ |
|
switch (confirmed[0]) { |
|
case BLOCKS_NEW: |
|
return blocks_new; |
|
case BLOCKS_CONFIRM: |
|
return blocks_confirm; |
|
case BLOCKS_42: |
|
return blocks_42; |
|
case BLOCKS_ORPHAN: |
|
return blocks_orphan; |
|
} |
|
return blocks_unknown; |
|
} |
|
|
|
void zero_on_new_block() |
|
{ |
|
WORKERSTATUS *workerstatus; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM *ws_item; |
|
|
|
K_WLOCK(workerstatus_free); |
|
pool.diffacc = pool.diffinv = pool.shareacc = |
|
pool.shareinv = pool.best_sdiff = 0; |
|
ws_item = first_in_ktree(workerstatus_root, ctx); |
|
while (ws_item) { |
|
DATA_WORKERSTATUS(workerstatus, ws_item); |
|
workerstatus->diffacc = workerstatus->diffinv = |
|
workerstatus->diffsta = workerstatus->diffdup = |
|
workerstatus->diffhi = workerstatus->diffrej = |
|
workerstatus->shareacc = workerstatus->shareinv = |
|
workerstatus->sharesta = workerstatus->sharedup = |
|
workerstatus->sharehi = workerstatus->sharerej = 0.0; |
|
ws_item = next_in_ktree(ctx); |
|
} |
|
K_WUNLOCK(workerstatus_free); |
|
|
|
} |
|
|
|
/* Currently only used at the end of the startup |
|
* Will need to add locking if it's used, later, after startup completes */ |
|
void set_block_share_counters() |
|
{ |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM *ss_item, ss_look, *ws_item; |
|
WORKERSTATUS *workerstatus; |
|
SHARESUMMARY *sharesummary, looksharesummary; |
|
|
|
INIT_SHARESUMMARY(&ss_look); |
|
|
|
zero_on_new_block(); |
|
|
|
ws_item = NULL; |
|
/* From the end backwards so we can skip the workinfoid's we don't |
|
* want by jumping back to just before the current worker when the |
|
* workinfoid goes below the limit */ |
|
K_RLOCK(sharesummary_free); |
|
ss_item = last_in_ktree(sharesummary_root, ctx); |
|
while (ss_item) { |
|
DATA_SHARESUMMARY(sharesummary, ss_item); |
|
if (sharesummary->workinfoid < pool.workinfoid) { |
|
// Skip back to the next worker |
|
looksharesummary.userid = sharesummary->userid; |
|
STRNCPY(looksharesummary.workername, |
|
sharesummary->workername); |
|
looksharesummary.workinfoid = -1; |
|
ss_look.data = (void *)(&looksharesummary); |
|
ss_item = find_before_in_ktree(sharesummary_root, &ss_look, |
|
cmp_sharesummary, ctx); |
|
continue; |
|
} |
|
|
|
/* Check for user/workername change for new workerstatus |
|
* The tree has user/workername grouped together in order |
|
* so this will only be once per user/workername */ |
|
if (!ws_item || |
|
sharesummary->userid != workerstatus->userid || |
|
strcmp(sharesummary->workername, workerstatus->workername)) { |
|
/* This is to trigger a console error if it is missing |
|
* since it should always exist |
|
* However, it is simplest to simply create it |
|
* and keep going */ |
|
K_RUNLOCK(sharesummary_free); |
|
ws_item = find_workerstatus(sharesummary->userid, |
|
sharesummary->workername, |
|
__FILE__, __func__, __LINE__); |
|
if (!ws_item) { |
|
ws_item = find_create_workerstatus(sharesummary->userid, |
|
sharesummary->workername, |
|
__FILE__, __func__, __LINE__); |
|
} |
|
K_RLOCK(sharesummary_free); |
|
DATA_WORKERSTATUS(workerstatus, ws_item); |
|
} |
|
|
|
pool.diffacc += sharesummary->diffacc; |
|
pool.diffinv += sharesummary->diffsta + sharesummary->diffdup + |
|
sharesummary->diffhi + sharesummary->diffrej; |
|
workerstatus->diffacc += sharesummary->diffacc; |
|
workerstatus->diffinv += sharesummary->diffsta + sharesummary->diffdup + |
|
sharesummary->diffhi + sharesummary->diffrej; |
|
workerstatus->diffsta += sharesummary->diffsta; |
|
workerstatus->diffdup += sharesummary->diffdup; |
|
workerstatus->diffhi += sharesummary->diffhi; |
|
workerstatus->diffrej += sharesummary->diffrej; |
|
workerstatus->shareacc += sharesummary->shareacc; |
|
workerstatus->shareinv += sharesummary->sharesta + sharesummary->sharedup + |
|
sharesummary->sharehi + sharesummary->sharerej; |
|
workerstatus->sharesta += sharesummary->sharesta; |
|
workerstatus->sharedup += sharesummary->sharedup; |
|
workerstatus->sharehi += sharesummary->sharehi; |
|
workerstatus->sharerej += sharesummary->sharerej; |
|
|
|
ss_item = prev_in_ktree(ctx); |
|
} |
|
K_RUNLOCK(sharesummary_free); |
|
} |
|
|
|
/* order by height asc,userid asc,expirydate asc |
|
* i.e. only one payout amount per block per user */ |
|
cmp_t cmp_miningpayouts(K_ITEM *a, K_ITEM *b) |
|
{ |
|
MININGPAYOUTS *ma, *mb; |
|
DATA_MININGPAYOUTS(ma, a); |
|
DATA_MININGPAYOUTS(mb, b); |
|
cmp_t c = CMP_INT(ma->height, mb->height); |
|
if (c == 0) { |
|
c = CMP_BIGINT(ma->userid, mb->userid); |
|
if (c == 0) |
|
c = CMP_TV(ma->expirydate, mb->expirydate); |
|
} |
|
return c; |
|
} |
|
|
|
// order by userid asc,createdate asc,authid asc,expirydate desc |
|
cmp_t cmp_auths(K_ITEM *a, K_ITEM *b) |
|
{ |
|
AUTHS *aa, *ab; |
|
DATA_AUTHS(aa, a); |
|
DATA_AUTHS(ab, b); |
|
cmp_t c = CMP_BIGINT(aa->userid, ab->userid); |
|
if (c == 0) { |
|
c = CMP_TV(aa->createdate, ab->createdate); |
|
if (c == 0) { |
|
c = CMP_BIGINT(aa->authid, ab->authid); |
|
if (c == 0) |
|
c = CMP_TV(ab->expirydate, aa->expirydate); |
|
} |
|
} |
|
return c; |
|
} |
|
|
|
// order by poolinstance asc,createdate asc |
|
cmp_t cmp_poolstats(K_ITEM *a, K_ITEM *b) |
|
{ |
|
POOLSTATS *pa, *pb; |
|
DATA_POOLSTATS(pa, a); |
|
DATA_POOLSTATS(pb, b); |
|
cmp_t c = CMP_STR(pa->poolinstance, pb->poolinstance); |
|
if (c == 0) |
|
c = CMP_TV(pa->createdate, pb->createdate); |
|
return c; |
|
} |
|
|
|
void dsp_userstats(K_ITEM *item, FILE *stream) |
|
{ |
|
char statsdate_buf[DATE_BUFSIZ], createdate_buf[DATE_BUFSIZ]; |
|
USERSTATS *u = NULL; |
|
|
|
if (!item) |
|
fprintf(stream, "%s() called with (null) item\n", __func__); |
|
else { |
|
DATA_USERSTATS(u, item); |
|
tv_to_buf(&(u->statsdate), statsdate_buf, sizeof(statsdate_buf)); |
|
tv_to_buf(&(u->createdate), createdate_buf, sizeof(createdate_buf)); |
|
fprintf(stream, " pi='%s' uid=%"PRId64" w='%s' e=%"PRId64" Hs=%f " |
|
"Hs5m=%f Hs1hr=%f Hs24hr=%f sl=%s sc=%d sd=%s cd=%s\n", |
|
u->poolinstance, u->userid, u->workername, |
|
u->elapsed, u->hashrate, u->hashrate5m, |
|
u->hashrate1hr, u->hashrate24hr, u->summarylevel, |
|
u->summarycount, statsdate_buf, createdate_buf); |
|
} |
|
} |
|
|
|
/* order by userid asc,statsdate asc,poolinstance asc,workername asc |
|
as per required for userstats homepage summarisation */ |
|
cmp_t cmp_userstats(K_ITEM *a, K_ITEM *b) |
|
{ |
|
USERSTATS *ua, *ub; |
|
DATA_USERSTATS(ua, a); |
|
DATA_USERSTATS(ub, b); |
|
cmp_t c = CMP_BIGINT(ua->userid, ub->userid); |
|
if (c == 0) { |
|
c = CMP_TV(ua->statsdate, ub->statsdate); |
|
if (c == 0) { |
|
c = CMP_STR(ua->poolinstance, ub->poolinstance); |
|
if (c == 0) |
|
c = CMP_STR(ua->workername, ub->workername); |
|
} |
|
} |
|
return c; |
|
} |
|
|
|
/* order by userid asc,workername asc |
|
temporary tree for summing userstats when sending user homepage info */ |
|
cmp_t cmp_userstats_workername(K_ITEM *a, K_ITEM *b) |
|
{ |
|
USERSTATS *ua, *ub; |
|
DATA_USERSTATS(ua, a); |
|
DATA_USERSTATS(ub, b); |
|
cmp_t c = CMP_BIGINT(ua->userid, ub->userid); |
|
if (c == 0) |
|
c = CMP_STR(ua->workername, ub->workername); |
|
return c; |
|
} |
|
|
|
/* order by statsdate,userid asc,statsdate asc,workername asc,poolinstance asc |
|
as per required for DB summarisation */ |
|
cmp_t cmp_userstats_statsdate(K_ITEM *a, K_ITEM *b) |
|
{ |
|
USERSTATS *ua, *ub; |
|
DATA_USERSTATS(ua, a); |
|
DATA_USERSTATS(ub, b); |
|
cmp_t c = CMP_TV(ua->statsdate, ub->statsdate); |
|
if (c == 0) { |
|
c = CMP_BIGINT(ua->userid, ub->userid); |
|
if (c == 0) { |
|
c = CMP_STR(ua->workername, ub->workername); |
|
if (c == 0) |
|
c = CMP_STR(ua->poolinstance, ub->poolinstance); |
|
} |
|
} |
|
return c; |
|
} |
|
|
|
/* order by userid asc,workername asc,statsdate asc,poolinstance asc |
|
built during data load to update workerstatus at the end of the load |
|
and used during reload to discard stats already in the DB */ |
|
cmp_t cmp_userstats_workerstatus(K_ITEM *a, K_ITEM *b) |
|
{ |
|
USERSTATS *ua, *ub; |
|
DATA_USERSTATS(ua, a); |
|
DATA_USERSTATS(ub, b); |
|
cmp_t c = CMP_BIGINT(ua->userid, ub->userid); |
|
if (c == 0) { |
|
c = CMP_STR(ua->workername, ub->workername); |
|
if (c == 0) { |
|
c = CMP_TV(ua->statsdate, ub->statsdate); |
|
if (c == 0) |
|
c = CMP_STR(ua->poolinstance, ub->poolinstance); |
|
} |
|
} |
|
return c; |
|
} |
|
|
|
bool userstats_starttimeband(USERSTATS *row, tv_t *statsdate) |
|
{ |
|
char buf[DATE_BUFSIZ+1]; |
|
|
|
copy_tv(statsdate, &(row->statsdate)); |
|
// Start of this timeband |
|
switch (row->summarylevel[0]) { |
|
case SUMMARY_DB: |
|
statsdate->tv_sec -= statsdate->tv_sec % USERSTATS_DB_S; |
|
statsdate->tv_usec = 0; |
|
break; |
|
case SUMMARY_FULL: |
|
statsdate->tv_sec -= statsdate->tv_sec % USERSTATS_DB_DS; |
|
statsdate->tv_usec = 0; |
|
break; |
|
default: |
|
tv_to_buf(statsdate, buf, sizeof(buf)); |
|
// Bad userstats are not fatal |
|
LOGERR("Unknown userstats summarylevel 0x%02x '%c' " |
|
"userid %"PRId64" workername %s statsdate %s", |
|
row->summarylevel[0], row->summarylevel[0], |
|
row->userid, row->workername, buf); |
|
return false; |
|
} |
|
return true; |
|
}
|
|
|