Richard W.M. Jones
2019-Aug-31 06:23 UTC
[Libguestfs] [PATCH libnbd] Add bindings for Rust language
Still not working, but I took the latest patch and: - rebased it against libnbd 1.0 - fixed it so it handles new args and cbargs The generator now runs without warnings. This patch doesn't handle optargs at all. In C these are converted to non-optional parameter. Rust doesn't (AFAIK) have optional or labelled arguments unfortunately. Rich.
Richard W.M. Jones
2019-Aug-31 06:23 UTC
[Libguestfs] [PATCH libnbd] Add bindings for Rust language
From: Martin Kletzander <mkletzan@redhat.com> PROBLEMS: - Does not handle optargs at all. - Multiple TODO items, see code. Signed-off-by: Martin Kletzander <mkletzan@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@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@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