scripts: moved important shell / perl scripts to scripts/ instead of contrib
[netsniff-ng.git] / contrib / misc / pcapfs.c
blob94fc29d6b979b771b517339de0ad80ad7950bbda
1 /*
2 * A read/write filesystem for (large) Pcap files
3 * By Daniel Borkmann, <daniel.borkmann@tik.ee.ethz.ch>
4 * GPL, version 2.0
6 * gcc -Wall `pkg-config fuse --cflags --libs` pcapfs.c -o pcapfs
8 * Usage:
9 * pcapfs <mntpoint> <trace.pcap>
10 * hexdump -C <mntpoint>/0.hex
11 * ls -la <mntpoint>/0.hex
12 * vim <mntpoint>/0.hex
13 * hit escape and type:
14 * :%!xxd to switch into hex mode
15 * when done hit escape and type:
16 * :%!xxd -r to exit from hex mode
17 * fusermount -u <mntpoint>
20 #define _FILE_OFFSET_BITS 64
21 #define FUSE_USE_VERSION 26
22 #include <fuse.h>
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <stdint.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <time.h>
29 #include <errno.h>
30 #include <syslog.h>
31 #include <signal.h>
32 #include <sys/file.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <fcntl.h>
36 #include <signal.h>
37 #include <sys/sendfile.h>
39 #define TCPDUMP_MAGIC 0xa1b2c3d4
40 #define PCAP_VERSION_MAJOR 2
41 #define PCAP_VERSION_MINOR 4
43 struct pcap_filehdr {
44 uint32_t magic;
45 uint16_t version_major;
46 uint16_t version_minor;
47 int32_t thiszone;
48 uint32_t sigfigs;
49 uint32_t snaplen;
50 uint32_t linktype;
53 struct pcap_timeval {
54 int32_t tv_sec;
55 int32_t tv_usec;
58 struct pcap_pkthdr {
59 struct pcap_timeval ts;
60 uint32_t caplen;
61 uint32_t len;
64 enum {
65 PCAP_NONE,
66 PCAP_ROOT,
67 PCAP_FILE,
70 struct pcap_fnode {
71 struct pcap_pkthdr meta;
72 off_t data;
73 int dirty;
74 uint8_t *cowbuff;
75 size_t cowlen;
78 static int pcap_fd;
80 static char *pcap_disc = NULL;
82 static struct pcap_fnode *table = NULL;
84 static size_t table_len = 0, table_next = 0;
86 static sig_atomic_t flushing = 0, rwing = 0;
88 static void pcapfs_flush_dirty_nodes_to_disc(void);
90 static void *xmalloc(size_t len)
92 void *ptr = malloc(len);
93 if (!ptr) {
94 syslog(LOG_ERR, "no mem left! panic!\n");
95 exit(1);
97 return ptr;
100 static void *xrealloc(void *ptr, size_t nlen)
102 void *nptr = realloc(ptr, nlen);
103 if (!nptr) {
104 syslog(LOG_ERR, "no mem left! panic!\n");
105 exit(1);
107 return nptr;
110 static int pcapfs_file_type(const char *path, size_t *node)
112 int ret;
113 if (strcmp(path, "/") == 0)
114 return PCAP_ROOT;
115 ret = sscanf(path, "/%zu.hex", node);
116 if (ret <= 0)
117 return PCAP_NONE;
118 if (*node >= table_next)
119 return PCAP_NONE;
120 return PCAP_FILE;
123 static int pcapfs_getattr(const char *path, struct stat *stbuf)
125 size_t node;
126 stbuf->st_uid = getuid();
127 stbuf->st_gid = getgid();
128 switch (pcapfs_file_type(path, &node)) {
129 case PCAP_ROOT:
130 stbuf->st_mode = S_IFDIR | 0755;
131 stbuf->st_nlink = 2;
132 stbuf->st_atime = stbuf->st_mtime = time(NULL);
133 break;
134 case PCAP_FILE:
135 stbuf->st_mode = S_IFREG | 0644;
136 stbuf->st_nlink = 1;
137 if (table[node].dirty)
138 stbuf->st_size = table[node].cowlen;
139 else
140 stbuf->st_size = table[node].meta.caplen;
141 stbuf->st_atime = stbuf->st_mtime = table[node].meta.ts.tv_sec;
142 break;
143 case PCAP_NONE:
144 default:
145 return -ENOENT;
147 return 0;
150 static int pcapfs_open(const char *path, struct fuse_file_info *fi)
152 size_t node;
153 (void) fi;
154 if (pcapfs_file_type(path, &node) != PCAP_NONE)
155 return 0;
156 return -ENOENT;
159 static int pcapfs_read(const char *path, char *buff, size_t size,
160 off_t offset, struct fuse_file_info *fi)
162 size_t node;
163 ssize_t ret = 0;
164 (void) fi;
165 if (pcapfs_file_type(path, &node) != PCAP_FILE)
166 return -EINVAL;
167 while (flushing)
168 sleep(0);
169 rwing = 1;
170 if (!table[node].dirty) {
171 if (offset >= table[node].meta.caplen)
172 goto out;
173 if (size > table[node].meta.caplen - offset)
174 size = table[node].meta.caplen - offset;
175 lseek(pcap_fd, table[node].data + offset, SEEK_SET);
176 ret = read(pcap_fd, buff, size);
177 } else {
178 if (offset >= table[node].cowlen)
179 goto out;
180 if (size > table[node].cowlen - offset)
181 size = table[node].cowlen - offset;
182 memcpy(buff, table[node].cowbuff + offset, size);
183 ret = size;
185 out:
186 rwing = 0;
187 return ret;
190 static int pcapfs_truncate(const char *path, off_t size)
192 size_t node;
193 if (pcapfs_file_type(path, &node) != PCAP_FILE)
194 return -EINVAL;
195 return size;
198 static int pcapfs_write(const char *path, const char *buff, size_t size,
199 off_t offset, struct fuse_file_info *fi)
201 size_t node;
202 ssize_t ret;
203 (void) fi;
204 if (pcapfs_file_type(path, &node) != PCAP_FILE)
205 return -EINVAL;
206 while (flushing)
207 sleep(0);
208 rwing = 1;
209 if (!table[node].dirty) {
210 table[node].dirty = 1;
211 table[node].cowlen = table[node].meta.caplen;
212 table[node].cowbuff = xmalloc(table[node].cowlen);
213 lseek(pcap_fd, table[node].data, SEEK_SET);
214 ret = read(pcap_fd, table[node].cowbuff, table[node].cowlen);
215 if (ret != table[node].cowlen) {
216 syslog(LOG_ERR, "error writing into cow buff of"
217 " %s!\n", path);
218 table[node].dirty = 0;
219 table[node].cowlen = 0;
220 free(table[node].cowbuff);
221 table[node].cowbuff = NULL;
222 rwing = 0;
223 return -EIO;
226 if (table[node].cowlen < size + offset) {
227 table[node].cowlen = size + offset;
228 table[node].cowbuff = xrealloc(table[node].cowbuff,
229 table[node].cowlen);
230 memset(table[node].cowbuff + table[node].meta.caplen,
231 0, table[node].cowlen - table[node].meta.caplen);
233 if (table[node].cowlen > size + offset) {
234 table[node].cowlen = size + offset;
235 table[node].cowbuff = xrealloc(table[node].cowbuff,
236 table[node].cowlen);
238 memcpy(table[node].cowbuff + offset, buff, size);
239 rwing = 0;
240 return size;
243 static int pcapfs_readdir(const char *path, void *buff,
244 fuse_fill_dir_t filler,
245 off_t offset, struct fuse_file_info *fi)
247 size_t i;
248 size_t node;
249 char tmp[256];
250 (void) fi;
251 (void) offset;
252 if (pcapfs_file_type(path, &node) != PCAP_ROOT)
253 return -ENOENT;
254 filler(buff, ".", NULL, 0);
255 filler(buff, "..", NULL, 0);
256 for (i = 0; i < table_next; ++i) {
257 memset(tmp, 0, sizeof(tmp));
258 snprintf(tmp, sizeof(tmp), "%zu.hex", i);
259 tmp[sizeof(tmp) - 1] = 0;
260 filler(buff, tmp, NULL, 0);
262 return 0;
265 static struct fuse_operations pcapfs_ops = {
266 .open = pcapfs_open,
267 .read = pcapfs_read,
268 .write = pcapfs_write,
269 .getattr = pcapfs_getattr,
270 .readdir = pcapfs_readdir,
271 .truncate = pcapfs_truncate,
274 static void pcapfs_build_cache(void)
276 ssize_t ret;
277 #define INIT_SLOTS 1024
278 table = xmalloc(sizeof(*table) * INIT_SLOTS);
279 table_len = INIT_SLOTS;
280 table_next = 0;
281 posix_fadvise(pcap_fd, 0, 0, POSIX_FADV_SEQUENTIAL);
282 while (1) {
283 ret = read(pcap_fd, &table[table_next].meta,
284 sizeof(table[table_next].meta));
285 if (ret == 0)
286 break;
287 if (ret != sizeof(table[table_next].meta))
288 goto die;
289 if (table[table_next].meta.caplen == 0 ||
290 table[table_next].meta.len == 0)
291 goto die;
292 table[table_next].data = lseek(pcap_fd, 0, SEEK_CUR);
293 table[table_next].dirty = 0;
294 table[table_next].cowbuff = NULL;
295 table[table_next].cowlen = 0;
296 ret = lseek(pcap_fd, table[table_next].meta.caplen, SEEK_CUR);
297 if (ret < 0)
298 goto die;
299 table_next++;
300 if (table_next == table_len) {
301 table_len = (size_t) table_len * 3 / 2;
302 table = xrealloc(table, table_len);
305 lseek(pcap_fd, 0, SEEK_SET);
306 posix_fadvise(pcap_fd, 0, 0, POSIX_FADV_RANDOM);
307 return;
308 die:
309 syslog(LOG_ERR, "error parsing the pcap file! corrupted?!\n");
310 exit(1);
313 static inline void pcapfs_destroy_cache(void)
315 free(table);
316 table = NULL;
317 table_len = 0;
318 table_next = 0;
321 static void ____pcapfs_flush_dirty_nodes_to_disc_dirty(size_t i)
323 ssize_t ret;
324 if ((table[i].meta.caplen == table[i].meta.len) ||
325 (table[i].meta.caplen < table[i].meta.len &&
326 table[i].cowlen > table[i].meta.len))
327 table[i].meta.len = table[i].cowlen;
328 table[i].meta.caplen = table[i].cowlen;
329 ret = write(pcap_fd, &table[i].meta, sizeof(table[i].meta));
330 if (ret != sizeof(table[i].meta))
331 syslog(LOG_ERR, "disc flush meta error at node %zu,"
332 "continuing\n", i + 1);
333 ret = write(pcap_fd, table[i].cowbuff, table[i].cowlen);
334 if (ret != table[i].cowlen)
335 syslog(LOG_ERR, "disc flush error at dirty node %zu,"
336 "continuing\n", i);
337 table[i].cowlen = 0;
338 free(table[i].cowbuff);
339 table[i].cowbuff = NULL;
340 table[i].dirty = 0;
341 table[i].data = lseek(pcap_fd, 0, SEEK_CUR) - table[i].meta.caplen;
344 static void
345 ____pcapfs_flush_dirty_nodes_to_disc_clean(size_t i, int pcap_fd2,
346 off_t offshift)
348 ssize_t ret;
349 uint8_t *tmp;
350 lseek(pcap_fd2, table[i].data - offshift, SEEK_SET);
351 ret = write(pcap_fd, &table[i].meta, sizeof(table[i].meta));
352 if (ret != sizeof(table[i].meta))
353 syslog(LOG_ERR, "disc flush meta error at node %zu,"
354 "continuing\n", i + 1);
355 /* we cannot do a sendfile backwards :-( but chunks here are smaller */
356 tmp = xmalloc(table[i].meta.caplen);
357 ret = read(pcap_fd2, tmp, table[i].meta.caplen);
358 if (ret != table[i].meta.caplen)
359 syslog(LOG_ERR, "disc flush error (%s) at clean node %zu read,"
360 "continuing\n", strerror(errno), i);
361 ret = write(pcap_fd, tmp, table[i].meta.caplen);
362 if (ret != table[i].meta.caplen)
363 syslog(LOG_ERR, "disc flush error (%s) at clean node %zu write,"
364 "continuing\n", strerror(errno), i);
365 table[i].data = lseek(pcap_fd, 0, SEEK_CUR) - table[i].meta.caplen;
366 free(tmp);
369 static void __pcapfs_flush_dirty_nodes_to_disc(size_t i_dirty, int pcap_fd2,
370 size_t *count, off_t offshift)
372 size_t i;
373 for (i = i_dirty; i < table_next; ++i) {
374 if (table[i].dirty) {
375 ____pcapfs_flush_dirty_nodes_to_disc_dirty(i);
376 (*count)++;
377 } else {
378 ____pcapfs_flush_dirty_nodes_to_disc_clean(i, pcap_fd2,
379 offshift);
384 static void pcapfs_flush_dirty_nodes_to_disc(void)
386 size_t i, count = 0;
387 ssize_t ret;
388 while (rwing)
389 sleep(0);
390 flushing = 1;
391 posix_fadvise(pcap_fd, 0, 0, POSIX_FADV_SEQUENTIAL);
392 for (i = 0; i < table_next; ++i) {
393 if (!table[i].dirty)
394 continue;
395 if (table[i].dirty &&
396 table[i].cowlen == table[i].meta.caplen) {
397 lseek(pcap_fd, table[i].data, SEEK_SET);
398 ret = write(pcap_fd, table[i].cowbuff,
399 table[i].cowlen);
400 if (ret != table[i].cowlen)
401 syslog(LOG_ERR, "disc flush error at node "
402 "%zu, continuing\n", i);
403 table[i].dirty = 0;
404 table[i].cowlen = 0;
405 free(table[i].cowbuff);
406 table[i].cowbuff = NULL;
407 count++;
408 } else if (table[i].dirty) {
409 int pcap_fd2;
410 size_t ii;
411 char *tmpfile = "/tmp/pcapfs.fubar";
412 off_t offshift = table[i].data;
413 size_t to_copy, chunk_size, chunk_blocks, chunk_rest;
414 struct stat ost;
415 fstat(pcap_fd, &ost);
416 pcap_fd2 = open(tmpfile, O_RDWR | O_CREAT | O_TRUNC,
417 S_IRUSR | S_IWUSR);
418 if (pcap_fd2 < 0) {
419 syslog(LOG_ERR, "error creating temp file!\n");
420 break;
422 posix_fadvise(pcap_fd2, 0, 0, POSIX_FADV_SEQUENTIAL);
423 to_copy = ost.st_size - table[i].data;
424 chunk_size = ost.st_blksize;
425 chunk_blocks = (size_t) (to_copy / chunk_size);
426 chunk_rest = to_copy % chunk_size;
427 lseek(pcap_fd, table[i].data, SEEK_SET);
428 for (ii = 0; ii < chunk_blocks; ++ii) {
429 ret = sendfile(pcap_fd2, pcap_fd, NULL,
430 chunk_size);
431 if (ret != chunk_size)
432 syslog(LOG_ERR, "error (%s) while "
433 "splicing!\n", strerror(errno));
435 ret = sendfile(pcap_fd2, pcap_fd, NULL, chunk_rest);
436 if (ret != chunk_rest)
437 syslog(LOG_ERR, "error while tee'ing!\n");
438 lseek(pcap_fd2, 0, SEEK_SET);
439 lseek(pcap_fd, table[i].data -
440 sizeof(struct pcap_pkthdr), SEEK_SET);
441 ftruncate(pcap_fd, table[i].data -
442 sizeof(struct pcap_pkthdr));
443 __pcapfs_flush_dirty_nodes_to_disc(i, pcap_fd2, &count,
444 offshift);
445 close(pcap_fd2);
446 unlink(tmpfile);
447 break;
450 fsync(pcap_fd);
451 posix_fadvise(pcap_fd, 0, 0, POSIX_FADV_RANDOM);
452 flushing = 0;
453 syslog(LOG_INFO, "%zu dirty marked node(s) flushed\n", count);
456 static void pcapfs_check_superblock(void)
458 ssize_t ret;
459 struct pcap_filehdr hdr;
460 ret = read(pcap_fd, &hdr, sizeof(hdr));
461 if (ret != sizeof(hdr))
462 goto die;
463 if (hdr.magic != TCPDUMP_MAGIC)
464 goto die;
465 if (hdr.version_major != PCAP_VERSION_MAJOR)
466 goto die;
467 if (hdr.version_minor != PCAP_VERSION_MINOR)
468 goto die;
469 return;
470 die:
471 fprintf(stderr, "this isn't a pcap file!\n");
472 exit(1);
475 static inline void pcapfs_lock_disc(void)
477 int ret = flock(pcap_fd, LOCK_EX);
478 if (ret < 0) {
479 syslog(LOG_ERR, "cannot lock pcap disc!\n");
480 exit(1);
484 static inline void pcapfs_unlock_disc(void)
486 flock(pcap_fd, LOCK_UN);
489 static inline void pcapfs_init_disc(void)
491 pcap_fd = open(pcap_disc, O_RDWR | O_APPEND);
492 if (pcap_fd < 0) {
493 syslog(LOG_ERR, "cannot open pcap disc!\n");
494 exit(1);
498 static inline void pcapfs_halt_disc(void)
500 close(pcap_fd);
503 static void pcapfs_cleanup(void)
505 pcapfs_flush_dirty_nodes_to_disc();
506 pcapfs_destroy_cache();
507 pcapfs_unlock_disc();
508 pcapfs_halt_disc();
509 syslog(LOG_INFO, "unmounted\n");
510 closelog();
513 static void pcapfs_init(void)
515 openlog("pcapfs", LOG_PID | LOG_CONS | LOG_NDELAY, LOG_DAEMON);
516 pcapfs_init_disc();
517 pcapfs_lock_disc();
518 pcapfs_check_superblock();
519 pcapfs_build_cache();
520 syslog(LOG_INFO, "mounted\n");
523 int main(int argc, char **argv)
525 int i, ret;
526 struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
527 if (argc < 3) {
528 fprintf(stderr, "usage: pcapfs <mntpoint> <pcap>\n");
529 exit(1);
531 for (i = 0; i < argc - 1; i++)
532 fuse_opt_add_arg(&args, argv[i]);
533 pcap_disc = argv[argc - 1];
534 pcapfs_init();
535 ret = fuse_main(args.argc, args.argv, &pcapfs_ops, NULL);
536 pcapfs_cleanup();
537 return ret;