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