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