Implement str_to_addr() and unit test.
[darkstat] / 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 a struct addr46 from a file. Addresses are always stored in network
156 * order, both in the file and in the host's memory (FIXME: is that right?)
157 * The component "dest->af" must specify AF_INET or AF_INET6.
158 */
159 int
160 readaddr(const int fd, struct addr46 *dest)
161 {
162 assert(sizeof(*dest) == sizeof(struct addr46));
163 assert(dest->af == AF_INET || dest->af == AF_INET6);
164
165 if (dest->af == AF_INET)
166 return readn(fd, &dest->addr.ip, sizeof(struct in_addr));
167 else
168 return readn(fd, &dest->addr.ip6, sizeof(struct in6_addr));
169 }
170
171 /* Read a network order uint64_t from a file
172 * and store it in host order in memory.
173 */
174 int
175 read64(const int fd, uint64_t *dest)
176 {
177 uint64_t tmp;
178
179 assert(sizeof(tmp) == 8);
180 if (!read(fd, &tmp, sizeof(tmp))) return 0;
181 *dest = ntoh64(tmp);
182 return 1;
183 }
184
185 /* ---------------------------------------------------------------------------
186 * Write-to-file helpers. They all return 0 on failure, and 1 on success.
187 */
188
189 /* Write <len> bytes to <fd>, warn() and return 0 on failure,
190 * or return 1 for success.
191 */
192 int
193 writen(const int fd, const void *dest, const size_t len)
194 {
195 ssize_t numwr;
196
197 numwr = write(fd, dest, len);
198 if (numwr == (ssize_t)len) return 1;
199
200 if (numwr == -1)
201 warn("couldn't write %d bytes", (int)len);
202 else
203 warnx("tried to write %d bytes but wrote %d",
204 (int)len, (int)numwr);
205 return 0;
206 }
207
208 int
209 write8(const int fd, const uint8_t i)
210 {
211 assert(sizeof(i) == 1);
212 return writen(fd, &i, sizeof(i));
213 }
214
215 /* Given a uint16_t in host order, write it to a file in network order.
216 */
217 int
218 write16(const int fd, const uint16_t i)
219 {
220 uint16_t tmp = htons(i);
221 assert(sizeof(tmp) == 2);
222 return writen(fd, &tmp, sizeof(tmp));
223 }
224
225 /* Given a uint32_t in host order, write it to a file in network order.
226 */
227 int
228 write32(const int fd, const uint32_t i)
229 {
230 uint32_t tmp = htonl(i);
231 assert(sizeof(tmp) == 4);
232 return writen(fd, &tmp, sizeof(tmp));
233 }
234
235 /* Given a uint64_t in host order, write it to a file in network order.
236 */
237 int
238 write64(const int fd, const uint64_t i)
239 {
240 uint64_t tmp = hton64(i);
241 assert(sizeof(tmp) == 8);
242 return writen(fd, &tmp, sizeof(tmp));
243 }
244
245
246 /* Write the active address part in a struct addr46 structure to a file.
247 * Addresses are always stored in network order, both in the file and
248 * in the host's memory (FIXME: is that right?)
249 */
250 int
251 writeaddr(const int fd, const struct addr46 *const ip)
252 {
253 assert(sizeof(*ip) == sizeof(struct addr46));
254 assert(ip->af == AF_INET || ip->af == AF_INET6);
255
256 if (ip->af == AF_INET)
257 return writen(fd, &ip->addr.ip, sizeof(ip->addr.ip));
258 else
259 return writen(fd, &ip->addr.ip6, sizeof(ip->addr.ip6));
260 }
261
262 /* ---------------------------------------------------------------------------
263 * db import/export code follows.
264 */
265
266 /* Check that the global file header is correct / supported. */
267 int
268 read_file_header(const int fd, const uint8_t expected[4])
269 {
270 uint8_t got[4];
271
272 if (!readn(fd, got, sizeof(got))) return 0;
273
274 /* Check the header data */
275 if (memcmp(got, expected, sizeof(got)) != 0) {
276 warnx("bad header: "
277 "expecting %02x%02x%02x%02x, got %02x%02x%02x%02x",
278 expected[0], expected[1], expected[2], expected[3],
279 got[0], got[1], got[2], got[3]);
280 return 0;
281 }
282 return 1;
283 }
284
285 /* Returns 0 on failure, 1 on success. */
286 static int
287 db_import_from_fd(const int fd)
288 {
289 if (!read_file_header(fd, export_file_header)) return 0;
290 if (!read_file_header(fd, export_tag_hosts_ver1)) return 0;
291 if (!hosts_db_import(fd)) return 0;
292 if (!read_file_header(fd, export_tag_graph_ver1)) return 0;
293 if (!graph_import(fd)) return 0;
294 return 1;
295 }
296
297 void
298 db_import(const char *filename)
299 {
300 int fd = open(filename, O_RDONLY | O_NOFOLLOW);
301 if (fd == -1) {
302 warn("can't import from \"%s\"", filename);
303 return;
304 }
305 if (!db_import_from_fd(fd)) {
306 warnx("import failed");
307 /* don't stay in an inconsistent state: */
308 hosts_db_reset();
309 graph_reset();
310 }
311 close(fd);
312 }
313
314 /* Returns 0 on failure, 1 on success. */
315 static int
316 db_export_to_fd(const int fd)
317 {
318 if (!writen(fd, export_file_header, sizeof(export_file_header)))
319 return 0;
320 if (!writen(fd, export_tag_hosts_ver1, sizeof(export_tag_hosts_ver1)))
321 return 0;
322 if (!hosts_db_export(fd))
323 return 0;
324 if (!writen(fd, export_tag_graph_ver1, sizeof(export_tag_graph_ver1)))
325 return 0;
326 if (!graph_export(fd))
327 return 0;
328 return 1;
329 }
330
331 void
332 db_export(const char *filename)
333 {
334 int fd = open(filename, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, 0600);
335 if (fd == -1) {
336 warn("can't export to \"%s\"", filename);
337 return;
338 }
339 verbosef("exporting db to file \"%s\"", filename);
340 if (!db_export_to_fd(fd))
341 warnx("export failed");
342 else
343 verbosef("export successful");
344
345 /* FIXME: should write to another filename and use the rename() syscall to
346 * atomically update the output file on success
347 */
348 close(fd);
349 }
350
351 /* vim:set ts=3 sw=3 tw=78 et: */