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