ringbuffer_extend() for adding extra space to the last added entry
[nobug.git] / src / nobug_ringbuffer.c
blobe30de18e8ab66f0857229a8e435f54c5d423087b
1 /*
2 This file is part of the NoBug debugging library.
4 Copyright (C)
5 2007, 2008, 2009, 2010, Christian Thaeter <ct@pipapo.org>
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, contact Christian Thaeter <ct@pipapo.org>.
20 #include <fcntl.h>
21 #include <errno.h>
22 #include <unistd.h>
23 #include <stdlib.h>
25 #include <sys/mman.h>
27 #define NOBUG_LIBNOBUG_C
28 #include "nobug.h"
30 #define NOBUG_RINGBUFFER_MAX_MSG 4094
32 #include "llist.h"
34 #if NOBUG_USE_PTHREAD
35 pthread_mutex_t nobug_ringbuffer_mutex = PTHREAD_MUTEX_INITIALIZER;
36 #endif
37 LLIST_AUTO(nobug_ringbuffer_registry);
40 //ringbuffer Ringbuffer
41 //ringbuffer ----------
42 //ringbuffer
43 //ringbuffer Implementation Details
44 //ringbuffer ~~~~~~~~~~~~~~~~~~~~~~
45 //ringbuffer
46 //ringbuffer This Ringbuffer implementation uses mmaped guard pages to implement fast wraparond
47 //ringbuffer by utilizing the MMU hardware. The guard pages mirror the beginning and end of the
48 //ringbuffer ringbuffers memory after respecive before it. By that one can write over the end
49 //ringbuffer without checking for wraparounds in one go, up to the size of the guard areas.
50 //ringbuffer
51 //ringbuffer guard before | ringbuffer memory | guard after
52 //ringbuffer aaaa|bbbbb aaaaa|bbbbb
53 //ringbuffer
54 //ringbuffer In the simpliest/smallest case this means that one page of memory is just mirrored
55 //ringbuffer 3 times after each other.
56 //ringbuffer
57 //ringbuffer
58 //ringbuffer In Memory Format
59 //ringbuffer ~~~~~~~~~~~~~~~~
60 //ringbuffer
61 //ringbuffer This implementation is meant for string data only, each stored string is delimited
62 //ringbuffer by a '\0' from the next one. There is only a write position, not a read position like
63 //ringbuffer in a queue. The write position is indicated by '\0xff' as sentinel value (for
64 //ringbuffer implementing persistent writebuffers). Even with only a write position, it is possible
65 //ringbuffer to iterate over existing entries, pop the last written entry, append to it and so on.
66 //ringbuffer But all this operations must not interleave with writes which may run over and invalidate
67 //ringbuffer any data queried from it.
68 //ringbuffer
71 struct nobug_ringbuffer*
72 nobug_ringbuffer_init (struct nobug_ringbuffer* self, size_t size, const char * name, int flags)
74 size_t pagesz = sysconf (_SC_PAGESIZE);
76 size = (size + pagesz-1) & ~(pagesz-1);
77 self->maxmsg = (NOBUG_RINGBUFFER_MAX_MSG+2 + pagesz-1) & ~(pagesz-1);
79 self->name[255] = '\0';
80 if (!name)
82 strcpy(self->name, "/tmp/nobug_ringbufferXXXXXX");
84 else
86 strncpy(self->name, name, 255);
89 int fd = mkstemp(self->name);
91 int oflags = O_RDWR|O_CREAT;
92 if (!(flags & NOBUG_RINGBUFFER_APPEND))
93 oflags |= O_TRUNC;
95 if (fd == -1 && errno == EINVAL)
96 fd = open(self->name, oflags, 0600);
98 if (fd == -1)
100 /* still error, exit here */
101 fprintf(stderr, "nobug_ringbuffer: Failed to open nobug_ringbuffer %s: %s\n", self->name, strerror(errno));
102 exit (EXIT_FAILURE);
105 if (!name || flags & NOBUG_RINGBUFFER_TEMP)
106 unlink (self->name);
108 if (!name || !(flags & NOBUG_RINGBUFFER_KEEP))
110 /* just in case we need the name later, store the first character at the end */
111 self->name[255] = self->name[0];
112 self->name[0] = 0;
115 if (ftruncate(fd, size))
117 fprintf(stderr, "nobug_ringbuffer: Failed to truncate ringbuffer to %zd: %s\n",
118 size, strerror(errno));
119 exit (EXIT_FAILURE);
121 self->size = size;
123 /* get contigous address range */
124 char * start = mmap (0, size+2*self->maxmsg, PROT_NONE, MAP_SHARED|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0);
125 if (!start)
127 fprintf(stderr, "nobug_ringbuffer: Failed to reserve %zd bytes of address space: %s\n",
128 size+2*self->maxmsg, strerror(errno));
129 exit (EXIT_FAILURE);
132 /* map the backing file */
133 self->start = mmap (start+self->maxmsg, size, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_SHARED, fd, 0);
134 if (!self->start)
136 fprintf(stderr, "nobug_ringbuffer: Failed to mmap backing file, %s\n", strerror(errno));
137 exit (EXIT_FAILURE);
139 /* map beginning after the end */
140 if (!mmap (start+self->maxmsg+size, self->maxmsg, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_SHARED, fd, 0))
142 fprintf(stderr, "nobug_ringbuffer: Failed to mmap trailing guard page, %s\n", strerror(errno));
143 exit (EXIT_FAILURE);
145 /* map end before beginning */
146 if (!mmap (start, self->maxmsg, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_SHARED, fd, size-self->maxmsg))
148 fprintf(stderr, "nobug_ringbuffer: Failed to mmap leading guard page, %s\n", strerror(errno));
149 exit (EXIT_FAILURE);
152 close (fd);
154 if (flags & NOBUG_RINGBUFFER_APPEND)
156 start = memchr (self->start, ~0, size);
157 if (!start)
158 /* new file, can't append */
159 goto init;
160 self->pos = start-1;
161 if (self->pos < self->start)
162 self->pos += size;
164 else
166 init:
167 /* set pos to the end of the file, then new writes will start at the beginning */
168 self->pos = self->start+size-1;
169 /* ~0 is used as marker for the turnaround point */
170 self->pos[1] = ~0;
173 #if NOBUG_USE_PTHREAD
174 pthread_mutex_lock (&nobug_ringbuffer_mutex);
175 llist_insert_tail (&nobug_ringbuffer_registry, llist_init (&self->node));
176 pthread_mutex_unlock (&nobug_ringbuffer_mutex);
177 #else
178 llist_insert_tail (&nobug_ringbuffer_registry, llist_init (&self->node));
179 #endif
181 return self;
185 struct nobug_ringbuffer*
186 nobug_ringbuffer_new (size_t size, const char * name, int flags)
188 struct nobug_ringbuffer* self = malloc (sizeof (struct nobug_ringbuffer));
189 if (!self)
190 return NULL;
191 return nobug_ringbuffer_init (self, size, name, flags);
195 struct nobug_ringbuffer*
196 nobug_ringbuffer_destroy (struct nobug_ringbuffer* self)
198 #if NOBUG_USE_PTHREAD
199 pthread_mutex_lock (&nobug_ringbuffer_mutex);
200 llist_unlink (&self->node);
201 pthread_mutex_unlock (&nobug_ringbuffer_mutex);
202 #else
203 llist_unlink (&self->node);
204 #endif
206 if (self->name[0])
207 unlink(self->name);
208 munmap(self->start-self->maxmsg, self->size + 2 * self->maxmsg);
209 return self;
213 void
214 nobug_ringbuffer_delete (struct nobug_ringbuffer* self)
216 free (nobug_ringbuffer_destroy (self));
220 void
221 nobug_ringbuffer_sync (struct nobug_ringbuffer* self)
223 msync (self->start, self->size, MS_SYNC);
227 void
228 nobug_ringbuffer_allsync (void)
230 #if NOBUG_USE_PTHREAD
231 pthread_mutex_lock (&nobug_ringbuffer_mutex);
232 LLIST_FOREACH(&nobug_ringbuffer_registry, n)
233 nobug_ringbuffer_sync ((struct nobug_ringbuffer*) n);
234 pthread_mutex_unlock (&nobug_ringbuffer_mutex);
235 #else
236 LLIST_FOREACH(&nobug_ringbuffer_registry, n)
237 nobug_ringbuffer_sync ((struct nobug_ringbuffer*) n);
238 #endif
243 nobug_ringbuffer_vprintf (struct nobug_ringbuffer* self, const char* fmt, va_list ap)
245 int written = vsnprintf (self->pos + 1, self->maxmsg-2, fmt, ap);
246 self->pos += (written + 1);
248 if (self->pos > self->start+self->size)
249 self->pos -= self->size;
251 self->pos[1] = ~0;
253 return written;
258 nobug_ringbuffer_printf (struct nobug_ringbuffer* self, const char* fmt, ...)
260 va_list ap;
261 va_start (ap, fmt);
262 int written = nobug_ringbuffer_vprintf (self, fmt, ap);
263 va_end(ap);
264 return written;
268 char*
269 nobug_ringbuffer_append (struct nobug_ringbuffer* self)
271 if (self->pos[-1] != 0)
272 --self->pos;
274 return self->pos;
279 resizes the current (last printed) message, the extended space filled with 'fill' characters unless
280 fill == 0, then the extended space is left untouched and expected to be properly set up by the client
281 returns 0 on failure (newsize would overflow the guard area)
284 nobug_ringbuffer_extend (struct nobug_ringbuffer* self, size_t newsize, const char fill)
286 char* prev = nobug_ringbuffer_prev (self, NULL);
288 if (prev >= self->start+self->size)
289 prev -= self->size;
291 if (prev+newsize+2 > self->start + self->size + self->maxmsg)
292 return 0;
294 if (fill)
295 memset(self->pos, fill, prev+newsize-self->pos);
297 self->pos = prev+newsize;
298 self->pos[0] = '\0';
299 self->pos[1] = ~0;
301 return 1;
305 char*
306 nobug_ringbuffer_prev (struct nobug_ringbuffer* self, char* pos)
308 if (!pos)
309 pos = self->pos;
310 else
311 --pos;
313 while(!*--pos);
314 while(*--pos);
315 if (pos < self->start)
316 pos += self->size;
318 if (pos[1] == (char)~0)
319 return NULL;
321 return pos+1;
324 char*
325 nobug_ringbuffer_next (struct nobug_ringbuffer* self, char* pos)
327 if (!pos)
328 pos = self->pos+1;
330 while (*++pos);
331 while (!*++pos);
332 if (pos > self->start+self->size)
333 pos -= self->size;
335 if (*pos == (char)~0)
336 return NULL;
338 return pos;
342 nobug_ringbuffer_save (struct nobug_ringbuffer* self, FILE* out)
344 int ret = 0;
345 int cnt;
346 char* next;
348 for (next = nobug_ringbuffer_next (self, NULL); next; next = nobug_ringbuffer_next(self, next))
350 cnt = fprintf (out,"%s\n", next);
351 if (cnt < 0)
352 return -ret-1;
353 else
354 ret += cnt;
356 return ret;
360 nobug_ringbuffer_load (struct nobug_ringbuffer* self, FILE* in)
362 int ret = 0;
363 char buf[NOBUG_RINGBUFFER_MAX_MSG];
365 while (fgets(buf, self->maxmsg, in))
367 size_t l = strlen(buf);
368 if (buf[l-1] == '\n')
369 buf[l-1] = '\0';
370 ret += nobug_ringbuffer_printf (self, "%s", buf);
372 return ret;
375 char*
376 nobug_ringbuffer_pos (struct nobug_ringbuffer* self)
378 return self->pos+1;
381 void
382 nobug_ringbuffer_pop (struct nobug_ringbuffer* self)
384 self->pos[0] = '\n'; /* clear the \0 */
385 self->pos[1] = ' '; /* clear the ~0 */
387 self->pos = strrchr(self->pos, 0);
388 if (self->pos < self->start)
389 self->pos += self->size;
391 self->pos[1] = ~0;