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 }