2 * A read/write filesystem for (large) Pcap files
3 * By Daniel Borkmann, <daniel.borkmann@tik.ee.ethz.ch>
6 * gcc -Wall `pkg-config fuse --cflags --libs` pcapfs.c -o pcapfs
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
33 #include <sys/types.h>
37 #include <sys/sendfile.h>
39 #define TCPDUMP_MAGIC 0xa1b2c3d4
40 #define PCAP_VERSION_MAJOR 2
41 #define PCAP_VERSION_MINOR 4
45 uint16_t version_major
;
46 uint16_t version_minor
;
59 struct pcap_timeval ts
;
71 struct pcap_pkthdr meta
;
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
);
94 syslog(LOG_ERR
, "no mem left! panic!\n");
100 static void *xrealloc(void *ptr
, size_t nlen
)
102 void *nptr
= realloc(ptr
, nlen
);
104 syslog(LOG_ERR
, "no mem left! panic!\n");
110 static int pcapfs_file_type(const char *path
, size_t *node
)
113 if (strcmp(path
, "/") == 0)
115 ret
= sscanf(path
, "/%zu.hex", node
);
118 if (*node
>= table_next
)
123 static int pcapfs_getattr(const char *path
, struct stat
*stbuf
)
126 stbuf
->st_uid
= getuid();
127 stbuf
->st_gid
= getgid();
128 switch (pcapfs_file_type(path
, &node
)) {
130 stbuf
->st_mode
= S_IFDIR
| 0755;
132 stbuf
->st_atime
= stbuf
->st_mtime
= time(NULL
);
135 stbuf
->st_mode
= S_IFREG
| 0644;
137 if (table
[node
].dirty
)
138 stbuf
->st_size
= table
[node
].cowlen
;
140 stbuf
->st_size
= table
[node
].meta
.caplen
;
141 stbuf
->st_atime
= stbuf
->st_mtime
= table
[node
].meta
.ts
.tv_sec
;
150 static int pcapfs_open(const char *path
, struct fuse_file_info
*fi
)
154 if (pcapfs_file_type(path
, &node
) != PCAP_NONE
)
159 static int pcapfs_read(const char *path
, char *buff
, size_t size
,
160 off_t offset
, struct fuse_file_info
*fi
)
165 if (pcapfs_file_type(path
, &node
) != PCAP_FILE
)
170 if (!table
[node
].dirty
) {
171 if (offset
>= table
[node
].meta
.caplen
)
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
);
178 if (offset
>= table
[node
].cowlen
)
180 if (size
> table
[node
].cowlen
- offset
)
181 size
= table
[node
].cowlen
- offset
;
182 memcpy(buff
, table
[node
].cowbuff
+ offset
, size
);
190 static int pcapfs_truncate(const char *path
, off_t size
)
193 if (pcapfs_file_type(path
, &node
) != PCAP_FILE
)
198 static int pcapfs_write(const char *path
, const char *buff
, size_t size
,
199 off_t offset
, struct fuse_file_info
*fi
)
204 if (pcapfs_file_type(path
, &node
) != PCAP_FILE
)
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"
218 table
[node
].dirty
= 0;
219 table
[node
].cowlen
= 0;
220 free(table
[node
].cowbuff
);
221 table
[node
].cowbuff
= NULL
;
226 if (table
[node
].cowlen
< size
+ offset
) {
227 table
[node
].cowlen
= size
+ offset
;
228 table
[node
].cowbuff
= xrealloc(table
[node
].cowbuff
,
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
,
238 memcpy(table
[node
].cowbuff
+ offset
, buff
, 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
)
252 if (pcapfs_file_type(path
, &node
) != PCAP_ROOT
)
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);
265 static struct fuse_operations pcapfs_ops
= {
268 .write
= pcapfs_write
,
269 .getattr
= pcapfs_getattr
,
270 .readdir
= pcapfs_readdir
,
271 .truncate
= pcapfs_truncate
,
274 static void pcapfs_build_cache(void)
277 #define INIT_SLOTS 1024
278 table
= xmalloc(sizeof(*table
) * INIT_SLOTS
);
279 table_len
= INIT_SLOTS
;
281 posix_fadvise(pcap_fd
, 0, 0, POSIX_FADV_SEQUENTIAL
);
283 ret
= read(pcap_fd
, &table
[table_next
].meta
,
284 sizeof(table
[table_next
].meta
));
287 if (ret
!= sizeof(table
[table_next
].meta
))
289 if (table
[table_next
].meta
.caplen
== 0 ||
290 table
[table_next
].meta
.len
== 0)
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
);
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
);
309 syslog(LOG_ERR
, "error parsing the pcap file! corrupted?!\n");
313 static inline void pcapfs_destroy_cache(void)
321 static void ____pcapfs_flush_dirty_nodes_to_disc_dirty(size_t i
)
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,"
338 free(table
[i
].cowbuff
);
339 table
[i
].cowbuff
= NULL
;
341 table
[i
].data
= lseek(pcap_fd
, 0, SEEK_CUR
) - table
[i
].meta
.caplen
;
345 ____pcapfs_flush_dirty_nodes_to_disc_clean(size_t i
, int pcap_fd2
,
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
;
369 static void __pcapfs_flush_dirty_nodes_to_disc(size_t i_dirty
, int pcap_fd2
,
370 size_t *count
, off_t offshift
)
373 for (i
= i_dirty
; i
< table_next
; ++i
) {
374 if (table
[i
].dirty
) {
375 ____pcapfs_flush_dirty_nodes_to_disc_dirty(i
);
378 ____pcapfs_flush_dirty_nodes_to_disc_clean(i
, pcap_fd2
,
384 static void pcapfs_flush_dirty_nodes_to_disc(void)
391 posix_fadvise(pcap_fd
, 0, 0, POSIX_FADV_SEQUENTIAL
);
392 for (i
= 0; i
< table_next
; ++i
) {
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
,
400 if (ret
!= table
[i
].cowlen
)
401 syslog(LOG_ERR
, "disc flush error at node "
402 "%zu, continuing\n", i
);
405 free(table
[i
].cowbuff
);
406 table
[i
].cowbuff
= NULL
;
408 } else if (table
[i
].dirty
) {
411 char *tmpfile
= "/tmp/pcapfs.fubar";
412 off_t offshift
= table
[i
].data
;
413 size_t to_copy
, chunk_size
, chunk_blocks
, chunk_rest
;
415 fstat(pcap_fd
, &ost
);
416 pcap_fd2
= open(tmpfile
, O_RDWR
| O_CREAT
| O_TRUNC
,
419 syslog(LOG_ERR
, "error creating temp file!\n");
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
,
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
,
451 posix_fadvise(pcap_fd
, 0, 0, POSIX_FADV_RANDOM
);
453 syslog(LOG_INFO
, "%zu dirty marked node(s) flushed\n", count
);
456 static void pcapfs_check_superblock(void)
459 struct pcap_filehdr hdr
;
460 ret
= read(pcap_fd
, &hdr
, sizeof(hdr
));
461 if (ret
!= sizeof(hdr
))
463 if (hdr
.magic
!= TCPDUMP_MAGIC
)
465 if (hdr
.version_major
!= PCAP_VERSION_MAJOR
)
467 if (hdr
.version_minor
!= PCAP_VERSION_MINOR
)
471 fprintf(stderr
, "this isn't a pcap file!\n");
475 static inline void pcapfs_lock_disc(void)
477 int ret
= flock(pcap_fd
, LOCK_EX
);
479 syslog(LOG_ERR
, "cannot lock pcap disc!\n");
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
);
493 syslog(LOG_ERR
, "cannot open pcap disc!\n");
498 static inline void pcapfs_halt_disc(void)
503 static void pcapfs_cleanup(void)
505 pcapfs_flush_dirty_nodes_to_disc();
506 pcapfs_destroy_cache();
507 pcapfs_unlock_disc();
509 syslog(LOG_INFO
, "unmounted\n");
513 static void pcapfs_init(void)
515 openlog("pcapfs", LOG_PID
| LOG_CONS
| LOG_NDELAY
, LOG_DAEMON
);
518 pcapfs_check_superblock();
519 pcapfs_build_cache();
520 syslog(LOG_INFO
, "mounted\n");
523 int main(int argc
, char **argv
)
526 struct fuse_args args
= FUSE_ARGS_INIT(0, NULL
);
528 fprintf(stderr
, "usage: pcapfs <mntpoint> <pcap>\n");
531 for (i
= 0; i
< argc
- 1; i
++)
532 fuse_opt_add_arg(&args
, argv
[i
]);
533 pcap_disc
= argv
[argc
- 1];
535 ret
= fuse_main(args
.argc
, args
.argv
, &pcapfs_ops
, NULL
);