| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548 |
- /*
- * 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 <https://www.gnu.org/licenses/>.
- *
- */
- #include <sys/user.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <sys/mman.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <fcntl.h>
- #include <string.h>
- #include <errno.h>
- #include <time.h>
- #include <openssl/md5.h>
- #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;
- }
- */
|