Tage Johansson
2023-Aug-02 12:40 UTC
[Libguestfs] [libnbd PATCH v4 01/11] rust: create basic Rust bindings
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 | 7 + .ocamlformat | 4 + Makefile.am | 1 + configure.ac | 13 + 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 | 50 ++++ rust/Makefile.am | 69 +++++ rust/libnbd-sys/Cargo.toml | 32 +++ rust/libnbd-sys/build.rs | 26 ++ rust/libnbd-sys/src/.keep | 0 rust/run-tests.sh | 24 ++ rust/src/error.rs | 154 +++++++++++ rust/src/handle.rs | 65 +++++ rust/src/lib.rs | 28 ++ rust/src/types.rs | 18 ++ rust/src/utils.rs | 23 ++ rustfmt.toml | 19 ++ 22 files changed, 1294 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/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 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..30bce94 100644 --- a/.gitignore +++ b/.gitignore @@ -174,6 +174,13 @@ 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 /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..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..3117980 --- /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 : 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 "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 : 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 : 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_raw_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 : 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 + +(* 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 : 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 } : 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 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/RustSys.ml b/generator/RustSys.ml new file mode 100644 index 0000000..a357b52 --- /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) : string * 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..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..5160e9e --- /dev/null +++ b/rust/Makefile.am @@ -0,0 +1,69 @@ +# 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 \ + $(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..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 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..3087896 --- /dev/null +++ b/rust/src/error.rs @@ -0,0 +1,154 @@ +// 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. + #[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..e26755c --- /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 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
Richard W.M. Jones
2023-Aug-02 15:42 UTC
[Libguestfs] [libnbd PATCH v4 01/11] rust: create basic Rust bindings
On Wed, Aug 02, 2023 at 12:40:46PM +0000, Tage Johansson wrote:> 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.It looks as if rust-bindgen isn't in fact being used (as you removed it, per the cover letter). There's just one mention of it in a comment. So I think this paragraph should be removed from the commit message. By the way we don't tend to use markup in the commit messages (although I suppose github/gitlab will understand it). I have no particular strong preference about this, but it's not standard.> 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 | 7 + > .ocamlformat | 4 + > Makefile.am | 1 + > configure.ac | 13 + > 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 | 50 ++++ > rust/Makefile.am | 69 +++++ > rust/libnbd-sys/Cargo.toml | 32 +++ > rust/libnbd-sys/build.rs | 26 ++ > rust/libnbd-sys/src/.keep | 0 > rust/run-tests.sh | 24 ++ > rust/src/error.rs | 154 +++++++++++ > rust/src/handle.rs | 65 +++++ > rust/src/lib.rs | 28 ++ > rust/src/types.rs | 18 ++ > rust/src/utils.rs | 23 ++ > rustfmt.toml | 19 ++ > 22 files changed, 1294 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/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 > 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..30bce94 100644 > --- a/.gitignore > +++ b/.gitignore > @@ -174,6 +174,13 @@ 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 > /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"]) > +So I think earlier we found that this wasn't sufficient to make rust actually work: https://listman.redhat.com/archives/libguestfs/2023-July/032121.html How about adding back the test that the compiler can build binaries from an earlier version? We have to cope with the environment we find. We don't control the many and varied environments that libnbd is compiled in. We can't have a situation like the one in the email above where ./configure passes but compilation then breaks. [...]> +(* Return type for a Rust function. *) > +let rust_ret_type (call : 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_typeIf there's no possible error, don't need to use a Result<> wrapper. Looks sensible.> +(* 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)I don't know if I mentioned this one before, and TBH it's a matter of your preference, but is using '(arg : arg)' (ie. declaring the named parameter 'arg' to have type 'arg') necessary? My editor tells me the type of OCaml expressions if I select them and use C-c C-t, and even if it didn't here it's pretty obvious what the type is.> +(* Print the ,comment for a rust function for a handle call. *) > +let print_rust_handle_call_comment callExcess comma (',') in the OCaml comment here?> +(* 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. *)Here we're using markdown, I guess, but ocamldoc comments prefer to use [ ... ] instead of ` ... ` (although we don't use ocamldoc really).> diff --git a/generator/RustSys.ml b/generator/RustSys.ml > new file mode 100644 > index 0000000..a357b52 > --- /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. *)This is what replaces rust-bindgen?> 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"If you wanted to (and it may or may not be a good idea) you could include the actual version of libnbd here. You'd need to move rust/Cargo.toml to rust/Cargo.toml.in and add an autoconf AC_CONFIG_FILES directive to near the end of configure.in.> +[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"Seems like the dependencies are pretty light ... good!> diff --git a/rust/Makefile.am b/rust/Makefile.am > new file mode 100644 > index 0000000..5160e9e > --- /dev/null > +++ b/rust/Makefile.am > @@ -0,0 +1,69 @@ > +# 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 \ > + $(NULL) > + > +EXTRA_DIST = \ > + $(source_files) \ > + $(NULL)This is mostly all good, but I think you will need to add libnbd-sys/src/.keep to EXTRA_DIST. You can check by doing: make && make dist && make maintainer-check-extra-dist> +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) docAnd I think this is now split up as requested before, so good too.> +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) cleanThis needs to be inside the if HAVE_RUST clause, since it requires $(CARGO) and the general test in ./configure that rust was OK.> +CLEANFILES += libnbd-sys/libnbd_versionBut this can stay outside since it just does a regular 'rm'.> 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 testAlthough it's not a very big deal, it'd be better if @CARGO@ was used here. The reason is that you could in theory do something like: ./configure CARGO=/usr/bin/my-special-cargo but the tests would run just cargo (not /usr/bin/my-special-cargo) test. However getting there is tricky. You have to rename rust/run-tests.sh to rust/run-tests.sh.in, and then add that to AC_CONFIG_FILES in configure.ac. Also I wonder if this breaks bisection? Since there are no tests yet.> diff --git a/rust/src/error.rs b/rust/src/error.rs > new file mode 100644 > index 0000000..3087896 > --- /dev/null > +++ b/rust/src/error.rs[...]> + > +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) > + } > + }Interesting ... What's the Fatal/Recoverable distinction used for?> + /// 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, > + } > + }... I guess here, but what's this used for?> + /// 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..e26755c > --- /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, > +}I think this (Debug trait) explains why we're hiding the various debug calls, so that seems fine.> +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 underliing C pointer to the handle."underlying"> + 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.0Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com nbdkit - Flexible, fast NBD server with plugins https://gitlab.com/nbdkit/nbdkit