From: Tage Johansson <frans.tage(a)gmail.com>
This commit creates basic Rust bindings in the rust directory.
The bindings are generated by generator/Rust.ml and generator/Rust.mli.
No tests are created so far.
In rust/libnbd-sys, [
rust-bindgen](https://github.com/rust-lang/rust-bindgen)
is used to generate low level Rust bindings to libnbd.h. This requires Clang,
see [this link](https://rust-lang.github.io/rust-bindgen/requirements.html#clang).
Ultimately, we shall generate the low level bindings without rust-bindgen in
the future so that Clang would not be required.
Apart from Clang, you would need Cargo with Rustdoc and Rustfmt to build
the Rust bindings. See [
here](https://www.rust-lang.org/tools/install)
for installation instructions.
---
Makefile.am | 1 +
configure.ac | 28 ++
generator/Makefile.am | 2 +
generator/Rust.ml | 529 +++++++++++++++++++++++++++++++++++++
generator/Rust.mli | 20 ++
generator/generator.ml | 2 +
generator/utils.ml | 9 +-
generator/utils.mli | 3 +-
rust/.gitignore | 3 +
rust/Cargo.toml | 48 ++++
rust/Makefile.am | 60 +++++
rust/cargo_test/.gitignore | 2 +
rust/cargo_test/Cargo.toml | 23 ++
rust/cargo_test/README.md | 3 +
rust/cargo_test/src/lib.rs | 31 +++
rust/libnbd-sys/.gitignore | 4 +
rust/libnbd-sys/Cargo.toml | 30 +++
rust/libnbd-sys/build.rs | 60 +++++
rust/libnbd-sys/src/lib.rs | 24 ++
rust/libnbd-sys/wrapper.h | 18 ++
rust/run-tests.sh | 24 ++
rust/src/error.rs | 47 ++++
rust/src/handle.rs | 53 ++++
rust/src/lib.rs | 28 ++
rust/src/types.rs | 18 ++
rust/src/utils.rs | 23 ++
rustfmt.toml | 18 ++
27 files changed, 1109 insertions(+), 2 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index 243fabd..9e1790b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -55,6 +55,7 @@ SUBDIRS = \
ocaml/tests \
golang \
golang/examples \
+ rust \
interop \
fuzzing \
bash-completion \
diff --git a/configure.ac b/configure.ac
index 6ac2862..b8e93ad 100644
--- a/configure.ac
+++ b/configure.ac
@@ -613,6 +613,32 @@ AS_IF([test "x$enable_golang" != "xno"],[
],[GOLANG=no])
AM_CONDITIONAL([HAVE_GOLANG],[test "x$GOLANG" != "xno"])
+dnl Rust.
+AC_ARG_ENABLE([rust],
+ AS_HELP_STRING([--disable-rust], [disable Rust language bindings]),
+ [],
+ [enable_rust=yes])
+AS_IF([test "x$enable_rust" != "xno"],[
+ AC_CHECK_PROG([CARGO],[cargo],[cargo],[no])
+ AC_CHECK_PROG([RUSTFMT],[rustfmt],[rustfmt],[no])
+ AS_IF([test "x$CARGO" != "xno"],[
+ AC_MSG_CHECKING([if $CARGO is usable])
+ AS_IF([ (
+ cd $srcdir/rust/cargo_test &&
+ $CARGO test 2>&AS_MESSAGE_LOG_FD 1>&2 &&
+ $CARGO doc 2>&AS_MESSAGE_LOG_FD 1>&2 &&
+ $CARGO fmt 2>&AS_MESSAGE_LOG_FD 1>&2
+ ) ],[
+ AC_MSG_RESULT([yes])
+ ],[
+ AC_MSG_RESULT([no])
+ AC_MSG_WARN([Rust ($CARGO) is installed but not usable])
+ CARGO=no
+ ])
+ ])
+],[CARGO=no])
+AM_CONDITIONAL([HAVE_RUST],[test "x$CARGO" != "xno" -a
"x$RUSTFMT" != "xno"])
+
AC_MSG_CHECKING([for how to mark DSO non-deletable at runtime])
NODELETE=
`$LD --help 2>&1 | grep -- "-z nodelete" >/dev/null` && \
@@ -657,6 +683,7 @@ AC_CONFIG_FILES([Makefile
generator/Makefile
golang/Makefile
golang/examples/Makefile
+ rust/Makefile
include/Makefile
info/Makefile
interop/Makefile
@@ -717,6 +744,7 @@ echo
echo "Language bindings:"
echo
feature "Go" test "x$HAVE_GOLANG_TRUE" =
"x"
+feature "Rust" test "x$HAVE_RUST_TRUE" =
"x"
feature "OCaml" test "x$HAVE_OCAML_TRUE" =
"x"
feature "Python" test "x$HAVE_PYTHON_TRUE" =
"x"
diff --git a/generator/Makefile.am b/generator/Makefile.am
index 91dbde5..cc1f9a4 100644
--- a/generator/Makefile.am
+++ b/generator/Makefile.am
@@ -60,6 +60,8 @@ sources = \
OCaml.ml \
GoLang.mli \
GoLang.ml \
+ Rust.mli \
+ Rust.ml \
generator.ml \
$(NULL)
diff --git a/generator/Rust.ml b/generator/Rust.ml
new file mode 100644
index 0000000..2493467
--- /dev/null
+++ b/generator/Rust.ml
@@ -0,0 +1,529 @@
+(* hey emacs, this is OCaml code: -*- tuareg -*- *)
+(* nbd client library in userspace: generator
+ * Copyright Tage Johansson
+ *
+ * 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
+ *)
+
+(* Rust language bindings. *)
+
+open Printf
+open API
+open Utils
+
+(* The type for a set of names. *)
+module NameSet = Set.Make (String)
+
+(* List of handle calls which should not be part of the public API. This could
+ for instance be `set_debug` and `set_debug_callback` which are handled
+ separately by the log crate *)
+let hidden_handle_calls : NameSet.t =
+ NameSet.of_list
+ [ "get_debug"; "set_debug"; "set_debug_callback";
"clear_debug_callback" ]
+
+let print_rust_constant (name, value) =
+ pr "pub const %s: u32 = %d;\n" name value
+
+let print_rust_enum (enum : enum) =
+ pr "#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n";
+ pr "#[repr(isize)]";
+ pr "pub enum %s {\n" (camel_case enum.enum_prefix);
+ List.iter
+ (fun (name, num) -> pr " %s = %d,\n" (camel_case name) num)
+ enum.enums;
+ pr "}\n\n"
+
+(* Print a Rust struct for a set of flags. *)
+let print_rust_flags ({ flag_prefix; flags } : flags) =
+ pr "bitflags! {\n";
+ pr " #[repr(C)]\n";
+ pr " #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n";
+ pr " pub struct %s: u32 {\n" (camel_case flag_prefix);
+ List.iter
+ (fun (name, value) -> pr " const %s = %d;\n" name value)
+ flags;
+ pr " }\n";
+ pr "}\n\n"
+
+(* Get the name of a rust argument. *)
+let rust_arg_name : arg -> string = function
+ | Bool n
+ | Int n
+ | UInt n
+ | UIntPtr n
+ | UInt32 n
+ | Int64 n
+ | UInt64 n
+ | SizeT n
+ | String n
+ | StringList n
+ | Path n
+ | Fd n
+ | Enum (n, _)
+ | Flags (n, _)
+ | SockAddrAndLen (n, _)
+ | BytesIn (n, _)
+ | BytesPersistIn (n, _)
+ | BytesOut (n, _)
+ | BytesPersistOut (n, _)
+ | Closure { cbname = n } ->
+ n
+
+(* Get the name of a rust optional argument. *)
+let rust_optarg_name : optarg -> string = function
+ | OClosure { cbname = n } | OFlags (n, _, _) -> n
+
+(* Get the name of a Rust closure argument. *)
+let rust_cbarg_name : cbarg -> string = function
+ | CBInt n | CBUInt n | CBInt64 n | CBUInt64 n | CBString n | CBBytesIn (n, _)
+ ->
+ n
+ | CBArrayAndLen (arg, _) | CBMutable arg -> rust_arg_name arg
+
+(* Get the Rust type for an argument. *)
+let rec rust_arg_type : arg -> string = function
+ | Bool _ -> "bool"
+ | Int _ -> "c_int"
+ | UInt _ -> "c_uint"
+ | UIntPtr _ -> "usize"
+ | UInt32 _ -> "u32"
+ | Int64 _ -> "i64"
+ | UInt64 _ -> "u64"
+ | SizeT _ -> "usize"
+ | String _ -> "&CStr"
+ | SockAddrAndLen _ -> "SocketAddr"
+ | StringList _ -> "&[&CStr]"
+ | Path _ -> "&PathBuf"
+ | Enum (_, { enum_prefix = name }) | Flags (_, { flag_prefix = name }) ->
+ camel_case name
+ | Fd _ -> "OwnedFd"
+ | BytesIn _ -> "&[u8]"
+ | BytesOut _ -> "&mut [u8]"
+ (* XXX: These should probably be changed to something but static slices. *)
+ | BytesPersistIn _ -> "&'static [u8]"
+ | BytesPersistOut _ -> "&'static mut [u8]"
+ | Closure { cbargs } -> "impl " ^ rust_closure_trait cbargs
+
+(* Get the Rust closure trait for a callback, That is `FnMut(...) -> ...)`. *)
+and rust_closure_trait (cbargs : cbarg list) : string =
+ let rust_cbargs = String.concat ", " (List.map rust_cbarg_type cbargs) in
+ "FnMut(" ^ rust_cbargs ^ ") -> Result<(), ()>"
+
+(* Get the Rust type for a callback argument. *)
+and rust_cbarg_type : cbarg -> string = function
+ | CBInt n -> rust_arg_type (Int n)
+ | CBUInt n -> rust_arg_type (UInt n)
+ | CBInt64 n -> rust_arg_type (Int64 n)
+ | CBUInt64 n -> rust_arg_type (UInt64 n)
+ | CBString n -> rust_arg_type (String n)
+ | CBBytesIn (n1, n2) -> rust_arg_type (BytesIn (n1, n2))
+ | CBArrayAndLen (elem, _) -> "&[" ^ rust_arg_type elem ^
"]"
+ | CBMutable arg -> "&mut " ^ rust_arg_type arg
+
+(* Get the type of a rust optional argument. *)
+let rust_optarg_type : optarg -> string = function
+ | OClosure x -> sprintf "Option<%s>" (rust_arg_type (Closure x))
+ | OFlags (name, flags, _) ->
+ sprintf "Option<%s>" (rust_arg_type (Flags (name, flags)))
+
+(* Given an argument, produce a list of names for arguments in FFI functions
+ corresponding to that argument. Most arguments will just produce one name for
+ one FFI argument, but for example [BytesIn] requires two separate FFI
+ arguments hence a list is produced. *)
+let ffi_arg_names : arg -> string list = function
+ | Bool n
+ | Int n
+ | UInt n
+ | UIntPtr n
+ | UInt32 n
+ | Int64 n
+ | UInt64 n
+ | SizeT n
+ | String n
+ | StringList n
+ | Path n
+ | Fd n
+ | Enum (n, _)
+ | Flags (n, _)
+ | Closure { cbname = n } ->
+ [ n ^ "_ffi" ]
+ | SockAddrAndLen (n1, n2)
+ | BytesIn (n1, n2)
+ | BytesPersistIn (n1, n2)
+ | BytesOut (n1, n2)
+ | BytesPersistOut (n1, n2) ->
+ [ n1 ^ "_ffi"; n2 ^ "_ffi" ]
+
+let ffi_optarg_name : optarg -> string = function
+ | OClosure { cbname = name } | OFlags (name, _, _) -> name ^ "_ffi"
+
+(* Given a closure argument, produce a list of names used by FFI functions for
+ that particular argument. Most closure arguments will just produce one FFI
+ argument, but for instance [CBArrayAndLen] will produce two, hence we return
+ a list. *)
+let ffi_cbarg_names : cbarg -> string list = function
+ | CBInt n | CBUInt n | CBInt64 n | CBUInt64 n | CBString n -> [ n ^ "_ffi"
]
+ | CBBytesIn (n1, n2) -> [ n1 ^ "_ffi"; n2 ^ "_ffi" ]
+ | CBArrayAndLen (arg, len) -> [ rust_arg_name arg ^ "_ffi"; len ^
"_ffi" ]
+ | CBMutable arg -> [ rust_arg_name arg ^ "_ffi" ]
+
+(* Given a closure argument, produce a list of types used by FFI functions for
+ that particular argument. Most closure arguments will just produce one FFI
+ argument, but for instance [CBArrayAndLen] will produce two, hence we return
+ a list. *)
+let ffi_cbarg_types : cbarg -> string list = function
+ | CBInt _ -> [ "c_int" ]
+ | CBUInt _ -> [ "c_uint" ]
+ | CBInt64 _ -> [ "i64" ]
+ | CBUInt64 _ -> [ "u64" ]
+ | CBString _ -> [ "*const c_char" ]
+ | CBBytesIn _ -> [ "*const c_void"; "usize" ]
+ | CBArrayAndLen (UInt32 _, _) -> [ "*mut u32"; "usize" ]
+ | CBArrayAndLen _ ->
+ failwith
+ "generator/Rust.ml: in ffi_cbarg_types: Unsupported type of array \
+ element."
+ | CBMutable (Int _) -> [ "*mut c_int" ]
+ | CBMutable _ ->
+ failwith
+ "generator/Rust.ml: in ffi_cbarg_types: Unsupported type of mutable \
+ argument."
+
+(* Return type for a Rust function. *)
+let rust_ret_type (call : call) : string =
+ let core_type =
+ match call.ret with
+ | RBool -> "bool"
+ | RStaticString -> "&'static CStr"
+ | RErr -> "()"
+ | RFd -> "RawFd"
+ | RInt -> "c_uint"
+ | RInt64 -> "u64"
+ | RCookie -> "Cookie"
+ | RSizeT -> "usize"
+ | RString -> "CString"
+ | RUInt -> "c_uint"
+ | RUIntPtr -> "usize"
+ | RUInt64 -> "u64"
+ | REnum { enum_prefix = name } | RFlags { flag_prefix = name } ->
+ camel_case name
+ in
+ if call.may_set_error then sprintf "Result<%s>" core_type else
core_type
+
+(* Given an argument ([arg : arg]), print Rust code for variable declarations
+ for all FFI arguments corresponding to [arg]. That is, for each `<FFI_NAME>`
+ in [ffi_arg_names arg], print `let <FFI_NAME> = <XXX>;`. Assuming that a
+ variable with name [rust_arg_name arg] and type [rust_arg_type arg] exists in
+ scope. *)
+let rust_arg_to_ffi (arg : arg) =
+ let rust_name = rust_arg_name arg in
+ let ffi_names = ffi_arg_names arg in
+ match arg with
+ | Bool _ | Int _ | UInt _ | UIntPtr _ | UInt32 _ | Int64 _ | UInt64 _
+ | SizeT _ ->
+ let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in
+ pr "let %s = %s;\n" ffi_name rust_name
+ | Enum _ ->
+ let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in
+ pr "let %s = %s as c_int;\n" ffi_name rust_name
+ | Flags _ ->
+ let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in
+ pr "let %s = %s.bits();\n" ffi_name rust_name
+ | String _ ->
+ let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in
+ pr "let %s = %s.as_ptr();\n" ffi_name rust_name
+ | SockAddrAndLen _ ->
+ let ffi_addr_name, ffi_len_name =
+ match ffi_names with [ x; y ] -> (x, y) | _ -> assert false
+ in
+ pr "let %s_os = OsSocketAddr::from(%s);\n" rust_name rust_name;
+ (* We assume here that the bindgen generated `sys::sockaddr` is equivalent
+ to `libc::sockaddr`. *)
+ pr "let %s = %s_os.as_ptr() as *const sys::sockaddr;\n" ffi_addr_name
+ rust_name;
+ pr "let %s = %s_os.len();\n" ffi_len_name rust_name
+ | StringList _ ->
+ let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in
+ (* Convert a slice to a null terminated slice. For some reason the C
+ functions have not marked the pointers as const, so we use `cast_mut`
+ and `as_mut_ptr` here even though the strings shouldn't be modified. *)
+ pr
+ "let mut %s_vec: Vec<*mut c_char> = %s.iter().map(|x| \
+ x.as_ptr().cast_mut()).collect();\n"
+ ffi_name rust_name;
+ pr "%s_vec.push(ptr::null_mut());\n" ffi_name;
+ pr "let %s = %s_vec.as_mut_ptr();\n" ffi_name ffi_name
+ | Path _ ->
+ let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in
+ pr
+ "let %s_buf = \
+ CString::new(%s.as_os_str().to_owned().into_raw_vec()).unwrap();\n"
+ rust_name rust_name;
+ pr "let %s = %s_buf.as_ptr();\n" ffi_name rust_name
+ | BytesIn _ | BytesPersistIn _ ->
+ let ffi_buf_name, ffi_len_name =
+ match ffi_names with [ x; y ] -> (x, y) | _ -> assert false
+ in
+ pr "let %s = %s.as_ptr() as *const c_void;\n" ffi_buf_name rust_name;
+ pr "let %s = %s.len();\n" ffi_len_name rust_name
+ | BytesOut _ | BytesPersistOut _ ->
+ let ffi_buf_name, ffi_len_name =
+ match ffi_names with [ x; y ] -> (x, y) | _ -> assert false
+ in
+ pr "let %s = %s.as_mut_ptr() as *mut c_void;\n" ffi_buf_name rust_name;
+ pr "let %s = %s.len();\n" ffi_len_name rust_name
+ | Fd _ ->
+ let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in
+ pr "let %s = %s.as_raw_fd();\n" ffi_name rust_name
+ | Closure _ ->
+ let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in
+ pr "let %s = %s_to_raw(%s);\n" ffi_name rust_name rust_name
+
+(* Same as [rust_arg_to_ffi] but for optional arguments. *)
+let rust_optarg_to_ffi (arg : optarg) =
+ let rust_name = rust_optarg_name arg in
+ let ffi_name = ffi_optarg_name arg in
+ match arg with
+ | OClosure { cbname } ->
+ pr "let %s = match %s {\n" ffi_name rust_name;
+ pr " Some(f) => %s_to_raw(f),\n" rust_name;
+ pr " None => sys::nbd_%s_callback { " cbname;
+ pr "callback: None, ";
+ pr "free: None, ";
+ pr "user_data: ptr::null_mut() ";
+ pr "},\n";
+ pr "};\n"
+ | OFlags (_, { flag_prefix }, _) ->
+ let flags_type = camel_case flag_prefix in
+ pr "let %s = %s.unwrap_or(%s::empty()).bits();\n" ffi_name rust_name
+ flags_type
+
+(* Same as [rust_arg_to_ffi] but for closure args instead. The <I>th variable
+ will be named `<NAME>_ffi_<I>` where `<NAME>` is the name of the
argument.
+ Keep in mind that this function may create multiple variables. *)
+let rust_cbarg_to_ffi cbarg = function
+ | CBInt n -> rust_arg_to_ffi (Int n)
+ | CBUInt n -> rust_arg_to_ffi (UInt n)
+ | CBInt64 n -> rust_arg_to_ffi (Int64 n)
+ | CBUInt64 n -> rust_arg_to_ffi (UInt64 n)
+ | CBString n -> rust_arg_to_ffi (String n)
+ | CBBytesIn (n1, n2) -> rust_arg_to_ffi (BytesIn (n1, n2))
+ | CBArrayAndLen (UInt32 _, _) ->
+ let ffi_arr_name, ffi_len_name =
+ match ffi_cbarg_names cbarg with
+ | [ x; y ] -> (x, y)
+ | _ -> assert false
+ in
+ let rust_name = rust_cbarg_name cbarg in
+ pr "let %s = %s.as_ptr();\n" ffi_arr_name rust_name;
+ pr "let %s = %s.len();\n" ffi_len_name rust_name
+ | CBArrayAndLen _ ->
+ failwith
+ "generator/Rust.ml: in rust_cbarg_to_ffi: Unsupported type of array \
+ element."
+ | CBMutable (Int _) ->
+ let ffi_name =
+ match ffi_cbarg_names cbarg with [ x ] -> x | _ -> assert false
+ in
+ let rust_name = rust_cbarg_name cbarg in
+ pr "let %s = %s.as_mut_ptr();\n" ffi_name rust_name
+ | CBMutable _ ->
+ failwith
+ "generator/Rust.ml: in rust_cbarg_to_ffi: Unsupported type of mutable \
+ argument."
+
+(* Given a closure argument ([x : cbarg]), print Rust code to create a variable
+ with name [rust_cbarg_name x] of type [rust_cbarg_type x]. Assuming that
+ variables with names from [ffi_cbarg_names x] exists in scope. *)
+let ffi_cbargs_to_rust cbarg =
+ let ffi_names = ffi_cbarg_names cbarg in
+ pr "let %s: %s = " (rust_cbarg_name cbarg) (rust_cbarg_type cbarg);
+ (match (cbarg, ffi_names) with
+ | (CBInt _ | CBUInt _ | CBInt64 _ | CBUInt64 _), [ ffi_name ] ->
+ pr "%s" ffi_name
+ | CBString _, [ ffi_name ] -> pr "CStr::from_ptr(%s)" ffi_name
+ | CBBytesIn _, [ ffi_buf_name; ffi_len_name ] ->
+ pr "slice::from_raw_parts(%s as *const u8, %s)" ffi_buf_name
ffi_len_name
+ | CBArrayAndLen (UInt32 _, _), [ ffi_arr_name; ffi_len_name ] ->
+ pr "slice::from_raw_parts(%s, %s)" ffi_arr_name ffi_len_name
+ | CBArrayAndLen _, [ _; _ ] ->
+ failwith
+ "generator/Rust.ml: in ffi_cbargs_to_rust: Unsupported type of array \
+ element."
+ | CBMutable (Int _), [ ffi_name ] -> pr "%s.as_mut().unwrap()" ffi_name
+ | CBMutable _, [ _ ] ->
+ failwith
+ "generator/Rust.ml: in ffi_cbargs_to_rust: Unsupported type of mutable \
+ argument."
+ | _, _ ->
+ failwith
+ "generator/Rust.ml: In ffi_cbargs_to_rust: bad number of ffi
arguments.");
+ pr ";\n"
+
+(* Print Rust code for converting a return value from an FFI call to a Rusty
+ return value. In other words, given [x : ret], this functions print a Rust
+ expression with type [rust_ret_type x], with a free variable [ffi_ret] with
+ the return value from the FFI call. *)
+let ffi_ret_to_rust (call : call) =
+ let ret_type = rust_ret_type call in
+ let pure_expr =
+ match call.ret with
+ | RBool -> "ffi_ret != 0"
+ | RErr -> "()"
+ | RInt -> "ffi_ret.try_into().unwrap()"
+ | RInt64 -> "ffi_ret.try_into().unwrap()"
+ | RSizeT -> "ffi_ret.try_into().unwrap()"
+ | RCookie -> "Cookie(ffi_ret.try_into().unwrap())"
+ | RFd -> "ffi_ret as RawFd"
+ | RStaticString -> "unsafe { CStr::from_ptr(ffi_ret) }"
+ | RString -> "unsafe { CStr::from_ptr(ffi_ret) }.to_owned()"
+ | RFlags { flag_prefix } ->
+ sprintf "%s::from_bits(ffi_ret).unwrap()" ret_type
+ | RUInt | RUIntPtr | RUInt64 -> sprintf "ffi_ret as %s" ret_type
+ | REnum _ ->
+ (* We know that each enum is represented by an isize, hence this
+ transmute is safe. *)
+ sprintf "unsafe { mem::transmute::<isize, %s>(ffi_ret as isize)
}"
+ ret_type
+ in
+ if call.may_set_error then (
+ (match call.ret with
+ | RBool | RErr | RInt | RFd | RInt64 | RCookie | RSizeT ->
+ pr "if ffi_ret < 0 { Err(unsafe { Error::get_error() }) }\n"
+ | RStaticString | RString ->
+ pr "if ffi_ret.is_null() { Err(unsafe { Error::get_error() }) }\n"
+ | RUInt | RUIntPtr | RUInt64 | REnum _ | RFlags _ ->
+ failwith "In ffi_ret_to_rust: Return type cannot be an error.");
+ pr "else { Ok(%s) }\n" pure_expr)
+ else pr "%s\n" pure_expr
+
+(* This function prints a rust function which converts a rust closure to a
+ (`repr(C)`) struct containing the function pointer, a `*mut c_void` for the
+ closure data, and a free function for the closure data. This struct is what
+ will be sent to a C function taking the closure as an argument. In fact, the
+ struct itself is generated by rust-bindgen. *)
+let print_rust_closure_to_raw_fn ({ cbname; cbargs } : closure) =
+ let closure_trait = rust_closure_trait cbargs in
+ let ffi_cbargs_names = List.flatten (List.map ffi_cbarg_names cbargs) in
+ let ffi_cbargs_types = List.flatten (List.map ffi_cbarg_types cbargs) in
+ let rust_cbargs_names = List.map rust_cbarg_name cbargs in
+ pr "fn %s_to_raw<F>(f: F) -> sys::nbd_%s_callback\n" cbname cbname;
+ pr " where F: %s\n" closure_trait;
+ pr "{\n";
+ pr
+ " unsafe extern \"C\" fn call_closure<F>(data: *mut c_void,
%s) -> c_int\n"
+ (String.concat ", "
+ (List.map2
+ (fun name typ -> name ^ ": " ^ typ)
+ ffi_cbargs_names ffi_cbargs_types));
+ pr " where F: %s\n" closure_trait;
+ pr " {\n";
+ pr " let callback_ptr = data as *mut F;\n";
+ pr " let callback = &mut *callback_ptr;\n";
+ List.iter ffi_cbargs_to_rust cbargs;
+ pr " let res = callback(%s);\n" (String.concat ", "
rust_cbargs_names);
+ pr " res.map(|()| 0).unwrap_or(-1)\n";
+ pr " }\n";
+ pr " let callback_data = Box::into_raw(Box::new(f));\n";
+ pr " sys::nbd_%s_callback {\n" cbname;
+ pr " callback: Some(call_closure::<F>),\n";
+ pr " user_data: callback_data as *mut _,\n";
+ pr " free: Some(utils::drop_data::<F>),\n";
+ pr " }\n";
+ pr "}\n";
+ pr "\n"
+
+(* Print the Rust function for a handle call. Note that this is a "method" on
+ the `NbdHandle` struct. So the printed Rust function should be in a `impl
+ NbdHandle {` block. *)
+let print_rust_handle_method ((name, call) : string * call) =
+ let rust_args_names =
+ List.map rust_arg_name call.args @ List.map rust_optarg_name call.optargs
+ in
+ let rust_args_types =
+ List.map rust_arg_type call.args @ List.map rust_optarg_type call.optargs
+ in
+ let ffi_args_names =
+ List.flatten (List.map ffi_arg_names call.args)
+ @ List.map ffi_optarg_name call.optargs
+ in
+ (* Print comments. *)
+ if call.shortdesc <> String.empty then
+ pr "/// %s\n"
+ (String.concat "\n/// " (String.split_on_char '\n'
call.shortdesc));
+ if call.longdesc <> String.empty then (
+ (* If a short comment was printed, print a blank comment line befor the long
+ description. *)
+ if call.shortdesc <> String.empty then pr "/// \n";
+ (* Print all lines of the long description. Since Rust comments are supposed
+ to be Markdown, all indented lines will be treated as code blocks. Hence
+ we trim all lines. Also brackets ("[" and "]") must be
escaped. *)
+ List.iter
+ (fun line ->
+ let unindented = String.trim line in
+ let escaped =
+ Str.global_replace (Str.regexp {|\(\[\|\]\)|}) {|\\\1|} unindented
+ in
+ pr "/// %s\n" escaped)
+ (pod2text call.longdesc));
+
+ (* Print visibility modifier. *)
+ if NameSet.mem name hidden_handle_calls then (
+ (* If this is hidden to the public API, it might be used only if some feature
+ * is active, and we don't want a unused-warning. *)
+ pr "#[allow(unused)]\n";
+ pr "pub(crate) ")
+ else pr "pub ";
+
+ pr "fn %s(&mut self, %s) -> %s {\n" name
+ (String.concat ", "
+ (List.map2
+ (fun name typ -> name ^ ": " ^ typ)
+ rust_args_names rust_args_types))
+ (rust_ret_type call);
+ List.iter rust_arg_to_ffi call.args;
+ List.iter rust_optarg_to_ffi call.optargs;
+ pr " let ffi_ret = unsafe { sys::nbd_%s(self.0, %s) };\n" name
+ (String.concat ", " ffi_args_names);
+ ffi_ret_to_rust call;
+ pr "}\n";
+ pr "\n"
+
+let print_rust_imports () =
+ pr "use bitflags::bitflags;\n";
+ pr "use crate::{sys, types::*, utils, Error, Result, NbdHandle};\n";
+ pr "use os_str_bytes::OsStringBytes as _;\n";
+ pr "use os_socketaddr::OsSocketAddr;\n";
+ pr "use std::ffi::*;\n";
+ pr "use std::mem;\n";
+ pr "use std::net::SocketAddr;\n";
+ pr "use std::os::fd::{AsRawFd, OwnedFd, RawFd};\n";
+ pr "use std::path::PathBuf;\n";
+ pr "use std::ptr;\n";
+ pr "use std::slice;\n";
+ pr "\n"
+
+let generate_rust_bindings () =
+ generate_header CStyle;
+ pr "\n";
+ print_rust_imports ();
+ List.iter print_rust_constant constants;
+ pr "\n";
+ List.iter print_rust_enum all_enums;
+ List.iter print_rust_flags all_flags;
+ List.iter print_rust_closure_to_raw_fn all_closures;
+ pr "impl NbdHandle {\n";
+ List.iter print_rust_handle_method handle_calls;
+ pr "}\n\n"
diff --git a/generator/Rust.mli b/generator/Rust.mli
new file mode 100644
index 0000000..450e4ca
--- /dev/null
+++ b/generator/Rust.mli
@@ -0,0 +1,20 @@
+(* nbd client library in userspace: generator
+ * Copyright Tage Johansson
+ *
+ * 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
+ *)
+
+(* Print all flag-structs, enums, constants and handle calls in Rust code. *)
+val generate_rust_bindings : unit -> unit
diff --git a/generator/generator.ml b/generator/generator.ml
index c73824e..18856f5 100644
--- a/generator/generator.ml
+++ b/generator/generator.ml
@@ -61,3 +61,5 @@ let () =
output_to "golang/closures.go" GoLang.generate_golang_closures_go;
output_to "golang/wrappers.go" GoLang.generate_golang_wrappers_go;
output_to "golang/wrappers.h" GoLang.generate_golang_wrappers_h;
+
+ output_to ~rustfmt:true "rust/src/bindings.rs" Rust.generate_rust_bindings;
diff --git a/generator/utils.ml b/generator/utils.ml
index 3a96929..3a1cc9f 100644
--- a/generator/utils.ml
+++ b/generator/utils.ml
@@ -413,7 +413,7 @@ let files_equal n1 n2 =
| 1 -> false
| i -> failwithf "%s: failed with error code %d" cmd i
-let output_to filename k =
+let output_to ?(rustfmt = false) filename k =
lineno := 1; col := 0;
let filename_new = filename ^ ".new" in
let c = open_out filename_new in
@@ -422,6 +422,13 @@ let output_to filename k =
close_out c;
chan := NoOutput;
+ if rustfmt then
+ (match system (sprintf "rustfmt %s" filename_new) with
+ | WEXITED 0 -> ()
+ | WEXITED i -> failwith (sprintf "Rustfmt failed with exit code %d"
i)
+ | _ -> failwith "Rustfmt was killed or stopped by a signal.");
+
+
(* Is the new file different from the current file? *)
if Sys.file_exists filename && files_equal filename filename_new then
unlink filename_new (* same, so skip it *)
diff --git a/generator/utils.mli b/generator/utils.mli
index b4a2525..7489fe0 100644
--- a/generator/utils.mli
+++ b/generator/utils.mli
@@ -50,7 +50,8 @@ val files_equal : string -> string -> bool
val generate_header : ?extra_sources:string list -> comment_style -> unit
-val output_to : string -> (unit -> 'a) -> unit
+(** Redirect stdout to a file. If `rustfmt` is true, will format the text with rustfmt.
*)
+val output_to : ?rustfmt:bool -> string -> (unit -> 'a) -> unit
val pr : ('a, unit, string, unit) format4 -> 'a
val pr_wrap : ?maxcol:int -> char -> (unit -> 'a) -> unit
val pr_wrap_cstr : ?maxcol:int -> (unit -> 'a) -> unit
diff --git a/rust/.gitignore b/rust/.gitignore
new file mode 100644
index 0000000..da001fd
--- /dev/null
+++ b/rust/.gitignore
@@ -0,0 +1,3 @@
+/target
+/Cargo.lock
+/src/bindings.rs
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
new file mode 100644
index 0000000..0a934e7
--- /dev/null
+++ b/rust/Cargo.toml
@@ -0,0 +1,48 @@
+# nbd client library in userspace
+# Copyright Tage Johansson
+#
+# 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
+
+[workspace]
+
+[workspace.package]
+# TODO: Add authors.
+version = "0.1.0"
+edition = "2021"
+description = "Rust bindings for libnbd, a client library for controlling block
devices over a network."
+license = "LGPL-2.1-only"
+keywords = ["libnbd", "block-device", "network"]
+categories = ["api-bindings", "emulators",
"virtualization"]
+
+[package]
+name = "libnbd"
+version.workspace = true
+edition.workspace = true
+description.workspace = true
+license.workspace = true
+keywords.workspace = true
+categories.workspace = true
+
+[dependencies]
+libnbd-sys = { path = "libnbd-sys" }
+bitflags = "2.3.1"
+errno = "0.3.1"
+os_socketaddr = "0.2.4"
+os_str_bytes = { version = "6.5.0", default-features = false }
+thiserror = "1.0.40"
+log = { version = "0.4.19", optional = true }
+
+[features]
+default = ["log"]
diff --git a/rust/Makefile.am b/rust/Makefile.am
new file mode 100644
index 0000000..cb8d7c9
--- /dev/null
+++ b/rust/Makefile.am
@@ -0,0 +1,60 @@
+# nbd client library in userspace
+# Copyright Tage Johansson
+#
+# 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
+
+generator_built = \
+ src/bindings.rs \
+ $(NULL)
+
+EXTRA_DIST = \
+ $(generator_built) \
+ .gitignore \
+ Cargo.toml \
+ src/lib.rs \
+ src/error.rs \
+ src/handle.rs \
+ src/types.rs \
+ src/utils.rs \
+ libnbd-sys/Cargo.toml \
+ libnbd-sys/build.rs \
+ libnbd-sys/wrapper.h \
+ libnbd-sys/src/lib.rs \
+ $(NULL)
+
+if HAVE_RUST
+
+all-local: $(source_files)
+ rm -f libnbd-sys/libnbd_version.t
+ $(abs_top_builddir)/run echo $(VERSION) > libnbd-sys/libnbd_version.t
+ mv libnbd-sys/libnbd_version.t libnbd-sys/libnbd_version
+ $(abs_top_builddir)/run $(CARGO) build
+ $(abs_top_builddir)/run $(CARGO) doc
+
+TESTS_ENVIRONMENT = \
+ LIBNBD_DEBUG=1 \
+ $(MALLOC_CHECKS) \
+ abs_top_srcdir=$(abs_top_srcdir) \
+ $(NULL)
+LOG_COMPILER = $(top_builddir)/run
+TESTS = run-tests.sh
+
+endif
+
+clean-local:
+ $(CARGO) clean
+CLEANFILES += libnbd-sys/libnbd_version
diff --git a/rust/cargo_test/.gitignore b/rust/cargo_test/.gitignore
new file mode 100644
index 0000000..4fffb2f
--- /dev/null
+++ b/rust/cargo_test/.gitignore
@@ -0,0 +1,2 @@
+/target
+/Cargo.lock
diff --git a/rust/cargo_test/Cargo.toml b/rust/cargo_test/Cargo.toml
new file mode 100644
index 0000000..9f9d478
--- /dev/null
+++ b/rust/cargo_test/Cargo.toml
@@ -0,0 +1,23 @@
+# nbd client library in userspace
+# Copyright Tage Johansson
+#
+# 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
+
+[workspace]
+
+[package]
+name = "cargo_test"
+edition = "2021"
+version = "0.1.0"
diff --git a/rust/cargo_test/README.md b/rust/cargo_test/README.md
new file mode 100644
index 0000000..f80646b
--- /dev/null
+++ b/rust/cargo_test/README.md
@@ -0,0 +1,3 @@
+The solely purpose of this directory is to serve as a test crate for checking if Cargo is
useable.
+`cargo test`, `cargo doc` and `cargo fmt` are run in the Autoconf script in this
directory. If any of the commands failes,
+Cargo is assumed not to be useable and the Rust bindings will be disabled.
diff --git a/rust/cargo_test/src/lib.rs b/rust/cargo_test/src/lib.rs
new file mode 100644
index 0000000..a5cbb84
--- /dev/null
+++ b/rust/cargo_test/src/lib.rs
@@ -0,0 +1,31 @@
+// nbd client library in userspace
+// Copyright Tage Johansson
+//
+// 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
+
+/// A dummy test function which adds one to an 32-bit integer.
+pub fn add_one(i: i32) -> i32 {
+ i + 1
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_add_one() {
+ assert_eq!(add_one(42), 43);
+ }
+}
diff --git a/rust/libnbd-sys/.gitignore b/rust/libnbd-sys/.gitignore
new file mode 100644
index 0000000..a662d0c
--- /dev/null
+++ b/rust/libnbd-sys/.gitignore
@@ -0,0 +1,4 @@
+/target
+/Cargo.lock
+/src/bindings.rs
+/libnbd_version
diff --git a/rust/libnbd-sys/Cargo.toml b/rust/libnbd-sys/Cargo.toml
new file mode 100644
index 0000000..7eadd0a
--- /dev/null
+++ b/rust/libnbd-sys/Cargo.toml
@@ -0,0 +1,30 @@
+# nbd client library in userspace
+# Copyright Tage Johansson
+#
+# 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
+
+[package]
+name = "libnbd-sys"
+version.workspace = true
+edition.workspace = true
+description.workspace = true
+license.workspace = true
+keywords.workspace = true
+categories.workspace = true
+links = "nbd"
+
+[build-dependencies]
+bindgen = "0.65.1"
+pkg-config = "0.3.27"
diff --git a/rust/libnbd-sys/build.rs b/rust/libnbd-sys/build.rs
new file mode 100644
index 0000000..3dcf8cc
--- /dev/null
+++ b/rust/libnbd-sys/build.rs
@@ -0,0 +1,60 @@
+// nbd client library in userspace
+// Copyright Tage Johansson
+//
+// 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
+
+extern crate bindgen;
+
+use std::env;
+use std::fs;
+use std::path::PathBuf;
+
+fn main() {
+ // If the environment variable LIBNBD_DIR is set, use it as the path for the
+ // libnbd shared library. Else, use pkg-config.
+ let libnbd_version = fs::read_to_string("libnbd_version").unwrap();
+ pkg_config::Config::new()
+ .atleast_version(libnbd_version.trim())
+ .probe("libnbd")
+ .unwrap();
+
+ // Tell cargo to tell rustc to link the system libnbd
+ // shared library.
+ //println!("cargo:rustc-link-lib=nbd");
+
+ // Tell cargo to invalidate the built crate whenever the wrapper changes
+ println!("cargo:rerun-if-changed=wrapper.h");
+
+ // The bindgen::Builder is the main entry point
+ // to bindgen, and lets you build up options for
+ // the resulting bindings.
+ let bindings = bindgen::Builder::default()
+ // The input header we would like to generate
+ // bindings for.
+ .header("wrapper.h")
+ // Tell cargo to invalidate the built crate whenever any of the
+ // included header files changed.
+ .parse_callbacks(Box::new(bindgen::CargoCallbacks))
+ // Finish the builder and generate the bindings.
+ .generate()
+ // Unwrap the Result and panic on failure.
+ .expect("Unable to generate bindings");
+
+ // Write the bindings to the $OUT_DIR/bindings.rs file.
+ let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
+ bindings
+ .write_to_file(out_path.join("bindings.rs"))
+ .expect("Couldn't write bindings!");
+}
diff --git a/rust/libnbd-sys/src/lib.rs b/rust/libnbd-sys/src/lib.rs
new file mode 100644
index 0000000..f93317c
--- /dev/null
+++ b/rust/libnbd-sys/src/lib.rs
@@ -0,0 +1,24 @@
+// nbd client library in userspace
+// Copyright Tage Johansson
+//
+// 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
+
+//! All functions from libnbd.h generated by bindgen.
+
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
diff --git a/rust/libnbd-sys/wrapper.h b/rust/libnbd-sys/wrapper.h
new file mode 100644
index 0000000..c25f751
--- /dev/null
+++ b/rust/libnbd-sys/wrapper.h
@@ -0,0 +1,18 @@
+// nbd client library in userspace
+// Copyright Tage Johansson
+//
+// 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 "../../include/libnbd.h"
diff --git a/rust/run-tests.sh b/rust/run-tests.sh
new file mode 100755
index 0000000..7a0bc85
--- /dev/null
+++ b/rust/run-tests.sh
@@ -0,0 +1,24 @@
+#!/bin/bash -
+# nbd client library in userspace
+# Copyright Red Hat
+#
+# 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
+
+. ../tests/functions.sh
+
+set -e
+set -x
+
+cargo test
diff --git a/rust/src/error.rs b/rust/src/error.rs
new file mode 100644
index 0000000..5c1780a
--- /dev/null
+++ b/rust/src/error.rs
@@ -0,0 +1,47 @@
+// nbd client library in userspace
+// Copyright Tage Johansson
+//
+// 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
+
+use crate::sys;
+use errno::Errno;
+use std::ffi::CStr;
+
+/// A general error type for libnbd.
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error("Errno: {errno}: {description}")]
+ WithErrno { errno: Errno, description: String },
+ #[error("{description}")]
+ WithoutErrno { description: String },
+}
+
+pub type Result<T, E = Error> = std::result::Result<T, E>;
+
+impl Error {
+ /// Retrieve the last error from libnbd in the current thread.
+ pub(crate) unsafe fn get_error() -> Self {
+ let description = CStr::from_ptr(sys::nbd_get_error())
+ .to_string_lossy()
+ .to_string();
+ match sys::nbd_get_errno() {
+ 0 => Self::WithoutErrno { description },
+ e => Self::WithErrno {
+ description,
+ errno: Errno(e),
+ },
+ }
+ }
+}
diff --git a/rust/src/handle.rs b/rust/src/handle.rs
new file mode 100644
index 0000000..48b84d9
--- /dev/null
+++ b/rust/src/handle.rs
@@ -0,0 +1,53 @@
+// nbd client library in userspace
+// Copyright Tage Johansson
+//
+// 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
+
+use crate::sys;
+use crate::{Error, Result};
+
+/// An NBD client handle.
+pub struct NbdHandle(pub(crate) *mut sys::nbd_handle);
+
+impl NbdHandle {
+ pub fn new() -> Result<Self> {
+ let handle = unsafe { sys::nbd_create() };
+ if handle.is_null() {
+ Err(unsafe { Error::get_error() })
+ } else {
+ #[allow(unused_mut)]
+ let mut nbd = NbdHandle(handle);
+ #[cfg(feature = "log")]
+ {
+ nbd.set_debug_callback(|func_name, msg| {
+ log::debug!(
+ target: func_name.to_string_lossy().as_ref(),
+ "{}",
+ msg.to_string_lossy()
+ );
+ Ok(())
+ })?;
+ nbd.set_debug(true)?;
+ }
+ Ok(nbd)
+ }
+ }
+}
+
+impl Drop for NbdHandle {
+ fn drop(&mut self) {
+ unsafe { sys::nbd_close(self.0) }
+ }
+}
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
new file mode 100644
index 0000000..4a4e74a
--- /dev/null
+++ b/rust/src/lib.rs
@@ -0,0 +1,28 @@
+// nbd client library in userspace
+// Copyright Tage Johansson
+//
+// 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
+
+#![deny(warnings)]
+
+mod bindings;
+mod error;
+mod handle;
+pub mod types;
+mod utils;
+pub use bindings::*;
+pub use error::{Error, Result};
+pub use handle::NbdHandle;
+pub(crate) use libnbd_sys as sys;
diff --git a/rust/src/types.rs b/rust/src/types.rs
new file mode 100644
index 0000000..eb2df06
--- /dev/null
+++ b/rust/src/types.rs
@@ -0,0 +1,18 @@
+// nbd client library in userspace
+// Copyright Tage Johansson
+//
+// 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
+
+pub struct Cookie(pub(crate) u64);
diff --git a/rust/src/utils.rs b/rust/src/utils.rs
new file mode 100644
index 0000000..b8200c1
--- /dev/null
+++ b/rust/src/utils.rs
@@ -0,0 +1,23 @@
+// nbd client library in userspace
+// Copyright Tage Johansson
+//
+// 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
+
+use std::ffi::c_void;
+
+/// Take a C pointer to some rust data of type `T` on the heap and drop it.
+pub unsafe extern "C" fn drop_data<T>(data: *mut c_void) {
+ drop(Box::from_raw(data as *mut T))
+}
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..249e923
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,18 @@
+# nbd client library in userspace
+# Copyright Tage Johansson
+#
+# 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
+
+max_width = 80
--
2.41.0