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