[Libguestfs] [PATCH nbdkit] Add support for writing plugins in Rust.

Richard W.M. Jones rjones at redhat.com
Fri Feb 8 11:55:06 UTC 2019


---
 .gitignore                          |   3 +
 README                              |   4 +-
 TODO                                |  13 +++
 configure.ac                        |  12 +++
 docs/nbdkit-plugin.pod              |   6 +-
 docs/nbdkit.pod                     |   1 +
 plugins/rust/Cargo.toml.in          |  14 +++
 plugins/rust/Makefile.am            |  65 ++++++++++++
 plugins/rust/examples/ramdisk.rs    | 119 +++++++++++++++++++++
 plugins/rust/nbdkit-rust-plugin.pod |  99 ++++++++++++++++++
 plugins/rust/src/lib.rs             | 155 ++++++++++++++++++++++++++++
 11 files changed, 487 insertions(+), 4 deletions(-)

diff --git a/.gitignore b/.gitignore
index 91229c6..e1c767c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,6 +53,9 @@ Makefile.in
 /missing
 /nbdkit
 /plugins/example4/nbdkit-example4-plugin
+/plugins/rust/Cargo.lock
+/plugins/rust/Cargo.toml
+/plugins/rust/target
 /plugins/tar/nbdkit-tar-plugin
 /podwrapper.pl
 /server/nbdkit
diff --git a/README b/README
index 5e6c886..4a5ef4b 100644
--- a/README
+++ b/README
@@ -15,8 +15,8 @@ The key features are:
  * Well-documented, simple plugin API with a stable ABI guarantee.
    Lets you export “unconventional” block devices easily.
 
- * You can write plugins in C, Lua, Perl, Python, OCaml, Ruby, shell
-   script or Tcl.
+ * You can write plugins in C, Lua, Perl, Python, OCaml, Ruby, Rust,
+   shell script or Tcl.
 
  * Filters can be stacked in front of plugins to transform the output.
 
diff --git a/TODO b/TODO
index 48c43a3..81c8ca9 100644
--- a/TODO
+++ b/TODO
@@ -164,3 +164,16 @@ Build-related
   not play nicely with --prefix builds for a non-root user.
 
 * Port to Windows.
+
+Rust plugins
+------------
+
+* Consider supporting a more idiomatic style for writing Rust plugins.
+
+* Better documentation.
+
+* Add tests.
+
+* There is no attempt to ‘make install’ or otherwise package the
+  crate.  Since it looks as if Rust code is normally distributed as
+  source it's not clear what that would even mean.
diff --git a/configure.ac b/configure.ac
index d87abd4..a0ed770 100644
--- a/configure.ac
+++ b/configure.ac
@@ -545,6 +545,15 @@ AS_IF([test "x$OCAMLOPT" != "xno" && test "x$enable_ocaml" != "xno"],[
 AM_CONDITIONAL([HAVE_OCAML],[test "x$OCAMLOPT" != "xno" &&
                              test "x$ocaml_link_shared" = "xyes"])
 
+dnl For developing plugins in Rust, optional.
+AC_CHECK_PROG([CARGO],[cargo],[cargo],[no])
+AC_ARG_ENABLE([rust],
+    [AS_HELP_STRING([--disable-rust], [disable Rust plugin])],
+    [],
+    [enable_rust=yes])
+AM_CONDITIONAL([HAVE_RUST],
+               [test "x$CARGO" != "xno" && test "x$enable_ruby" != "xno"])
+
 dnl Check for Ruby, for embedding in the Ruby plugin.
 AC_CHECK_PROG([RUBY],[ruby],[ruby],[no])
 AC_ARG_ENABLE([ruby],
@@ -765,6 +774,7 @@ lang_plugins="\
         perl \
         python \
         ruby \
+        rust \
         sh \
         tcl \
         "
@@ -854,6 +864,8 @@ AC_CONFIG_FILES([Makefile
                  plugins/python/Makefile
                  plugins/random/Makefile
                  plugins/ruby/Makefile
+                 plugins/rust/Cargo.toml
+                 plugins/rust/Makefile
                  plugins/sh/Makefile
                  plugins/split/Makefile
                  plugins/streaming/Makefile
diff --git a/docs/nbdkit-plugin.pod b/docs/nbdkit-plugin.pod
index 72f6d40..a7a6a15 100644
--- a/docs/nbdkit-plugin.pod
+++ b/docs/nbdkit-plugin.pod
@@ -66,6 +66,7 @@ L<nbdkit-ocaml-plugin(3)>,
 L<nbdkit-perl-plugin(3)>,
 L<nbdkit-python-plugin(3)>,
 L<nbdkit-ruby-plugin(3)>,
+L<nbdkit-rust-plugin(3)>,
 L<nbdkit-sh-plugin(3)>,
 L<nbdkit-tcl-plugin(3)>.
 
@@ -950,8 +951,8 @@ which defines C<$(NBDKIT_PLUGINDIR)> in automake-generated Makefiles.
 =head1 WRITING PLUGINS IN OTHER PROGRAMMING LANGUAGES
 
 You can also write nbdkit plugins in Lua, OCaml, Perl, Python, Ruby,
-shell script or Tcl.  Other programming languages may be offered in
-future.
+Rust, shell script or Tcl.  Other programming languages may be offered
+in future.
 
 For more information see:
 L<nbdkit-lua-plugin(3)>,
@@ -959,6 +960,7 @@ L<nbdkit-ocaml-plugin(3)>,
 L<nbdkit-perl-plugin(3)>,
 L<nbdkit-python-plugin(3)>,
 L<nbdkit-ruby-plugin(3)>,
+L<nbdkit-rust-plugin(3)>,
 L<nbdkit-sh-plugin(3)>,
 L<nbdkit-tcl-plugin(3)>.
 
diff --git a/docs/nbdkit.pod b/docs/nbdkit.pod
index 1100b97..a9c64c7 100644
--- a/docs/nbdkit.pod
+++ b/docs/nbdkit.pod
@@ -517,6 +517,7 @@ L<nbdkit-ocaml-plugin(3)>,
 L<nbdkit-perl-plugin(3)>,
 L<nbdkit-python-plugin(3)>,
 L<nbdkit-ruby-plugin(3)>,
+L<nbdkit-rust-plugin(3)>,
 L<nbdkit-sh-plugin(3)>,
 L<nbdkit-tcl-plugin(3)>.
 
diff --git a/plugins/rust/Cargo.toml.in b/plugins/rust/Cargo.toml.in
new file mode 100644
index 0000000..f7a9f41
--- /dev/null
+++ b/plugins/rust/Cargo.toml.in
@@ -0,0 +1,14 @@
+[package]
+name = "nbdkit"
+version = "@VERSION@"
+authors = ["Richard W.M. Jones <rjones at redhat.com>"]
+edition = "2018"
+
+[dependencies]
+libc = "0.2"
+# lazy_static is used by the example.
+lazy_static = "1.2.0"
+
+[[example]]
+name = "ramdisk"
+crate-type = ["cdylib"]
diff --git a/plugins/rust/Makefile.am b/plugins/rust/Makefile.am
new file mode 100644
index 0000000..ccdb000
--- /dev/null
+++ b/plugins/rust/Makefile.am
@@ -0,0 +1,65 @@
+# nbdkit
+# Copyright (C) 2019 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 $(top_srcdir)/common-rules.mk
+
+EXTRA_DIST = \
+	Cargo.toml.in \
+	examples/ramdisk.rs \
+	nbdkit-rust-plugin.pod \
+	src/lib.rs
+
+if HAVE_RUST
+
+noinst_SCRIPTS = \
+	target/release/libnbdkit.rlib \
+	target/release/examples/libramdisk.so
+
+target/release/libnbdkit.rlib: Cargo.toml src/lib.rs
+	cargo build --release
+
+target/release/examples/libramdisk.so: Cargo.toml examples/ramdisk.rs
+	cargo build --release --example ramdisk
+
+if HAVE_POD
+
+man_MANS = nbdkit-rust-plugin.3
+CLEANFILES += $(man_MANS)
+
+nbdkit-rust-plugin.3: nbdkit-rust-plugin.pod
+	$(PODWRAPPER) --section=3 --man $@ \
+	    --html $(top_builddir)/html/$@.html \
+	    $<
+
+endif HAVE_POD
+
+endif
diff --git a/plugins/rust/examples/ramdisk.rs b/plugins/rust/examples/ramdisk.rs
new file mode 100644
index 0000000..b03a50c
--- /dev/null
+++ b/plugins/rust/examples/ramdisk.rs
@@ -0,0 +1,119 @@
+// nbdkit
+// Copyright (C) 2019 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.
+
+extern crate nbdkit;
+
+#[macro_use]
+extern crate lazy_static;
+
+use libc::*;
+use std::ptr;
+use std::os::raw::c_int;
+use std::sync::Mutex;
+
+use nbdkit::*;
+use nbdkit::ThreadModel::*;
+
+// The RAM disk.
+lazy_static! {
+    static ref DISK: Mutex<Vec<u8>> = Mutex::new (vec![0; 100 * 1024 * 1024]);
+}
+
+struct Handle {
+    // Box::new doesn't allocate anything unless we put some dummy
+    // fields here.  In a real implementation you would put per-handle
+    // data here as required.
+    _not_used: i32,
+}
+
+extern fn ramdisk_open (_readonly: c_int) -> *mut c_void {
+    let h = Handle {_not_used: 0};
+    let h = Box::new(h);
+    return Box::into_raw(h) as *mut c_void;
+}
+
+extern fn ramdisk_close (h: *mut c_void) {
+    let h = unsafe { Box::from_raw(h as *mut Handle) };
+    drop (h);
+}
+
+extern fn ramdisk_get_size (_h: *mut c_void) -> int64_t {
+    return DISK.lock().unwrap().capacity() as int64_t;
+}
+
+extern fn ramdisk_pread (_h: *mut c_void, buf: *mut c_char, count: uint32_t,
+                         offset: uint64_t, _flags: uint32_t) -> c_int {
+    let offset = offset as usize;
+    let count = count as usize;
+    let disk = DISK.lock().unwrap();
+    unsafe {
+        ptr::copy_nonoverlapping (&disk[offset], buf as *mut u8, count);
+    }
+    return 0;
+}
+
+extern fn ramdisk_pwrite (_h: *mut c_void, buf: *const c_char, count: uint32_t,
+                          offset: uint64_t, _flags: uint32_t) -> c_int {
+    let offset = offset as usize;
+    let count = count as usize;
+    let mut disk = DISK.lock().unwrap();
+    unsafe {
+        ptr::copy_nonoverlapping (buf as *const u8, &mut disk[offset], count);
+    }
+    return 0;
+}
+
+// Every plugin must define a public, C-compatible plugin_init
+// function which returns a pointer to an Plugin struct.
+#[no_mangle]
+pub extern fn plugin_init () -> *const Plugin {
+    // Plugin name.
+    // https://github.com/rust-lang/rfcs/issues/400
+    let name = "ramdisk\0" as *const str as *const [c_char] as *const c_char;
+
+    // Create a mutable plugin, setting the 5 required fields.
+    let mut plugin = Plugin::new (
+        Parallel,
+        name,
+        ramdisk_open,
+        ramdisk_get_size,
+        ramdisk_pread
+    );
+    // Update any other fields as required.
+    plugin.close = Some (ramdisk_close);
+    plugin.pwrite = Some (ramdisk_pwrite);
+
+    // Return the pointer.
+    let plugin = Box::new(plugin);
+    // XXX Memory leak.
+    return Box::into_raw(plugin);
+}
diff --git a/plugins/rust/nbdkit-rust-plugin.pod b/plugins/rust/nbdkit-rust-plugin.pod
new file mode 100644
index 0000000..d158762
--- /dev/null
+++ b/plugins/rust/nbdkit-rust-plugin.pod
@@ -0,0 +1,99 @@
+=head1 NAME
+
+nbdkit-rust-plugin - writing nbdkit plugins in Rust
+
+=head1 SYNOPSIS
+
+ nbdkit /path/to/libplugin.so [arguments...]
+
+=head1 DESCRIPTION
+
+This manual page describes how to write nbdkit plugins in compiled
+Rust code.  Rust plugins are compiled to F<*.so> files (the same as
+plugins written in C) and are used in the same way.
+
+=head1 WRITING A RUST NBDKIT PLUGIN
+
+Broadly speaking, Rust nbdkit plugins work like C ones, so you should
+read L<nbdkit-plugin(3)> first.
+
+You should also look at C<plugins/rust/src/lib.rs> and
+C<plugins/rust/examples/ramdisk.rs> in the nbdkit source tree, which
+describe the plugin interface for Rust plugins and provides an
+example.
+
+We may change how Rust plugins are written in future to make them more
+idiomatic.  At the moment each callback corresponds directly to a C
+callback - in fact each is called directly from the server.
+
+Your Rust code should define a public C<plugin_init> function which
+returns a pointer to a C<Plugin> struct.  This struct is exactly
+compatible with the C struct used by C plugins.
+
+ #[no_mangle]
+ pub extern fn plugin_init () -> *const Plugin {
+    // Plugin name.
+    let name = "myplugin\0"
+      as *const str as *const [c_char] as *const c_char;
+
+    // Create a mutable plugin, setting the 5 required fields.
+    let mut plugin = Plugin::new (
+        Serialize_All_Requests,
+        name,
+        myplugin_open,
+        myplugin_get_size,
+        myplugin_pread
+    );
+    // Update any other fields as required.
+    plugin.close = Some (myplugin_close);
+    plugin.pwrite = Some (myplugin_pwrite);
+
+    // Return the pointer.
+    let plugin = Box::new(plugin);
+    return Box::into_raw(plugin);
+}
+
+=head2 Compiling a Rust nbdkit plugin
+
+Because you are building a C-compatible shared library, the crate type
+must be set to:
+
+ crate-type = ["cdylib"]
+
+After compiling using C<cargo build> you can then use
+C<libmyplugin.so> as an nbdkit plugin (see L<nbdkit(1)>,
+L<nbdkit-plugin(3)>):
+
+ nbdkit ./libmyplugin.so [args ...]
+
+=head2 Threads
+
+The first parameter of C<Plugin::new> is the thread model, which can
+be one of the values in the table below.  For more information on
+thread models, see L<nbdkit-plugin(3)/THREADS>.
+
+=over 4
+
+=item C<Serialize_Connections>
+
+=item C<Serialize_All_Requests>
+
+=item C<Serialize_Requests>
+
+=item C<Parallel>
+
+=back
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-plugin(3)>,
+L<cargo(1)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones
+
+=head1 COPYRIGHT
+
+Copyright (C) 2019 Red Hat Inc.
diff --git a/plugins/rust/src/lib.rs b/plugins/rust/src/lib.rs
new file mode 100644
index 0000000..23aa204
--- /dev/null
+++ b/plugins/rust/src/lib.rs
@@ -0,0 +1,155 @@
+// nbdkit
+// Copyright (C) 2019 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.
+
+extern crate libc;
+
+use libc::*;
+use std::os::raw::c_int;
+use std::mem;
+
+// This struct describes the plugin ABI which your plugin_init()
+// function must return.
+#[repr(C)]
+pub struct Plugin {
+    _struct_size: uint64_t,
+    _api_version: c_int,
+    _thread_model: c_int,
+
+    pub name: *const c_char,
+    pub longname: *const c_char,
+    pub version: *const c_char,
+    pub description: *const c_char,
+
+    pub load: Option<extern fn ()>,
+    pub unload: Option<extern fn ()>,
+
+    pub config: Option<extern fn (*const c_char, *const c_char)>,
+    pub config_complete: Option<extern fn () -> c_int>,
+    pub config_help: *const c_char,
+
+    pub open: extern fn (c_int) -> *mut c_void,
+    pub close: Option<extern fn (*mut c_void)>,
+
+    pub get_size: extern fn (*mut c_void) -> int64_t,
+
+    pub can_write: Option<extern fn (*mut c_void) -> c_int>,
+    pub can_flush: Option<extern fn (*mut c_void) -> c_int>,
+    pub is_rotational: Option<extern fn (*mut c_void) -> c_int>,
+    pub can_trim: Option<extern fn (*mut c_void) -> c_int>,
+
+    // Slots for old v1 API functions.
+    _pread_old: Option<extern fn ()>,
+    _pwrite_old: Option<extern fn ()>,
+    _flush_old: Option<extern fn ()>,
+    _trim_old: Option<extern fn ()>,
+    _zero_old: Option<extern fn ()>,
+
+    errno_is_preserved: c_int,
+
+    pub dump_plugin: Option<extern fn ()>,
+
+    pub can_zero: Option<extern fn (*mut c_void) -> c_int>,
+    pub can_fua: Option<extern fn (*mut c_void) -> c_int>,
+
+    pub pread: extern fn (h: *mut c_void, buf: *mut c_char, count: uint32_t,
+                          offset: uint64_t,
+                          flags: uint32_t) -> c_int,
+    pub pwrite: Option<extern fn (h: *mut c_void, buf: *const c_char,
+                                  count: uint32_t, offset: uint64_t,
+                                  flags: uint32_t) -> c_int>,
+    pub flush: Option<extern fn (h: *mut c_void, flags: uint32_t) -> c_int>,
+    pub trim: Option<extern fn (h: *mut c_void,
+                                count: uint32_t, offset: uint64_t,
+                                flags: uint32_t) -> c_int>,
+    pub zero: Option<extern fn (h: *mut c_void,
+                                count: uint32_t, offset: uint64_t,
+                                flags: uint32_t) -> c_int>,
+
+    pub magic_config_key: *const c_char,
+
+    pub can_multi_conn: Option<extern fn (h: *mut c_void) -> c_int>,
+}
+
+pub enum ThreadModel {
+    SerializeConnections = 0,
+    SerializeAllRequests = 1,
+    SerializeRequests = 2,
+    Parallel = 3,
+}
+
+impl Plugin {
+    pub fn new (thread_model: ThreadModel,
+                name: *const c_char,
+                open: extern fn (c_int) -> *mut c_void,
+                get_size: extern fn (*mut c_void) -> int64_t,
+                pread: extern fn (h: *mut c_void, buf: *mut c_char,
+                                  count: uint32_t, offset: uint64_t,
+                                  flags: uint32_t) -> c_int) -> Plugin {
+        Plugin {
+            _struct_size: mem::size_of::<Plugin>() as uint64_t,
+            _api_version: 2,
+            _thread_model: thread_model as c_int,
+            name: name,
+            longname: std::ptr::null(),
+            version: std::ptr::null(),
+            description: std::ptr::null(),
+            load: None,
+            unload: None,
+            config: None,
+            config_complete: None,
+            config_help: std::ptr::null(),
+            open: open,
+            close: None,
+            get_size: get_size,
+            can_write: None,
+            can_flush: None,
+            is_rotational: None,
+            can_trim: None,
+            _pread_old: None,
+            _pwrite_old: None,
+            _flush_old: None,
+            _trim_old: None,
+            _zero_old: None,
+            errno_is_preserved: 0,
+            dump_plugin: None,
+            can_zero: None,
+            can_fua: None,
+            pread: pread,
+            pwrite: None,
+            flush: None,
+            trim: None,
+            zero: None,
+            magic_config_key: std::ptr::null(),
+            can_multi_conn: None,
+        }
+    }
+}
-- 
2.20.1




More information about the Libguestfs mailing list