Draft initial database format
[people/ms/libloc.git] / src / database.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 <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
32 struct 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
43 const char* LOC_DATABASE_MAGIC = "LOCDBXX";
44 unsigned int LOC_DATABASE_VERSION = 0;
45
46 struct loc_database_magic {
47         char magic[7];
48
49         // Database version information
50         uint8_t version;
51 };
52
53 struct 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
65 LOC_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
88 LOC_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
96 LOC_EXPORT struct loc_database* loc_database_ref(struct loc_database* db) {
97         db->refcount++;
98
99         return db;
100 }
101
102 static 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
111 LOC_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
119 LOC_EXPORT const char* loc_database_get_vendor(struct loc_database* db) {
120         return loc_stringpool_get(db->pool, db->vendor);
121 }
122
123 LOC_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
133 LOC_EXPORT const char* loc_database_get_description(struct loc_database* db) {
134         return loc_stringpool_get(db->pool, db->description);
135 }
136
137 LOC_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
147 static 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
177 static 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
203 static 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
214 LOC_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
232 static 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
241 LOC_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 }