From 173ef882376d5384ba5fc6a52d2a6000b4fc4bf1 Mon Sep 17 00:00:00 2001 From: Karel Zak Date: Wed, 29 Nov 2017 14:26:42 +0100 Subject: fallocate: dig holes only in data extents Based on patch from Vaclav Dolezal , this implementation is less invasive. The patch adds a new while() for pread() call (so diff is mostly code indention). The pread() is called for a real data only (addressed by 'off' and 'end') and we use SEEK_{DATA,HOLE} before the pread() to skip already existing holes. The variables 'file_off' and 'file_end' addresses area in the file as specified on fallocate command line. Test: $ truncate -s 10G testfile $ dd if=/dev/zero of=testfile count=10 bs=1M conv=notrunc old version: $ time /usr/bin/fallocate --dig-holes --verbose testfile testfile: 10 GiB (10737418240 bytes) converted to sparse holes. real 0m3.013s user 0m0.700s sys 0m2.304s new version: $ time ./fallocate --dig-holes --verbose testfile testfile: 10 MiB (10485760 bytes) converted to sparse holes. real 0m0.026s user 0m0.002s sys 0m0.004s The old version scans all file. The change has minimal overhead for files without holes. Addresses: https://github.com/karelzak/util-linux/issues/421 Co-Author: Vaclav Dolezal Signed-off-by: Karel Zak --- sys-utils/fallocate.c | 119 +++++++++++++++++++++++--------------------------- 1 file changed, 55 insertions(+), 64 deletions(-) (limited to 'sys-utils/fallocate.c') diff --git a/sys-utils/fallocate.c b/sys-utils/fallocate.c index c8b2b2fcc..4c53f8535 100644 --- a/sys-utils/fallocate.c +++ b/sys-utils/fallocate.c @@ -147,25 +147,6 @@ static void xposix_fallocate(int fd, off_t offset, off_t length) } #endif -static int skip_hole(int fd, off_t *off) -{ - off_t newoff; - - errno = 0; - newoff = lseek(fd, *off, SEEK_DATA); - - /* ENXIO means that there is no more data -- probably sparse hole at - * the end of the file */ - if (newoff < 0 && errno == ENXIO) - return 1; - - if (newoff > *off) { - *off = newoff; - return 0; /* success */ - } - return -1; /* no hole */ -} - /* The real buffer size has to be bufsize + sizeof(uintptr_t) */ static int is_nul(void *buf, size_t bufsize) { @@ -191,16 +172,16 @@ static int is_nul(void *buf, size_t bufsize) return cbuf + bufsize < cp; } -static void dig_holes(int fd, off_t off, off_t len) +static void dig_holes(int fd, off_t file_off, off_t len) { - off_t end = len ? off + len : 0; + off_t file_end = len ? file_off + len : 0; off_t hole_start = 0, hole_sz = 0; uintmax_t ct = 0; size_t bufsz; char *buf; struct stat st; #if defined(POSIX_FADV_SEQUENTIAL) && defined(HAVE_POSIX_FADVISE) - off_t cache_start = off; + off_t cache_start = file_off; /* * We don't want to call POSIX_FADV_DONTNEED to discard cached * data in PAGE_SIZE steps. IMHO it's overkill (too many syscalls). @@ -217,60 +198,70 @@ static void dig_holes(int fd, off_t off, off_t len) bufsz = st.st_blksize; - if (lseek(fd, off, SEEK_SET) < 0) + if (lseek(fd, file_off, SEEK_SET) < 0) err(EXIT_FAILURE, _("seek on %s failed"), filename); /* buffer + extra space for is_nul() sentinel */ buf = xmalloc(bufsz + sizeof(uintptr_t)); + while (file_end == 0 || file_off < file_end) { + /* + * Detect data area (skip exiting holes) + */ + off_t end, off; + + off = lseek(fd, file_off, SEEK_DATA); + if ((off == -1 && errno == ENXIO) || + (file_end && off >= file_end)) + break; + + end = lseek(fd, off, SEEK_HOLE); + if (file_end && end > file_end) + end = file_end; + #if defined(POSIX_FADV_SEQUENTIAL) && defined(HAVE_POSIX_FADVISE) - posix_fadvise(fd, off, 0, POSIX_FADV_SEQUENTIAL); + posix_fadvise(fd, off, end, POSIX_FADV_SEQUENTIAL); #endif + /* + * Dig holes in the area + */ + while (off < end) { + ssize_t rsz = pread(fd, buf, bufsz, off); + if (rsz < 0 && errno) + err(EXIT_FAILURE, _("%s: read failed"), filename); + if (end && rsz > 0 && off > end - rsz) + rsz = end - off; + if (rsz <= 0) + break; + + if (is_nul(buf, rsz)) { + if (!hole_sz) /* new hole detected */ + hole_start = off; + hole_sz += rsz; + } else if (hole_sz) { + xfallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, + hole_start, hole_sz); + ct += hole_sz; + hole_sz = hole_start = 0; + } - while (end == 0 || off < end) { - ssize_t rsz; - - rsz = pread(fd, buf, bufsz, off); - if (rsz < 0 && errno) - err(EXIT_FAILURE, _("%s: read failed"), filename); - if (end && rsz > 0 && off > end - rsz) - rsz = end - off; - if (rsz <= 0) - break; +#if defined(POSIX_FADV_DONTNEED) && defined(HAVE_POSIX_FADVISE) + /* discard cached data */ + if (off - cache_start > (off_t) cachesz) { + size_t clen = off - cache_start; - if (is_nul(buf, rsz)) { - if (!hole_sz) { /* new hole detected */ - int rc = skip_hole(fd, &off); - if (rc == 0) - continue; /* hole skipped */ - else if (rc == 1) - break; /* end of file */ - hole_start = off; + clen = (clen / cachesz) * cachesz; + posix_fadvise(fd, cache_start, clen, POSIX_FADV_DONTNEED); + cache_start = cache_start + clen; } - hole_sz += rsz; - } else if (hole_sz) { +#endif + off += rsz; + } + if (hole_sz) { xfallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, - hole_start, hole_sz); + hole_start, hole_sz); ct += hole_sz; - hole_sz = hole_start = 0; } - -#if defined(POSIX_FADV_DONTNEED) && defined(HAVE_POSIX_FADVISE) - /* discard cached data */ - if (off - cache_start > (off_t) cachesz) { - size_t clen = off - cache_start; - - clen = (clen / cachesz) * cachesz; - posix_fadvise(fd, cache_start, clen, POSIX_FADV_DONTNEED); - cache_start = cache_start + clen; - } -#endif - off += rsz; - } - - if (hole_sz) { - xfallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, - hole_start, hole_sz); - ct += hole_sz; + file_off = off; } free(buf); -- cgit v1.2.3-55-g7522