]> git.ipfire.org Git - people/ms/libloc.git/blame - src/database.c
database: Copy the file pointer so we can keep the file open
[people/ms/libloc.git] / src / database.c
CommitLineData
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
35struct 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
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
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
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
a5db3e49
MT
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
3f35869a 201static 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 231static 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 265static 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 299static 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
310LOC_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
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
349LOC_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
2601e83e
MT
390 // Save the offset of the pool section
391 DEBUG(db->ctx, "Pool starts at %jd bytes\n", offset);
392 header.pool_offset = htonl(offset);
393
394 // Size of the pool
395 size_t pool_length = loc_stringpool_write(db->pool, f);
396 DEBUG(db->ctx, "Pool has a length of %zu bytes\n", pool_length);
397 header.pool_length = htonl(pool_length);
398
399 // Write the header
400 r = fseek(f, sizeof(magic), SEEK_SET);
401 if (r)
402 return r;
403
404 offset += fwrite(&header, 1, sizeof(header), f);
405
406 return 0;
407}