[Libguestfs] [libnbd PATCH v3 01/10] rust: create basic Rust bindings

Tage Johansson tage.j.lists at posteo.net
Mon Jul 24 10:38:13 UTC 2023


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.
---
 .gitignore                 |   8 +
 .ocamlformat               |   4 +
 Makefile.am                |   1 +
 configure.ac               |  13 +
 generator/Makefile.am      |   2 +
 generator/Rust.ml          | 578 +++++++++++++++++++++++++++++++++++++
 generator/Rust.mli         |  20 ++
 generator/generator.ml     |   2 +
 rust/Cargo.toml            |  50 ++++
 rust/Makefile.am           |  71 +++++
 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          | 111 +++++++
 rust/src/handle.rs         |  65 +++++
 rust/src/lib.rs            |  28 ++
 rust/src/types.rs          |  18 ++
 rust/src/utils.rs          |  23 ++
 rustfmt.toml               |  19 ++
 21 files changed, 1169 insertions(+)
 create mode 100644 .ocamlformat
 create mode 100644 generator/Rust.ml
 create mode 100644 generator/Rust.mli
 create mode 100644 rust/Cargo.toml
 create mode 100644 rust/Makefile.am
 create mode 100644 rust/libnbd-sys/Cargo.toml
 create mode 100644 rust/libnbd-sys/build.rs
 create mode 100644 rust/libnbd-sys/src/lib.rs
 create mode 100644 rust/libnbd-sys/wrapper.h
 create mode 100755 rust/run-tests.sh
 create mode 100644 rust/src/error.rs
 create mode 100644 rust/src/handle.rs
 create mode 100644 rust/src/lib.rs
 create mode 100644 rust/src/types.rs
 create mode 100644 rust/src/utils.rs
 create mode 100644 rustfmt.toml

diff --git a/.gitignore b/.gitignore
index efe3080..098e004 100644
--- a/.gitignore
+++ b/.gitignore
@@ -174,6 +174,14 @@ Makefile.in
 /python/nbd.py
 /python/run-python-tests
 /run
+/rust/Cargo.lock
+/rust/libnbd-sys/Cargo.lock
+/rust/libnbd-sys/libnbd_version
+/rust/libnbd-sys/src/bindings.rs
+/rust/libnbd-sys/target
+/rust/src/async_bindings.rs
+/rust/src/bindings.rs
+/rust/target
 /sh/nbdsh
 /sh/nbdsh.1
 /stamp-h1
diff --git a/.ocamlformat b/.ocamlformat
new file mode 100644
index 0000000..7bfe155
--- /dev/null
+++ b/.ocamlformat
@@ -0,0 +1,4 @@
+profile = default
+version = 0.25.1
+wrap-comments = true
+margin = 78
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 0b94f5e..0004f25 100644
--- a/configure.ac
+++ b/configure.ac
@@ -613,6 +613,17 @@ 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])
+],[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 +668,7 @@ AC_CONFIG_FILES([Makefile
                  generator/Makefile
                  golang/Makefile
                  golang/examples/Makefile
+                 rust/Makefile
                  include/Makefile
                  info/Makefile
                  interop/Makefile
@@ -717,6 +729,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 c3d53b2..5b93734 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..8e2ca13
--- /dev/null
+++ b/generator/Rust.ml
@@ -0,0 +1,578 @@
+(* 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"
+
+(* Print metadata namespaces. *)
+let print_metadata_namespace (ns, ctxts) =
+  pr "/// The string \"%s:\" as a &[CStr].\n" ns;
+  pr "pub const NAMESPACE_%s: &CStr = c_str!(\"%s:\");\n"
+    (String.uppercase_ascii ns)
+    ns;
+  List.iter
+    (fun (ctxt, consts) ->
+      let s = ns ^ ":" ^ ctxt in
+      pr "/// The string \"%s\" as a &[CStr].\n" s;
+      pr "pub const CONTEXT_%s_%s: &CStr = c_str!(\"%s\");\n"
+        (String.uppercase_ascii ns)
+        (String.uppercase_ascii ctxt)
+        s;
+      List.iter
+        (fun (n, i) ->
+          pr "pub const %s: u32 = %d;\n" (String.uppercase_ascii n) i)
+        consts)
+    ctxts
+
+(* 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]"
+  | BytesPersistIn _ -> "&'static [u8]"
+  | BytesPersistOut _ -> "&'static mut [u8]"
+  | Closure { cbargs } -> "impl " ^ rust_closure_trait cbargs
+
+(* Get the Rust closure trait for a callback, That is `Fn*(...) -> ...)`. *)
+and rust_closure_trait ?(lifetime = Some "'static") cbargs : string =
+  let rust_cbargs = String.concat ", " (List.map rust_cbarg_type cbargs)
+  and lifetime_constraint =
+    match lifetime with None -> "" | Some x -> " + " ^ x
+  in
+  "FnMut(" ^ rust_cbargs ^ ") -> c_int + Send + Sync" ^ lifetime_constraint
+
+(* 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> = <...>;`.
+   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 = unsafe { crate::bindings::%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) => unsafe { crate::bindings::%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 -> "TryInto::<u32>::try_into(ffi_ret).unwrap()"
+    | RInt64 -> "TryInto::<u64>::try_into(ffi_ret).unwrap()"
+    | RSizeT -> "TryInto::<usize>::try_into(ffi_ret).unwrap()"
+    | RCookie -> "Cookie(ffi_ret.try_into().unwrap())"
+    | RFd -> "ffi_ret as RawFd"
+    | RStaticString -> "unsafe { CStr::from_ptr(ffi_ret) }"
+    | RString ->
+        "{ let res = unsafe { CStr::from_ptr(ffi_ret) }.to_owned();\n"
+        ^ "unsafe { libc::free(ffi_ret.cast()); }\n" ^ "res}"
+    | 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 {\n";
+        pr "    Err(unsafe { Error::get_error(self.raw_handle()) })\n";
+        pr "}\n"
+    | RStaticString | RString ->
+        pr "if ffi_ret.is_null() {\n";
+        pr "    Err(unsafe { Error::get_error(self.raw_handle()) })\n";
+        pr "}\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 ~lifetime:None 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 "pub(crate) unsafe 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 (sprintf "%s: %s") 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 "        callback(%s)\n" (String.concat ", " rust_cbargs_names);
+  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 ,comment for a rust function for a handle call. *)
+let print_rust_handle_call_comment call =
+  (* 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 a Rust expression which converts Rust like arguments to FFI like
+   arguments, makes a call on the raw FFI handle, and converts the return
+   value to a Rusty type. The expression assumes that variables with name
+   `rust_arg_name arg` for all `arg` in `call.args` exists in scope. *)
+let print_ffi_call name handle call =
+  let ffi_args_names =
+    List.flatten (List.map ffi_arg_names call.args)
+    @ List.map ffi_optarg_name call.optargs
+  in
+  pr "{\n";
+  pr "    // Convert all arguments to FFI-like types.\n";
+  List.iter rust_arg_to_ffi call.args;
+  List.iter rust_optarg_to_ffi call.optargs;
+  pr "\n";
+  pr "    // Call the FFI-function.\n";
+  pr "    let ffi_ret = unsafe { sys::nbd_%s(%s, %s) };\n" name handle
+    (String.concat ", " ffi_args_names);
+  pr "\n";
+  pr "    // Convert the result to something more rusty.\n";
+  ffi_ret_to_rust call;
+  pr "}\n"
+
+(* Print the Rust function for a handle call. Note that this is a "method" on
+   the `Handle` struct. So the printed Rust function should be in an `impl
+   Handle {` 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
+  and rust_args_types =
+    List.map rust_arg_type call.args @ List.map rust_optarg_type call.optargs
+  in
+  let rust_args =
+    String.concat ", "
+      (List.map2 (sprintf "%s: %s") rust_args_names rust_args_types)
+  in
+  print_rust_handle_call_comment call;
+  (* 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(&self, %s) -> %s\n" name rust_args (rust_ret_type call);
+  print_ffi_call name "self.handle" call;
+  pr "\n"
+
+let print_rust_imports () =
+  pr "use bitflags::bitflags;\n";
+  pr "use byte_strings::const_::c_str;\n";
+  pr "use crate::{*, types::*};\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 ~copyright:"Tage Johansson";
+  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_metadata_namespace metadata_namespaces;
+  List.iter print_rust_closure_to_raw_fn all_closures;
+  pr "impl Handle {\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..67b9502 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 ~formatter:(Some Rustfmt) "rust/src/bindings.rs" Rust.generate_rust_bindings;
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
new file mode 100644
index 0000000..e81360b
--- /dev/null
+++ b/rust/Cargo.toml
@@ -0,0 +1,50 @@
+# 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 }
+libc = "0.2.147"
+byte-strings = "0.3.1"
+
+[features]
+default = ["log"]
diff --git a/rust/Makefile.am b/rust/Makefile.am
new file mode 100644
index 0000000..e8a916f
--- /dev/null
+++ b/rust/Makefile.am
@@ -0,0 +1,71 @@
+# 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)
+
+source_files = \
+	$(generator_built) \
+	Cargo.toml \
+	src/lib.rs \
+	src/error.rs \
+	src/handle.rs \
+	src/types.rs \
+	src/utils.rs \
+	src/async_handle.rs \
+	libnbd-sys/Cargo.toml \
+	libnbd-sys/build.rs \
+	libnbd-sys/wrapper.h \
+	libnbd-sys/src/lib.rs \
+	$(NULL)
+
+EXTRA_DIST = \
+	$(source_files) \
+	$(NULL)
+
+if HAVE_RUST
+
+all-local: libnbd-sys/libnbd_version target/debug/liblibnbd.rlib \
+		target/doc/libnbd/index.html
+
+libnbd-sys/libnbd_version: Makefile
+	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
+
+target/debug/liblibnbd.rlib: $(source_files)
+	$(abs_top_builddir)/run $(CARGO) build
+
+target/doc/libnbd/index.html: $(source_files)
+	$(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/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..615a178
--- /dev/null
+++ b/rust/src/error.rs
@@ -0,0 +1,111 @@
+// 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;
+use std::io;
+
+/// A general error type for libnbd.
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    #[error(transparent)]
+    Recoverable(ErrorKind),
+    #[error("Fatal: NBD handle is dead: {0}")]
+    Fatal(FatalErrorKind),
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum ErrorKind {
+    #[error("Errno: {errno}: {description}")]
+    WithErrno { errno: Errno, description: String },
+    #[error("{description}")]
+    WithoutErrno { description: String },
+    #[error(transparent)]
+    Errno(#[from] Errno),
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum FatalErrorKind {
+    #[error(transparent)]
+    Kind(#[from] ErrorKind),
+    #[error(transparent)]
+    Io(#[from] io::Error),
+}
+
+pub type Result<T, E = Error> = std::result::Result<T, E>;
+
+impl ErrorKind {
+    /// 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),
+            },
+        }
+    }
+
+    /// Create an error from an errno value without any additional description.
+    pub fn from_errno(val: i32) -> Self {
+        Self::Errno(Errno(val))
+    }
+
+    /// Get the errno value if any.
+    pub fn errno(&self) -> Option<i32> {
+        match self {
+            Self::WithErrno {
+                errno: Errno(x), ..
+            }
+            | Self::Errno(Errno(x)) => Some(*x),
+            Self::WithoutErrno { .. } => None,
+        }
+    }
+}
+
+impl Error {
+    /// Retrieve the last error from libnbd in the current thread and check if
+    /// the handle is dead to determine if the error is fatal or not.
+    pub(crate) unsafe fn get_error(handle: *mut sys::nbd_handle) -> Self {
+        let kind = ErrorKind::get_error();
+        if sys::nbd_aio_is_dead(handle) != 0 {
+            Self::Fatal(FatalErrorKind::Kind(kind))
+        } else {
+            Self::Recoverable(kind)
+        }
+    }
+
+    /// Get the errno value if any.
+    pub fn errno(&self) -> Option<i32> {
+        match self {
+            Self::Recoverable(e) | Self::Fatal(FatalErrorKind::Kind(e)) => {
+                e.errno()
+            }
+            Self::Fatal(FatalErrorKind::Io(e)) => e.raw_os_error(),
+        }
+    }
+}
+
+impl From<io::Error> for Error {
+    fn from(err: io::Error) -> Self {
+        Self::Fatal(err.into())
+    }
+}
diff --git a/rust/src/handle.rs b/rust/src/handle.rs
new file mode 100644
index 0000000..477faa4
--- /dev/null
+++ b/rust/src/handle.rs
@@ -0,0 +1,65 @@
+// 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, ErrorKind, Result};
+
+/// An NBD client handle.
+#[derive(Debug)]
+pub struct Handle {
+    /// A pointer to the raw handle.
+    pub(crate) handle: *mut sys::nbd_handle,
+}
+
+impl Handle {
+    pub fn new() -> Result<Self> {
+        let handle = unsafe { sys::nbd_create() };
+        if handle.is_null() {
+            Err(unsafe { Error::Fatal(ErrorKind::get_error().into()) })
+        } else {
+            #[allow(unused_mut)]
+            let mut nbd = Handle { 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()
+                    );
+                    0
+                })?;
+                nbd.set_debug(true)?;
+            }
+            Ok(nbd)
+        }
+    }
+
+    /// Get the underliing C pointer to the handle.
+    pub(crate) fn raw_handle(&self) -> *mut sys::nbd_handle {
+        self.handle
+    }
+}
+
+impl Drop for Handle {
+    fn drop(&mut self) {
+        unsafe { sys::nbd_close(self.handle) }
+    }
+}
+
+unsafe impl Send for Handle {}
+unsafe impl Sync for Handle {}
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
new file mode 100644
index 0000000..a6f3131
--- /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, ErrorKind, FatalErrorKind, Result};
+pub use handle::Handle;
+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..e6250dd
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,19 @@
+# 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
+
+edition = "2021"
+max_width = 80
-- 
2.41.0



More information about the Libguestfs mailing list