Use be*toh and htobe* to convert to big-endian
[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 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 = be16toh(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 = be32toh(header.vendor);
117 db->description = be32toh(header.description);
118
119 // Open pool
120 off_t pool_offset = be32toh(header.pool_offset);
121 size_t pool_length = be32toh(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 = be32toh(header.as_offset);
130 size_t as_length = be32toh(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 static int loc_database_read(struct loc_database* db) {
160 clock_t start = clock();
161
162 // Read magic bytes
163 int r = loc_database_read_magic(db);
164 if (r)
165 return r;
166
167 // Read the header
168 r = loc_database_read_header(db);
169 if (r)
170 return r;
171
172 clock_t end = clock();
173
174 INFO(db->ctx, "Opened database in %.8fs\n",
175 (double)(end - start) / CLOCKS_PER_SEC);
176
177 return 0;
178 }
179
180 LOC_EXPORT int loc_database_new(struct loc_ctx* ctx, struct loc_database** database, FILE* f) {
181 struct loc_database* db = calloc(1, sizeof(*db));
182 if (!db)
183 return -ENOMEM;
184
185 // Reference context
186 db->ctx = loc_ref(ctx);
187 db->refcount = 1;
188
189 DEBUG(db->ctx, "Database object allocated at %p\n", db);
190
191 // Copy the file pointer and work on that so we don't care if
192 // the calling function closes the file
193 db->file = copy_file_pointer(f);
194 if (!db->file)
195 goto FAIL;
196
197 int r = loc_database_read(db);
198 if (r) {
199 loc_database_unref(db);
200 return r;
201 }
202
203 *database = db;
204
205 return 0;
206
207 FAIL:
208 loc_database_unref(db);
209
210 return -errno;
211 }
212
213 LOC_EXPORT struct loc_database* loc_database_ref(struct loc_database* db) {
214 db->refcount++;
215
216 return db;
217 }
218
219 static void loc_database_free(struct loc_database* db) {
220 DEBUG(db->ctx, "Releasing database %p\n", db);
221
222 // Removing all ASes
223 if (db->as_v0) {
224 int r = munmap(db->as_v0, db->as_count * sizeof(*db->as_v0));
225 if (r)
226 ERROR(db->ctx, "Could not unmap AS section: %s\n", strerror(errno));
227 }
228
229 loc_stringpool_unref(db->pool);
230
231 // Close file
232 if (db->file)
233 fclose(db->file);
234
235 loc_unref(db->ctx);
236 free(db);
237 }
238
239 LOC_EXPORT struct loc_database* loc_database_unref(struct loc_database* db) {
240 if (--db->refcount > 0)
241 return NULL;
242
243 loc_database_free(db);
244 return NULL;
245 }
246
247 LOC_EXPORT time_t loc_database_created_at(struct loc_database* db) {
248 return db->created_at;
249 }
250
251 LOC_EXPORT const char* loc_database_get_vendor(struct loc_database* db) {
252 return loc_stringpool_get(db->pool, db->vendor);
253 }
254
255 LOC_EXPORT const char* loc_database_get_description(struct loc_database* db) {
256 return loc_stringpool_get(db->pool, db->description);
257 }
258
259 LOC_EXPORT size_t loc_database_count_as(struct loc_database* db) {
260 return db->as_count;
261 }
262
263 // Returns the AS at position pos
264 static int loc_database_fetch_as(struct loc_database* db, struct loc_as** as, off_t pos) {
265 if ((size_t)pos >= db->as_count)
266 return -EINVAL;
267
268 DEBUG(db->ctx, "Fetching AS at position %jd\n", pos);
269
270 int r;
271 switch (db->version) {
272 case 0:
273 r = loc_as_new_from_database_v0(db->ctx, db->pool, as, db->as_v0 + pos);
274 break;
275
276 default:
277 return -1;
278 }
279
280 if (r == 0) {
281 DEBUG(db->ctx, "Got AS%u\n", loc_as_get_number(*as));
282 }
283
284 return r;
285 }
286
287 // Performs a binary search to find the AS in the list
288 LOC_EXPORT int loc_database_get_as(struct loc_database* db, struct loc_as** as, uint32_t number) {
289 off_t lo = 0;
290 off_t hi = db->as_count - 1;
291
292 // Save start time
293 clock_t start = clock();
294
295 while (lo <= hi) {
296 off_t i = (lo + hi) / 2;
297
298 // Fetch AS in the middle between lo and hi
299 int r = loc_database_fetch_as(db, as, i);
300 if (r)
301 return r;
302
303 // Check if this is a match
304 uint32_t as_number = loc_as_get_number(*as);
305 if (as_number == number) {
306 clock_t end = clock();
307
308 // Log how fast this has been
309 DEBUG(db->ctx, "Found AS%u in %.8fs\n", as_number,
310 (double)(end - start) / CLOCKS_PER_SEC);
311
312 return 0;
313 }
314
315 // If it wasn't, we release the AS and
316 // adjust our search pointers
317 loc_as_unref(*as);
318
319 if (as_number < number) {
320 lo = i + 1;
321 } else
322 hi = i - 1;
323 }
324
325 // Nothing found
326 *as = NULL;
327
328 return 1;
329 }