database: Copy the file pointer so we can keep the file open
[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 LOC_EXPORT int loc_database_write(struct loc_database* db, FILE* f) {
350 struct loc_database_magic magic;
351 loc_database_make_magic(db, &magic);
352
353 // Make the header
354 struct loc_database_header_v0 header;
355 header.vendor = htonl(db->vendor);
356 header.description = htonl(db->description);
357
358 int r;
359 off_t offset = 0;
360
361 // Start writing at the beginning of the file
362 r = fseek(f, 0, SEEK_SET);
363 if (r)
364 return r;
365
366 // Write the magic
367 offset += fwrite(&magic, 1, sizeof(magic), f);
368
369 // Skip the space we need to write the header later
370 r = fseek(f, sizeof(header), SEEK_CUR);
371 if (r) {
372 DEBUG(db->ctx, "Could not seek to position after header\n");
373 return r;
374 }
375 offset += sizeof(header);
376
377 // Write all ASes
378 header.as_offset = htonl(offset);
379
380 struct loc_database_as_v0 dbas;
381 for (unsigned int i = 0; i < db->as_count; i++) {
382 // Convert AS into database format
383 loc_as_to_database_v0(db->as[i], &dbas);
384
385 // Write to disk
386 offset += fwrite(&dbas, 1, sizeof(dbas), f);
387 }
388 header.as_length = htonl(db->as_count * sizeof(dbas));
389
390 // Save the offset of the pool section
391 DEBUG(db->ctx, "Pool starts at %jd bytes\n", offset);
392 header.pool_offset = htonl(offset);
393
394 // Size of the pool
395 size_t pool_length = loc_stringpool_write(db->pool, f);
396 DEBUG(db->ctx, "Pool has a length of %zu bytes\n", pool_length);
397 header.pool_length = htonl(pool_length);
398
399 // Write the header
400 r = fseek(f, sizeof(magic), SEEK_SET);
401 if (r)
402 return r;
403
404 offset += fwrite(&header, 1, sizeof(header), f);
405
406 return 0;
407 }