2 libloc - A library to determine the location of someone on the Internet
4 Copyright (C) 2017 IPFire Development Team <info@ipfire.org>
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.
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.
17 #include <arpa/inet.h>
24 #include <sys/types.h>
28 #include <loc/libloc.h>
29 #include <loc/format.h>
31 #include "libloc-private.h"
34 #include "stringpool.h"
46 // ASes in the database
50 struct loc_stringpool
* pool
;
53 LOC_EXPORT
int loc_database_new(struct loc_ctx
* ctx
, struct loc_database
** database
, size_t pool_size
) {
54 struct loc_database
* db
= calloc(1, sizeof(*db
));
59 db
->ctx
= loc_ref(ctx
);
63 db
->created_at
= time(NULL
);
65 DEBUG(db
->ctx
, "Database allocated at %p\n", db
);
68 int r
= loc_stringpool_new(db
->ctx
, &db
->pool
, pool_size
);
70 loc_database_unref(db
);
79 LOC_EXPORT
int loc_database_open(struct loc_ctx
* ctx
, struct loc_database
** database
, FILE* f
) {
80 int r
= loc_database_new(ctx
, database
, 0);
84 return loc_database_read(*database
, f
);
87 LOC_EXPORT
struct loc_database
* loc_database_ref(struct loc_database
* db
) {
93 static void loc_database_free(struct loc_database
* db
) {
94 DEBUG(db
->ctx
, "Releasing database %p\n", db
);
96 // Remove references to all ASes
98 for (unsigned int i
= 0; i
< db
->as_count
; i
++) {
99 loc_as_unref(db
->as
[i
]);
104 loc_stringpool_unref(db
->pool
);
114 LOC_EXPORT
struct loc_database
* loc_database_unref(struct loc_database
* db
) {
115 if (--db
->refcount
> 0)
118 loc_database_free(db
);
122 LOC_EXPORT
time_t loc_database_created_at(struct loc_database
* db
) {
123 return db
->created_at
;
126 LOC_EXPORT
const char* loc_database_get_vendor(struct loc_database
* db
) {
127 return loc_stringpool_get(db
->pool
, db
->vendor
);
130 LOC_EXPORT
int loc_database_set_vendor(struct loc_database
* db
, const char* vendor
) {
131 // Add the string to the string pool
132 off_t offset
= loc_stringpool_add(db
->pool
, vendor
);
140 LOC_EXPORT
const char* loc_database_get_description(struct loc_database
* db
) {
141 return loc_stringpool_get(db
->pool
, db
->description
);
144 LOC_EXPORT
int loc_database_set_description(struct loc_database
* db
, const char* description
) {
145 // Add the string to the string pool
146 off_t offset
= loc_stringpool_add(db
->pool
, description
);
150 db
->description
= offset
;
154 LOC_EXPORT
size_t loc_database_count_as(struct loc_database
* db
) {
158 static int loc_database_has_as(struct loc_database
* db
, struct loc_as
* as
) {
159 for (unsigned int i
= 0; i
< db
->as_count
; i
++) {
160 if (loc_as_cmp(as
, db
->as
[i
]) == 0)
167 static int __loc_as_cmp(const void* as1
, const void* as2
) {
168 return loc_as_cmp(*(struct loc_as
**)as1
, *(struct loc_as
**)as2
);
171 static void loc_database_sort_ases(struct loc_database
* db
) {
172 qsort(db
->as
, db
->as_count
, sizeof(*db
->as
), __loc_as_cmp
);
175 static struct loc_as
* __loc_database_add_as(struct loc_database
* db
, struct loc_as
* as
) {
176 // Check if AS exists already
177 int i
= loc_database_has_as(db
, as
);
181 // Select already existing AS
184 return loc_as_ref(as
);
189 // Make space for the new entry
190 db
->as
= realloc(db
->as
, sizeof(*db
->as
) * db
->as_count
);
192 // Add the new entry at the end
193 db
->as
[db
->as_count
- 1] = loc_as_ref(as
);
196 loc_database_sort_ases(db
);
201 LOC_EXPORT
struct loc_as
* loc_database_add_as(struct loc_database
* db
, uint32_t number
) {
203 int r
= loc_as_new(db
->ctx
, db
->pool
, &as
, number
);
207 return __loc_database_add_as(db
, as
);
210 static int loc_database_read_magic(struct loc_database
* db
) {
211 struct loc_database_magic magic
;
214 size_t bytes_read
= fread(&magic
, 1, sizeof(magic
), db
->file
);
216 // Check if we have been able to read enough data
217 if (bytes_read
< sizeof(magic
)) {
218 ERROR(db
->ctx
, "Could not read enough data to validate magic bytes\n");
219 DEBUG(db
->ctx
, "Read %zu bytes, but needed %zu\n", bytes_read
, sizeof(magic
));
223 // Compare magic bytes
224 if (memcmp(LOC_DATABASE_MAGIC
, magic
.magic
, strlen(LOC_DATABASE_MAGIC
)) == 0) {
225 DEBUG(db
->ctx
, "Magic value matches\n");
228 db
->version
= ntohs(magic
.version
);
229 DEBUG(db
->ctx
, "Database version is %u\n", db
->version
);
234 ERROR(db
->ctx
, "Database format is not compatible\n");
240 static int loc_database_read_as_section_v0(struct loc_database
* db
,
241 off_t as_offset
, size_t as_length
) {
242 struct loc_database_as_v0 dbobj
;
244 // Read from the start of the section
245 int r
= fseek(db
->file
, as_offset
, SEEK_SET
);
250 size_t as_count
= as_length
/ sizeof(dbobj
);
251 for (unsigned int i
= 0; i
< as_count
; i
++) {
252 size_t bytes_read
= fread(&dbobj
, 1, sizeof(dbobj
), db
->file
);
253 if (bytes_read
< sizeof(dbobj
)) {
254 ERROR(db
->ctx
, "Could not read an AS object\n");
260 r
= loc_as_new_from_database_v0(db
->ctx
, db
->pool
, &as
, &dbobj
);
264 // Attach it to the database
265 as
= __loc_database_add_as(db
, as
);
269 INFO(db
->ctx
, "Read %zu ASes from the database\n", db
->as_count
);
274 static int loc_database_read_header_v0(struct loc_database
* db
) {
275 struct loc_database_header_v0 header
;
278 size_t size
= fread(&header
, 1, sizeof(header
), db
->file
);
280 if (size
< sizeof(header
)) {
281 ERROR(db
->ctx
, "Could not read enough data for header\n");
286 db
->created_at
= be64toh(header
.created_at
);
287 db
->vendor
= ntohl(header
.vendor
);
288 db
->description
= ntohl(header
.description
);
291 off_t pool_offset
= ntohl(header
.pool_offset
);
292 size_t pool_length
= ntohl(header
.pool_length
);
294 int r
= loc_stringpool_read(db
->pool
, db
->file
, pool_offset
, pool_length
);
299 off_t as_offset
= ntohl(header
.as_offset
);
300 size_t as_length
= ntohl(header
.as_length
);
302 r
= loc_database_read_as_section_v0(db
, as_offset
, as_length
);
309 static int loc_database_read_header(struct loc_database
* db
) {
310 switch (db
->version
) {
312 return loc_database_read_header_v0(db
);
315 ERROR(db
->ctx
, "Incompatible database version: %u\n", db
->version
);
320 LOC_EXPORT
int loc_database_read(struct loc_database
* db
, FILE* f
) {
321 // Copy the file pointer and work on that so we don't care if
322 // the calling function closes the file
328 // Retrieve a file pointer
329 db
->file
= fdopen(fd
, "r");
333 int r
= fseek(db
->file
, 0, SEEK_SET
);
338 r
= loc_database_read_magic(db
);
343 r
= loc_database_read_header(db
);
350 static void loc_database_make_magic(struct loc_database
* db
, struct loc_database_magic
* magic
) {
352 for (unsigned int i
= 0; i
< strlen(LOC_DATABASE_MAGIC
); i
++)
353 magic
->magic
[i
] = LOC_DATABASE_MAGIC
[i
];
356 magic
->version
= htons(LOC_DATABASE_VERSION
);
359 static void loc_database_align_page_boundary(off_t
* offset
, FILE* f
) {
360 // Move to next page boundary
361 while (*offset
% LOC_DATABASE_PAGE_SIZE
> 0)
362 *offset
+= fwrite("", 1, 1, f
);
365 static int loc_database_write_pool(struct loc_database
* db
, struct loc_database_header_v0
* header
, off_t
* offset
, FILE* f
) {
366 // Save the offset of the pool section
367 DEBUG(db
->ctx
, "Pool starts at %jd bytes\n", *offset
);
368 header
->pool_offset
= htonl(*offset
);
371 size_t pool_length
= loc_stringpool_write(db
->pool
, f
);
372 *offset
+= pool_length
;
374 DEBUG(db
->ctx
, "Pool has a length of %zu bytes\n", pool_length
);
375 header
->pool_length
= htonl(pool_length
);
380 static int loc_database_write_as_section(struct loc_database
* db
,
381 struct loc_database_header_v0
* header
, off_t
* offset
, FILE* f
) {
382 DEBUG(db
->ctx
, "AS section starts at %jd bytes\n", *offset
);
383 header
->as_offset
= htonl(*offset
);
385 size_t as_length
= 0;
387 struct loc_database_as_v0 dbas
;
388 for (unsigned int i
= 0; i
< db
->as_count
; i
++) {
389 // Convert AS into database format
390 loc_as_to_database_v0(db
->as
[i
], &dbas
);
393 offset
+= fwrite(&dbas
, 1, sizeof(dbas
), f
);
394 as_length
+= sizeof(dbas
);
397 DEBUG(db
->ctx
, "AS section has a length of %zu bytes\n", as_length
);
398 header
->as_length
= htonl(as_length
);
403 LOC_EXPORT
int loc_database_write(struct loc_database
* db
, FILE* f
) {
404 struct loc_database_magic magic
;
405 loc_database_make_magic(db
, &magic
);
408 struct loc_database_header_v0 header
;
409 header
.created_at
= htobe64(db
->created_at
);
410 header
.vendor
= htonl(db
->vendor
);
411 header
.description
= htonl(db
->description
);
416 // Start writing at the beginning of the file
417 r
= fseek(f
, 0, SEEK_SET
);
422 offset
+= fwrite(&magic
, 1, sizeof(magic
), f
);
424 // Skip the space we need to write the header later
425 r
= fseek(f
, sizeof(header
), SEEK_CUR
);
427 DEBUG(db
->ctx
, "Could not seek to position after header\n");
430 offset
+= sizeof(header
);
432 loc_database_align_page_boundary(&offset
, f
);
435 r
= loc_database_write_pool(db
, &header
, &offset
, f
);
439 loc_database_align_page_boundary(&offset
, f
);
442 r
= loc_database_write_as_section(db
, &header
, &offset
, f
);
447 r
= fseek(f
, sizeof(magic
), SEEK_SET
);
451 offset
+= fwrite(&header
, 1, sizeof(header
), f
);