From: Martin Kletzander <mkletzan(a)redhat.com>
PROBLEMS:
- Does not handle optargs at all.
- Multiple TODO items, see code.
Signed-off-by: Martin Kletzander <mkletzan(a)redhat.com>
---
.gitignore | 5 +
Makefile.am | 1 +
configure.ac | 16 ++
generator/generator | 342 ++++++++++++++++++++++++++++++
run.in | 9 +
rust/Cargo.toml | 6 +
rust/Makefile.am | 42 ++++
rust/libnbd-sys/Cargo.toml.in | 10 +
rust/libnbd-sys/build.rs | 9 +
rust/libnbd-sys/src/.gitkeep | 0
rust/libnbd/Cargo.toml.in | 9 +
rust/libnbd/examples/hello-nbd.rs | 7 +
rust/libnbd/src/lib.rs | 4 +
rust/libnbd/src/nbd_error.rs | 31 +++
rust/run-tests | 4 +
15 files changed, 495 insertions(+)
diff --git a/.gitignore b/.gitignore
index 53aa206..45c5b9f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -101,6 +101,11 @@ Makefile.in
/python/nbd.py
/python/run-python-tests
/run
+/rust/**/Cargo.lock
+/rust/*/Cargo.toml
+/rust/libnbd/src/glue.rs
+/rust/libnbd-sys/src/lib.rs
+/rust/target
/sh/nbdsh
/sh/nbdsh.1
/stamp-h1
diff --git a/Makefile.am b/Makefile.am
index 59918b9..d1960cb 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -42,6 +42,7 @@ SUBDIRS = \
ocaml/examples \
ocaml/tests \
interop \
+ rust \
$(NULL)
noinst_SCRIPTS = run
diff --git a/configure.ac b/configure.ac
index 6ea3197..5411c3f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -323,6 +323,19 @@ AS_IF([test "x$enable_python" != "xno"],[
AM_CONDITIONAL([HAVE_PYTHON],
[test "x$PYTHON" != "xno" && test
"x$have_python_module" = "x1" ])
+dnl Rust, optional, just runs tests
+AC_ARG_ENABLE([rust],
+ AS_HELP_STRING([--disable-rust], [disable Rust language binding tests]),
+ [],
+ [enable_rust=yes])
+AS_IF([test "x$enable_rust" != "xno"],
+ [AC_CHECK_PROG([CARGO],[cargo],[cargo],[no])])
+AS_IF([test "$CARGO" = "no"],
+ [AC_MSG_ERROR([cargo not found])])
+
+AM_CONDITIONAL([HAVE_RUST],
+ [test "$CARGO" != "no" && test "$enable_rust" =
"yes" ])
+
dnl Produce output files.
AC_CONFIG_HEADERS([config.h])
@@ -349,6 +362,9 @@ AC_CONFIG_FILES([Makefile
ocaml/examples/Makefile
ocaml/tests/Makefile
python/Makefile
+ rust/Makefile
+ rust/libnbd-sys/Cargo.toml
+ rust/libnbd/Cargo.toml
sh/Makefile
tests/Makefile
tests/functions.sh
diff --git a/generator/generator b/generator/generator
index 0d45ade..d374146 100755
--- a/generator/generator
+++ b/generator/generator
@@ -3267,6 +3267,11 @@ let () =
| name, { ret = RUInt; may_set_error = true } ->
failwithf "%s: if ret is RUInt, may_set_error must be false" name
+ (* may_set_error = false is obviously incompatible with RErr. *)
+ | name, { ret = RErr; may_set_error = false } ->
+ failwithf "%s: if ret is RErr, then may_set_error cannot be false"
+ name
+
(* !may_set_error is incompatible with certain parameters because
* we have to do a NULL-check on those which may return an error.
*)
@@ -5626,6 +5631,341 @@ end
(*----------------------------------------------------------------------*)
+(* Rust bindings. *)
+
+module Rust : sig
+ val generate_rust_sys_lib_rs : unit -> unit
+ val generate_rust_glue_rs : unit -> unit
+end = struct
+
+let rec pr_indent = function
+ | 0 -> ()
+ | n -> pr " "; pr_indent (n - 1)
+
+let rec print_rust_ffi_arg_list ?(handle = false) indent args =
+ let pri () = pr_indent indent in
+ let pri2 () = pr_indent (indent + 1) in
+ pr "(\n";
+ if handle then (
+ pri (); pr "h: *mut nbd_handle,\n";
+ );
+ List.iter (
+ function
+ | Bool n -> pri (); pr "%s: bool,\n" n
+ | BytesIn (n, len)
+ | BytesPersistIn (n, len) ->
+ pri (); pr "%s: *const c_void,\n" n;
+ pri (); pr "%s: usize,\n" len
+ | BytesOut (n, len)
+ | BytesPersistOut (n, len) ->
+ pri (); pr "%s: *mut c_void,\n" n;
+ pri (); pr "%s: usize,\n" len
+ | Closure {cbname; cbargs} ->
+ pri (); pr "%s: Option<\n" cbname;
+ pri2 (); pr "unsafe extern \"C\" fn";
+ print_rust_ffi_cbarg_list (indent + 2) cbargs;
+ pr " -> c_int,\n";
+ pri (); pr ">,\n"
+ | Enum (n, _) -> pr "%s: c_int,\n" n
+ | Flags (n, _) -> pri (); pr "%s: u32,\n" n
+ | Int n -> pri (); pr "%s: c_int,\n" n
+ | Int64 n -> pri (); pr "%s: i64,\n" n
+ | Path n
+ | String n -> pri (); pr "%s: *const c_char,\n" n
+ | StringList n -> pri (); pr "%s: *mut *mut c_char,\n" n
+ | SockAddrAndLen (n, len) ->
+ pri (); pr "%s: *const libc::sockaddr,\n" n;
+ pri (); pr "%s: libc::socklen_t,\n" len
+ | UInt n -> pri (); pr "%s: c_uint,\n" n
+ | UInt32 n -> pri (); pr "%s: u32,\n" n
+ | UInt64 n -> pri (); pr "%s: u64,\n" n
+ ) args;
+ pr_indent (indent - 1); pr ")"
+
+and print_rust_ffi_cbarg_list indent cbargs =
+ let pri () = pr_indent indent in
+ pr "(\n";
+ List.iter (
+ function
+ | CBArrayAndLen (UInt32 n, len) ->
+ pri (); pr "%s: *mut u32,\n" n;
+ pri (); pr "%s: usize,\n" len
+ | CBArrayAndLen _ -> assert false
+ | CBBytesIn (n, len) ->
+ pri (); pr "%s: *const c_void,\n" n;
+ pri (); pr "%s: usize,\n" len
+ | CBInt n -> pri (); pr "%s: c_int,\n" n
+ | CBInt64 n -> pri (); pr "%s: i64,\n" n
+ | CBMutable (Int n) ->
+ pri (); pr "%s: *mut c_int,\n" n
+ | CBMutable _ -> assert false
+ | CBString n -> pri (); pr "%s: *const c_char,\n" n
+ | CBUInt n -> pri (); pr "%s: c_uint,\n" n
+ | CBUInt64 n -> pri (); pr "%s: u64,\n" n
+ ) cbargs;
+ pr_indent (indent - 1); pr ")"
+
+let print_rust_arg_list ?(handle = false) indent args =
+ let pri () = pr_indent indent in
+ let pri2 () = pr_indent (indent + 1) in
+ pr "(\n";
+ if handle then (
+ pri (); pr "&self,\n";
+ );
+ List.iter (
+ function
+ (* TODO: rewrite to use Rust types *)
+ | Bool n -> pri (); pr "%s: bool,\n" n
+ | BytesIn (n, len)
+ | BytesPersistIn (n, len) ->
+ pri (); pr "%s: *const c_void,\n" n;
+ pri (); pr "%s: usize,\n" len
+ | BytesOut (n, len)
+ | BytesPersistOut (n, len) ->
+ pri (); pr "%s: *mut c_void,\n" n;
+ pri (); pr "%s: usize,\n" len
+ | Closure {cbname; cbargs} ->
+ pri (); pr "%s: Option<\n" cbname;
+ pri2 (); pr "unsafe extern \"C\" fn";
+ print_rust_ffi_cbarg_list (indent + 2) cbargs;
+ pr " -> c_int,\n";
+ pri (); pr ">,\n"
+ | Enum (n, _) -> pri (); pr "%s: c_int,\n" n
+ | Flags (n, _) -> pri (); pr "%s: u32,\n" n
+ | Int n -> pri (); pr "%s: c_int,\n" n
+ | Int64 n -> pri (); pr "%s: i64,\n" n
+ | Path n
+ | String n -> pri (); pr "%s: *const c_char,\n" n
+ | StringList n -> pri (); pr "%s: *mut *mut c_char,\n" n
+ | SockAddrAndLen (n, len) ->
+ pri (); pr "%s: *const libc::sockaddr,\n" n;
+ pri (); pr "%s: libc::socklen_t" len
+ | UInt n -> pri (); pr "%s: c_uint,\n" n
+ | UInt32 n -> pri (); pr "%s: u32,\n" n
+ | UInt64 n -> pri (); pr "%s: u64,\n" n
+ ) args;
+ pr_indent (indent - 1); pr ")"
+
+let generate_rust_sys_lib_rs () =
+ let print_extern (name, {args; ret; _ }) =
+ let ret_rs_type =
+ match ret with
+ | RBool
+ | RErr
+ | RFd
+ | RInt -> "c_int"
+ | RUInt -> "c_uint"
+ | RStaticString -> "*const c_char"
+ | RInt64 | RCookie -> "i64"
+ | RString -> "*mut c_char"
+ in
+
+ (* TODO: print shortdesc and longdesc *)
+ pr " pub fn nbd_%s" name;
+ print_rust_ffi_arg_list ~handle:true 2 args;
+ pr " -> %s;\n" ret_rs_type
+ in
+
+ generate_header CStyle;
+
+ (* TODO: global documentation *)
+
+ pr "#[allow(unused_imports)]\n";
+ pr "use std::os::raw::{c_char, c_int, c_uint, c_void};\n";
+ pr "\n";
+
+ (* TODO: print constants *)
+ (* TODO: print constants for metadata namespaces *)
+
+ pr "#[link(name = \"nbd\")]";
+ pr "extern \"C\" {\n";
+ pr " pub fn nbd_create() -> *mut nbd_handle;\n";
+ pr " pub fn nbd_close(handle: *mut nbd_handle);\n";
+ pr " pub fn nbd_add_close_callback(\n";
+ pr " handle: *mut nbd_handle,\n";
+ pr " callback: Option<unsafe extern \"C\" fn(*mut
c_void)>,\n";
+ pr " ) -> c_int;\n";
+ pr "";
+ pr " pub fn nbd_get_error() -> *const c_char;\n";
+ pr " pub fn nbd_get_errno() -> c_int;\n";
+ pr "\n";
+ List.iter print_extern handle_calls;
+ pr "}\n";
+ pr "\n";
+ pr "#[repr(C)]\n";
+ pr "#[derive(Debug, Copy, Clone)]\n";
+ pr "pub struct nbd_handle {\n";
+ pr " _unused: [u8; 0],\n";
+ pr "}\n"
+
+let rust_name_of_arg = function
+ | Bool n -> n
+ | BytesIn (n, len) -> n
+ | BytesOut (n, len) -> n
+ | BytesPersistIn (n, len) -> n
+ | BytesPersistOut (n, len) -> n
+ | Closure { cbname } -> cbname
+ | Enum (n, _) -> n
+ | Flags (n, _) -> n
+ | Int n -> n
+ | Int64 n -> n
+ | Path n -> n
+ | SockAddrAndLen (n, len) -> n
+ | String n -> n
+ | StringList n -> n
+ | UInt n -> n
+ | UInt32 n -> n
+ | UInt64 n -> n
+
+let generate_rust_glue_rs () =
+ let print_wrapper (name, {args; ret; permitted_states;
+ is_locked; may_set_error}) =
+ let ret_rs_type =
+ let typ =
+ match ret with
+ | RBool -> "bool"
+ | RErr -> "()"
+ | RFd -> "RawFd"
+ | RInt -> "i32"
+ | RUInt -> "u32"
+ | RStaticString -> "&'static str"
+ | RInt64 | RCookie -> "i64"
+ | RString -> "String" in
+ if may_set_error then "Result<" ^ typ ^ ", NbdError>"
else typ
+ in
+
+ (* TODO: at least shortdesc *)
+ pr "\n pub fn %s" name;
+ print_rust_arg_list ~handle:true 2 args;
+ pr " -> %s {\n" ret_rs_type;
+
+ let num = ref 0 in
+ let arg_transform arg num =
+ (* TODO: transform Rust types to C types *)
+ match arg with
+ | Bool n -> [], 0
+ | BytesIn (n, _) | BytesPersistIn (n, _) -> [""], 2
+ | BytesPersistOut (n, _) -> [""], 2
+ | BytesOut (_, count) -> [""], 2
+ | Closure _ -> [""], 2
+ | Enum (n, _) -> [""], 1
+ | Flags (n, _) -> [""], 1
+ | Int n -> [""], 1
+ | Int64 n -> [""], 1
+ | Path n -> [""], 1
+ | SockAddrAndLen (n, _) -> [""], 2
+ | String n -> [""], 1
+ | StringList n -> [""], 1
+ | UInt n -> [""], 1
+ | UInt32 n -> [""], 1
+ | UInt64 n -> [""], 1
+ in
+
+ let before = List.flatten (List.map (fun arg ->
+ let code, n = arg_transform arg num in
+ num := !num + n;
+ code
+ ) args)
+ in
+
+ let ret_transform ret =
+ let err_chk =
+ [(match ret with
+ | RBool
+ | RErr
+ | RFd
+ | RInt
+ | RInt64
+ | RUInt
+ | RCookie -> "if ret == -1 {"
+ | RStaticString
+ | RString -> "if ret.is_null() {");
+ " return Err(NbdError::from_libnbd());";
+ "}";
+ ]
+ in
+ let trans =
+ match ret with
+ | RBool
+ | RErr
+ | RFd
+ | RInt
+ | RInt64
+ | RUInt
+ | RCookie -> []
+ | RStaticString -> [
+ "let ret = unsafe { CStr::from_ptr(ret) };";
+ "let ret = ret.to_str().unwrap();";
+ ]
+ | RString -> [
+ "let c_str = unsafe { CStr::from_ptr(ret as *const c_char) };";
+ "let ret = c_str.to_string_lossy().into_owned();";
+ "unsafe { libc::free(c_str.as_ptr() as *mut c_void) }; ";
+ ]
+ in
+ let real_ret =
+ match ret with
+ | RBool -> "ret != 0";
+ | RErr -> "()"
+ | RFd
+ | RInt
+ | RInt64
+ | RUInt
+ | RCookie
+ | RStaticString
+ | RString -> "ret"
+ in
+ (if may_set_error then err_chk else []) @
+ trans @
+ [(if may_set_error then "Ok(" ^ real_ret ^ ")" else real_ret)]
+ in
+
+ List.iter (fun str -> pr " %s\n" str) before;
+ pr " let ret = unsafe { nbd_%s (self.handle" name;
+ let argnames = List.map rust_name_of_arg args in
+ List.iter (pr ", %s") argnames;
+ pr ") };\n";
+ List.iter (fun str -> pr " %s\n" str) (ret_transform ret);
+ pr " }\n";
+ in
+
+ generate_header CStyle;
+
+ pr "#[allow(unused_imports)]\n";
+ pr "use std::os::raw::{c_char, c_int, c_uint, c_void};\n";
+ pr "use std::os::unix::io::RawFd;\n";
+ pr "use std::ffi::CStr;\n";
+ pr "use libnbd_sys::*;\n";
+ pr "use libc;\n";
+ pr "\n";
+ pr "pub use super::nbd_error::*;\n";
+ pr "\n";
+
+ pr "pub struct Nbd {\n";
+ pr " handle: *mut nbd_handle,\n";
+ pr "}\n";
+ pr "\n";
+ pr "impl Nbd {\n";
+ pr " pub fn create() -> Result<Self, NbdError> {\n";
+ pr " let handle = unsafe { nbd_create() };\n";
+ pr " if handle.is_null() {\n";
+ pr " return Err(NbdError::from_libnbd());\n";
+ pr " }\n";
+ pr " Ok(Self { handle: handle })\n";
+ pr " }\n";
+
+ List.iter print_wrapper handle_calls;
+ pr "}\n";
+
+ pr "impl Drop for Nbd {\n";
+ pr " fn drop(&mut self) {\n";
+ pr " unsafe { nbd_close(self.handle);\n";
+ pr " }\n";
+ pr "}\n"
+end
+
+(*----------------------------------------------------------------------*)
+
(* Write the output files. *)
let () =
output_to "lib/states.h" StateMachine.generate_lib_states_h;
@@ -5649,3 +5989,5 @@ let () =
output_to "ocaml/NBD.mli" OCaml.generate_ocaml_nbd_mli;
output_to "ocaml/NBD.ml" OCaml.generate_ocaml_nbd_ml;
output_to "ocaml/nbd-c.c" OCaml.generate_ocaml_nbd_c;
+ output_to "rust/libnbd-sys/src/lib.rs" Rust.generate_rust_sys_lib_rs;
+ output_to "rust/libnbd/src/glue.rs" Rust.generate_rust_glue_rs
diff --git a/run.in b/run.in
index 83c92a7..e62b757 100755
--- a/run.in
+++ b/run.in
@@ -107,5 +107,14 @@ fi
export GNOME_KEYRING_CONTROL=
export GNOME_KEYRING_PID=
+# For rust
+export RUST="@RUST@"
+if [ -z "$RUSTFLAGS" ]; then
+ RUSTFLAGS="-C link-args=-L$b/lib/.libs"
+else
+ RUSTFLAGS="$RUSTFLAGS -C link-args=-L$b/lib/.libs"
+fi
+export RUSTFLAGS
+
# Run the program.
exec $libtool $valgrind "$@"
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
new file mode 100644
index 0000000..8774460
--- /dev/null
+++ b/rust/Cargo.toml
@@ -0,0 +1,6 @@
+[workspace]
+
+members = [
+ "libnbd-sys",
+ "libnbd",
+]
diff --git a/rust/Makefile.am b/rust/Makefile.am
new file mode 100644
index 0000000..ca2974e
--- /dev/null
+++ b/rust/Makefile.am
@@ -0,0 +1,42 @@
+# nbd client library in userspace
+# Copyright (C) 2013-2019 Red Hat Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+include $(top_srcdir)/subdir-rules.mk
+
+clean-local:
+ -cargo clean
+
+generator_built = \
+ src/ffi.rs
+
+EXTRA_DIST = \
+ $(generator_built) \
+ Cargo.toml.in
+
+if HAVE_RUST
+
+all: target/release/libnbd-sys.rlib target/release/examples/hello-nbd
+
+target/release/libnbd-sys.rlib:
+ $(top_builddir)/run cargo build --release
+
+target/release/examples/hello-nbd: target/release/libnbd-sys.rlib
+ $(top_builddir)/run cargo build --release --example hello-nbd
+
+TESTS = run-tests
+
+endif HAVE_RUST
diff --git a/rust/libnbd-sys/Cargo.toml.in b/rust/libnbd-sys/Cargo.toml.in
new file mode 100644
index 0000000..684510f
--- /dev/null
+++ b/rust/libnbd-sys/Cargo.toml.in
@@ -0,0 +1,10 @@
+[package]
+name = "libnbd-sys"
+version = "@VERSION@"
+authors = ["Martin Kletzander <mkletzan(a)redhat.com>"]
+edition = "2018"
+links = "nbd"
+build = "build.rs"
+
+[dependencies]
+libc = "^0.2.58"
diff --git a/rust/libnbd-sys/build.rs b/rust/libnbd-sys/build.rs
new file mode 100644
index 0000000..acfdf8e
--- /dev/null
+++ b/rust/libnbd-sys/build.rs
@@ -0,0 +1,9 @@
+/*
+ * We have to have a build.rs script in order to use `links` in Cargo.toml.
+ * Ideally this should figure out whether we can build with a library installed
+ * in the system and if not, it should add C files to the build and build it
+ * locally or basically build it without the library being installed in the
+ * system.
+ */
+
+fn main() {}
diff --git a/rust/libnbd-sys/src/.gitkeep b/rust/libnbd-sys/src/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/rust/libnbd/Cargo.toml.in b/rust/libnbd/Cargo.toml.in
new file mode 100644
index 0000000..6ba1296
--- /dev/null
+++ b/rust/libnbd/Cargo.toml.in
@@ -0,0 +1,9 @@
+[package]
+name = "libnbd"
+version = "@VERSION@"
+authors = ["Martin Kletzander <mkletzan(a)redhat.com>"]
+edition = "2018"
+
+[dependencies]
+libc = "^0.2.58"
+libnbd-sys = { version = "^@VERSION@", path = "../libnbd-sys" }
diff --git a/rust/libnbd/examples/hello-nbd.rs b/rust/libnbd/examples/hello-nbd.rs
new file mode 100644
index 0000000..5d62790
--- /dev/null
+++ b/rust/libnbd/examples/hello-nbd.rs
@@ -0,0 +1,7 @@
+use libnbd::*;
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+ let nbd = Nbd::create()?;
+ println!("libnbd version is {}", nbd.get_version());
+ Ok(())
+}
diff --git a/rust/libnbd/src/lib.rs b/rust/libnbd/src/lib.rs
new file mode 100644
index 0000000..b69b7c1
--- /dev/null
+++ b/rust/libnbd/src/lib.rs
@@ -0,0 +1,4 @@
+mod nbd_error;
+
+mod glue;
+pub use glue::*;
diff --git a/rust/libnbd/src/nbd_error.rs b/rust/libnbd/src/nbd_error.rs
new file mode 100644
index 0000000..0a7f667
--- /dev/null
+++ b/rust/libnbd/src/nbd_error.rs
@@ -0,0 +1,31 @@
+use libnbd_sys::{nbd_get_errno, nbd_get_error};
+use std::ffi::CStr;
+use std::fmt;
+
+#[derive(Debug, Copy, Clone)]
+pub struct NbdError {
+ errno: i32,
+ strerr: &'static CStr,
+}
+
+impl NbdError {
+ pub fn from_libnbd() -> Self {
+ Self {
+ errno: unsafe { nbd_get_errno() },
+ strerr: unsafe { CStr::from_ptr(nbd_get_error()) },
+ }
+ }
+}
+
+impl std::error::Error for NbdError {}
+
+impl fmt::Display for NbdError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "NBD Error (errno = {}): {}",
+ self.errno,
+ self.strerr.to_string_lossy()
+ )
+ }
+}
diff --git a/rust/run-tests b/rust/run-tests
new file mode 100755
index 0000000..d1e7b58
--- /dev/null
+++ b/rust/run-tests
@@ -0,0 +1,4 @@
+#!/bin/sh
+set -e
+
+$CARGO test
--
2.23.0