IRC bouncer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ring.c 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. /* Copyright (C) 2019 C. McEnroe <june@causal.agency>
  2. *
  3. * This program is free software: you can redistribute it and/or modify
  4. * it under the terms of the GNU General Public License as published by
  5. * the Free Software Foundation, either version 3 of the License, or
  6. * (at your option) any later version.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU General Public License
  14. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  15. */
  16. #include <assert.h>
  17. #include <err.h>
  18. #include <stdio.h>
  19. #include <stdlib.h>
  20. #include <sys/time.h>
  21. #include <sysexits.h>
  22. #include "bounce.h"
  23. static struct {
  24. size_t len;
  25. char **lines;
  26. struct timeval *times;
  27. } ring;
  28. void ringAlloc(size_t len) {
  29. if (len & (len - 1)) {
  30. errx(EX_CONFIG, "ring length must be power of two: %zu", len);
  31. }
  32. ring.lines = calloc(len, sizeof(*ring.lines));
  33. if (!ring.lines) err(EX_OSERR, "calloc");
  34. ring.times = calloc(len, sizeof(*ring.times));
  35. if (!ring.times) err(EX_OSERR, "calloc");
  36. ring.len = len;
  37. }
  38. size_t producer;
  39. void ringProduce(const char *line) {
  40. size_t i = producer++ & (ring.len - 1);
  41. if (ring.lines[i]) free(ring.lines[i]);
  42. gettimeofday(&ring.times[i], NULL);
  43. ring.lines[i] = strdup(line);
  44. if (!ring.lines[i]) err(EX_OSERR, "strdup");
  45. }
  46. struct Consumer {
  47. char *name;
  48. size_t pos;
  49. };
  50. static struct {
  51. struct Consumer *ptr;
  52. size_t cap, len;
  53. } consumers;
  54. size_t ringConsumer(const char *name) {
  55. for (size_t i = 0; i < consumers.len; ++i) {
  56. if (!strcmp(consumers.ptr[i].name, name)) return i;
  57. }
  58. if (consumers.len == consumers.cap) {
  59. consumers.cap = (consumers.cap ? consumers.cap * 2 : 8);
  60. void *ptr = realloc(
  61. consumers.ptr, sizeof(*consumers.ptr) * consumers.cap
  62. );
  63. if (!ptr) err(EX_OSERR, "realloc");
  64. consumers.ptr = ptr;
  65. }
  66. struct Consumer *consumer = &consumers.ptr[consumers.len];
  67. consumer->pos = 0;
  68. consumer->name = strdup(name);
  69. if (!consumer->name) err(EX_OSERR, "strdup");
  70. return consumers.len++;
  71. }
  72. size_t ringDiff(size_t consumer) {
  73. assert(consumer < consumers.len);
  74. return producer - consumers.ptr[consumer].pos;
  75. }
  76. const char *ringPeek(struct timeval *time, size_t consumer) {
  77. if (!ringDiff(consumer)) return NULL;
  78. if (ringDiff(consumer) > ring.len) {
  79. warnx(
  80. "consumer %s dropped %zu messages",
  81. consumers.ptr[consumer].name, ringDiff(consumer) - ring.len
  82. );
  83. consumers.ptr[consumer].pos = producer - ring.len;
  84. }
  85. size_t i = consumers.ptr[consumer].pos & (ring.len - 1);
  86. if (time) *time = ring.times[i];
  87. assert(ring.lines[i]);
  88. return ring.lines[i];
  89. }
  90. const char *ringConsume(struct timeval *time, size_t consumer) {
  91. const char *line = ringPeek(time, consumer);
  92. if (line) consumers.ptr[consumer].pos++;
  93. return line;
  94. }
  95. void ringInfo(void) {
  96. fprintf(stderr, "producer: %zu\n", producer);
  97. for (size_t i = 0; i < consumers.len; ++i) {
  98. fprintf(
  99. stderr, "consumer %s: %zu (%zu)\n",
  100. consumers.ptr[i].name,
  101. consumers.ptr[i].pos, producer - consumers.ptr[i].pos
  102. );
  103. }
  104. }
  105. static const size_t Signatures[] = {
  106. 0x0165636E756F70, // no ring size
  107. 0x0265636E756F70, // time_t only
  108. 0x0365636E756F70,
  109. };
  110. static size_t signatureVersion(size_t signature) {
  111. for (size_t i = 0; i < ARRAY_LEN(Signatures); ++i) {
  112. if (signature == Signatures[i]) return i;
  113. }
  114. errx(EX_DATAERR, "unknown file signature %zX", signature);
  115. }
  116. static int writeSize(FILE *file, size_t value) {
  117. return (fwrite(&value, sizeof(value), 1, file) ? 0 : -1);
  118. }
  119. static int writeTime(FILE *file, struct timeval time) {
  120. return (fwrite(&time, sizeof(time), 1, file) ? 0 : -1);
  121. }
  122. static int writeString(FILE *file, const char *str) {
  123. return (fwrite(str, strlen(str) + 1, 1, file) ? 0 : -1);
  124. }
  125. int ringSave(FILE *file) {
  126. if (writeSize(file, Signatures[2])) return -1;
  127. if (writeSize(file, ring.len)) return -1;
  128. if (writeSize(file, producer)) return -1;
  129. if (writeSize(file, consumers.len)) return -1;
  130. for (size_t i = 0; i < consumers.len; ++i) {
  131. if (writeString(file, consumers.ptr[i].name)) return -1;
  132. if (writeSize(file, consumers.ptr[i].pos)) return -1;
  133. }
  134. for (size_t i = 0; i < ring.len; ++i) {
  135. if (writeTime(file, ring.times[i])) return -1;
  136. }
  137. for (size_t i = 0; i < ring.len; ++i) {
  138. if (!ring.lines[i]) break;
  139. if (writeString(file, ring.lines[i])) return -1;
  140. }
  141. return 0;
  142. }
  143. static void readSize(FILE *file, size_t *value) {
  144. fread(value, sizeof(*value), 1, file);
  145. if (ferror(file)) err(EX_IOERR, "fread");
  146. if (feof(file)) err(EX_DATAERR, "unexpected eof");
  147. }
  148. static void readTime(FILE *file, struct timeval *time) {
  149. fread(time, sizeof(*time), 1, file);
  150. if (ferror(file)) err(EX_IOERR, "fread");
  151. if (feof(file)) err(EX_DATAERR, "unexpected eof");
  152. }
  153. static void readTimeT(FILE *file, time_t *time) {
  154. fread(time, sizeof(*time), 1, file);
  155. if (ferror(file)) err(EX_IOERR, "fread");
  156. if (feof(file)) err(EX_DATAERR, "unexpected eof");
  157. }
  158. static void readString(FILE *file, char **buf, size_t *cap) {
  159. ssize_t len = getdelim(buf, cap, '\0', file);
  160. if (len < 0 && !feof(file)) err(EX_IOERR, "getdelim");
  161. }
  162. void ringLoad(FILE *file) {
  163. size_t signature;
  164. fread(&signature, sizeof(signature), 1, file);
  165. if (ferror(file)) err(EX_IOERR, "fread");
  166. if (feof(file)) return;
  167. size_t version = signatureVersion(signature);
  168. size_t saveLen = 4096;
  169. if (version > 0) readSize(file, &saveLen);
  170. if (saveLen > ring.len) {
  171. errx(EX_DATAERR, "cannot load save with larger ring");
  172. }
  173. readSize(file, &producer);
  174. char *buf = NULL;
  175. size_t cap = 0;
  176. size_t len;
  177. readSize(file, &len);
  178. for (size_t i = 0; i < len; ++i) {
  179. readString(file, &buf, &cap);
  180. size_t consumer = ringConsumer(buf);
  181. readSize(file, &consumers.ptr[consumer].pos);
  182. }
  183. for (size_t i = 0; i < saveLen; ++i) {
  184. if (version < 2) {
  185. readTimeT(file, &ring.times[i].tv_sec);
  186. } else {
  187. readTime(file, &ring.times[i]);
  188. }
  189. }
  190. for (size_t i = 0; i < saveLen; ++i) {
  191. readString(file, &buf, &cap);
  192. if (feof(file)) break;
  193. ring.lines[i] = strdup(buf);
  194. if (!ring.lines[i]) err(EX_OSERR, "strdup");
  195. }
  196. free(buf);
  197. if (ring.len > saveLen) {
  198. producer %= saveLen;
  199. for (size_t i = 0; i < consumers.len; ++i) {
  200. struct Consumer *consumer = &consumers.ptr[i];
  201. consumer->pos %= saveLen;
  202. if (consumer->pos > producer) consumer->pos = 0;
  203. }
  204. }
  205. }