Tage Johansson
2023-Aug-03 15:36 UTC
[Libguestfs] [libnbd PATCH v5 01/12] 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/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 + + at 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
Richard W.M. Jones
2023-Aug-04 08:54 UTC
[Libguestfs] [libnbd PATCH v5 01/12] rust: create basic Rust bindings
I applied the first 4 patches to my local repo. 'make clean' now fails at: error: failed to load manifest for dependency `libnbd-sys` Caused by: failed to parse manifest at `/home/rjones/d/libnbd/rust/libnbd-sys/Cargo.toml` Caused by: no targets specified in the manifest either src/lib.rs, src/main.rs, a [lib] section, or [[bin]] section must be present make[1]: *** [Makefile:1057: clean-local] Error 101 make[1]: Leaving directory '/home/rjones/d/libnbd/rust' I'm not sure what that means. rust/libnbd-sys/Cargo.toml exists. ./configure works which is good: checking for cargo... cargo checking for rustfmt... rustfmt checking if cargo is usable... yes ... Rust ................................... yes However the generator fails to compile with: make[3]: Entering directory '/home/rjones/d/libnbd/generator' ocamlc.opt -g -annot -safe-string -warn-error +C+D+E+F+L+M+P+S+U+V+Y+Z+X+52-3 -I . -I . \ -I +str str.cma -I +unix unix.cma utils.mli utils.ml state_machine.mli state_machine.ml API.mli API.ml state_machine_generator.mli state_machine_generator.ml C.mli C.ml Python.mli Python.ml OCaml.mli OCaml.ml GoLang.mli GoLang.ml RustSys.mli RustSys.ml Rust.mli Rust.ml generator.ml -o generator File "generator.ml", line 65, characters 2-11: 65 | output_to ~formatter:(Some Rustfmt) "rust/libnbd-sys/src/lib.rs" RustSys.generate_rust_sys_bindings; ^^^^^^^^^ Error: This function has type string -> (unit -> 'weak1) -> unit It is applied to too many arguments; maybe you forgot a `;'. This confused me for a while, but I think it's because this series depends on another series or needs to be rebased? I removed the ~formatter parameter to get it to compile. The next problem is: /home/rjones/d/libnbd/run cargo build Compiling libnbd v0.1.0 (/home/rjones/d/libnbd/rust) error: expected one of `:`, `;`, or `=`, found `-` --> src/bindings.rs:116:29 | 116 | pub const CONTEXT_QEMU_DIRTY-BITMAP:: &[u8] = b"qemu:dirty-bitmap:"; | ^ expected one of `:`, `;`, or `=` error[E0599]: no method named `set_debug_callback` found for struct `Handle` in the current scope --> src/handle.rs:38:21 | 23 | pub struct Handle { | ----------------- method `set_debug_callback` not found for this struct ... 38 | nbd.set_debug_callback(|func_name, msg| { | ----^^^^^^^^^^^^^^^^^^ method not found in `Handle` [and some more] It might be an idea to run this command to check that the series is bisectable: git rebase -i HEAD~12 -x 'make clean && make && make check' It will generate a series of rebase commands which look like: pick 7475e8560a rust: create basic Rust bindings exec make clean && make && make check pick c48c7eee0f rust: Add a couple of integration tests exec make clean && make && make check pick 16debe7848 rust: Make it possible to run tests with Valgrind exec make clean && make && make check [etc] which will do a full build cycle after every patch to make sure they all work incrementally. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-p2v converts physical machines to virtual machines. Boot with a live CD or over the network (PXE) and turn machines into KVM guests. http://libguestfs.org/virt-v2v
Eric Blake
2023-Aug-11 12:00 UTC
[Libguestfs] [libnbd PATCH v5 01/12] rust: create basic Rust bindings
On Thu, Aug 03, 2023 at 03:36:05PM +0000, Tage Johansson wrote:> This commit creates basic Rust bindings in the rust directory. > The bindings are generated by generator/Rust.ml and > generator/RustSys.ml. > ---> --- /dev/null > +++ b/generator/RustSys.ml> +(** 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"Why is 'callback' an Option<> rather than a mandatory argument? I get that 'free' must be an Option<> (because it corresponds to an OClosure, which is an optional callback), but 'callback' is a mandatory function pointer in the C API; why would it ever be acceptable to pass None instead of Some<function>? -- Eric Blake, Principal Software Engineer Red Hat, Inc. Virtualization: qemu.org | libguestfs.org
Eric Blake
2023-Aug-11 13:22 UTC
[Libguestfs] [libnbd PATCH v5 01/12] rust: create basic Rust bindings
On Thu, Aug 03, 2023 at 03:36:05PM +0000, Tage Johansson wrote:> This commit creates basic Rust bindings in the rust directory. > The bindings are generated by generator/Rust.ml and > generator/RustSys.ml. > ---> +++ 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 says LGPLv2+...> +# > +# 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"...but this does not. Why the discrepancy? It is a disservice to clients to have a more restrictive license for the Rust bindings than for the rest of libnbd. -- Eric Blake, Principal Software Engineer Red Hat, Inc. Virtualization: qemu.org | libguestfs.org