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