]> git.ipfire.org Git - people/ms/libloc.git/blob - src/database.c
a263d0270dba16d422ea28e85d52fa49f800847f
[people/ms/libloc.git] / src / database.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 <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>
25 #include <unistd.h>
26
27 #include <loc/libloc.h>
28 #include <loc/format.h>
29
30 #include "libloc-private.h"
31 #include "as.h"
32 #include "database.h"
33 #include "stringpool.h"
34
35 struct loc_database {
36 struct loc_ctx* ctx;
37 int refcount;
38
39 FILE* file;
40 unsigned int version;
41 off_t vendor;
42 off_t description;
43
44 // ASes in the database
45 struct loc_as** as;
46 size_t as_count;
47
48 struct loc_stringpool* pool;
49 };
50
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
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
99 loc_stringpool_unref(db->pool);
100
101 // Close file
102 if (db->file)
103 fclose(db->file);
104
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
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
201 static int loc_database_read_magic(struct loc_database* db) {
202 struct loc_database_magic magic;
203
204 // Read from file
205 size_t bytes_read = fread(&magic, 1, sizeof(magic), db->file);
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
231 static int loc_database_read_as_section_v0(struct loc_database* db,
232 off_t as_offset, size_t as_length) {
233 struct loc_database_as_v0 dbobj;
234
235 // Read from the start of the section
236 int r = fseek(db->file, as_offset, SEEK_SET);
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++) {
243 size_t bytes_read = fread(&dbobj, 1, sizeof(dbobj), db->file);
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
265 static int loc_database_read_header_v0(struct loc_database* db) {
266 struct loc_database_header_v0 header;
267
268 // Read from file
269 size_t size = fread(&header, 1, sizeof(header), db->file);
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
284 int r = loc_stringpool_read(db->pool, db->file, pool_offset, pool_length);
285 if (r)
286 return r;
287
288 // AS section
289 off_t as_offset = ntohl(header.as_offset);
290 size_t as_length = ntohl(header.as_length);
291
292 r = loc_database_read_as_section_v0(db, as_offset, as_length);
293 if (r)
294 return r;
295
296 return 0;
297 }
298
299 static int loc_database_read_header(struct loc_database* db) {
300 switch (db->version) {
301 case 0:
302 return loc_database_read_header_v0(db);
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) {
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);
324 if (r)
325 return r;
326
327 // Read magic bytes
328 r = loc_database_read_magic(db);
329 if (r)
330 return r;
331
332 // Read the header
333 r = loc_database_read_header(db);
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 static void loc_database_align_page_boundary(off_t* offset, FILE* f) {
350 // Move to next page boundary
351 while (*offset % LOC_DATABASE_PAGE_SIZE > 0)
352 *offset += fwrite("", 1, 1, f);
353 }
354
355 static int loc_database_write_pool(struct loc_database* db, struct loc_database_header_v0* header, off_t* offset, FILE* f) {
356 // Save the offset of the pool section
357 DEBUG(db->ctx, "Pool starts at %jd bytes\n", *offset);
358 header->pool_offset = htonl(*offset);
359
360 // Write the pool
361 size_t pool_length = loc_stringpool_write(db->pool, f);
362 *offset += pool_length;
363
364 DEBUG(db->ctx, "Pool has a length of %zu bytes\n", pool_length);
365 header->pool_length = htonl(pool_length);
366
367 return 0;
368 }
369
370 static int loc_database_write_as_section(struct loc_database* db,
371 struct loc_database_header_v0* header, off_t* offset, FILE* f) {
372 DEBUG(db->ctx, "AS section starts at %jd bytes\n", *offset);
373 header->as_offset = htonl(*offset);
374
375 size_t as_length = 0;
376
377 struct loc_database_as_v0 dbas;
378 for (unsigned int i = 0; i < db->as_count; i++) {
379 // Convert AS into database format
380 loc_as_to_database_v0(db->as[i], &dbas);
381
382 // Write to disk
383 offset += fwrite(&dbas, 1, sizeof(dbas), f);
384 as_length += sizeof(dbas);
385 }
386
387 DEBUG(db->ctx, "AS section has a length of %zu bytes\n", as_length);
388 header->as_length = htonl(as_length);
389
390 return 0;
391 }
392
393 LOC_EXPORT int loc_database_write(struct loc_database* db, FILE* f) {
394 struct loc_database_magic magic;
395 loc_database_make_magic(db, &magic);
396
397 // Make the header
398 struct loc_database_header_v0 header;
399 header.vendor = htonl(db->vendor);
400 header.description = htonl(db->description);
401
402 int r;
403 off_t offset = 0;
404
405 // Start writing at the beginning of the file
406 r = fseek(f, 0, SEEK_SET);
407 if (r)
408 return r;
409
410 // Write the magic
411 offset += fwrite(&magic, 1, sizeof(magic), f);
412
413 // Skip the space we need to write the header later
414 r = fseek(f, sizeof(header), SEEK_CUR);
415 if (r) {
416 DEBUG(db->ctx, "Could not seek to position after header\n");
417 return r;
418 }
419 offset += sizeof(header);
420
421 loc_database_align_page_boundary(&offset, f);
422
423 // Write pool
424 r = loc_database_write_pool(db, &header, &offset, f);
425 if (r)
426 return r;
427
428 loc_database_align_page_boundary(&offset, f);
429
430 // Write all ASes
431 r = loc_database_write_as_section(db, &header, &offset, f);
432 if (r)
433 return r;
434
435 // Write the header
436 r = fseek(f, sizeof(magic), SEEK_SET);
437 if (r)
438 return r;
439
440 offset += fwrite(&header, 1, sizeof(header), f);
441
442 return 0;
443 }