billdb.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. /*
  2. * Copyright (c) 2019 Clementine Computing LLC.
  3. *
  4. * This file is part of PopuFare.
  5. *
  6. * PopuFare is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * PopuFare is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with PopuFare. If not, see <https://www.gnu.org/licenses/>.
  18. *
  19. */
  20. #include <sys/user.h>
  21. #include <sys/types.h>
  22. #include <sys/stat.h>
  23. #include <sys/mman.h>
  24. #include <stdio.h>
  25. #include <stdlib.h>
  26. #include <unistd.h>
  27. #include <fcntl.h>
  28. #include <string.h>
  29. #include <errno.h>
  30. #include <time.h>
  31. #include <openssl/md5.h>
  32. #include "../common/common_defs.h"
  33. #include "billdb.h"
  34. static void free_billing_node_list(billing_node *head)
  35. {
  36. billing_node *p = head;
  37. billing_node *q;
  38. while(p)
  39. {
  40. q = p;
  41. p = p->next;
  42. free(q);
  43. }
  44. }
  45. int format_new_billdb()
  46. {
  47. char blank[MEMORY_PAGE_SIZE] = {0};
  48. int i, n;
  49. int fd;
  50. fd = creat(BILLING_FILE, S_IRUSR | S_IWUSR);
  51. if( fd < 0 )
  52. {
  53. fprintf(stderr, "Cannot create billing file!\n");
  54. return FAIL_DATABASE;
  55. }
  56. n = BILLING_MAP_SIZE / MEMORY_PAGE_SIZE;
  57. for(i = 0; i < n; i++)
  58. {
  59. if( write(fd, &blank, sizeof(blank)) != sizeof(blank) )
  60. {
  61. fprintf(stderr, "Cannot write blank data to billing file!\n");
  62. close(fd);
  63. return FAIL_DATABASE;;
  64. }
  65. }
  66. close(fd);
  67. return 0;
  68. }
  69. int detach_from_billdb(billdb_context *ctx)
  70. {
  71. if(!ctx)
  72. return FAIL_PARAM;
  73. if(ctx->last_tx != NULL)
  74. {
  75. free(ctx->last_tx);
  76. }
  77. free_billing_node_list(ctx->freelist);
  78. free_billing_node_list(ctx->activelist);
  79. if(ctx->bills != NULL)
  80. {
  81. munmap(ctx->bills, BILLING_MAP_SIZE);
  82. }
  83. memset(ctx, 0, sizeof(billdb_context));
  84. if(ctx->mmap_broken)
  85. {
  86. close(ctx->billing_fd);
  87. }
  88. return 0;
  89. }
  90. int attach_to_billdb(billdb_context *ctx)
  91. {
  92. int n;
  93. int retval;
  94. struct stat st;
  95. int fd;
  96. billing_record *foo;
  97. int mmap_broken = 0;
  98. billing_node *freehead, *acthead, *q;
  99. int numfree, numact;
  100. time_t *last_tx;
  101. int i;
  102. //--------
  103. if(!ctx) //fail if we get passed a null pointer
  104. return FAIL_PARAM;
  105. //We also want to fail if we get passed a pointer to an active/in-use context...
  106. if(ctx->bills || ctx->activelist || ctx->freelist || ctx->last_tx)
  107. {
  108. return FAIL_PARAM;
  109. }
  110. //Go and stat the billing database file
  111. retval = stat(BILLING_FILE, &st);
  112. if(retval)
  113. {
  114. fprintf(stderr, "Cannot find billing file!\n");
  115. return FAIL_DATABASE;
  116. }
  117. //Make sure it is the right size...
  118. n = (st.st_size / sizeof(billing_record));
  119. if(n != NUM_BILLING_ENTRIES)
  120. {
  121. fprintf(stderr, "Billing file contains %d records, expecting %d!\n", n, NUM_BILLING_ENTRIES);
  122. return FAIL_DATABASE;;
  123. }
  124. //open the file
  125. fd = open(BILLING_FILE, O_RDWR | O_SYNC);
  126. if(fd < 0)
  127. {
  128. fprintf(stderr, "Cannot open billing file!\n");
  129. return FAIL_DATABASE;;
  130. }
  131. //mmap() the file into a pointer in our address space
  132. foo = (billing_record *) mmap(NULL, BILLING_MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  133. if( (foo == NULL) || (foo == MAP_FAILED) ) //if the MAP_SHARED option fails...
  134. {
  135. //try again with MAP_PRIVATE and see if it works...
  136. foo = (billing_record *) mmap(NULL, BILLING_MAP_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
  137. if( (foo == NULL) || (foo == MAP_FAILED) )
  138. {
  139. close(fd);
  140. fprintf(stderr, "Cannot mmap billing file! Try checking sysctl settings kernel.shmall and kernel.shmmax (return == %p errno == %d)\n",foo,errno);
  141. return FAIL_DATABASE;;
  142. }
  143. else
  144. {
  145. //set our mmap broken flag
  146. printf("mmap seems to be broken... operating in braindead file IO mode...\n");
  147. mmap_broken = 1;
  148. }
  149. }
  150. else
  151. {
  152. //close the file (we no longer need it open once it is mmap()'d) (provided mmap() isn't broken)
  153. close(fd);
  154. }
  155. last_tx = (time_t *)malloc(sizeof(time_t) * NUM_BILLING_ENTRIES);
  156. if(last_tx == NULL)
  157. {
  158. fprintf(stderr, "Cannot allocate transmit timestamps for billing log\n");
  159. munmap(foo, n * sizeof(billing_record));
  160. return FAIL_MEM;
  161. }
  162. else
  163. {
  164. memset(last_tx, 0, sizeof(time_t) * NUM_BILLING_ENTRIES);
  165. }
  166. //------
  167. freehead = acthead = q = NULL;
  168. numfree = numact = 0;
  169. //For all records in our flat file
  170. for(i=0; i < n; i++)
  171. {
  172. //if the record is not in use
  173. if(foo[i].checksum[0] == '\0')
  174. {
  175. //add it to the freelist
  176. q = (billing_node *) malloc( sizeof(billing_node) );
  177. if(!q)
  178. {
  179. free_billing_node_list(freehead);
  180. free_billing_node_list(acthead);
  181. fprintf(stderr, "Malloc returned NULL loading billing data!\n");
  182. munmap(foo, n * sizeof(billing_record));
  183. return FAIL_MEM;
  184. }
  185. else
  186. {
  187. numfree++;
  188. q->next = freehead;
  189. q->idx = i;
  190. freehead = q;
  191. }
  192. }
  193. else
  194. {
  195. //add it to the active list
  196. q = (billing_node *) malloc( sizeof(billing_node) );
  197. if(!q)
  198. {
  199. free_billing_node_list(freehead);
  200. free_billing_node_list(acthead);
  201. fprintf(stderr, "Malloc returned NULL loading billing data!\n");
  202. munmap(foo, n * sizeof(billing_record));
  203. return FAIL_MEM;
  204. }
  205. else
  206. {
  207. numact++;
  208. q->next = acthead;
  209. q->idx = i;
  210. acthead = q;
  211. }
  212. }
  213. }
  214. ctx->bills = foo;
  215. ctx->freelist = freehead;
  216. ctx->activelist = acthead;
  217. if(mmap_broken)
  218. {
  219. ctx->mmap_broken = 1;
  220. ctx->billing_fd = fd;
  221. }
  222. else
  223. {
  224. ctx->mmap_broken = 0;
  225. ctx->billing_fd = 0;
  226. }
  227. ctx->last_tx = last_tx;
  228. ctx->num_free_records = numfree;
  229. printf("Loaded %d records (%d used, %d free);\n", n, numact, numfree);
  230. return n;
  231. }
  232. static void sync_bill_change(billdb_context *ctx, int idx)
  233. {
  234. int offset;
  235. if(!ctx) //fail if we get passed a null pointer
  236. return;
  237. if(idx < 0)
  238. return;
  239. if(idx >= NUM_BILLING_ENTRIES)
  240. return;
  241. if(ctx->mmap_broken)
  242. {
  243. offset = (idx * sizeof(billing_record)) / MEMORY_PAGE_SIZE; //calculate the beginning page number
  244. offset *= MEMORY_PAGE_SIZE; //multiply by page size
  245. lseek(ctx->billing_fd, offset, SEEK_SET);
  246. write(ctx->billing_fd, ((void *)ctx->bills) + offset, MEMORY_PAGE_SIZE);
  247. }
  248. else
  249. {
  250. msync(ctx->bills, BILLING_MAP_SIZE, MS_SYNC | MS_INVALIDATE);
  251. }
  252. }
  253. static int alloc_bill(billdb_context *ctx)
  254. {
  255. billing_node *p;
  256. if(!ctx)
  257. return FAIL_PARAM;
  258. p = ctx->freelist;
  259. if(p) //this should take the freelist and look for the first free node
  260. {
  261. ctx->freelist = ctx->freelist->next; //advance the freelist to remove the first node
  262. p->next = ctx->activelist; //and roll it onto the head of the active list
  263. ctx->activelist = p;
  264. ctx->num_free_records--; //decrement the counter of free records
  265. return p->idx; //return the index of the newly allocated record in the mmap()'d billing file
  266. }
  267. else //if there are no free nodes, return failure
  268. {
  269. return FAIL_MEM;
  270. }
  271. }
  272. static int free_bill(billdb_context *ctx, int idx)
  273. {
  274. billing_node *p, *q;
  275. if(!ctx)
  276. return FAIL_PARAM;
  277. q = NULL;
  278. p = ctx->activelist;
  279. while(p) //this takes the list of active nodes
  280. {
  281. if( p->idx == idx ) //if the current node is the one requested for freeing
  282. break; //stop now
  283. q = p; //otherwise, advance the traveling pointers.
  284. p = p->next;
  285. }
  286. if(p) //if we found the node to free
  287. {
  288. if(q) //and it is NOT the list head
  289. {
  290. q->next = p->next; //snip it out of the active list
  291. }
  292. else //if it is the list head
  293. {
  294. ctx->activelist = p->next; //advance the list head to the next node
  295. }
  296. p->next = ctx->freelist; //insert this node at the head of the freelist
  297. ctx->freelist = p;
  298. memset(&ctx->bills[idx], 0, sizeof(billing_record)); //clear the corresponding record in the mmap()'d biling file
  299. ctx->last_tx[idx] = 0;
  300. ctx->num_free_records++; //increment the counter of free records
  301. return 0;
  302. }
  303. else
  304. {
  305. return WARN_NOTFOUND;
  306. }
  307. }
  308. int add_billing_entry(billdb_context *ctx, char *line)
  309. {
  310. int n, i, idx;
  311. billing_record foo = {{0},{0}};
  312. unsigned char cksum[MD5_DIGEST_LENGTH];
  313. if(!ctx)
  314. return FAIL_PARAM;
  315. if(!ctx->freelist)
  316. {
  317. return FAIL_FULL;
  318. }
  319. //Translate any mid-string linebreaks to spaces, and trim any trailing linebreaks
  320. strip_crlf(line);
  321. //See how many bytes we have left over once the message has been trimmed.
  322. n = strlen(line);
  323. //If the billing entry is too long to process, return error
  324. if( n >= BILLING_LINE_SIZE )
  325. {
  326. return FAIL_PARAM;
  327. }
  328. //If the billing entry is blank, return error
  329. if( n == 0 )
  330. {
  331. return FAIL_PARAM;
  332. }
  333. //Calculate the MD5 checksum of our message
  334. MD5( (unsigned char *) line, n, cksum);
  335. //Convert it into lower-case hex notation to match what the server does
  336. //and stick that into our temporary billing entry
  337. for(i = 0; i < MD5_DIGEST_LENGTH; i++)
  338. {
  339. sprintf(foo.checksum + (i * 2), "%02x", cksum[i]);
  340. }
  341. //Copy the data into our billing entry
  342. strcpy(foo.data, line);
  343. //allocate ourselves a billing record entry
  344. idx = alloc_bill(ctx);
  345. //If our billing entry allocation fails
  346. if(idx < 0)
  347. {
  348. return idx; //pass that error back to the caller
  349. }
  350. //and copy our entry into that slog
  351. memcpy(&ctx->bills[idx], &foo, sizeof(billing_record));
  352. //Sync the SHM
  353. sync_bill_change(ctx, idx);
  354. return 0;
  355. }
  356. int clear_billing_entry(billdb_context *ctx, char *checksum)
  357. {
  358. int i;
  359. unsigned long *a = (unsigned long *) checksum;
  360. unsigned long *b;
  361. if(!ctx)
  362. return FAIL_PARAM;
  363. for(i=0; i < NUM_BILLING_ENTRIES; i++)
  364. {
  365. b = (unsigned long *)ctx->bills[i].checksum;
  366. if(a[0] != b[0]) continue;
  367. if(a[1] != b[1]) continue;
  368. if(a[2] != b[2]) continue;
  369. if(a[3] != b[3]) continue;
  370. if(a[4] != b[4]) continue;
  371. if(a[5] != b[5]) continue;
  372. if(a[6] != b[6]) continue;
  373. if(a[7] != b[7]) continue;
  374. free_bill(ctx, i);
  375. sync_bill_change(ctx, i);
  376. break;
  377. }
  378. if(i == NUM_BILLING_ENTRIES)
  379. {
  380. return WARN_NOTFOUND;
  381. }
  382. return 0;
  383. }
  384. int next_pending_entry(billdb_context *ctx)
  385. {
  386. if(!ctx)
  387. return -1;
  388. if( !ctx->bills || !ctx->last_tx )
  389. return -1;
  390. time_t oldest = time(NULL) - DEFAULT_BILL_SYNC_RETRY;
  391. int oldest_idx = -1;
  392. billing_node *p;
  393. p = ctx->activelist;
  394. while(p)
  395. {
  396. if(ctx->last_tx[p->idx] <= oldest)
  397. {
  398. oldest = ctx->last_tx[p->idx];
  399. oldest_idx = p->idx;
  400. }
  401. p = p->next;
  402. }
  403. return oldest_idx;
  404. }
  405. /*
  406. int main()
  407. {
  408. billdb_context ctx={0};
  409. char linebuffer[1024];
  410. if( attach_to_billdb(&ctx) < 0 )
  411. {
  412. format_new_billdb();
  413. if( attach_to_billdb(&ctx) < 0 )
  414. return -1;
  415. }
  416. // add_billing_entry(&ctx, "Kaboom");
  417. // add_billing_entry(&ctx, "Splat");
  418. // clear_billing_entry(&ctx, "01677e4c0ae5468b9b8b823487f14524");
  419. reset_billing_cursor(&ctx);
  420. while(next_billing_entry(&ctx, linebuffer))
  421. {
  422. printf("\"%s\"\n", linebuffer);
  423. }
  424. return 0;
  425. }
  426. */