/*
* Copyright (c) 2019 Clementine Computing LLC.
*
* This file is part of PopuFare.
*
* PopuFare is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PopuFare is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with PopuFare. If not, see .
*
*/
#include "ruleparam_db.h"
extern int commhub_fd;
struct message_record log_msg;
int ruleparam_db_init( ruleparam_db_ctx *ctx )
{
ctx->n = 0;
ctx->head = NULL;
ctx->sync_counter = 0;
ctx->sync_every_n = 100;
ctx->n_filename_history = 4;
ctx->pos_filename_history = 0;
ctx->seq = 0;
return 0;
}
static int _chomp(char *s)
{
int n;
if (!s)
{
return -1;
}
n = strlen(s);
if (n==0)
return 0;
if (s[n-1] == '\n')
{
s[n-1] = '\0';
return n-1;
}
return n;
}
// In the consistency check, we allocate our own ruleparam_db, so we needed to expose
// this function.
//
passdb_slim_ruleparam *_alloc_ruleparam_db_nod(int id, char *name, char *param)
{
passdb_slim_ruleparam *nod;
nod = (passdb_slim_ruleparam *)malloc(sizeof(passdb_slim_ruleparam));
if (!nod)
{
fprintf(stderr, "_alloc_ruelparam_db_nod malloc failed: %s line %i\n", __FILE__, __LINE__ );
return NULL;
}
nod->id = id;
strncpy( nod->name, name, RULENAME_LEN);
strncpy( nod->param, param, PARAM_LEN);
nod->name[RULENAME_LEN-1] = '\0';
nod->param[PARAM_LEN-1] = '\0';
nod->reference_count = 0;
nod->next = NULL;
return nod;
}
ruleparam_db_ctx * ruleparam_db_alloc(void)
{
ruleparam_db_ctx *ctx;
ctx = (ruleparam_db_ctx *)malloc(sizeof(ruleparam_db_ctx));
if (!ctx)
{
fprintf(stderr, "ruleparam_db_alloc: malloc failed: %s line %i\n", __FILE__, __LINE__ );
return NULL;
}
ctx->n_filename_history = 4;
ctx->pos_filename_history = 0;
ruleparam_db_init(ctx);
return ctx;
}
int ruleparam_db_load( ruleparam_db_ctx **ctx, char *db_fn )
{
FILE *fp;
passdb_slim_ruleparam *nod, *prev=NULL;
ruleparam_db_ctx *tctx = NULL;
FILE *seq_fp;
char db_seq_fn[1024];
char buf[1024];
char name[RULENAME_LEN];
char param[PARAM_LEN];
char *chp, *chp_next;
char *token;
int r = -1;
int id;
int line_no = 0;
int k;
unsigned long long seq=0;
long long int tseq;
int rulecount=-1;
tctx = (ruleparam_db_ctx *)malloc(sizeof(ruleparam_db_ctx));
CHECK_ALLOC_FAIL( (tctx) );
ruleparam_db_init( tctx );
// Read in sequence number from file.
// Keep going if file does not exist.
//
snprintf(db_seq_fn, 128, "%s.seq", db_fn );
seq_fp = fopen( db_seq_fn, "r");
if (!seq_fp)
{
perror( db_seq_fn );
}
else
{
fgets( buf, 1024, seq_fp );
tseq = atoll( buf );
if (tseq > 0)
{
seq = (unsigned long long)tseq;
}
fclose(seq_fp);
}
fp = fopen(db_fn, "r");
if (!fp)
{
perror(db_fn);
if (tctx)
free(tctx);
return -1;
}
while (fgets(buf, 1024, fp ))
{
line_no++;
chp = buf;
if (!chp)
break;
if (!(*chp))
continue;
token = chp;
// skip initial whitespace
//
while ( (*token) && (*token == ' ') )
token++;
// skip comment
//
if (token[0] == '#')
{
/*
if (strncmp( token, "#seq: ", strlen("#seq: ")) == 0)
{
tseq = atoll( token + strlen("#seq: ") );
if (tseq > 0)
{
seq = (unsigned long long)tseq;
}
}
else
*/
if (strncmp( token, "#rulecount: ", strlen("#rulecount: ")) == 0)
{
rulecount = atoi( token + strlen("#rulecount: ") );
}
continue;
}
// skip if empty line
//
if (*token == '\n') continue;
// FIRST TOKEN (id)
//find next token (tab)
chp_next = strchr( chp, '\t' );
if (!chp_next) { goto _cleanup; }
*chp_next = '\0';
// and process
k = _chomp(token);
if (k<0) { goto _cleanup; }
if (k==0) continue;
id = atoi(token);
// SECOND TOKEN (rule)
// find next token (tab)
token = chp_next+1;
chp_next = strchr( token, '\t' );
if ( !chp_next ) { goto _cleanup; }
*chp_next = '\0';
_chomp(token);
// and process
strncpy(name, token, RULENAME_LEN);
name[RULENAME_LEN-1] = '\0';
// THIRD TOKEN (param)
// NOTE: fourth token assummed to exist, even though we don't read it
// in. By convention, the fourth token is the reference count.
//
// find next token (tab)
token = chp_next+1;
chp_next = strchr( token, '\t' );
if ( !chp_next ) { goto _cleanup; }
*chp_next = '\0';
_chomp(token);
// and process
strncpy(param, token, PARAM_LEN);
param[PARAM_LEN-1] = '\0';
nod = _alloc_ruleparam_db_nod( id, name, param );
if (tctx->head) prev->next = nod;
else tctx->head = nod;
prev = nod;
tctx->n++;
}
if (tctx->n != rulecount)
{
fprintf(stderr, "WARNING: ruleparam_db_load: read rule count (%i) != reported rule count (%i)\n", tctx->n, rulecount);
}
r = 0;
tctx->db_filename = strdup( db_fn );
tctx->seq = seq;
*ctx = tctx;
return 0;
_cleanup:
fprintf(stderr, "ERROR: ruleparam_db_load: FAIL on line %i\n", line_no);
if (tctx)
{
nod = tctx->head;
while (nod)
{
prev = nod;
nod = nod->next;
free(prev);
}
free(tctx);
}
fclose(fp);
return -1;
}
int _copy_file( char *src_fn, char *dst_fn )
{
FILE *src_fp, *dst_fp;
char buf[1024];
int read_len=0, write_len=0;
int ret=0;
if ( !(src_fp = fopen(src_fn, "r")) )
{
perror(src_fn);
return errno;
}
if ( !(dst_fp = fopen(dst_fn, "w")) )
{
fclose(src_fp);
perror(dst_fn);
return errno;
}
while (!feof(src_fp))
{
read_len = fread(buf, 1, sizeof(buf), src_fp);
if (read_len <= 0)
{
if (ferror(src_fp) != 0)
{
perror(src_fn);
ret = errno;
goto _copy_file_cleanup;
}
continue;
}
while ( (write_len = fwrite(buf, 1, read_len, dst_fp)) != read_len )
{
perror(dst_fn);
ret = errno;
goto _copy_file_cleanup;
}
}
_copy_file_cleanup:
fclose(src_fp);
fclose(dst_fp);
return ret;
}
int ruleparam_db_seq_save( ruleparam_db_ctx *ctx )
{
FILE *seq_fp;
char tmp_fn[512];
char seq_fn[512];
// After ruleparam.db has been synchronized, finally
// write out sequence number in 'ruleparam.db.seq' file.
//
snprintf(tmp_fn, 512, "%s.seq.tmp", ctx->db_filename );
snprintf(seq_fn, 512, "%s.seq", ctx->db_filename );
seq_fp = fopen( tmp_fn, "w" );
if (seq_fp)
{
fprintf(seq_fp, "%llu", ctx->seq);
fclose(seq_fp);
sync();
rename( tmp_fn, seq_fn );
sync();
}
else
{
perror( tmp_fn );
return -1;
}
return 0;
}
int ruleparam_db_save( ruleparam_db_ctx *ctx )
{
FILE *fp;
passdb_slim_ruleparam *nod;
struct tm tm_tim;
struct timeval timval;
char str_tim[64];
char tmp_fn[1024];
char log_fn[1024];
int fn_num = 0;
int rule_count=0;
if ( ctx->n_filename_history > 1 )
{
if (ctx->pos_filename_history < 0)
{
ctx->pos_filename_history = 0;
}
else
{
ctx->pos_filename_history++;
ctx->pos_filename_history %= ctx->n_filename_history;
}
fn_num = ctx->pos_filename_history;
}
sprintf(tmp_fn, "%s.%i.tmp", ctx->db_filename, fn_num);
sprintf(log_fn, "%s.%i", ctx->db_filename, fn_num);
gettimeofday(&timval, NULL);
localtime_r(&(timval.tv_sec), &tm_tim);
asctime_r(&tm_tim, str_tim);
fp = fopen(tmp_fn, "w");
if (!fp)
{
perror(tmp_fn);
format_log_message(&log_msg, LOGLEVEL_ERROR, "Ruleparam dabase open error (%s)", tmp_fn);
if (commhub_fd >= 0)
{
send_message(commhub_fd, &log_msg);
}
return -1;
}
fprintf(fp, "# %s", str_tim);
fprintf(fp, "# Tabs are a necessary field delimiter.\n");
fprintf(fp, "#\n# InternalID,RuleName,RuleParam,ReferenceCount\n");
fprintf(fp, "# Note: The reference count in this file is only used for debugging purposes.\n");
fprintf(fp, "#seq: %llu\n", ctx->seq);
fprintf(fp, "#\n#\n");
nod = ctx->head;
while (nod)
{
fprintf(fp, "%i\t%s\t%s\t%i\n", nod->id, nod->name, nod->param, nod->reference_count);
nod = nod->next;
rule_count++;
}
fprintf(fp, "#rulecount: %i\n", rule_count);
fclose(fp);
sync();
if (_copy_file( tmp_fn, log_fn ) < 0)
{
format_log_message(&log_msg, LOGLEVEL_ERROR, "Ruleparam dabase copy error (%s -> %s)", tmp_fn, log_fn);
if (commhub_fd >= 0)
{
send_message(commhub_fd, &log_msg);
}
return errno;
}
sync();
rename( tmp_fn, ctx->db_filename );
sync();
ruleparam_db_seq_save( ctx );
return 0;
}
int format_new_ruleparamdb( char *fn )
{
ruleparam_db_ctx ctx;
ruleparam_db_init(&ctx);
//return ruleparam_db_save( &ctx, fn );
return ruleparam_db_save( &ctx );
}
int ruleparam_db_add( ruleparam_db_ctx *ctx, char *name, char *param)
{
int id;
passdb_slim_ruleparam *prev = NULL, *nod ;
if (ctx->n == RULEPARAM_DB_MAX)
return RULEPARAM_DB_FULL;
id = ruleparam_db_find( ctx, name, param );
if (id >= 0)
return RULEPARAM_DB_DUP;
id = ruleparam_db_find_free_id( ctx );
if (id < 0)
return id;
nod = _alloc_ruleparam_db_nod( id, name, param);
ctx->n++;
if (!(ctx->head))
{
ctx->head = nod;
return 0;
}
prev = ctx->head;
while(prev->next) prev = prev->next;
prev->next = nod;
return id;
}
int ruleparam_db_remove( ruleparam_db_ctx *ctx, int id)
{
passdb_slim_ruleparam *prev = NULL, *nod ;
nod = ctx->head;
while (nod)
{
if (nod->id == id)
{
if (prev) prev->next = nod->next;
else ctx->head = nod->next;
free(nod);
ctx->n--;
return 0;
}
prev = nod;
nod = nod->next;
}
return RULEPARAM_DB_NOT_FOUND;
}
// Return id of found rule record.
// RULEPARAM_DB_NOT_FOUND if none found.
//
int ruleparam_db_find( ruleparam_db_ctx *ctx, char *name, char *param )
{
passdb_slim_ruleparam *nod;
nod = ctx->head;
while (nod)
{
if ( (strncmp( nod->name, name, RULENAME_LEN ) == 0) &&
(strncmp( nod->param, param, PARAM_LEN ) == 0) )
return nod->id;
nod = nod->next;
}
return RULEPARAM_DB_NOT_FOUND;
}
// Return reference count of rule give as (name,param) string pair
//
int ruleparam_db_reference_count( ruleparam_db_ctx *ctx, char *name, char *param )
{
passdb_slim_ruleparam *nod;
nod = ctx->head;
while (nod)
{
if ( (strncmp( nod->name, name, RULENAME_LEN ) == 0) &&
(strncmp( nod->param, param, PARAM_LEN ) == 0) )
return nod->reference_count;
nod = nod->next;
}
return RULEPARAM_DB_NOT_FOUND;
}
// Populate name and param from rule id, if found
//
int ruleparam_db_get( char *name, char *param, ruleparam_db_ctx *ctx, int id )
{
passdb_slim_ruleparam *nod;
nod = ctx->head;
while (nod)
{
if (nod->id == id)
{
memcpy( name, nod->name, RULENAME_LEN );
memcpy( param, nod->param, PARAM_LEN );
name[RULENAME_LEN-1] = '\0';
name[PARAM_LEN-1] = '\0';
return 0;
}
nod = nod->next;
}
return RULEPARAM_DB_NOT_FOUND;
}
static int _icmp( const void *a, const void *b)
{
return (*((int *)a)) - (*((int *)b)) ;
}
// Take all id's, put them into an array.
// Sort it.
// Find the first non colliding entry starting
// at 0.
//
int ruleparam_db_find_free_id( ruleparam_db_ctx *ctx )
{
int i, n, pos=0;
int *p;
passdb_slim_ruleparam *nod;
n = ctx->n;
if (n == RULEPARAM_DB_MAX)
return RULEPARAM_DB_FULL;
if (n == 0)
return 0;
p = (int *)malloc(sizeof(int)*n);
CHECK_ALLOC_FAIL( (p) );
nod = ctx->head;
while (nod)
{
p[pos++] = nod->id;
nod = nod->next;
}
qsort(p, n, sizeof(int), _icmp);
i=0;
pos=0;
while ( (in);
nod = ctx->head;
while (nod)
{
printf(" (%i) '%s' '%s'\n", nod->id, nod->name, nod->param );
nod = nod->next;
}
printf("\n");
}
passdb_slim_ruleparam *ruleparam_db_get_node( ruleparam_db_ctx *ctx, int id )
{
passdb_slim_ruleparam *nod;
nod = ctx->head;
while (nod)
{
if (nod->id == id )
return nod;
nod = nod->next;
}
return NULL;
}
int ruleparam_db_update( ruleparam_db_ctx *ctx, char *name, char *param, int delta_ref_count )
{
int id;
int r;
passdb_slim_ruleparam *nod = NULL;
id = ruleparam_db_find( ctx, name, param );
// If it's not found, create it and update reference count
//
if ( id == RULEPARAM_DB_NOT_FOUND )
{
if (delta_ref_count < 0)
{
goto _ruleparam_db_update_sanity_error;
}
id = ruleparam_db_add( ctx, name, param );
if (id < 0)
return id;
nod = ruleparam_db_get_node( ctx, id );
nod->reference_count += delta_ref_count;
//ruleparam_db_save( ctx, ctx->db_filename );
ruleparam_db_save( ctx );
return id;
}
// Rule record was found, update reference count
//
nod = ruleparam_db_get_node( ctx, id );
nod->reference_count += delta_ref_count;
// check for sanity
//
if (nod->reference_count < 0)
{
goto _ruleparam_db_update_sanity_error;
}
// simply return if there are more references to this record
//
if (nod->reference_count > 0)
{
return id;
}
// otherwise we need to remove this rule record
//
r = ruleparam_db_remove( ctx, id );
if (r < 0)
goto _ruleparam_db_update_sanity_error;
//ruleparam_db_save( ctx, ctx->db_filename );
ruleparam_db_save( ctx );
return r;
_ruleparam_db_update_sanity_error:
fprintf(stderr, "ERROR: sanity! ruleparam_db_update: id %i (%s,%s), delta_ref_count: %i, [ nod(%p).reference_count:%i ] \n",
id, name, param, delta_ref_count,
nod, nod ? nod->reference_count : 0 );
fprintf(stdout, "ERROR: sanity! ruleparam_db_update: id %i (%s,%s), delta_ref_count: %i, [ nod(%p).reference_count:%i ] \n",
id, name, param, delta_ref_count,
nod, nod ? nod->reference_count : 0 );
return RULEPARAM_DB_SANITY;
}
// Remove entries that have zero references
//
int ruleparam_db_clean (ruleparam_db_ctx *ctx)
{
passdb_slim_ruleparam *nod;
passdb_slim_ruleparam *prv = NULL;
passdb_slim_ruleparam *nex ;
nod = ctx->head;
while (nod)
{
nex = nod->next;
if (nod->reference_count == 0)
{
if (prv) prv->next = nex;
else ctx->head = nex;
free(nod);
ctx->n--;
}
else
{
prv = nod;
}
nod = nex;
}
if (ctx->n == 0)
ctx->head = NULL;
return 0;
}
int ruleparam_db_consistency_check( ruleparam_db_ctx *ctx )
{
int n=0;
passdb_slim_ruleparam *nod;
if (!ctx)
{
printf("ruleparam_db_consistenc_check: error, ctx is null\n");
return 0;
}
if ((ctx->n > 0) && (!(ctx->head)))
{
printf("ruleparam_db_consistenc_check: error, ctx->n > 0 (%i) but ctx->head is null \n", ctx->n);
}
nod = ctx->head;
while(nod)
{
if (nod->name[0] == '\0')
{
printf("ruleparam_db_consistenc_check: error, node id %i (pos %i) name is empty\n", nod->id, n);
return 0;
}
if (nod->reference_count <= 0)
{
printf("ruleparam_db_consistenc_check: error, node id %i (pos %i) (%s,%s) reference count (%i) <= 0 \n", nod->id, n, nod->name, nod->param, nod->reference_count);
return 0;
}
n++;
nod = nod->next;
}
if (n != ctx->n)
{
printf("ruleparam_db_consistenc_check: error, ctx->n (%i) != list length (%i) \n", ctx->n, n);
return 0;
}
return 1;
}
int ruleparam_db_debug_dump( ruleparam_db_ctx *ctx)
{
int count=0;
passdb_slim_ruleparam *nod;
printf("ruleparam_db_ctx:\n");
printf(" n: %i\n", ctx->n);
printf(" db_filename: %s\n", ctx->db_filename);
nod = ctx->head;
while (nod)
{
printf(" {%i} id: %i, reference_count: %i, name: %s, param: %s\n",
count++, nod->id, nod->reference_count, nod->name, nod->param );
nod = nod->next;
}
printf("\n");
return 0;
}
int ruleparam_db_free( ruleparam_db_ctx *ctx )
{
passdb_slim_ruleparam *nod, *prev = NULL;
nod = ctx->head;
while (nod)
{
prev = nod;
nod = nod->next;
free(prev);
}
if (ctx->db_filename)
free(ctx->db_filename);
free(ctx);
return 0;
}
// Periodically refresh ruleparam_db, mostly for debugging purposes
// so we can see a somewhat recent snapshot of reference counts.
//
// ruleparam updates through the 'ruleparam_db_update' function
// will automatically write to the ruleparam_db file if a deletion
// or insertion happened.
//
int ruleparam_db_rate_limited_sync( ruleparam_db_ctx *ctx )
{
int retval = 0;
if (ctx->sync_every_n < 1)
{
ctx->sync_counter++;
return retval;
}
if ((ctx->sync_counter % ctx->sync_every_n) == 0)
{
ruleparam_db_save( ctx );
retval = 1;
}
// Else do a sequence number save regardless
//
else
{
ruleparam_db_seq_save( ctx );
sync();
}
ctx->sync_counter++;
return retval;
}
/*
int main(int argc, char **argv)
{
int id=-1, k;
char name[64], param[64];
name[0] = '\0';
param[0] = '\0';
ruleparam_db_ctx *ctx;
ruleparam_db_load(&ctx, RULEPARAM_DB_FILE);
ruleparam_db_dump(ctx);
ruleparam_db_add( ctx, "myrule", "myparam" );
id = ruleparam_db_add( ctx, "myrule0", "myparam" );
ruleparam_db_add( ctx, "myrule", "myparam0" );
ruleparam_db_add( ctx, "myrule0", "myparam0" );
ruleparam_db_dump(ctx);
printf("removing %i\n", id);
printf("\n\n");
ruleparam_db_remove( ctx, id );
ruleparam_db_dump(ctx);
ruleparam_db_save(ctx, RULEPARAM_DB_FILE);
id = ruleparam_db_find(ctx, "myrule0", "myparam");
printf("got %i\n", id);
k = ruleparam_db_get(name, param, ctx, id);
printf(" (%i) %s %s\n", k, name, param);
}
*/