/* * 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); } */