database: Log how long it took to retrieve an AS
[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>
c182393f 24#include <sys/mman.h>
2601e83e 25#include <sys/types.h>
96ea74a5 26#include <time.h>
3f35869a 27#include <unistd.h>
2601e83e
MT
28
29#include <loc/libloc.h>
a5db3e49 30#include <loc/format.h>
2601e83e
MT
31
32#include "libloc-private.h"
a5db3e49 33#include "as.h"
2601e83e
MT
34#include "database.h"
35#include "stringpool.h"
36
37struct loc_database {
38 struct loc_ctx* ctx;
39 int refcount;
40
3f35869a 41 FILE* file;
2601e83e 42 unsigned int version;
96ea74a5 43 time_t created_at;
2601e83e
MT
44 off_t vendor;
45 off_t description;
46
a5db3e49 47 // ASes in the database
c182393f 48 struct loc_database_as_v0* as_v0;
a5db3e49
MT
49 size_t as_count;
50
2601e83e
MT
51 struct loc_stringpool* pool;
52};
53
3f35869a 54static int loc_database_read_magic(struct loc_database* db) {
2601e83e
MT
55 struct loc_database_magic magic;
56
57 // Read from file
3f35869a 58 size_t bytes_read = fread(&magic, 1, sizeof(magic), db->file);
2601e83e
MT
59
60 // Check if we have been able to read enough data
61 if (bytes_read < sizeof(magic)) {
62 ERROR(db->ctx, "Could not read enough data to validate magic bytes\n");
63 DEBUG(db->ctx, "Read %zu bytes, but needed %zu\n", bytes_read, sizeof(magic));
64 return -ENOMSG;
65 }
66
67 // Compare magic bytes
68 if (memcmp(LOC_DATABASE_MAGIC, magic.magic, strlen(LOC_DATABASE_MAGIC)) == 0) {
69 DEBUG(db->ctx, "Magic value matches\n");
70
71 // Parse version
72 db->version = ntohs(magic.version);
73 DEBUG(db->ctx, "Database version is %u\n", db->version);
74
75 return 0;
76 }
77
78 ERROR(db->ctx, "Database format is not compatible\n");
79
80 // Return an error
81 return 1;
82}
83
a5db3e49 84static int loc_database_read_as_section_v0(struct loc_database* db,
3f35869a 85 off_t as_offset, size_t as_length) {
c182393f 86 DEBUG(db->ctx, "Reading AS section from %jd (%zu bytes)\n", as_offset, as_length);
a5db3e49 87
c182393f
MT
88 if (as_length > 0) {
89 db->as_v0 = mmap(NULL, as_length, PROT_READ,
90 MAP_SHARED, fileno(db->file), as_offset);
a5db3e49 91
c182393f
MT
92 if (db->as_v0 == MAP_FAILED)
93 return -errno;
a5db3e49
MT
94 }
95
c182393f
MT
96 db->as_count = as_length / sizeof(*db->as_v0);
97
a5db3e49
MT
98 INFO(db->ctx, "Read %zu ASes from the database\n", db->as_count);
99
100 return 0;
101}
102
3f35869a 103static int loc_database_read_header_v0(struct loc_database* db) {
2601e83e
MT
104 struct loc_database_header_v0 header;
105
106 // Read from file
3f35869a 107 size_t size = fread(&header, 1, sizeof(header), db->file);
2601e83e
MT
108
109 if (size < sizeof(header)) {
110 ERROR(db->ctx, "Could not read enough data for header\n");
111 return -ENOMSG;
112 }
113
114 // Copy over data
96ea74a5 115 db->created_at = be64toh(header.created_at);
2601e83e
MT
116 db->vendor = ntohl(header.vendor);
117 db->description = ntohl(header.description);
118
119 // Open pool
120 off_t pool_offset = ntohl(header.pool_offset);
121 size_t pool_length = ntohl(header.pool_length);
122
0e974d4b
MT
123 int r = loc_stringpool_open(db->ctx, &db->pool,
124 db->file, pool_length, pool_offset);
2601e83e
MT
125 if (r)
126 return r;
127
a5db3e49
MT
128 // AS section
129 off_t as_offset = ntohl(header.as_offset);
130 size_t as_length = ntohl(header.as_length);
131
3f35869a 132 r = loc_database_read_as_section_v0(db, as_offset, as_length);
a5db3e49
MT
133 if (r)
134 return r;
135
2601e83e
MT
136 return 0;
137}
138
3f35869a 139static int loc_database_read_header(struct loc_database* db) {
2601e83e
MT
140 switch (db->version) {
141 case 0:
3f35869a 142 return loc_database_read_header_v0(db);
2601e83e
MT
143
144 default:
145 ERROR(db->ctx, "Incompatible database version: %u\n", db->version);
146 return 1;
147 }
148}
149
c182393f 150static FILE* copy_file_pointer(FILE* f) {
3f35869a
MT
151 int fd = fileno(f);
152
153 // Make a copy
154 fd = dup(fd);
155
c182393f
MT
156 return fdopen(fd, "r");
157}
3f35869a 158
c182393f
MT
159LOC_EXPORT int loc_database_new(struct loc_ctx* ctx, struct loc_database** database, FILE* f) {
160 struct loc_database* db = calloc(1, sizeof(*db));
161 if (!db)
162 return -ENOMEM;
163
164 // Reference context
165 db->ctx = loc_ref(ctx);
166 db->refcount = 1;
167
168 DEBUG(db->ctx, "Database object allocated at %p\n", db);
169
170 // Copy the file pointer and work on that so we don't care if
171 // the calling function closes the file
172 db->file = copy_file_pointer(f);
173 if (!db->file)
174 goto FAIL;
2601e83e
MT
175
176 // Read magic bytes
c182393f 177 int r = loc_database_read_magic(db);
2601e83e
MT
178 if (r)
179 return r;
180
181 // Read the header
3f35869a 182 r = loc_database_read_header(db);
2601e83e
MT
183 if (r)
184 return r;
185
c182393f
MT
186 *database = db;
187
2601e83e 188 return 0;
2601e83e 189
c182393f
MT
190FAIL:
191 loc_database_unref(db);
2601e83e 192
c182393f 193 return -errno;
2601e83e
MT
194}
195
c182393f
MT
196LOC_EXPORT struct loc_database* loc_database_ref(struct loc_database* db) {
197 db->refcount++;
198
199 return db;
8f5b676a
MT
200}
201
c182393f
MT
202static void loc_database_free(struct loc_database* db) {
203 DEBUG(db->ctx, "Releasing database %p\n", db);
c34e76f1 204
c182393f
MT
205 // Removing all ASes
206 if (db->as_v0) {
207 int r = munmap(db->as_v0, db->as_count * sizeof(*db->as_v0));
208 if (r)
209 ERROR(db->ctx, "Could not unmap AS section: %s\n", strerror(errno));
210 }
c34e76f1 211
c182393f 212 loc_stringpool_unref(db->pool);
c34e76f1 213
c182393f
MT
214 // Close file
215 if (db->file)
216 fclose(db->file);
217
218 loc_unref(db->ctx);
219 free(db);
c34e76f1
MT
220}
221
c182393f
MT
222LOC_EXPORT struct loc_database* loc_database_unref(struct loc_database* db) {
223 if (--db->refcount > 0)
224 return NULL;
78ace4ed 225
c182393f
MT
226 loc_database_free(db);
227 return NULL;
228}
78ace4ed 229
c182393f
MT
230LOC_EXPORT time_t loc_database_created_at(struct loc_database* db) {
231 return db->created_at;
232}
78ace4ed 233
c182393f
MT
234LOC_EXPORT const char* loc_database_get_vendor(struct loc_database* db) {
235 return loc_stringpool_get(db->pool, db->vendor);
236}
78ace4ed 237
c182393f
MT
238LOC_EXPORT const char* loc_database_get_description(struct loc_database* db) {
239 return loc_stringpool_get(db->pool, db->description);
240}
78ace4ed 241
c182393f
MT
242LOC_EXPORT size_t loc_database_count_as(struct loc_database* db) {
243 return db->as_count;
78ace4ed
MT
244}
245
c182393f
MT
246// Returns the AS at position pos
247static int loc_database_fetch_as(struct loc_database* db, struct loc_as** as, off_t pos) {
248 if ((size_t)pos >= db->as_count)
249 return -EINVAL;
2601e83e 250
c182393f 251 DEBUG(db->ctx, "Fetching AS at position %jd\n", pos);
2601e83e
MT
252
253 int r;
c182393f
MT
254 switch (db->version) {
255 case 0:
256 r = loc_as_new_from_database_v0(db->ctx, db->pool, as, db->as_v0 + pos);
257 break;
2601e83e 258
c182393f
MT
259 default:
260 return -1;
261 }
2601e83e 262
c182393f
MT
263 if (r == 0) {
264 DEBUG(db->ctx, "Got AS%u\n", loc_as_get_number(*as));
2601e83e 265 }
2601e83e 266
c182393f
MT
267 return r;
268}
c34e76f1 269
c182393f
MT
270// Performs a binary search to find the AS in the list
271LOC_EXPORT int loc_database_get_as(struct loc_database* db, struct loc_as** as, uint32_t number) {
272 off_t lo = 0;
273 off_t hi = db->as_count - 1;
c34e76f1 274
8f3e2a06
MT
275 // Save start time
276 clock_t start = clock();
277
c182393f
MT
278 while (lo <= hi) {
279 off_t i = (lo + hi) / 2;
8f5b676a 280
c182393f
MT
281 // Fetch AS in the middle between lo and hi
282 int r = loc_database_fetch_as(db, as, i);
283 if (r)
284 return r;
a5db3e49 285
c182393f
MT
286 // Check if this is a match
287 uint32_t as_number = loc_as_get_number(*as);
8f3e2a06
MT
288 if (as_number == number) {
289 clock_t end = clock();
290
291 // Log how fast this has been
292 DEBUG(db->ctx, "Found AS%u in %.8fs\n", as_number,
293 (double)(end - start) / CLOCKS_PER_SEC);
294
c182393f 295 return 0;
8f3e2a06 296 }
c182393f
MT
297
298 // If it wasn't, we release the AS and
299 // adjust our search pointers
300 loc_as_unref(*as);
301
302 if (as_number < number) {
303 lo = i + 1;
304 } else
305 hi = i - 1;
306 }
2601e83e 307
c182393f
MT
308 // Nothing found
309 *as = NULL;
2601e83e 310
8f3e2a06 311 return 1;
2601e83e 312}