345b4f7ec272c5b80f811be188b2987369194fde
[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/types.h>
25
26 #include <loc/libloc.h>
27 #include <loc/format.h>
28
29 #include "libloc-private.h"
30 #include "as.h"
31 #include "database.h"
32 #include "stringpool.h"
33
34 struct loc_database {
35         struct loc_ctx* ctx;
36         int refcount;
37
38         unsigned int version;
39         off_t vendor;
40         off_t description;
41
42         // ASes in the database
43         struct loc_as** as;
44         size_t as_count;
45
46         struct loc_stringpool* pool;
47 };
48
49 LOC_EXPORT int loc_database_new(struct loc_ctx* ctx, struct loc_database** database, size_t pool_size) {
50         struct loc_database* db = calloc(1, sizeof(*db));
51         if (!db)
52                 return -ENOMEM;
53
54         // Reference context
55         db->ctx = loc_ref(ctx);
56         db->refcount = 1;
57
58         DEBUG(db->ctx, "Database allocated at %p\n", db);
59
60         // Create string pool
61         int r = loc_stringpool_new(db->ctx, &db->pool, pool_size);
62         if (r) {
63                 loc_database_unref(db);
64                 return r;
65         }
66
67         *database = db;
68
69         return 0;
70 }
71
72 LOC_EXPORT int loc_database_open(struct loc_ctx* ctx, struct loc_database** database, FILE* f) {
73         int r = loc_database_new(ctx, database, 0);
74         if (r)
75                 return r;
76
77         return loc_database_read(*database, f);
78 }
79
80 LOC_EXPORT struct loc_database* loc_database_ref(struct loc_database* db) {
81         db->refcount++;
82
83         return db;
84 }
85
86 static void loc_database_free(struct loc_database* db) {
87         DEBUG(db->ctx, "Releasing database %p\n", db);
88
89         // Remove references to all ASes
90         if (db->as) {
91                 for (unsigned int i = 0; i < db->as_count; i++) {
92                         loc_as_unref(db->as[i]);
93                 }
94                 free(db->as);
95         }
96
97         loc_stringpool_unref(db->pool);
98
99         loc_unref(db->ctx);
100         free(db);
101 }
102
103 LOC_EXPORT struct loc_database* loc_database_unref(struct loc_database* db) {
104         if (--db->refcount > 0)
105                 return NULL;
106
107         loc_database_free(db);
108         return NULL;
109 }
110
111 LOC_EXPORT const char* loc_database_get_vendor(struct loc_database* db) {
112         return loc_stringpool_get(db->pool, db->vendor);
113 }
114
115 LOC_EXPORT int loc_database_set_vendor(struct loc_database* db, const char* vendor) {
116         // Add the string to the string pool
117         off_t offset = loc_stringpool_add(db->pool, vendor);
118         if (offset < 0)
119                 return offset;
120
121         db->vendor = offset;
122         return 0;
123 }
124
125 LOC_EXPORT const char* loc_database_get_description(struct loc_database* db) {
126         return loc_stringpool_get(db->pool, db->description);
127 }
128
129 LOC_EXPORT int loc_database_set_description(struct loc_database* db, const char* description) {
130         // Add the string to the string pool
131         off_t offset = loc_stringpool_add(db->pool, description);
132         if (offset < 0)
133                 return offset;
134
135         db->description = offset;
136         return 0;
137 }
138
139 LOC_EXPORT size_t loc_database_count_as(struct loc_database* db) {
140         return db->as_count;
141 }
142
143 static int loc_database_has_as(struct loc_database* db, struct loc_as* as) {
144         for (unsigned int i = 0; i < db->as_count; i++) {
145                 if (loc_as_cmp(as, db->as[i]) == 0)
146                         return i;
147         }
148
149         return -1;
150 }
151
152 static int __loc_as_cmp(const void* as1, const void* as2) {
153         return loc_as_cmp(*(struct loc_as**)as1, *(struct loc_as**)as2);
154 }
155
156 static void loc_database_sort_ases(struct loc_database* db) {
157         qsort(db->as, db->as_count, sizeof(*db->as), __loc_as_cmp);
158 }
159
160 static struct loc_as* __loc_database_add_as(struct loc_database* db, struct loc_as* as) {
161         // Check if AS exists already
162         int i = loc_database_has_as(db, as);
163         if (i >= 0) {
164                 loc_as_unref(as);
165
166                 // Select already existing AS
167                 as = db->as[i];
168
169                 return loc_as_ref(as);
170         }
171
172         db->as_count++;
173
174         // Make space for the new entry
175         db->as = realloc(db->as, sizeof(*db->as) * db->as_count);
176
177         // Add the new entry at the end
178         db->as[db->as_count - 1] = loc_as_ref(as);
179
180         // Sort everything
181         loc_database_sort_ases(db);
182
183         return as;
184 }
185
186 LOC_EXPORT struct loc_as* loc_database_add_as(struct loc_database* db, uint32_t number) {
187         struct loc_as* as;
188         int r = loc_as_new(db->ctx, db->pool, &as, number);
189         if (r)
190                 return NULL;
191
192         return __loc_database_add_as(db, as);
193 }
194
195 static int loc_database_read_magic(struct loc_database* db, FILE* f) {
196         struct loc_database_magic magic;
197
198         // Read from file
199         size_t bytes_read = fread(&magic, 1, sizeof(magic), f);
200
201         // Check if we have been able to read enough data
202         if (bytes_read < sizeof(magic)) {
203                 ERROR(db->ctx, "Could not read enough data to validate magic bytes\n");
204                 DEBUG(db->ctx, "Read %zu bytes, but needed %zu\n", bytes_read, sizeof(magic));
205                 return -ENOMSG;
206         }
207
208         // Compare magic bytes
209         if (memcmp(LOC_DATABASE_MAGIC, magic.magic, strlen(LOC_DATABASE_MAGIC)) == 0) {
210                 DEBUG(db->ctx, "Magic value matches\n");
211
212                 // Parse version
213                 db->version = ntohs(magic.version);
214                 DEBUG(db->ctx, "Database version is %u\n", db->version);
215
216                 return 0;
217         }
218
219         ERROR(db->ctx, "Database format is not compatible\n");
220
221         // Return an error
222         return 1;
223 }
224
225 static int loc_database_read_as_section_v0(struct loc_database* db,
226                 off_t as_offset, size_t as_length, FILE* f) {
227         struct loc_database_as_v0 dbobj;
228
229         // Read from the start of the section
230         int r = fseek(f, as_offset, SEEK_SET);
231         if (r)
232                 return r;
233
234         // Read all ASes
235         size_t as_count = as_length / sizeof(dbobj);
236         for (unsigned int i = 0; i < as_count; i++) {
237                 size_t bytes_read = fread(&dbobj, 1, sizeof(dbobj), f);
238                 if (bytes_read < sizeof(dbobj)) {
239                         ERROR(db->ctx, "Could not read an AS object\n");
240                         return -ENOMSG;
241                 }
242
243                 // Allocate a new AS
244                 struct loc_as* as;
245                 r = loc_as_new_from_database_v0(db->ctx, db->pool, &as, &dbobj);
246                 if (r)
247                         return r;
248
249                 // Attach it to the database
250                 as = __loc_database_add_as(db, as);
251                 loc_as_unref(as);
252         }
253
254         INFO(db->ctx, "Read %zu ASes from the database\n", db->as_count);
255
256         return 0;
257 }
258
259 static int loc_database_read_header_v0(struct loc_database* db, FILE* f) {
260         struct loc_database_header_v0 header;
261
262         // Read from file
263         size_t size = fread(&header, 1, sizeof(header), f);
264
265         if (size < sizeof(header)) {
266                 ERROR(db->ctx, "Could not read enough data for header\n");
267                 return -ENOMSG;
268         }
269
270         // Copy over data
271         db->vendor      = ntohl(header.vendor);
272         db->description = ntohl(header.description);
273
274         // Open pool
275         off_t pool_offset  = ntohl(header.pool_offset);
276         size_t pool_length = ntohl(header.pool_length);
277
278         int r = loc_stringpool_read(db->pool, f, pool_offset, pool_length);
279         if (r)
280                 return r;
281
282         // AS section
283         off_t as_offset  = ntohl(header.as_offset);
284         size_t as_length = ntohl(header.as_length);
285
286         r = loc_database_read_as_section_v0(db, as_offset, as_length, f);
287         if (r)
288                 return r;
289
290         return 0;
291 }
292
293 static int loc_database_read_header(struct loc_database* db, FILE* f) {
294         switch (db->version) {
295                 case 0:
296                         return loc_database_read_header_v0(db, f);
297
298                 default:
299                         ERROR(db->ctx, "Incompatible database version: %u\n", db->version);
300                         return 1;
301         }
302 }
303
304 LOC_EXPORT int loc_database_read(struct loc_database* db, FILE* f) {
305         int r = fseek(f, 0, SEEK_SET);
306         if (r)
307                 return r;
308
309         // Read magic bytes
310         r = loc_database_read_magic(db, f);
311         if (r)
312                 return r;
313
314         // Read the header
315         r = loc_database_read_header(db, f);
316         if (r)
317                 return r;
318
319         return 0;
320 }
321
322 static void loc_database_make_magic(struct loc_database* db, struct loc_database_magic* magic) {
323         // Copy magic bytes
324         for (unsigned int i = 0; i < strlen(LOC_DATABASE_MAGIC); i++)
325                 magic->magic[i] = LOC_DATABASE_MAGIC[i];
326
327         // Set version
328         magic->version = htons(LOC_DATABASE_VERSION);
329 }
330
331 LOC_EXPORT int loc_database_write(struct loc_database* db, FILE* f) {
332         struct loc_database_magic magic;
333         loc_database_make_magic(db, &magic);
334
335         // Make the header
336         struct loc_database_header_v0 header;
337         header.vendor      = htonl(db->vendor);
338         header.description = htonl(db->description);
339
340         int r;
341         off_t offset = 0;
342
343         // Start writing at the beginning of the file
344         r = fseek(f, 0, SEEK_SET);
345         if (r)
346                 return r;
347
348         // Write the magic
349         offset += fwrite(&magic, 1, sizeof(magic), f);
350
351         // Skip the space we need to write the header later
352         r = fseek(f, sizeof(header), SEEK_CUR);
353         if (r) {
354                 DEBUG(db->ctx, "Could not seek to position after header\n");
355                 return r;
356         }
357         offset += sizeof(header);
358
359         // Write all ASes
360         header.as_offset = htonl(offset);
361
362         struct loc_database_as_v0 dbas;
363         for (unsigned int i = 0; i < db->as_count; i++) {
364                 // Convert AS into database format
365                 loc_as_to_database_v0(db->as[i], &dbas);
366
367                 // Write to disk
368                 offset += fwrite(&dbas, 1, sizeof(dbas), f);
369         }
370         header.as_length = htonl(db->as_count * sizeof(dbas));
371
372         // Save the offset of the pool section
373         DEBUG(db->ctx, "Pool starts at %jd bytes\n", offset);
374         header.pool_offset = htonl(offset);
375
376         // Size of the pool
377         size_t pool_length = loc_stringpool_write(db->pool, f);
378         DEBUG(db->ctx, "Pool has a length of %zu bytes\n", pool_length);
379         header.pool_length = htonl(pool_length);
380
381         // Write the header
382         r = fseek(f, sizeof(magic), SEEK_SET);
383         if (r)
384                 return r;
385
386         offset += fwrite(&header, 1, sizeof(header), f);
387
388         return 0;
389 }