41b5ffd3c9687bd635915f4521f82035211b1165
[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_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);
128 if (r)
129 return r;
130
131 // AS section
132 off_t as_offset = ntohl(header.as_offset);
133 size_t as_length = ntohl(header.as_length);
134
135 r = loc_database_read_as_section_v0(db, as_offset, as_length);
136 if (r)
137 return r;
138
139 return 0;
140 }
141
142 static int loc_database_read_header(struct loc_database* db) {
143 switch (db->version) {
144 case 0:
145 return loc_database_read_header_v0(db);
146
147 default:
148 ERROR(db->ctx, "Incompatible database version: %u\n", db->version);
149 return 1;
150 }
151 }
152
153 static FILE* copy_file_pointer(FILE* f) {
154 int fd = fileno(f);
155
156 // Make a copy
157 fd = dup(fd);
158
159 return fdopen(fd, "r");
160 }
161
162 LOC_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;
178
179 // Read magic bytes
180 int r = loc_database_read_magic(db);
181 if (r)
182 return r;
183
184 // Read the header
185 r = loc_database_read_header(db);
186 if (r)
187 return r;
188
189 *database = db;
190
191 return 0;
192
193 FAIL:
194 loc_database_unref(db);
195
196 return -errno;
197 }
198
199 LOC_EXPORT struct loc_database* loc_database_ref(struct loc_database* db) {
200 db->refcount++;
201
202 return db;
203 }
204
205 static void loc_database_free(struct loc_database* db) {
206 DEBUG(db->ctx, "Releasing database %p\n", db);
207
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 }
214
215 loc_stringpool_unref(db->pool);
216
217 // Close file
218 if (db->file)
219 fclose(db->file);
220
221 loc_unref(db->ctx);
222 free(db);
223 }
224
225 LOC_EXPORT struct loc_database* loc_database_unref(struct loc_database* db) {
226 if (--db->refcount > 0)
227 return NULL;
228
229 loc_database_free(db);
230 return NULL;
231 }
232
233 LOC_EXPORT time_t loc_database_created_at(struct loc_database* db) {
234 return db->created_at;
235 }
236
237 LOC_EXPORT const char* loc_database_get_vendor(struct loc_database* db) {
238 return loc_stringpool_get(db->pool, db->vendor);
239 }
240
241 LOC_EXPORT const char* loc_database_get_description(struct loc_database* db) {
242 return loc_stringpool_get(db->pool, db->description);
243 }
244
245 LOC_EXPORT size_t loc_database_count_as(struct loc_database* db) {
246 return db->as_count;
247 }
248
249 // Returns the AS at position pos
250 static 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;
253
254 DEBUG(db->ctx, "Fetching AS at position %jd\n", pos);
255
256 int r;
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;
261
262 default:
263 return -1;
264 }
265
266 if (r == 0) {
267 DEBUG(db->ctx, "Got AS%u\n", loc_as_get_number(*as));
268 }
269
270 return r;
271 }
272
273 // Performs a binary search to find the AS in the list
274 LOC_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;
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 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 }
300
301 // Nothing found
302 *as = NULL;
303
304 return 0;
305 }