Draft initial database format
[people/ms/libloc.git] / src / database.c
CommitLineData
2601e83e
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 <errno.h>
19#include <stddef.h>
20#include <stdint.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24#include <sys/types.h>
25
26#include <loc/libloc.h>
27
28#include "libloc-private.h"
29#include "database.h"
30#include "stringpool.h"
31
32struct loc_database {
33 struct loc_ctx* ctx;
34 int refcount;
35
36 unsigned int version;
37 off_t vendor;
38 off_t description;
39
40 struct loc_stringpool* pool;
41};
42
43const char* LOC_DATABASE_MAGIC = "LOCDBXX";
44unsigned int LOC_DATABASE_VERSION = 0;
45
46struct loc_database_magic {
47 char magic[7];
48
49 // Database version information
50 uint8_t version;
51};
52
53struct loc_database_header_v0 {
54 // Vendor who created the database
55 uint32_t vendor;
56
57 // Description of the database
58 uint32_t description;
59
60 // Tells us where the pool starts
61 uint32_t pool_offset;
62 uint32_t pool_length;
63};
64
65LOC_EXPORT int loc_database_new(struct loc_ctx* ctx, struct loc_database** database, size_t pool_size) {
66 struct loc_database* db = calloc(1, sizeof(*db));
67 if (!db)
68 return -ENOMEM;
69
70 // Reference context
71 db->ctx = loc_ref(ctx);
72 db->refcount = 1;
73
74 DEBUG(db->ctx, "Database allocated at %p\n", db);
75
76 // Create string pool
77 int r = loc_stringpool_new(db->ctx, &db->pool, pool_size);
78 if (r) {
79 loc_database_unref(db);
80 return r;
81 }
82
83 *database = db;
84
85 return 0;
86}
87
88LOC_EXPORT int loc_database_open(struct loc_ctx* ctx, struct loc_database** database, FILE* f) {
89 int r = loc_database_new(ctx, database, 0);
90 if (r)
91 return r;
92
93 return loc_database_read(*database, f);
94}
95
96LOC_EXPORT struct loc_database* loc_database_ref(struct loc_database* db) {
97 db->refcount++;
98
99 return db;
100}
101
102static void loc_database_free(struct loc_database* db) {
103 DEBUG(db->ctx, "Releasing database %p\n", db);
104
105 loc_stringpool_unref(db->pool);
106
107 loc_unref(db->ctx);
108 free(db);
109}
110
111LOC_EXPORT struct loc_database* loc_database_unref(struct loc_database* db) {
112 if (--db->refcount > 0)
113 return NULL;
114
115 loc_database_free(db);
116 return NULL;
117}
118
119LOC_EXPORT const char* loc_database_get_vendor(struct loc_database* db) {
120 return loc_stringpool_get(db->pool, db->vendor);
121}
122
123LOC_EXPORT int loc_database_set_vendor(struct loc_database* db, const char* vendor) {
124 // Add the string to the string pool
125 off_t offset = loc_stringpool_add(db->pool, vendor);
126 if (offset < 0)
127 return offset;
128
129 db->vendor = offset;
130 return 0;
131}
132
133LOC_EXPORT const char* loc_database_get_description(struct loc_database* db) {
134 return loc_stringpool_get(db->pool, db->description);
135}
136
137LOC_EXPORT int loc_database_set_description(struct loc_database* db, const char* description) {
138 // Add the string to the string pool
139 off_t offset = loc_stringpool_add(db->pool, description);
140 if (offset < 0)
141 return offset;
142
143 db->description = offset;
144 return 0;
145}
146
147static int loc_database_read_magic(struct loc_database* db, FILE* f) {
148 struct loc_database_magic magic;
149
150 // Read from file
151 size_t bytes_read = fread(&magic, 1, sizeof(magic), f);
152
153 // Check if we have been able to read enough data
154 if (bytes_read < sizeof(magic)) {
155 ERROR(db->ctx, "Could not read enough data to validate magic bytes\n");
156 DEBUG(db->ctx, "Read %zu bytes, but needed %zu\n", bytes_read, sizeof(magic));
157 return -ENOMSG;
158 }
159
160 // Compare magic bytes
161 if (memcmp(LOC_DATABASE_MAGIC, magic.magic, strlen(LOC_DATABASE_MAGIC)) == 0) {
162 DEBUG(db->ctx, "Magic value matches\n");
163
164 // Parse version
165 db->version = ntohs(magic.version);
166 DEBUG(db->ctx, "Database version is %u\n", db->version);
167
168 return 0;
169 }
170
171 ERROR(db->ctx, "Database format is not compatible\n");
172
173 // Return an error
174 return 1;
175}
176
177static int loc_database_read_header_v0(struct loc_database* db, FILE* f) {
178 struct loc_database_header_v0 header;
179
180 // Read from file
181 size_t size = fread(&header, 1, sizeof(header), f);
182
183 if (size < sizeof(header)) {
184 ERROR(db->ctx, "Could not read enough data for header\n");
185 return -ENOMSG;
186 }
187
188 // Copy over data
189 db->vendor = ntohl(header.vendor);
190 db->description = ntohl(header.description);
191
192 // Open pool
193 off_t pool_offset = ntohl(header.pool_offset);
194 size_t pool_length = ntohl(header.pool_length);
195
196 int r = loc_stringpool_read(db->pool, f, pool_offset, pool_length);
197 if (r)
198 return r;
199
200 return 0;
201}
202
203static int loc_database_read_header(struct loc_database* db, FILE* f) {
204 switch (db->version) {
205 case 0:
206 return loc_database_read_header_v0(db, f);
207
208 default:
209 ERROR(db->ctx, "Incompatible database version: %u\n", db->version);
210 return 1;
211 }
212}
213
214LOC_EXPORT int loc_database_read(struct loc_database* db, FILE* f) {
215 int r = fseek(f, 0, SEEK_SET);
216 if (r)
217 return r;
218
219 // Read magic bytes
220 r = loc_database_read_magic(db, f);
221 if (r)
222 return r;
223
224 // Read the header
225 r = loc_database_read_header(db, f);
226 if (r)
227 return r;
228
229 return 0;
230}
231
232static void loc_database_make_magic(struct loc_database* db, struct loc_database_magic* magic) {
233 // Copy magic bytes
234 for (unsigned int i = 0; i < strlen(LOC_DATABASE_MAGIC); i++)
235 magic->magic[i] = LOC_DATABASE_MAGIC[i];
236
237 // Set version
238 magic->version = htons(LOC_DATABASE_VERSION);
239}
240
241LOC_EXPORT int loc_database_write(struct loc_database* db, FILE* f) {
242 struct loc_database_magic magic;
243 loc_database_make_magic(db, &magic);
244
245 // Make the header
246 struct loc_database_header_v0 header;
247 header.vendor = htonl(db->vendor);
248 header.description = htonl(db->description);
249
250 int r;
251 off_t offset = 0;
252
253 // Start writing at the beginning of the file
254 r = fseek(f, 0, SEEK_SET);
255 if (r)
256 return r;
257
258 // Write the magic
259 offset += fwrite(&magic, 1, sizeof(magic), f);
260
261 // Skip the space we need to write the header later
262 r = fseek(f, sizeof(header), SEEK_CUR);
263 if (r) {
264 DEBUG(db->ctx, "Could not seek to position after header\n");
265 return r;
266 }
267 offset += sizeof(header);
268
269 // Save the offset of the pool section
270 DEBUG(db->ctx, "Pool starts at %jd bytes\n", offset);
271 header.pool_offset = htonl(offset);
272
273 // Size of the pool
274 size_t pool_length = loc_stringpool_write(db->pool, f);
275 DEBUG(db->ctx, "Pool has a length of %zu bytes\n", pool_length);
276 header.pool_length = htonl(pool_length);
277
278 // Write the header
279 r = fseek(f, sizeof(magic), SEEK_SET);
280 if (r)
281 return r;
282
283 offset += fwrite(&header, 1, sizeof(header), f);
284
285 return 0;
286}