Permalink
Browse files

Add support for writing plugins in Rust.

  • Loading branch information...
rwmjones committed Feb 7, 2019
1 parent 14ee9e8 commit 617b64f0376d3fbef976f5ce869a469160df9efe
Showing with 494 additions and 4 deletions.
  1. +3 −0 .gitignore
  2. +6 −2 README
  3. +13 −0 TODO
  4. +12 −0 configure.ac
  5. +2 −2 docs/nbdkit-plugin.pod
  6. +14 −0 plugins/rust/Cargo.toml.in
  7. +65 −0 plugins/rust/Makefile.am
  8. +119 −0 plugins/rust/examples/ramdisk.rs
  9. +105 −0 plugins/rust/nbdkit-rust-plugin.pod
  10. +155 −0 plugins/rust/src/lib.rs
@@ -54,6 +54,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
8 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.

@@ -129,6 +129,10 @@ For the Lua plugin:

- Lua development library and headers

For the Rust plugin:

- cargo (other dependencies will be downloaded at build time)

For bash tab completion:

- bash-completion >= 1.99
13 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.
@@ -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_rust" != "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
@@ -944,8 +944,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:
__LANG_PLUGIN_LINKS__.
@@ -0,0 +1,14 @@
[package]
name = "nbdkit"
version = "@VERSION@"
authors = ["Richard W.M. Jones <rjones@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"]
@@ -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
@@ -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 a 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);
}
@@ -0,0 +1,105 @@
=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
L<https://github.com/libguestfs/nbdkit/blob/master/plugins/rust/src/lib.rs>
and
L<https://github.com/libguestfs/nbdkit/blob/master/plugins/rust/examples/ramdisk.rs>
in the nbdkit source tree. The first describes the plugin interface
for Rust plugins and the second provides a simple 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.

extern crate nbdkit;
use nbdkit::*;
use nbdkit::ThreadModel::*;

#[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<nbdkit::ThreadModel::Serialize_Connections>

=item C<nbdkit::ThreadModel::Serialize_All_Requests>

=item C<nbdkit::ThreadModel::Serialize_Requests>

=item C<nbdkit::ThreadModel::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.
Oops, something went wrong.

0 comments on commit 617b64f

Please sign in to comment.