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