]> git.ipfire.org Git - people/ms/libloc.git/blame - src/writer.c
writer: Write countries before pool is being written
[people/ms/libloc.git] / src / writer.c
CommitLineData
c182393f
MT
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
c182393f
MT
17#include <endian.h>
18#include <errno.h>
19#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
438db08c 22#include <sys/queue.h>
c182393f
MT
23#include <time.h>
24
9fc7f001
MT
25#include <loc/libloc.h>
26#include <loc/as.h>
ec684c1a 27#include <loc/country.h>
c182393f 28#include <loc/format.h>
3b5f4af2 29#include <loc/network.h>
9fc7f001 30#include <loc/private.h>
c182393f
MT
31#include <loc/writer.h>
32
c182393f
MT
33struct loc_writer {
34 struct loc_ctx* ctx;
35 int refcount;
36
37 struct loc_stringpool* pool;
38 off_t vendor;
39 off_t description;
4bf49d00 40 off_t license;
c182393f
MT
41
42 struct loc_as** as;
43 size_t as_count;
3b5f4af2 44
ec684c1a
MT
45 struct loc_country** countries;
46 size_t countries_count;
47
3b5f4af2 48 struct loc_network_tree* networks;
c182393f
MT
49};
50
51LOC_EXPORT int loc_writer_new(struct loc_ctx* ctx, struct loc_writer** writer) {
52 struct loc_writer* w = calloc(1, sizeof(*w));
53 if (!w)
54 return -ENOMEM;
55
56 w->ctx = loc_ref(ctx);
57 w->refcount = 1;
58
0e974d4b 59 int r = loc_stringpool_new(ctx, &w->pool);
c182393f
MT
60 if (r) {
61 loc_writer_unref(w);
62 return r;
63 }
64
3b5f4af2
MT
65 // Initialize the network tree
66 r = loc_network_tree_new(ctx, &w->networks);
67 if (r) {
68 loc_writer_unref(w);
69 return r;
70 }
71
c182393f
MT
72 *writer = w;
73 return 0;
74}
75
76LOC_EXPORT struct loc_writer* loc_writer_ref(struct loc_writer* writer) {
77 writer->refcount++;
78
79 return writer;
80}
81
82static void loc_writer_free(struct loc_writer* writer) {
83 DEBUG(writer->ctx, "Releasing writer at %p\n", writer);
84
85 // Unref all AS
86 for (unsigned int i = 0; i < writer->as_count; i++) {
87 loc_as_unref(writer->as[i]);
88 }
89
3b5f4af2
MT
90 // Release network tree
91 if (writer->networks)
92 loc_network_tree_unref(writer->networks);
93
c182393f
MT
94 // Unref the string pool
95 loc_stringpool_unref(writer->pool);
96
97 loc_unref(writer->ctx);
98 free(writer);
99}
100
101LOC_EXPORT struct loc_writer* loc_writer_unref(struct loc_writer* writer) {
102 if (--writer->refcount > 0)
103 return writer;
104
105 loc_writer_free(writer);
106
107 return NULL;
108}
109
110LOC_EXPORT const char* loc_writer_get_vendor(struct loc_writer* writer) {
111 return loc_stringpool_get(writer->pool, writer->vendor);
112}
113
114LOC_EXPORT int loc_writer_set_vendor(struct loc_writer* writer, const char* vendor) {
115 // Add the string to the string pool
116 off_t offset = loc_stringpool_add(writer->pool, vendor);
117 if (offset < 0)
118 return offset;
119
120 writer->vendor = offset;
121 return 0;
122}
123
124LOC_EXPORT const char* loc_writer_get_description(struct loc_writer* writer) {
125 return loc_stringpool_get(writer->pool, writer->description);
126}
127
128LOC_EXPORT int loc_writer_set_description(struct loc_writer* writer, const char* description) {
129 // Add the string to the string pool
130 off_t offset = loc_stringpool_add(writer->pool, description);
131 if (offset < 0)
132 return offset;
133
134 writer->description = offset;
135 return 0;
136}
137
4bf49d00
MT
138LOC_EXPORT const char* loc_writer_get_license(struct loc_writer* writer) {
139 return loc_stringpool_get(writer->pool, writer->license);
140}
141
142LOC_EXPORT int loc_writer_set_license(struct loc_writer* writer, const char* license) {
143 // Add the string to the string pool
144 off_t offset = loc_stringpool_add(writer->pool, license);
145 if (offset < 0)
146 return offset;
147
148 writer->license = offset;
149 return 0;
150}
151
c182393f
MT
152static 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
156LOC_EXPORT int loc_writer_add_as(struct loc_writer* writer, struct loc_as** as, uint32_t number) {
29bd8211 157 int r = loc_as_new(writer->ctx, as, number);
c182393f
MT
158 if (r)
159 return r;
160
161 // We have a new AS to add
162 writer->as_count++;
163
164 // Make space
165 writer->as = realloc(writer->as, sizeof(*writer->as) * writer->as_count);
166 if (!writer->as)
167 return -ENOMEM;
168
169 // Add as last element
170 writer->as[writer->as_count - 1] = loc_as_ref(*as);
171
172 // Sort everything
173 qsort(writer->as, writer->as_count, sizeof(*writer->as), __loc_as_cmp);
174
175 return 0;
176}
177
3b5f4af2
MT
178LOC_EXPORT int loc_writer_add_network(struct loc_writer* writer, struct loc_network** network, const char* string) {
179 int r;
180
181 // Create a new network object
182 r = loc_network_new_from_string(writer->ctx, network, string);
183 if (r)
184 return r;
185
186 // Add it to the local tree
187 return loc_network_tree_add_network(writer->networks, *network);
188}
189
ec684c1a
MT
190static int __loc_country_cmp(const void* country1, const void* country2) {
191 return loc_country_cmp(*(struct loc_country**)country1, *(struct loc_country**)country2);
192}
193
194LOC_EXPORT int loc_writer_add_country(struct loc_writer* writer, struct loc_country** country, const char* country_code) {
195 int r = loc_country_new(writer->ctx, country, country_code);
196 if (r)
197 return r;
198
199 // We have a new country to add
200 writer->countries_count++;
201
202 // Make space
203 writer->countries = realloc(writer->countries, sizeof(*writer->countries) * writer->countries_count);
204 if (!writer->countries)
205 return -ENOMEM;
206
207 // Add as last element
208 writer->countries[writer->countries_count - 1] = loc_country_ref(*country);
209
210 // Sort everything
211 qsort(writer->countries, writer->countries_count, sizeof(*writer->countries), __loc_country_cmp);
212
213 return 0;
214}
215
c182393f
MT
216static void make_magic(struct loc_writer* writer, struct loc_database_magic* magic) {
217 // Copy magic bytes
218 for (unsigned int i = 0; i < strlen(LOC_DATABASE_MAGIC); i++)
219 magic->magic[i] = LOC_DATABASE_MAGIC[i];
220
221 // Set version
0676cd80 222 magic->version = htobe16(LOC_DATABASE_VERSION);
c182393f
MT
223}
224
225static void align_page_boundary(off_t* offset, FILE* f) {
226 // Move to next page boundary
227 while (*offset % LOC_DATABASE_PAGE_SIZE > 0)
228 *offset += fwrite("", 1, 1, f);
229}
230
231static int loc_database_write_pool(struct loc_writer* writer,
232 struct loc_database_header_v0* header, off_t* offset, FILE* f) {
233 // Save the offset of the pool section
234 DEBUG(writer->ctx, "Pool starts at %jd bytes\n", *offset);
0676cd80 235 header->pool_offset = htobe32(*offset);
c182393f
MT
236
237 // Write the pool
238 size_t pool_length = loc_stringpool_write(writer->pool, f);
239 *offset += pool_length;
240
241 DEBUG(writer->ctx, "Pool has a length of %zu bytes\n", pool_length);
0676cd80 242 header->pool_length = htobe32(pool_length);
c182393f
MT
243
244 return 0;
245}
246
247static int loc_database_write_as_section(struct loc_writer* writer,
248 struct loc_database_header_v0* header, off_t* offset, FILE* f) {
249 DEBUG(writer->ctx, "AS section starts at %jd bytes\n", *offset);
0676cd80 250 header->as_offset = htobe32(*offset);
c182393f
MT
251
252 size_t as_length = 0;
253
254 struct loc_database_as_v0 as;
255 for (unsigned int i = 0; i < writer->as_count; i++) {
256 // Convert AS into database format
29bd8211 257 loc_as_to_database_v0(writer->as[i], writer->pool, &as);
c182393f
MT
258
259 // Write to disk
65862405 260 *offset += fwrite(&as, 1, sizeof(as), f);
c182393f
MT
261 as_length += sizeof(as);
262 }
263
264 DEBUG(writer->ctx, "AS section has a length of %zu bytes\n", as_length);
0676cd80 265 header->as_length = htobe32(as_length);
c182393f 266
fda89f24
MT
267 align_page_boundary(offset, f);
268
c182393f
MT
269 return 0;
270}
271
438db08c
MT
272struct node {
273 TAILQ_ENTRY(node) nodes;
f3e02bc5 274
438db08c 275 struct loc_network_tree_node* node;
f3e02bc5 276
438db08c
MT
277 // Indices of the child nodes
278 uint32_t index_zero;
279 uint32_t index_one;
280};
f3e02bc5 281
438db08c
MT
282static struct node* make_node(struct loc_network_tree_node* node) {
283 struct node* n = malloc(sizeof(*n));
284 if (!n)
285 return NULL;
f3e02bc5 286
438db08c
MT
287 n->node = loc_network_tree_node_ref(node);
288 n->index_zero = n->index_one = 0;
289
290 return n;
291}
292
293static void free_node(struct node* node) {
294 loc_network_tree_node_unref(node->node);
295
296 free(node);
297}
298
299struct network {
300 TAILQ_ENTRY(network) networks;
301
302 struct loc_network* network;
303};
304
305static struct network* make_network(struct loc_network* network) {
306 struct network* n = malloc(sizeof(*n));
307 if (!n)
308 return NULL;
309
310 n->network = loc_network_ref(network);
311
312 return n;
f3e02bc5
MT
313}
314
438db08c
MT
315static void free_network(struct network* network) {
316 loc_network_unref(network->network);
317
318 free(network);
319}
320
321static int loc_database_write_networks(struct loc_writer* writer,
f3e02bc5 322 struct loc_database_header_v0* header, off_t* offset, FILE* f) {
438db08c
MT
323 // Write the network tree
324 DEBUG(writer->ctx, "Network tree starts at %jd bytes\n", *offset);
325 header->network_tree_offset = htobe32(*offset);
f3e02bc5 326
438db08c
MT
327 size_t network_tree_length = 0;
328 size_t network_data_length = 0;
f3e02bc5 329
438db08c
MT
330 struct node* node;
331 struct node* child_node;
332
333 uint32_t index = 0;
334 uint32_t network_index = 0;
335
336 struct loc_database_network_v0 db_network;
337 struct loc_database_network_node_v0 db_node;
338
339 // Initialize queue for nodes
340 TAILQ_HEAD(node_t, node) nodes;
341 TAILQ_INIT(&nodes);
342
343 // Initialize queue for networks
344 TAILQ_HEAD(network_t, network) networks;
345 TAILQ_INIT(&networks);
346
347 // Add root
348 struct loc_network_tree_node* root = loc_network_tree_get_root(writer->networks);
349 node = make_node(root);
350
351 TAILQ_INSERT_TAIL(&nodes, node, nodes);
352
353 while (!TAILQ_EMPTY(&nodes)) {
354 // Pop first node in list
355 node = TAILQ_FIRST(&nodes);
356 TAILQ_REMOVE(&nodes, node, nodes);
357
358 DEBUG(writer->ctx, "Processing node %p\n", node);
359
360 // Get child nodes
361 struct loc_network_tree_node* node_zero = loc_network_tree_node_get(node->node, 0);
362 if (node_zero) {
363 node->index_zero = ++index;
364
365 child_node = make_node(node_zero);
366 loc_network_tree_node_unref(node_zero);
367
368 TAILQ_INSERT_TAIL(&nodes, child_node, nodes);
369 }
370
371 struct loc_network_tree_node* node_one = loc_network_tree_node_get(node->node, 1);
372 if (node_one) {
373 node->index_one = ++index;
374
375 child_node = make_node(node_one);
376 loc_network_tree_node_unref(node_one);
377
378 TAILQ_INSERT_TAIL(&nodes, child_node, nodes);
379 }
380
381 // Prepare what we are writing to disk
39a55353
MT
382 db_node.zero = htobe32(node->index_zero);
383 db_node.one = htobe32(node->index_one);
384
438db08c
MT
385 if (loc_network_tree_node_is_leaf(node->node)) {
386 struct loc_network* network = loc_network_tree_node_get_network(node->node);
387
388 // Append network to be written out later
389 struct network* nw = make_network(network);
390 TAILQ_INSERT_TAIL(&networks, nw, networks);
391
39a55353 392 db_node.network = htobe32(network_index++);
438db08c
MT
393 loc_network_unref(network);
394 } else {
39a55353 395 db_node.network = htobe32(0xffffffff);
438db08c
MT
396 }
397
398 // Write the current node
399 DEBUG(writer->ctx, "Writing node %p (0 = %d, 1 = %d)\n",
400 node, node->index_zero, node->index_one);
401
402 *offset += fwrite(&db_node, 1, sizeof(db_node), f);
403 network_tree_length += sizeof(db_node);
404
405 free_node(node);
406 }
407
408 loc_network_tree_node_unref(root);
409
410 header->network_tree_length = htobe32(network_tree_length);
411
412 align_page_boundary(offset, f);
413
414 DEBUG(writer->ctx, "Networks data section starts at %jd bytes\n", *offset);
415 header->network_data_offset = htobe32(*offset);
416
417 // We have now written the entire tree and have all networks
418 // in a queue in order as they are indexed
419 while (!TAILQ_EMPTY(&networks)) {
420 struct network* nw = TAILQ_FIRST(&networks);
421 TAILQ_REMOVE(&networks, nw, networks);
422
423 // Prepare what we are writing to disk
424 int r = loc_network_to_database_v0(nw->network, &db_network);
425 if (r)
426 return r;
427
428 *offset += fwrite(&db_network, 1, sizeof(db_network), f);
429 network_data_length += sizeof(db_network);
430
431 free_network(nw);
432 }
f3e02bc5 433
438db08c 434 header->network_data_length = htobe32(network_data_length);
f3e02bc5 435
fda89f24
MT
436 align_page_boundary(offset, f);
437
f3e02bc5
MT
438 return 0;
439}
440
ec684c1a
MT
441static int loc_database_write_countries(struct loc_writer* writer,
442 struct loc_database_header_v0* header, off_t* offset, FILE* f) {
443 DEBUG(writer->ctx, "Countries section starts at %jd bytes\n", *offset);
444 header->countries_offset = htobe32(*offset);
445
446 size_t countries_length = 0;
447
448 struct loc_database_country_v0 country;
449 for (unsigned int i = 0; i < writer->countries_count; i++) {
450 // Convert country into database format
451 loc_country_to_database_v0(writer->countries[i], writer->pool, &country);
452
453 // Write to disk
454 *offset += fwrite(&country, 1, sizeof(country), f);
455 countries_length += sizeof(country);
456 }
457
458 DEBUG(writer->ctx, "Countries section has a length of %zu bytes\n", countries_length);
459 header->countries_length = htobe32(countries_length);
460
461 align_page_boundary(offset, f);
462
463 return 0;
464}
465
c182393f
MT
466LOC_EXPORT int loc_writer_write(struct loc_writer* writer, FILE* f) {
467 struct loc_database_magic magic;
468 make_magic(writer, &magic);
469
470 // Make the header
471 struct loc_database_header_v0 header;
0676cd80
MT
472 header.vendor = htobe32(writer->vendor);
473 header.description = htobe32(writer->description);
4bf49d00 474 header.license = htobe32(writer->license);
c182393f
MT
475
476 time_t now = time(NULL);
477 header.created_at = htobe64(now);
478
479 int r;
480 off_t offset = 0;
481
482 // Start writing at the beginning of the file
483 r = fseek(f, 0, SEEK_SET);
484 if (r)
485 return r;
486
487 // Write the magic
488 offset += fwrite(&magic, 1, sizeof(magic), f);
489
490 // Skip the space we need to write the header later
491 r = fseek(f, sizeof(header), SEEK_CUR);
492 if (r) {
493 DEBUG(writer->ctx, "Could not seek to position after header\n");
494 return r;
495 }
496 offset += sizeof(header);
497
498 align_page_boundary(&offset, f);
499
500 // Write all ASes
501 r = loc_database_write_as_section(writer, &header, &offset, f);
502 if (r)
503 return r;
504
f3e02bc5 505 // Write all networks
438db08c 506 r = loc_database_write_networks(writer, &header, &offset, f);
f3e02bc5
MT
507 if (r)
508 return r;
509
47bea941
MT
510 // Write countries
511 r = loc_database_write_countries(writer, &header, &offset, f);
c182393f
MT
512 if (r)
513 return r;
514
47bea941
MT
515 // Write pool
516 r = loc_database_write_pool(writer, &header, &offset, f);
ec684c1a
MT
517 if (r)
518 return r;
519
c182393f
MT
520 // Write the header
521 r = fseek(f, sizeof(magic), SEEK_SET);
522 if (r)
523 return r;
524
525 offset += fwrite(&header, 1, sizeof(header), f);
526
527 return 0;
528}