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