]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
fallocate: dig holes only in data extents
authorKarel Zak <kzak@redhat.com>
Wed, 29 Nov 2017 13:26:42 +0000 (14:26 +0100)
committerKarel Zak <kzak@redhat.com>
Wed, 29 Nov 2017 14:01:39 +0000 (15:01 +0100)
Based on patch from Vaclav Dolezal <vdolezal@redhat.com>, 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 <vdolezal@redhat.com>
Signed-off-by: Karel Zak <kzak@redhat.com>
sys-utils/fallocate.c

index c8b2b2fcc5cb43542605a8f6e9f427bbaa4812b4..4c53f85353e6d357a83182cc470425423195e0d8 100644 (file)
@@ -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);