block/raw-posix: Try both FIEMAP and SEEK_HOLE
The current version of raw-posix always uses ioctl(FS_IOC_FIEMAP) if FIEMAP is available; lseek with SEEK_HOLE/SEEK_DATA are not even compiled in in this case. However, there may be implementations which support the latter but not the former (e.g., NFSv4.2) as well as vice versa. To cover both cases, try FIEMAP first (as this will return -ENOTSUP if not supported instead of returning a failsafe value (everything allocated as a single extent)) and if that does not work, fall back to SEEK_HOLE/SEEK_DATA. Signed-off-by: Max Reitz <mreitz@redhat.com> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
parent
4557117d9e
commit
4f11aa8a40
@ -146,6 +146,9 @@ typedef struct BDRVRawState {
|
||||
bool has_discard:1;
|
||||
bool has_write_zeroes:1;
|
||||
bool discard_zeroes:1;
|
||||
#ifdef CONFIG_FIEMAP
|
||||
bool skip_fiemap;
|
||||
#endif
|
||||
} BDRVRawState;
|
||||
|
||||
typedef struct BDRVRawReopenState {
|
||||
@ -1272,6 +1275,83 @@ static int raw_create(const char *filename, QEMUOptionParameter *options,
|
||||
return result;
|
||||
}
|
||||
|
||||
static int64_t try_fiemap(BlockDriverState *bs, off_t start, off_t *data,
|
||||
off_t *hole, int nb_sectors, int *pnum)
|
||||
{
|
||||
#ifdef CONFIG_FIEMAP
|
||||
BDRVRawState *s = bs->opaque;
|
||||
int64_t ret = BDRV_BLOCK_DATA | BDRV_BLOCK_OFFSET_VALID | start;
|
||||
struct {
|
||||
struct fiemap fm;
|
||||
struct fiemap_extent fe;
|
||||
} f;
|
||||
|
||||
if (s->skip_fiemap) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
f.fm.fm_start = start;
|
||||
f.fm.fm_length = (int64_t)nb_sectors * BDRV_SECTOR_SIZE;
|
||||
f.fm.fm_flags = 0;
|
||||
f.fm.fm_extent_count = 1;
|
||||
f.fm.fm_reserved = 0;
|
||||
if (ioctl(s->fd, FS_IOC_FIEMAP, &f) == -1) {
|
||||
s->skip_fiemap = true;
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (f.fm.fm_mapped_extents == 0) {
|
||||
/* No extents found, data is beyond f.fm.fm_start + f.fm.fm_length.
|
||||
* f.fm.fm_start + f.fm.fm_length must be clamped to the file size!
|
||||
*/
|
||||
off_t length = lseek(s->fd, 0, SEEK_END);
|
||||
*hole = f.fm.fm_start;
|
||||
*data = MIN(f.fm.fm_start + f.fm.fm_length, length);
|
||||
} else {
|
||||
*data = f.fe.fe_logical;
|
||||
*hole = f.fe.fe_logical + f.fe.fe_length;
|
||||
if (f.fe.fe_flags & FIEMAP_EXTENT_UNWRITTEN) {
|
||||
ret |= BDRV_BLOCK_ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
#else
|
||||
return -ENOTSUP;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int64_t try_seek_hole(BlockDriverState *bs, off_t start, off_t *data,
|
||||
off_t *hole, int *pnum)
|
||||
{
|
||||
#if defined SEEK_HOLE && defined SEEK_DATA
|
||||
BDRVRawState *s = bs->opaque;
|
||||
|
||||
*hole = lseek(s->fd, start, SEEK_HOLE);
|
||||
if (*hole == -1) {
|
||||
/* -ENXIO indicates that sector_num was past the end of the file.
|
||||
* There is a virtual hole there. */
|
||||
assert(errno != -ENXIO);
|
||||
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (*hole > start) {
|
||||
*data = start;
|
||||
} else {
|
||||
/* On a hole. We need another syscall to find its end. */
|
||||
*data = lseek(s->fd, start, SEEK_DATA);
|
||||
if (*data == -1) {
|
||||
*data = lseek(s->fd, 0, SEEK_END);
|
||||
}
|
||||
}
|
||||
|
||||
return BDRV_BLOCK_DATA | BDRV_BLOCK_OFFSET_VALID | start;
|
||||
#else
|
||||
return -ENOTSUP;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true iff the specified sector is present in the disk image. Drivers
|
||||
* not implementing the functionality are assumed to not support backing files,
|
||||
@ -1288,10 +1368,10 @@ static int raw_create(const char *filename, QEMUOptionParameter *options,
|
||||
* beyond the end of the disk image it will be clamped.
|
||||
*/
|
||||
static int64_t coroutine_fn raw_co_get_block_status(BlockDriverState *bs,
|
||||
int64_t sector_num,
|
||||
int nb_sectors, int *pnum)
|
||||
int64_t sector_num,
|
||||
int nb_sectors, int *pnum)
|
||||
{
|
||||
off_t start, data, hole;
|
||||
off_t start, data = 0, hole = 0;
|
||||
int64_t ret;
|
||||
|
||||
ret = fd_open(bs);
|
||||
@ -1300,71 +1380,18 @@ static int64_t coroutine_fn raw_co_get_block_status(BlockDriverState *bs,
|
||||
}
|
||||
|
||||
start = sector_num * BDRV_SECTOR_SIZE;
|
||||
ret = BDRV_BLOCK_DATA | BDRV_BLOCK_OFFSET_VALID | start;
|
||||
|
||||
#ifdef CONFIG_FIEMAP
|
||||
|
||||
BDRVRawState *s = bs->opaque;
|
||||
struct {
|
||||
struct fiemap fm;
|
||||
struct fiemap_extent fe;
|
||||
} f;
|
||||
|
||||
f.fm.fm_start = start;
|
||||
f.fm.fm_length = (int64_t)nb_sectors * BDRV_SECTOR_SIZE;
|
||||
f.fm.fm_flags = 0;
|
||||
f.fm.fm_extent_count = 1;
|
||||
f.fm.fm_reserved = 0;
|
||||
if (ioctl(s->fd, FS_IOC_FIEMAP, &f) == -1) {
|
||||
/* Assume everything is allocated. */
|
||||
*pnum = nb_sectors;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (f.fm.fm_mapped_extents == 0) {
|
||||
/* No extents found, data is beyond f.fm.fm_start + f.fm.fm_length.
|
||||
* f.fm.fm_start + f.fm.fm_length must be clamped to the file size!
|
||||
*/
|
||||
off_t length = lseek(s->fd, 0, SEEK_END);
|
||||
hole = f.fm.fm_start;
|
||||
data = MIN(f.fm.fm_start + f.fm.fm_length, length);
|
||||
} else {
|
||||
data = f.fe.fe_logical;
|
||||
hole = f.fe.fe_logical + f.fe.fe_length;
|
||||
if (f.fe.fe_flags & FIEMAP_EXTENT_UNWRITTEN) {
|
||||
ret |= BDRV_BLOCK_ZERO;
|
||||
ret = try_fiemap(bs, start, &data, &hole, nb_sectors, pnum);
|
||||
if (ret < 0) {
|
||||
ret = try_seek_hole(bs, start, &data, &hole, pnum);
|
||||
if (ret < 0) {
|
||||
/* Assume everything is allocated. */
|
||||
data = 0;
|
||||
hole = start + nb_sectors * BDRV_SECTOR_SIZE;
|
||||
ret = BDRV_BLOCK_DATA | BDRV_BLOCK_OFFSET_VALID | start;
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined SEEK_HOLE && defined SEEK_DATA
|
||||
|
||||
BDRVRawState *s = bs->opaque;
|
||||
|
||||
hole = lseek(s->fd, start, SEEK_HOLE);
|
||||
if (hole == -1) {
|
||||
/* -ENXIO indicates that sector_num was past the end of the file.
|
||||
* There is a virtual hole there. */
|
||||
assert(errno != -ENXIO);
|
||||
|
||||
/* Most likely EINVAL. Assume everything is allocated. */
|
||||
*pnum = nb_sectors;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (hole > start) {
|
||||
data = start;
|
||||
} else {
|
||||
/* On a hole. We need another syscall to find its end. */
|
||||
data = lseek(s->fd, start, SEEK_DATA);
|
||||
if (data == -1) {
|
||||
data = lseek(s->fd, 0, SEEK_END);
|
||||
}
|
||||
}
|
||||
#else
|
||||
data = 0;
|
||||
hole = start + nb_sectors * BDRV_SECTOR_SIZE;
|
||||
#endif
|
||||
|
||||
if (data <= start) {
|
||||
/* On a data extent, compute sectors to the end of the extent. */
|
||||
*pnum = MIN(nb_sectors, (hole - start) / BDRV_SECTOR_SIZE);
|
||||
|
Loading…
Reference in New Issue
Block a user