[Libguestfs] [PATCH libnbd] Add bindings for Rust language

Richard W.M. Jones rjones at redhat.com
Sat Aug 31 06:23:42 UTC 2019


From: Martin Kletzander <mkletzan at redhat.com>

PROBLEMS:
 - Does not handle optargs at all.
 - Multiple TODO items, see code.

Signed-off-by: Martin Kletzander <mkletzan at 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 at 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 at 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




More information about the Libguestfs mailing list