]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
fallocate: Add "--dig-holes" option
authorRodrigo Campos <rodrigo@sdfg.com.ar>
Sun, 26 Jan 2014 15:06:50 +0000 (15:06 +0000)
committerKarel Zak <kzak@redhat.com>
Fri, 14 Feb 2014 10:31:12 +0000 (11:31 +0100)
This option tries to detect chunk of '\0's and punch a hole, making the file
sparse in-place.

[kzak@redhat.com: - fix coding style, use xalloc.h and err.h]

Signed-off-by: Rodrigo Campos <rodrigo@sdfg.com.ar>
Signed-off-by: Karel Zak <kzak@redhat.com>
bash-completion/fallocate
sys-utils/fallocate.1
sys-utils/fallocate.c

index 2c6e4cbae756d947f541dd1bfbfe06fd2287401b..ce5f4f14cd1d58c0749505a47f8840b23f71609a 100644 (file)
@@ -15,7 +15,7 @@ _fallocate_module()
        esac
        case $cur in
                -*)
-                       OPTS="--keep-size --punch-hole --offset --length --help --version"
+                       OPTS="--keep-size --punch-hole --dig-holes --offset --length --help --version"
                        COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) )
                        return 0
                        ;;
index efa42c1d20dce864df7d6599d32395c0bc77af72..04c426375970def50677f915c9bbcfd0d25ce916 100644 (file)
@@ -11,6 +11,12 @@ fallocate \- preallocate or deallocate space to a file
 .B \-l
 .IR length
 .I filename
+.PP
+.B fallocate
+.RB \-d
+.RB [ \-l
+.IR length ]
+.I filename
 .SH DESCRIPTION
 .B fallocate
 is used to manipulate the allocated disk space for a file, either to deallocate
@@ -20,7 +26,8 @@ uninitialized, requiring no IO to the data blocks. This is much faster than
 creating a file by filling it with zeros.
 .PP
 As of the Linux Kernel v2.6.31, the fallocate system call is supported by the
-btrfs, ext4, ocfs2, and xfs filesystems.
+btrfs, ext4, ocfs2, and xfs filesystems. Support for options needed to run with
+\fI\-\-punch-hole\fR or \fI\-\-detect-holes\fR was added in Linux 2.6.38.
 .PP
 The exit code returned by
 .B fallocate
@@ -36,6 +43,16 @@ Do not modify the apparent length of the file.  This may effectively allocate
 blocks past EOF, which can be removed with a truncate.
 .IP "\fB\-p, \-\-punch-hole\fP"
 Punch holes in the file, the range should not exceed the length of the file.
+.IP "\fB\-d, \-\-dig-holes\fP"
+Detect and dig holes of, at least, \fIlength\fR size. If \fIlength\fR is not
+specified, it defaults to 32k. Makes the file sparse in-place, without using
+extra disk space. You can think of this as doing a "\fBcp --sparse\fP" and
+renaming the dest file as the original, without the need for extra disk space.
+.PP
+.IP
+Note that too small values for \fIlength\fR might be ignored. And too big values
+might use lot of RAM and not detect many holes. Also, when using this option,
+\fI\-\-keep-size\fP is implied.
 .IP "\fB\-o, \-\-offset\fP \fIoffset\fP
 Specifies the beginning offset of the allocation, in bytes.
 .IP "\fB\-l, \-\-length\fP \fIlength\fP
index 5c665535ab7dde5faefcc876843c2630ab5ab407..b03d123724aa84792ddecdcd304be9caa13f52a2 100644 (file)
@@ -23,6 +23,7 @@
  */
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <sys/mman.h>
 #include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -31,6 +32,7 @@
 #include <unistd.h>
 #include <getopt.h>
 #include <limits.h>
+#include <string.h>
 
 #ifndef HAVE_FALLOCATE
 # include <sys/syscall.h>
@@ -53,6 +55,7 @@
 #include "strutils.h"
 #include "c.h"
 #include "closestream.h"
+#include "xalloc.h"
 
 static void __attribute__((__noreturn__)) usage(FILE *out)
 {
@@ -62,6 +65,7 @@ static void __attribute__((__noreturn__)) usage(FILE *out)
        fputs(USAGE_OPTIONS, out);
        fputs(_(" -n, --keep-size     don't modify the length of the file\n"
                " -p, --punch-hole    punch holes in the file\n"
+               " -d, --dig-holes     detect and dig holes\n"
                " -o, --offset <num>  offset of the (de)allocation, in bytes\n"
                " -l, --length <num>  length of the (de)allocation, in bytes\n"), out);
        fputs(USAGE_SEPARATOR, out);
@@ -82,7 +86,7 @@ static loff_t cvtnum(char *s)
        return x;
 }
 
-static int xfallocate(int fd, int mode, off_t offset, off_t length)
+static void xfallocate(int fd, int mode, off_t offset, off_t length)
 {
        int error;
 
@@ -96,34 +100,74 @@ static int xfallocate(int fd, int mode, off_t offset, off_t length)
         * ENOSYS: The filesystem does not support sys_fallocate
         */
        if (error < 0) {
-               if ((mode & FALLOC_FL_KEEP_SIZE) && errno == EOPNOTSUPP) {
-                       fputs(_("keep size mode (-n option) unsupported\n"),
-                             stderr);
-               } else {
-                       fputs(_("fallocate failed\n"), stderr);
-               }
+               if ((mode & FALLOC_FL_KEEP_SIZE) && errno == EOPNOTSUPP)
+                       errx(EXIT_FAILURE, _("keep size mode (-n option) unsupported"));
+               err(EXIT_FAILURE, _("fallocate failed"));
+       }
+}
+
+/*
+ * Look for chunks of '\0's with size hole_size and when we find them, dig a
+ * hole on that offset with that size
+ */
+static void detect_holes(int fd, size_t hole_size)
+{
+       void *zeros;
+       ssize_t bufsz = hole_size;
+       void *buf;
+       off_t offset, end;
+
+       /* Create a buffer of '\0's to compare against */
+       /* XXX: Use mmap() with MAP_PRIVATE so Linux can avoid this allocation */
+       zeros = mmap(NULL, hole_size, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+       if (zeros == MAP_FAILED)
+               err(EXIT_FAILURE, _("mmap failed"));
+
+       /* buffer to read the file */
+       buf = xmalloc(bufsz);
+
+       end = lseek(fd, 0, SEEK_END);
+       if (end < 0)
+               err(EXIT_FAILURE, _("seek failed"));
+
+       for (offset = 0; offset + (ssize_t) hole_size <= end; offset += bufsz) {
+               /* Try to read hole_size bytes */
+               bufsz = pread(fd, buf, hole_size, offset);
+               if (bufsz == -1)
+                       err(EXIT_FAILURE, _("read failed"));
+
+               /* Always use bufsz, as we may read less than hole_size bytes */
+               if (memcmp(buf, zeros, bufsz))
+                       continue;
+
+               xfallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE,
+                               offset, bufsz);
        }
-       return error;
+
+       if (munmap(zeros, hole_size))
+               err(EXIT_FAILURE, _("munmap failed"));
+       free(buf);
 }
 
 int main(int argc, char **argv)
 {
        char    *fname;
        int     c;
-       int     error;
        int     fd;
        int     mode = 0;
+       int     dig_holes = 0;
        loff_t  length = -2LL;
        loff_t  offset = 0;
 
        static const struct option longopts[] = {
-           { "help",      0, 0, 'h' },
-           { "version",   0, 0, 'V' },
-           { "keep-size", 0, 0, 'n' },
+           { "help",       0, 0, 'h' },
+           { "version",    0, 0, 'V' },
+           { "keep-size",  0, 0, 'n' },
            { "punch-hole", 0, 0, 'p' },
-           { "offset",    1, 0, 'o' },
-           { "length",    1, 0, 'l' },
-           { NULL,        0, 0, 0 }
+           { "dig-holes",  0, 0, 'd' },
+           { "offset",     1, 0, 'o' },
+           { "length",     1, 0, 'l' },
+           { NULL,         0, 0, 0 }
        };
 
        setlocale(LC_ALL, "");
@@ -131,7 +175,7 @@ int main(int argc, char **argv)
        textdomain(PACKAGE);
        atexit(close_stdout);
 
-       while ((c = getopt_long(argc, argv, "hVnpl:o:", longopts, NULL)) != -1) {
+       while ((c = getopt_long(argc, argv, "hVnpdl:o:", longopts, NULL)) != -1) {
                switch(c) {
                case 'h':
                        usage(stdout);
@@ -145,6 +189,9 @@ int main(int argc, char **argv)
                case 'n':
                        mode |= FALLOC_FL_KEEP_SIZE;
                        break;
+               case 'd':
+                       dig_holes = 1;
+                       break;
                case 'l':
                        length = cvtnum(optarg);
                        break;
@@ -156,8 +203,13 @@ int main(int argc, char **argv)
                        break;
                }
        }
-
-       if (length == -2LL)
+       if (dig_holes && mode != 0)
+               errx(EXIT_FAILURE, _("Can't use -p or -n with --dig-holes"));
+       if (dig_holes && offset != 0)
+               errx(EXIT_FAILURE, _("Can't use -o with --dig-holes"));
+       if (length == -2LL && dig_holes)
+               length = 32 * 1024;
+       if (length == -2LL && !dig_holes)
                errx(EXIT_FAILURE, _("no length argument specified"));
        if (length <= 0)
                errx(EXIT_FAILURE, _("invalid length value specified"));
@@ -173,16 +225,17 @@ int main(int argc, char **argv)
                usage(stderr);
        }
 
-       fd = open(fname, O_WRONLY|O_CREAT, 0644);
+       fd = open(fname, O_RDWR|O_CREAT, 0644);
        if (fd < 0)
                err(EXIT_FAILURE, _("cannot open %s"), fname);
 
-       error = xfallocate(fd, mode, offset, length);
-
-       if (error < 0)
-               exit(EXIT_FAILURE);
+       if (dig_holes)
+               detect_holes(fd, length);
+       else
+               xfallocate(fd, mode, offset, length);
 
        if (close_fd(fd) != 0)
                err(EXIT_FAILURE, _("write failed: %s"), fname);
+
        return EXIT_SUCCESS;
 }