This commit creates basic Rust bindings in the rust directory.
The bindings are generated by generator/Rust.ml and
generator/RustSys.ml.
---
.gitignore | 10 +
.ocamlformat | 4 +
Makefile.am | 2 +
configure.ac | 30 ++
generator/Makefile.am | 4 +
generator/Rust.ml | 548 +++++++++++++++++++++++++++++++++++++
generator/Rust.mli | 20 ++
generator/RustSys.ml | 167 +++++++++++
generator/RustSys.mli | 19 ++
generator/generator.ml | 3 +
rust/Cargo.toml | 49 ++++
rust/Makefile.am | 76 +++++
rust/cargo_test/Cargo.toml | 23 ++
rust/cargo_test/README.md | 3 +
rust/cargo_test/src/lib.rs | 31 +++
rust/libnbd-sys/Cargo.toml | 32 +++
rust/libnbd-sys/build.rs | 26 ++
rust/libnbd-sys/src/.keep | 0
rust/run-tests.sh.in | 24 ++
rust/src/error.rs | 157 +++++++++++
rust/src/handle.rs | 65 +++++
rust/src/lib.rs | 28 ++
rust/src/types.rs | 18 ++
rust/src/utils.rs | 23 ++
rustfmt.toml | 19 ++
scripts/git.orderfile | 10 +
26 files changed, 1391 insertions(+)
create mode 100644 .ocamlformat
create mode 100644 generator/Rust.ml
create mode 100644 generator/Rust.mli
create mode 100644 generator/RustSys.ml
create mode 100644 generator/RustSys.mli
create mode 100644 rust/Cargo.toml
create mode 100644 rust/Makefile.am
create mode 100644 rust/cargo_test/Cargo.toml
create mode 100644 rust/cargo_test/README.md
create mode 100644 rust/cargo_test/src/lib.rs
create mode 100644 rust/libnbd-sys/Cargo.toml
create mode 100644 rust/libnbd-sys/build.rs
create mode 100644 rust/libnbd-sys/src/.keep
create mode 100755 rust/run-tests.sh.in
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..ac514b0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -174,6 +174,16 @@ 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/lib.rs
+/rust/src/async_bindings.rs
+/rust/src/bindings.rs
+/rust/target
+/rust/cargo_test/Cargo.lock
+/rust/cargo_test/target
+/rust/run-tests.sh
/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..9f7707a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -28,6 +28,7 @@ EXTRA_DIST = \
README.md \
scripts/git.orderfile \
SECURITY \
+ rustfmt.toml \
$(NULL)
CLEANFILES += m4/*~
@@ -55,6 +56,7 @@ SUBDIRS = \
ocaml/tests \
golang \
golang/examples \
+ rust \
interop \
fuzzing \
bash-completion \
diff --git a/configure.ac b/configure.ac
index 0b94f5e..816b59e 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` && \
@@ -643,6 +669,8 @@ AC_CONFIG_FILES([run],
[chmod +x,-w run])
AC_CONFIG_FILES([sh/nbdsh],
[chmod +x,-w sh/nbdsh])
+AC_CONFIG_FILES([rust/run-tests.sh],
+ [chmod +x,-w rust/run-tests.sh])
AC_CONFIG_FILES([Makefile
bash-completion/Makefile
@@ -657,6 +685,7 @@ AC_CONFIG_FILES([Makefile
generator/Makefile
golang/Makefile
golang/examples/Makefile
+ rust/Makefile
include/Makefile
info/Makefile
interop/Makefile
@@ -717,6 +746,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..5e148be 100644
--- a/generator/Makefile.am
+++ b/generator/Makefile.am
@@ -60,6 +60,10 @@ sources = \
OCaml.ml \
GoLang.mli \
GoLang.ml \
+ RustSys.mli \
+ RustSys.ml \
+ Rust.mli \
+ Rust.ml \
generator.ml \
$(NULL)
diff --git a/generator/Rust.ml b/generator/Rust.ml
new file mode 100644
index 0000000..88434c3
--- /dev/null
+++ b/generator/Rust.ml
@@ -0,0 +1,548 @@
+(* 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 =
+ 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 } =
+ 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 "pub const NAMESPACE_%s: &[u8] = b\"%s:\";\n"
+ (String.uppercase_ascii ns)
+ ns;
+ List.iter
+ (fun (ctxt, consts) ->
+ let s = ns ^ ":" ^ ctxt in
+ pr "pub const CONTEXT_%s_%s: &[u8] = b\"%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 _ -> "impl Into<Vec<u8>>"
+ | SockAddrAndLen _ -> "SocketAddr"
+ | StringList _ -> "impl IntoIterator<Item = impl
AsRef<[u8]>>"
+ | Path _ -> "impl Into<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 -> "&[u8]"
+ | 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 : string =
+ let core_type =
+ match call.ret with
+ | RBool -> "bool"
+ | RStaticString -> "&'static [u8]"
+ | RErr -> "()"
+ | RFd -> "RawFd"
+ | RInt -> "c_uint"
+ | RInt64 -> "u64"
+ | RCookie -> "Cookie"
+ | RSizeT -> "usize"
+ | RString -> "Vec<u8>"
+ | 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 =
+ 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
+ | 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;
+ pr "let %s = %s_os.as_ptr();\n" ffi_addr_name rust_name;
+ pr "let %s = %s_os.len();\n" ffi_len_name rust_name
+ | String _ ->
+ let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in
+ pr
+ "let %s_buf = CString::new(%s.into()).map_err(|e| Error::from(e))?;\n"
+ rust_name rust_name;
+ pr "let %s = %s_buf.as_ptr();\n" ffi_name rust_name
+ | Path _ ->
+ let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in
+ pr "let %s_buf = " rust_name;
+ pr "CString::new(%s.into().into_os_string().into_vec())" rust_name;
+ pr ".map_err(|e| Error::from(e))?;\n";
+ pr "let %s = %s_buf.as_ptr();\n" ffi_name rust_name
+ | StringList _ ->
+ let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in
+ (* Create a `Vec` with the arguments as `CString`s. This will copy every
+ string and thereby require some extra heap allocations. *)
+ pr "let %s_c_strs: Vec<CString> = " ffi_name;
+ pr "%s.into_iter()" rust_name;
+ pr ".map(|x| CString::new(x.as_ref())";
+ pr ".map_err(|e| Error::from(e.to_string())))";
+ pr ".collect::<Result<Vec<CString>>>()?;\n";
+ (* Create a vector of pointers to all of these `CString`s. For some
+ reason, the C API hasn't 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_ptrs: Vec<*mut c_char> = \n" ffi_name;
+ pr " %s_c_strs.iter().map(|x| x.as_ptr().cast_mut()).collect();\n"
+ ffi_name;
+ (* Add a null pointer to mark the end of the list. *)
+ pr "%s_ptrs.push(ptr::null_mut());\n" ffi_name;
+ pr "let %s = %s_ptrs.as_mut_ptr();\n" ffi_name ffi_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 =
+ 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
+
+(* 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).to_bytes()" 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 =
+ 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) }.to_bytes()"
+ | RString ->
+ "{ let res = \n"
+ ^ " unsafe { CStr::from_ptr(ffi_ret) }.to_owned().into_bytes();\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 } =
+ 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) =
+ 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 crate::{*, types::*};\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::os::unix::prelude::*;\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/RustSys.ml b/generator/RustSys.ml
new file mode 100644
index 0000000..2a50a40
--- /dev/null
+++ b/generator/RustSys.ml
@@ -0,0 +1,167 @@
+(* 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
+ *)
+
+(* Low level Rust bindings for the libnbd-sys crate. *)
+
+open Printf
+open API
+open Utils
+
+(** A list of the argument types corresponding to an [arg]. *)
+let arg_types : arg -> string list = function
+ | Bool n -> [ "bool" ]
+ | Int n | Fd n | Enum (n, _) -> [ "c_int" ]
+ | UInt n -> [ "c_uint" ]
+ | UIntPtr n -> [ "uintptr_t" ]
+ | SizeT n -> [ "size_t" ]
+ | UInt32 n | Flags (n, _) -> [ "u32" ]
+ | Int64 n -> [ "i64" ]
+ | UInt64 n -> [ "u64" ]
+ | String n | Path n -> [ "*const c_char" ]
+ | SockAddrAndLen (n1, n2) -> [ "*const sockaddr"; "socklen_t" ]
+ | StringList n -> [ "*mut *mut c_char" ]
+ | BytesIn (n1, n2) | BytesPersistIn (n1, n2) -> [ "*const c_void";
"usize" ]
+ | BytesOut (n1, n2) | BytesPersistOut (n1, n2) -> [ "*mut c_void";
"usize" ]
+ | Closure { cbname } -> [ sprintf "nbd_%s_callback" cbname ]
+
+(** The type of an optional argument. *)
+let optarg_type : optarg -> string = function
+ | OClosure { cbname } -> sprintf "nbd_%s_callback" cbname
+ | OFlags _ -> "u32"
+
+(** The types of arguments corresponding to a [cbarg]. *)
+let cbarg_types : cbarg -> string list = function
+ | CBInt n -> arg_types (Int n)
+ | CBUInt n -> arg_types (UInt n)
+ | CBInt64 n -> arg_types (Int64 n)
+ | CBUInt64 n -> arg_types (UInt64 n)
+ | CBString n -> arg_types (String n)
+ | CBBytesIn (n1, n2) -> arg_types (BytesIn (n1, n2))
+ | CBMutable arg -> arg_types arg |> List.map (fun x -> "*mut " ^ x)
+ | CBArrayAndLen (elem, _) ->
+ let elem_type =
+ match arg_types elem with
+ | [ x ] -> x
+ | _ -> failwith "Bad array element type"
+ in
+ [ sprintf "*mut %s" elem_type; "usize" ]
+
+(** Get a return type. *)
+let ret_type : ret -> string = function
+ | RBool -> "c_int"
+ | RStaticString -> "*const c_char"
+ | RInt | RErr | RFd | REnum _ -> "c_int"
+ | RInt64 | RCookie -> "i64"
+ | RSizeT -> "isize"
+ | RString -> "*mut c_char"
+ | RUInt -> "c_uint"
+ | RUInt64 -> "u64"
+ | RUIntPtr -> "uintptr_t"
+ | RFlags _ -> "u32"
+
+(** The names of all arguments corresponding to an [arg]. *)
+let 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 ]
+ | SockAddrAndLen (n1, n2)
+ | BytesIn (n1, n2)
+ | BytesPersistIn (n1, n2)
+ | BytesOut (n1, n2)
+ | BytesPersistOut (n1, n2) ->
+ [ n1; n2 ]
+
+(** The name of an optional argument. *)
+let optarg_name : optarg -> string = function
+ | OClosure { cbname = name } | OFlags (name, _, _) -> name
+
+(** Print the struct for a closure. *)
+let print_closure_struct { cbname; cbargs } =
+ pr "#[repr(C)]\n";
+ pr "#[derive(Debug, Clone, Copy)]\n";
+ pr "pub struct nbd_%s_callback {\n" cbname;
+ pr " pub callback: \n";
+ pr " Option<unsafe extern \"C\" fn(*mut c_void, %s) ->
c_int>,\n"
+ (cbargs |> List.map cbarg_types |> List.flatten |> String.concat ",
");
+ pr " pub user_data: *mut c_void,\n";
+ pr " pub free: Option<unsafe extern \"C\" fn(*mut
c_void)>,\n";
+ pr "}\n"
+
+(** Print an "extern definition" for a handle call. *)
+let print_handle_call (name, call) =
+ let args_names =
+ (call.args |> List.map arg_names |> List.flatten)
+ @ (call.optargs |> List.map optarg_name)
+ in
+ let args_types =
+ (call.args |> List.map arg_types |> List.flatten)
+ @ (call.optargs |> List.map optarg_type)
+ in
+ pr "pub fn nbd_%s(handle: *mut nbd_handle, %s) -> %s;\n" name
+ (List.map2 (fun n ty -> sprintf "%s: %s" n ty) args_names args_types
+ |> String.concat ", ")
+ (ret_type call.ret)
+
+(** Print a definition of the "nbd_handle" type. *)
+let print_nbd_handle () =
+ pr "#[repr(C)]\n";
+ pr "#[derive(Debug, Clone, Copy)]\n";
+ pr "pub struct nbd_handle {\n";
+ pr " _unused: [u8; 0],\n";
+ pr "}\n";
+ pr "\n"
+
+(** Print some more "extern definitions". *)
+let print_more_defs () =
+ pr "extern \"C\" {\n";
+ pr "pub fn nbd_get_error() -> *const c_char;\n";
+ pr "pub fn nbd_get_errno() -> c_int;\n";
+ pr "pub fn nbd_create() -> *mut nbd_handle;\n";
+ pr "pub fn nbd_close(h: *mut nbd_handle);\n";
+ pr "}\n";
+ pr "\n"
+
+let print_imports () =
+ pr "use libc::*;\n";
+ pr "use std::ffi::c_void;\n";
+ pr "\n"
+
+let generate_rust_sys_bindings () =
+ generate_header CStyle ~copyright:"Tage Johansson";
+ pr "\n";
+ print_imports ();
+ print_nbd_handle ();
+ print_more_defs ();
+ all_closures |> List.iter print_closure_struct;
+ pr "extern \"C\" {\n";
+ handle_calls |> List.iter print_handle_call;
+ pr "}\n\n"
diff --git a/generator/RustSys.mli b/generator/RustSys.mli
new file mode 100644
index 0000000..de66bdc
--- /dev/null
+++ b/generator/RustSys.mli
@@ -0,0 +1,19 @@
+(* 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
+ *)
+
+val generate_rust_sys_bindings : unit -> unit
diff --git a/generator/generator.ml b/generator/generator.ml
index c73824e..f5ef7cc 100644
--- a/generator/generator.ml
+++ b/generator/generator.ml
@@ -61,3 +61,6 @@ 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/libnbd-sys/src/lib.rs"
RustSys.generate_rust_sys_bindings;
+ 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..c745972
--- /dev/null
+++ b/rust/Cargo.toml
@@ -0,0 +1,49 @@
+# 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]
+authors = ["Tage Johansson"]
+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"
+authors.workspace = true
+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"
+thiserror = "1.0.40"
+log = { version = "0.4.19", optional = true }
+libc = "0.2.147"
+
+[features]
+default = ["log"]
diff --git a/rust/Makefile.am b/rust/Makefile.am
new file mode 100644
index 0000000..8615794
--- /dev/null
+++ b/rust/Makefile.am
@@ -0,0 +1,76 @@
+# 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 = \
+ libnbd-sys/src/lib.rs \
+ 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 \
+ libnbd-sys/Cargo.toml \
+ libnbd-sys/build.rs \
+ libnbd-sys/src/.keep \
+ cargo_test/Cargo.toml \
+ cargo_test/src/lib.rs \
+ cargo_test/README.md \
+ run-tests.sh.in \
+ $(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
+
+clean-local:
+ $(CARGO) clean
+ $(CARGO) clean --manifest-path cargo_test/Cargo.toml
+
+endif
+
+CLEANFILES += libnbd-sys/libnbd_version
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/Cargo.toml b/rust/libnbd-sys/Cargo.toml
new file mode 100644
index 0000000..3fa581f
--- /dev/null
+++ b/rust/libnbd-sys/Cargo.toml
@@ -0,0 +1,32 @@
+# 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]
+pkg-config = "0.3.27"
+
+[dependencies]
+libc = "0.2.147"
diff --git a/rust/libnbd-sys/build.rs b/rust/libnbd-sys/build.rs
new file mode 100644
index 0000000..46a39b7
--- /dev/null
+++ b/rust/libnbd-sys/build.rs
@@ -0,0 +1,26 @@
+// 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::fs;
+
+fn main() {
+ let libnbd_version = fs::read_to_string("libnbd_version").unwrap();
+ pkg_config::Config::new()
+ .atleast_version(libnbd_version.trim())
+ .probe("libnbd")
+ .unwrap();
+}
diff --git a/rust/libnbd-sys/src/.keep b/rust/libnbd-sys/src/.keep
new file mode 100644
index 0000000..e69de29
diff --git a/rust/run-tests.sh.in b/rust/run-tests.sh.in
new file mode 100755
index 0000000..f4d220c
--- /dev/null
+++ b/rust/run-tests.sh.in
@@ -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 -- --nocapture
diff --git a/rust/src/error.rs b/rust/src/error.rs
new file mode 100644
index 0000000..631d731
--- /dev/null
+++ b/rust/src/error.rs
@@ -0,0 +1,157 @@
+// 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, NulError};
+use std::io;
+
+/// A general error type for libnbd.
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ /// Non fatal errors used when a command failed but the handle is not dead.
+ ///
+ /// If such an error is returned, it may still makes sense to call further
+ /// commands on the handle.
+ #[error(transparent)]
+ Recoverable(ErrorKind),
+ /// A fatal error. After such an error, the handle is dead and there is no
+ /// point in issuing further commands.
+ #[error("Fatal: NBD handle is dead: {0}")]
+ Fatal(FatalErrorKind),
+}
+
+/// An error kind for a Libnbd related error.
+#[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),
+}
+
+/// The kind of a fatal error.
+#[derive(Debug, thiserror::Error)]
+pub enum FatalErrorKind {
+ /// A Libnbd related error.
+ #[error(transparent)]
+ Libnbd(#[from] ErrorKind),
+ /// Some other io error.
+ #[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::Libnbd(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::Libnbd(e)) => {
+ e.errno()
+ }
+ Self::Fatal(FatalErrorKind::Io(e)) => e.raw_os_error(),
+ }
+ }
+
+ /// Check if this is a fatal error.
+ pub fn is_fatal(&self) -> bool {
+ match self {
+ Self::Fatal(_) => true,
+ Self::Recoverable(_) => false,
+ }
+ }
+
+ /// Check if this is a recoverable error.
+ pub fn is_recoverable(&self) -> bool {
+ match self {
+ Self::Recoverable(_) => true,
+ Self::Fatal(_) => false,
+ }
+ }
+
+ /// Turn this error to a [FatalErrorKind].
+ pub fn to_fatal(self) -> FatalErrorKind {
+ match self {
+ Self::Fatal(e) => e,
+ Self::Recoverable(e) => FatalErrorKind::Libnbd(e),
+ }
+ }
+}
+
+impl From<io::Error> for Error {
+ fn from(err: io::Error) -> Self {
+ Self::Fatal(err.into())
+ }
+}
+
+impl From<String> for Error {
+ fn from(description: String) -> Self {
+ Self::Recoverable(ErrorKind::WithoutErrno { description })
+ }
+}
+
+impl From<NulError> for Error {
+ fn from(e: NulError) -> Self {
+ e.to_string().into()
+ }
+}
diff --git a/rust/src/handle.rs b/rust/src/handle.rs
new file mode 100644
index 0000000..eecc593
--- /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: String::from_utf8_lossy(func_name).as_ref(),
+ "{}",
+ String::from_utf8_lossy(msg)
+ );
+ 0
+ })?;
+ nbd.set_debug(true)?;
+ }
+ Ok(nbd)
+ }
+ }
+
+ /// Get the underlying 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
diff --git a/scripts/git.orderfile b/scripts/git.orderfile
index a5a5f26..9dd3d54 100644
--- a/scripts/git.orderfile
+++ b/scripts/git.orderfile
@@ -61,6 +61,16 @@ common/*
# Language bindings.
python/*
ocaml/*
+rust/Cargo.toml
+rust/Makefile.am
+rust/run-tests.sh
+rust/src/error.rs
+rust/src/types.rs
+rust/src/utils.rs
+rust/src/lib.rs
+rust/src/handle.rs
+rust/libnbd-sys/*
+rust/tests/*
# Tests.
tests/*
--
2.41.0