]> git.ipfire.org Git - people/ms/libloc.git/blob - src/database.c
Move all database format definition into format.h
[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 }