Use be*toh and htobe* to convert to big-endian
[people/ms/libloc.git] / src / database.c
CommitLineData
2601e83e
MT
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
0676cd80 17#include <endian.h>
2601e83e
MT
18#include <errno.h>
19#include <stddef.h>
20#include <stdint.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
c182393f 24#include <sys/mman.h>
2601e83e 25#include <sys/types.h>
96ea74a5 26#include <time.h>
3f35869a 27#include <unistd.h>
2601e83e
MT
28
29#include <loc/libloc.h>
a5db3e49 30#include <loc/format.h>
2601e83e
MT
31
32#include "libloc-private.h"
a5db3e49 33#include "as.h"
2601e83e
MT
34#include "database.h"
35#include "stringpool.h"
36
37struct loc_database {
38 struct loc_ctx* ctx;
39 int refcount;
40
3f35869a 41 FILE* file;
2601e83e 42 unsigned int version;
96ea74a5 43 time_t created_at;
2601e83e
MT
44 off_t vendor;
45 off_t description;
46
a5db3e49 47 // ASes in the database
c182393f 48 struct loc_database_as_v0* as_v0;
a5db3e49
MT
49 size_t as_count;
50
2601e83e
MT
51 struct loc_stringpool* pool;
52};
53
3f35869a 54static int loc_database_read_magic(struct loc_database* db) {
2601e83e
MT
55 struct loc_database_magic magic;
56
57 // Read from file
3f35869a 58 size_t bytes_read = fread(&magic, 1, sizeof(magic), db->file);
2601e83e
MT
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
0676cd80 72 db->version = be16toh(magic.version);
2601e83e
MT
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
a5db3e49 84static int loc_database_read_as_section_v0(struct loc_database* db,
3f35869a 85 off_t as_offset, size_t as_length) {
c182393f 86 DEBUG(db->ctx, "Reading AS section from %jd (%zu bytes)\n", as_offset, as_length);
a5db3e49 87
c182393f
MT
88 if (as_length > 0) {
89 db->as_v0 = mmap(NULL, as_length, PROT_READ,
90 MAP_SHARED, fileno(db->file), as_offset);
a5db3e49 91
c182393f
MT
92 if (db->as_v0 == MAP_FAILED)
93 return -errno;
a5db3e49
MT
94 }
95
c182393f
MT
96 db->as_count = as_length / sizeof(*db->as_v0);
97
a5db3e49
MT
98 INFO(db->ctx, "Read %zu ASes from the database\n", db->as_count);
99
100 return 0;
101}
102
3f35869a 103static int loc_database_read_header_v0(struct loc_database* db) {
2601e83e
MT
104 struct loc_database_header_v0 header;
105
106 // Read from file
3f35869a 107 size_t size = fread(&header, 1, sizeof(header), db->file);
2601e83e
MT
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
96ea74a5 115 db->created_at = be64toh(header.created_at);
0676cd80
MT
116 db->vendor = be32toh(header.vendor);
117 db->description = be32toh(header.description);
2601e83e
MT
118
119 // Open pool
0676cd80
MT
120 off_t pool_offset = be32toh(header.pool_offset);
121 size_t pool_length = be32toh(header.pool_length);
2601e83e 122
0e974d4b
MT
123 int r = loc_stringpool_open(db->ctx, &db->pool,
124 db->file, pool_length, pool_offset);
2601e83e
MT
125 if (r)
126 return r;
127
a5db3e49 128 // AS section
0676cd80
MT
129 off_t as_offset = be32toh(header.as_offset);
130 size_t as_length = be32toh(header.as_length);
a5db3e49 131
3f35869a 132 r = loc_database_read_as_section_v0(db, as_offset, as_length);
a5db3e49
MT
133 if (r)
134 return r;
135
2601e83e
MT
136 return 0;
137}
138
3f35869a 139static int loc_database_read_header(struct loc_database* db) {
2601e83e
MT
140 switch (db->version) {
141 case 0:
3f35869a 142 return loc_database_read_header_v0(db);
2601e83e
MT
143
144 default:
145 ERROR(db->ctx, "Incompatible database version: %u\n", db->version);
146 return 1;
147 }
148}
149
c182393f 150static FILE* copy_file_pointer(FILE* f) {
3f35869a
MT
151 int fd = fileno(f);
152
153 // Make a copy
154 fd = dup(fd);
155
c182393f
MT
156 return fdopen(fd, "r");
157}
3f35869a 158
02879100
MT
159static 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
c182393f
MT
180LOC_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;
2601e83e 196
02879100
MT
197 int r = loc_database_read(db);
198 if (r) {
199 loc_database_unref(db);
2601e83e 200 return r;
02879100 201 }
2601e83e 202
c182393f
MT
203 *database = db;
204
2601e83e 205 return 0;
2601e83e 206
c182393f
MT
207FAIL:
208 loc_database_unref(db);
2601e83e 209
c182393f 210 return -errno;
2601e83e
MT
211}
212
c182393f
MT
213LOC_EXPORT struct loc_database* loc_database_ref(struct loc_database* db) {
214 db->refcount++;
215
216 return db;
8f5b676a
MT
217}
218
c182393f
MT
219static void loc_database_free(struct loc_database* db) {
220 DEBUG(db->ctx, "Releasing database %p\n", db);
c34e76f1 221
c182393f
MT
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 }
c34e76f1 228
c182393f 229 loc_stringpool_unref(db->pool);
c34e76f1 230
c182393f
MT
231 // Close file
232 if (db->file)
233 fclose(db->file);
234
235 loc_unref(db->ctx);
236 free(db);
c34e76f1
MT
237}
238
c182393f
MT
239LOC_EXPORT struct loc_database* loc_database_unref(struct loc_database* db) {
240 if (--db->refcount > 0)
241 return NULL;
78ace4ed 242
c182393f
MT
243 loc_database_free(db);
244 return NULL;
245}
78ace4ed 246
c182393f
MT
247LOC_EXPORT time_t loc_database_created_at(struct loc_database* db) {
248 return db->created_at;
249}
78ace4ed 250
c182393f
MT
251LOC_EXPORT const char* loc_database_get_vendor(struct loc_database* db) {
252 return loc_stringpool_get(db->pool, db->vendor);
253}
78ace4ed 254
c182393f
MT
255LOC_EXPORT const char* loc_database_get_description(struct loc_database* db) {
256 return loc_stringpool_get(db->pool, db->description);
257}
78ace4ed 258
c182393f
MT
259LOC_EXPORT size_t loc_database_count_as(struct loc_database* db) {
260 return db->as_count;
78ace4ed
MT
261}
262
c182393f
MT
263// Returns the AS at position pos
264static 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;
2601e83e 267
c182393f 268 DEBUG(db->ctx, "Fetching AS at position %jd\n", pos);
2601e83e
MT
269
270 int r;
c182393f
MT
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;
2601e83e 275
c182393f
MT
276 default:
277 return -1;
278 }
2601e83e 279
c182393f
MT
280 if (r == 0) {
281 DEBUG(db->ctx, "Got AS%u\n", loc_as_get_number(*as));
2601e83e 282 }
2601e83e 283
c182393f
MT
284 return r;
285}
c34e76f1 286
c182393f
MT
287// Performs a binary search to find the AS in the list
288LOC_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;
c34e76f1 291
8f3e2a06
MT
292 // Save start time
293 clock_t start = clock();
294
c182393f
MT
295 while (lo <= hi) {
296 off_t i = (lo + hi) / 2;
8f5b676a 297
c182393f
MT
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;
a5db3e49 302
c182393f
MT
303 // Check if this is a match
304 uint32_t as_number = loc_as_get_number(*as);
8f3e2a06
MT
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
c182393f 312 return 0;
8f3e2a06 313 }
c182393f
MT
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 }
2601e83e 324
c182393f
MT
325 // Nothing found
326 *as = NULL;
2601e83e 327
8f3e2a06 328 return 1;
2601e83e 329}