177464634d5d9fcff6cdbb37e8efa88f5017dd99
[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 <arpa/inet.h>
18 #include <endian.h>
19 #include <errno.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <time.h>
24
25 #include <loc/format.h>
26 #include <loc/writer.h>
27
28 #include "libloc-private.h"
29 #include "as.h"
30
31 struct loc_writer {
32         struct loc_ctx* ctx;
33         int refcount;
34
35         struct loc_stringpool* pool;
36         off_t vendor;
37         off_t description;
38
39         struct loc_as** as;
40         size_t as_count;
41 };
42
43 LOC_EXPORT int loc_writer_new(struct loc_ctx* ctx, struct loc_writer** writer) {
44         struct loc_writer* w = calloc(1, sizeof(*w));
45         if (!w)
46                 return -ENOMEM;
47
48         w->ctx = loc_ref(ctx);
49         w->refcount = 1;
50
51         int r = loc_stringpool_new(ctx, &w->pool);
52         if (r) {
53                 loc_writer_unref(w);
54                 return r;
55         }
56
57         *writer = w;
58         return 0;
59 }
60
61 LOC_EXPORT struct loc_writer* loc_writer_ref(struct loc_writer* writer) {
62         writer->refcount++;
63
64         return writer;
65 }
66
67 static void loc_writer_free(struct loc_writer* writer) {
68         DEBUG(writer->ctx, "Releasing writer at %p\n", writer);
69
70         // Unref all AS
71         for (unsigned int i = 0; i < writer->as_count; i++) {
72                 loc_as_unref(writer->as[i]);
73         }
74
75         // Unref the string pool
76         loc_stringpool_unref(writer->pool);
77
78         loc_unref(writer->ctx);
79         free(writer);
80 }
81
82 LOC_EXPORT struct loc_writer* loc_writer_unref(struct loc_writer* writer) {
83         if (--writer->refcount > 0)
84                 return writer;
85
86         loc_writer_free(writer);
87
88         return NULL;
89 }
90
91 LOC_EXPORT const char* loc_writer_get_vendor(struct loc_writer* writer) {
92         return loc_stringpool_get(writer->pool, writer->vendor);
93 }
94
95 LOC_EXPORT int loc_writer_set_vendor(struct loc_writer* writer, const char* vendor) {
96         // Add the string to the string pool
97         off_t offset = loc_stringpool_add(writer->pool, vendor);
98         if (offset < 0)
99                 return offset;
100
101         writer->vendor = offset;
102         return 0;
103 }
104
105 LOC_EXPORT const char* loc_writer_get_description(struct loc_writer* writer) {
106         return loc_stringpool_get(writer->pool, writer->description);
107 }
108
109 LOC_EXPORT int loc_writer_set_description(struct loc_writer* writer, const char* description) {
110         // Add the string to the string pool
111         off_t offset = loc_stringpool_add(writer->pool, description);
112         if (offset < 0)
113                 return offset;
114
115         writer->description = offset;
116         return 0;
117 }
118
119 static int __loc_as_cmp(const void* as1, const void* as2) {
120         return loc_as_cmp(*(struct loc_as**)as1, *(struct loc_as**)as2);
121 }
122
123 LOC_EXPORT int loc_writer_add_as(struct loc_writer* writer, struct loc_as** as, uint32_t number) {
124         int r = loc_as_new(writer->ctx, writer->pool, as, number);
125         if (r)
126                 return r;
127
128         // We have a new AS to add
129         writer->as_count++;
130
131         // Make space
132         writer->as = realloc(writer->as, sizeof(*writer->as) * writer->as_count);
133         if (!writer->as)
134                 return -ENOMEM;
135
136         // Add as last element
137         writer->as[writer->as_count - 1] = loc_as_ref(*as);
138
139         // Sort everything
140         qsort(writer->as, writer->as_count, sizeof(*writer->as), __loc_as_cmp);
141
142         return 0;
143 }
144
145 static void make_magic(struct loc_writer* writer, struct loc_database_magic* magic) {
146         // Copy magic bytes
147         for (unsigned int i = 0; i < strlen(LOC_DATABASE_MAGIC); i++)
148                 magic->magic[i] = LOC_DATABASE_MAGIC[i];
149
150         // Set version
151         magic->version = htons(LOC_DATABASE_VERSION);
152 }
153
154 static void align_page_boundary(off_t* offset, FILE* f) {
155         // Move to next page boundary
156         while (*offset % LOC_DATABASE_PAGE_SIZE > 0)
157                 *offset += fwrite("", 1, 1, f);
158 }
159
160 static int loc_database_write_pool(struct loc_writer* writer,
161                 struct loc_database_header_v0* header, off_t* offset, FILE* f) {
162         // Save the offset of the pool section
163         DEBUG(writer->ctx, "Pool starts at %jd bytes\n", *offset);
164         header->pool_offset = htonl(*offset);
165
166         // Write the pool
167         size_t pool_length = loc_stringpool_write(writer->pool, f);
168         *offset += pool_length;
169
170         DEBUG(writer->ctx, "Pool has a length of %zu bytes\n", pool_length);
171         header->pool_length = htonl(pool_length);
172
173         return 0;
174 }
175
176 static int loc_database_write_as_section(struct loc_writer* writer,
177                 struct loc_database_header_v0* header, off_t* offset, FILE* f) {
178         DEBUG(writer->ctx, "AS section starts at %jd bytes\n", *offset);
179         header->as_offset = htonl(*offset);
180
181         size_t as_length = 0;
182
183         struct loc_database_as_v0 as;
184         for (unsigned int i = 0; i < writer->as_count; i++) {
185                 // Convert AS into database format
186                 loc_as_to_database_v0(writer->as[i], &as);
187
188                 // Write to disk
189                 offset += fwrite(&as, 1, sizeof(as), f);
190                 as_length += sizeof(as);
191         }
192
193         DEBUG(writer->ctx, "AS section has a length of %zu bytes\n", as_length);
194         header->as_length = htonl(as_length);
195
196         return 0;
197 }
198
199 LOC_EXPORT int loc_writer_write(struct loc_writer* writer, FILE* f) {
200         struct loc_database_magic magic;
201         make_magic(writer, &magic);
202
203         // Make the header
204         struct loc_database_header_v0 header;
205         header.vendor      = htonl(writer->vendor);
206         header.description = htonl(writer->description);
207
208         time_t now = time(NULL);
209         header.created_at = htobe64(now);
210
211         int r;
212         off_t offset = 0;
213
214         // Start writing at the beginning of the file
215         r = fseek(f, 0, SEEK_SET);
216         if (r)
217                 return r;
218
219         // Write the magic
220         offset += fwrite(&magic, 1, sizeof(magic), f);
221
222         // Skip the space we need to write the header later
223         r = fseek(f, sizeof(header), SEEK_CUR);
224         if (r) {
225                 DEBUG(writer->ctx, "Could not seek to position after header\n");
226                 return r;
227         }
228         offset += sizeof(header);
229
230         align_page_boundary(&offset, f);
231
232         // Write all ASes
233         r = loc_database_write_as_section(writer, &header, &offset, f);
234         if (r)
235                 return r;
236
237         align_page_boundary(&offset, f);
238
239         // Write pool
240         r = loc_database_write_pool(writer, &header, &offset, f);
241         if (r)
242                 return r;
243
244         // Write the header
245         r = fseek(f, sizeof(magic), SEEK_SET);
246         if (r)
247                 return r;
248
249         offset += fwrite(&header, 1, sizeof(header), f);
250
251         return 0;
252 }