database: Add function to align to page boundaries
[people/ms/libloc.git] / src / stringpool.c
1 /*
2         libloc - A library to determine the location of someone on the Internet
3
4         Copyright (C) 2017 IPFire Development Team <info@ipfire.org>
5
6         This library is free software; you can redistribute it and/or
7         modify it under the terms of the GNU Lesser General Public
8         License as published by the Free Software Foundation; either
9         version 2.1 of the License, or (at your option) any later version.
10
11         This library 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 GNU
14         Lesser General Public License for more details.
15 */
16
17 #include <errno.h>
18 #include <stddef.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <sys/mman.h>
23 #include <unistd.h>
24
25 #include <loc/libloc.h>
26 #include <loc/format.h>
27 #include "libloc-private.h"
28 #include "stringpool.h"
29
30 struct loc_stringpool {
31         struct loc_ctx* ctx;
32
33         int refcount;
34         char* data;
35         char* pos;
36
37         ssize_t max_length;
38 };
39
40 static int loc_stringpool_deallocate(struct loc_stringpool* pool) {
41         if (pool->data) {
42                 int r = munmap(pool->data, pool->max_length);
43                 if (r) {
44                         ERROR(pool->ctx, "Could not unmap data at %p: %s\n",
45                                 pool->data, strerror(errno));
46
47                         return r;
48                 }
49         }
50
51         return 0;
52 }
53
54 static int loc_stringpool_allocate(struct loc_stringpool* pool, size_t length) {
55         // Drop old data
56         int r = loc_stringpool_deallocate(pool);
57         if (r)
58                 return r;
59
60         pool->max_length = length;
61
62         // Align to page size
63         while (pool->max_length % sysconf(_SC_PAGE_SIZE) > 0)
64                 pool->max_length++;
65
66         DEBUG(pool->ctx, "Allocating pool of %zu bytes\n", pool->max_length);
67
68         // Allocate some memory
69         pool->data = pool->pos = mmap(NULL, pool->max_length,
70                 PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
71
72         if (pool->data == MAP_FAILED) {
73                 DEBUG(pool->ctx, "%s\n", strerror(errno));
74                 return -errno;
75         }
76
77         DEBUG(pool->ctx, "Allocated pool at %p\n", pool->data);
78
79         return 0;
80 }
81
82 LOC_EXPORT int loc_stringpool_new(struct loc_ctx* ctx, struct loc_stringpool** pool, size_t max_length) {
83         struct loc_stringpool* p = calloc(1, sizeof(*p));
84         if (!p)
85                 return -ENOMEM;
86
87         p->ctx = loc_ref(ctx);
88         p->refcount = 1;
89
90         // Allocate the data section
91         if (max_length > 0) {
92                 int r = loc_stringpool_allocate(p, max_length);
93                 if (r) {
94                         loc_stringpool_unref(p);
95                         return r;
96                 }
97         }
98
99         DEBUG(p->ctx, "String pool allocated at %p\n", p);
100         DEBUG(p->ctx, "  Maximum size: %zu bytes\n", p->max_length);
101         *pool = p;
102
103         return 0;
104 }
105
106 LOC_EXPORT struct loc_stringpool* loc_stringpool_ref(struct loc_stringpool* pool) {
107         pool->refcount++;
108
109         return pool;
110 }
111
112 static void loc_stringpool_free(struct loc_stringpool* pool) {
113         DEBUG(pool->ctx, "Releasing string pool %p\n", pool);
114
115         loc_stringpool_deallocate(pool);
116         loc_unref(pool->ctx);
117         free(pool);
118 }
119
120 LOC_EXPORT struct loc_stringpool* loc_stringpool_unref(struct loc_stringpool* pool) {
121         if (--pool->refcount > 0)
122                 return NULL;
123
124         loc_stringpool_free(pool);
125
126         return NULL;
127 }
128
129 static off_t loc_stringpool_get_offset(struct loc_stringpool* pool, const char* pos) {
130         if (pos < pool->data)
131                 return -EFAULT;
132
133         if (pos > (pool->data + pool->max_length))
134                 return -EFAULT;
135
136         return pos - pool->data;
137 }
138
139 static off_t loc_stringpool_get_next_offset(struct loc_stringpool* pool, off_t offset) {
140         const char* string = loc_stringpool_get(pool, offset);
141
142         return offset + strlen(string) + 1;
143 }
144
145 static size_t loc_stringpool_space_left(struct loc_stringpool* pool) {
146         return pool->max_length - loc_stringpool_get_size(pool);
147 }
148
149 LOC_EXPORT const char* loc_stringpool_get(struct loc_stringpool* pool, off_t offset) {
150         if (offset >= (ssize_t)pool->max_length)
151                 return NULL;
152
153         const char* string = pool->data + offset;
154
155         // If the string is empty, we have reached the end
156         if (!*string)
157                 return NULL;
158
159         return string;
160 }
161
162 LOC_EXPORT size_t loc_stringpool_get_size(struct loc_stringpool* pool) {
163         return loc_stringpool_get_offset(pool, pool->pos);
164 }
165
166 static off_t loc_stringpool_find(struct loc_stringpool* pool, const char* s) {
167         if (!s || !*s)
168                 return -EINVAL;
169
170         off_t offset = 0;
171         while (offset < pool->max_length) {
172                 const char* string = loc_stringpool_get(pool, offset);
173                 if (!string)
174                         break;
175
176                 int r = strcmp(s, string);
177                 if (r == 0)
178                         return offset;
179
180                 offset = loc_stringpool_get_next_offset(pool, offset);
181         }
182
183         return -ENOENT;
184 }
185
186 static off_t loc_stringpool_append(struct loc_stringpool* pool, const char* string) {
187         if (!string || !*string)
188                 return -EINVAL;
189
190         DEBUG(pool->ctx, "Appending '%s' to string pool at %p\n", string, pool);
191
192         // Check if we have enough space left
193         size_t l = strlen(string) + 1;
194         if (l > loc_stringpool_space_left(pool)) {
195                 DEBUG(pool->ctx, "Not enough space to append '%s'\n", string);
196                 DEBUG(pool->ctx, "  Need %zu bytes but only have %zu\n", l, loc_stringpool_space_left(pool));
197                 return -ENOSPC;
198         }
199
200         off_t offset = loc_stringpool_get_offset(pool, pool->pos);
201
202         // Copy string byte by byte
203         while (*string && loc_stringpool_space_left(pool) > 1) {
204                 *pool->pos++ = *string++;
205         }
206
207         // Terminate the string
208         *pool->pos++ = '\0';
209
210         return offset;
211 }
212
213 LOC_EXPORT off_t loc_stringpool_add(struct loc_stringpool* pool, const char* string) {
214         off_t offset = loc_stringpool_find(pool, string);
215         if (offset >= 0) {
216                 DEBUG(pool->ctx, "Found '%s' at position %jd\n", string, offset);
217                 return offset;
218         }
219
220         return loc_stringpool_append(pool, string);
221 }
222
223 LOC_EXPORT void loc_stringpool_dump(struct loc_stringpool* pool) {
224         off_t offset = 0;
225
226         while (offset < pool->max_length) {
227                 const char* string = loc_stringpool_get(pool, offset);
228                 if (!string)
229                         break;
230
231                 printf("%jd (%zu): %s\n", offset, strlen(string), string);
232
233                 offset = loc_stringpool_get_next_offset(pool, offset);
234         }
235 }
236
237 #include <assert.h>
238
239 LOC_EXPORT int loc_stringpool_read(struct loc_stringpool* pool, FILE* f, off_t offset, size_t length) {
240         DEBUG(pool->ctx, "Reading string pool from %zu (%zu bytes)\n", offset, length);
241
242         pool->data = pool->pos = mmap(NULL, length, PROT_READ,
243                 MAP_PRIVATE, fileno(f), offset);
244         pool->max_length = length;
245
246         if (pool->data == MAP_FAILED)
247                 return -errno;
248
249         return 0;
250 }
251
252 LOC_EXPORT size_t loc_stringpool_write(struct loc_stringpool* pool, FILE* f) {
253         size_t size = loc_stringpool_get_size(pool);
254
255         return fwrite(pool->data, sizeof(*pool->data), size, f);
256 }