From 24b2a479fd5ba138e9b836fa2115939eb3b59dfe Mon Sep 17 00:00:00 2001 From: Rodrigo Campos Date: Sun, 26 Jan 2014 15:06:50 +0000 Subject: [PATCH] fallocate: Add "--dig-holes" option 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 Signed-off-by: Karel Zak --- bash-completion/fallocate | 2 +- sys-utils/fallocate.1 | 19 +++++++- sys-utils/fallocate.c | 99 ++++++++++++++++++++++++++++++--------- 3 files changed, 95 insertions(+), 25 deletions(-) diff --git a/bash-completion/fallocate b/bash-completion/fallocate index 2c6e4cbae..ce5f4f14c 100644 --- a/bash-completion/fallocate +++ b/bash-completion/fallocate @@ -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 ;; diff --git a/sys-utils/fallocate.1 b/sys-utils/fallocate.1 index efa42c1d2..04c426375 100644 --- a/sys-utils/fallocate.1 +++ b/sys-utils/fallocate.1 @@ -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 diff --git a/sys-utils/fallocate.c b/sys-utils/fallocate.c index 5c665535a..b03d12372 100644 --- a/sys-utils/fallocate.c +++ b/sys-utils/fallocate.c @@ -23,6 +23,7 @@ */ #include #include +#include #include #include #include @@ -31,6 +32,7 @@ #include #include #include +#include #ifndef HAVE_FALLOCATE # include @@ -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 offset of the (de)allocation, in bytes\n" " -l, --length 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")); } - return error; +} + +/* + * 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); + } + + 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; }