/*
* 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "../common/common_defs.h"
#include "billdb.h"
static void free_billing_node_list(billing_node *head)
{
billing_node *p = head;
billing_node *q;
while(p)
{
q = p;
p = p->next;
free(q);
}
}
int format_new_billdb()
{
char blank[MEMORY_PAGE_SIZE] = {0};
int i, n;
int fd;
fd = creat(BILLING_FILE, S_IRUSR | S_IWUSR);
if( fd < 0 )
{
fprintf(stderr, "Cannot create billing file!\n");
return FAIL_DATABASE;
}
n = BILLING_MAP_SIZE / MEMORY_PAGE_SIZE;
for(i = 0; i < n; i++)
{
if( write(fd, &blank, sizeof(blank)) != sizeof(blank) )
{
fprintf(stderr, "Cannot write blank data to billing file!\n");
close(fd);
return FAIL_DATABASE;;
}
}
close(fd);
return 0;
}
int detach_from_billdb(billdb_context *ctx)
{
if(!ctx)
return FAIL_PARAM;
if(ctx->last_tx != NULL)
{
free(ctx->last_tx);
}
free_billing_node_list(ctx->freelist);
free_billing_node_list(ctx->activelist);
if(ctx->bills != NULL)
{
munmap(ctx->bills, BILLING_MAP_SIZE);
}
memset(ctx, 0, sizeof(billdb_context));
if(ctx->mmap_broken)
{
close(ctx->billing_fd);
}
return 0;
}
int attach_to_billdb(billdb_context *ctx)
{
int n;
int retval;
struct stat st;
int fd;
billing_record *foo;
int mmap_broken = 0;
billing_node *freehead, *acthead, *q;
int numfree, numact;
time_t *last_tx;
int i;
//--------
if(!ctx) //fail if we get passed a null pointer
return FAIL_PARAM;
//We also want to fail if we get passed a pointer to an active/in-use context...
if(ctx->bills || ctx->activelist || ctx->freelist || ctx->last_tx)
{
return FAIL_PARAM;
}
//Go and stat the billing database file
retval = stat(BILLING_FILE, &st);
if(retval)
{
fprintf(stderr, "Cannot find billing file!\n");
return FAIL_DATABASE;
}
//Make sure it is the right size...
n = (st.st_size / sizeof(billing_record));
if(n != NUM_BILLING_ENTRIES)
{
fprintf(stderr, "Billing file contains %d records, expecting %d!\n", n, NUM_BILLING_ENTRIES);
return FAIL_DATABASE;;
}
//open the file
fd = open(BILLING_FILE, O_RDWR | O_SYNC);
if(fd < 0)
{
fprintf(stderr, "Cannot open billing file!\n");
return FAIL_DATABASE;;
}
//mmap() the file into a pointer in our address space
foo = (billing_record *) mmap(NULL, BILLING_MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if( (foo == NULL) || (foo == MAP_FAILED) ) //if the MAP_SHARED option fails...
{
//try again with MAP_PRIVATE and see if it works...
foo = (billing_record *) mmap(NULL, BILLING_MAP_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
if( (foo == NULL) || (foo == MAP_FAILED) )
{
close(fd);
fprintf(stderr, "Cannot mmap billing file! Try checking sysctl settings kernel.shmall and kernel.shmmax (return == %p errno == %d)\n",foo,errno);
return FAIL_DATABASE;;
}
else
{
//set our mmap broken flag
printf("mmap seems to be broken... operating in braindead file IO mode...\n");
mmap_broken = 1;
}
}
else
{
//close the file (we no longer need it open once it is mmap()'d) (provided mmap() isn't broken)
close(fd);
}
last_tx = (time_t *)malloc(sizeof(time_t) * NUM_BILLING_ENTRIES);
if(last_tx == NULL)
{
fprintf(stderr, "Cannot allocate transmit timestamps for billing log\n");
munmap(foo, n * sizeof(billing_record));
return FAIL_MEM;
}
else
{
memset(last_tx, 0, sizeof(time_t) * NUM_BILLING_ENTRIES);
}
//------
freehead = acthead = q = NULL;
numfree = numact = 0;
//For all records in our flat file
for(i=0; i < n; i++)
{
//if the record is not in use
if(foo[i].checksum[0] == '\0')
{
//add it to the freelist
q = (billing_node *) malloc( sizeof(billing_node) );
if(!q)
{
free_billing_node_list(freehead);
free_billing_node_list(acthead);
fprintf(stderr, "Malloc returned NULL loading billing data!\n");
munmap(foo, n * sizeof(billing_record));
return FAIL_MEM;
}
else
{
numfree++;
q->next = freehead;
q->idx = i;
freehead = q;
}
}
else
{
//add it to the active list
q = (billing_node *) malloc( sizeof(billing_node) );
if(!q)
{
free_billing_node_list(freehead);
free_billing_node_list(acthead);
fprintf(stderr, "Malloc returned NULL loading billing data!\n");
munmap(foo, n * sizeof(billing_record));
return FAIL_MEM;
}
else
{
numact++;
q->next = acthead;
q->idx = i;
acthead = q;
}
}
}
ctx->bills = foo;
ctx->freelist = freehead;
ctx->activelist = acthead;
if(mmap_broken)
{
ctx->mmap_broken = 1;
ctx->billing_fd = fd;
}
else
{
ctx->mmap_broken = 0;
ctx->billing_fd = 0;
}
ctx->last_tx = last_tx;
ctx->num_free_records = numfree;
printf("Loaded %d records (%d used, %d free);\n", n, numact, numfree);
return n;
}
static void sync_bill_change(billdb_context *ctx, int idx)
{
int offset;
if(!ctx) //fail if we get passed a null pointer
return;
if(idx < 0)
return;
if(idx >= NUM_BILLING_ENTRIES)
return;
if(ctx->mmap_broken)
{
offset = (idx * sizeof(billing_record)) / MEMORY_PAGE_SIZE; //calculate the beginning page number
offset *= MEMORY_PAGE_SIZE; //multiply by page size
lseek(ctx->billing_fd, offset, SEEK_SET);
write(ctx->billing_fd, ((void *)ctx->bills) + offset, MEMORY_PAGE_SIZE);
}
else
{
msync(ctx->bills, BILLING_MAP_SIZE, MS_SYNC | MS_INVALIDATE);
}
}
static int alloc_bill(billdb_context *ctx)
{
billing_node *p;
if(!ctx)
return FAIL_PARAM;
p = ctx->freelist;
if(p) //this should take the freelist and look for the first free node
{
ctx->freelist = ctx->freelist->next; //advance the freelist to remove the first node
p->next = ctx->activelist; //and roll it onto the head of the active list
ctx->activelist = p;
ctx->num_free_records--; //decrement the counter of free records
return p->idx; //return the index of the newly allocated record in the mmap()'d billing file
}
else //if there are no free nodes, return failure
{
return FAIL_MEM;
}
}
static int free_bill(billdb_context *ctx, int idx)
{
billing_node *p, *q;
if(!ctx)
return FAIL_PARAM;
q = NULL;
p = ctx->activelist;
while(p) //this takes the list of active nodes
{
if( p->idx == idx ) //if the current node is the one requested for freeing
break; //stop now
q = p; //otherwise, advance the traveling pointers.
p = p->next;
}
if(p) //if we found the node to free
{
if(q) //and it is NOT the list head
{
q->next = p->next; //snip it out of the active list
}
else //if it is the list head
{
ctx->activelist = p->next; //advance the list head to the next node
}
p->next = ctx->freelist; //insert this node at the head of the freelist
ctx->freelist = p;
memset(&ctx->bills[idx], 0, sizeof(billing_record)); //clear the corresponding record in the mmap()'d biling file
ctx->last_tx[idx] = 0;
ctx->num_free_records++; //increment the counter of free records
return 0;
}
else
{
return WARN_NOTFOUND;
}
}
int add_billing_entry(billdb_context *ctx, char *line)
{
int n, i, idx;
billing_record foo = {{0},{0}};
unsigned char cksum[MD5_DIGEST_LENGTH];
if(!ctx)
return FAIL_PARAM;
if(!ctx->freelist)
{
return FAIL_FULL;
}
//Translate any mid-string linebreaks to spaces, and trim any trailing linebreaks
strip_crlf(line);
//See how many bytes we have left over once the message has been trimmed.
n = strlen(line);
//If the billing entry is too long to process, return error
if( n >= BILLING_LINE_SIZE )
{
return FAIL_PARAM;
}
//If the billing entry is blank, return error
if( n == 0 )
{
return FAIL_PARAM;
}
//Calculate the MD5 checksum of our message
MD5( (unsigned char *) line, n, cksum);
//Convert it into lower-case hex notation to match what the server does
//and stick that into our temporary billing entry
for(i = 0; i < MD5_DIGEST_LENGTH; i++)
{
sprintf(foo.checksum + (i * 2), "%02x", cksum[i]);
}
//Copy the data into our billing entry
strcpy(foo.data, line);
//allocate ourselves a billing record entry
idx = alloc_bill(ctx);
//If our billing entry allocation fails
if(idx < 0)
{
return idx; //pass that error back to the caller
}
//and copy our entry into that slog
memcpy(&ctx->bills[idx], &foo, sizeof(billing_record));
//Sync the SHM
sync_bill_change(ctx, idx);
return 0;
}
int clear_billing_entry(billdb_context *ctx, char *checksum)
{
int i;
unsigned long *a = (unsigned long *) checksum;
unsigned long *b;
if(!ctx)
return FAIL_PARAM;
for(i=0; i < NUM_BILLING_ENTRIES; i++)
{
b = (unsigned long *)ctx->bills[i].checksum;
if(a[0] != b[0]) continue;
if(a[1] != b[1]) continue;
if(a[2] != b[2]) continue;
if(a[3] != b[3]) continue;
if(a[4] != b[4]) continue;
if(a[5] != b[5]) continue;
if(a[6] != b[6]) continue;
if(a[7] != b[7]) continue;
free_bill(ctx, i);
sync_bill_change(ctx, i);
break;
}
if(i == NUM_BILLING_ENTRIES)
{
return WARN_NOTFOUND;
}
return 0;
}
int next_pending_entry(billdb_context *ctx)
{
if(!ctx)
return -1;
if( !ctx->bills || !ctx->last_tx )
return -1;
time_t oldest = time(NULL) - DEFAULT_BILL_SYNC_RETRY;
int oldest_idx = -1;
billing_node *p;
p = ctx->activelist;
while(p)
{
if(ctx->last_tx[p->idx] <= oldest)
{
oldest = ctx->last_tx[p->idx];
oldest_idx = p->idx;
}
p = p->next;
}
return oldest_idx;
}
/*
int main()
{
billdb_context ctx={0};
char linebuffer[1024];
if( attach_to_billdb(&ctx) < 0 )
{
format_new_billdb();
if( attach_to_billdb(&ctx) < 0 )
return -1;
}
// add_billing_entry(&ctx, "Kaboom");
// add_billing_entry(&ctx, "Splat");
// clear_billing_entry(&ctx, "01677e4c0ae5468b9b8b823487f14524");
reset_billing_cursor(&ctx);
while(next_billing_entry(&ctx, linebuffer))
{
printf("\"%s\"\n", linebuffer);
}
return 0;
}
*/