diff mbox series

[01/20] io: add a QIOChannelNull equivalent to /dev/null

Message ID 20220524110235.145079-2-berrange@redhat.com
State New
Headers show
Series migration: remove QEMUFileOps concept and assume use of QIOChannel | expand

Commit Message

Daniel P. Berrangé May 24, 2022, 11:02 a.m. UTC
This is for code which needs a portable equivalent to a QIOChannelFile
connected to /dev/null.

Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 include/io/channel-null.h         |  55 +++++++
 io/channel-null.c                 | 237 ++++++++++++++++++++++++++++++
 io/meson.build                    |   1 +
 io/trace-events                   |   3 +
 tests/unit/meson.build            |   1 +
 tests/unit/test-io-channel-null.c |  95 ++++++++++++
 6 files changed, 392 insertions(+)
 create mode 100644 include/io/channel-null.h
 create mode 100644 io/channel-null.c
 create mode 100644 tests/unit/test-io-channel-null.c

Comments

Eric Blake May 24, 2022, 9:14 p.m. UTC | #1
On Tue, May 24, 2022 at 12:02:16PM +0100, Daniel P. Berrangé wrote:
> This is for code which needs a portable equivalent to a QIOChannelFile
> connected to /dev/null.
> 
> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> ---
>  include/io/channel-null.h         |  55 +++++++
>  io/channel-null.c                 | 237 ++++++++++++++++++++++++++++++
>  io/meson.build                    |   1 +
>  io/trace-events                   |   3 +
>  tests/unit/meson.build            |   1 +
>  tests/unit/test-io-channel-null.c |  95 ++++++++++++
>  6 files changed, 392 insertions(+)
>  create mode 100644 include/io/channel-null.h
>  create mode 100644 io/channel-null.c
>  create mode 100644 tests/unit/test-io-channel-null.c

> +/**
> + * QIOChannelNull:
> + *
> + * The QIOChannelNull object provides a channel implementation
> + * that discards all writes and returns zero bytes for all reads.

That describes the behavior of /dev/zero, not /dev/null, where reads
always fail with EOF.

> + */
> +
> +struct QIOChannelNull {
> +    QIOChannel parent;
> +    bool closed;
> +};
> +

> diff --git a/io/channel-null.c b/io/channel-null.c

> +
> +static ssize_t
> +qio_channel_null_readv(QIOChannel *ioc,
> +                       const struct iovec *iov,
> +                       size_t niov,
> +                       int **fds G_GNUC_UNUSED,
> +                       size_t *nfds G_GNUC_UNUSED,
> +                       Error **errp)
> +{
> +    QIOChannelNull *nioc = QIO_CHANNEL_NULL(ioc);
> +
> +    if (nioc->closed) {
> +        error_setg_errno(errp, EINVAL,
> +                         "Channel is closed");
> +        return -1;
> +    }
> +
> +    return 0;
> +}

But this behavior is returning early EOF instead of using iov_memset()
to read all zeroes the way /dev/zero would.

> +++ b/tests/unit/test-io-channel-null.c

> +static void test_io_channel_null_io(void)
> +{
> +    g_autoptr(QIOChannelNull) null = qio_channel_null_new();
> +    char buf[1024];
> +    GIOCondition gotcond = 0;
> +    Error *local_err = NULL;
> +
> +    g_assert(qio_channel_write(QIO_CHANNEL(null),
> +                               "Hello World", 11,
> +                               &error_abort) == 11);

I still cringe seeing tests inside g_assert(), but this is not the
first instance of it.

> +
> +    g_assert(qio_channel_read(QIO_CHANNEL(null),
> +                              buf, sizeof(buf),
> +                              &error_abort) == 0);

Okay, you're testing for /dev/null behavior of early EOF.

Other than misleading comments, this looks reasonable.  But those
comments are core enough as to what this channel does that I don't
feel comfortable giving R-b yet.
Daniel P. Berrangé June 16, 2022, 4:26 p.m. UTC | #2
On Tue, May 24, 2022 at 04:14:31PM -0500, Eric Blake wrote:
> On Tue, May 24, 2022 at 12:02:16PM +0100, Daniel P. Berrangé wrote:
> > This is for code which needs a portable equivalent to a QIOChannelFile
> > connected to /dev/null.
> > 
> > Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> > ---
> >  include/io/channel-null.h         |  55 +++++++
> >  io/channel-null.c                 | 237 ++++++++++++++++++++++++++++++
> >  io/meson.build                    |   1 +
> >  io/trace-events                   |   3 +
> >  tests/unit/meson.build            |   1 +
> >  tests/unit/test-io-channel-null.c |  95 ++++++++++++
> >  6 files changed, 392 insertions(+)
> >  create mode 100644 include/io/channel-null.h
> >  create mode 100644 io/channel-null.c
> >  create mode 100644 tests/unit/test-io-channel-null.c
> 
> > +/**
> > + * QIOChannelNull:
> > + *
> > + * The QIOChannelNull object provides a channel implementation
> > + * that discards all writes and returns zero bytes for all reads.
> 
> That describes the behavior of /dev/zero, not /dev/null, where reads
> always fail with EOF.

Nb, I wrote 'zero bytes' (as in "byte count equal to zero")  not
'zeroed bytes' (as in "set all bytes to the value zero").

I'll reword it to make it less confusing though.

> 
> > + */
> > +
> > +struct QIOChannelNull {
> > +    QIOChannel parent;
> > +    bool closed;
> > +};
> > +
> 
> > diff --git a/io/channel-null.c b/io/channel-null.c
> 
> > +
> > +static ssize_t
> > +qio_channel_null_readv(QIOChannel *ioc,
> > +                       const struct iovec *iov,
> > +                       size_t niov,
> > +                       int **fds G_GNUC_UNUSED,
> > +                       size_t *nfds G_GNUC_UNUSED,
> > +                       Error **errp)
> > +{
> > +    QIOChannelNull *nioc = QIO_CHANNEL_NULL(ioc);
> > +
> > +    if (nioc->closed) {
> > +        error_setg_errno(errp, EINVAL,
> > +                         "Channel is closed");
> > +        return -1;
> > +    }
> > +
> > +    return 0;
> > +}
> 
> But this behavior is returning early EOF instead of using iov_memset()
> to read all zeroes the way /dev/zero would.
> 
> > +++ b/tests/unit/test-io-channel-null.c
> 
> > +static void test_io_channel_null_io(void)
> > +{
> > +    g_autoptr(QIOChannelNull) null = qio_channel_null_new();
> > +    char buf[1024];
> > +    GIOCondition gotcond = 0;
> > +    Error *local_err = NULL;
> > +
> > +    g_assert(qio_channel_write(QIO_CHANNEL(null),
> > +                               "Hello World", 11,
> > +                               &error_abort) == 11);
> 
> I still cringe seeing tests inside g_assert(), but this is not the
> first instance of it.
> 
> > +
> > +    g_assert(qio_channel_read(QIO_CHANNEL(null),
> > +                              buf, sizeof(buf),
> > +                              &error_abort) == 0);
> 
> Okay, you're testing for /dev/null behavior of early EOF.
> 
> Other than misleading comments, this looks reasonable.  But those
> comments are core enough as to what this channel does that I don't
> feel comfortable giving R-b yet.
> 
> -- 
> Eric Blake, Principal Software Engineer
> Red Hat, Inc.           +1-919-301-3266
> Virtualization:  qemu.org | libvirt.org
> 

With regards,
Daniel
diff mbox series

Patch

diff --git a/include/io/channel-null.h b/include/io/channel-null.h
new file mode 100644
index 0000000000..d7c950e494
--- /dev/null
+++ b/include/io/channel-null.h
@@ -0,0 +1,55 @@ 
+/*
+ * QEMU I/O channels null driver
+ *
+ * Copyright (c) 2022 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QIO_CHANNEL_FILE_H
+#define QIO_CHANNEL_FILE_H
+
+#include "io/channel.h"
+#include "qom/object.h"
+
+#define TYPE_QIO_CHANNEL_NULL "qio-channel-null"
+OBJECT_DECLARE_SIMPLE_TYPE(QIOChannelNull, QIO_CHANNEL_NULL)
+
+
+/**
+ * QIOChannelNull:
+ *
+ * The QIOChannelNull object provides a channel implementation
+ * that discards all writes and returns zero bytes for all reads.
+ */
+
+struct QIOChannelNull {
+    QIOChannel parent;
+    bool closed;
+};
+
+
+/**
+ * qio_channel_null_new:
+ *
+ * Create a new IO channel object that discards all writes
+ * and returns zero bytes for all reads.
+ *
+ * Returns: the new channel object
+ */
+QIOChannelNull *
+qio_channel_null_new(void);
+
+#endif /* QIO_CHANNEL_NULL_H */
diff --git a/io/channel-null.c b/io/channel-null.c
new file mode 100644
index 0000000000..75e3781507
--- /dev/null
+++ b/io/channel-null.c
@@ -0,0 +1,237 @@ 
+/*
+ * QEMU I/O channels null driver
+ *
+ * Copyright (c) 2022 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "io/channel-null.h"
+#include "io/channel-watch.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "qemu/iov.h"
+
+typedef struct QIOChannelNullSource QIOChannelNullSource;
+struct QIOChannelNullSource {
+    GSource parent;
+    QIOChannel *ioc;
+    GIOCondition condition;
+};
+
+
+QIOChannelNull *
+qio_channel_null_new(void)
+{
+    QIOChannelNull *ioc;
+
+    ioc = QIO_CHANNEL_NULL(object_new(TYPE_QIO_CHANNEL_NULL));
+
+    trace_qio_channel_null_new(ioc);
+
+    return ioc;
+}
+
+
+static void
+qio_channel_null_init(Object *obj)
+{
+    QIOChannelNull *ioc = QIO_CHANNEL_NULL(obj);
+    ioc->closed = false;
+}
+
+
+static ssize_t
+qio_channel_null_readv(QIOChannel *ioc,
+                       const struct iovec *iov,
+                       size_t niov,
+                       int **fds G_GNUC_UNUSED,
+                       size_t *nfds G_GNUC_UNUSED,
+                       Error **errp)
+{
+    QIOChannelNull *nioc = QIO_CHANNEL_NULL(ioc);
+
+    if (nioc->closed) {
+        error_setg_errno(errp, EINVAL,
+                         "Channel is closed");
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static ssize_t
+qio_channel_null_writev(QIOChannel *ioc,
+                        const struct iovec *iov,
+                        size_t niov,
+                        int *fds G_GNUC_UNUSED,
+                        size_t nfds G_GNUC_UNUSED,
+                        int flags G_GNUC_UNUSED,
+                        Error **errp)
+{
+    QIOChannelNull *nioc = QIO_CHANNEL_NULL(ioc);
+
+    if (nioc->closed) {
+        error_setg_errno(errp, EINVAL,
+                         "Channel is closed");
+        return -1;
+    }
+
+    return iov_size(iov, niov);
+}
+
+
+static int
+qio_channel_null_set_blocking(QIOChannel *ioc G_GNUC_UNUSED,
+                              bool enabled G_GNUC_UNUSED,
+                              Error **errp G_GNUC_UNUSED)
+{
+    return 0;
+}
+
+
+static off_t
+qio_channel_null_seek(QIOChannel *ioc G_GNUC_UNUSED,
+                      off_t offset G_GNUC_UNUSED,
+                      int whence G_GNUC_UNUSED,
+                      Error **errp G_GNUC_UNUSED)
+{
+    return 0;
+}
+
+
+static int
+qio_channel_null_close(QIOChannel *ioc,
+                       Error **errp G_GNUC_UNUSED)
+{
+    QIOChannelNull *nioc = QIO_CHANNEL_NULL(ioc);
+
+    nioc->closed = true;
+    return 0;
+}
+
+
+static void
+qio_channel_null_set_aio_fd_handler(QIOChannel *ioc G_GNUC_UNUSED,
+                                    AioContext *ctx G_GNUC_UNUSED,
+                                    IOHandler *io_read G_GNUC_UNUSED,
+                                    IOHandler *io_write G_GNUC_UNUSED,
+                                    void *opaque G_GNUC_UNUSED)
+{
+}
+
+
+static gboolean
+qio_channel_null_source_prepare(GSource *source G_GNUC_UNUSED,
+                                gint *timeout)
+{
+    *timeout = -1;
+
+    return TRUE;
+}
+
+
+static gboolean
+qio_channel_null_source_check(GSource *source G_GNUC_UNUSED)
+{
+    return TRUE;
+}
+
+
+static gboolean
+qio_channel_null_source_dispatch(GSource *source,
+                                 GSourceFunc callback,
+                                 gpointer user_data)
+{
+    QIOChannelFunc func = (QIOChannelFunc)callback;
+    QIOChannelNullSource *ssource = (QIOChannelNullSource *)source;
+
+    return (*func)(ssource->ioc,
+                   ssource->condition,
+                   user_data);
+}
+
+
+static void
+qio_channel_null_source_finalize(GSource *source)
+{
+    QIOChannelNullSource *ssource = (QIOChannelNullSource *)source;
+
+    object_unref(OBJECT(ssource->ioc));
+}
+
+
+GSourceFuncs qio_channel_null_source_funcs = {
+    qio_channel_null_source_prepare,
+    qio_channel_null_source_check,
+    qio_channel_null_source_dispatch,
+    qio_channel_null_source_finalize
+};
+
+
+static GSource *
+qio_channel_null_create_watch(QIOChannel *ioc,
+                              GIOCondition condition)
+{
+    GSource *source;
+    QIOChannelNullSource *ssource;
+
+    source = g_source_new(&qio_channel_null_source_funcs,
+                          sizeof(QIOChannelNullSource));
+    ssource = (QIOChannelNullSource *)source;
+
+    ssource->ioc = ioc;
+    object_ref(OBJECT(ioc));
+
+    ssource->condition = condition;
+
+    return source;
+}
+
+
+static void
+qio_channel_null_class_init(ObjectClass *klass,
+                            void *class_data G_GNUC_UNUSED)
+{
+    QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass);
+
+    ioc_klass->io_writev = qio_channel_null_writev;
+    ioc_klass->io_readv = qio_channel_null_readv;
+    ioc_klass->io_set_blocking = qio_channel_null_set_blocking;
+    ioc_klass->io_seek = qio_channel_null_seek;
+    ioc_klass->io_close = qio_channel_null_close;
+    ioc_klass->io_create_watch = qio_channel_null_create_watch;
+    ioc_klass->io_set_aio_fd_handler = qio_channel_null_set_aio_fd_handler;
+}
+
+
+static const TypeInfo qio_channel_null_info = {
+    .parent = TYPE_QIO_CHANNEL,
+    .name = TYPE_QIO_CHANNEL_NULL,
+    .instance_size = sizeof(QIOChannelNull),
+    .instance_init = qio_channel_null_init,
+    .class_init = qio_channel_null_class_init,
+};
+
+
+static void
+qio_channel_null_register_types(void)
+{
+    type_register_static(&qio_channel_null_info);
+}
+
+type_init(qio_channel_null_register_types);
diff --git a/io/meson.build b/io/meson.build
index bbcd3c53a4..283b9b2bdb 100644
--- a/io/meson.build
+++ b/io/meson.build
@@ -3,6 +3,7 @@  io_ss.add(files(
   'channel-buffer.c',
   'channel-command.c',
   'channel-file.c',
+  'channel-null.c',
   'channel-socket.c',
   'channel-tls.c',
   'channel-util.c',
diff --git a/io/trace-events b/io/trace-events
index c5e814eb44..3cc5cf1efd 100644
--- a/io/trace-events
+++ b/io/trace-events
@@ -10,6 +10,9 @@  qio_task_thread_result(void *task) "Task thread result task=%p"
 qio_task_thread_source_attach(void *task, void *source) "Task thread source attach task=%p source=%p"
 qio_task_thread_source_cancel(void *task, void *source) "Task thread source cancel task=%p source=%p"
 
+# channel-null.c
+qio_channel_null_new(void *ioc) "Null new ioc=%p"
+
 # channel-socket.c
 qio_channel_socket_new(void *ioc) "Socket new ioc=%p"
 qio_channel_socket_new_fd(void *ioc, int fd) "Socket new ioc=%p fd=%d"
diff --git a/tests/unit/meson.build b/tests/unit/meson.build
index 264f2bc0c8..6074ce51aa 100644
--- a/tests/unit/meson.build
+++ b/tests/unit/meson.build
@@ -86,6 +86,7 @@  if have_block
     'test-io-channel-file': ['io-channel-helpers.c', io],
     'test-io-channel-command': ['io-channel-helpers.c', io],
     'test-io-channel-buffer': ['io-channel-helpers.c', io],
+    'test-io-channel-null': [io],
     'test-crypto-ivgen': [io],
     'test-crypto-afsplit': [io],
     'test-crypto-block': [io],
diff --git a/tests/unit/test-io-channel-null.c b/tests/unit/test-io-channel-null.c
new file mode 100644
index 0000000000..b3aab17ccc
--- /dev/null
+++ b/tests/unit/test-io-channel-null.c
@@ -0,0 +1,95 @@ 
+/*
+ * QEMU I/O channel null test
+ *
+ * Copyright (c) 2022 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "io/channel-null.h"
+#include "qapi/error.h"
+
+static gboolean test_io_channel_watch(QIOChannel *ioc,
+                                      GIOCondition condition,
+                                      gpointer opaque)
+{
+    GIOCondition *gotcond = opaque;
+    *gotcond = condition;
+    return G_SOURCE_REMOVE;
+}
+
+static void test_io_channel_null_io(void)
+{
+    g_autoptr(QIOChannelNull) null = qio_channel_null_new();
+    char buf[1024];
+    GIOCondition gotcond = 0;
+    Error *local_err = NULL;
+
+    g_assert(qio_channel_write(QIO_CHANNEL(null),
+                               "Hello World", 11,
+                               &error_abort) == 11);
+
+    g_assert(qio_channel_read(QIO_CHANNEL(null),
+                              buf, sizeof(buf),
+                              &error_abort) == 0);
+
+    qio_channel_add_watch(QIO_CHANNEL(null),
+                          G_IO_IN,
+                          test_io_channel_watch,
+                          &gotcond,
+                          NULL);
+
+    g_main_context_iteration(NULL, false);
+
+    g_assert(gotcond == G_IO_IN);
+
+    qio_channel_add_watch(QIO_CHANNEL(null),
+                          G_IO_IN | G_IO_OUT,
+                          test_io_channel_watch,
+                          &gotcond,
+                          NULL);
+
+    g_main_context_iteration(NULL, false);
+
+    g_assert(gotcond == (G_IO_IN | G_IO_OUT));
+
+    qio_channel_close(QIO_CHANNEL(null), &error_abort);
+
+    g_assert(qio_channel_write(QIO_CHANNEL(null),
+                               "Hello World", 11,
+                               &local_err) == -1);
+    g_assert_nonnull(local_err);
+
+    g_clear_pointer(&local_err, error_free);
+
+    g_assert(qio_channel_read(QIO_CHANNEL(null),
+                              buf, sizeof(buf),
+                              &local_err) == -1);
+    g_assert_nonnull(local_err);
+
+    g_clear_pointer(&local_err, error_free);
+}
+
+int main(int argc, char **argv)
+{
+    module_call_init(MODULE_INIT_QOM);
+
+    g_test_init(&argc, &argv, NULL);
+
+    g_test_add_func("/io/channel/null/io", test_io_channel_null_io);
+
+    return g_test_run();
+}