---
README | 6 +
configure.ac | 24 +++
docs/nbdkit.pod | 1 +
plugins/Makefile.am | 1 +
plugins/ext2/Makefile.am | 68 +++++++
plugins/ext2/ext2.c | 359 ++++++++++++++++++++++++++++++++++++
plugins/ext2/nbdkit-ext2-plugin.pod | 130 +++++++++++++
7 files changed, 589 insertions(+)
diff --git a/README b/README
index baa29fc..e58df8b 100644
--- a/README
+++ b/README
@@ -65,6 +65,12 @@ For the libguestfs plugin, and to run the test suite:
- guestfish (from libguestfs)
+For the ext2 plugin:
+
+ - ext2fs
+
+ - com_err
+
For the VDDK plugin:
- VDDK (see plugins/vddk/README.VDDK)
diff --git a/configure.ac b/configure.ac
index 4bd9aac..c86c6ce 100644
--- a/configure.ac
+++ b/configure.ac
@@ -426,6 +426,29 @@ 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.
+AC_ARG_WITH([ext2],[
+ AS_HELP_STRING([--without-ext2],
+ [disable ext2 plugin @<:@default=check@:>@])],
+ [],
+ [with_ext2=check])
+AS_IF([test "$with_ext2" != "no"], [
+ PKG_CHECK_MODULES([EXT2FS], [ext2fs], [
+ AC_SUBST([EXT2FS_CFLAGS])
+ AC_SUBST([EXT2FS_LIBS])
+ AC_DEFINE([HAVE_EXT2FS],[1],[ext2fs found at compile time.])
+ ],
+ [AC_MSG_WARN([ext2fs not found, ext2 plugin will be disabled])])
+ PKG_CHECK_MODULES([COM_ERR], [com_err], [
+ AC_SUBST([COM_ERR_CFLAGS])
+ AC_SUBST([COM_ERR_LIBS])
+ AC_DEFINE([HAVE_COM_ERR],[1],[com_err found at compile time.])
+ ],
+ [AC_MSG_WARN([com_err not found, ext2 plugin will be disabled])])
+])
+AM_CONDITIONAL([HAVE_EXT2],
+ [test "x$EXT2FS_LIBS" != "x" && test
"x$COM_ERR_LIBS" != "x"])
+
dnl See plugins/vddk/README.VDDK.
AC_CHECK_SIZEOF([size_t])
AS_IF([test "x$ac_cv_sizeof_size_t" = "x4"],[bits=32],[bits=64])
@@ -511,6 +534,7 @@ AC_CONFIG_FILES([Makefile
plugins/example2/Makefile
plugins/example3/Makefile
plugins/example4/Makefile
+ plugins/ext2/Makefile
plugins/file/Makefile
plugins/guestfs/Makefile
plugins/gzip/Makefile
diff --git a/docs/nbdkit.pod b/docs/nbdkit.pod
index a515353..6ba981b 100644
--- a/docs/nbdkit.pod
+++ b/docs/nbdkit.pod
@@ -909,6 +909,7 @@ L<nbdkit-example1-plugin(1)>,
L<nbdkit-example2-plugin(1)>,
L<nbdkit-example3-plugin(1)>,
L<nbdkit-example4-plugin(1)>,
+L<nbdkit-ext2-plugin(1)>,
L<nbdkit-file-plugin(1)>,
L<nbdkit-guestfs-plugin(1)>,
L<nbdkit-gzip-plugin(1)>,
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index ba67feb..328bc01 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -36,6 +36,7 @@ SUBDIRS = \
example2 \
example3 \
example4 \
+ ext2 \
file \
guestfs \
gzip \
diff --git a/plugins/ext2/Makefile.am b/plugins/ext2/Makefile.am
new file mode 100644
index 0000000..525013f
--- /dev/null
+++ b/plugins/ext2/Makefile.am
@@ -0,0 +1,68 @@
+# nbdkit
+# Copyright (C) 2017-2018 Red Hat Inc.
+# All rights reserved.
+#
+# 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.
+
+EXTRA_DIST = nbdkit-ext2-plugin.pod
+
+CLEANFILES = *~
+
+plugindir = $(libdir)/nbdkit/plugins
+
+if HAVE_EXT2
+
+plugin_LTLIBRARIES = nbdkit-ext2-plugin.la
+
+nbdkit_ext2_plugin_la_SOURCES = \
+ ext2.c \
+ $(top_srcdir)/include/nbdkit-plugin.h
+
+nbdkit_ext2_plugin_la_CPPFLAGS = \
+ -I$(top_srcdir)/include
+nbdkit_ext2_plugin_la_CFLAGS = \
+ $(WARNINGS_CFLAGS) \
+ $(EXT2FS_CFLAGS) $(COM_ERR_CFLAGS)
+nbdkit_ext2_plugin_la_LIBADD = \
+ $(EXT2FS_LIBS) $(COM_ERR_LIBS)
+nbdkit_ext2_plugin_la_LDFLAGS = \
+ -module -avoid-version -shared
+
+if HAVE_POD2MAN
+
+man_MANS = nbdkit-ext2-plugin.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-ext2-plugin.1: nbdkit-ext2-plugin.pod
+ $(POD2MAN) $(POD2MAN_ARGS) --section=1 --name=`basename $@ .1` $< $@.t && \
+ if grep 'POD ERROR' $@.t; then rm $@.t; exit 1; fi && \
+ mv $@.t $@
+
+endif
+endif
diff --git a/plugins/ext2/ext2.c b/plugins/ext2/ext2.c
new file mode 100644
index 0000000..1e8f6dd
--- /dev/null
+++ b/plugins/ext2/ext2.c
@@ -0,0 +1,359 @@
+/* nbdkit
+ * Copyright (C) 2017-2018 Red Hat Inc.
+ * All rights reserved.
+ *
+ * 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>
+
+/* 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-plugin.h>
+
+/* Disk image and filename parameters. */
+static char *disk;
+static char *file;
+
+static void
+ext2_load (void)
+{
+ initialize_ext2_error_table ();
+}
+
+static void
+ext2_unload (void)
+{
+ free (disk);
+ free (file);
+}
+
+static int
+ext2_config (const char *key, const char *value)
+{
+ if (strcmp (key, "disk") == 0) {
+ if (disk != NULL) {
+ nbdkit_error ("disk parameter specified more than once");
+ return -1;
+ }
+ disk = nbdkit_absolute_path (value);
+ if (disk == NULL)
+ return -1;
+ }
+ else if (strcmp (key, "file") == 0) {
+ if (file != NULL) {
+ nbdkit_error ("file parameter specified more than once");
+ return -1;
+ }
+ file = strdup (value);
+ if (file == NULL) {
+ nbdkit_error ("strdup: %m");
+ return -1;
+ }
+ }
+ else {
+ nbdkit_error ("unknown parameter '%s'", key);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+ext2_config_complete (void)
+{
+ if (disk == NULL || file == NULL) {
+ nbdkit_error ("you must supply disk=<DISK> and file=<FILE>
parameters "
+ "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 0;
+}
+
+#define ext2_config_help \
+ "disk=<FILENAME> (required) Raw ext2, ext3 or ext4 filesystem.\n" \
+ "file=<FILENAME> (required) File to serve inside the disk image."
+
+/* The per-connection handle. */
+struct handle {
+ int readonly;
+ ext2_filsys fs; /* Filesystem handle. */
+ ext2_ino_t ino; /* Inode of open file. */
+ ext2_file_t file; /* File handle. */
+};
+
+/* Create the per-connection handle. */
+static void *
+ext2_open (int readonly)
+{
+ struct handle *h;
+ errcode_t err;
+ int fs_flags;
+ int file_flags;
+ struct ext2_inode inode;
+
+ h = malloc (sizeof *h);
+ if (h == NULL) {
+ nbdkit_error ("malloc: %m");
+ return NULL;
+ }
+
+ h->readonly = readonly;
+
+ fs_flags = 0;
+#ifdef EXT2_FLAG_64BITS
+ fs_flags |= EXT2_FLAG_64BITS;
+#endif
+ if (!readonly)
+ fs_flags |= EXT2_FLAG_RW;
+
+ err = ext2fs_open (disk, fs_flags, 0, 0, unix_io_manager, &h->fs);
+ if (err != 0) {
+ nbdkit_error ("%s: open: %s", disk, 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: %s: namei: %s", disk, 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: %s: inode: %s", disk, file, error_message (err));
+ goto err1;
+ }
+ if (!LINUX_S_ISREG (inode.i_mode)) {
+ nbdkit_error ("%s: %s: must be a regular file in the disk image",
+ disk, 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: %s: open: %s", disk, file, error_message (err));
+ goto err1;
+ }
+
+ return h;
+
+ err1:
+ ext2fs_close (h->fs);
+ err0:
+ free (h);
+ return NULL;
+}
+
+/* Free up the per-connection handle. */
+static void
+ext2_close (void *handle)
+{
+ struct handle *h = handle;
+
+ ext2fs_file_close (h->file);
+ ext2fs_close (h->fs);
+ free (h);
+}
+
+static int
+ext2_can_fua (void *handle)
+{
+ return NBDKIT_FUA_NATIVE;
+}
+
+/* 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.
+ */
+#define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_CONNECTIONS
+
+/* Get the disk size. */
+static int64_t
+ext2_get_size (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: %s: lsize: %s", disk, file, error_message (err));
+ return -1;
+ }
+ return (int64_t) size;
+}
+
+/* Read data. */
+static int
+ext2_pread (void *handle, void *buf, uint32_t count, uint64_t offset,
+ uint32_t flags)
+{
+ 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: %s: llseek: %s", disk, file, error_message (err));
+ return -1;
+ }
+
+ err = ext2fs_file_read (h->file, buf, (unsigned int) count, &got);
+ if (err != 0) {
+ nbdkit_error ("%s: %s: read: %s", disk, file, error_message (err));
+ return -1;
+ }
+
+ buf += got;
+ count -= got;
+ offset += got;
+ }
+
+ return 0;
+}
+
+/* Write data to the file. */
+static int
+ext2_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset,
+ uint32_t flags)
+{
+ 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: %s: llseek: %s", disk, file, error_message (err));
+ return -1;
+ }
+
+ err = ext2fs_file_write (h->file, buf, (unsigned int) count, &written);
+ if (err != 0) {
+ nbdkit_error ("%s: %s: write: %s", disk, file, error_message (err));
+ 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: %s: flush: %s", disk, file, error_message (err));
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+ext2_flush (void *handle, uint32_t flags)
+{
+ struct handle *h = handle;
+ errcode_t err;
+
+ err = ext2fs_file_flush (h->file);
+ if (err != 0) {
+ nbdkit_error ("%s: %s: flush: %s", disk, file, error_message (err));
+ 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_plugin plugin = {
+ .name = "ext2",
+ .version = PACKAGE_VERSION,
+ .load = ext2_load,
+ .unload = ext2_unload,
+ .config = ext2_config,
+ .config_complete = ext2_config_complete,
+ .config_help = ext2_config_help,
+ .open = ext2_open,
+ .close = ext2_close,
+ .can_fua = ext2_can_fua,
+ .get_size = ext2_get_size,
+ .pread = ext2_pread,
+ .pwrite = ext2_pwrite,
+ .flush = ext2_flush,
+ .errno_is_preserved = 1,
+};
+
+NBDKIT_REGISTER_PLUGIN(plugin)
diff --git a/plugins/ext2/nbdkit-ext2-plugin.pod b/plugins/ext2/nbdkit-ext2-plugin.pod
new file mode 100644
index 0000000..29c76aa
--- /dev/null
+++ b/plugins/ext2/nbdkit-ext2-plugin.pod
@@ -0,0 +1,130 @@
+=encoding utf8
+
+=head1 NAME
+
+nbdkit-ext2-plugin - Read and write files inside ext2, ext3 or ext4 filesystems
+
+=head1 SYNOPSIS
+
+ nbdkit ext2 disk=fs.img file=/disks/disk.raw
+
+ nbdkit --filter=partition ext2 \
+ disk=disk.img file=/disks/disk.raw \
+ partition=1
+
+=head1 DESCRIPTION
+
+C<nbdkit-ext2-plugin> is an nbdkit plugin 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 ext2 disk=fs.img file=/disks/disk.raw
+
+Commonly disk images are partitioned. In that case you must
+use L<nbdkit-partition-filter(1)> to select the partition:
+
+ nbdkit --filter=partition ext2 disk=.. file=.. partition=1
+
+This plugin 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 plugin 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 plugin 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<disk=FILENAME>
+
+The ext2, ext3 or ext4 filesystem, a file on the host.
+
+You could also use a device name here if the filesystem is located on
+a device. Be careful that the filesystem is not being accessed in
+parallel by another program and is not mounted, as that will almost
+certainly result in disk corruption in the filesystem.
+
+The plugin expects a raw filesystem. If the file/device is
+partitioned, use L<nbdkit-partition-filter(1)>.
+
+=item B<file=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 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 Red Hat Inc.
+
+=head1 LICENSE
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+=over 4
+
+=item *
+
+Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+=item *
+
+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.
+
+=item *
+
+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.
+
+=back
+
+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.
--
2.16.2