Split database into a writer and reader
[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
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>
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
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
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);
2601e83e
MT
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
c182393f
MT
123 int r = loc_stringpool_new(db->ctx, &db->pool, 0);
124 if (r)
125 return r;
126
127 r = loc_stringpool_read(db->pool, db->file, pool_offset, pool_length);
2601e83e
MT
128 if (r)
129 return r;
130
a5db3e49
MT
131 // AS section
132 off_t as_offset = ntohl(header.as_offset);
133 size_t as_length = ntohl(header.as_length);
134
3f35869a 135 r = loc_database_read_as_section_v0(db, as_offset, as_length);
a5db3e49
MT
136 if (r)
137 return r;
138
2601e83e
MT
139 return 0;
140}
141
3f35869a 142static int loc_database_read_header(struct loc_database* db) {
2601e83e
MT
143 switch (db->version) {
144 case 0:
3f35869a 145 return loc_database_read_header_v0(db);
2601e83e
MT
146
147 default:
148 ERROR(db->ctx, "Incompatible database version: %u\n", db->version);
149 return 1;
150 }
151}
152
c182393f 153static FILE* copy_file_pointer(FILE* f) {
3f35869a
MT
154 int fd = fileno(f);
155
156 // Make a copy
157 fd = dup(fd);
158
c182393f
MT
159 return fdopen(fd, "r");
160}
3f35869a 161
c182393f
MT
162LOC_EXPORT int loc_database_new(struct loc_ctx* ctx, struct loc_database** database, FILE* f) {
163 struct loc_database* db = calloc(1, sizeof(*db));
164 if (!db)
165 return -ENOMEM;
166
167 // Reference context
168 db->ctx = loc_ref(ctx);
169 db->refcount = 1;
170
171 DEBUG(db->ctx, "Database object allocated at %p\n", db);
172
173 // Copy the file pointer and work on that so we don't care if
174 // the calling function closes the file
175 db->file = copy_file_pointer(f);
176 if (!db->file)
177 goto FAIL;
2601e83e
MT
178
179 // Read magic bytes
c182393f 180 int r = loc_database_read_magic(db);
2601e83e
MT
181 if (r)
182 return r;
183
184 // Read the header
3f35869a 185 r = loc_database_read_header(db);
2601e83e
MT
186 if (r)
187 return r;
188
c182393f
MT
189 *database = db;
190
2601e83e 191 return 0;
2601e83e 192
c182393f
MT
193FAIL:
194 loc_database_unref(db);
2601e83e 195
c182393f 196 return -errno;
2601e83e
MT
197}
198
c182393f
MT
199LOC_EXPORT struct loc_database* loc_database_ref(struct loc_database* db) {
200 db->refcount++;
201
202 return db;
8f5b676a
MT
203}
204
c182393f
MT
205static void loc_database_free(struct loc_database* db) {
206 DEBUG(db->ctx, "Releasing database %p\n", db);
c34e76f1 207
c182393f
MT
208 // Removing all ASes
209 if (db->as_v0) {
210 int r = munmap(db->as_v0, db->as_count * sizeof(*db->as_v0));
211 if (r)
212 ERROR(db->ctx, "Could not unmap AS section: %s\n", strerror(errno));
213 }
c34e76f1 214
c182393f 215 loc_stringpool_unref(db->pool);
c34e76f1 216
c182393f
MT
217 // Close file
218 if (db->file)
219 fclose(db->file);
220
221 loc_unref(db->ctx);
222 free(db);
c34e76f1
MT
223}
224
c182393f
MT
225LOC_EXPORT struct loc_database* loc_database_unref(struct loc_database* db) {
226 if (--db->refcount > 0)
227 return NULL;
78ace4ed 228
c182393f
MT
229 loc_database_free(db);
230 return NULL;
231}
78ace4ed 232
c182393f
MT
233LOC_EXPORT time_t loc_database_created_at(struct loc_database* db) {
234 return db->created_at;
235}
78ace4ed 236
c182393f
MT
237LOC_EXPORT const char* loc_database_get_vendor(struct loc_database* db) {
238 return loc_stringpool_get(db->pool, db->vendor);
239}
78ace4ed 240
c182393f
MT
241LOC_EXPORT const char* loc_database_get_description(struct loc_database* db) {
242 return loc_stringpool_get(db->pool, db->description);
243}
78ace4ed 244
c182393f
MT
245LOC_EXPORT size_t loc_database_count_as(struct loc_database* db) {
246 return db->as_count;
78ace4ed
MT
247}
248
c182393f
MT
249// Returns the AS at position pos
250static int loc_database_fetch_as(struct loc_database* db, struct loc_as** as, off_t pos) {
251 if ((size_t)pos >= db->as_count)
252 return -EINVAL;
2601e83e 253
c182393f 254 DEBUG(db->ctx, "Fetching AS at position %jd\n", pos);
2601e83e
MT
255
256 int r;
c182393f
MT
257 switch (db->version) {
258 case 0:
259 r = loc_as_new_from_database_v0(db->ctx, db->pool, as, db->as_v0 + pos);
260 break;
2601e83e 261
c182393f
MT
262 default:
263 return -1;
264 }
2601e83e 265
c182393f
MT
266 if (r == 0) {
267 DEBUG(db->ctx, "Got AS%u\n", loc_as_get_number(*as));
2601e83e 268 }
2601e83e 269
c182393f
MT
270 return r;
271}
c34e76f1 272
c182393f
MT
273// Performs a binary search to find the AS in the list
274LOC_EXPORT int loc_database_get_as(struct loc_database* db, struct loc_as** as, uint32_t number) {
275 off_t lo = 0;
276 off_t hi = db->as_count - 1;
c34e76f1 277
c182393f
MT
278 while (lo <= hi) {
279 off_t i = (lo + hi) / 2;
8f5b676a 280
c182393f
MT
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;
a5db3e49 285
c182393f
MT
286 // Check if this is a match
287 uint32_t as_number = loc_as_get_number(*as);
288 if (as_number == number)
289 return 0;
290
291 // If it wasn't, we release the AS and
292 // adjust our search pointers
293 loc_as_unref(*as);
294
295 if (as_number < number) {
296 lo = i + 1;
297 } else
298 hi = i - 1;
299 }
2601e83e 300
c182393f
MT
301 // Nothing found
302 *as = NULL;
2601e83e
MT
303
304 return 0;
305}