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 <rodrigo@sdfg.com.ar>
Signed-off-by: Karel Zak <kzak@redhat.com>
This commit is contained in:
Rodrigo Campos 2014-01-26 15:06:50 +00:00 committed by Karel Zak
parent bcd9315d4b
commit 24b2a479fd
3 changed files with 95 additions and 25 deletions

View 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
;;

View 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

View 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"));
}
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;
}