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