* [PATCH v3 1/4] io/channel: introduce qio_channel_pread{v, }_all{, _eof}()
2026-04-13 21:45 [PATCH v3 0/4] io/channel: complete pread/pwrite_all API and fix multifd_file_recv_data Junjie Cao
@ 2026-04-13 21:45 ` Junjie Cao
2026-04-14 15:49 ` Fabiano Rosas
2026-04-17 9:45 ` [PATCH v3 1/4] io/channel: introduce qio_channel_pread{v,}_all{,_eof}() Daniel P. Berrangé
2026-04-13 21:45 ` [PATCH v3 2/4] io/channel: introduce qio_channel_pwrite{v,}_all() Junjie Cao
` (4 subsequent siblings)
5 siblings, 2 replies; 18+ messages in thread
From: Junjie Cao @ 2026-04-13 21:45 UTC (permalink / raw)
To: qemu-devel; +Cc: berrange, peterx, farosas, junjie.cao
qio_channel_pread() and qio_channel_preadv() perform a single
positioned read and may return a short result. Callers that need all
bytes currently have to open-code a retry loop or simply treat a short
read as an error.
Introduce four new helpers following the existing read_all / readv_all
pattern:
qio_channel_preadv_all_eof() -- retry loop; returns 1 on success,
0 on clean EOF, -1 on error.
qio_channel_preadv_all() -- wraps _eof; treats early EOF as
error; returns 0 / -1.
qio_channel_pread_all_eof() -- single-buffer convenience wrapper
around preadv_all_eof().
qio_channel_pread_all() -- single-buffer convenience wrapper
around preadv_all().
These advance the file offset internally after each partial read.
All four are marked coroutine_mixed_fn, consistent with the existing
_all helpers.
Suggested-by: Peter Xu <peterx@redhat.com>
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
---
include/io/channel.h | 92 ++++++++++++++++++++++++++++++++++++++++++++
io/channel.c | 91 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 183 insertions(+)
diff --git a/include/io/channel.h b/include/io/channel.h
index 1b02350437..47af409ede 100644
--- a/include/io/channel.h
+++ b/include/io/channel.h
@@ -634,6 +634,98 @@ ssize_t qio_channel_preadv(QIOChannel *ioc, const struct iovec *iov,
ssize_t qio_channel_pread(QIOChannel *ioc, void *buf, size_t buflen,
off_t offset, Error **errp);
+/**
+ * qio_channel_preadv_all_eof:
+ * @ioc: the channel object
+ * @iov: the array of memory regions to read data into
+ * @niov: the length of the @iov array
+ * @offset: the starting offset in the channel to read from
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Reads @iov, possibly blocking or (if the channel is non-blocking)
+ * yielding from the current coroutine multiple times until the entire
+ * content is read. If end-of-file occurs immediately it is not an
+ * error, but if it occurs after data has been read it will return
+ * an error rather than a short-read. Otherwise behaves as
+ * qio_channel_preadv().
+ *
+ * Returns: 1 if all bytes were read, 0 if end-of-file occurs
+ * without data, or -1 on error
+ */
+int coroutine_mixed_fn qio_channel_preadv_all_eof(QIOChannel *ioc,
+ const struct iovec *iov,
+ size_t niov,
+ off_t offset,
+ Error **errp);
+
+/**
+ * qio_channel_preadv_all:
+ * @ioc: the channel object
+ * @iov: the array of memory regions to read data into
+ * @niov: the length of the @iov array
+ * @offset: the starting offset in the channel to read from
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Reads @iov, possibly blocking or (if the channel is non-blocking)
+ * yielding from the current coroutine multiple times until the entire
+ * content is read. If end-of-file occurs before all requested data
+ * has been read, an error will be reported. Otherwise behaves as
+ * qio_channel_preadv().
+ *
+ * Returns: 0 if all bytes were read, or -1 on error
+ */
+int coroutine_mixed_fn qio_channel_preadv_all(QIOChannel *ioc,
+ const struct iovec *iov,
+ size_t niov,
+ off_t offset,
+ Error **errp);
+
+/**
+ * qio_channel_pread_all_eof:
+ * @ioc: the channel object
+ * @buf: the memory region to read data into
+ * @buflen: the number of bytes to read into @buf
+ * @offset: the starting offset in the channel to read from
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Reads @buflen bytes, possibly blocking or (if the channel is
+ * non-blocking) yielding from the current coroutine multiple times
+ * until the entire content is read. If end-of-file occurs
+ * immediately it is not an error, but if it occurs after data has
+ * been read it will return an error rather than a short-read.
+ * Otherwise behaves as qio_channel_pread().
+ *
+ * Returns: 1 if all bytes were read, 0 if end-of-file occurs
+ * without data, or -1 on error
+ */
+int coroutine_mixed_fn qio_channel_pread_all_eof(QIOChannel *ioc,
+ void *buf,
+ size_t buflen,
+ off_t offset,
+ Error **errp);
+
+/**
+ * qio_channel_pread_all:
+ * @ioc: the channel object
+ * @buf: the memory region to read data into
+ * @buflen: the number of bytes to read into @buf
+ * @offset: the starting offset in the channel to read from
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Reads @buflen bytes, possibly blocking or (if the channel is
+ * non-blocking) yielding from the current coroutine multiple times
+ * until the entire content is read. If end-of-file occurs before
+ * all requested data has been read, an error will be reported.
+ * Otherwise behaves as qio_channel_pread().
+ *
+ * Returns: 0 if all bytes were read, or -1 on error
+ */
+int coroutine_mixed_fn qio_channel_pread_all(QIOChannel *ioc,
+ void *buf,
+ size_t buflen,
+ off_t offset,
+ Error **errp);
+
/**
* qio_channel_shutdown:
* @ioc: the channel object
diff --git a/io/channel.c b/io/channel.c
index cc02d997a4..52c1abfcbc 100644
--- a/io/channel.c
+++ b/io/channel.c
@@ -507,6 +507,97 @@ ssize_t qio_channel_pread(QIOChannel *ioc, void *buf, size_t buflen,
return qio_channel_preadv(ioc, &iov, 1, offset, errp);
}
+int coroutine_mixed_fn qio_channel_preadv_all_eof(QIOChannel *ioc,
+ const struct iovec *iov,
+ size_t niov,
+ off_t offset,
+ Error **errp)
+{
+ int ret = -1;
+ struct iovec *local_iov = g_new(struct iovec, niov);
+ struct iovec *local_iov_head = local_iov;
+ unsigned int nlocal_iov = niov;
+ bool partial = false;
+
+ nlocal_iov = iov_copy(local_iov, nlocal_iov,
+ iov, niov,
+ 0, iov_size(iov, niov));
+
+ while (nlocal_iov > 0) {
+ ssize_t len;
+ len = qio_channel_preadv(ioc, local_iov, nlocal_iov, offset, errp);
+
+ if (len == QIO_CHANNEL_ERR_BLOCK) {
+ qio_channel_wait_cond(ioc, G_IO_IN);
+ continue;
+ }
+
+ if (len == 0) {
+ if (!partial) {
+ ret = 0;
+ goto cleanup;
+ }
+ error_setg(errp,
+ "Unexpected end-of-file before all data were read");
+ goto cleanup;
+ }
+
+ if (len < 0) {
+ goto cleanup;
+ }
+
+ partial = true;
+ offset += len;
+ iov_discard_front(&local_iov, &nlocal_iov, len);
+ }
+
+ ret = 1;
+
+ cleanup:
+ g_free(local_iov_head);
+ return ret;
+}
+
+int coroutine_mixed_fn qio_channel_preadv_all(QIOChannel *ioc,
+ const struct iovec *iov,
+ size_t niov,
+ off_t offset,
+ Error **errp)
+{
+ int ret = qio_channel_preadv_all_eof(ioc, iov, niov, offset, errp);
+
+ if (ret == 0) {
+ error_setg(errp,
+ "Unexpected end-of-file before all data were read");
+ return -1;
+ }
+ if (ret == 1) {
+ return 0;
+ }
+
+ return ret;
+}
+
+int coroutine_mixed_fn qio_channel_pread_all_eof(QIOChannel *ioc,
+ void *buf,
+ size_t buflen,
+ off_t offset,
+ Error **errp)
+{
+ struct iovec iov = { .iov_base = buf, .iov_len = buflen };
+ return qio_channel_preadv_all_eof(ioc, &iov, 1, offset, errp);
+}
+
+int coroutine_mixed_fn qio_channel_pread_all(QIOChannel *ioc,
+ void *buf,
+ size_t buflen,
+ off_t offset,
+ Error **errp)
+{
+ struct iovec iov = { .iov_base = buf, .iov_len = buflen };
+ return qio_channel_preadv_all(ioc, &iov, 1, offset, errp);
+}
+
int qio_channel_shutdown(QIOChannel *ioc,
QIOChannelShutdown how,
Error **errp)
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH v3 1/4] io/channel: introduce qio_channel_pread{v, }_all{, _eof}()
2026-04-13 21:45 ` [PATCH v3 1/4] io/channel: introduce qio_channel_pread{v, }_all{, _eof}() Junjie Cao
@ 2026-04-14 15:49 ` Fabiano Rosas
2026-04-17 9:45 ` [PATCH v3 1/4] io/channel: introduce qio_channel_pread{v,}_all{,_eof}() Daniel P. Berrangé
1 sibling, 0 replies; 18+ messages in thread
From: Fabiano Rosas @ 2026-04-14 15:49 UTC (permalink / raw)
To: Junjie Cao, qemu-devel; +Cc: berrange, peterx, junjie.cao
Junjie Cao <junjie.cao@intel.com> writes:
> qio_channel_pread() and qio_channel_preadv() perform a single
> positioned read and may return a short result. Callers that need all
> bytes currently have to open-code a retry loop or simply treat a short
> read as an error.
>
> Introduce four new helpers following the existing read_all / readv_all
> pattern:
>
> qio_channel_preadv_all_eof() -- retry loop; returns 1 on success,
> 0 on clean EOF, -1 on error.
> qio_channel_preadv_all() -- wraps _eof; treats early EOF as
> error; returns 0 / -1.
> qio_channel_pread_all_eof() -- single-buffer convenience wrapper
> around preadv_all_eof().
> qio_channel_pread_all() -- single-buffer convenience wrapper
> around preadv_all().
>
> These advance the file offset internally after each partial read.
> All four are marked coroutine_mixed_fn, consistent with the existing
> _all helpers.
>
> Suggested-by: Peter Xu <peterx@redhat.com>
> Signed-off-by: Junjie Cao <junjie.cao@intel.com>
> ---
> include/io/channel.h | 92 ++++++++++++++++++++++++++++++++++++++++++++
> io/channel.c | 91 +++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 183 insertions(+)
>
> diff --git a/include/io/channel.h b/include/io/channel.h
> index 1b02350437..47af409ede 100644
> --- a/include/io/channel.h
> +++ b/include/io/channel.h
> @@ -634,6 +634,98 @@ ssize_t qio_channel_preadv(QIOChannel *ioc, const struct iovec *iov,
> ssize_t qio_channel_pread(QIOChannel *ioc, void *buf, size_t buflen,
> off_t offset, Error **errp);
>
> +/**
> + * qio_channel_preadv_all_eof:
> + * @ioc: the channel object
> + * @iov: the array of memory regions to read data into
> + * @niov: the length of the @iov array
> + * @offset: the starting offset in the channel to read from
> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Reads @iov, possibly blocking or (if the channel is non-blocking)
> + * yielding from the current coroutine multiple times until the entire
> + * content is read. If end-of-file occurs immediately it is not an
> + * error, but if it occurs after data has been read it will return
> + * an error rather than a short-read. Otherwise behaves as
> + * qio_channel_preadv().
> + *
> + * Returns: 1 if all bytes were read, 0 if end-of-file occurs
> + * without data, or -1 on error
> + */
> +int coroutine_mixed_fn qio_channel_preadv_all_eof(QIOChannel *ioc,
> + const struct iovec *iov,
> + size_t niov,
> + off_t offset,
> + Error **errp);
> +
> +/**
> + * qio_channel_preadv_all:
> + * @ioc: the channel object
> + * @iov: the array of memory regions to read data into
> + * @niov: the length of the @iov array
> + * @offset: the starting offset in the channel to read from
> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Reads @iov, possibly blocking or (if the channel is non-blocking)
> + * yielding from the current coroutine multiple times until the entire
> + * content is read. If end-of-file occurs before all requested data
> + * has been read, an error will be reported. Otherwise behaves as
> + * qio_channel_preadv().
> + *
> + * Returns: 0 if all bytes were read, or -1 on error
> + */
> +int coroutine_mixed_fn qio_channel_preadv_all(QIOChannel *ioc,
> + const struct iovec *iov,
> + size_t niov,
> + off_t offset,
> + Error **errp);
> +
> +/**
> + * qio_channel_pread_all_eof:
> + * @ioc: the channel object
> + * @buf: the memory region to read data into
> + * @buflen: the number of bytes to read into @buf
> + * @offset: the starting offset in the channel to read from
> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Reads @buflen bytes, possibly blocking or (if the channel is
> + * non-blocking) yielding from the current coroutine multiple times
> + * until the entire content is read. If end-of-file occurs
> + * immediately it is not an error, but if it occurs after data has
> + * been read it will return an error rather than a short-read.
> + * Otherwise behaves as qio_channel_pread().
> + *
> + * Returns: 1 if all bytes were read, 0 if end-of-file occurs
> + * without data, or -1 on error
> + */
> +int coroutine_mixed_fn qio_channel_pread_all_eof(QIOChannel *ioc,
> + void *buf,
> + size_t buflen,
> + off_t offset,
> + Error **errp);
> +
> +/**
> + * qio_channel_pread_all:
> + * @ioc: the channel object
> + * @buf: the memory region to read data into
> + * @buflen: the number of bytes to read into @buf
> + * @offset: the starting offset in the channel to read from
> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Reads @buflen bytes, possibly blocking or (if the channel is
> + * non-blocking) yielding from the current coroutine multiple times
> + * until the entire content is read. If end-of-file occurs before
> + * all requested data has been read, an error will be reported.
> + * Otherwise behaves as qio_channel_pread().
> + *
> + * Returns: 0 if all bytes were read, or -1 on error
> + */
> +int coroutine_mixed_fn qio_channel_pread_all(QIOChannel *ioc,
> + void *buf,
> + size_t buflen,
> + off_t offset,
> + Error **errp);
> +
> /**
> * qio_channel_shutdown:
> * @ioc: the channel object
> diff --git a/io/channel.c b/io/channel.c
> index cc02d997a4..52c1abfcbc 100644
> --- a/io/channel.c
> +++ b/io/channel.c
> @@ -507,6 +507,97 @@ ssize_t qio_channel_pread(QIOChannel *ioc, void *buf, size_t buflen,
> return qio_channel_preadv(ioc, &iov, 1, offset, errp);
> }
>
> +int coroutine_mixed_fn qio_channel_preadv_all_eof(QIOChannel *ioc,
> + const struct iovec *iov,
> + size_t niov,
> + off_t offset,
> + Error **errp)
> +{
> + int ret = -1;
> + struct iovec *local_iov = g_new(struct iovec, niov);
> + struct iovec *local_iov_head = local_iov;
> + unsigned int nlocal_iov = niov;
> + bool partial = false;
> +
> + nlocal_iov = iov_copy(local_iov, nlocal_iov,
> + iov, niov,
> + 0, iov_size(iov, niov));
> +
> + while (nlocal_iov > 0) {
> + ssize_t len;
> + len = qio_channel_preadv(ioc, local_iov, nlocal_iov, offset, errp);
> +
> + if (len == QIO_CHANNEL_ERR_BLOCK) {
> + qio_channel_wait_cond(ioc, G_IO_IN);
> + continue;
> + }
> +
> + if (len == 0) {
> + if (!partial) {
> + ret = 0;
> + goto cleanup;
> + }
> + error_setg(errp,
> + "Unexpected end-of-file before all data were read");
> + goto cleanup;
> + }
> +
> + if (len < 0) {
> + goto cleanup;
> + }
> +
> + partial = true;
> + offset += len;
> + iov_discard_front(&local_iov, &nlocal_iov, len);
> + }
> +
> + ret = 1;
> +
> + cleanup:
> + g_free(local_iov_head);
> + return ret;
> +}
> +
> +int coroutine_mixed_fn qio_channel_preadv_all(QIOChannel *ioc,
> + const struct iovec *iov,
> + size_t niov,
> + off_t offset,
> + Error **errp)
> +{
> + int ret = qio_channel_preadv_all_eof(ioc, iov, niov, offset, errp);
> +
> + if (ret == 0) {
> + error_setg(errp,
> + "Unexpected end-of-file before all data were read");
> + return -1;
> + }
> + if (ret == 1) {
> + return 0;
> + }
> +
> + return ret;
> +}
> +
> +int coroutine_mixed_fn qio_channel_pread_all_eof(QIOChannel *ioc,
> + void *buf,
> + size_t buflen,
> + off_t offset,
> + Error **errp)
> +{
> + struct iovec iov = { .iov_base = buf, .iov_len = buflen };
> + return qio_channel_preadv_all_eof(ioc, &iov, 1, offset, errp);
> +}
> +
> +int coroutine_mixed_fn qio_channel_pread_all(QIOChannel *ioc,
> + void *buf,
> + size_t buflen,
> + off_t offset,
> + Error **errp)
> +{
> + struct iovec iov = { .iov_base = buf, .iov_len = buflen };
> + return qio_channel_preadv_all(ioc, &iov, 1, offset, errp);
> +}
> +
> int qio_channel_shutdown(QIOChannel *ioc,
> QIOChannelShutdown how,
> Error **errp)
Reviewed-by: Fabiano Rosas <farosas@suse.de>
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v3 1/4] io/channel: introduce qio_channel_pread{v,}_all{,_eof}()
2026-04-13 21:45 ` [PATCH v3 1/4] io/channel: introduce qio_channel_pread{v, }_all{, _eof}() Junjie Cao
2026-04-14 15:49 ` Fabiano Rosas
@ 2026-04-17 9:45 ` Daniel P. Berrangé
1 sibling, 0 replies; 18+ messages in thread
From: Daniel P. Berrangé @ 2026-04-17 9:45 UTC (permalink / raw)
To: Junjie Cao; +Cc: qemu-devel, peterx, farosas
On Tue, Apr 14, 2026 at 05:45:46AM +0800, Junjie Cao wrote:
> qio_channel_pread() and qio_channel_preadv() perform a single
> positioned read and may return a short result. Callers that need all
> bytes currently have to open-code a retry loop or simply treat a short
> read as an error.
>
> Introduce four new helpers following the existing read_all / readv_all
> pattern:
>
> qio_channel_preadv_all_eof() -- retry loop; returns 1 on success,
> 0 on clean EOF, -1 on error.
> qio_channel_preadv_all() -- wraps _eof; treats early EOF as
> error; returns 0 / -1.
> qio_channel_pread_all_eof() -- single-buffer convenience wrapper
> around preadv_all_eof().
> qio_channel_pread_all() -- single-buffer convenience wrapper
> around preadv_all().
>
> These advance the file offset internally after each partial read.
> All four are marked coroutine_mixed_fn, consistent with the existing
> _all helpers.
>
> Suggested-by: Peter Xu <peterx@redhat.com>
> Signed-off-by: Junjie Cao <junjie.cao@intel.com>
> ---
> include/io/channel.h | 92 ++++++++++++++++++++++++++++++++++++++++++++
> io/channel.c | 91 +++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 183 insertions(+)
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Acked-by: Daniel P. Berrangé <berrange@redhat.com>
With regards,
Daniel
--
|: https://berrange.com ~~ https://hachyderm.io/@berrange :|
|: https://libvirt.org ~~ https://entangle-photo.org :|
|: https://pixelfed.art/berrange ~~ https://fstop138.berrange.com :|
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v3 2/4] io/channel: introduce qio_channel_pwrite{v,}_all()
2026-04-13 21:45 [PATCH v3 0/4] io/channel: complete pread/pwrite_all API and fix multifd_file_recv_data Junjie Cao
2026-04-13 21:45 ` [PATCH v3 1/4] io/channel: introduce qio_channel_pread{v, }_all{, _eof}() Junjie Cao
@ 2026-04-13 21:45 ` Junjie Cao
2026-04-14 15:54 ` [PATCH v3 2/4] io/channel: introduce qio_channel_pwrite{v, }_all() Fabiano Rosas
2026-04-17 9:46 ` Daniel P. Berrangé via qemu development
2026-04-13 21:45 ` [PATCH v3 3/4] migration/file: fix type mismatch and NULL deref in multifd_file_recv_data Junjie Cao
` (3 subsequent siblings)
5 siblings, 2 replies; 18+ messages in thread
From: Junjie Cao @ 2026-04-13 21:45 UTC (permalink / raw)
To: qemu-devel; +Cc: berrange, peterx, farosas, junjie.cao
Add positioned write helpers that retry on short writes, matching
the pread_all family from the previous patch.
qio_channel_pwritev_all() -- retry loop; returns 0 on success,
-1 on error.
qio_channel_pwrite_all() -- single-buffer convenience wrapper.
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
---
include/io/channel.h | 41 +++++++++++++++++++++++++++++++++++++
io/channel.c | 48 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 89 insertions(+)
diff --git a/include/io/channel.h b/include/io/channel.h
index 47af409ede..287d10cd6f 100644
--- a/include/io/channel.h
+++ b/include/io/channel.h
@@ -598,6 +598,47 @@ ssize_t qio_channel_pwritev(QIOChannel *ioc, const struct iovec *iov,
ssize_t qio_channel_pwrite(QIOChannel *ioc, void *buf, size_t buflen,
off_t offset, Error **errp);
+/**
+ * qio_channel_pwritev_all:
+ * @ioc: the channel object
+ * @iov: the array of memory regions to write data from
+ * @niov: the length of the @iov array
+ * @offset: the starting offset in the channel to write to
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Writes @iov, possibly blocking or (if the channel is non-blocking)
+ * yielding from the current coroutine multiple times until the entire
+ * content is written. Otherwise behaves as qio_channel_pwritev().
+ *
+ * Returns: 0 if all bytes were written, or -1 on error
+ */
+int coroutine_mixed_fn qio_channel_pwritev_all(QIOChannel *ioc,
+ const struct iovec *iov,
+ size_t niov,
+ off_t offset,
+ Error **errp);
+
+/**
+ * qio_channel_pwrite_all:
+ * @ioc: the channel object
+ * @buf: the memory region to write data from
+ * @buflen: the number of bytes to write from @buf
+ * @offset: the starting offset in the channel to write to
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Writes @buflen bytes from @buf, possibly blocking or (if the
+ * channel is non-blocking) yielding from the current coroutine
+ * multiple times until the entire content is written. Otherwise
+ * behaves as qio_channel_pwrite().
+ *
+ * Returns: 0 if all bytes were written, or -1 on error
+ */
+int coroutine_mixed_fn qio_channel_pwrite_all(QIOChannel *ioc,
+ const void *buf,
+ size_t buflen,
+ off_t offset,
+ Error **errp);
+
/**
* qio_channel_preadv
* @ioc: the channel object
diff --git a/io/channel.c b/io/channel.c
index 52c1abfcbc..2853dadb68 100644
--- a/io/channel.c
+++ b/io/channel.c
@@ -478,6 +478,54 @@ ssize_t qio_channel_pwrite(QIOChannel *ioc, void *buf, size_t buflen,
return qio_channel_pwritev(ioc, &iov, 1, offset, errp);
}
+int coroutine_mixed_fn qio_channel_pwritev_all(QIOChannel *ioc,
+ const struct iovec *iov,
+ size_t niov,
+ off_t offset,
+ Error **errp)
+{
+ int ret = -1;
+ struct iovec *local_iov = g_new(struct iovec, niov);
+ struct iovec *local_iov_head = local_iov;
+ unsigned int nlocal_iov = niov;
+
+ nlocal_iov = iov_copy(local_iov, nlocal_iov,
+ iov, niov,
+ 0, iov_size(iov, niov));
+
+ while (nlocal_iov > 0) {
+ ssize_t len;
+
+ len = qio_channel_pwritev(ioc, local_iov, nlocal_iov, offset, errp);
+
+ if (len == QIO_CHANNEL_ERR_BLOCK) {
+ qio_channel_wait_cond(ioc, G_IO_OUT);
+ continue;
+ }
+ if (len < 0) {
+ goto cleanup;
+ }
+
+ offset += len;
+ iov_discard_front(&local_iov, &nlocal_iov, len);
+ }
+
+ ret = 0;
+ cleanup:
+ g_free(local_iov_head);
+ return ret;
+}
+
+int coroutine_mixed_fn qio_channel_pwrite_all(QIOChannel *ioc,
+ const void *buf,
+ size_t buflen,
+ off_t offset,
+ Error **errp)
+{
+ struct iovec iov = { .iov_base = (char *)buf, .iov_len = buflen };
+ return qio_channel_pwritev_all(ioc, &iov, 1, offset, errp);
+}
+
ssize_t qio_channel_preadv(QIOChannel *ioc, const struct iovec *iov,
size_t niov, off_t offset, Error **errp)
{
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH v3 2/4] io/channel: introduce qio_channel_pwrite{v, }_all()
2026-04-13 21:45 ` [PATCH v3 2/4] io/channel: introduce qio_channel_pwrite{v,}_all() Junjie Cao
@ 2026-04-14 15:54 ` Fabiano Rosas
2026-04-17 9:46 ` Daniel P. Berrangé via qemu development
1 sibling, 0 replies; 18+ messages in thread
From: Fabiano Rosas @ 2026-04-14 15:54 UTC (permalink / raw)
To: Junjie Cao, qemu-devel; +Cc: berrange, peterx, junjie.cao
Junjie Cao <junjie.cao@intel.com> writes:
> Add positioned write helpers that retry on short writes, matching
> the pread_all family from the previous patch.
>
> qio_channel_pwritev_all() -- retry loop; returns 0 on success,
> -1 on error.
> qio_channel_pwrite_all() -- single-buffer convenience wrapper.
>
> Signed-off-by: Junjie Cao <junjie.cao@intel.com>
> ---
> include/io/channel.h | 41 +++++++++++++++++++++++++++++++++++++
> io/channel.c | 48 ++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 89 insertions(+)
>
> diff --git a/include/io/channel.h b/include/io/channel.h
> index 47af409ede..287d10cd6f 100644
> --- a/include/io/channel.h
> +++ b/include/io/channel.h
> @@ -598,6 +598,47 @@ ssize_t qio_channel_pwritev(QIOChannel *ioc, const struct iovec *iov,
> ssize_t qio_channel_pwrite(QIOChannel *ioc, void *buf, size_t buflen,
> off_t offset, Error **errp);
>
> +/**
> + * qio_channel_pwritev_all:
> + * @ioc: the channel object
> + * @iov: the array of memory regions to write data from
> + * @niov: the length of the @iov array
> + * @offset: the starting offset in the channel to write to
> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Writes @iov, possibly blocking or (if the channel is non-blocking)
> + * yielding from the current coroutine multiple times until the entire
> + * content is written. Otherwise behaves as qio_channel_pwritev().
> + *
> + * Returns: 0 if all bytes were written, or -1 on error
> + */
> +int coroutine_mixed_fn qio_channel_pwritev_all(QIOChannel *ioc,
> + const struct iovec *iov,
> + size_t niov,
> + off_t offset,
> + Error **errp);
> +
> +/**
> + * qio_channel_pwrite_all:
> + * @ioc: the channel object
> + * @buf: the memory region to write data from
> + * @buflen: the number of bytes to write from @buf
> + * @offset: the starting offset in the channel to write to
> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Writes @buflen bytes from @buf, possibly blocking or (if the
> + * channel is non-blocking) yielding from the current coroutine
> + * multiple times until the entire content is written. Otherwise
> + * behaves as qio_channel_pwrite().
> + *
> + * Returns: 0 if all bytes were written, or -1 on error
> + */
> +int coroutine_mixed_fn qio_channel_pwrite_all(QIOChannel *ioc,
> + const void *buf,
> + size_t buflen,
> + off_t offset,
> + Error **errp);
> +
> /**
> * qio_channel_preadv
> * @ioc: the channel object
> diff --git a/io/channel.c b/io/channel.c
> index 52c1abfcbc..2853dadb68 100644
> --- a/io/channel.c
> +++ b/io/channel.c
> @@ -478,6 +478,54 @@ ssize_t qio_channel_pwrite(QIOChannel *ioc, void *buf, size_t buflen,
> return qio_channel_pwritev(ioc, &iov, 1, offset, errp);
> }
>
> +int coroutine_mixed_fn qio_channel_pwritev_all(QIOChannel *ioc,
> + const struct iovec *iov,
> + size_t niov,
> + off_t offset,
> + Error **errp)
> +{
> + int ret = -1;
> + struct iovec *local_iov = g_new(struct iovec, niov);
> + struct iovec *local_iov_head = local_iov;
> + unsigned int nlocal_iov = niov;
> +
> + nlocal_iov = iov_copy(local_iov, nlocal_iov,
> + iov, niov,
> + 0, iov_size(iov, niov));
> +
> + while (nlocal_iov > 0) {
> + ssize_t len;
> +
> + len = qio_channel_pwritev(ioc, local_iov, nlocal_iov, offset, errp);
> +
> + if (len == QIO_CHANNEL_ERR_BLOCK) {
> + qio_channel_wait_cond(ioc, G_IO_OUT);
> + continue;
> + }
> + if (len < 0) {
> + goto cleanup;
> + }
> +
> + offset += len;
> + iov_discard_front(&local_iov, &nlocal_iov, len);
> + }
> +
> + ret = 0;
> + cleanup:
> + g_free(local_iov_head);
> + return ret;
> +}
> +
> +int coroutine_mixed_fn qio_channel_pwrite_all(QIOChannel *ioc,
> + const void *buf,
> + size_t buflen,
> + off_t offset,
> + Error **errp)
> +{
> + struct iovec iov = { .iov_base = (char *)buf, .iov_len = buflen };
> + return qio_channel_pwritev_all(ioc, &iov, 1, offset, errp);
> +}
> +
> ssize_t qio_channel_preadv(QIOChannel *ioc, const struct iovec *iov,
> size_t niov, off_t offset, Error **errp)
> {
Reviewed-by: Fabiano Rosas <farosas@suse.de>
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v3 2/4] io/channel: introduce qio_channel_pwrite{v, }_all()
2026-04-13 21:45 ` [PATCH v3 2/4] io/channel: introduce qio_channel_pwrite{v,}_all() Junjie Cao
2026-04-14 15:54 ` [PATCH v3 2/4] io/channel: introduce qio_channel_pwrite{v, }_all() Fabiano Rosas
@ 2026-04-17 9:46 ` Daniel P. Berrangé via qemu development
1 sibling, 0 replies; 18+ messages in thread
From: Daniel P. Berrangé via qemu development @ 2026-04-17 9:46 UTC (permalink / raw)
To: Junjie Cao; +Cc: qemu-devel, peterx, farosas
On Tue, Apr 14, 2026 at 05:45:47AM +0800, Junjie Cao wrote:
> Add positioned write helpers that retry on short writes, matching
> the pread_all family from the previous patch.
>
> qio_channel_pwritev_all() -- retry loop; returns 0 on success,
> -1 on error.
> qio_channel_pwrite_all() -- single-buffer convenience wrapper.
>
> Signed-off-by: Junjie Cao <junjie.cao@intel.com>
> ---
> include/io/channel.h | 41 +++++++++++++++++++++++++++++++++++++
> io/channel.c | 48 ++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 89 insertions(+)
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Acked-by: Daniel P. Berrangé <berrange@redhat.com>
With regards,
Daniel
--
|: https://berrange.com ~~ https://hachyderm.io/@berrange :|
|: https://libvirt.org ~~ https://entangle-photo.org :|
|: https://pixelfed.art/berrange ~~ https://fstop138.berrange.com :|
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v3 3/4] migration/file: fix type mismatch and NULL deref in multifd_file_recv_data
2026-04-13 21:45 [PATCH v3 0/4] io/channel: complete pread/pwrite_all API and fix multifd_file_recv_data Junjie Cao
2026-04-13 21:45 ` [PATCH v3 1/4] io/channel: introduce qio_channel_pread{v, }_all{, _eof}() Junjie Cao
2026-04-13 21:45 ` [PATCH v3 2/4] io/channel: introduce qio_channel_pwrite{v,}_all() Junjie Cao
@ 2026-04-13 21:45 ` Junjie Cao
2026-04-14 15:55 ` Fabiano Rosas
2026-04-13 21:45 ` [PATCH v3 4/4] tests/unit: add pread/pwrite _all tests for io channel file Junjie Cao
` (2 subsequent siblings)
5 siblings, 1 reply; 18+ messages in thread
From: Junjie Cao @ 2026-04-13 21:45 UTC (permalink / raw)
To: qemu-devel; +Cc: berrange, peterx, farosas, junjie.cao
multifd_file_recv_data() stores the return value of qio_channel_pread()
(ssize_t) in a size_t variable. On I/O error the -1 return value wraps
to SIZE_MAX, producing a nonsensical read size in the error message.
More critically, a short read (0 <= ret < data->size) is possible when
the migration file is truncated. In that case qio_channel_pread()
returns a non-negative value without setting *errp. The function then
calls error_prepend(errp, ...) which dereferences *errp -- a NULL
pointer -- crashing QEMU.
Fix both issues by switching to qio_channel_pread_all() introduced in
a previous patch, which retries on short reads and treats end-of-file
as an error, so the caller no longer needs to check the byte count
manually. Add ERRP_GUARD() so that error_prepend() works correctly
even when errp is &error_fatal or NULL.
Fixes: a49d15a38d3d ("migration/multifd: Support incoming mapped-ram stream format")
Suggested-by: Peter Xu <peterx@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
---
migration/file.c | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/migration/file.c b/migration/file.c
index 5618aced49..0853188601 100644
--- a/migration/file.c
+++ b/migration/file.c
@@ -254,15 +254,16 @@ int file_write_ramblock_iov(QIOChannel *ioc, const struct iovec *iov,
int multifd_file_recv_data(MultiFDRecvParams *p, Error **errp)
{
+ ERRP_GUARD();
MultiFDRecvData *data = p->data;
- size_t ret;
+ int ret;
- ret = qio_channel_pread(p->c, (char *) data->opaque,
- data->size, data->file_offset, errp);
- if (ret != data->size) {
+ ret = qio_channel_pread_all(p->c, (char *) data->opaque,
+ data->size, data->file_offset, errp);
+ if (ret != 0) {
error_prepend(errp,
- "multifd recv (%u): read 0x%zx, expected 0x%zx",
- p->id, ret, data->size);
+ "multifd recv (%u): ",
+ p->id);
return -1;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH v3 3/4] migration/file: fix type mismatch and NULL deref in multifd_file_recv_data
2026-04-13 21:45 ` [PATCH v3 3/4] migration/file: fix type mismatch and NULL deref in multifd_file_recv_data Junjie Cao
@ 2026-04-14 15:55 ` Fabiano Rosas
0 siblings, 0 replies; 18+ messages in thread
From: Fabiano Rosas @ 2026-04-14 15:55 UTC (permalink / raw)
To: Junjie Cao, qemu-devel; +Cc: berrange, peterx, junjie.cao
Junjie Cao <junjie.cao@intel.com> writes:
> multifd_file_recv_data() stores the return value of qio_channel_pread()
> (ssize_t) in a size_t variable. On I/O error the -1 return value wraps
> to SIZE_MAX, producing a nonsensical read size in the error message.
>
> More critically, a short read (0 <= ret < data->size) is possible when
> the migration file is truncated. In that case qio_channel_pread()
> returns a non-negative value without setting *errp. The function then
> calls error_prepend(errp, ...) which dereferences *errp -- a NULL
> pointer -- crashing QEMU.
>
> Fix both issues by switching to qio_channel_pread_all() introduced in
> a previous patch, which retries on short reads and treats end-of-file
> as an error, so the caller no longer needs to check the byte count
> manually. Add ERRP_GUARD() so that error_prepend() works correctly
> even when errp is &error_fatal or NULL.
>
> Fixes: a49d15a38d3d ("migration/multifd: Support incoming mapped-ram stream format")
> Suggested-by: Peter Xu <peterx@redhat.com>
> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
> Signed-off-by: Junjie Cao <junjie.cao@intel.com>
> ---
> migration/file.c | 13 +++++++------
> 1 file changed, 7 insertions(+), 6 deletions(-)
>
> diff --git a/migration/file.c b/migration/file.c
> index 5618aced49..0853188601 100644
> --- a/migration/file.c
> +++ b/migration/file.c
> @@ -254,15 +254,16 @@ int file_write_ramblock_iov(QIOChannel *ioc, const struct iovec *iov,
>
> int multifd_file_recv_data(MultiFDRecvParams *p, Error **errp)
> {
> + ERRP_GUARD();
> MultiFDRecvData *data = p->data;
> - size_t ret;
> + int ret;
>
> - ret = qio_channel_pread(p->c, (char *) data->opaque,
> - data->size, data->file_offset, errp);
> - if (ret != data->size) {
> + ret = qio_channel_pread_all(p->c, (char *) data->opaque,
> + data->size, data->file_offset, errp);
> + if (ret != 0) {
> error_prepend(errp,
> - "multifd recv (%u): read 0x%zx, expected 0x%zx",
> - p->id, ret, data->size);
> + "multifd recv (%u): ",
> + p->id);
> return -1;
> }
Reviewed-by: Fabiano Rosas <farosas@suse.de>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v3 4/4] tests/unit: add pread/pwrite _all tests for io channel file
2026-04-13 21:45 [PATCH v3 0/4] io/channel: complete pread/pwrite_all API and fix multifd_file_recv_data Junjie Cao
` (2 preceding siblings ...)
2026-04-13 21:45 ` [PATCH v3 3/4] migration/file: fix type mismatch and NULL deref in multifd_file_recv_data Junjie Cao
@ 2026-04-13 21:45 ` Junjie Cao
2026-04-14 16:01 ` Fabiano Rosas
2026-04-17 9:47 ` Daniel P. Berrangé
2026-04-17 16:51 ` [PATCH v3 0/4] io/channel: complete pread/pwrite_all API and fix multifd_file_recv_data Junjie Cao
2026-04-26 8:15 ` Michael Tokarev
5 siblings, 2 replies; 18+ messages in thread
From: Junjie Cao @ 2026-04-13 21:45 UTC (permalink / raw)
To: qemu-devel; +Cc: berrange, peterx, farosas, junjie.cao
Add unit tests for the new qio_channel_pread{v,}_all{,_eof}() and
qio_channel_pwrite{v,}_all() APIs.
The basic tests write data to a file channel, then read it back at
various offsets using both the single-buffer and iovec variants to
make sure the round-trip produces identical content. The _eof tests
verify all three return cases -- full read (1), clean EOF (0), and
partial-then-EOF (-1 with error set) -- and check that the strict
wrappers (preadv_all / pread_all) treat a clean EOF as an error.
All tests are guarded by CONFIG_PREADV since the underlying channel
methods require preadv(2).
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
---
tests/unit/test-io-channel-file.c | 207 ++++++++++++++++++++++++++++++
1 file changed, 207 insertions(+)
diff --git a/tests/unit/test-io-channel-file.c b/tests/unit/test-io-channel-file.c
index 1977006ce9..b597350dca 100644
--- a/tests/unit/test-io-channel-file.c
+++ b/tests/unit/test-io-channel-file.c
@@ -102,6 +102,203 @@ static void test_io_channel_fd(void)
}
+#ifdef CONFIG_PREADV
+static void test_io_channel_pread_all(void)
+{
+ QIOChannel *ioc;
+ char write_buf[] = "Hello World, pread_all";
+ char read_buf[sizeof(write_buf)] = {0};
+ int ret;
+
+ unlink(TEST_FILE);
+ ioc = QIO_CHANNEL(qio_channel_file_new_path(
+ TEST_FILE,
+ O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
+ TEST_MASK,
+ &error_abort));
+
+ ret = qio_channel_pwrite_all(ioc, write_buf, sizeof(write_buf),
+ 0, &error_abort);
+ g_assert_cmpint(ret, ==, 0);
+
+ /* Read back at offset 0 */
+ ret = qio_channel_pread_all(ioc, read_buf, sizeof(read_buf),
+ 0, &error_abort);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpmem(write_buf, sizeof(write_buf),
+ read_buf, sizeof(read_buf));
+
+ /* Read at a non-zero offset */
+ memset(read_buf, 0, sizeof(read_buf));
+ ret = qio_channel_pread_all(ioc, read_buf, sizeof(write_buf) - 7,
+ 7, &error_abort);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpmem(write_buf + 7, sizeof(write_buf) - 7,
+ read_buf, sizeof(write_buf) - 7);
+
+ unlink(TEST_FILE);
+ object_unref(OBJECT(ioc));
+}
+
+static void test_io_channel_preadv_all(void)
+{
+ QIOChannel *ioc;
+ char write_buf[256];
+ char read_buf[256] = {0};
+ struct iovec write_iov[2];
+ struct iovec read_iov[2];
+ int ret;
+ size_t i;
+
+ for (i = 0; i < sizeof(write_buf); i++) {
+ write_buf[i] = i & 0xff;
+ }
+
+ unlink(TEST_FILE);
+ ioc = QIO_CHANNEL(qio_channel_file_new_path(
+ TEST_FILE,
+ O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
+ TEST_MASK,
+ &error_abort));
+
+ /* Write using pwritev_all with 2 iovecs */
+ write_iov[0].iov_base = write_buf;
+ write_iov[0].iov_len = 128;
+ write_iov[1].iov_base = write_buf + 128;
+ write_iov[1].iov_len = 128;
+ ret = qio_channel_pwritev_all(ioc, write_iov, 2, 0, &error_abort);
+ g_assert_cmpint(ret, ==, 0);
+
+ /* Read back using preadv_all with 2 iovecs */
+ read_iov[0].iov_base = read_buf;
+ read_iov[0].iov_len = 128;
+ read_iov[1].iov_base = read_buf + 128;
+ read_iov[1].iov_len = 128;
+ ret = qio_channel_preadv_all(ioc, read_iov, 2, 0, &error_abort);
+ g_assert_cmpint(ret, ==, 0);
+
+ g_assert_cmpmem(write_buf, sizeof(write_buf),
+ read_buf, sizeof(read_buf));
+
+ /* Read at non-zero offset with preadv_all */
+ memset(read_buf, 0, sizeof(read_buf));
+ read_iov[0].iov_base = read_buf;
+ read_iov[0].iov_len = 64;
+ read_iov[1].iov_base = read_buf + 64;
+ read_iov[1].iov_len = 64;
+ ret = qio_channel_preadv_all(ioc, read_iov, 2, 128, &error_abort);
+ g_assert_cmpint(ret, ==, 0);
+
+ g_assert_cmpmem(write_buf + 128, 128,
+ read_buf, 128);
+
+ unlink(TEST_FILE);
+ object_unref(OBJECT(ioc));
+}
+
+static void test_io_channel_preadv_all_eof(void)
+{
+ QIOChannel *ioc;
+ char write_buf[] = "Hello World, preadv_all_eof";
+ char read_buf[sizeof(write_buf)] = {0};
+ struct iovec iov;
+ int ret;
+ Error *err = NULL;
+
+ unlink(TEST_FILE);
+ ioc = QIO_CHANNEL(qio_channel_file_new_path(
+ TEST_FILE,
+ O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
+ TEST_MASK,
+ &error_abort));
+
+ ret = qio_channel_pwrite_all(ioc, write_buf, sizeof(write_buf),
+ 0, &error_abort);
+ g_assert_cmpint(ret, ==, 0);
+
+ /* Full read succeeds: should return 1 */
+ iov.iov_base = read_buf;
+ iov.iov_len = sizeof(read_buf);
+ ret = qio_channel_preadv_all_eof(ioc, &iov, 1, 0, &error_abort);
+ g_assert_cmpint(ret, ==, 1);
+ g_assert_cmpmem(write_buf, sizeof(write_buf),
+ read_buf, sizeof(read_buf));
+
+ /* Clean EOF: offset at file end, should return 0 */
+ iov.iov_base = read_buf;
+ iov.iov_len = 1;
+ ret = qio_channel_preadv_all_eof(ioc, &iov, 1,
+ sizeof(write_buf), &err);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_null(err);
+
+ /* Partial EOF: start before end, request extends past */
+ iov.iov_base = read_buf;
+ iov.iov_len = 8;
+ ret = qio_channel_preadv_all_eof(ioc, &iov, 1,
+ sizeof(write_buf) - 4, &err);
+ g_assert_cmpint(ret, ==, -1);
+ g_assert_nonnull(err);
+ error_free(err);
+ err = NULL;
+
+ /* Strict wrapper (preadv_all) treats clean EOF as error */
+ iov.iov_base = read_buf;
+ iov.iov_len = 1;
+ ret = qio_channel_preadv_all(ioc, &iov, 1,
+ sizeof(write_buf), &err);
+ g_assert_cmpint(ret, ==, -1);
+ g_assert_nonnull(err);
+ error_free(err);
+
+ unlink(TEST_FILE);
+ object_unref(OBJECT(ioc));
+}
+
+static void test_io_channel_pread_all_eof(void)
+{
+ QIOChannel *ioc;
+ char write_buf[] = "Hello World, pread_all_eof";
+ char read_buf[sizeof(write_buf)] = {0};
+ int ret;
+ Error *err = NULL;
+
+ unlink(TEST_FILE);
+ ioc = QIO_CHANNEL(qio_channel_file_new_path(
+ TEST_FILE,
+ O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
+ TEST_MASK,
+ &error_abort));
+
+ ret = qio_channel_pwrite_all(ioc, write_buf, sizeof(write_buf),
+ 0, &error_abort);
+ g_assert_cmpint(ret, ==, 0);
+
+ /* Full read succeeds: should return 1 */
+ ret = qio_channel_pread_all_eof(ioc, read_buf, sizeof(read_buf),
+ 0, &error_abort);
+ g_assert_cmpint(ret, ==, 1);
+ g_assert_cmpmem(write_buf, sizeof(write_buf),
+ read_buf, sizeof(read_buf));
+
+ /* Clean EOF: should return 0 */
+ ret = qio_channel_pread_all_eof(ioc, read_buf, 1,
+ sizeof(write_buf), &err);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_null(err);
+
+ /* Partial EOF: should return -1 */
+ ret = qio_channel_pread_all_eof(ioc, read_buf, 8,
+ sizeof(write_buf) - 4, &err);
+ g_assert_cmpint(ret, ==, -1);
+ g_assert_nonnull(err);
+ error_free(err);
+
+ unlink(TEST_FILE);
+ object_unref(OBJECT(ioc));
+}
+#endif /* CONFIG_PREADV */
+
#ifndef _WIN32
static void test_io_channel_pipe(bool async)
{
@@ -147,6 +344,16 @@ int main(int argc, char **argv)
g_test_add_func("/io/channel/file", test_io_channel_file);
g_test_add_func("/io/channel/file/rdwr", test_io_channel_file_rdwr);
g_test_add_func("/io/channel/file/fd", test_io_channel_fd);
+#ifdef CONFIG_PREADV
+ g_test_add_func("/io/channel/file/pread-all",
+ test_io_channel_pread_all);
+ g_test_add_func("/io/channel/file/preadv-all",
+ test_io_channel_preadv_all);
+ g_test_add_func("/io/channel/file/preadv-all-eof",
+ test_io_channel_preadv_all_eof);
+ g_test_add_func("/io/channel/file/pread-all-eof",
+ test_io_channel_pread_all_eof);
+#endif
#ifndef _WIN32
g_test_add_func("/io/channel/pipe/sync", test_io_channel_pipe_sync);
g_test_add_func("/io/channel/pipe/async", test_io_channel_pipe_async);
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH v3 4/4] tests/unit: add pread/pwrite _all tests for io channel file
2026-04-13 21:45 ` [PATCH v3 4/4] tests/unit: add pread/pwrite _all tests for io channel file Junjie Cao
@ 2026-04-14 16:01 ` Fabiano Rosas
2026-04-17 9:47 ` Daniel P. Berrangé
1 sibling, 0 replies; 18+ messages in thread
From: Fabiano Rosas @ 2026-04-14 16:01 UTC (permalink / raw)
To: Junjie Cao, qemu-devel; +Cc: berrange, peterx, junjie.cao
Junjie Cao <junjie.cao@intel.com> writes:
> Add unit tests for the new qio_channel_pread{v,}_all{,_eof}() and
> qio_channel_pwrite{v,}_all() APIs.
>
> The basic tests write data to a file channel, then read it back at
> various offsets using both the single-buffer and iovec variants to
> make sure the round-trip produces identical content. The _eof tests
> verify all three return cases -- full read (1), clean EOF (0), and
> partial-then-EOF (-1 with error set) -- and check that the strict
> wrappers (preadv_all / pread_all) treat a clean EOF as an error.
>
> All tests are guarded by CONFIG_PREADV since the underlying channel
> methods require preadv(2).
>
> Signed-off-by: Junjie Cao <junjie.cao@intel.com>
> ---
> tests/unit/test-io-channel-file.c | 207 ++++++++++++++++++++++++++++++
> 1 file changed, 207 insertions(+)
>
> diff --git a/tests/unit/test-io-channel-file.c b/tests/unit/test-io-channel-file.c
> index 1977006ce9..b597350dca 100644
> --- a/tests/unit/test-io-channel-file.c
> +++ b/tests/unit/test-io-channel-file.c
> @@ -102,6 +102,203 @@ static void test_io_channel_fd(void)
> }
>
>
> +#ifdef CONFIG_PREADV
> +static void test_io_channel_pread_all(void)
> +{
> + QIOChannel *ioc;
> + char write_buf[] = "Hello World, pread_all";
> + char read_buf[sizeof(write_buf)] = {0};
> + int ret;
> +
> + unlink(TEST_FILE);
> + ioc = QIO_CHANNEL(qio_channel_file_new_path(
> + TEST_FILE,
> + O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
> + TEST_MASK,
> + &error_abort));
> +
> + ret = qio_channel_pwrite_all(ioc, write_buf, sizeof(write_buf),
> + 0, &error_abort);
> + g_assert_cmpint(ret, ==, 0);
> +
> + /* Read back at offset 0 */
> + ret = qio_channel_pread_all(ioc, read_buf, sizeof(read_buf),
> + 0, &error_abort);
> + g_assert_cmpint(ret, ==, 0);
> + g_assert_cmpmem(write_buf, sizeof(write_buf),
> + read_buf, sizeof(read_buf));
> +
> + /* Read at a non-zero offset */
> + memset(read_buf, 0, sizeof(read_buf));
> + ret = qio_channel_pread_all(ioc, read_buf, sizeof(write_buf) - 7,
> + 7, &error_abort);
> + g_assert_cmpint(ret, ==, 0);
> + g_assert_cmpmem(write_buf + 7, sizeof(write_buf) - 7,
> + read_buf, sizeof(write_buf) - 7);
> +
> + unlink(TEST_FILE);
> + object_unref(OBJECT(ioc));
> +}
> +
> +static void test_io_channel_preadv_all(void)
> +{
> + QIOChannel *ioc;
> + char write_buf[256];
> + char read_buf[256] = {0};
> + struct iovec write_iov[2];
> + struct iovec read_iov[2];
> + int ret;
> + size_t i;
> +
> + for (i = 0; i < sizeof(write_buf); i++) {
> + write_buf[i] = i & 0xff;
> + }
> +
> + unlink(TEST_FILE);
> + ioc = QIO_CHANNEL(qio_channel_file_new_path(
> + TEST_FILE,
> + O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
> + TEST_MASK,
> + &error_abort));
> +
> + /* Write using pwritev_all with 2 iovecs */
> + write_iov[0].iov_base = write_buf;
> + write_iov[0].iov_len = 128;
> + write_iov[1].iov_base = write_buf + 128;
> + write_iov[1].iov_len = 128;
> + ret = qio_channel_pwritev_all(ioc, write_iov, 2, 0, &error_abort);
> + g_assert_cmpint(ret, ==, 0);
> +
> + /* Read back using preadv_all with 2 iovecs */
> + read_iov[0].iov_base = read_buf;
> + read_iov[0].iov_len = 128;
> + read_iov[1].iov_base = read_buf + 128;
> + read_iov[1].iov_len = 128;
> + ret = qio_channel_preadv_all(ioc, read_iov, 2, 0, &error_abort);
> + g_assert_cmpint(ret, ==, 0);
> +
> + g_assert_cmpmem(write_buf, sizeof(write_buf),
> + read_buf, sizeof(read_buf));
> +
> + /* Read at non-zero offset with preadv_all */
> + memset(read_buf, 0, sizeof(read_buf));
> + read_iov[0].iov_base = read_buf;
> + read_iov[0].iov_len = 64;
> + read_iov[1].iov_base = read_buf + 64;
> + read_iov[1].iov_len = 64;
> + ret = qio_channel_preadv_all(ioc, read_iov, 2, 128, &error_abort);
> + g_assert_cmpint(ret, ==, 0);
> +
> + g_assert_cmpmem(write_buf + 128, 128,
> + read_buf, 128);
> +
> + unlink(TEST_FILE);
> + object_unref(OBJECT(ioc));
> +}
> +
> +static void test_io_channel_preadv_all_eof(void)
> +{
> + QIOChannel *ioc;
> + char write_buf[] = "Hello World, preadv_all_eof";
> + char read_buf[sizeof(write_buf)] = {0};
> + struct iovec iov;
> + int ret;
> + Error *err = NULL;
> +
> + unlink(TEST_FILE);
> + ioc = QIO_CHANNEL(qio_channel_file_new_path(
> + TEST_FILE,
> + O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
> + TEST_MASK,
> + &error_abort));
> +
> + ret = qio_channel_pwrite_all(ioc, write_buf, sizeof(write_buf),
> + 0, &error_abort);
> + g_assert_cmpint(ret, ==, 0);
> +
> + /* Full read succeeds: should return 1 */
> + iov.iov_base = read_buf;
> + iov.iov_len = sizeof(read_buf);
> + ret = qio_channel_preadv_all_eof(ioc, &iov, 1, 0, &error_abort);
> + g_assert_cmpint(ret, ==, 1);
> + g_assert_cmpmem(write_buf, sizeof(write_buf),
> + read_buf, sizeof(read_buf));
> +
> + /* Clean EOF: offset at file end, should return 0 */
> + iov.iov_base = read_buf;
> + iov.iov_len = 1;
> + ret = qio_channel_preadv_all_eof(ioc, &iov, 1,
> + sizeof(write_buf), &err);
> + g_assert_cmpint(ret, ==, 0);
> + g_assert_null(err);
> +
> + /* Partial EOF: start before end, request extends past */
> + iov.iov_base = read_buf;
> + iov.iov_len = 8;
> + ret = qio_channel_preadv_all_eof(ioc, &iov, 1,
> + sizeof(write_buf) - 4, &err);
> + g_assert_cmpint(ret, ==, -1);
> + g_assert_nonnull(err);
> + error_free(err);
> + err = NULL;
> +
> + /* Strict wrapper (preadv_all) treats clean EOF as error */
> + iov.iov_base = read_buf;
> + iov.iov_len = 1;
> + ret = qio_channel_preadv_all(ioc, &iov, 1,
> + sizeof(write_buf), &err);
> + g_assert_cmpint(ret, ==, -1);
> + g_assert_nonnull(err);
> + error_free(err);
> +
> + unlink(TEST_FILE);
> + object_unref(OBJECT(ioc));
> +}
> +
> +static void test_io_channel_pread_all_eof(void)
> +{
> + QIOChannel *ioc;
> + char write_buf[] = "Hello World, pread_all_eof";
> + char read_buf[sizeof(write_buf)] = {0};
> + int ret;
> + Error *err = NULL;
> +
> + unlink(TEST_FILE);
> + ioc = QIO_CHANNEL(qio_channel_file_new_path(
> + TEST_FILE,
> + O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
> + TEST_MASK,
> + &error_abort));
> +
> + ret = qio_channel_pwrite_all(ioc, write_buf, sizeof(write_buf),
> + 0, &error_abort);
> + g_assert_cmpint(ret, ==, 0);
> +
> + /* Full read succeeds: should return 1 */
> + ret = qio_channel_pread_all_eof(ioc, read_buf, sizeof(read_buf),
> + 0, &error_abort);
> + g_assert_cmpint(ret, ==, 1);
> + g_assert_cmpmem(write_buf, sizeof(write_buf),
> + read_buf, sizeof(read_buf));
> +
> + /* Clean EOF: should return 0 */
> + ret = qio_channel_pread_all_eof(ioc, read_buf, 1,
> + sizeof(write_buf), &err);
> + g_assert_cmpint(ret, ==, 0);
> + g_assert_null(err);
> +
> + /* Partial EOF: should return -1 */
> + ret = qio_channel_pread_all_eof(ioc, read_buf, 8,
> + sizeof(write_buf) - 4, &err);
> + g_assert_cmpint(ret, ==, -1);
> + g_assert_nonnull(err);
> + error_free(err);
> +
> + unlink(TEST_FILE);
> + object_unref(OBJECT(ioc));
> +}
> +#endif /* CONFIG_PREADV */
> +
> #ifndef _WIN32
> static void test_io_channel_pipe(bool async)
> {
> @@ -147,6 +344,16 @@ int main(int argc, char **argv)
> g_test_add_func("/io/channel/file", test_io_channel_file);
> g_test_add_func("/io/channel/file/rdwr", test_io_channel_file_rdwr);
> g_test_add_func("/io/channel/file/fd", test_io_channel_fd);
> +#ifdef CONFIG_PREADV
> + g_test_add_func("/io/channel/file/pread-all",
> + test_io_channel_pread_all);
> + g_test_add_func("/io/channel/file/preadv-all",
> + test_io_channel_preadv_all);
> + g_test_add_func("/io/channel/file/preadv-all-eof",
> + test_io_channel_preadv_all_eof);
> + g_test_add_func("/io/channel/file/pread-all-eof",
> + test_io_channel_pread_all_eof);
> +#endif
> #ifndef _WIN32
> g_test_add_func("/io/channel/pipe/sync", test_io_channel_pipe_sync);
> g_test_add_func("/io/channel/pipe/async", test_io_channel_pipe_async);
Reviewed-by: Fabiano Rosas <farosas@suse.de>
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v3 4/4] tests/unit: add pread/pwrite _all tests for io channel file
2026-04-13 21:45 ` [PATCH v3 4/4] tests/unit: add pread/pwrite _all tests for io channel file Junjie Cao
2026-04-14 16:01 ` Fabiano Rosas
@ 2026-04-17 9:47 ` Daniel P. Berrangé
1 sibling, 0 replies; 18+ messages in thread
From: Daniel P. Berrangé @ 2026-04-17 9:47 UTC (permalink / raw)
To: Junjie Cao; +Cc: qemu-devel, peterx, farosas
On Tue, Apr 14, 2026 at 05:45:49AM +0800, Junjie Cao wrote:
> Add unit tests for the new qio_channel_pread{v,}_all{,_eof}() and
> qio_channel_pwrite{v,}_all() APIs.
>
> The basic tests write data to a file channel, then read it back at
> various offsets using both the single-buffer and iovec variants to
> make sure the round-trip produces identical content. The _eof tests
> verify all three return cases -- full read (1), clean EOF (0), and
> partial-then-EOF (-1 with error set) -- and check that the strict
> wrappers (preadv_all / pread_all) treat a clean EOF as an error.
>
> All tests are guarded by CONFIG_PREADV since the underlying channel
> methods require preadv(2).
>
> Signed-off-by: Junjie Cao <junjie.cao@intel.com>
> ---
> tests/unit/test-io-channel-file.c | 207 ++++++++++++++++++++++++++++++
> 1 file changed, 207 insertions(+)
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Acked-by: Daniel P. Berrangé <berrange@redhat.com>
With regards,
Daniel
--
|: https://berrange.com ~~ https://hachyderm.io/@berrange :|
|: https://libvirt.org ~~ https://entangle-photo.org :|
|: https://pixelfed.art/berrange ~~ https://fstop138.berrange.com :|
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v3 0/4] io/channel: complete pread/pwrite_all API and fix multifd_file_recv_data
2026-04-13 21:45 [PATCH v3 0/4] io/channel: complete pread/pwrite_all API and fix multifd_file_recv_data Junjie Cao
` (3 preceding siblings ...)
2026-04-13 21:45 ` [PATCH v3 4/4] tests/unit: add pread/pwrite _all tests for io channel file Junjie Cao
@ 2026-04-17 16:51 ` Junjie Cao
2026-04-17 9:48 ` Daniel P. Berrangé
2026-04-26 8:15 ` Michael Tokarev
5 siblings, 1 reply; 18+ messages in thread
From: Junjie Cao @ 2026-04-17 16:51 UTC (permalink / raw)
To: qemu-devel; +Cc: berrange, peterx, farosas, Junjie Cao
Hi Daniel, Peter,
Just checking in on this series -- does v3 look alright to you?
I have a follow-up series that depends on this one to fix the
remaining positioned I/O call sites in migration, so it would be
great to get this moving.
Many thanks,
Junjie
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v3 0/4] io/channel: complete pread/pwrite_all API and fix multifd_file_recv_data
2026-04-17 16:51 ` [PATCH v3 0/4] io/channel: complete pread/pwrite_all API and fix multifd_file_recv_data Junjie Cao
@ 2026-04-17 9:48 ` Daniel P. Berrangé
2026-04-17 14:04 ` Peter Xu
0 siblings, 1 reply; 18+ messages in thread
From: Daniel P. Berrangé @ 2026-04-17 9:48 UTC (permalink / raw)
To: Junjie Cao; +Cc: qemu-devel, peterx, farosas
On Sat, Apr 18, 2026 at 12:51:55AM +0800, Junjie Cao wrote:
> Hi Daniel, Peter,
>
> Just checking in on this series -- does v3 look alright to you?
>
> I have a follow-up series that depends on this one to fix the
> remaining positioned I/O call sites in migration, so it would be
> great to get this moving.
I've ackd the io subsystem patches, so Peter / Fabiano can just
include them in a migration pull request.
With regards,
Daniel
--
|: https://berrange.com ~~ https://hachyderm.io/@berrange :|
|: https://libvirt.org ~~ https://entangle-photo.org :|
|: https://pixelfed.art/berrange ~~ https://fstop138.berrange.com :|
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v3 0/4] io/channel: complete pread/pwrite_all API and fix multifd_file_recv_data
2026-04-17 9:48 ` Daniel P. Berrangé
@ 2026-04-17 14:04 ` Peter Xu
0 siblings, 0 replies; 18+ messages in thread
From: Peter Xu @ 2026-04-17 14:04 UTC (permalink / raw)
To: Daniel P. Berrangé; +Cc: Junjie Cao, qemu-devel, farosas
On Fri, Apr 17, 2026 at 10:48:32AM +0100, Daniel P. Berrangé wrote:
> On Sat, Apr 18, 2026 at 12:51:55AM +0800, Junjie Cao wrote:
> > Hi Daniel, Peter,
> >
> > Just checking in on this series -- does v3 look alright to you?
> >
> > I have a follow-up series that depends on this one to fix the
> > remaining positioned I/O call sites in migration, so it would be
> > great to get this moving.
>
> I've ackd the io subsystem patches, so Peter / Fabiano can just
> include them in a migration pull request.
Junjie, sorry for the late response. It looks all good here, thanks for
your work!
I bet Fabiano will pick this up for his first pull when 11.1 dev window
opens.
--
Peter Xu
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v3 0/4] io/channel: complete pread/pwrite_all API and fix multifd_file_recv_data
2026-04-13 21:45 [PATCH v3 0/4] io/channel: complete pread/pwrite_all API and fix multifd_file_recv_data Junjie Cao
` (4 preceding siblings ...)
2026-04-17 16:51 ` [PATCH v3 0/4] io/channel: complete pread/pwrite_all API and fix multifd_file_recv_data Junjie Cao
@ 2026-04-26 8:15 ` Michael Tokarev
2026-04-26 8:21 ` Michael Tokarev
5 siblings, 1 reply; 18+ messages in thread
From: Michael Tokarev @ 2026-04-26 8:15 UTC (permalink / raw)
To: Junjie Cao, qemu-devel; +Cc: berrange, peterx, farosas, qemu-stable
On 14.04.2026 00:45, Junjie Cao wrote:
> This series fixes two bugs in multifd_file_recv_data() and completes
> the positioned read/write "all" API set for QIO channels.
>
> Bug summary:
> - size_t variable storing ssize_t return: -1 wraps to SIZE_MAX
> - error_prepend() called without ERRP_GUARD(): NULL deref on short read
> Junjie Cao (4):
> io/channel: introduce qio_channel_pread{v,}_all{,_eof}()
> io/channel: introduce qio_channel_pwrite{v,}_all()
> migration/file: fix type mismatch and NULL deref in
> multifd_file_recv_data
> tests/unit: add pread/pwrite _all tests for io channel file
>
> include/io/channel.h | 133 +++++++++++++++++++
> io/channel.c | 139 ++++++++++++++++++++
> migration/file.c | 13 +-
> tests/unit/test-io-channel-file.c | 207 ++++++++++++++++++++++++++++++
> 4 files changed, 486 insertions(+), 6 deletions(-)
Hi!
Is this a qemu-stable material? The amount of new code this series
introduces is somewhat large, but it fixes real bugs in the end. On
the other hand, these bugs doesn't seem to be very serious, so.. I'm
not sure :)
How do you think, should we pick this up for the current stable releases?
It applies cleanly to 10.0.x (the earliest stable series currently
supported).
Thanks,
/mjt
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v3 0/4] io/channel: complete pread/pwrite_all API and fix multifd_file_recv_data
2026-04-26 8:15 ` Michael Tokarev
@ 2026-04-26 8:21 ` Michael Tokarev
2026-04-29 14:24 ` Fabiano Rosas
0 siblings, 1 reply; 18+ messages in thread
From: Michael Tokarev @ 2026-04-26 8:21 UTC (permalink / raw)
To: Junjie Cao, qemu-devel; +Cc: berrange, peterx, farosas, qemu-stable
On 26.04.2026 11:15, Michael Tokarev wrote:
> On 14.04.2026 00:45, Junjie Cao wrote:
>> This series fixes two bugs in multifd_file_recv_data() and completes
>> the positioned read/write "all" API set for QIO channels.
>>
>> Bug summary:
>> - size_t variable storing ssize_t return: -1 wraps to SIZE_MAX
>> - error_prepend() called without ERRP_GUARD(): NULL deref on short
>> read
>> Junjie Cao (4):
>> io/channel: introduce qio_channel_pread{v,}_all{,_eof}()
>> io/channel: introduce qio_channel_pwrite{v,}_all()
>> migration/file: fix type mismatch and NULL deref in
>> multifd_file_recv_data
>> tests/unit: add pread/pwrite _all tests for io channel file
>>
>> include/io/channel.h | 133 +++++++++++++++++++
>> io/channel.c | 139 ++++++++++++++++++++
>> migration/file.c | 13 +-
>> tests/unit/test-io-channel-file.c | 207 ++++++++++++++++++++++++++++++
>> 4 files changed, 486 insertions(+), 6 deletions(-)
>
> Hi!
>
> Is this a qemu-stable material? The amount of new code this series
> introduces is somewhat large, but it fixes real bugs in the end. On
> the other hand, these bugs doesn't seem to be very serious, so.. I'm
> not sure :)
>
> How do you think, should we pick this up for the current stable releases?
> It applies cleanly to 10.0.x (the earliest stable series currently
> supported).
For 10.0.x, it also needs two more changes to work:
a5bc1ccca95 migration: simplify error reporting after channel read
1edf0df2840 io: Add qio_channel_wait_cond() helper
Thanks,
/mjt
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v3 0/4] io/channel: complete pread/pwrite_all API and fix multifd_file_recv_data
2026-04-26 8:21 ` Michael Tokarev
@ 2026-04-29 14:24 ` Fabiano Rosas
0 siblings, 0 replies; 18+ messages in thread
From: Fabiano Rosas @ 2026-04-29 14:24 UTC (permalink / raw)
To: Michael Tokarev, Junjie Cao, qemu-devel; +Cc: berrange, peterx, qemu-stable
Michael Tokarev <mjt@tls.msk.ru> writes:
> On 26.04.2026 11:15, Michael Tokarev wrote:
>> On 14.04.2026 00:45, Junjie Cao wrote:
>>> This series fixes two bugs in multifd_file_recv_data() and completes
>>> the positioned read/write "all" API set for QIO channels.
>>>
>>> Bug summary:
>>> - size_t variable storing ssize_t return: -1 wraps to SIZE_MAX
>>> - error_prepend() called without ERRP_GUARD(): NULL deref on short
>>> read
>>> Junjie Cao (4):
>>> io/channel: introduce qio_channel_pread{v,}_all{,_eof}()
>>> io/channel: introduce qio_channel_pwrite{v,}_all()
>>> migration/file: fix type mismatch and NULL deref in
>>> multifd_file_recv_data
>>> tests/unit: add pread/pwrite _all tests for io channel file
>>>
>>> include/io/channel.h | 133 +++++++++++++++++++
>>> io/channel.c | 139 ++++++++++++++++++++
>>> migration/file.c | 13 +-
>>> tests/unit/test-io-channel-file.c | 207 ++++++++++++++++++++++++++++++
>>> 4 files changed, 486 insertions(+), 6 deletions(-)
>>
>> Hi!
>>
>> Is this a qemu-stable material? The amount of new code this series
>> introduces is somewhat large, but it fixes real bugs in the end. On
>> the other hand, these bugs doesn't seem to be very serious, so.. I'm
>> not sure :)
>>
>> How do you think, should we pick this up for the current stable releases?
>> It applies cleanly to 10.0.x (the earliest stable series currently
>> supported).
>
> For 10.0.x, it also needs two more changes to work:
>
> a5bc1ccca95 migration: simplify error reporting after channel read
> 1edf0df2840 io: Add qio_channel_wait_cond() helper
>
> Thanks,
Hi! Sorry for the delay. I don't think we need this series for
stable. It's a fairly unlikely issue.
^ permalink raw reply [flat|nested] 18+ messages in thread