]> git.ipfire.org Git - location/libloc.git/blob - src/stringpool.c
stringpool: Avoid memory leak if mmap() fails
[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 <loc/libloc.h>
26 #include <loc/format.h>
27 #include <loc/private.h>
28 #include <loc/stringpool.h>
29
30 enum loc_stringpool_mode {
31 STRINGPOOL_DEFAULT,
32 STRINGPOOL_MMAP,
33 };
34
35 struct loc_stringpool {
36 struct loc_ctx* ctx;
37 int refcount;
38
39 enum loc_stringpool_mode mode;
40
41 char* data;
42 ssize_t length;
43
44 char* pos;
45 };
46
47 static off_t loc_stringpool_get_offset(struct loc_stringpool* pool, const char* pos) {
48 if (pos < pool->data)
49 return -EFAULT;
50
51 if (pos > (pool->data + pool->length))
52 return -EFAULT;
53
54 return pos - pool->data;
55 }
56
57 static char* __loc_stringpool_get(struct loc_stringpool* pool, off_t offset) {
58 if (offset < 0 || offset >= pool->length)
59 return NULL;
60
61 return pool->data + offset;
62 }
63
64 static int loc_stringpool_grow(struct loc_stringpool* pool, size_t length) {
65 DEBUG(pool->ctx, "Growing string pool to %zu bytes\n", length);
66
67 // Save pos pointer
68 off_t pos = loc_stringpool_get_offset(pool, pool->pos);
69
70 // Reallocate data section
71 pool->data = realloc(pool->data, length);
72 if (!pool->data)
73 return -ENOMEM;
74
75 pool->length = length;
76
77 // Restore pos
78 pool->pos = __loc_stringpool_get(pool, pos);
79
80 return 0;
81 }
82
83 static off_t loc_stringpool_append(struct loc_stringpool* pool, const char* string) {
84 if (!string)
85 return -EINVAL;
86
87 DEBUG(pool->ctx, "Appending '%s' to string pool at %p\n", string, pool);
88
89 // Make sure we have enough space
90 int r = loc_stringpool_grow(pool, pool->length + strlen(string) + 1);
91 if (r) {
92 errno = r;
93 return -1;
94 }
95
96 off_t offset = loc_stringpool_get_offset(pool, pool->pos);
97
98 // Copy string byte by byte
99 while (*string)
100 *pool->pos++ = *string++;
101
102 // Terminate the string
103 *pool->pos++ = '\0';
104
105 return offset;
106 }
107
108 static void loc_stringpool_free(struct loc_stringpool* pool) {
109 DEBUG(pool->ctx, "Releasing string pool %p\n", pool);
110 int r;
111
112 switch (pool->mode) {
113 case STRINGPOOL_DEFAULT:
114 if (pool->data)
115 free(pool->data);
116 break;
117
118 case STRINGPOOL_MMAP:
119 if (pool->data) {
120 r = munmap(pool->data, pool->length);
121 if (r)
122 ERROR(pool->ctx, "Could not unmap data at %p: %s\n",
123 pool->data, strerror(errno));
124 }
125 break;
126 }
127
128 loc_unref(pool->ctx);
129 free(pool);
130 }
131
132 LOC_EXPORT int loc_stringpool_new(struct loc_ctx* ctx, struct loc_stringpool** pool) {
133 struct loc_stringpool* p = calloc(1, sizeof(*p));
134 if (!p)
135 return 1;
136
137 p->ctx = loc_ref(ctx);
138 p->refcount = 1;
139
140 // Save mode
141 p->mode = STRINGPOOL_DEFAULT;
142
143 *pool = p;
144
145 return 0;
146 }
147
148 static int loc_stringpool_mmap(struct loc_stringpool* pool, FILE* f, size_t length, off_t offset) {
149 if (pool->mode != STRINGPOOL_MMAP)
150 return -EINVAL;
151
152 DEBUG(pool->ctx, "Reading string pool starting from %jd (%zu bytes)\n", (intmax_t)offset, length);
153
154 // Map file content into memory
155 pool->data = pool->pos = mmap(NULL, length, PROT_READ,
156 MAP_PRIVATE, fileno(f), offset);
157
158 // Store size of section
159 pool->length = length;
160
161 if (pool->data == MAP_FAILED)
162 return 1;
163
164 return 0;
165 }
166
167 LOC_EXPORT int loc_stringpool_open(struct loc_ctx* ctx, struct loc_stringpool** pool,
168 FILE* f, size_t length, off_t offset) {
169 struct loc_stringpool* p = NULL;
170
171 // Allocate a new stringpool
172 int r = loc_stringpool_new(ctx, &p);
173 if (r)
174 return r;
175
176 // Change mode to mmap
177 p->mode = STRINGPOOL_MMAP;
178
179 // Map data into memory
180 if (length > 0) {
181 r = loc_stringpool_mmap(p, f, length, offset);
182 if (r) {
183 loc_stringpool_free(p);
184 return r;
185 }
186 }
187
188 *pool = p;
189 return 0;
190 }
191
192 LOC_EXPORT struct loc_stringpool* loc_stringpool_ref(struct loc_stringpool* pool) {
193 pool->refcount++;
194
195 return pool;
196 }
197
198 LOC_EXPORT struct loc_stringpool* loc_stringpool_unref(struct loc_stringpool* pool) {
199 if (--pool->refcount > 0)
200 return NULL;
201
202 loc_stringpool_free(pool);
203
204 return NULL;
205 }
206
207 static off_t loc_stringpool_get_next_offset(struct loc_stringpool* pool, off_t offset) {
208 const char* string = loc_stringpool_get(pool, offset);
209 if (!string)
210 return offset;
211
212 return offset + strlen(string) + 1;
213 }
214
215 LOC_EXPORT const char* loc_stringpool_get(struct loc_stringpool* pool, off_t offset) {
216 return __loc_stringpool_get(pool, offset);
217 }
218
219 LOC_EXPORT size_t loc_stringpool_get_size(struct loc_stringpool* pool) {
220 return loc_stringpool_get_offset(pool, pool->pos);
221 }
222
223 static off_t loc_stringpool_find(struct loc_stringpool* pool, const char* s) {
224 if (!s || !*s)
225 return -EINVAL;
226
227 off_t offset = 0;
228 while (offset < pool->length) {
229 const char* string = loc_stringpool_get(pool, offset);
230 if (!string)
231 break;
232
233 int r = strcmp(s, string);
234 if (r == 0)
235 return offset;
236
237 offset = loc_stringpool_get_next_offset(pool, offset);
238 }
239
240 return -ENOENT;
241 }
242
243 LOC_EXPORT off_t loc_stringpool_add(struct loc_stringpool* pool, const char* string) {
244 off_t offset = loc_stringpool_find(pool, string);
245 if (offset >= 0) {
246 DEBUG(pool->ctx, "Found '%s' at position %jd\n", string, (intmax_t)offset);
247 return offset;
248 }
249
250 return loc_stringpool_append(pool, string);
251 }
252
253 LOC_EXPORT void loc_stringpool_dump(struct loc_stringpool* pool) {
254 off_t offset = 0;
255
256 while (offset < pool->length) {
257 const char* string = loc_stringpool_get(pool, offset);
258 if (!string)
259 break;
260
261 printf("%jd (%zu): %s\n", (intmax_t)offset, strlen(string), string);
262
263 offset = loc_stringpool_get_next_offset(pool, offset);
264 }
265 }
266
267 LOC_EXPORT size_t loc_stringpool_write(struct loc_stringpool* pool, FILE* f) {
268 size_t size = loc_stringpool_get_size(pool);
269
270 return fwrite(pool->data, sizeof(*pool->data), size, f);
271 }