]> git.ipfire.org Git - people/ms/libloc.git/blob - src/database.c
writer: Write out the network tree
[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 <endian.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/as.h>
31 #include <loc/database.h>
32 #include <loc/format.h>
33 #include <loc/private.h>
34 #include <loc/stringpool.h>
35
36 struct loc_database {
37 struct loc_ctx* ctx;
38 int refcount;
39
40 unsigned int version;
41 time_t created_at;
42 off_t vendor;
43 off_t description;
44
45 // ASes in the database
46 struct loc_database_as_v0* as_v0;
47 size_t as_count;
48
49 struct loc_stringpool* pool;
50 };
51
52 static int loc_database_read_magic(struct loc_database* db, FILE* f) {
53 struct loc_database_magic magic;
54
55 // Read from file
56 size_t bytes_read = fread(&magic, 1, sizeof(magic), f);
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
70 db->version = be16toh(magic.version);
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
82 static int loc_database_read_as_section_v0(struct loc_database* db,
83 FILE* f, off_t as_offset, size_t as_length) {
84 DEBUG(db->ctx, "Reading AS section from %jd (%zu bytes)\n", as_offset, as_length);
85
86 if (as_length > 0) {
87 db->as_v0 = mmap(NULL, as_length, PROT_READ,
88 MAP_SHARED, fileno(f), as_offset);
89
90 if (db->as_v0 == MAP_FAILED)
91 return -errno;
92 }
93
94 db->as_count = as_length / sizeof(*db->as_v0);
95
96 INFO(db->ctx, "Read %zu ASes from the database\n", db->as_count);
97
98 return 0;
99 }
100
101 static int loc_database_read_header_v0(struct loc_database* db, FILE* f) {
102 struct loc_database_header_v0 header;
103
104 // Read from file
105 size_t size = fread(&header, 1, sizeof(header), f);
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
113 db->created_at = be64toh(header.created_at);
114 db->vendor = be32toh(header.vendor);
115 db->description = be32toh(header.description);
116
117 // Open pool
118 off_t pool_offset = be32toh(header.pool_offset);
119 size_t pool_length = be32toh(header.pool_length);
120
121 int r = loc_stringpool_open(db->ctx, &db->pool,
122 f, pool_length, pool_offset);
123 if (r)
124 return r;
125
126 // AS section
127 off_t as_offset = be32toh(header.as_offset);
128 size_t as_length = be32toh(header.as_length);
129
130 r = loc_database_read_as_section_v0(db, f, as_offset, as_length);
131 if (r)
132 return r;
133
134 return 0;
135 }
136
137 static int loc_database_read_header(struct loc_database* db, FILE* f) {
138 switch (db->version) {
139 case 0:
140 return loc_database_read_header_v0(db, f);
141
142 default:
143 ERROR(db->ctx, "Incompatible database version: %u\n", db->version);
144 return 1;
145 }
146 }
147
148 static int loc_database_read(struct loc_database* db, FILE* f) {
149 clock_t start = clock();
150
151 // Read magic bytes
152 int r = loc_database_read_magic(db, f);
153 if (r)
154 return r;
155
156 // Read the header
157 r = loc_database_read_header(db, f);
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
169 LOC_EXPORT int loc_database_new(struct loc_ctx* ctx, struct loc_database** database, FILE* f) {
170 // Fail on invalid file handle
171 if (!f)
172 return -EINVAL;
173
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
184 int r = loc_database_read(db, f);
185 if (r) {
186 loc_database_unref(db);
187 return r;
188 }
189
190 *database = db;
191
192 return 0;
193 }
194
195 LOC_EXPORT struct loc_database* loc_database_ref(struct loc_database* db) {
196 db->refcount++;
197
198 return db;
199 }
200
201 static void loc_database_free(struct loc_database* db) {
202 DEBUG(db->ctx, "Releasing database %p\n", db);
203
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 }
210
211 loc_stringpool_unref(db->pool);
212
213 loc_unref(db->ctx);
214 free(db);
215 }
216
217 LOC_EXPORT struct loc_database* loc_database_unref(struct loc_database* db) {
218 if (--db->refcount > 0)
219 return NULL;
220
221 loc_database_free(db);
222 return NULL;
223 }
224
225 LOC_EXPORT time_t loc_database_created_at(struct loc_database* db) {
226 return db->created_at;
227 }
228
229 LOC_EXPORT const char* loc_database_get_vendor(struct loc_database* db) {
230 return loc_stringpool_get(db->pool, db->vendor);
231 }
232
233 LOC_EXPORT const char* loc_database_get_description(struct loc_database* db) {
234 return loc_stringpool_get(db->pool, db->description);
235 }
236
237 LOC_EXPORT size_t loc_database_count_as(struct loc_database* db) {
238 return db->as_count;
239 }
240
241 // Returns the AS at position pos
242 static 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;
245
246 DEBUG(db->ctx, "Fetching AS at position %jd\n", pos);
247
248 int r;
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;
253
254 default:
255 return -1;
256 }
257
258 if (r == 0) {
259 DEBUG(db->ctx, "Got AS%u\n", loc_as_get_number(*as));
260 }
261
262 return r;
263 }
264
265 // Performs a binary search to find the AS in the list
266 LOC_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;
269
270 // Save start time
271 clock_t start = clock();
272
273 while (lo <= hi) {
274 off_t i = (lo + hi) / 2;
275
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;
280
281 // Check if this is a match
282 uint32_t as_number = loc_as_get_number(*as);
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
290 return 0;
291 }
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 }
302
303 // Nothing found
304 *as = NULL;
305
306 return 1;
307 }