fallocate: dig holes only in data extents

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>
This commit is contained in:
Karel Zak 2017-11-29 14:26:42 +01:00
parent f4b03edb73
commit 173ef88237
1 changed files with 55 additions and 64 deletions

View 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));
#if defined(POSIX_FADV_SEQUENTIAL) && defined(HAVE_POSIX_FADVISE)
posix_fadvise(fd, off, 0, POSIX_FADV_SEQUENTIAL);
#endif
while (file_end == 0 || file_off < file_end) {
/*
* Detect data area (skip exiting holes)
*/
off_t end, off;
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)
off = lseek(fd, file_off, SEEK_DATA);
if ((off == -1 && errno == ENXIO) ||
(file_end && off >= file_end))
break;
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;
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, 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;
}
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;
}
#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;
/* 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;
}
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;
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);