]>
Commit | Line | Data |
---|---|---|
2601e83e MT |
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 <arpa/inet.h> | |
18 | #include <errno.h> | |
19 | #include <stddef.h> | |
20 | #include <stdint.h> | |
21 | #include <stdio.h> | |
22 | #include <stdlib.h> | |
23 | #include <string.h> | |
24 | #include <sys/types.h> | |
3f35869a | 25 | #include <unistd.h> |
2601e83e MT |
26 | |
27 | #include <loc/libloc.h> | |
a5db3e49 | 28 | #include <loc/format.h> |
2601e83e MT |
29 | |
30 | #include "libloc-private.h" | |
a5db3e49 | 31 | #include "as.h" |
2601e83e MT |
32 | #include "database.h" |
33 | #include "stringpool.h" | |
34 | ||
35 | struct loc_database { | |
36 | struct loc_ctx* ctx; | |
37 | int refcount; | |
38 | ||
3f35869a | 39 | FILE* file; |
2601e83e MT |
40 | unsigned int version; |
41 | off_t vendor; | |
42 | off_t description; | |
43 | ||
a5db3e49 MT |
44 | // ASes in the database |
45 | struct loc_as** as; | |
46 | size_t as_count; | |
47 | ||
2601e83e MT |
48 | struct loc_stringpool* pool; |
49 | }; | |
50 | ||
2601e83e MT |
51 | LOC_EXPORT int loc_database_new(struct loc_ctx* ctx, struct loc_database** database, size_t pool_size) { |
52 | struct loc_database* db = calloc(1, sizeof(*db)); | |
53 | if (!db) | |
54 | return -ENOMEM; | |
55 | ||
56 | // Reference context | |
57 | db->ctx = loc_ref(ctx); | |
58 | db->refcount = 1; | |
59 | ||
60 | DEBUG(db->ctx, "Database allocated at %p\n", db); | |
61 | ||
62 | // Create string pool | |
63 | int r = loc_stringpool_new(db->ctx, &db->pool, pool_size); | |
64 | if (r) { | |
65 | loc_database_unref(db); | |
66 | return r; | |
67 | } | |
68 | ||
69 | *database = db; | |
70 | ||
71 | return 0; | |
72 | } | |
73 | ||
74 | LOC_EXPORT int loc_database_open(struct loc_ctx* ctx, struct loc_database** database, FILE* f) { | |
75 | int r = loc_database_new(ctx, database, 0); | |
76 | if (r) | |
77 | return r; | |
78 | ||
79 | return loc_database_read(*database, f); | |
80 | } | |
81 | ||
82 | LOC_EXPORT struct loc_database* loc_database_ref(struct loc_database* db) { | |
83 | db->refcount++; | |
84 | ||
85 | return db; | |
86 | } | |
87 | ||
88 | static void loc_database_free(struct loc_database* db) { | |
89 | DEBUG(db->ctx, "Releasing database %p\n", db); | |
90 | ||
a5db3e49 MT |
91 | // Remove references to all ASes |
92 | if (db->as) { | |
93 | for (unsigned int i = 0; i < db->as_count; i++) { | |
94 | loc_as_unref(db->as[i]); | |
95 | } | |
96 | free(db->as); | |
97 | } | |
98 | ||
2601e83e MT |
99 | loc_stringpool_unref(db->pool); |
100 | ||
3f35869a MT |
101 | // Close file |
102 | if (db->file) | |
103 | fclose(db->file); | |
104 | ||
2601e83e MT |
105 | loc_unref(db->ctx); |
106 | free(db); | |
107 | } | |
108 | ||
109 | LOC_EXPORT struct loc_database* loc_database_unref(struct loc_database* db) { | |
110 | if (--db->refcount > 0) | |
111 | return NULL; | |
112 | ||
113 | loc_database_free(db); | |
114 | return NULL; | |
115 | } | |
116 | ||
117 | LOC_EXPORT const char* loc_database_get_vendor(struct loc_database* db) { | |
118 | return loc_stringpool_get(db->pool, db->vendor); | |
119 | } | |
120 | ||
121 | LOC_EXPORT int loc_database_set_vendor(struct loc_database* db, const char* vendor) { | |
122 | // Add the string to the string pool | |
123 | off_t offset = loc_stringpool_add(db->pool, vendor); | |
124 | if (offset < 0) | |
125 | return offset; | |
126 | ||
127 | db->vendor = offset; | |
128 | return 0; | |
129 | } | |
130 | ||
131 | LOC_EXPORT const char* loc_database_get_description(struct loc_database* db) { | |
132 | return loc_stringpool_get(db->pool, db->description); | |
133 | } | |
134 | ||
135 | LOC_EXPORT int loc_database_set_description(struct loc_database* db, const char* description) { | |
136 | // Add the string to the string pool | |
137 | off_t offset = loc_stringpool_add(db->pool, description); | |
138 | if (offset < 0) | |
139 | return offset; | |
140 | ||
141 | db->description = offset; | |
142 | return 0; | |
143 | } | |
144 | ||
a5db3e49 MT |
145 | LOC_EXPORT size_t loc_database_count_as(struct loc_database* db) { |
146 | return db->as_count; | |
147 | } | |
148 | ||
149 | static int loc_database_has_as(struct loc_database* db, struct loc_as* as) { | |
150 | for (unsigned int i = 0; i < db->as_count; i++) { | |
151 | if (loc_as_cmp(as, db->as[i]) == 0) | |
152 | return i; | |
153 | } | |
154 | ||
155 | return -1; | |
156 | } | |
157 | ||
158 | static int __loc_as_cmp(const void* as1, const void* as2) { | |
159 | return loc_as_cmp(*(struct loc_as**)as1, *(struct loc_as**)as2); | |
160 | } | |
161 | ||
162 | static void loc_database_sort_ases(struct loc_database* db) { | |
163 | qsort(db->as, db->as_count, sizeof(*db->as), __loc_as_cmp); | |
164 | } | |
165 | ||
166 | static struct loc_as* __loc_database_add_as(struct loc_database* db, struct loc_as* as) { | |
167 | // Check if AS exists already | |
168 | int i = loc_database_has_as(db, as); | |
169 | if (i >= 0) { | |
170 | loc_as_unref(as); | |
171 | ||
172 | // Select already existing AS | |
173 | as = db->as[i]; | |
174 | ||
175 | return loc_as_ref(as); | |
176 | } | |
177 | ||
178 | db->as_count++; | |
179 | ||
180 | // Make space for the new entry | |
181 | db->as = realloc(db->as, sizeof(*db->as) * db->as_count); | |
182 | ||
183 | // Add the new entry at the end | |
184 | db->as[db->as_count - 1] = loc_as_ref(as); | |
185 | ||
186 | // Sort everything | |
187 | loc_database_sort_ases(db); | |
188 | ||
189 | return as; | |
190 | } | |
191 | ||
192 | LOC_EXPORT struct loc_as* loc_database_add_as(struct loc_database* db, uint32_t number) { | |
193 | struct loc_as* as; | |
194 | int r = loc_as_new(db->ctx, db->pool, &as, number); | |
195 | if (r) | |
196 | return NULL; | |
197 | ||
198 | return __loc_database_add_as(db, as); | |
199 | } | |
200 | ||
3f35869a | 201 | static int loc_database_read_magic(struct loc_database* db) { |
2601e83e MT |
202 | struct loc_database_magic magic; |
203 | ||
204 | // Read from file | |
3f35869a | 205 | size_t bytes_read = fread(&magic, 1, sizeof(magic), db->file); |
2601e83e MT |
206 | |
207 | // Check if we have been able to read enough data | |
208 | if (bytes_read < sizeof(magic)) { | |
209 | ERROR(db->ctx, "Could not read enough data to validate magic bytes\n"); | |
210 | DEBUG(db->ctx, "Read %zu bytes, but needed %zu\n", bytes_read, sizeof(magic)); | |
211 | return -ENOMSG; | |
212 | } | |
213 | ||
214 | // Compare magic bytes | |
215 | if (memcmp(LOC_DATABASE_MAGIC, magic.magic, strlen(LOC_DATABASE_MAGIC)) == 0) { | |
216 | DEBUG(db->ctx, "Magic value matches\n"); | |
217 | ||
218 | // Parse version | |
219 | db->version = ntohs(magic.version); | |
220 | DEBUG(db->ctx, "Database version is %u\n", db->version); | |
221 | ||
222 | return 0; | |
223 | } | |
224 | ||
225 | ERROR(db->ctx, "Database format is not compatible\n"); | |
226 | ||
227 | // Return an error | |
228 | return 1; | |
229 | } | |
230 | ||
a5db3e49 | 231 | static int loc_database_read_as_section_v0(struct loc_database* db, |
3f35869a | 232 | off_t as_offset, size_t as_length) { |
a5db3e49 MT |
233 | struct loc_database_as_v0 dbobj; |
234 | ||
235 | // Read from the start of the section | |
3f35869a | 236 | int r = fseek(db->file, as_offset, SEEK_SET); |
a5db3e49 MT |
237 | if (r) |
238 | return r; | |
239 | ||
240 | // Read all ASes | |
241 | size_t as_count = as_length / sizeof(dbobj); | |
242 | for (unsigned int i = 0; i < as_count; i++) { | |
3f35869a | 243 | size_t bytes_read = fread(&dbobj, 1, sizeof(dbobj), db->file); |
a5db3e49 MT |
244 | if (bytes_read < sizeof(dbobj)) { |
245 | ERROR(db->ctx, "Could not read an AS object\n"); | |
246 | return -ENOMSG; | |
247 | } | |
248 | ||
249 | // Allocate a new AS | |
250 | struct loc_as* as; | |
251 | r = loc_as_new_from_database_v0(db->ctx, db->pool, &as, &dbobj); | |
252 | if (r) | |
253 | return r; | |
254 | ||
255 | // Attach it to the database | |
256 | as = __loc_database_add_as(db, as); | |
257 | loc_as_unref(as); | |
258 | } | |
259 | ||
260 | INFO(db->ctx, "Read %zu ASes from the database\n", db->as_count); | |
261 | ||
262 | return 0; | |
263 | } | |
264 | ||
3f35869a | 265 | static int loc_database_read_header_v0(struct loc_database* db) { |
2601e83e MT |
266 | struct loc_database_header_v0 header; |
267 | ||
268 | // Read from file | |
3f35869a | 269 | size_t size = fread(&header, 1, sizeof(header), db->file); |
2601e83e MT |
270 | |
271 | if (size < sizeof(header)) { | |
272 | ERROR(db->ctx, "Could not read enough data for header\n"); | |
273 | return -ENOMSG; | |
274 | } | |
275 | ||
276 | // Copy over data | |
277 | db->vendor = ntohl(header.vendor); | |
278 | db->description = ntohl(header.description); | |
279 | ||
280 | // Open pool | |
281 | off_t pool_offset = ntohl(header.pool_offset); | |
282 | size_t pool_length = ntohl(header.pool_length); | |
283 | ||
3f35869a | 284 | int r = loc_stringpool_read(db->pool, db->file, pool_offset, pool_length); |
2601e83e MT |
285 | if (r) |
286 | return r; | |
287 | ||
a5db3e49 MT |
288 | // AS section |
289 | off_t as_offset = ntohl(header.as_offset); | |
290 | size_t as_length = ntohl(header.as_length); | |
291 | ||
3f35869a | 292 | r = loc_database_read_as_section_v0(db, as_offset, as_length); |
a5db3e49 MT |
293 | if (r) |
294 | return r; | |
295 | ||
2601e83e MT |
296 | return 0; |
297 | } | |
298 | ||
3f35869a | 299 | static int loc_database_read_header(struct loc_database* db) { |
2601e83e MT |
300 | switch (db->version) { |
301 | case 0: | |
3f35869a | 302 | return loc_database_read_header_v0(db); |
2601e83e MT |
303 | |
304 | default: | |
305 | ERROR(db->ctx, "Incompatible database version: %u\n", db->version); | |
306 | return 1; | |
307 | } | |
308 | } | |
309 | ||
310 | LOC_EXPORT int loc_database_read(struct loc_database* db, FILE* f) { | |
3f35869a MT |
311 | // Copy the file pointer and work on that so we don't care if |
312 | // the calling function closes the file | |
313 | int fd = fileno(f); | |
314 | ||
315 | // Make a copy | |
316 | fd = dup(fd); | |
317 | ||
318 | // Retrieve a file pointer | |
319 | db->file = fdopen(fd, "r"); | |
320 | if (!db->file) | |
321 | return -errno; | |
322 | ||
323 | int r = fseek(db->file, 0, SEEK_SET); | |
2601e83e MT |
324 | if (r) |
325 | return r; | |
326 | ||
327 | // Read magic bytes | |
3f35869a | 328 | r = loc_database_read_magic(db); |
2601e83e MT |
329 | if (r) |
330 | return r; | |
331 | ||
332 | // Read the header | |
3f35869a | 333 | r = loc_database_read_header(db); |
2601e83e MT |
334 | if (r) |
335 | return r; | |
336 | ||
337 | return 0; | |
338 | } | |
339 | ||
340 | static void loc_database_make_magic(struct loc_database* db, struct loc_database_magic* magic) { | |
341 | // Copy magic bytes | |
342 | for (unsigned int i = 0; i < strlen(LOC_DATABASE_MAGIC); i++) | |
343 | magic->magic[i] = LOC_DATABASE_MAGIC[i]; | |
344 | ||
345 | // Set version | |
346 | magic->version = htons(LOC_DATABASE_VERSION); | |
347 | } | |
348 | ||
349 | LOC_EXPORT int loc_database_write(struct loc_database* db, FILE* f) { | |
350 | struct loc_database_magic magic; | |
351 | loc_database_make_magic(db, &magic); | |
352 | ||
353 | // Make the header | |
354 | struct loc_database_header_v0 header; | |
355 | header.vendor = htonl(db->vendor); | |
356 | header.description = htonl(db->description); | |
357 | ||
358 | int r; | |
359 | off_t offset = 0; | |
360 | ||
361 | // Start writing at the beginning of the file | |
362 | r = fseek(f, 0, SEEK_SET); | |
363 | if (r) | |
364 | return r; | |
365 | ||
366 | // Write the magic | |
367 | offset += fwrite(&magic, 1, sizeof(magic), f); | |
368 | ||
369 | // Skip the space we need to write the header later | |
370 | r = fseek(f, sizeof(header), SEEK_CUR); | |
371 | if (r) { | |
372 | DEBUG(db->ctx, "Could not seek to position after header\n"); | |
373 | return r; | |
374 | } | |
375 | offset += sizeof(header); | |
376 | ||
a5db3e49 MT |
377 | // Write all ASes |
378 | header.as_offset = htonl(offset); | |
379 | ||
380 | struct loc_database_as_v0 dbas; | |
381 | for (unsigned int i = 0; i < db->as_count; i++) { | |
382 | // Convert AS into database format | |
383 | loc_as_to_database_v0(db->as[i], &dbas); | |
384 | ||
385 | // Write to disk | |
386 | offset += fwrite(&dbas, 1, sizeof(dbas), f); | |
387 | } | |
388 | header.as_length = htonl(db->as_count * sizeof(dbas)); | |
389 | ||
6809d5ac MT |
390 | // Move to next page boundary |
391 | while (offset % LOC_DATABASE_PAGE_SIZE > 0) | |
392 | offset += fwrite("", 1, 1, f); | |
393 | ||
2601e83e MT |
394 | // Save the offset of the pool section |
395 | DEBUG(db->ctx, "Pool starts at %jd bytes\n", offset); | |
396 | header.pool_offset = htonl(offset); | |
397 | ||
398 | // Size of the pool | |
399 | size_t pool_length = loc_stringpool_write(db->pool, f); | |
400 | DEBUG(db->ctx, "Pool has a length of %zu bytes\n", pool_length); | |
401 | header.pool_length = htonl(pool_length); | |
402 | ||
403 | // Write the header | |
404 | r = fseek(f, sizeof(magic), SEEK_SET); | |
405 | if (r) | |
406 | return r; | |
407 | ||
408 | offset += fwrite(&header, 1, sizeof(header), f); | |
409 | ||
410 | return 0; | |
411 | } |