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