]> git.ipfire.org Git - location/libloc.git/blob - src/stringpool.c
stringpool: Drop function to find next offset
[location/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 <libloc/libloc.h>
26 #include <libloc/format.h>
27 #include <libloc/private.h>
28 #include <libloc/stringpool.h>
29
30 struct loc_stringpool {
31 struct loc_ctx* ctx;
32 int refcount;
33
34 // A file descriptor when we open an existing stringpool
35 int fd;
36
37 off_t offset;
38 ssize_t length;
39
40 // Mapped data (from mmap())
41 char* mmapped_data;
42
43 char* data;
44 char* pos;
45
46 char buffer[LOC_DATABASE_PAGE_SIZE];
47 };
48
49 static off_t loc_stringpool_get_offset(struct loc_stringpool* pool, const char* pos) {
50 if (pos < pool->data) {
51 errno = EFAULT;
52 return -1;
53 }
54
55 if (pos > (pool->data + pool->length)) {
56 errno = EFAULT;
57 return -1;
58 }
59
60 return pos - pool->data;
61 }
62
63 static char* __loc_stringpool_get(struct loc_stringpool* pool, off_t offset) {
64 ssize_t bytes_read;
65
66 // Check boundaries
67 if (offset < 0 || offset >= pool->length) {
68 errno = ERANGE;
69 return NULL;
70 }
71
72 // Return any data that we have in memory
73 if (pool->data)
74 return pool->data + offset;
75
76 // Otherwise read a block from file
77 bytes_read = pread(pool->fd, pool->buffer, sizeof(pool->buffer),
78 pool->offset + offset);
79
80 // Break on error
81 if (bytes_read < 0) {
82 ERROR(pool->ctx, "Could not read from string pool: %m\n");
83 return NULL;
84 }
85
86 // It is okay, if we did not read as much as we wanted, since we might be reading
87 // the last block which might be of an unknown size.
88
89 // Search for a complete string. If there is no NULL byte, the block is garbage.
90 char* end = memchr(pool->buffer, bytes_read, '\0');
91 if (!end)
92 return NULL;
93
94 // Return what's in the buffer
95 return pool->buffer;
96 }
97
98 static int loc_stringpool_grow(struct loc_stringpool* pool, size_t length) {
99 DEBUG(pool->ctx, "Growing string pool to %zu bytes\n", length);
100
101 // Save pos pointer
102 off_t pos = loc_stringpool_get_offset(pool, pool->pos);
103
104 // Reallocate data section
105 pool->data = realloc(pool->data, length);
106 if (!pool->data)
107 return 1;
108
109 pool->length = length;
110
111 // Restore pos
112 pool->pos = __loc_stringpool_get(pool, pos);
113
114 return 0;
115 }
116
117 static off_t loc_stringpool_append(struct loc_stringpool* pool, const char* string) {
118 if (!string) {
119 errno = EINVAL;
120 return -1;
121 }
122
123 DEBUG(pool->ctx, "Appending '%s' to string pool at %p\n", string, pool);
124
125 // Make sure we have enough space
126 int r = loc_stringpool_grow(pool, pool->length + strlen(string) + 1);
127 if (r)
128 return -1;
129
130 off_t offset = loc_stringpool_get_offset(pool, pool->pos);
131
132 // Copy string byte by byte
133 while (*string)
134 *pool->pos++ = *string++;
135
136 // Terminate the string
137 *pool->pos++ = '\0';
138
139 return offset;
140 }
141
142 static void loc_stringpool_free(struct loc_stringpool* pool) {
143 DEBUG(pool->ctx, "Releasing string pool %p\n", pool);
144 int r;
145
146 // Close file
147 if (pool->fd > 0)
148 close(pool->fd);
149
150 // Unmap any mapped memory
151 if (pool->mmapped_data) {
152 r = munmap(pool->mmapped_data, pool->length);
153 if (r)
154 ERROR(pool->ctx, "Error unmapping string pool: %m\n");
155
156 if (pool->mmapped_data == pool->data)
157 pool->data = NULL;
158 }
159
160 // Free any data
161 if (pool->data)
162 free(pool->data);
163
164 loc_unref(pool->ctx);
165 free(pool);
166 }
167
168 int loc_stringpool_new(struct loc_ctx* ctx, struct loc_stringpool** pool) {
169 struct loc_stringpool* p = calloc(1, sizeof(*p));
170 if (!p)
171 return 1;
172
173 p->ctx = loc_ref(ctx);
174 p->refcount = 1;
175
176 *pool = p;
177
178 return 0;
179 }
180
181 static int loc_stringpool_mmap(struct loc_stringpool* pool) {
182 // Try mmap()
183 char* p = mmap(NULL, pool->length, PROT_READ, MAP_PRIVATE, pool->fd, pool->offset);
184
185 if (p == MAP_FAILED) {
186 // Ignore if data hasn't been aligned correctly
187 if (errno == EINVAL)
188 return 0;
189
190 ERROR(pool->ctx, "Could not mmap stringpool: %m\n");
191 return 1;
192 }
193
194 // Store mapped memory area
195 pool->data = pool->mmapped_data = pool->pos = p;
196
197 return 0;
198 }
199
200 int loc_stringpool_open(struct loc_ctx* ctx, struct loc_stringpool** pool,
201 FILE* f, size_t length, off_t offset) {
202 struct loc_stringpool* p = NULL;
203
204 // Allocate a new stringpool
205 int r = loc_stringpool_new(ctx, &p);
206 if (r)
207 goto ERROR;
208
209 // Store offset and length
210 p->offset = offset;
211 p->length = length;
212
213 DEBUG(p->ctx, "Reading string pool starting from %jd (%zu bytes)\n",
214 (intmax_t)p->offset, p->length);
215
216 int fd = fileno(f);
217
218 // Copy the file descriptor
219 p->fd = dup(fd);
220 if (p->fd < 0) {
221 ERROR(ctx, "Could not duplicate file the file descriptor: %m\n");
222 r = 1;
223 goto ERROR;
224 }
225
226 // Map data into memory
227 if (p->length > 0) {
228 r = loc_stringpool_mmap(p);
229 if (r)
230 goto ERROR;
231 }
232
233 *pool = p;
234 return 0;
235
236 ERROR:
237 if (p)
238 loc_stringpool_free(p);
239
240 return r;
241 }
242
243 struct loc_stringpool* loc_stringpool_ref(struct loc_stringpool* pool) {
244 pool->refcount++;
245
246 return pool;
247 }
248
249 struct loc_stringpool* loc_stringpool_unref(struct loc_stringpool* pool) {
250 if (--pool->refcount > 0)
251 return NULL;
252
253 loc_stringpool_free(pool);
254
255 return NULL;
256 }
257
258 const char* loc_stringpool_get(struct loc_stringpool* pool, off_t offset) {
259 return __loc_stringpool_get(pool, offset);
260 }
261
262 size_t loc_stringpool_get_size(struct loc_stringpool* pool) {
263 return loc_stringpool_get_offset(pool, pool->pos);
264 }
265
266 static off_t loc_stringpool_find(struct loc_stringpool* pool, const char* s) {
267 if (!s || !*s) {
268 errno = EINVAL;
269 return -1;
270 }
271
272 off_t offset = 0;
273 while (offset < pool->length) {
274 const char* string = loc_stringpool_get(pool, offset);
275
276 // Error!
277 if (!string)
278 return 1;
279
280 // Is this a match?
281 if (strcmp(s, string) == 0)
282 return offset;
283
284 // Shift offset
285 offset += strlen(string) + 1;
286 }
287
288 // Nothing found
289 errno = ENOENT;
290 return -1;
291 }
292
293 off_t loc_stringpool_add(struct loc_stringpool* pool, const char* string) {
294 off_t offset = loc_stringpool_find(pool, string);
295 if (offset >= 0) {
296 DEBUG(pool->ctx, "Found '%s' at position %jd\n", string, (intmax_t)offset);
297 return offset;
298 }
299
300 return loc_stringpool_append(pool, string);
301 }
302
303 void loc_stringpool_dump(struct loc_stringpool* pool) {
304 off_t offset = 0;
305
306 while (offset < pool->length) {
307 const char* string = loc_stringpool_get(pool, offset);
308 if (!string)
309 return;
310
311 printf("%jd (%zu): %s\n", (intmax_t)offset, strlen(string), string);
312
313 // Shift offset
314 offset += strlen(string) + 1;
315 }
316 }
317
318 size_t loc_stringpool_write(struct loc_stringpool* pool, FILE* f) {
319 size_t size = loc_stringpool_get_size(pool);
320
321 return fwrite(pool->data, sizeof(*pool->data), size, f);
322 }