---
.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(a)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