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