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