Tage Johansson
2023-Jul-19 09:09 UTC
[Libguestfs] [libnbd PATCH 02/10] 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 | 8 + .ocamlformat | 4 + Makefile.am | 1 + configure.ac | 13 + generator/Makefile.am | 2 + generator/Rust.ml | 582 +++++++++++++++++++++++++++++++++++++ generator/Rust.mli | 20 ++ generator/generator.ml | 2 + rust/Cargo.toml | 50 ++++ rust/Makefile.am | 60 ++++ rust/libnbd-sys/Cargo.toml | 30 ++ rust/libnbd-sys/build.rs | 60 ++++ rust/libnbd-sys/src/lib.rs | 24 ++ rust/libnbd-sys/wrapper.h | 18 ++ rust/run-tests.sh | 24 ++ rust/src/error.rs | 111 +++++++ rust/src/handle.rs | 65 +++++ rust/src/lib.rs | 28 ++ rust/src/types.rs | 18 ++ rust/src/utils.rs | 23 ++ rustfmt.toml | 19 ++ 21 files changed, 1162 insertions(+) create mode 100644 .ocamlformat create mode 100644 generator/Rust.ml create mode 100644 generator/Rust.mli create mode 100644 rust/Cargo.toml create mode 100644 rust/Makefile.am create mode 100644 rust/libnbd-sys/Cargo.toml create mode 100644 rust/libnbd-sys/build.rs create mode 100644 rust/libnbd-sys/src/lib.rs create mode 100644 rust/libnbd-sys/wrapper.h create mode 100755 rust/run-tests.sh create mode 100644 rust/src/error.rs create mode 100644 rust/src/handle.rs create mode 100644 rust/src/lib.rs create mode 100644 rust/src/types.rs create mode 100644 rust/src/utils.rs create mode 100644 rustfmt.toml diff --git a/.gitignore b/.gitignore index efe3080..098e004 100644 --- a/.gitignore +++ b/.gitignore @@ -174,6 +174,14 @@ Makefile.in /python/nbd.py /python/run-python-tests /run +/rust/Cargo.lock +/rust/libnbd-sys/Cargo.lock +/rust/libnbd-sys/libnbd_version +/rust/libnbd-sys/src/bindings.rs +/rust/libnbd-sys/target +/rust/src/async_bindings.rs +/rust/src/bindings.rs +/rust/target /sh/nbdsh /sh/nbdsh.1 /stamp-h1 diff --git a/.ocamlformat b/.ocamlformat new file mode 100644 index 0000000..7bfe155 --- /dev/null +++ b/.ocamlformat @@ -0,0 +1,4 @@ +profile = default +version = 0.25.1 +wrap-comments = true +margin = 78 diff --git a/Makefile.am b/Makefile.am index 243fabd..9e1790b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -55,6 +55,7 @@ SUBDIRS = \ ocaml/tests \ golang \ golang/examples \ + rust \ interop \ fuzzing \ bash-completion \ diff --git a/configure.ac b/configure.ac index 0b94f5e..0004f25 100644 --- a/configure.ac +++ b/configure.ac @@ -613,6 +613,17 @@ AS_IF([test "x$enable_golang" != "xno"],[ ],[GOLANG=no]) AM_CONDITIONAL([HAVE_GOLANG],[test "x$GOLANG" != "xno"]) +dnl Rust. +AC_ARG_ENABLE([rust], + AS_HELP_STRING([--disable-rust], [disable Rust language bindings]), + [], + [enable_rust=yes]) +AS_IF([test "x$enable_rust" != "xno"],[ + AC_CHECK_PROG([CARGO],[cargo],[cargo],[no]) + AC_CHECK_PROG([RUSTFMT],[rustfmt],[rustfmt],[no]) +],[CARGO=no]) +AM_CONDITIONAL([HAVE_RUST],[test "x$CARGO" != "xno" -a "x$RUSTFMT" != "xno"]) + AC_MSG_CHECKING([for how to mark DSO non-deletable at runtime]) NODELETE `$LD --help 2>&1 | grep -- "-z nodelete" >/dev/null` && \ @@ -657,6 +668,7 @@ AC_CONFIG_FILES([Makefile generator/Makefile golang/Makefile golang/examples/Makefile + rust/Makefile include/Makefile info/Makefile interop/Makefile @@ -717,6 +729,7 @@ echo echo "Language bindings:" echo feature "Go" test "x$HAVE_GOLANG_TRUE" = "x" +feature "Rust" test "x$HAVE_RUST_TRUE" = "x" feature "OCaml" test "x$HAVE_OCAML_TRUE" = "x" feature "Python" test "x$HAVE_PYTHON_TRUE" = "x" diff --git a/generator/Makefile.am b/generator/Makefile.am index c3d53b2..5b93734 100644 --- a/generator/Makefile.am +++ b/generator/Makefile.am @@ -60,6 +60,8 @@ sources = \ OCaml.ml \ GoLang.mli \ GoLang.ml \ + Rust.mli \ + Rust.ml \ generator.ml \ $(NULL) diff --git a/generator/Rust.ml b/generator/Rust.ml new file mode 100644 index 0000000..d0d1562 --- /dev/null +++ b/generator/Rust.ml @@ -0,0 +1,582 @@ +(* hey emacs, this is OCaml code: -*- tuareg -*- *) +(* nbd client library in userspace: generator + * Copyright Tage Johansson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + *) + +(* Rust language bindings. *) + +open Printf +open API +open Utils + +(* The type for a set of names. *) +module NameSet = Set.Make (String) + +(* List of handle calls which should not be part of the public API. This could + for instance be `set_debug` and `set_debug_callback` which are handled + separately by the log crate *) +let hidden_handle_calls : NameSet.t + NameSet.of_list + [ "get_debug"; "set_debug"; "set_debug_callback"; "clear_debug_callback" ] + +let print_rust_constant (name, value) + pr "pub const %s: u32 = %d;\n" name value + +let print_rust_enum (enum : enum) + pr "#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n"; + pr "#[repr(isize)]"; + pr "pub enum %s {\n" (camel_case enum.enum_prefix); + List.iter + (fun (name, num) -> pr " %s = %d,\n" (camel_case name) num) + enum.enums; + pr "}\n\n" + +(* Print a Rust struct for a set of flags. *) +let print_rust_flags ({ flag_prefix; flags } : flags) + pr "bitflags! {\n"; + pr " #[repr(C)]\n"; + pr " #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n"; + pr " pub struct %s: u32 {\n" (camel_case flag_prefix); + List.iter + (fun (name, value) -> pr " const %s = %d;\n" name value) + flags; + pr " }\n"; + pr "}\n\n" + +(* Print metadata namespaces. *) +let print_metadata_namespace (ns, ctxts) + pr "/// The string \"%s:\" as a &[CStr].\n" ns; + pr "pub const NAMESPACE_%s: &CStr = c_str!(\"%s:\");\n" + (String.uppercase_ascii ns) + ns; + List.iter + (fun (ctxt, consts) -> + let s = ns ^ ":" ^ ctxt in + pr "/// The string \"%s\" as a &[CStr].\n" s; + pr "pub const CONTEXT_%s_%s: &CStr = c_str!(\"%s\");\n" + (String.uppercase_ascii ns) + (String.uppercase_ascii ctxt) + s; + List.iter + (fun (n, i) -> + pr "pub const %s: u32 = %d;\n" (String.uppercase_ascii n) i) + consts) + ctxts + +(* Get the name of a rust argument. *) +let rust_arg_name : arg -> string = function + | Bool n + | Int n + | UInt n + | UIntPtr n + | UInt32 n + | Int64 n + | UInt64 n + | SizeT n + | String n + | StringList n + | Path n + | Fd n + | Enum (n, _) + | Flags (n, _) + | SockAddrAndLen (n, _) + | BytesIn (n, _) + | BytesPersistIn (n, _) + | BytesOut (n, _) + | BytesPersistOut (n, _) + | Closure { cbname = n } -> + n + +(* Get the name of a rust optional argument. *) +let rust_optarg_name : optarg -> string = function + | OClosure { cbname = n } | OFlags (n, _, _) -> n + +(* Get the name of a Rust closure argument. *) +let rust_cbarg_name : cbarg -> string = function + | CBInt n | CBUInt n | CBInt64 n | CBUInt64 n | CBString n | CBBytesIn (n, _) + -> + n + | CBArrayAndLen (arg, _) | CBMutable arg -> rust_arg_name arg + +(* Get the Rust type for an argument. *) +let rec rust_arg_type : arg -> string = function + | Bool _ -> "bool" + | Int _ -> "c_int" + | UInt _ -> "c_uint" + | UIntPtr _ -> "usize" + | UInt32 _ -> "u32" + | Int64 _ -> "i64" + | UInt64 _ -> "u64" + | SizeT _ -> "usize" + | String _ -> "&CStr" + | SockAddrAndLen _ -> "SocketAddr" + | StringList _ -> "&[&CStr]" + | Path _ -> "&PathBuf" + | Enum (_, { enum_prefix = name }) | Flags (_, { flag_prefix = name }) -> + camel_case name + | Fd _ -> "OwnedFd" + | BytesIn _ -> "&[u8]" + | BytesOut _ -> "&mut [u8]" + | BytesPersistIn _ -> "&'static [u8]" + | BytesPersistOut _ -> "&'static mut [u8]" + | Closure { cbargs } -> "impl " ^ rust_closure_trait cbargs + +(* Get the Rust closure trait for a callback, That is `Fn*(...) -> ...)`. *) +and rust_closure_trait ?(lifetime = Some "'static") cbargs : string + let rust_cbargs = String.concat ", " (List.map rust_cbarg_type cbargs) + and lifetime_constraint + match lifetime with None -> "" | Some x -> " + " ^ x + in + "FnMut(" ^ rust_cbargs ^ ") -> c_int + Send + Sync" ^ lifetime_constraint + +(* Get the Rust type for a callback argument. *) +and rust_cbarg_type : cbarg -> string = function + | CBInt n -> rust_arg_type (Int n) + | CBUInt n -> rust_arg_type (UInt n) + | CBInt64 n -> rust_arg_type (Int64 n) + | CBUInt64 n -> rust_arg_type (UInt64 n) + | CBString n -> rust_arg_type (String n) + | CBBytesIn (n1, n2) -> rust_arg_type (BytesIn (n1, n2)) + | CBArrayAndLen (elem, _) -> "&[" ^ rust_arg_type elem ^ "]" + | CBMutable arg -> "&mut " ^ rust_arg_type arg + +(* Get the type of a rust optional argument. *) +let rust_optarg_type : optarg -> string = function + | OClosure x -> sprintf "Option<%s>" (rust_arg_type (Closure x)) + | OFlags (name, flags, _) -> + sprintf "Option<%s>" (rust_arg_type (Flags (name, flags))) + +(* Given an argument, produce a list of names for arguments in FFI functions + corresponding to that argument. Most arguments will just produce one name + for one FFI argument, but for example [BytesIn] requires two separate FFI + arguments hence a list is produced. *) +let ffi_arg_names : arg -> string list = function + | Bool n + | Int n + | UInt n + | UIntPtr n + | UInt32 n + | Int64 n + | UInt64 n + | SizeT n + | String n + | StringList n + | Path n + | Fd n + | Enum (n, _) + | Flags (n, _) + | Closure { cbname = n } -> + [ n ^ "_ffi" ] + | SockAddrAndLen (n1, n2) + | BytesIn (n1, n2) + | BytesPersistIn (n1, n2) + | BytesOut (n1, n2) + | BytesPersistOut (n1, n2) -> + [ n1 ^ "_ffi"; n2 ^ "_ffi" ] + +let ffi_optarg_name : optarg -> string = function + | OClosure { cbname = name } | OFlags (name, _, _) -> name ^ "_ffi" + +(* Given a closure argument, produce a list of names used by FFI functions for + that particular argument. Most closure arguments will just produce one FFI + argument, but for instance [CBArrayAndLen] will produce two, hence we + return a list. *) +let ffi_cbarg_names : cbarg -> string list = function + | CBInt n | CBUInt n | CBInt64 n | CBUInt64 n | CBString n -> [ n ^ "_ffi" ] + | CBBytesIn (n1, n2) -> [ n1 ^ "_ffi"; n2 ^ "_ffi" ] + | CBArrayAndLen (arg, len) -> [ rust_arg_name arg ^ "_ffi"; len ^ "_ffi" ] + | CBMutable arg -> [ rust_arg_name arg ^ "_ffi" ] + +(* Given a closure argument, produce a list of types used by FFI functions for + that particular argument. Most closure arguments will just produce one FFI + argument, but for instance [CBArrayAndLen] will produce two, hence we + return a list. *) +let ffi_cbarg_types : cbarg -> string list = function + | CBInt _ -> [ "c_int" ] + | CBUInt _ -> [ "c_uint" ] + | CBInt64 _ -> [ "i64" ] + | CBUInt64 _ -> [ "u64" ] + | CBString _ -> [ "*const c_char" ] + | CBBytesIn _ -> [ "*const c_void"; "usize" ] + | CBArrayAndLen (UInt32 _, _) -> [ "*mut u32"; "usize" ] + | CBArrayAndLen _ -> + failwith + "generator/Rust.ml: in ffi_cbarg_types: Unsupported type of array \ + element." + | CBMutable (Int _) -> [ "*mut c_int" ] + | CBMutable _ -> + failwith + "generator/Rust.ml: in ffi_cbarg_types: Unsupported type of mutable \ + argument." + +(* Return type for a Rust function. *) +let rust_ret_type (call : call) : string + let core_type + match call.ret with + | RBool -> "bool" + | RStaticString -> "&'static CStr" + | RErr -> "()" + | RFd -> "RawFd" + | RInt -> "c_uint" + | RInt64 -> "u64" + | RCookie -> "Cookie" + | RSizeT -> "usize" + | RString -> "CString" + | RUInt -> "c_uint" + | RUIntPtr -> "usize" + | RUInt64 -> "u64" + | REnum { enum_prefix = name } | RFlags { flag_prefix = name } -> + camel_case name + in + if call.may_set_error then sprintf "Result<%s>" core_type else core_type + +(* Given an argument ([arg : arg]), print Rust code for variable declarations + for all FFI arguments corresponding to [arg]. That is, for each + `<FFI_NAME>` in [ffi_arg_names arg], print `let <FFI_NAME> = <...>;`. + Assuming that a variable with name [rust_arg_name arg] and type + [rust_arg_type arg] exists in scope. *) +let rust_arg_to_ffi (arg : arg) + let rust_name = rust_arg_name arg in + let ffi_names = ffi_arg_names arg in + match arg with + | Bool _ | Int _ | UInt _ | UIntPtr _ | UInt32 _ | Int64 _ | UInt64 _ + | SizeT _ -> + let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in + pr "let %s = %s;\n" ffi_name rust_name + | Enum _ -> + let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in + pr "let %s = %s as c_int;\n" ffi_name rust_name + | Flags _ -> + let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in + pr "let %s = %s.bits();\n" ffi_name rust_name + | String _ -> + let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in + pr "let %s = %s.as_ptr();\n" ffi_name rust_name + | SockAddrAndLen _ -> + let ffi_addr_name, ffi_len_name + match ffi_names with [ x; y ] -> (x, y) | _ -> assert false + in + pr "let %s_os = OsSocketAddr::from(%s);\n" rust_name rust_name; + (* We assume here that the bindgen generated `sys::sockaddr` is + equivalent to `libc::sockaddr`. *) + pr "let %s = %s_os.as_ptr() as *const sys::sockaddr;\n" ffi_addr_name + rust_name; + pr "let %s = %s_os.len();\n" ffi_len_name rust_name + | StringList _ -> + let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in + (* Convert a slice to a null terminated slice. For some reason the C + functions have not marked the pointers as const, so we use `cast_mut` + and `as_mut_ptr` here even though the strings shouldn't be + modified. *) + pr + "let mut %s_vec: Vec<*mut c_char> = %s.iter().map(|x| \ + x.as_ptr().cast_mut()).collect();\n" + ffi_name rust_name; + pr "%s_vec.push(ptr::null_mut());\n" ffi_name; + pr "let %s = %s_vec.as_mut_ptr();\n" ffi_name ffi_name + | Path _ -> + let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in + pr + "let %s_buf = \ + CString::new(%s.as_os_str().to_owned().into_raw_vec()).unwrap();\n" + rust_name rust_name; + pr "let %s = %s_buf.as_ptr();\n" ffi_name rust_name + | BytesIn _ | BytesPersistIn _ -> + let ffi_buf_name, ffi_len_name + match ffi_names with [ x; y ] -> (x, y) | _ -> assert false + in + pr "let %s = %s.as_ptr() as *const c_void;\n" ffi_buf_name rust_name; + pr "let %s = %s.len();\n" ffi_len_name rust_name + | BytesOut _ | BytesPersistOut _ -> + let ffi_buf_name, ffi_len_name + match ffi_names with [ x; y ] -> (x, y) | _ -> assert false + in + pr "let %s = %s.as_mut_ptr() as *mut c_void;\n" ffi_buf_name rust_name; + pr "let %s = %s.len();\n" ffi_len_name rust_name + | Fd _ -> + let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in + pr "let %s = %s.as_raw_fd();\n" ffi_name rust_name + | Closure _ -> + let ffi_name = match ffi_names with [ x ] -> x | _ -> assert false in + pr "let %s = unsafe { crate::bindings::%s_to_raw(%s) };\n" ffi_name + rust_name rust_name + +(* Same as [rust_arg_to_ffi] but for optional arguments. *) +let rust_optarg_to_ffi (arg : optarg) + let rust_name = rust_optarg_name arg in + let ffi_name = ffi_optarg_name arg in + match arg with + | OClosure { cbname } -> + pr "let %s = match %s {\n" ffi_name rust_name; + pr " Some(f) => unsafe { crate::bindings::%s_to_raw(f) },\n" + rust_name; + pr " None => sys::nbd_%s_callback { " cbname; + pr "callback: None, "; + pr "free: None, "; + pr "user_data: ptr::null_mut() "; + pr "},\n"; + pr "};\n" + | OFlags (_, { flag_prefix }, _) -> + let flags_type = camel_case flag_prefix in + pr "let %s = %s.unwrap_or(%s::empty()).bits();\n" ffi_name rust_name + flags_type + +(* Same as [rust_arg_to_ffi] but for closure args instead. The <I>th variable + will be named `<NAME>_ffi_<I>` where `<NAME>` is the name of the argument. + Keep in mind that this function may create multiple variables. *) +let rust_cbarg_to_ffi cbarg = function + | CBInt n -> rust_arg_to_ffi (Int n) + | CBUInt n -> rust_arg_to_ffi (UInt n) + | CBInt64 n -> rust_arg_to_ffi (Int64 n) + | CBUInt64 n -> rust_arg_to_ffi (UInt64 n) + | CBString n -> rust_arg_to_ffi (String n) + | CBBytesIn (n1, n2) -> rust_arg_to_ffi (BytesIn (n1, n2)) + | CBArrayAndLen (UInt32 _, _) -> + let ffi_arr_name, ffi_len_name + match ffi_cbarg_names cbarg with + | [ x; y ] -> (x, y) + | _ -> assert false + in + let rust_name = rust_cbarg_name cbarg in + pr "let %s = %s.as_ptr();\n" ffi_arr_name rust_name; + pr "let %s = %s.len();\n" ffi_len_name rust_name + | CBArrayAndLen _ -> + failwith + "generator/Rust.ml: in rust_cbarg_to_ffi: Unsupported type of array \ + element." + | CBMutable (Int _) -> + let ffi_name + match ffi_cbarg_names cbarg with [ x ] -> x | _ -> assert false + in + let rust_name = rust_cbarg_name cbarg in + pr "let %s = %s.as_mut_ptr();\n" ffi_name rust_name + | CBMutable _ -> + failwith + "generator/Rust.ml: in rust_cbarg_to_ffi: Unsupported type of \ + mutable argument." + +(* Given a closure argument ([x : cbarg]), print Rust code to create a + variable with name [rust_cbarg_name x] of type [rust_cbarg_type x]. + Assuming that variables with names from [ffi_cbarg_names x] exists in + scope. *) +let ffi_cbargs_to_rust cbarg + let ffi_names = ffi_cbarg_names cbarg in + pr "let %s: %s = " (rust_cbarg_name cbarg) (rust_cbarg_type cbarg); + (match (cbarg, ffi_names) with + | (CBInt _ | CBUInt _ | CBInt64 _ | CBUInt64 _), [ ffi_name ] -> + pr "%s" ffi_name + | CBString _, [ ffi_name ] -> pr "CStr::from_ptr(%s)" ffi_name + | CBBytesIn _, [ ffi_buf_name; ffi_len_name ] -> + pr "slice::from_raw_parts(%s as *const u8, %s)" ffi_buf_name + ffi_len_name + | CBArrayAndLen (UInt32 _, _), [ ffi_arr_name; ffi_len_name ] -> + pr "slice::from_raw_parts(%s, %s)" ffi_arr_name ffi_len_name + | CBArrayAndLen _, [ _; _ ] -> + failwith + "generator/Rust.ml: in ffi_cbargs_to_rust: Unsupported type of array \ + element." + | CBMutable (Int _), [ ffi_name ] -> pr "%s.as_mut().unwrap()" ffi_name + | CBMutable _, [ _ ] -> + failwith + "generator/Rust.ml: in ffi_cbargs_to_rust: Unsupported type of \ + mutable argument." + | _, _ -> + failwith + "generator/Rust.ml: In ffi_cbargs_to_rust: bad number of ffi \ + arguments."); + pr ";\n" + +(* Print Rust code for converting a return value from an FFI call to a Rusty + return value. In other words, given [x : ret], this functions print a Rust + expression with type [rust_ret_type x], with a free variable [ffi_ret] with + the return value from the FFI call. *) +let ffi_ret_to_rust (call : call) + let ret_type = rust_ret_type call in + let pure_expr + match call.ret with + | RBool -> "ffi_ret != 0" + | RErr -> "()" + | RInt -> "TryInto::<u32>::try_into(ffi_ret).unwrap()" + | RInt64 -> "TryInto::<u64>::try_into(ffi_ret).unwrap()" + | RSizeT -> "TryInto::<usize>::try_into(ffi_ret).unwrap()" + | RCookie -> "Cookie(ffi_ret.try_into().unwrap())" + | RFd -> "ffi_ret as RawFd" + | RStaticString -> "unsafe { CStr::from_ptr(ffi_ret) }" + | RString -> + "{ let res = unsafe { CStr::from_ptr(ffi_ret) }.to_owned();\n" + ^ "unsafe { libc::free(ffi_ret.cast()); }\n" ^ "res}" + | RFlags { flag_prefix } -> + sprintf "%s::from_bits(ffi_ret).unwrap()" ret_type + | RUInt | RUIntPtr | RUInt64 -> sprintf "ffi_ret as %s" ret_type + | REnum _ -> + (* We know that each enum is represented by an isize, hence this + transmute is safe. *) + sprintf "unsafe { mem::transmute::<isize, %s>(ffi_ret as isize) }" + ret_type + in + if call.may_set_error then ( + (match call.ret with + | RBool | RErr | RInt | RFd | RInt64 | RCookie | RSizeT -> + pr "if ffi_ret < 0 {\n"; + pr " Err(unsafe { Error::get_error(self.raw_handle()) })\n"; + pr "}\n" + | RStaticString | RString -> + pr "if ffi_ret.is_null() {\n"; + pr " Err(unsafe { Error::get_error(self.raw_handle()) })\n"; + pr "}\n" + | RUInt | RUIntPtr | RUInt64 | REnum _ | RFlags _ -> + failwith "In ffi_ret_to_rust: Return type cannot be an error."); + pr "else { Ok(%s) }\n" pure_expr) + else pr "%s\n" pure_expr + +(* This function prints a rust function which converts a rust closure to a + (`repr(C)`) struct containing the function pointer, a `*mut c_void` for the + closure data, and a free function for the closure data. This struct is what + will be sent to a C function taking the closure as an argument. In fact, + the struct itself is generated by rust-bindgen. *) +let print_rust_closure_to_raw_fn ({ cbname; cbargs } : closure) + let closure_trait = rust_closure_trait cbargs ~lifetime:None in + let ffi_cbargs_names = List.flatten (List.map ffi_cbarg_names cbargs) in + let ffi_cbargs_types = List.flatten (List.map ffi_cbarg_types cbargs) in + let rust_cbargs_names = List.map rust_cbarg_name cbargs in + pr "pub(crate) unsafe fn %s_to_raw<F>(f: F) -> sys::nbd_%s_callback\n" + cbname cbname; + pr " where F: %s\n" closure_trait; + pr "{\n"; + pr + " unsafe extern \"C\" fn call_closure<F>(data: *mut c_void, %s) -> \ + c_int\n" + (String.concat ", " + (List.map2 + (fun name typ -> name ^ ": " ^ typ) + ffi_cbargs_names ffi_cbargs_types)); + pr " where F: %s\n" closure_trait; + pr " {\n"; + pr " let callback_ptr = data as *mut F;\n"; + pr " let callback = &mut *callback_ptr;\n"; + List.iter ffi_cbargs_to_rust cbargs; + pr " 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 + (fun name typ -> name ^ ": " ^ typ) + rust_args_names rust_args_types) + in + print_rust_handle_call_comment call; + (* Print visibility modifier. *) + if NameSet.mem name hidden_handle_calls then ( + (* If this is hidden to the public API, it might be used only if some feature + * is active, and we don't want a unused-warning. *) + pr "#[allow(unused)]\n"; + pr "pub(crate) ") + else pr "pub "; + pr "fn %s(&self, %s) -> %s\n" name rust_args (rust_ret_type call); + print_ffi_call name "self.handle" call; + pr "\n" + +let print_rust_imports () + pr "use bitflags::bitflags;\n"; + pr "use byte_strings::const_::c_str;\n"; + pr "use crate::{*, types::*};\n"; + pr "use os_str_bytes::OsStringBytes as _;\n"; + pr "use os_socketaddr::OsSocketAddr;\n"; + pr "use std::ffi::*;\n"; + pr "use std::mem;\n"; + pr "use std::net::SocketAddr;\n"; + pr "use std::os::fd::{AsRawFd, OwnedFd, RawFd};\n"; + pr "use std::path::PathBuf;\n"; + pr "use std::ptr;\n"; + pr "use std::slice;\n"; + pr "\n" + +let generate_rust_bindings () + generate_header CStyle ~copyright:"Tage Johansson"; + pr "\n"; + print_rust_imports (); + List.iter print_rust_constant constants; + pr "\n"; + List.iter print_rust_enum all_enums; + List.iter print_rust_flags all_flags; + List.iter print_metadata_namespace metadata_namespaces; + List.iter print_rust_closure_to_raw_fn all_closures; + pr "impl Handle {\n"; + List.iter print_rust_handle_method handle_calls; + pr "}\n\n" diff --git a/generator/Rust.mli b/generator/Rust.mli new file mode 100644 index 0000000..450e4ca --- /dev/null +++ b/generator/Rust.mli @@ -0,0 +1,20 @@ +(* nbd client library in userspace: generator + * Copyright Tage Johansson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + *) + +(* Print all flag-structs, enums, constants and handle calls in Rust code. *) +val generate_rust_bindings : unit -> unit diff --git a/generator/generator.ml b/generator/generator.ml index c73824e..67b9502 100644 --- a/generator/generator.ml +++ b/generator/generator.ml @@ -61,3 +61,5 @@ let () output_to "golang/closures.go" GoLang.generate_golang_closures_go; output_to "golang/wrappers.go" GoLang.generate_golang_wrappers_go; output_to "golang/wrappers.h" GoLang.generate_golang_wrappers_h; + + output_to ~formatter:(Some Rustfmt) "rust/src/bindings.rs" Rust.generate_rust_bindings; diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000..e81360b --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,50 @@ +# nbd client library in userspace +# Copyright Tage Johansson +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +[workspace] + +[workspace.package] +# TODO: Add authors. +version = "0.1.0" +edition = "2021" +description = "Rust bindings for libnbd, a client library for controlling block devices over a network." +license = "LGPL-2.1-only" +keywords = ["libnbd", "block-device", "network"] +categories = ["api-bindings", "emulators", "virtualization"] + +[package] +name = "libnbd" +version.workspace = true +edition.workspace = true +description.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true + +[dependencies] +libnbd-sys = { path = "libnbd-sys" } +bitflags = "2.3.1" +errno = "0.3.1" +os_socketaddr = "0.2.4" +os_str_bytes = { version = "6.5.0", default-features = false } +thiserror = "1.0.40" +log = { version = "0.4.19", optional = true } +libc = "0.2.147" +byte-strings = "0.3.1" + +[features] +default = ["log"] diff --git a/rust/Makefile.am b/rust/Makefile.am new file mode 100644 index 0000000..cb8d7c9 --- /dev/null +++ b/rust/Makefile.am @@ -0,0 +1,60 @@ +# nbd client library in userspace +# Copyright Tage Johansson +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +include $(top_srcdir)/subdir-rules.mk + +generator_built = \ + src/bindings.rs \ + $(NULL) + +EXTRA_DIST = \ + $(generator_built) \ + .gitignore \ + Cargo.toml \ + src/lib.rs \ + src/error.rs \ + src/handle.rs \ + src/types.rs \ + src/utils.rs \ + libnbd-sys/Cargo.toml \ + libnbd-sys/build.rs \ + libnbd-sys/wrapper.h \ + libnbd-sys/src/lib.rs \ + $(NULL) + +if HAVE_RUST + +all-local: $(source_files) + rm -f libnbd-sys/libnbd_version.t + $(abs_top_builddir)/run echo $(VERSION) > libnbd-sys/libnbd_version.t + mv libnbd-sys/libnbd_version.t libnbd-sys/libnbd_version + $(abs_top_builddir)/run $(CARGO) build + $(abs_top_builddir)/run $(CARGO) doc + +TESTS_ENVIRONMENT = \ + LIBNBD_DEBUG=1 \ + $(MALLOC_CHECKS) \ + abs_top_srcdir=$(abs_top_srcdir) \ + $(NULL) +LOG_COMPILER = $(top_builddir)/run +TESTS = run-tests.sh + +endif + +clean-local: + $(CARGO) clean +CLEANFILES += libnbd-sys/libnbd_version diff --git a/rust/libnbd-sys/Cargo.toml b/rust/libnbd-sys/Cargo.toml new file mode 100644 index 0000000..7eadd0a --- /dev/null +++ b/rust/libnbd-sys/Cargo.toml @@ -0,0 +1,30 @@ +# nbd client library in userspace +# Copyright Tage Johansson +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +[package] +name = "libnbd-sys" +version.workspace = true +edition.workspace = true +description.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true +links = "nbd" + +[build-dependencies] +bindgen = "0.65.1" +pkg-config = "0.3.27" diff --git a/rust/libnbd-sys/build.rs b/rust/libnbd-sys/build.rs new file mode 100644 index 0000000..3dcf8cc --- /dev/null +++ b/rust/libnbd-sys/build.rs @@ -0,0 +1,60 @@ +// nbd client library in userspace +// Copyright Tage Johansson +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +extern crate bindgen; + +use std::env; +use std::fs; +use std::path::PathBuf; + +fn main() { + // If the environment variable LIBNBD_DIR is set, use it as the path for the + // libnbd shared library. Else, use pkg-config. + let libnbd_version = fs::read_to_string("libnbd_version").unwrap(); + pkg_config::Config::new() + .atleast_version(libnbd_version.trim()) + .probe("libnbd") + .unwrap(); + + // Tell cargo to tell rustc to link the system libnbd + // shared library. + //println!("cargo:rustc-link-lib=nbd"); + + // Tell cargo to invalidate the built crate whenever the wrapper changes + println!("cargo:rerun-if-changed=wrapper.h"); + + // The bindgen::Builder is the main entry point + // to bindgen, and lets you build up options for + // the resulting bindings. + let bindings = bindgen::Builder::default() + // The input header we would like to generate + // bindings for. + .header("wrapper.h") + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + // Finish the builder and generate the bindings. + .generate() + // Unwrap the Result and panic on failure. + .expect("Unable to generate bindings"); + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} diff --git a/rust/libnbd-sys/src/lib.rs b/rust/libnbd-sys/src/lib.rs new file mode 100644 index 0000000..f93317c --- /dev/null +++ b/rust/libnbd-sys/src/lib.rs @@ -0,0 +1,24 @@ +// nbd client library in userspace +// Copyright Tage Johansson +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +//! All functions from libnbd.h generated by bindgen. + +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/rust/libnbd-sys/wrapper.h b/rust/libnbd-sys/wrapper.h new file mode 100644 index 0000000..c25f751 --- /dev/null +++ b/rust/libnbd-sys/wrapper.h @@ -0,0 +1,18 @@ +// nbd client library in userspace +// Copyright Tage Johansson +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include "../../include/libnbd.h" diff --git a/rust/run-tests.sh b/rust/run-tests.sh new file mode 100755 index 0000000..7a0bc85 --- /dev/null +++ b/rust/run-tests.sh @@ -0,0 +1,24 @@ +#!/bin/bash - +# nbd client library in userspace +# Copyright Red Hat +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +. ../tests/functions.sh + +set -e +set -x + +cargo test diff --git a/rust/src/error.rs b/rust/src/error.rs new file mode 100644 index 0000000..615a178 --- /dev/null +++ b/rust/src/error.rs @@ -0,0 +1,111 @@ +// nbd client library in userspace +// Copyright Tage Johansson +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +use crate::sys; +use errno::Errno; +use std::ffi::CStr; +use std::io; + +/// A general error type for libnbd. +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Recoverable(ErrorKind), + #[error("Fatal: NBD handle is dead: {0}")] + Fatal(FatalErrorKind), +} + +#[derive(Debug, thiserror::Error)] +pub enum ErrorKind { + #[error("Errno: {errno}: {description}")] + WithErrno { errno: Errno, description: String }, + #[error("{description}")] + WithoutErrno { description: String }, + #[error(transparent)] + Errno(#[from] Errno), +} + +#[derive(Debug, thiserror::Error)] +pub enum FatalErrorKind { + #[error(transparent)] + Kind(#[from] ErrorKind), + #[error(transparent)] + Io(#[from] io::Error), +} + +pub type Result<T, E = Error> = std::result::Result<T, E>; + +impl ErrorKind { + /// Retrieve the last error from libnbd in the current thread. + pub(crate) unsafe fn get_error() -> Self { + let description = CStr::from_ptr(sys::nbd_get_error()) + .to_string_lossy() + .to_string(); + match sys::nbd_get_errno() { + 0 => Self::WithoutErrno { description }, + e => Self::WithErrno { + description, + errno: Errno(e), + }, + } + } + + /// Create an error from an errno value without any additional description. + pub fn from_errno(val: i32) -> Self { + Self::Errno(Errno(val)) + } + + /// Get the errno value if any. + pub fn errno(&self) -> Option<i32> { + match self { + Self::WithErrno { + errno: Errno(x), .. + } + | Self::Errno(Errno(x)) => Some(*x), + Self::WithoutErrno { .. } => None, + } + } +} + +impl Error { + /// Retrieve the last error from libnbd in the current thread and check if + /// the handle is dead to determine if the error is fatal or not. + pub(crate) unsafe fn get_error(handle: *mut sys::nbd_handle) -> Self { + let kind = ErrorKind::get_error(); + if sys::nbd_aio_is_dead(handle) != 0 { + Self::Fatal(FatalErrorKind::Kind(kind)) + } else { + Self::Recoverable(kind) + } + } + + /// Get the errno value if any. + pub fn errno(&self) -> Option<i32> { + match self { + Self::Recoverable(e) | Self::Fatal(FatalErrorKind::Kind(e)) => { + e.errno() + } + Self::Fatal(FatalErrorKind::Io(e)) => e.raw_os_error(), + } + } +} + +impl From<io::Error> for Error { + fn from(err: io::Error) -> Self { + Self::Fatal(err.into()) + } +} diff --git a/rust/src/handle.rs b/rust/src/handle.rs new file mode 100644 index 0000000..477faa4 --- /dev/null +++ b/rust/src/handle.rs @@ -0,0 +1,65 @@ +// nbd client library in userspace +// Copyright Tage Johansson +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +use crate::sys; +use crate::{Error, ErrorKind, Result}; + +/// An NBD client handle. +#[derive(Debug)] +pub struct Handle { + /// A pointer to the raw handle. + pub(crate) handle: *mut sys::nbd_handle, +} + +impl Handle { + pub fn new() -> Result<Self> { + let handle = unsafe { sys::nbd_create() }; + if handle.is_null() { + Err(unsafe { Error::Fatal(ErrorKind::get_error().into()) }) + } else { + #[allow(unused_mut)] + let mut nbd = Handle { handle }; + #[cfg(feature = "log")] + { + nbd.set_debug_callback(|func_name, msg| { + log::debug!( + target: func_name.to_string_lossy().as_ref(), + "{}", + msg.to_string_lossy() + ); + 0 + })?; + nbd.set_debug(true)?; + } + Ok(nbd) + } + } + + /// Get the underliing C pointer to the handle. + pub(crate) fn raw_handle(&self) -> *mut sys::nbd_handle { + self.handle + } +} + +impl Drop for Handle { + fn drop(&mut self) { + unsafe { sys::nbd_close(self.handle) } + } +} + +unsafe impl Send for Handle {} +unsafe impl Sync for Handle {} diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 0000000..a6f3131 --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,28 @@ +// nbd client library in userspace +// Copyright Tage Johansson +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#![deny(warnings)] + +mod bindings; +mod error; +mod handle; +pub mod types; +mod utils; +pub use bindings::*; +pub use error::{Error, ErrorKind, FatalErrorKind, Result}; +pub use handle::Handle; +pub(crate) use libnbd_sys as sys; diff --git a/rust/src/types.rs b/rust/src/types.rs new file mode 100644 index 0000000..eb2df06 --- /dev/null +++ b/rust/src/types.rs @@ -0,0 +1,18 @@ +// nbd client library in userspace +// Copyright Tage Johansson +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +pub struct Cookie(pub(crate) u64); diff --git a/rust/src/utils.rs b/rust/src/utils.rs new file mode 100644 index 0000000..b8200c1 --- /dev/null +++ b/rust/src/utils.rs @@ -0,0 +1,23 @@ +// nbd client library in userspace +// Copyright Tage Johansson +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +use std::ffi::c_void; + +/// Take a C pointer to some rust data of type `T` on the heap and drop it. +pub unsafe extern "C" fn drop_data<T>(data: *mut c_void) { + drop(Box::from_raw(data as *mut T)) +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..e6250dd --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,19 @@ +# nbd client library in userspace +# Copyright Tage Johansson +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +edition = "2021" +max_width = 80 -- 2.41.0
Richard W.M. Jones
2023-Jul-19 14:28 UTC
[Libguestfs] [libnbd PATCH 02/10] rust: create basic Rust bindings
On Wed, Jul 19, 2023 at 09:09:46AM +0000, Tage Johansson wrote:> diff --git a/rust/Makefile.am b/rust/Makefile.am > new file mode 100644 > index 0000000..cb8d7c9 > --- /dev/null > +++ b/rust/Makefile.am...> +all-local: $(source_files) > + rm -f libnbd-sys/libnbd_version.t > + $(abs_top_builddir)/run echo $(VERSION) > libnbd-sys/libnbd_version.t > + mv libnbd-sys/libnbd_version.t libnbd-sys/libnbd_version > + $(abs_top_builddir)/run $(CARGO) build > + $(abs_top_builddir)/run $(CARGO) docYou could split this rule to avoid all the commands being run every time, and to allow parallel builds, something like ... all-local: libnbd-sys/libnbd_version SOME_BUILT_FILE SOME_DOC_FILE 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 $@ SOME_BUILT_FILE: $(source_files) libnbd-sys/libnbd_version $(abs_top_builddir)/run $(CARGO) build SOME_DOC_FILE: $(source_files) libnbd-sys/libnbd_version $(abs_top_builddir)/run $(CARGO) doc where SOME_BUILT_FILE and SOME_DOC_FILE are single files which are built by cargo build / cargo doc and can act as a canary that the build was successful last time. (Don't put multiple files here, make won't do what you expect.) Notice also that I made the version file depend on Makefile so that it will get properly updated when the version is changed by a source file is not touched. 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