]> git.ipfire.org Git - people/ms/libloc.git/blame_incremental - src/database.c
database: Cleanup writing AS section
[people/ms/libloc.git] / src / database.c
... / ...
CommitLineData
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
35struct 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
51LOC_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
74LOC_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
82LOC_EXPORT struct loc_database* loc_database_ref(struct loc_database* db) {
83 db->refcount++;
84
85 return db;
86}
87
88static 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
109LOC_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
117LOC_EXPORT const char* loc_database_get_vendor(struct loc_database* db) {
118 return loc_stringpool_get(db->pool, db->vendor);
119}
120
121LOC_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
131LOC_EXPORT const char* loc_database_get_description(struct loc_database* db) {
132 return loc_stringpool_get(db->pool, db->description);
133}
134
135LOC_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
145LOC_EXPORT size_t loc_database_count_as(struct loc_database* db) {
146 return db->as_count;
147}
148
149static 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
158static 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
162static void loc_database_sort_ases(struct loc_database* db) {
163 qsort(db->as, db->as_count, sizeof(*db->as), __loc_as_cmp);
164}
165
166static 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
192LOC_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
201static 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
231static 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
265static 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
299static 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
310LOC_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
340static 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
349static int loc_database_write_pool(struct loc_database* db, struct loc_database_header_v0* header, off_t* offset, FILE* f) {
350 // Save the offset of the pool section
351 DEBUG(db->ctx, "Pool starts at %jd bytes\n", *offset);
352 header->pool_offset = htonl(*offset);
353
354 // Write the pool
355 size_t pool_length = loc_stringpool_write(db->pool, f);
356 *offset += pool_length;
357
358 DEBUG(db->ctx, "Pool has a length of %zu bytes\n", pool_length);
359 header->pool_length = htonl(pool_length);
360
361 return 0;
362}
363
364static int loc_database_write_as_section(struct loc_database* db,
365 struct loc_database_header_v0* header, off_t* offset, FILE* f) {
366 DEBUG(db->ctx, "AS section starts at %jd bytes\n", *offset);
367 header->as_offset = htonl(*offset);
368
369 size_t as_length = 0;
370
371 struct loc_database_as_v0 dbas;
372 for (unsigned int i = 0; i < db->as_count; i++) {
373 // Convert AS into database format
374 loc_as_to_database_v0(db->as[i], &dbas);
375
376 // Write to disk
377 offset += fwrite(&dbas, 1, sizeof(dbas), f);
378 as_length += sizeof(dbas);
379 }
380
381 DEBUG(db->ctx, "AS section has a length of %zu bytes\n", as_length);
382 header->as_length = htonl(as_length);
383
384 return 0;
385}
386
387LOC_EXPORT int loc_database_write(struct loc_database* db, FILE* f) {
388 struct loc_database_magic magic;
389 loc_database_make_magic(db, &magic);
390
391 // Make the header
392 struct loc_database_header_v0 header;
393 header.vendor = htonl(db->vendor);
394 header.description = htonl(db->description);
395
396 int r;
397 off_t offset = 0;
398
399 // Start writing at the beginning of the file
400 r = fseek(f, 0, SEEK_SET);
401 if (r)
402 return r;
403
404 // Write the magic
405 offset += fwrite(&magic, 1, sizeof(magic), f);
406
407 // Skip the space we need to write the header later
408 r = fseek(f, sizeof(header), SEEK_CUR);
409 if (r) {
410 DEBUG(db->ctx, "Could not seek to position after header\n");
411 return r;
412 }
413 offset += sizeof(header);
414
415 // Move to next page boundary
416 while (offset % LOC_DATABASE_PAGE_SIZE > 0)
417 offset += fwrite("", 1, 1, f);
418
419 // Write pool
420 r = loc_database_write_pool(db, &header, &offset, f);
421 if (r)
422 return r;
423
424 // Write all ASes
425 r = loc_database_write_as_section(db, &header, &offset, f);
426 if (r)
427 return r;
428
429 // Write the header
430 r = fseek(f, sizeof(magic), SEEK_SET);
431 if (r)
432 return r;
433
434 offset += fwrite(&header, 1, sizeof(header), f);
435
436 return 0;
437}