mirror of https://github.com/xemu-project/xemu.git
file-posix: Make .bdrv_co_truncate asynchronous
This moves the code to resize an image file to the thread pool to avoid blocking. Creating large images with preallocation with blockdev-create is now actually a background job instead of blocking the monitor (and most other things) until the preallocation has completed. Signed-off-by: Kevin Wolf <kwolf@redhat.com> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
parent
1bc5f09f2e
commit
93f4e2ff4b
|
@ -188,8 +188,16 @@ typedef struct RawPosixAIOData {
|
||||||
#define aio_ioctl_cmd aio_nbytes /* for QEMU_AIO_IOCTL */
|
#define aio_ioctl_cmd aio_nbytes /* for QEMU_AIO_IOCTL */
|
||||||
off_t aio_offset;
|
off_t aio_offset;
|
||||||
int aio_type;
|
int aio_type;
|
||||||
int aio_fd2;
|
union {
|
||||||
off_t aio_offset2;
|
struct {
|
||||||
|
int aio_fd2;
|
||||||
|
off_t aio_offset2;
|
||||||
|
};
|
||||||
|
struct {
|
||||||
|
PreallocMode prealloc;
|
||||||
|
Error **errp;
|
||||||
|
};
|
||||||
|
};
|
||||||
} RawPosixAIOData;
|
} RawPosixAIOData;
|
||||||
|
|
||||||
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
||||||
|
@ -1539,6 +1547,122 @@ static ssize_t handle_aiocb_discard(RawPosixAIOData *aiocb)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int handle_aiocb_truncate(RawPosixAIOData *aiocb)
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
int64_t current_length = 0;
|
||||||
|
char *buf = NULL;
|
||||||
|
struct stat st;
|
||||||
|
int fd = aiocb->aio_fildes;
|
||||||
|
int64_t offset = aiocb->aio_offset;
|
||||||
|
Error **errp = aiocb->errp;
|
||||||
|
|
||||||
|
if (fstat(fd, &st) < 0) {
|
||||||
|
result = -errno;
|
||||||
|
error_setg_errno(errp, -result, "Could not stat file");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_length = st.st_size;
|
||||||
|
if (current_length > offset && aiocb->prealloc != PREALLOC_MODE_OFF) {
|
||||||
|
error_setg(errp, "Cannot use preallocation for shrinking files");
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (aiocb->prealloc) {
|
||||||
|
#ifdef CONFIG_POSIX_FALLOCATE
|
||||||
|
case PREALLOC_MODE_FALLOC:
|
||||||
|
/*
|
||||||
|
* Truncating before posix_fallocate() makes it about twice slower on
|
||||||
|
* file systems that do not support fallocate(), trying to check if a
|
||||||
|
* block is allocated before allocating it, so don't do that here.
|
||||||
|
*/
|
||||||
|
if (offset != current_length) {
|
||||||
|
result = -posix_fallocate(fd, current_length,
|
||||||
|
offset - current_length);
|
||||||
|
if (result != 0) {
|
||||||
|
/* posix_fallocate() doesn't set errno. */
|
||||||
|
error_setg_errno(errp, -result,
|
||||||
|
"Could not preallocate new data");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = 0;
|
||||||
|
}
|
||||||
|
goto out;
|
||||||
|
#endif
|
||||||
|
case PREALLOC_MODE_FULL:
|
||||||
|
{
|
||||||
|
int64_t num = 0, left = offset - current_length;
|
||||||
|
off_t seek_result;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Knowing the final size from the beginning could allow the file
|
||||||
|
* system driver to do less allocations and possibly avoid
|
||||||
|
* fragmentation of the file.
|
||||||
|
*/
|
||||||
|
if (ftruncate(fd, offset) != 0) {
|
||||||
|
result = -errno;
|
||||||
|
error_setg_errno(errp, -result, "Could not resize file");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = g_malloc0(65536);
|
||||||
|
|
||||||
|
seek_result = lseek(fd, current_length, SEEK_SET);
|
||||||
|
if (seek_result < 0) {
|
||||||
|
result = -errno;
|
||||||
|
error_setg_errno(errp, -result,
|
||||||
|
"Failed to seek to the old end of file");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (left > 0) {
|
||||||
|
num = MIN(left, 65536);
|
||||||
|
result = write(fd, buf, num);
|
||||||
|
if (result < 0) {
|
||||||
|
result = -errno;
|
||||||
|
error_setg_errno(errp, -result,
|
||||||
|
"Could not write zeros for preallocation");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
left -= result;
|
||||||
|
}
|
||||||
|
if (result >= 0) {
|
||||||
|
result = fsync(fd);
|
||||||
|
if (result < 0) {
|
||||||
|
result = -errno;
|
||||||
|
error_setg_errno(errp, -result,
|
||||||
|
"Could not flush file to disk");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
case PREALLOC_MODE_OFF:
|
||||||
|
if (ftruncate(fd, offset) != 0) {
|
||||||
|
result = -errno;
|
||||||
|
error_setg_errno(errp, -result, "Could not resize file");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
default:
|
||||||
|
result = -ENOTSUP;
|
||||||
|
error_setg(errp, "Unsupported preallocation mode: %s",
|
||||||
|
PreallocMode_str(aiocb->prealloc));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (result < 0) {
|
||||||
|
if (ftruncate(fd, current_length) < 0) {
|
||||||
|
error_report("Failed to restore old file length: %s",
|
||||||
|
strerror(errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(buf);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static int aio_worker(void *arg)
|
static int aio_worker(void *arg)
|
||||||
{
|
{
|
||||||
RawPosixAIOData *aiocb = arg;
|
RawPosixAIOData *aiocb = arg;
|
||||||
|
@ -1582,6 +1706,9 @@ static int aio_worker(void *arg)
|
||||||
case QEMU_AIO_COPY_RANGE:
|
case QEMU_AIO_COPY_RANGE:
|
||||||
ret = handle_aiocb_copy_range(aiocb);
|
ret = handle_aiocb_copy_range(aiocb);
|
||||||
break;
|
break;
|
||||||
|
case QEMU_AIO_TRUNCATE:
|
||||||
|
ret = handle_aiocb_truncate(aiocb);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
fprintf(stderr, "invalid aio request (0x%x)\n", aiocb->aio_type);
|
fprintf(stderr, "invalid aio request (0x%x)\n", aiocb->aio_type);
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
|
@ -1765,117 +1892,25 @@ static void raw_close(BlockDriverState *bs)
|
||||||
*
|
*
|
||||||
* Returns: 0 on success, -errno on failure.
|
* Returns: 0 on success, -errno on failure.
|
||||||
*/
|
*/
|
||||||
static int raw_regular_truncate(int fd, int64_t offset, PreallocMode prealloc,
|
static int coroutine_fn
|
||||||
Error **errp)
|
raw_regular_truncate(BlockDriverState *bs, int fd, int64_t offset,
|
||||||
|
PreallocMode prealloc, Error **errp)
|
||||||
{
|
{
|
||||||
int result = 0;
|
RawPosixAIOData *acb = g_new(RawPosixAIOData, 1);
|
||||||
int64_t current_length = 0;
|
ThreadPool *pool;
|
||||||
char *buf = NULL;
|
|
||||||
struct stat st;
|
|
||||||
|
|
||||||
if (fstat(fd, &st) < 0) {
|
*acb = (RawPosixAIOData) {
|
||||||
result = -errno;
|
.bs = bs,
|
||||||
error_setg_errno(errp, -result, "Could not stat file");
|
.aio_fildes = fd,
|
||||||
return result;
|
.aio_type = QEMU_AIO_TRUNCATE,
|
||||||
}
|
.aio_offset = offset,
|
||||||
|
.prealloc = prealloc,
|
||||||
|
.errp = errp,
|
||||||
|
};
|
||||||
|
|
||||||
current_length = st.st_size;
|
/* @bs can be NULL, bdrv_get_aio_context() returns the main context then */
|
||||||
if (current_length > offset && prealloc != PREALLOC_MODE_OFF) {
|
pool = aio_get_thread_pool(bdrv_get_aio_context(bs));
|
||||||
error_setg(errp, "Cannot use preallocation for shrinking files");
|
return thread_pool_submit_co(pool, aio_worker, acb);
|
||||||
return -ENOTSUP;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (prealloc) {
|
|
||||||
#ifdef CONFIG_POSIX_FALLOCATE
|
|
||||||
case PREALLOC_MODE_FALLOC:
|
|
||||||
/*
|
|
||||||
* Truncating before posix_fallocate() makes it about twice slower on
|
|
||||||
* file systems that do not support fallocate(), trying to check if a
|
|
||||||
* block is allocated before allocating it, so don't do that here.
|
|
||||||
*/
|
|
||||||
if (offset != current_length) {
|
|
||||||
result = -posix_fallocate(fd, current_length, offset - current_length);
|
|
||||||
if (result != 0) {
|
|
||||||
/* posix_fallocate() doesn't set errno. */
|
|
||||||
error_setg_errno(errp, -result,
|
|
||||||
"Could not preallocate new data");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = 0;
|
|
||||||
}
|
|
||||||
goto out;
|
|
||||||
#endif
|
|
||||||
case PREALLOC_MODE_FULL:
|
|
||||||
{
|
|
||||||
int64_t num = 0, left = offset - current_length;
|
|
||||||
off_t seek_result;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Knowing the final size from the beginning could allow the file
|
|
||||||
* system driver to do less allocations and possibly avoid
|
|
||||||
* fragmentation of the file.
|
|
||||||
*/
|
|
||||||
if (ftruncate(fd, offset) != 0) {
|
|
||||||
result = -errno;
|
|
||||||
error_setg_errno(errp, -result, "Could not resize file");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = g_malloc0(65536);
|
|
||||||
|
|
||||||
seek_result = lseek(fd, current_length, SEEK_SET);
|
|
||||||
if (seek_result < 0) {
|
|
||||||
result = -errno;
|
|
||||||
error_setg_errno(errp, -result,
|
|
||||||
"Failed to seek to the old end of file");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (left > 0) {
|
|
||||||
num = MIN(left, 65536);
|
|
||||||
result = write(fd, buf, num);
|
|
||||||
if (result < 0) {
|
|
||||||
result = -errno;
|
|
||||||
error_setg_errno(errp, -result,
|
|
||||||
"Could not write zeros for preallocation");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
left -= result;
|
|
||||||
}
|
|
||||||
if (result >= 0) {
|
|
||||||
result = fsync(fd);
|
|
||||||
if (result < 0) {
|
|
||||||
result = -errno;
|
|
||||||
error_setg_errno(errp, -result,
|
|
||||||
"Could not flush file to disk");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
case PREALLOC_MODE_OFF:
|
|
||||||
if (ftruncate(fd, offset) != 0) {
|
|
||||||
result = -errno;
|
|
||||||
error_setg_errno(errp, -result, "Could not resize file");
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
default:
|
|
||||||
result = -ENOTSUP;
|
|
||||||
error_setg(errp, "Unsupported preallocation mode: %s",
|
|
||||||
PreallocMode_str(prealloc));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
out:
|
|
||||||
if (result < 0) {
|
|
||||||
if (ftruncate(fd, current_length) < 0) {
|
|
||||||
error_report("Failed to restore old file length: %s",
|
|
||||||
strerror(errno));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g_free(buf);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int coroutine_fn raw_co_truncate(BlockDriverState *bs, int64_t offset,
|
static int coroutine_fn raw_co_truncate(BlockDriverState *bs, int64_t offset,
|
||||||
|
@ -1892,7 +1927,7 @@ static int coroutine_fn raw_co_truncate(BlockDriverState *bs, int64_t offset,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (S_ISREG(st.st_mode)) {
|
if (S_ISREG(st.st_mode)) {
|
||||||
return raw_regular_truncate(s->fd, offset, prealloc, errp);
|
return raw_regular_truncate(bs, s->fd, offset, prealloc, errp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prealloc != PREALLOC_MODE_OFF) {
|
if (prealloc != PREALLOC_MODE_OFF) {
|
||||||
|
@ -2094,7 +2129,8 @@ static int64_t raw_get_allocated_file_size(BlockDriverState *bs)
|
||||||
return (int64_t)st.st_blocks * 512;
|
return (int64_t)st.st_blocks * 512;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int raw_co_create(BlockdevCreateOptions *options, Error **errp)
|
static int coroutine_fn
|
||||||
|
raw_co_create(BlockdevCreateOptions *options, Error **errp)
|
||||||
{
|
{
|
||||||
BlockdevCreateOptionsFile *file_opts;
|
BlockdevCreateOptionsFile *file_opts;
|
||||||
int fd;
|
int fd;
|
||||||
|
@ -2146,7 +2182,7 @@ static int raw_co_create(BlockdevCreateOptions *options, Error **errp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Clear the file by truncating it to 0 */
|
/* Clear the file by truncating it to 0 */
|
||||||
result = raw_regular_truncate(fd, 0, PREALLOC_MODE_OFF, errp);
|
result = raw_regular_truncate(NULL, fd, 0, PREALLOC_MODE_OFF, errp);
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
goto out_close;
|
goto out_close;
|
||||||
}
|
}
|
||||||
|
@ -2168,8 +2204,8 @@ static int raw_co_create(BlockdevCreateOptions *options, Error **errp)
|
||||||
|
|
||||||
/* Resize and potentially preallocate the file to the desired
|
/* Resize and potentially preallocate the file to the desired
|
||||||
* final size */
|
* final size */
|
||||||
result = raw_regular_truncate(fd, file_opts->size, file_opts->preallocation,
|
result = raw_regular_truncate(NULL, fd, file_opts->size,
|
||||||
errp);
|
file_opts->preallocation, errp);
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
goto out_close;
|
goto out_close;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#define QEMU_AIO_DISCARD 0x0010
|
#define QEMU_AIO_DISCARD 0x0010
|
||||||
#define QEMU_AIO_WRITE_ZEROES 0x0020
|
#define QEMU_AIO_WRITE_ZEROES 0x0020
|
||||||
#define QEMU_AIO_COPY_RANGE 0x0040
|
#define QEMU_AIO_COPY_RANGE 0x0040
|
||||||
|
#define QEMU_AIO_TRUNCATE 0x0080
|
||||||
#define QEMU_AIO_TYPE_MASK \
|
#define QEMU_AIO_TYPE_MASK \
|
||||||
(QEMU_AIO_READ | \
|
(QEMU_AIO_READ | \
|
||||||
QEMU_AIO_WRITE | \
|
QEMU_AIO_WRITE | \
|
||||||
|
@ -33,7 +34,8 @@
|
||||||
QEMU_AIO_FLUSH | \
|
QEMU_AIO_FLUSH | \
|
||||||
QEMU_AIO_DISCARD | \
|
QEMU_AIO_DISCARD | \
|
||||||
QEMU_AIO_WRITE_ZEROES | \
|
QEMU_AIO_WRITE_ZEROES | \
|
||||||
QEMU_AIO_COPY_RANGE)
|
QEMU_AIO_COPY_RANGE | \
|
||||||
|
QEMU_AIO_TRUNCATE)
|
||||||
|
|
||||||
/* AIO flags */
|
/* AIO flags */
|
||||||
#define QEMU_AIO_MISALIGNED 0x1000
|
#define QEMU_AIO_MISALIGNED 0x1000
|
||||||
|
|
Loading…
Reference in New Issue