]> git.ipfire.org Git - people/ms/libloc.git/blob - src/stringpool.c
test: We do not guarantee that string pointers never change
[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 LOC_EXPORT int loc_stringpool_read(struct loc_stringpool* pool, FILE* f, off_t offset, size_t length) {
238 DEBUG(pool->ctx, "Reading string pool from %zu (%zu bytes)\n", offset, length);
239
240 pool->data = pool->pos = mmap(NULL, length, PROT_READ,
241 MAP_PRIVATE, fileno(f), offset);
242 pool->max_length = length;
243
244 if (pool->data == MAP_FAILED)
245 return -errno;
246
247 return 0;
248 }
249
250 LOC_EXPORT size_t loc_stringpool_write(struct loc_stringpool* pool, FILE* f) {
251 size_t size = loc_stringpool_get_size(pool);
252
253 return fwrite(pool->data, sizeof(*pool->data), size, f);
254 }