database: Log how long it took to retrieve an AS
[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/mman.h>
25 #include <sys/types.h>
26 #include <time.h>
27 #include <unistd.h>
28
29 #include <loc/libloc.h>
30 #include <loc/format.h>
31
32 #include "libloc-private.h"
33 #include "as.h"
34 #include "database.h"
35 #include "stringpool.h"
36
37 struct loc_database {
38         struct loc_ctx* ctx;
39         int refcount;
40
41         FILE* file;
42         unsigned int version;
43         time_t created_at;
44         off_t vendor;
45         off_t description;
46
47         // ASes in the database
48         struct loc_database_as_v0* as_v0;
49         size_t as_count;
50
51         struct loc_stringpool* pool;
52 };
53
54 static int loc_database_read_magic(struct loc_database* db) {
55         struct loc_database_magic magic;
56
57         // Read from file
58         size_t bytes_read = fread(&magic, 1, sizeof(magic), db->file);
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
84 static int loc_database_read_as_section_v0(struct loc_database* db,
85                 off_t as_offset, size_t as_length) {
86         DEBUG(db->ctx, "Reading AS section from %jd (%zu bytes)\n", as_offset, as_length);
87
88         if (as_length > 0) {
89                 db->as_v0 = mmap(NULL, as_length, PROT_READ,
90                         MAP_SHARED, fileno(db->file), as_offset);
91
92                 if (db->as_v0 == MAP_FAILED)
93                         return -errno;
94         }
95
96         db->as_count = as_length / sizeof(*db->as_v0);
97
98         INFO(db->ctx, "Read %zu ASes from the database\n", db->as_count);
99
100         return 0;
101 }
102
103 static int loc_database_read_header_v0(struct loc_database* db) {
104         struct loc_database_header_v0 header;
105
106         // Read from file
107         size_t size = fread(&header, 1, sizeof(header), db->file);
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
115         db->created_at  = be64toh(header.created_at);
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
123         int r = loc_stringpool_open(db->ctx, &db->pool,
124                 db->file, pool_length, pool_offset);
125         if (r)
126                 return r;
127
128         // AS section
129         off_t as_offset  = ntohl(header.as_offset);
130         size_t as_length = ntohl(header.as_length);
131
132         r = loc_database_read_as_section_v0(db, as_offset, as_length);
133         if (r)
134                 return r;
135
136         return 0;
137 }
138
139 static int loc_database_read_header(struct loc_database* db) {
140         switch (db->version) {
141                 case 0:
142                         return loc_database_read_header_v0(db);
143
144                 default:
145                         ERROR(db->ctx, "Incompatible database version: %u\n", db->version);
146                         return 1;
147         }
148 }
149
150 static FILE* copy_file_pointer(FILE* f) {
151         int fd = fileno(f);
152
153         // Make a copy
154         fd = dup(fd);
155
156         return fdopen(fd, "r");
157 }
158
159 LOC_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;
175
176         // Read magic bytes
177         int r = loc_database_read_magic(db);
178         if (r)
179                 return r;
180
181         // Read the header
182         r = loc_database_read_header(db);
183         if (r)
184                 return r;
185
186         *database = db;
187
188         return 0;
189
190 FAIL:
191         loc_database_unref(db);
192
193         return -errno;
194 }
195
196 LOC_EXPORT struct loc_database* loc_database_ref(struct loc_database* db) {
197         db->refcount++;
198
199         return db;
200 }
201
202 static void loc_database_free(struct loc_database* db) {
203         DEBUG(db->ctx, "Releasing database %p\n", db);
204
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         }
211
212         loc_stringpool_unref(db->pool);
213
214         // Close file
215         if (db->file)
216                 fclose(db->file);
217
218         loc_unref(db->ctx);
219         free(db);
220 }
221
222 LOC_EXPORT struct loc_database* loc_database_unref(struct loc_database* db) {
223         if (--db->refcount > 0)
224                 return NULL;
225
226         loc_database_free(db);
227         return NULL;
228 }
229
230 LOC_EXPORT time_t loc_database_created_at(struct loc_database* db) {
231         return db->created_at;
232 }
233
234 LOC_EXPORT const char* loc_database_get_vendor(struct loc_database* db) {
235         return loc_stringpool_get(db->pool, db->vendor);
236 }
237
238 LOC_EXPORT const char* loc_database_get_description(struct loc_database* db) {
239         return loc_stringpool_get(db->pool, db->description);
240 }
241
242 LOC_EXPORT size_t loc_database_count_as(struct loc_database* db) {
243         return db->as_count;
244 }
245
246 // Returns the AS at position pos
247 static 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;
250
251         DEBUG(db->ctx, "Fetching AS at position %jd\n", pos);
252
253         int r;
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;
258
259                 default:
260                         return -1;
261         }
262
263         if (r == 0) {
264                 DEBUG(db->ctx, "Got AS%u\n", loc_as_get_number(*as));
265         }
266
267         return r;
268 }
269
270 // Performs a binary search to find the AS in the list
271 LOC_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;
274
275         // Save start time
276         clock_t start = clock();
277
278         while (lo <= hi) {
279                 off_t i = (lo + hi) / 2;
280
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;
285
286                 // Check if this is a match
287                 uint32_t as_number = loc_as_get_number(*as);
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
295                         return 0;
296                 }
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         }
307
308         // Nothing found
309         *as = NULL;
310
311         return 1;
312 }