make depend
[darkstat] / db.c
1 /* darkstat 3
2 *
3 * db.c: load and save in-memory database from/to file
4 * copyright (c) 2007-2011 Ben Stewart, Emil Mikulic.
5 *
6 * You may use, modify and redistribute this file under the terms of the
7 * GNU General Public License version 2. (see COPYING.GPL)
8 */
9
10 #define _GNU_SOURCE 1 /* for O_NOFOLLOW in Linux */
11
12 #include <sys/types.h>
13 #include <netinet/in.h> /* for ntohs() and friends */
14 #include <assert.h>
15 #include <fcntl.h>
16 #include <string.h>
17 #include <unistd.h>
18
19 #include "cdefs.h"
20 #include "err.h"
21 #include "hosts_db.h"
22 #include "graph_db.h"
23 #include "db.h"
24
25 static const unsigned char export_file_header[] = {0xDA, 0x31, 0x41, 0x59};
26 static const unsigned char export_tag_hosts_ver1[] = {0xDA, 'H', 'S', 0x01};
27 static const unsigned char export_tag_graph_ver1[] = {0xDA, 'G', 'R', 0x01};
28
29 #ifndef swap64
30 static uint64_t swap64(uint64_t _x) {
31 /* this is __bswap64 from:
32 * $FreeBSD: src/sys/i386/include/endian.h,v 1.41$
33 */
34 return ((_x >> 56) | ((_x >> 40) & 0xff00) | ((_x >> 24) & 0xff0000) |
35 ((_x >> 8) & 0xff000000) | ((_x << 8) & ((uint64_t)0xff << 32)) |
36 ((_x << 24) & ((uint64_t)0xff << 40)) |
37 ((_x << 40) & ((uint64_t)0xff << 48)) | ((_x << 56)));
38 }
39 #endif
40
41 #define ntoh64 hton64
42 static inline uint64_t hton64(const uint64_t ho) {
43 if (ntohs(0x1234) == 0x1234)
44 return ho;
45 else
46 return swap64(ho);
47 }
48
49 void
50 test_64order(void)
51 {
52 static const char str[] = { 0x79,0x74,0x69,0x63,0x6b,0x72,0x65,0x6a };
53 uint64_t no, ho;
54
55 assert(sizeof(no) == 8);
56 memcpy(&no, str, 8);
57 ho = ntoh64(no);
58 assert(ho == 8751735851613054314ULL);
59 assert(hton64(ntoh64(no)) == no);
60 }
61
62 /* ---------------------------------------------------------------------------
63 * Read-from-file helpers. They all return 0 on failure, and 1 on success.
64 */
65
66 unsigned int
67 xtell(const int fd)
68 {
69 off_t ofs = lseek(fd, 0, SEEK_CUR);
70 if (ofs == -1)
71 err(1, "lseek(0, SEEK_CUR) failed");
72 return (unsigned int)ofs;
73 }
74
75 /* Read <len> bytes from <fd>, warn() and return 0 on failure,
76 * or return 1 for success.
77 */
78 int
79 readn(const int fd, void *dest, const size_t len)
80 {
81 ssize_t numread;
82
83 numread = read(fd, dest, len);
84 if (numread == (ssize_t)len) return 1;
85
86 if (numread == -1)
87 warn("at pos %u: couldn't read %d bytes", xtell(fd), (int)len);
88 else
89 warnx("at pos %u: tried to read %d bytes, got %d",
90 xtell(fd), (int)len, (int)numread);
91 return 0;
92 }
93
94 /* Read a byte. */
95 int
96 read8(const int fd, uint8_t *dest)
97 {
98 assert(sizeof(*dest) == 1);
99 return readn(fd, dest, sizeof(*dest));
100 }
101
102 /* Read a byte and compare it to the expected data.
103 * Returns 0 on failure or mismatch, 1 on success.
104 */
105 int
106 expect8(const int fd, uint8_t expecting)
107 {
108 uint8_t tmp;
109
110 assert(sizeof(tmp) == 1);
111 if (!readn(fd, &tmp, sizeof(tmp))) return 0;
112 if (tmp == expecting) return 1;
113
114 warnx("at pos %u: expecting 0x%02x, got 0x%02x",
115 xtell(fd)-1, expecting, tmp);
116 return 0;
117 }
118
119 /* Read a network order uint16_t from a file
120 * and store it in host order in memory.
121 */
122 int
123 read16(const int fd, uint16_t *dest)
124 {
125 uint16_t tmp;
126
127 assert(sizeof(tmp) == 2);
128 if (!read(fd, &tmp, sizeof(tmp))) return 0;
129 *dest = ntohs(tmp);
130 return 1;
131 }
132
133 /* Read a network order uint32_t from a file
134 * and store it in host order in memory.
135 */
136 int
137 read32(const int fd, uint32_t *dest)
138 {
139 uint32_t tmp;
140
141 assert(sizeof(tmp) == 4);
142 if (!read(fd, &tmp, sizeof(tmp))) return 0;
143 *dest = ntohl(tmp);
144 return 1;
145 }
146
147 /* Read an IPv4 addr from a file. This is for backward compatibility with
148 * host records version 1 and 2.
149 */
150 int
151 readaddr_ipv4(const int fd, struct addr *dest)
152 {
153 dest->family = IPv4;
154 return readn(fd, &(dest->ip.v4), sizeof(dest->ip.v4));
155 }
156
157 /* Read a struct addr from a file. Addresses are always stored in network
158 * order, both in the file and in the host's memory (FIXME: is that right?)
159 */
160 int
161 readaddr(const int fd, struct addr *dest)
162 {
163 unsigned char family;
164
165 if (!read8(fd, &family))
166 return 0;
167
168 if (family == 4) {
169 dest->family = IPv4;
170 return readn(fd, &(dest->ip.v4), sizeof(dest->ip.v4));
171 }
172 else if (family == 6) {
173 dest->family = IPv6;
174 return readn(fd, dest->ip.v6.s6_addr, sizeof(dest->ip.v6.s6_addr));
175 }
176 else
177 return 0; /* no address family I ever heard of */
178 }
179
180 /* Read a network order uint64_t from a file
181 * and store it in host order in memory.
182 */
183 int
184 read64(const int fd, uint64_t *dest)
185 {
186 uint64_t tmp;
187
188 assert(sizeof(tmp) == 8);
189 if (!read(fd, &tmp, sizeof(tmp))) return 0;
190 *dest = ntoh64(tmp);
191 return 1;
192 }
193
194 /* ---------------------------------------------------------------------------
195 * Write-to-file helpers. They all return 0 on failure, and 1 on success.
196 */
197
198 /* Write <len> bytes to <fd>, warn() and return 0 on failure,
199 * or return 1 for success.
200 */
201 int
202 writen(const int fd, const void *dest, const size_t len)
203 {
204 ssize_t numwr;
205
206 numwr = write(fd, dest, len);
207 if (numwr == (ssize_t)len) return 1;
208
209 if (numwr == -1)
210 warn("couldn't write %d bytes", (int)len);
211 else
212 warnx("tried to write %d bytes but wrote %d",
213 (int)len, (int)numwr);
214 return 0;
215 }
216
217 int
218 write8(const int fd, const uint8_t i)
219 {
220 assert(sizeof(i) == 1);
221 return writen(fd, &i, sizeof(i));
222 }
223
224 /* Given a uint16_t in host order, write it to a file in network order.
225 */
226 int
227 write16(const int fd, const uint16_t i)
228 {
229 uint16_t tmp = htons(i);
230 assert(sizeof(tmp) == 2);
231 return writen(fd, &tmp, sizeof(tmp));
232 }
233
234 /* Given a uint32_t in host order, write it to a file in network order.
235 */
236 int
237 write32(const int fd, const uint32_t i)
238 {
239 uint32_t tmp = htonl(i);
240 assert(sizeof(tmp) == 4);
241 return writen(fd, &tmp, sizeof(tmp));
242 }
243
244 /* Given a uint64_t in host order, write it to a file in network order.
245 */
246 int
247 write64(const int fd, const uint64_t i)
248 {
249 uint64_t tmp = hton64(i);
250 assert(sizeof(tmp) == 8);
251 return writen(fd, &tmp, sizeof(tmp));
252 }
253
254
255 /* Write the active address part in a struct addr to a file.
256 * Addresses are always stored in network order, both in the file and
257 * in the host's memory (FIXME: is that right?)
258 */
259 int
260 writeaddr(const int fd, const struct addr *const a)
261 {
262 if (!write8(fd, a->family))
263 return 0;
264
265 if (a->family == IPv4)
266 return writen(fd, &(a->ip.v4), sizeof(a->ip.v4));
267 else {
268 assert(a->family == IPv6);
269 return writen(fd, a->ip.v6.s6_addr, sizeof(a->ip.v6.s6_addr));
270 }
271 }
272
273 /* ---------------------------------------------------------------------------
274 * db import/export code follows.
275 */
276
277 /* Check that the global file header is correct / supported. */
278 int
279 read_file_header(const int fd, const uint8_t expected[4])
280 {
281 uint8_t got[4];
282
283 if (!readn(fd, got, sizeof(got))) return 0;
284
285 /* Check the header data */
286 if (memcmp(got, expected, sizeof(got)) != 0) {
287 warnx("bad header: "
288 "expecting %02x%02x%02x%02x, got %02x%02x%02x%02x",
289 expected[0], expected[1], expected[2], expected[3],
290 got[0], got[1], got[2], got[3]);
291 return 0;
292 }
293 return 1;
294 }
295
296 /* Returns 0 on failure, 1 on success. */
297 static int
298 db_import_from_fd(const int fd)
299 {
300 if (!read_file_header(fd, export_file_header)) return 0;
301 if (!read_file_header(fd, export_tag_hosts_ver1)) return 0;
302 if (!hosts_db_import(fd)) return 0;
303 if (!read_file_header(fd, export_tag_graph_ver1)) return 0;
304 if (!graph_import(fd)) return 0;
305 return 1;
306 }
307
308 void
309 db_import(const char *filename)
310 {
311 int fd = open(filename, O_RDONLY | O_NOFOLLOW);
312 if (fd == -1) {
313 warn("can't import from \"%s\"", filename);
314 return;
315 }
316 if (!db_import_from_fd(fd)) {
317 warnx("import failed");
318 /* don't stay in an inconsistent state: */
319 hosts_db_reset();
320 graph_reset();
321 }
322 close(fd);
323 }
324
325 /* Returns 0 on failure, 1 on success. */
326 static int
327 db_export_to_fd(const int fd)
328 {
329 if (!writen(fd, export_file_header, sizeof(export_file_header)))
330 return 0;
331 if (!writen(fd, export_tag_hosts_ver1, sizeof(export_tag_hosts_ver1)))
332 return 0;
333 if (!hosts_db_export(fd))
334 return 0;
335 if (!writen(fd, export_tag_graph_ver1, sizeof(export_tag_graph_ver1)))
336 return 0;
337 if (!graph_export(fd))
338 return 0;
339 return 1;
340 }
341
342 void
343 db_export(const char *filename)
344 {
345 int fd = open(filename, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, 0600);
346 if (fd == -1) {
347 warn("can't export to \"%s\"", filename);
348 return;
349 }
350 verbosef("exporting db to file \"%s\"", filename);
351 if (!db_export_to_fd(fd))
352 warnx("export failed");
353 else
354 verbosef("export successful");
355
356 /* FIXME: should write to another filename and use the rename() syscall to
357 * atomically update the output file on success
358 */
359 close(fd);
360 }
361
362 /* vim:set ts=3 sw=3 tw=78 et: */