[Libguestfs] [nbdkit PATCH 1/3] filters: Add ext2 filter

Eric Blake eblake at redhat.com
Wed Feb 12 02:19:00 UTC 2020


The ext2 plugin does not work with partitions, and requires the disk
image to be in the local filesystem.  Better is to have a filter that
lets any other plugin be the disk image (including with the partition
filter), where the ext2 code then calls into our backend code.  But
since our backend code is different than Unix fd I/O, we need a new
custom io_manager (I heavily borrowed ideas from the existing
unix_io_manager and test_io_manager in e2fsprog).

With that in place, the new filter version of ext2.c is very similar
to the plugin version: it only needs tweaks for the different filter
API, and for calling into the new io_manager with the 'const char
*name' parameter actually being the opaque parameter that lets the
rest of io.c call back into nbdkit.

The next commit will then deprecate the ext2 plugin.

Signed-off-by: Eric Blake <eblake at redhat.com>
---
 TODO                                |   5 -
 configure.ac                        |   8 +-
 filters/ext2/Makefile.am            |  75 +++++
 filters/ext2/ext2.c                 | 400 ++++++++++++++++++++++++
 filters/ext2/io.c                   | 466 ++++++++++++++++++++++++++++
 filters/ext2/io.h                   |  57 ++++
 filters/ext2/nbdkit-ext2-filter.pod |  92 ++++++
 tests/Makefile.am                   |   2 +-
 tests/test-ext2.c                   |  28 +-
 9 files changed, 1117 insertions(+), 16 deletions(-)
 create mode 100644 filters/ext2/Makefile.am
 create mode 100644 filters/ext2/ext2.c
 create mode 100644 filters/ext2/io.c
 create mode 100644 filters/ext2/io.h
 create mode 100644 filters/ext2/nbdkit-ext2-filter.pod

diff --git a/TODO b/TODO
index 3af8be31..21cbfe5b 100644
--- a/TODO
+++ b/TODO
@@ -153,11 +153,6 @@ Rust:
 Suggestions for filters
 -----------------------

-* ext2 plugin should really be a filter, with a custom io_manager.
-  Needed so we can support:
-   nbdkit --filter=ext2 --filter=partition file disk.img partition=1 \
-       ext2file=/path/to/embedded
-
 * tar plugin should really be a filter

 * gzip plugin should really be a filter
diff --git a/configure.ac b/configure.ac
index a60c080c..c1ef2c0d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -97,6 +97,7 @@ filters="\
         cow \
         delay \
         error \
+	ext2 \
         extentlist \
         fua \
         ip \
@@ -871,10 +872,10 @@ dnl Check for guestfish (only needed for some of the tests).
 AC_CHECK_PROG([GUESTFISH], [guestfish], [guestfish], [no])
 AM_CONDITIONAL([HAVE_GUESTFISH], [test "x$GUESTFISH" != "xno"])

-dnl Check for ext2fs and com_err, for the ext2 plugin.
+dnl Check for ext2fs and com_err, for the ext2 filter and plugin.
 AC_ARG_WITH([ext2],
     [AS_HELP_STRING([--without-ext2],
-                    [disable ext2 plugin @<:@default=check@:>@])],
+                    [disable ext2 filter @<:@default=check@:>@])],
     [],
     [with_ext2=check])
 AS_IF([test "$with_ext2" != "no"], [
@@ -986,6 +987,7 @@ AC_CONFIG_FILES([Makefile
                  filters/cow/Makefile
                  filters/delay/Makefile
                  filters/error/Makefile
+                 filters/ext2/Makefile
                  filters/extentlist/Makefile
                  filters/fua/Makefile
                  filters/ip/Makefile
@@ -1090,6 +1092,8 @@ feature "tcl .................................... " \
 echo
 echo "Optional filters:"
 echo
+feature "ext2 ................................... " \
+        test "x$HAVE_EXT2_TRUE" = "x"
 feature "xz ..................................... " \
         test "x$HAVE_LIBLZMA_TRUE" = "x"

diff --git a/filters/ext2/Makefile.am b/filters/ext2/Makefile.am
new file mode 100644
index 00000000..1fb7ede5
--- /dev/null
+++ b/filters/ext2/Makefile.am
@@ -0,0 +1,75 @@
+# nbdkit
+# Copyright (C) 2017-2020 Red Hat Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of Red Hat nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+include $(top_srcdir)/common-rules.mk
+
+EXTRA_DIST = nbdkit-ext2-filter.pod
+
+if HAVE_EXT2
+
+filter_LTLIBRARIES = nbdkit-ext2-filter.la
+
+nbdkit_ext2_filter_la_SOURCES = \
+	ext2.c \
+	io.c \
+	io.h \
+	$(top_srcdir)/include/nbdkit-filter.h \
+	$(NULL)
+
+nbdkit_ext2_filter_la_CPPFLAGS = \
+	-I$(top_srcdir)/include \
+	-I$(top_srcdir)/common/utils \
+	$(NULL)
+nbdkit_ext2_filter_la_CFLAGS = \
+	$(WARNINGS_CFLAGS) \
+	$(EXT2FS_CFLAGS) $(COM_ERR_CFLAGS) \
+	$(NULL)
+nbdkit_ext2_filter_la_LIBADD = \
+	$(EXT2FS_LIBS) $(COM_ERR_LIBS) \
+	$(top_builddir)/common/utils/libutils.la \
+	$(NULL)
+nbdkit_ext2_filter_la_LDFLAGS = \
+	-module -avoid-version -shared \
+	-Wl,--version-script=$(top_srcdir)/filters/filters.syms \
+	$(NULL)
+
+if HAVE_POD
+
+man_MANS = nbdkit-ext2-filter.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-ext2-filter.1: nbdkit-ext2-filter.pod
+	$(PODWRAPPER) --section=1 --man $@ \
+	    --html $(top_builddir)/html/$@.html \
+	    $<
+
+endif HAVE_POD
+endif
diff --git a/filters/ext2/ext2.c b/filters/ext2/ext2.c
new file mode 100644
index 00000000..d53743cd
--- /dev/null
+++ b/filters/ext2/ext2.c
@@ -0,0 +1,400 @@
+/* nbdkit
+ * Copyright (C) 2017-2020 Red Hat Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <errno.h>
+
+/* Inlining is broken in the ext2fs header file.  Disable it by
+ * defining the following:
+ */
+#define NO_INLINE_FUNCS
+#include <ext2fs.h>
+
+#define NBDKIT_API_VERSION 2
+
+#include <nbdkit-filter.h>
+
+#include "cleanup.h"
+#include "io.h"
+
+/* Filename parameter. */
+static char *file;
+
+static void
+ext2_load (void)
+{
+  initialize_ext2_error_table ();
+}
+
+static void
+ext2_unload (void)
+{
+  free (file);
+}
+
+static int
+ext2_config (nbdkit_next_config *next, void *nxdata,
+             const char *key, const char *value)
+{
+  if (strcmp (key, "ext2file") == 0) {
+    if (file != NULL) {
+      nbdkit_error ("ext2file parameter specified more than once");
+      return -1;
+    }
+    file = strdup (value);
+    if (file == NULL) {
+      nbdkit_error ("strdup: %m");
+      return -1;
+    }
+    return 0;
+  }
+  else
+    return next (nxdata, key, value);
+}
+
+static int
+ext2_config_complete (nbdkit_next_config_complete *next, void *nxdata)
+{
+  if (file == NULL) {
+    nbdkit_error ("you must supply ext2file=<FILE> parameter "
+                  "after the plugin name on the command line");
+    return -1;
+  }
+
+  if (file[0] != '/') {
+    nbdkit_error ("the file parameter must refer to an absolute path");
+    return -1;
+  }
+
+  return next (nxdata);
+}
+
+#define ext2_config_help \
+  "ext2file=<FILENAME>  (required) File to serve inside the disk image."
+
+/* The per-connection handle. */
+struct handle {
+  ext2_filsys fs;               /* Filesystem handle. */
+  ext2_ino_t ino;               /* Inode of open file. */
+  ext2_file_t file;             /* File handle. */
+  struct nbdkit_next next;      /* "name" parameter to ext2fs_open. */
+};
+
+/* Create the per-connection handle. */
+static void *
+ext2_open (nbdkit_next_open *next, void *nxdata, int readonly)
+{
+  struct handle *h;
+
+  /* Request write access to the underlying plugin, for journal replay. */
+  if (next (nxdata, 0) == -1)
+    return NULL;
+
+  h = calloc (1, sizeof *h);
+  if (h == NULL) {
+    nbdkit_error ("calloc: %m");
+    return NULL;
+  }
+
+  return h;
+}
+
+static int
+ext2_prepare (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle,
+              int readonly)
+{
+  struct handle *h = handle;
+  errcode_t err;
+  int fs_flags;
+  int file_flags;
+  struct ext2_inode inode;
+  int64_t r;
+  CLEANUP_FREE char *name = NULL;
+
+  fs_flags = 0;
+#ifdef EXT2_FLAG_64BITS
+  fs_flags |= EXT2_FLAG_64BITS;
+#endif
+  r = next_ops->get_size (nxdata);
+  if (r == -1)
+    return -1;
+  r = next_ops->can_write (nxdata);
+  if (r == -1)
+    return -1;
+  if (r == 0)
+    readonly = 1;
+
+  if (!readonly)
+    fs_flags |= EXT2_FLAG_RW;
+
+  h->next.next_ops = next_ops;
+  h->next.nxdata = nxdata;
+  name = nbdkit_io_encode (&h->next);
+  if (!name) {
+    nbdkit_error ("nbdkit_io_encode: %m");
+    return -1;
+  }
+
+  err = ext2fs_open (name, fs_flags, 0, 0, nbdkit_io_manager, &h->fs);
+  if (err != 0) {
+    nbdkit_error ("open: %s", error_message (err));
+    goto err0;
+  }
+
+  if (strcmp (file, "/") == 0)
+    /* probably gonna fail, but we'll catch it later */
+    h->ino = EXT2_ROOT_INO;
+  else {
+    err = ext2fs_namei (h->fs, EXT2_ROOT_INO, EXT2_ROOT_INO,
+                        &file[1], &h->ino);
+    if (err != 0) {
+      nbdkit_error ("%s: namei: %s", file, error_message (err));
+      goto err1;
+    }
+  }
+
+  /* Check the file is a regular file.
+   * XXX This won't follow symlinks, we'd have to do that manually.
+   */
+  err = ext2fs_read_inode (h->fs, h->ino, &inode);
+  if (err != 0) {
+    nbdkit_error ("%s: inode: %s", file, error_message (err));
+    goto err1;
+  }
+  if (!LINUX_S_ISREG (inode.i_mode)) {
+    nbdkit_error ("%s: must be a regular file in the disk image", file);
+    goto err1;
+  }
+
+  file_flags = 0;
+  if (!readonly)
+    file_flags |= EXT2_FILE_WRITE;
+  err = ext2fs_file_open2 (h->fs, h->ino, NULL, file_flags, &h->file);
+  if (err != 0) {
+    nbdkit_error ("%s: open: %s", file, error_message (err));
+    goto err1;
+  }
+
+  return 0;
+
+ err1:
+  ext2fs_close (h->fs);
+  h->fs = NULL;
+ err0:
+  return -1;
+}
+
+/* Free up the per-connection handle. */
+static void
+ext2_close (void *handle)
+{
+  struct handle *h = handle;
+
+  if (h->fs) {
+    ext2fs_file_close (h->file);
+    ext2fs_close (h->fs);
+  }
+  free (h);
+}
+
+static int
+ext2_can_fua (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle)
+{
+  return NBDKIT_FUA_NATIVE;
+}
+
+static int
+ext2_can_cache (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle)
+{
+  /* Let nbdkit call pread to populate the file system cache. */
+  return NBDKIT_CACHE_EMULATE;
+}
+
+/* It might be possible to relax this, but it's complicated.
+ *
+ * It's desirable for ‘nbdkit -r’ to behave the same way as
+ * ‘mount -o ro’.  But we don't know the state of the readonly flag
+ * until ext2_open is called (because the NBD client can also request
+ * a readonly connection).  So we could not set the "ro" flag if we
+ * opened the filesystem any earlier (eg in ext2_config).
+ *
+ * So out of necessity we have one ext2_filsys handle per connection,
+ * but if we allowed parallel work on those handles then we would get
+ * data corruption, so we need to serialize connections.
+ */
+static int ext2_thread_model (void)
+{
+  return NBDKIT_THREAD_MODEL_SERIALIZE_CONNECTIONS;
+}
+
+/* Get the disk size. */
+static int64_t
+ext2_get_size (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle)
+{
+  struct handle *h = handle;
+  errcode_t err;
+  uint64_t size;
+
+  err = ext2fs_file_get_lsize (h->file, (__u64 *) &size);
+  if (err != 0) {
+    nbdkit_error ("%s: lsize: %s", file, error_message (err));
+    return -1;
+  }
+  return (int64_t) size;
+}
+
+/* Read data. */
+static int
+ext2_pread (struct nbdkit_next_ops *next_ops, void *nxdata,
+            void *handle, void *buf, uint32_t count, uint64_t offset,
+            uint32_t flags, int *errp)
+{
+  struct handle *h = handle;
+  errcode_t err;
+  unsigned int got;
+
+  while (count > 0) {
+    /* Although this function weirdly can return the new offset,
+     * examination of the code shows that it never returns anything
+     * different from what we set, so NULL out that parameter.
+     */
+    err = ext2fs_file_llseek (h->file, offset, EXT2_SEEK_SET, NULL);
+    if (err != 0) {
+      nbdkit_error ("%s: llseek: %s", file, error_message (err));
+      *errp = errno;
+      return -1;
+    }
+
+    err = ext2fs_file_read (h->file, buf, (unsigned int) count, &got);
+    if (err != 0) {
+      nbdkit_error ("%s: read: %s", file, error_message (err));
+      *errp = errno;
+      return -1;
+    }
+
+    buf += got;
+    count -= got;
+    offset += got;
+  }
+
+  return 0;
+}
+
+/* Write data to the file. */
+static int
+ext2_pwrite (struct nbdkit_next_ops *next_ops, void *nxdata,
+             void *handle, const void *buf, uint32_t count, uint64_t offset,
+             uint32_t flags, int *errp)
+{
+  struct handle *h = handle;
+  errcode_t err;
+  unsigned int written;
+
+  while (count > 0) {
+    err = ext2fs_file_llseek (h->file, offset, EXT2_SEEK_SET, NULL);
+    if (err != 0) {
+      nbdkit_error ("%s: llseek: %s", file, error_message (err));
+      *errp = errno;
+      return -1;
+    }
+
+    err = ext2fs_file_write (h->file, buf, (unsigned int) count, &written);
+    if (err != 0) {
+      nbdkit_error ("%s: write: %s", file, error_message (err));
+      *errp = errno;
+      return -1;
+    }
+
+    buf += written;
+    count -= written;
+    offset += written;
+  }
+
+  if ((flags & NBDKIT_FLAG_FUA) != 0) {
+    err = ext2fs_file_flush (h->file);
+    if (err != 0) {
+      nbdkit_error ("%s: flush: %s", file, error_message (err));
+      *errp = errno;
+      return -1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+ext2_flush (struct nbdkit_next_ops *next_ops, void *nxdata,
+            void *handle, uint32_t flags, int *errp)
+{
+  struct handle *h = handle;
+  errcode_t err;
+
+  err = ext2fs_file_flush (h->file);
+  if (err != 0) {
+    nbdkit_error ("%s: flush: %s", file, error_message (err));
+    *errp = errno;
+    return -1;
+  }
+
+  return 0;
+}
+
+/* XXX It seems as if we should be able to support trim and zero, if
+ * we could work out how those are implemented in the ext2fs API which
+ * is very obscure.
+ */
+
+static struct nbdkit_filter filter = {
+  .name              = "ext2",
+  .longname          = "nbdkit ext2 filter",
+  .load              = ext2_load,
+  .unload            = ext2_unload,
+  .config            = ext2_config,
+  .config_complete   = ext2_config_complete,
+  .config_help       = ext2_config_help,
+  .thread_model      = ext2_thread_model,
+  .open              = ext2_open,
+  .prepare           = ext2_prepare,
+  .close             = ext2_close,
+  .can_fua           = ext2_can_fua,
+  .can_cache         = ext2_can_cache,
+  .get_size          = ext2_get_size,
+  .pread             = ext2_pread,
+  .pwrite            = ext2_pwrite,
+  .flush             = ext2_flush,
+};
+
+NBDKIT_REGISTER_FILTER(filter)
diff --git a/filters/ext2/io.c b/filters/ext2/io.c
new file mode 100644
index 00000000..d4d9c941
--- /dev/null
+++ b/filters/ext2/io.c
@@ -0,0 +1,466 @@
+/* nbdkit
+ * Copyright (C) 2020 Red Hat Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* Copies heavily from e2fsprogs lib/ext2fs/unix_io.c: */
+
+/*
+ * io.c --- This is an nbdkit filter implementation of the I/O manager.
+ *
+ * Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
+ *      2002 by Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Library
+ * General Public License, version 2.
+ * %End-Header%
+ */
+
+#include <config.h>
+
+#include <ext2_fs.h>
+#include <ext2fs.h>
+
+#include "io.h"
+
+/*
+ * For checking structure magic numbers...
+ */
+
+#define EXT2_CHECK_MAGIC(struct, code) \
+  if ((struct)->magic != (code)) return (code)
+
+struct io_private_data {
+  int magic;
+  struct nbdkit_next_ops *next_ops;
+  void *nxdata;
+  ext2_loff_t offset;
+  struct struct_io_stats io_stats;
+};
+
+static errcode_t
+io_get_stats (io_channel channel, io_stats *stats)
+{
+  errcode_t retval = 0;
+
+  struct io_private_data *data;
+
+  EXT2_CHECK_MAGIC (channel, EXT2_ET_MAGIC_IO_CHANNEL);
+  data = (struct io_private_data *) channel->private_data;
+  EXT2_CHECK_MAGIC (data, EXT2_ET_MAGIC_NBDKIT_IO_CHANNEL);
+
+  if (stats)
+    *stats = &data->io_stats;
+
+  return retval;
+}
+
+/*
+ * Here are the raw I/O functions
+ */
+static errcode_t
+raw_read_blk (io_channel channel,
+              struct io_private_data *data,
+              unsigned long long block,
+              int count, void *bufv)
+{
+  errcode_t retval;
+  ssize_t size;
+  ext2_loff_t location;
+  int actual = 0;
+  unsigned char *buf = bufv;
+
+  size = (count < 0) ? -count : (ext2_loff_t) count * channel->block_size;
+  data->io_stats.bytes_read += size;
+  location = ((ext2_loff_t) block * channel->block_size) + data->offset;
+
+  /* TODO is 32-bit overflow ever likely to be a problem? */
+  if (data->next_ops->pread (data->nxdata, buf, size, location, 0, &errno) == 0)
+    return 0;
+
+  if (channel->read_error)
+    retval = (channel->read_error)(channel, block, count, buf,
+                 size, actual, errno);
+  return retval;
+}
+
+static errcode_t
+raw_write_blk (io_channel channel,
+               struct io_private_data *data,
+               unsigned long long block,
+               int count, const void *bufv)
+{
+  ssize_t size;
+  ext2_loff_t location;
+  int actual = 0;
+  errcode_t retval;
+  const unsigned char *buf = bufv;
+
+  if (count == 1)
+    size = channel->block_size;
+  else {
+    if (count < 0)
+      size = -count;
+    else
+      size = (ext2_loff_t) count * channel->block_size;
+  }
+  data->io_stats.bytes_written += size;
+
+  location = ((ext2_loff_t) block * channel->block_size) + data->offset;
+
+  /* TODO is 32-bit overflow ever likely to be a problem? */
+  if (data->next_ops->pwrite (data->nxdata, buf, size, location, 0,
+                              &errno) == 0)
+    return 0;
+
+  if (channel->write_error)
+    retval = (channel->write_error)(channel, block, count, buf,
+                                    size, actual, errno);
+  return retval;
+}
+
+char *
+nbdkit_io_encode (const struct nbdkit_next *next)
+{
+  char *ret;
+
+  if (asprintf (&ret, "nbdkit:%p:%p", next->next_ops, next->nxdata) < 0)
+    return NULL;
+  return ret;
+}
+
+int
+nbdkit_io_decode (const char *name, struct nbdkit_next *next)
+{
+  int n;
+
+  if (sscanf (name, "nbdkit:%p:%p%n", &next->next_ops, &next->nxdata,
+              &n) != 2 || n != strlen (name))
+    return -1;
+  return 0;
+}
+
+static errcode_t
+io_open (const char *name, int flags,
+         io_channel *channel)
+{
+  struct nbdkit_next next;
+  io_channel io = NULL;
+  struct io_private_data *data = NULL;
+  errcode_t retval;
+
+  if (nbdkit_io_decode (name, &next) == -1)
+    return EXT2_ET_BAD_DEVICE_NAME;
+
+  retval = ext2fs_get_mem (sizeof (struct struct_io_channel), &io);
+  if (retval)
+    goto cleanup;
+  memset (io, 0, sizeof (struct struct_io_channel));
+  io->magic = EXT2_ET_MAGIC_IO_CHANNEL;
+  retval = ext2fs_get_mem (sizeof (struct io_private_data), &data);
+  if (retval)
+    goto cleanup;
+
+  io->manager = nbdkit_io_manager;
+  retval = ext2fs_get_mem (strlen (name)+1, &io->name);
+  if (retval)
+    goto cleanup;
+
+  strcpy (io->name, name);
+  io->private_data = data;
+  io->block_size = 1024;
+  io->read_error = 0;
+  io->write_error = 0;
+  io->refcount = 1;
+
+  memset (data, 0, sizeof (struct io_private_data));
+  data->magic = EXT2_ET_MAGIC_NBDKIT_IO_CHANNEL;
+  data->io_stats.num_fields = 2;
+  data->next_ops = next.next_ops;
+  data->nxdata = next.nxdata;
+
+  /* Too bad NBD doesn't tell is if next_ops->trim guarantees read as zero. */
+  /* if (next_ops-> XXX (...)
+     io->flags |= CHANNEL_FLAGS_DISCARD_ZEROES; */
+
+  if (flags & IO_FLAG_RW && next.next_ops->can_write (next.nxdata) != 1) {
+    retval = EPERM;
+    goto cleanup;
+  }
+  *channel = io;
+  return 0;
+
+cleanup:
+  if (data)
+    ext2fs_free_mem (&data);
+  if (io) {
+    if (io->name) {
+      ext2fs_free_mem (&io->name);
+    }
+    ext2fs_free_mem (&io);
+  }
+  return retval;
+}
+
+static errcode_t
+io_close (io_channel channel)
+{
+  struct io_private_data *data;
+  errcode_t retval = 0;
+
+  EXT2_CHECK_MAGIC (channel, EXT2_ET_MAGIC_IO_CHANNEL);
+  data = (struct io_private_data *) channel->private_data;
+  EXT2_CHECK_MAGIC (data, EXT2_ET_MAGIC_NBDKIT_IO_CHANNEL);
+
+  if (--channel->refcount > 0)
+    return 0;
+
+  ext2fs_free_mem (&channel->private_data);
+  if (channel->name)
+    ext2fs_free_mem (&channel->name);
+  ext2fs_free_mem (&channel);
+  return retval;
+}
+
+static errcode_t
+io_set_blksize (io_channel channel, int blksize)
+{
+  struct io_private_data *data;
+
+  EXT2_CHECK_MAGIC (channel, EXT2_ET_MAGIC_IO_CHANNEL);
+  data = (struct io_private_data *) channel->private_data;
+  EXT2_CHECK_MAGIC (data, EXT2_ET_MAGIC_NBDKIT_IO_CHANNEL);
+
+  channel->block_size = blksize;
+  return 0;
+}
+
+static errcode_t
+io_read_blk64 (io_channel channel, unsigned long long block,
+               int count, void *buf)
+{
+  struct io_private_data *data;
+
+  EXT2_CHECK_MAGIC (channel, EXT2_ET_MAGIC_IO_CHANNEL);
+  data = (struct io_private_data *) channel->private_data;
+  EXT2_CHECK_MAGIC (data, EXT2_ET_MAGIC_NBDKIT_IO_CHANNEL);
+
+  return raw_read_blk (channel, data, block, count, buf);
+}
+
+static errcode_t
+io_read_blk (io_channel channel, unsigned long block,
+             int count, void *buf)
+{
+  return io_read_blk64 (channel, block, count, buf);
+}
+
+static errcode_t
+io_write_blk64 (io_channel channel, unsigned long long block,
+                int count, const void *buf)
+{
+  struct io_private_data *data;
+
+  EXT2_CHECK_MAGIC (channel, EXT2_ET_MAGIC_IO_CHANNEL);
+  data = (struct io_private_data *) channel->private_data;
+  EXT2_CHECK_MAGIC (data, EXT2_ET_MAGIC_NBDKIT_IO_CHANNEL);
+
+  return raw_write_blk (channel, data, block, count, buf);
+}
+
+static errcode_t
+io_cache_readahead (io_channel channel,
+                    unsigned long long block,
+                    unsigned long long count)
+{
+  struct io_private_data *data;
+
+  EXT2_CHECK_MAGIC (channel, EXT2_ET_MAGIC_IO_CHANNEL);
+  data = (struct io_private_data *)channel->private_data;
+  EXT2_CHECK_MAGIC (data, EXT2_ET_MAGIC_NBDKIT_IO_CHANNEL);
+
+  if (data->next_ops->can_cache (data->nxdata) == 1) {
+    /* TODO is 32-bit overflow ever likely to be a problem? */
+    if (data->next_ops->cache (data->nxdata,
+                               (ext2_loff_t)count * channel->block_size,
+                               ((ext2_loff_t)block * channel->block_size +
+                                data->offset),
+                               0, &errno) == -1)
+      return errno;
+    return 0;
+  }
+
+  return EXT2_ET_OP_NOT_SUPPORTED;
+
+}
+
+static errcode_t
+io_write_blk (io_channel channel, unsigned long block,
+              int count, const void *buf)
+{
+  return io_write_blk64 (channel, block, count, buf);
+}
+
+static errcode_t
+io_write_byte (io_channel channel, unsigned long offset,
+               int size, const void *buf)
+{
+  struct io_private_data *data;
+
+  EXT2_CHECK_MAGIC (channel, EXT2_ET_MAGIC_IO_CHANNEL);
+  data = (struct io_private_data *) channel->private_data;
+  EXT2_CHECK_MAGIC (data, EXT2_ET_MAGIC_NBDKIT_IO_CHANNEL);
+
+  if (data->next_ops->pwrite (data->nxdata, buf, size,
+                              offset + data->offset, 0, &errno) == -1)
+    return errno;
+
+  return 0;
+}
+
+/*
+ * Flush data buffers to disk.
+ */
+static errcode_t
+io_flush (io_channel channel)
+{
+  struct io_private_data *data;
+  errcode_t retval = 0;
+
+  EXT2_CHECK_MAGIC (channel, EXT2_ET_MAGIC_IO_CHANNEL);
+  data = (struct io_private_data *) channel->private_data;
+  EXT2_CHECK_MAGIC (data, EXT2_ET_MAGIC_NBDKIT_IO_CHANNEL);
+
+  if (data->next_ops->flush (data->nxdata, 0, &errno) == -1)
+    return errno;
+  return retval;
+}
+
+static errcode_t
+io_set_option (io_channel channel, const char *option,
+               const char *arg)
+{
+  struct io_private_data *data;
+  unsigned long long tmp;
+  char *end;
+
+  EXT2_CHECK_MAGIC (channel, EXT2_ET_MAGIC_IO_CHANNEL);
+  data = (struct io_private_data *) channel->private_data;
+  EXT2_CHECK_MAGIC (data, EXT2_ET_MAGIC_NBDKIT_IO_CHANNEL);
+
+  if (!strcmp (option, "offset")) {
+    if (!arg)
+      return EXT2_ET_INVALID_ARGUMENT;
+
+    tmp = strtoull (arg, &end, 0);
+    if (*end)
+      return EXT2_ET_INVALID_ARGUMENT;
+    data->offset = tmp;
+    if (data->offset < 0)
+      return EXT2_ET_INVALID_ARGUMENT;
+    return 0;
+  }
+  return EXT2_ET_INVALID_ARGUMENT;
+}
+
+static errcode_t
+io_discard (io_channel channel, unsigned long long block,
+            unsigned long long count)
+{
+  struct io_private_data *data;
+
+  EXT2_CHECK_MAGIC (channel, EXT2_ET_MAGIC_IO_CHANNEL);
+  data = (struct io_private_data *) channel->private_data;
+  EXT2_CHECK_MAGIC (data, EXT2_ET_MAGIC_NBDKIT_IO_CHANNEL);
+
+  if (data->next_ops->can_trim (data->nxdata) == 1) {
+    /* TODO is 32-bit overflow ever likely to be a problem? */
+    if (data->next_ops->trim (data->nxdata,
+                              (off_t)(count) * channel->block_size,
+                              ((off_t)(block) * channel->block_size +
+                               data->offset),
+                              0, &errno) == 0)
+      return 0;
+    if (errno == EOPNOTSUPP)
+      goto unimplemented;
+    return errno;
+  }
+
+unimplemented:
+  return EXT2_ET_UNIMPLEMENTED;
+}
+
+static errcode_t
+io_zeroout (io_channel channel, unsigned long long block,
+            unsigned long long count)
+{
+  struct io_private_data *data;
+
+  EXT2_CHECK_MAGIC (channel, EXT2_ET_MAGIC_IO_CHANNEL);
+  data = (struct io_private_data *) channel->private_data;
+  EXT2_CHECK_MAGIC (data, EXT2_ET_MAGIC_NBDKIT_IO_CHANNEL);
+
+  if (data->next_ops->can_zero (data->nxdata) == 1) {
+    if (data->next_ops->zero (data->nxdata,
+                              (off_t)(count) * channel->block_size,
+                              ((off_t)(block) * channel->block_size +
+                               data->offset),
+                              NBDKIT_FLAG_MAY_TRIM, &errno) == 0)
+      return 0;
+    if (errno == EOPNOTSUPP)
+      goto unimplemented;
+    return errno;
+  }
+
+unimplemented:
+  return EXT2_ET_UNIMPLEMENTED;
+}
+
+static struct struct_io_manager struct_nbdkit_manager = {
+  .magic = EXT2_ET_MAGIC_IO_MANAGER,
+  .name = "nbdkit I/O Manager",
+  .open = io_open,
+  .close = io_close,
+  .set_blksize = io_set_blksize,
+  .read_blk = io_read_blk,
+  .write_blk = io_write_blk,
+  .flush = io_flush,
+  .write_byte = io_write_byte,
+  .set_option = io_set_option,
+  .get_stats = io_get_stats,
+  .read_blk64 = io_read_blk64,
+  .write_blk64 = io_write_blk64,
+  .discard = io_discard,
+  .cache_readahead = io_cache_readahead,
+  .zeroout = io_zeroout,
+};
+
+io_manager nbdkit_io_manager = &struct_nbdkit_manager;
diff --git a/filters/ext2/io.h b/filters/ext2/io.h
new file mode 100644
index 00000000..da286812
--- /dev/null
+++ b/filters/ext2/io.h
@@ -0,0 +1,57 @@
+/* nbdkit
+ * Copyright (C) 2020 Red Hat Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef NBDKIT_IO_H
+#define NBDKIT_IO_H
+
+#include <ext2_io.h>
+
+#include <nbdkit-filter.h>
+
+#define EXT2_ET_MAGIC_NBDKIT_IO_CHANNEL EXT2_ET_MAGIC_RESERVED_19
+
+struct nbdkit_next {
+  struct nbdkit_next_ops *next_ops;
+  void *nxdata;
+};
+
+/* Utility functions for encoding nbdkit_next as a name usable by ext2fs */
+extern char *nbdkit_io_encode(const struct nbdkit_next *next)
+  __attribute__ ((__nonnull__ (1)));
+extern int nbdkit_io_decode(const char *name, struct nbdkit_next *out)
+  __attribute__ ((__nonnull__ (1, 2)));
+
+/* Custom io manager that performs all ext2fs I/O on the next nbdkit layer */
+extern io_manager nbdkit_io_manager;
+
+
+#endif /* NBDKIT_IO_H */
diff --git a/filters/ext2/nbdkit-ext2-filter.pod b/filters/ext2/nbdkit-ext2-filter.pod
new file mode 100644
index 00000000..c0927637
--- /dev/null
+++ b/filters/ext2/nbdkit-ext2-filter.pod
@@ -0,0 +1,92 @@
+=head1 NAME
+
+nbdkit-ext2-filter - read and write files inside ext2, ext3 or
+ext4 filesystems
+
+=head1 SYNOPSIS
+
+ nbdkit --filter=ext2 file fs.img ext2file=/disks/disk.raw
+
+=head1 DESCRIPTION
+
+C<nbdkit-ext2-filter> is an nbdkit filter which can read and
+write files inside ext2, ext3 or ext4 filesystem images.
+
+Suppose you have an ext2/3/4 filesystem image called F<fs.img>
+which contains inside itself a file called F<disk.raw>
+inside a directory on the filesystem called F</disks>, then
+you could serve that file over NBD using:
+
+ nbdkit --filter=ext2 file fs.img ext2file=/disks/disk.raw
+
+Commonly disk images are partitioned.  In that case, you must select
+just the partition of a disk image that contains an ext2 filesystem,
+by using L<nbdkit-partition-filter(1)>:
+
+ nbdkit --filter=ext2 --filter=partition file fs.img \
+    partition=1 ext2file=/disks/disk.raw
+
+This filter can both read and write to the file inside the filesystem.
+Use the I<-r> flag to force a readonly connection, but note this does
+I<not> guarantee that no writes are made to the filesystem.  In
+particular we may have to replay the ext3 journal in order to open a
+filesystem even read-only.
+
+The filter does I<not> support multiple parallel connections, because
+there is a risk of corrupting the filesystem (as if the filesystem was
+mounted by multiple machines).  If a second connection is made to
+nbdkit, it will block until the first connection closes.
+
+The filter is implemented using the ext2fs library which is provided
+in most Linux distros, and also available as part of the e2fsprogs
+project.
+
+L<nbdkit-guestfs-plugin(1)> is a more generic plugin which can read
+files from all kinds of different filesystem types, even if they are
+partitioned or use logical volumes.  It uses libguestfs instead of
+e2fsprogs.
+
+=head1 PARAMETERS
+
+=over 4
+
+=item B<ext2file=>PATH
+
+The full path of the file within the filesystem that will be exposed
+over NBD.  The path must be absolute (starts with C</>).
+
+=back
+
+=head1 FILES
+
+=over 4
+
+=item F<$filterdir/nbdkit-ext2-filter.so>
+
+The filter.
+
+Use C<nbdkit --dump-config> to find the location of C<$filterdir>.
+
+=back
+
+=head1 VERSION
+
+C<nbdkit-ext2-filter> first appeared in nbdkit 1.18, replacing an older
+C<nbdkit-ext2-plugin> from nbdkit 1.4.
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-plugin(3)>,
+L<nbdkit-partition-filter(1)>,
+L<nbdkit-guestfs-plugin(1)>,
+L<http://e2fsprogs.sourceforge.net/>,
+L<fuse2fs(1)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones
+
+=head1 COPYRIGHT
+
+Copyright (C) 2018-2020 Red Hat Inc.
diff --git a/tests/Makefile.am b/tests/Makefile.am
index ea6b1478..e1284231 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -515,7 +515,7 @@ test_data_LDADD = libtest.la $(LIBGUESTFS_LIBS)
 # eval plugin test.
 TESTS += test-eval.sh

-# ext2 plugin test.
+# ext2 filter and plugin test.
 if HAVE_EXT2
 if HAVE_GUESTFISH

diff --git a/tests/test-ext2.c b/tests/test-ext2.c
index d5b46f0c..cbb85855 100644
--- a/tests/test-ext2.c
+++ b/tests/test-ext2.c
@@ -1,5 +1,5 @@
 /* nbdkit
- * Copyright (C) 2013-2018 Red Hat Inc.
+ * Copyright (C) 2013-2020 Red Hat Inc.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -43,17 +43,13 @@

 #include "test.h"

-int
-main (int argc, char *argv[])
+static int
+do_test (void)
 {
   guestfs_h *g;
   int r;
   char *data;

-  if (test_start_nbdkit ("ext2", "-r", "disk=ext2.img", "file=/disks/disk.img",
-                         NULL) == -1)
-    exit (EXIT_FAILURE);
-
   g = guestfs_create ();
   if (g == NULL) {
     perror ("guestfs_create");
@@ -89,5 +85,21 @@ main (int argc, char *argv[])
   }

   guestfs_close (g);
-  exit (EXIT_SUCCESS);
+  return 0;
+}
+
+int
+main (int argc, char *argv[])
+{
+  /* Older plugin */
+  if (test_start_nbdkit ("ext2", "-r", "disk=ext2.img", "file=/disks/disk.img",
+                         NULL) == -1)
+  do_test ();
+
+  /* Newer filter */
+  if (test_start_nbdkit ("--filter", "ext2", "-r", "file", "ext2.img",
+                         "ext2file=/disks/disk.img", NULL) == -1)
+    exit (EXIT_FAILURE);
+  do_test ();
+  return 0;
 }
-- 
2.24.1




More information about the Libguestfs mailing list