e5460da3bb25bc5b49f1d2af9fc8f1f3985d7cf6
[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 }