I'm impressed that I was able to whip this out in just one day of hacking. Below, I'll include a diff between the plugin and the filter as of patch 1, if it aids review. Eric Blake (3): filters: Add ext2 filter ext2: Deprecate ext2 plugin ext2: Add mode for letting client exportname choose file from image TODO | 5 - configure.ac | 8 +- filters/ext2/Makefile.am | 75 +++++ filters/ext2/ext2.c | 415 +++++++++++++++++++++++++ filters/ext2/io.c | 466 ++++++++++++++++++++++++++++ filters/ext2/io.h | 57 ++++ filters/ext2/nbdkit-ext2-filter.pod | 102 ++++++ plugins/ext2/nbdkit-ext2-plugin.pod | 12 +- tests/Makefile.am | 2 +- tests/test-ext2.c | 28 +- 10 files changed, 1153 insertions(+), 17 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 {plugins => filters}/ext2/ext2.c | 173 ++++++++++++++++++++++++--------------- 1 file changed, 105 insertions(+), 68 deletions(-) diff --git a/plugins/ext2/ext2.c b/filters/ext2/ext2.c index 6698d99f..d53743cd 100644 --- a/plugins/ext2/ext2.c +++ b/filters/ext2/ext2.c @@ -1,5 +1,5 @@ /* nbdkit - * Copyright (C) 2017-2019 Red Hat Inc. + * 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 @@ -35,6 +35,7 @@ #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: @@ -44,10 +45,12 @@ #define NBDKIT_API_VERSION 2 -#include <nbdkit-plugin.h> +#include <nbdkit-filter.h> -/* Disk image and filename parameters. */ -static char *disk; +#include "cleanup.h" +#include "io.h" + +/* Filename parameter. */ static char *file; static void @@ -59,25 +62,16 @@ ext2_load (void) static void ext2_unload (void) { - free (disk); free (file); } static int -ext2_config (const char *key, const char *value) +ext2_config (nbdkit_next_config *next, void *nxdata, + 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 (strcmp (key, "ext2file") == 0) { if (file != NULL) { - nbdkit_error ("file parameter specified more than once"); + nbdkit_error ("ext2file parameter specified more than once"); return -1; } file = strdup (value); @@ -85,20 +79,17 @@ ext2_config (const char *key, const char *value) nbdkit_error ("strdup: %m"); return -1; } + return 0; } - else { - nbdkit_error ("unknown parameter '%s'", key); - return -1; - } - - return 0; + else + return next (nxdata, key, value); } static int -ext2_config_complete (void) +ext2_config_complete (nbdkit_next_config_complete *next, void *nxdata) { - if (disk == NULL || file == NULL) { - nbdkit_error ("you must supply disk=<DISK> and file=<FILE> parameters " + if (file == NULL) { + nbdkit_error ("you must supply ext2file=<FILE> parameter " "after the plugin name on the command line"); return -1; } @@ -108,46 +99,78 @@ ext2_config_complete (void) return -1; } - return 0; + return next (nxdata); } #define ext2_config_help \ - "disk=<FILENAME> (required) Raw ext2, ext3 or ext4 filesystem.\n" \ - "file=<FILENAME> (required) File to serve inside the disk image." + "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 (int readonly) +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; - - h = malloc (sizeof *h); - if (h == NULL) { - nbdkit_error ("malloc: %m"); - return NULL; - } + 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; - err = ext2fs_open (disk, fs_flags, 0, 0, unix_io_manager, &h->fs); + 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 ("%s: open: %s", disk, error_message (err)); + nbdkit_error ("open: %s", error_message (err)); goto err0; } @@ -158,7 +181,7 @@ ext2_open (int readonly) 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)); + nbdkit_error ("%s: namei: %s", file, error_message (err)); goto err1; } } @@ -168,12 +191,11 @@ ext2_open (int readonly) */ err = ext2fs_read_inode (h->fs, h->ino, &inode); if (err != 0) { - nbdkit_error ("%s: %s: inode: %s", disk, file, error_message (err)); + nbdkit_error ("%s: inode: %s", 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); + nbdkit_error ("%s: must be a regular file in the disk image", file); goto err1; } @@ -182,17 +204,17 @@ ext2_open (int 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)); + nbdkit_error ("%s: open: %s", file, error_message (err)); goto err1; } - return h; + return 0; err1: ext2fs_close (h->fs); + h->fs = NULL; err0: - free (h); - return NULL; + return -1; } /* Free up the per-connection handle. */ @@ -201,19 +223,21 @@ ext2_close (void *handle) { struct handle *h = handle; - ext2fs_file_close (h->file); - ext2fs_close (h->fs); + if (h->fs) { + ext2fs_file_close (h->file); + ext2fs_close (h->fs); + } free (h); } static int -ext2_can_fua (void *handle) +ext2_can_fua (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle) { return NBDKIT_FUA_NATIVE; } static int -ext2_can_cache (void *handle) +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; @@ -231,11 +255,14 @@ ext2_can_cache (void *handle) * 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 +static int ext2_thread_model (void) +{ + return NBDKIT_THREAD_MODEL_SERIALIZE_CONNECTIONS; +} /* Get the disk size. */ static int64_t -ext2_get_size (void *handle) +ext2_get_size (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle) { struct handle *h = handle; errcode_t err; @@ -243,7 +270,7 @@ ext2_get_size (void *handle) err = ext2fs_file_get_lsize (h->file, (__u64 *) &size); if (err != 0) { - nbdkit_error ("%s: %s: lsize: %s", disk, file, error_message (err)); + nbdkit_error ("%s: lsize: %s", file, error_message (err)); return -1; } return (int64_t) size; @@ -251,8 +278,9 @@ ext2_get_size (void *handle) /* Read data. */ static int -ext2_pread (void *handle, void *buf, uint32_t count, uint64_t offset, - uint32_t flags) +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; @@ -265,13 +293,15 @@ ext2_pread (void *handle, void *buf, uint32_t count, uint64_t offset, */ 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)); + 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: %s: read: %s", disk, file, error_message (err)); + nbdkit_error ("%s: read: %s", file, error_message (err)); + *errp = errno; return -1; } @@ -285,8 +315,9 @@ ext2_pread (void *handle, void *buf, uint32_t count, uint64_t offset, /* Write data to the file. */ static int -ext2_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset, - uint32_t flags) +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; @@ -295,13 +326,15 @@ ext2_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset, 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)); + 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: %s: write: %s", disk, file, error_message (err)); + nbdkit_error ("%s: write: %s", file, error_message (err)); + *errp = errno; return -1; } @@ -313,7 +346,8 @@ ext2_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset, 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)); + nbdkit_error ("%s: flush: %s", file, error_message (err)); + *errp = errno; return -1; } } @@ -322,14 +356,16 @@ ext2_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset, } static int -ext2_flush (void *handle, uint32_t flags) +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: %s: flush: %s", disk, file, error_message (err)); + nbdkit_error ("%s: flush: %s", file, error_message (err)); + *errp = errno; return -1; } @@ -341,15 +377,17 @@ ext2_flush (void *handle, uint32_t flags) * is very obscure. */ -static struct nbdkit_plugin plugin = { +static struct nbdkit_filter filter = { .name = "ext2", - .version = PACKAGE_VERSION, + .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, @@ -357,7 +395,6 @@ static struct nbdkit_plugin plugin = { .pread = ext2_pread, .pwrite = ext2_pwrite, .flush = ext2_flush, - .errno_is_preserved = 1, }; -NBDKIT_REGISTER_PLUGIN(plugin) +NBDKIT_REGISTER_FILTER(filter) -- 2.24.1
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@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
Eric Blake
2020-Feb-12 02:19 UTC
[Libguestfs] [nbdkit PATCH 2/3] ext2: Deprecate ext2 plugin
Plan to remove it in the next-but-one stable release. Signed-off-by: Eric Blake <eblake@redhat.com> --- plugins/ext2/nbdkit-ext2-plugin.pod | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/plugins/ext2/nbdkit-ext2-plugin.pod b/plugins/ext2/nbdkit-ext2-plugin.pod index b8d6c21e..5691b6a2 100644 --- a/plugins/ext2/nbdkit-ext2-plugin.pod +++ b/plugins/ext2/nbdkit-ext2-plugin.pod @@ -7,6 +7,15 @@ ext4 filesystems nbdkit ext2 disk=fs.img file=/disks/disk.raw +=head1 DEPRECATED + +B<The ext2 plugin is deprecated in S<nbdkit E<ge> 1.17.8> and will be +removed in S<nbdkit 1.20>>. It has been replaced with a filter with +the same functionality, see L<nbdkit-ext2-filter(1)>. You can use the +filter like this: + + nbdkit --filter=ext2 file fs.img ext2file=/disks/disk.raw + =head1 DESCRIPTION C<nbdkit-ext2-plugin> is an nbdkit plugin which can read and @@ -82,6 +91,7 @@ C<nbdkit-ext2-plugin> first appeared in nbdkit 1.4. L<nbdkit(1)>, L<nbdkit-plugin(3)>, +L<nbdkit-ext2-filter(1)>, L<nbdkit-partition-filter(1)>, L<nbdkit-guestfs-plugin(1)>, L<http://e2fsprogs.sourceforge.net/>, @@ -93,4 +103,4 @@ Richard W.M. Jones =head1 COPYRIGHT -Copyright (C) 2018 Red Hat Inc. +Copyright (C) 2018-2020 Red Hat Inc. -- 2.24.1
Eric Blake
2020-Feb-12 02:19 UTC
[Libguestfs] [nbdkit PATCH 3/3] ext2: Add mode for letting client exportname choose file from image
Not enabled by default, because of the potential security concern that a client can use this to sniff what file names exist in the image; but for an opt-in setting in the server, this lets a single nbdkit process serve multiple files from an ext2 image (one at a time, unless we find a way to allow a filter to open the plugin once without a current connection, then share that opened plugin across multiple client connections). In testing this manually, I learned that qemu 4.0 fails to preserve leading / in nbd://host:port//path/to/file (no matter how many slashes you add), so the code here adds a leading slash to be nice to clients like broken qemu. Signed-off-by: Eric Blake <eblake@redhat.com> --- filters/ext2/ext2.c | 35 ++++++++++++++++++++--------- filters/ext2/nbdkit-ext2-filter.pod | 10 +++++++++ 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/filters/ext2/ext2.c b/filters/ext2/ext2.c index d53743cd..92c8601a 100644 --- a/filters/ext2/ext2.c +++ b/filters/ext2/ext2.c @@ -50,7 +50,7 @@ #include "cleanup.h" #include "io.h" -/* Filename parameter. */ +/* Filename parameter, or NULL to honor export name. */ static char *file; static void @@ -94,7 +94,11 @@ ext2_config_complete (nbdkit_next_config_complete *next, void *nxdata) return -1; } - if (file[0] != '/') { + if (strcmp (file, "exportname") == 0) { + free (file); + file = NULL; + } + else if (file[0] != '/') { nbdkit_error ("the file parameter must refer to an absolute path"); return -1; } @@ -103,7 +107,8 @@ ext2_config_complete (nbdkit_next_config_complete *next, void *nxdata) } #define ext2_config_help \ - "ext2file=<FILENAME> (required) File to serve inside the disk image." + "ext2file=<FILENAME> (required) Absolute name of file to serve inside the\n" \ + " disk image, or 'exportname' for client choice." /* The per-connection handle. */ struct handle { @@ -143,6 +148,8 @@ ext2_prepare (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle, struct ext2_inode inode; int64_t r; CLEANUP_FREE char *name = NULL; + const char *fname = file ?: nbdkit_export_name (); + CLEANUP_FREE char *absname = NULL; fs_flags = 0; #ifdef EXT2_FLAG_64BITS @@ -168,34 +175,42 @@ ext2_prepare (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle, return -1; } + if (fname[0] != '/') { + if (asprintf (&absname, "/%s", fname) < 0) { + nbdkit_error ("asprintf: %m"); + return -1; + } + fname = absname; + } + 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) + if (strcmp (fname, "/") == 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); + &fname[1], &h->ino); if (err != 0) { - nbdkit_error ("%s: namei: %s", file, error_message (err)); + nbdkit_error ("%s: namei: %s", fname, error_message (err)); goto err1; } } - /* Check the file is a regular file. + /* Check that fname 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)); + nbdkit_error ("%s: inode: %s", fname, 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); + nbdkit_error ("%s: must be a regular file in the disk image", fname); goto err1; } @@ -204,7 +219,7 @@ ext2_prepare (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle, 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)); + nbdkit_error ("%s: open: %s", fname, error_message (err)); goto err1; } diff --git a/filters/ext2/nbdkit-ext2-filter.pod b/filters/ext2/nbdkit-ext2-filter.pod index c0927637..d621ee45 100644 --- a/filters/ext2/nbdkit-ext2-filter.pod +++ b/filters/ext2/nbdkit-ext2-filter.pod @@ -7,6 +7,9 @@ ext4 filesystems nbdkit --filter=ext2 file fs.img ext2file=/disks/disk.raw + nbdkit --filter=ext2 --filter=partition file fs.img \ + partition=1 ext2file=exportname + =head1 DESCRIPTION C<nbdkit-ext2-filter> is an nbdkit filter which can read and @@ -55,6 +58,13 @@ e2fsprogs. The full path of the file within the filesystem that will be exposed over NBD. The path must be absolute (starts with C</>). +=item B<ext2file=exportname> + +The plugin will expose the path within the filesystem chosen by the +exportname passed by the client. Note that this mode allows the +client to deduce which files exist within the disk image, which may be +a security risk in some situations. + =back =head1 FILES -- 2.24.1
Richard W.M. Jones
2020-Feb-12 13:51 UTC
Re: [Libguestfs] [nbdkit PATCH 3/3] ext2: Add mode for letting client exportname choose file from image
Looks OK to me, please push the whole thing so I can do a release soon. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-p2v converts physical machines to virtual machines. Boot with a live CD or over the network (PXE) and turn machines into KVM guests. http://libguestfs.org/virt-v2v