diff --git a/src/sparse.c b/src/sparse.c index d41c0ea..d0a7a55 100644 --- a/src/sparse.c +++ b/src/sparse.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "common.h" struct tar_sparse_file; @@ -261,12 +262,58 @@ sparse_scan_file_raw (struct tar_sparse_file *file) return tar_sparse_scan (file, scan_end, NULL); } +enum sparse_fs_behavior + { + sparse_fs_behavior_init = 0, + sparse_fs_behavior_fine, + sparse_fs_behavior_uncertain + }; + +static enum sparse_fs_behavior +check_sparse_behavior (int fd) +{ + struct statfs buf; + if (fstatfs (fd, &buf)) + return sparse_fs_behavior_fine; + + if (buf.f_type == 0x9123683e) + return sparse_fs_behavior_uncertain; /* btrfs */ + + return sparse_fs_behavior_fine; +} + +static bool +wholesparse_detection_prohibited (struct tar_stat_info *st) +{ + static dev_t cached_device = 0; + static enum sparse_fs_behavior behavior; + + if (behavior == sparse_fs_behavior_init + || cached_device != st->stat.st_dev) + { + cached_device = st->stat.st_dev; + behavior = check_sparse_behavior (st->fd); + } + + return behavior == sparse_fs_behavior_uncertain; +} + + static bool sparse_scan_file_wholesparse (struct tar_sparse_file *file) { struct tar_stat_info *st = file->stat_info; struct sp_array sp = {0, 0}; + /* Some file-systems report st_blksize=0 for files which have some + inode-inlined data. This is, per bug-tar@, rather unfortunate + behavior, but we need to deal with these filesystems somehow. So, + let's prohibit the "wholesparse" detection method for such filesystems, + and let's hope that 'SEEK_HOLE/SEEK_DATA' works (if not, we fallback to + slow-but-safe 'raw' method anyway). */ + if (wholesparse_detection_prohibited (file->stat_info)) + return false; + /* Note that this function is called only for truly sparse files of size >= 1 block size (checked via ST_IS_SPARSE before). See the thread http://www.mail-archive.com/bug-tar@gnu.org/msg04209.html for more info */