Split database into a writer and reader
[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
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
31struct 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
43LOC_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, 1024 * 1024);
52 if (r) {
53 loc_writer_unref(w);
54 return r;
55 }
56
57 *writer = w;
58 return 0;
59}
60
61LOC_EXPORT struct loc_writer* loc_writer_ref(struct loc_writer* writer) {
62 writer->refcount++;
63
64 return writer;
65}
66
67static 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
82LOC_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
91LOC_EXPORT const char* loc_writer_get_vendor(struct loc_writer* writer) {
92 return loc_stringpool_get(writer->pool, writer->vendor);
93}
94
95LOC_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
105LOC_EXPORT const char* loc_writer_get_description(struct loc_writer* writer) {
106 return loc_stringpool_get(writer->pool, writer->description);
107}
108
109LOC_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
119static 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
123LOC_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
145static 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
154static 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
160static 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
176static 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
199LOC_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}