Hiroyuki Katsura
2019-Jul-23 08:36 UTC
Re: [Libguestfs] [PATCH] Rust bindings: Add Rust bindings
I found a bug in the bindings, so I fixed it. I'm sorry about sending you the patch many times. Regards, Hiroyuki 2019年7月20日(土) 16:23 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com>:> From: Hiroyuki_Katsura <hiroyuki.katsura.0513@gmail.com> > > Rust bindings: Add create / close functions > > Rust bindings: Add 4 bindings tests > > Rust bindings: Add generator of structs > > Rust bindings: Add generator of structs for optional arguments > > Rust bindings: Add generator of function signatures > > Rust bindings: Complete actions > > Rust bindings: Fix memory management > > Rust bindings: Add bindtests > > Rust bindings: Add additional 4 bindings tests > > Rust bindings: Format test files > > Rust bindings: Incorporate bindings to build system > --- > Makefile.am | 3 + > configure.ac | 6 + > generator/Makefile.am | 3 + > generator/bindtests.ml | 66 ++++ > generator/bindtests.mli | 1 + > generator/main.ml | 5 + > generator/rust.ml | 564 ++++++++++++++++++++++++++++ > generator/rust.mli | 22 ++ > m4/guestfs-rust.m4 | 33 ++ > run.in | 9 + > rust/.gitignore | 3 + > rust/Cargo.toml.in | 6 + > rust/Makefile.am | 42 +++ > rust/run-bindtests | 23 ++ > rust/run-tests | 21 ++ > rust/src/.gitkeep | 0 > rust/src/base.rs | 125 ++++++ > rust/src/bin/.gitkeep | 0 > rust/src/error.rs | 68 ++++ > rust/src/lib.rs | 7 + > rust/src/utils.rs | 136 +++++++ > rust/tests/.gitkeep | 0 > rust/tests/010_load.rs | 24 ++ > rust/tests/020_create.rs | 24 ++ > rust/tests/030_create_flags.rs | 29 ++ > rust/tests/040_create_multiple.rs | 38 ++ > rust/tests/050_handle_properties.rs | 62 +++ > rust/tests/070_opt_args.rs | 41 ++ > rust/tests/080_version.rs | 26 ++ > rust/tests/090_ret_values.rs | 61 +++ > rust/tests/100_launch.rs | 65 ++++ > 31 files changed, 1513 insertions(+) > create mode 100644 generator/rust.ml > create mode 100644 generator/rust.mli > create mode 100644 m4/guestfs-rust.m4 > create mode 100644 rust/.gitignore > create mode 100644 rust/Cargo.toml.in > create mode 100644 rust/Makefile.am > create mode 100755 rust/run-bindtests > create mode 100755 rust/run-tests > create mode 100644 rust/src/.gitkeep > create mode 100644 rust/src/base.rs > create mode 100644 rust/src/bin/.gitkeep > create mode 100644 rust/src/error.rs > create mode 100644 rust/src/lib.rs > create mode 100644 rust/src/utils.rs > create mode 100644 rust/tests/.gitkeep > create mode 100644 rust/tests/010_load.rs > create mode 100644 rust/tests/020_create.rs > create mode 100644 rust/tests/030_create_flags.rs > create mode 100644 rust/tests/040_create_multiple.rs > create mode 100644 rust/tests/050_handle_properties.rs > create mode 100644 rust/tests/070_opt_args.rs > create mode 100644 rust/tests/080_version.rs > create mode 100644 rust/tests/090_ret_values.rs > create mode 100644 rust/tests/100_launch.rs > > diff --git a/Makefile.am b/Makefile.am > index e76ea6daf..c1690386d 100644 > --- a/Makefile.am > +++ b/Makefile.am > @@ -151,6 +151,9 @@ endif > if HAVE_GOLANG > SUBDIRS += golang golang/examples > endif > +if HAVE_RUST > +SUBDIRS += rust > +endif > > # Unconditional because nothing is built yet. > SUBDIRS += csharp > diff --git a/configure.ac b/configure.ac > index 46bb7684a..4b445953d 100644 > --- a/configure.ac > +++ b/configure.ac > @@ -161,6 +161,8 @@ HEADING([Checking for Go]) > m4_include([m4/guestfs-golang.m4]) > HEADING([Checking for GObject Introspection]) > m4_include([m4/guestfs-gobject.m4]) > +HEADING([Checking for Rust]) > +m4_include([m4/guestfs-rust.m4]) > HEADING([Checking for Vala]) > VAPIGEN_CHECK > > @@ -315,6 +317,8 @@ AC_CONFIG_FILES([Makefile > ruby/Rakefile > ruby/examples/Makefile > ruby/ext/guestfs/extconf.rb > + rust/Makefile > + rust/Cargo.toml > sparsify/Makefile > sysprep/Makefile > test-data/Makefile > @@ -433,6 +437,8 @@ AS_ECHO_N(["Vala bindings ....................... "]) > if test "x$ENABLE_VAPIGEN_TRUE" = "x"; then echo "yes"; else echo "no"; fi > AS_ECHO_N(["bash completion ..................... "]) > if test "x$HAVE_BASH_COMPLETION_TRUE" = "x"; then echo "yes"; else echo > "no"; fi > +AS_ECHO_N(["Rust bindings ....................... "]) > +if test "x$HAVE_RUST_TRUE" = "x"; then echo "yes"; else echo "no"; fi > echo > echo "If any optional component is configured 'no' when you expected > 'yes'" > echo "then you should check the preceding messages." > diff --git a/generator/Makefile.am b/generator/Makefile.am > index 0322a7561..283cf3769 100644 > --- a/generator/Makefile.am > +++ b/generator/Makefile.am > @@ -103,6 +103,8 @@ sources = \ > python.mli \ > ruby.ml \ > ruby.mli \ > + rust.ml \ > + rust.mli \ > structs.ml \ > structs.mli \ > tests_c_api.ml \ > @@ -161,6 +163,7 @@ objects = \ > lua.cmo \ > GObject.cmo \ > golang.cmo \ > + rust.cmo \ > bindtests.cmo \ > errnostring.cmo \ > customize.cmo \ > diff --git a/generator/bindtests.ml b/generator/bindtests.ml > index 58d7897b3..e88e71c8a 100644 > --- a/generator/bindtests.ml > +++ b/generator/bindtests.ml > @@ -983,6 +983,72 @@ and generate_php_bindtests () > > dump "bindtests" > > +and generate_rust_bindtests () > + generate_header CStyle GPLv2plus; > + > + pr "extern crate guestfs;\n"; > + pr "use guestfs::*;\n"; > + pr "use std::default::Default;\n"; > + pr "\n"; > + pr "fn main() {\n"; > + pr " let g = match Handle::create() {\n"; > + pr " Ok(g) => g,\n"; > + pr " Err(e) => panic!(format!(\" could not create handle {}\", > e)),\n"; > + pr " };\n"; > + generate_lang_bindtests ( > + fun f args optargs -> > + pr " g.%s(" f; > + let needs_comma = ref false in > + List.iter ( > + fun arg -> > + if !needs_comma then pr ", "; > + needs_comma := true; > + match arg with > + | CallString s -> pr "\"%s\"" s > + | CallOptString None -> pr "None" > + | CallOptString (Some s) -> pr "Some(\"%s\")" s > + | CallStringList xs -> > + pr "&vec![%s]" > + (String.concat ", " (List.map (sprintf "\"%s\"") xs)) > + | CallInt i -> pr "%d" i > + | CallInt64 i -> pr "%Ldi64" i > + | CallBool b -> pr "%b" b > + | CallBuffer s -> > + let f = fun x -> sprintf "%d" (Char.code x) in > + pr "&[%s]" > + (String.concat ", " (List.map f (String.explode s))) > + ) args; > + if !needs_comma then pr ", "; > + (match optargs with > + | None -> pr "Default::default()" > + | Some optargs -> > + pr "%sOptArgs{" (Rust.snake2caml f); > + needs_comma := false; > + List.iter ( > + fun optarg -> > + if !needs_comma then pr ", "; > + needs_comma := true; > + match optarg with > + | CallOBool (n, v) -> > + pr "%s: Some(%b)" n v > + | CallOInt (n, v) -> > + pr "%s: Some(%d)" n v > + | CallOInt64 (n, v) -> > + pr "%s: Some(%Ldi64)" n v > + | CallOString (n, v) -> > + pr "%s: Some(\"%s\")" n v > + | CallOStringList (n, xs) -> > + pr "%s: Some(&[%s])" > + n (String.concat ", " (List.map (sprintf "\"%s\"") xs)) > + ) optargs; > + if !needs_comma then pr ", "; > + pr ".. Default::default()}"; > + ); > + pr ").expect(\"failed to run\");\n"; > + ); > + pr " println!(\"EOF\");\n"; > + pr "}\n"; > + > (* Language-independent bindings tests - we do it this way to > * ensure there is parity in testing bindings across all languages. > *) > diff --git a/generator/bindtests.mli b/generator/bindtests.mli > index 6f469b3a1..0e18a4c44 100644 > --- a/generator/bindtests.mli > +++ b/generator/bindtests.mli > @@ -28,3 +28,4 @@ val generate_perl_bindtests : unit -> unit > val generate_php_bindtests : unit -> unit > val generate_python_bindtests : unit -> unit > val generate_ruby_bindtests : unit -> unit > +val generate_rust_bindtests : unit -> unit > diff --git a/generator/main.ml b/generator/main.ml > index acacfb9e4..80000b1e3 100644 > --- a/generator/main.ml > +++ b/generator/main.ml > @@ -363,6 +363,11 @@ Run it from the top source directory using the command > output_to "customize/customize-options.pod" > Customize.generate_customize_options_pod; > > + output_to "rust/src/guestfs.rs" > + Rust.generate_rust; > + output_to "rust/src/bin/bindtests.rs" > + Bindtests.generate_rust_bindtests; > + > (* Generate the list of files generated -- last. *) > printf "generated %d lines of code\n" (get_lines_generated ()); > let files = List.sort compare (get_files_generated ()) in > diff --git a/generator/rust.ml b/generator/rust.ml > new file mode 100644 > index 000000000..09ca89982 > --- /dev/null > +++ b/generator/rust.ml > @@ -0,0 +1,564 @@ > +(* libguestfs > + * Copyright (C) 2019 Red Hat Inc. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program 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 General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > 02110-1301 USA > +*) > + > +(* Please read generator/README first. *) > + > +open Std_utils > +open Types > +open Utils > +open Pr > +open Docstrings > +open Optgroups > +open Actions > +open Structs > +open C > +open Events > + > +(* Utilities for Rust *) > +(* Are there corresponding functions to them? *) > +(* Should they be placed in utils.ml? *) > +let rec indent n = match n with > + | x when x > 0 -> pr " "; indent (x - 1) > + | _ -> () > + > +(* split_on_char exists since OCaml 4.04 *) > +(* but current requirements: >=4.01 *) > +let split_on_char c = Str.split (Str.regexp (String.make 1 c)) > + > +let snake2caml name > + let l = split_on_char '_' name in > + let l = List.map (fun x -> String.capitalize_ascii x) l in > + String.concat "" l > + > +(* because there is a function which contains 'unsafe' field *) > +let black_list = ["unsafe"] > + > +let translate_bad_symbols s > + if List.exists (fun x -> s = x) black_list then > + s ^ "_" > + else > + s > + > +let generate_rust () > + generate_header CStyle LGPLv2plus; > + > + pr " > +use crate::base::*; > +use crate::utils::*; > +use crate::error::*; > +use std::collections; > +use std::convert; > +use std::convert::TryFrom; > +use std::ffi; > +use std::os::raw::{c_char, c_int, c_void}; > +use std::ptr; > +use std::slice; > + > +extern \"C\" { > + fn free(buf: *const c_void); > +} > +"; > + > + List.iter ( > + fun { s_camel_name = name; s_name = c_name; s_cols = cols } -> > + pr "\n"; > + pr "pub struct %s {\n" name; > + List.iter ( > + function > + | n, FChar -> pr " pub %s: i8,\n" n > + | n, FString -> pr " pub %s: String,\n" n > + | n, FBuffer -> pr " pub %s: Vec<u8>,\n" n > + | n, FUInt32 -> pr " pub %s: u32,\n" n > + | n, FInt32 -> pr " pub %s: i32,\n" n > + | n, (FUInt64 | FBytes) -> pr " pub %s: u64,\n" n > + | n, FInt64 -> pr " pub %s: i64,\n" n > + | n, FUUID -> pr " pub %s: UUID,\n" n > + | n, FOptPercent -> pr " pub %s: Option<f32>,\n" n > + ) cols; > + pr "}\n"; > + pr "#[repr(C)]\n"; > + pr "struct Raw%s {\n" name; > + List.iter ( > + function > + | n, FChar -> pr " %s: c_char,\n" n > + | n, FString -> pr " %s: *const c_char,\n" n > + | n, FBuffer -> > + pr " %s_len: usize,\n" n; > + pr " %s: *const c_char,\n" n; > + | n, FUUID -> pr " %s: [u8; 32],\n" n > + | n, FUInt32 -> pr " %s: u32,\n" n > + | n, FInt32 -> pr " %s: i32,\n" n > + | n, (FUInt64 | FBytes) -> pr " %s: u64,\n" n > + | n, FInt64 -> pr " %s: i64,\n" n > + | n, FOptPercent -> pr " %s: f32,\n" n > + ) cols; > + pr "}\n"; > + pr "\n"; > + pr "impl TryFrom<*const Raw%s> for %s {\n" name name; > + pr " type Error = Error;\n"; > + pr " fn try_from(raw: *const Raw%s) -> Result<Self, Self::Error> > {\n" name; > + pr " Ok(unsafe {\n"; > + pr " %s {\n" name; > + List.iter ( > + fun x -> > + indent 4; > + match x with > + | n, FChar -> > + pr "%s: (*raw).%s as i8,\n" n n; > + | n, FString -> > + pr "%s: {\n" n; > + indent 5; > + pr "let s = ffi::CStr::from_ptr((*raw).%s);\n" n; > + indent 5; > + pr "s.to_str()?.to_string()\n"; > + indent 4; > + pr "},\n" > + | n, FBuffer -> > + pr "%s: slice::from_raw_parts((*raw).%s as *const u8, > (*raw).%s_len).to_vec(),\n" n n n > + | n, FUUID -> > + pr "%s: UUID::new((*raw).%s),\n" n n > + | n, (FUInt32 | FInt32 | FUInt64 | FInt64 | FBytes) -> > + pr "%s: (*raw).%s,\n" n n > + | n, FOptPercent -> > + pr "%s: if (*raw).%s < 0.0 {\n" n n; > + indent 4; pr " None\n"; > + indent 4; pr "} else {\n"; > + indent 4; pr " Some((*raw).%s)\n" n; > + indent 4; pr"},\n" > + ) cols; > + pr " }\n"; > + pr " })\n"; > + pr " }\n"; > + pr "}\n" > + ) external_structs; > + > + (* generate free functionf of structs *) > + pr "\n"; > + pr "extern \"C\" {\n"; > + List.iter ( > + fun { s_camel_name = name; s_name = c_name; } -> > + pr " #[allow(dead_code)]\n"; > + pr " fn guestfs_free_%s(v: *const Raw%s);\n" c_name name; > + pr " #[allow(dead_code)]\n"; > + pr " fn guestfs_free_%s_list(l: *const RawList<Raw%s>);\n" > c_name name; > + ) external_structs; > + pr "}\n"; > + > + (* [Outline] There are three types for each optional structs: SOptArgs, > + * CExprSOptArgs, RawSOptArgs. > + * SOptArgs: for Rust bindings' API. This can be seen by bindings' > users. > + * CExprSOptArgs: Each field has C expression(e.g. CString, *const > c_char) > + * RawSOptArgs: Each field has raw pointers or integer values > + * > + * SOptArgs ---try_into()---> CExprSOptArgs ---to_raw()---> RawSOptArgs > + * > + * Note: direct translation from SOptArgs to RawSOptArgs will cause a > memory > + * management problem. Using into_raw/from_raw, this problem can be > avoided, > + * but it is complex to handle freeing memories manually in Rust > because of > + * panic/?/etc. > + *) > + (* generate structs for optional arguments *) > + List.iter ( > + fun ({ name = name; shortdesc = shortdesc; > + style = (ret, args, optargs) }) -> > + let cname = snake2caml name in > + let rec contains_ptr args = match args with > + | [] -> false > + | OString _ ::_ > + | OStringList _::_ -> true > + | _::xs -> contains_ptr xs > + in > + let opt_life_parameter = if contains_ptr optargs then "<'a>" else > "" in > + if optargs <> [] then ( > + pr "\n"; > + pr "/* Optional Structs */\n"; > + pr "#[derive(Default)]\n"; > + pr "pub struct %sOptArgs%s {\n" cname opt_life_parameter; > + List.iter ( > + fun optarg -> > + let n = translate_bad_symbols (name_of_optargt optarg) in > + match optarg with > + | OBool _ -> > + pr " pub %s: Option<bool>,\n" n > + | OInt _ -> > + pr " pub %s: Option<i32>,\n" n > + | OInt64 _ -> > + pr " pub %s: Option<i64>,\n" n > + | OString _ -> > + pr " pub %s: Option<&'a str>,\n" n > + | OStringList _ -> > + pr " pub %s: Option<&'a [&'a str]>,\n" n > + ) optargs; > + pr "}\n\n"; > + > + pr "struct CExpr%sOptArgs {\n" cname; > + List.iter ( > + fun optarg -> > + let n = translate_bad_symbols (name_of_optargt optarg) in > + match optarg with > + | OBool _ | OInt _ -> > + pr " %s: Option<c_int>,\n" n > + | OInt64 _ -> > + pr " %s: Option<i64>,\n" n > + | OString _ -> > + pr " %s: Option<ffi::CString>,\n" n > + | OStringList _ -> > + (* buffers and their pointer vector *) > + pr " %s: Option<(Vec<ffi::CString>, Vec<*const > c_char>)>,\n" n > + ) optargs; > + pr "}\n\n"; > + > + pr "impl%s TryFrom<%sOptArgs%s> for CExpr%sOptArgs {\n" > + opt_life_parameter cname opt_life_parameter cname; > + pr " type Error = Error;\n"; > + pr " fn try_from(optargs: %sOptArgs%s) -> Result<Self, > Self::Error> {\n" cname opt_life_parameter; > + pr " Ok(CExpr%sOptArgs {\n" cname; > + List.iteri ( > + fun index optarg -> > + let n = translate_bad_symbols (name_of_optargt optarg) in > + match optarg with > + | OBool _ -> > + pr " %s: optargs.%s.map(|b| if b { 1 } else { 0 > }),\n" n n; > + | OInt _ | OInt64 _ -> > + pr " %s: optargs.%s, \n" n n; > + | OString _ -> > + pr " %s: optargs.%s.map(|v| > ffi::CString::new(v)).transpose()?,\n" n n; > + | OStringList _ -> > + pr " %s: optargs.%s.map(\n" n n; > + pr " |v| Ok::<_, Error>({\n"; > + pr " let v > arg_string_list(v)?;\n"; > + pr " let mut w > (&v).into_iter()\n"; > + pr " .map(|v| > v.as_ptr())\n"; > + pr " > .collect::<Vec<_>>();\n"; > + pr " w.push(ptr::null());\n"; > + pr " (v, w)\n"; > + pr " })\n"; > + pr " ).transpose()?,\n"; > + ) optargs; > + pr " })\n"; > + pr " }\n"; > + pr "}\n"; > + > + (* raw struct for C bindings *) > + pr "#[repr(C)]\n"; > + pr "struct Raw%sOptArgs {\n" cname; > + pr " bitmask: u64,\n"; > + List.iter ( > + fun optarg -> > + let n = translate_bad_symbols (name_of_optargt optarg) in > + match optarg with > + | OBool _ -> > + pr " %s: c_int,\n" n > + | OInt _ -> > + pr " %s: c_int,\n" n > + | OInt64 _ -> > + pr " %s: i64,\n" n > + | OString _ -> > + pr " %s: *const c_char,\n" n > + | OStringList _ -> > + pr " %s: *const *const c_char,\n" n > + ) optargs; > + pr "}\n\n"; > + > + pr "impl convert::From<&CExpr%sOptArgs> for Raw%sOptArgs {\n" > + cname cname; > + pr " fn from(optargs: &CExpr%sOptArgs) -> Self {\n" cname; > + pr " let mut bitmask = 0;\n"; > + pr " Raw%sOptArgs {\n" cname; > + List.iteri ( > + fun index optarg -> > + let n = translate_bad_symbols (name_of_optargt optarg) in > + match optarg with > + | OBool _ | OInt _ | OInt64 _ -> > + pr " %s: if let Some(v) = optargs.%s {\n" n n; > + pr " bitmask |= 1 << %d;\n" index; > + pr " v\n"; > + pr " } else {\n"; > + pr " 0\n"; > + pr " },\n"; > + | OString _ -> > + pr " %s: if let Some(ref v) = optargs.%s {\n" n n; > + pr " bitmask |= 1 << %d;\n" index; > + pr " v.as_ptr()\n"; > + pr " } else {\n"; > + pr " ptr::null()\n"; > + pr " },\n"; > + | OStringList _ -> > + pr " %s: if let Some((_, ref v)) = optargs.%s {\n" n > n; > + pr " bitmask |= 1 << %d;\n" index; > + pr " v.as_ptr()\n"; > + pr " } else {\n"; > + pr " ptr::null()\n"; > + pr " },\n"; > + ) optargs; > + pr " bitmask,\n"; > + pr " }\n"; > + pr " }\n"; > + pr "}\n"; > + ); > + ) (actions |> external_functions |> sort); > + > + (* extern C APIs *) > + pr "extern \"C\" {\n"; > + List.iter ( > + fun ({ name = name; shortdesc = shortdesc; > + style = (ret, args, optargs) } as f) -> > + let cname = snake2caml name in > + pr " #[allow(non_snake_case)]\n"; > + pr " fn %s(g: *const guestfs_h" f.c_function; > + List.iter ( > + fun arg -> > + pr ", "; > + match arg with > + | Bool n -> pr "%s: c_int" n > + | String (_, n) -> pr "%s: *const c_char" n > + | OptString n -> pr "%s: *const c_char" n > + | Int n -> pr "%s: c_int" n > + | Int64 n -> pr "%s: i64" n > + | Pointer (_, n) -> pr "%s: *const ffi::c_void" n > + | StringList (_, n) -> pr "%s: *const *const c_char" n > + | BufferIn n -> pr "%s: *const c_char, %s_len: usize" n n > + ) args; > + (match ret with > + | RBufferOut _ -> pr ", size: *const usize" > + | _ -> () > + ); > + if optargs <> [] then > + pr ", optarg: *const Raw%sOptArgs" cname; > + > + pr ") -> "; > + > + (match ret with > + | RErr | RInt _ | RBool _ -> pr "c_int" > + | RInt64 _ -> pr "i64" > + | RConstString _ | RString _ | RConstOptString _ -> pr "*const > c_char" > + | RBufferOut _ -> pr "*const u8" > + | RStringList _ | RHashtable _-> pr "*const *const c_char" > + | RStruct (_, n) -> > + let n = camel_name_of_struct n in > + pr "*const Raw%s" n > + | RStructList (_, n) -> > + let n = camel_name_of_struct n in > + pr "*const RawList<Raw%s>" n > + ); > + pr ";\n"; > + > + ) (actions |> external_functions |> sort); > + pr "}\n"; > + > + > + pr "impl Handle {\n"; > + List.iter ( > + fun ({ name = name; shortdesc = shortdesc; longdesc = longdesc; > + style = (ret, args, optargs) } as f) -> > + let cname = snake2caml name in > + pr " /// %s\n" shortdesc; > + pr " #[allow(non_snake_case)]\n"; > + pr " pub fn %s" name; > + > + (* generate arguments *) > + pr "(&self, "; > + > + let comma = ref false in > + List.iter ( > + fun arg -> > + if !comma then pr ", "; > + comma := true; > + match arg with > + | Bool n -> pr "%s: bool" n > + | Int n -> pr "%s: i32" n > + | Int64 n -> pr "%s: i64" n > + | String (_, n) -> pr "%s: &str" n > + | OptString n -> pr "%s: Option<&str>" n > + | StringList (_, n) -> pr "%s: &[&str]" n > + | BufferIn n -> pr "%s: &[u8]" n > + | Pointer (_, n) -> pr "%s: *mut c_void" n > + ) args; > + if optargs <> [] then ( > + if !comma then pr ", "; > + comma := true; > + pr "optargs: %sOptArgs" cname > + ); > + pr ")"; > + > + (* generate return type *) > + pr " -> Result<"; > + (match ret with > + | RErr -> pr "()" > + | RInt _ -> pr "i32" > + | RInt64 _ -> pr "i64" > + | RBool _ -> pr "bool" > + | RConstString _ -> pr "&'static str" > + | RString _ -> pr "String" > + | RConstOptString _ -> pr "Option<&'static str>" > + | RStringList _ -> pr "Vec<String>" > + | RStruct (_, sn) -> > + let sn = camel_name_of_struct sn in > + pr "%s" sn > + | RStructList (_, sn) -> > + let sn = camel_name_of_struct sn in > + pr "Vec<%s>" sn > + | RHashtable _ -> pr "collections::HashMap<String, String>" > + | RBufferOut _ -> pr "Vec<u8>"); > + pr ", Error> {\n"; > + > + > + let _pr = pr in > + let pr fs = indent 2; pr fs in > + List.iter ( > + function > + | Bool n -> > + pr "let %s = if %s { 1 } else { 0 };\n" n n > + | String (_, n) -> > + pr "let c_%s = ffi::CString::new(%s)?;\n" n n; > + | OptString n -> > + pr "let c_%s = %s.map(|s| > ffi::CString::new(s)).transpose()?;\n" n n; > + | StringList (_, n) -> > + pr "let c_%s_v = arg_string_list(%s)?;\n" n n; > + pr "let mut c_%s = (&c_%s_v).into_iter().map(|v| > v.as_ptr()).collect::<Vec<_>>();\n" n n; > + pr "c_%s.push(ptr::null());\n" n; > + | BufferIn n -> > + pr "let c_%s_len = %s.len();\n" n n; > + pr "let c_%s = unsafe { > ffi::CString::from_vec_unchecked(%s.to_vec())};\n" n n; > + | Int _ | Int64 _ | Pointer _ -> () > + ) args; > + > + (match ret with > + | RBufferOut _ -> > + pr "let mut size = 0usize;\n" > + | _ -> () > + ); > + > + if optargs <> [] then ( > + pr "let optargs_cexpr = CExpr%sOptArgs::try_from(optargs)?;\n" > cname; > + ); > + > + pr "\n"; > + > + pr "let r = unsafe { %s(self.g" f.c_function; > + let pr = _pr in > + List.iter ( > + fun arg -> > + pr ", "; > + match arg with > + | String (_, n) -> pr "(&c_%s).as_ptr()" n > + | OptString n -> pr "match &c_%s { Some(ref s) => s.as_ptr(), > None => ptr::null() }\n" n > + | StringList (_, n) -> pr "(&c_%s).as_ptr() as *const *const > c_char" n > + | Bool n | Int n | Int64 n | Pointer (_, n) -> pr "%s" n > + | BufferIn n -> pr "(&c_%s).as_ptr(), c_%s_len" n n > + ) args; > + (match ret with > + | RBufferOut _ -> pr ", &mut size as *mut usize" > + | _ -> () > + ); > + if optargs <> [] then ( > + pr ", &(Raw%sOptArgs::from(&optargs_cexpr)) as *const > Raw%sOptArgs" > + cname cname; > + ); > + pr ") };\n"; > + > + let _pr = pr in > + let pr fs = indent 2; pr fs in > + (match errcode_of_ret ret with > + | `CannotReturnError -> () > + | `ErrorIsMinusOne -> > + pr "if r == -1 {\n"; > + pr " return Err(self.get_error_from_handle(\"%s\"));\n" name; > + pr "}\n" > + | `ErrorIsNULL -> > + pr "if r.is_null() {\n"; > + pr " return Err(self.get_error_from_handle(\"%s\"));\n" name; > + pr "}\n" > + ); > + > + (* This part is not required, but type system will guarantee that > + * the buffers are still alive. This is useful because Rust cannot > + * know whether raw pointers used above are alive or not. > + *) > + List.iter ( > + function > + | Bool _ | Int _ | Int64 _ | Pointer _ -> () > + | String (_, n) > + | OptString n > + | BufferIn n -> pr "drop(c_%s);\n" n; > + | StringList (_, n) -> > + pr "drop(c_%s);\n" n; > + pr "drop(c_%s_v);\n" n; > + ) args; > + if optargs <> [] then ( > + pr "drop(optargs_cexpr);\n"; > + ); > + > + pr "Ok("; > + let pr = _pr in > + let pr3 fs = indent 3; pr fs in > + (match ret with > + | RErr -> pr "()" > + | RInt _ | RInt64 _ -> pr "r" > + | RBool _ -> pr "r != 0" > + | RConstString _ -> > + pr "unsafe{ ffi::CStr::from_ptr(r) }.to_str()?" > + | RString _ -> > + pr "{\n"; > + pr3 "let s = unsafe { ffi::CStr::from_ptr(r) };\n"; > + pr3 "unsafe { free(r as *const c_void) };\n"; > + pr3 "s.to_str()?.to_string()\n"; > + indent 2; pr "}"; > + | RConstOptString _ -> > + pr "if r.is_null() {\n"; > + pr3 "None\n"; > + indent 2; pr "} else {\n"; > + pr3 "Some(unsafe { ffi::CStr::from_ptr(r) }.to_str()?)\n"; > + indent 2; pr "}"; > + | RStringList _ -> > + pr "{\n"; > + pr3 "let s = string_list(r);\n"; > + pr3 "free_string_list(r);\n"; > + pr3 "s?\n"; > + indent 2; pr "}"; > + | RStruct (_, n) -> > + let sn = camel_name_of_struct n in > + pr "{\n"; > + pr3 "let s = %s::try_from(r);\n" sn; > + pr3 "unsafe { guestfs_free_%s(r) };\n" n; > + pr3 "s?\n"; > + indent 2; pr "}"; > + | RStructList (_, n) -> > + let sn = camel_name_of_struct n in > + pr "{\n"; > + pr3 "let l = struct_list::<Raw%s, %s>(r);\n" sn sn; > + pr3 "unsafe { guestfs_free_%s_list(r) };\n" n; > + pr3 "l?\n"; > + indent 2; pr "}"; > + | RHashtable _ -> > + pr "{\n"; > + pr3 "let h = hashmap(r);\n"; > + pr3 "free_string_list(r);\n"; > + pr3 "h?\n"; > + indent 2; pr "}"; > + | RBufferOut _ -> > + pr "{\n"; > + pr3 "let s = unsafe { slice::from_raw_parts(r, size) > }.to_vec();\n"; > + pr3 "unsafe { free(r as *const c_void) } ;\n"; > + pr3 "s\n"; > + indent 2; pr "}"; > + ); > + pr ")\n"; > + pr " }\n\n" > + ) (actions |> external_functions |> sort); > + pr "}\n" > diff --git a/generator/rust.mli b/generator/rust.mli > new file mode 100644 > index 000000000..5410286c8 > --- /dev/null > +++ b/generator/rust.mli > @@ -0,0 +1,22 @@ > +(* libguestfs > + * Copyright (C) 2009-2019 Red Hat Inc. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program 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 General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > 02110-1301 USA > +*) > + > +val generate_rust: unit -> unit > + > +(* for bindtests.ml *) > +val snake2caml: string -> string > diff --git a/m4/guestfs-rust.m4 b/m4/guestfs-rust.m4 > new file mode 100644 > index 000000000..48eee433e > --- /dev/null > +++ b/m4/guestfs-rust.m4 > @@ -0,0 +1,33 @@ > +# libguestfs > +# Copyright (C) 2009-2019 Red Hat Inc. > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; either version 2 of the License, or > +# (at your option) any later version. > +# > +# This program 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 General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > 02110-1301 USA. > + > +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([RUSTC],[rustc],[rustc],[no]) > + AC_CHECK_PROG([CARGO],[cargo],[cargo],[no]) > + > + AS_IF([test "x$RUSTC" == "xno"], [AC_MSG_WARN([rustc not found])]) > + AS_IF([test "x$CARGO" == "xno"], [AC_MSG_WARN([cargo not found])]) > +],[ > + RUSTC=no > + CARGO=no > + ]) > +AM_CONDITIONAL([HAVE_RUST],[test "x$RUSTC" != "xno" && test "x$CARGO" !> "xno"]) > diff --git a/run.in b/run.in > index 488e1b937..301b02664 100755 > --- a/run.in > +++ b/run.in > @@ -201,6 +201,15 @@ else > fi > export CGO_LDFLAGS > > +# For rust > +export RUST="@RUST@" > +if [ -z "$RUSTFLAGS" ]; then > + RUSTFLAGS="-C link-args=-L$b/lib/.libs" > +else > + RUSTFLAGS="$RUSTFLAGS -C link-args=-L$b/lib/.libs" > +fi > +export RUSTFLAGS > + > # For GObject, Javascript and friends. > export GJS="@GJS@" > prepend GI_TYPELIB_PATH "$b/gobject" > diff --git a/rust/.gitignore b/rust/.gitignore > new file mode 100644 > index 000000000..693699042 > --- /dev/null > +++ b/rust/.gitignore > @@ -0,0 +1,3 @@ > +/target > +**/*.rs.bk > +Cargo.lock > diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in > new file mode 100644 > index 000000000..e25dfe768 > --- /dev/null > +++ b/rust/Cargo.toml.in > @@ -0,0 +1,6 @@ > +[package] > +name = "guestfs" > +version = "@VERSION@" > +edition = "2018" > + > +[dependencies] > diff --git a/rust/Makefile.am b/rust/Makefile.am > new file mode 100644 > index 000000000..2ec4f7d08 > --- /dev/null > +++ b/rust/Makefile.am > @@ -0,0 +1,42 @@ > +# libguestfs rust bindings > +# Copyright (C) 2019 Red Hat Inc. > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; either version 2 of the License, or > +# (at your option) any later version. > +# > +# This program 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 General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; 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/bin/bindtests.rs \ > + src/lib.rs > + > +EXTRA_DIST = \ > + .gitignore \ > + $(generator_built) \ > + tests/*.rs \ > + Cargo.toml \ > + Cargo.lock \ > + run-bindtests \ > + run-tests > + > +if HAVE_RUST > + > +all: src/lib.rs > + $(top_builddir)/run $(CARGO) build --release > + > +TESTS = run-bindtests run-tests > + > +CLEANFILES += target/*~ > + > +endif > diff --git a/rust/run-bindtests b/rust/run-bindtests > new file mode 100755 > index 000000000..55484a2c7 > --- /dev/null > +++ b/rust/run-bindtests > @@ -0,0 +1,23 @@ > +#!/bin/sh - > +# libguestfs Rust bindings > +# Copyright (C) 2013 Red Hat Inc. > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; either version 2 of the License, or > +# (at your option) any later version. > +# > +# This program 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 General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > 02110-1301 USA. > + > +set -e > + > +$CARGO run --bin bindtests > bindtests.tmp > +diff -u $srcdir/../bindtests bindtests.tmp > +rm bindtests.tmp > diff --git a/rust/run-tests b/rust/run-tests > new file mode 100755 > index 000000000..9a5e7a1e4 > --- /dev/null > +++ b/rust/run-tests > @@ -0,0 +1,21 @@ > +#!/bin/sh - > +# libguestfs Rust tests > +# Copyright (C) 2013 Red Hat Inc. > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; either version 2 of the License, or > +# (at your option) any later version. > +# > +# This program 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 General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > 02110-1301 USA. > + > +set -e > + > +$CARGO test > diff --git a/rust/src/.gitkeep b/rust/src/.gitkeep > new file mode 100644 > index 000000000..e69de29bb > diff --git a/rust/src/base.rs b/rust/src/base.rs > new file mode 100644 > index 000000000..db64284e7 > --- /dev/null > +++ b/rust/src/base.rs > @@ -0,0 +1,125 @@ > +/* libguestfs generated file > + * WARNING: THIS FILE IS GENERATED > + * from the code in the generator/ subdirectory. > + * ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST. > + * > + * Copyright (C) 2009-2019 Red Hat Inc. > + * > + * 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::str; > + > +#[allow(non_camel_case_types)] > +#[repr(C)] > +pub(crate) struct guestfs_h { > + _unused: [u32; 0], > +} > + > +#[link(name = "guestfs")] > +extern "C" { > + fn guestfs_create() -> *mut guestfs_h; > + fn guestfs_create_flags(flags: i64) -> *mut guestfs_h; > + fn guestfs_close(g: *mut guestfs_h); > +} > + > +const GUESTFS_CREATE_NO_ENVIRONMENT: i64 = 1; > +const GUESTFS_CREATE_NO_CLOSE_ON_EXIT: i64 = 2; > + > +pub struct Handle { > + pub(crate) g: *mut guestfs_h, > +} > + > +impl Handle { > + pub fn create() -> Result<Handle, &'static str> { > + let g = unsafe { guestfs_create() }; > + if g.is_null() { > + Err("failed to create guestfs handle") > + } else { > + Ok(Handle { g }) > + } > + } > + > + pub fn create_flags(flags: CreateFlags) -> Result<Handle, &'static > str> { > + let g = unsafe { guestfs_create_flags(flags.to_libc_int()) }; > + if g.is_null() { > + Err("failed to create guestfs handle") > + } else { > + Ok(Handle { g }) > + } > + } > +} > + > +impl Drop for Handle { > + fn drop(&mut self) { > + unsafe { guestfs_close(self.g) } > + } > +} > + > +pub struct CreateFlags { > + create_no_environment_flag: bool, > + create_no_close_on_exit_flag: bool, > +} > + > +impl CreateFlags { > + pub fn none() -> CreateFlags { > + CreateFlags { > + create_no_environment_flag: false, > + create_no_close_on_exit_flag: false, > + } > + } > + > + pub fn new() -> CreateFlags { > + CreateFlags::none() > + } > + > + pub fn create_no_environment(mut self, flag: bool) -> CreateFlags { > + self.create_no_environment_flag = flag; > + self > + } > + > + pub fn create_no_close_on_exit_flag(mut self, flag: bool) -> > CreateFlags { > + self.create_no_close_on_exit_flag = flag; > + self > + } > + > + unsafe fn to_libc_int(self) -> i64 { > + let mut flag = 0; > + flag |= if self.create_no_environment_flag { > + GUESTFS_CREATE_NO_ENVIRONMENT > + } else { > + 0 > + }; > + flag |= if self.create_no_close_on_exit_flag { > + GUESTFS_CREATE_NO_CLOSE_ON_EXIT > + } else { > + 0 > + }; > + flag > + } > +} > + > +pub struct UUID { > + uuid: [u8; 32], > +} > + > +impl UUID { > + pub(crate) fn new(uuid: [u8; 32]) -> UUID { > + UUID { uuid } > + } > + pub fn to_bytes(self) -> [u8; 32] { > + self.uuid > + } > +} > diff --git a/rust/src/bin/.gitkeep b/rust/src/bin/.gitkeep > new file mode 100644 > index 000000000..e69de29bb > diff --git a/rust/src/error.rs b/rust/src/error.rs > new file mode 100644 > index 000000000..047fae7a1 > --- /dev/null > +++ b/rust/src/error.rs > @@ -0,0 +1,68 @@ > +/* libguestfs Rust bindings > + * Copyright (C) 2009-2019 Red Hat Inc. > + * > + * 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::base::*; > +use std::convert; > +use std::ffi; > +use std::os::raw::{c_char, c_int}; > +use std::str; > + > +#[link(name = "guestfs")] > +extern "C" { > + fn guestfs_last_error(g: *mut guestfs_h) -> *const c_char; > + fn guestfs_last_errno(g: *mut guestfs_h) -> c_int; > +} > + > +#[derive(Debug)] > +pub struct APIError { > + operation: &'static str, > + message: String, > + errno: i32, > +} > + > +#[derive(Debug)] > +pub enum Error { > + API(APIError), > + IllegalString(ffi::NulError), > + Utf8Error(str::Utf8Error), > +} > + > +impl convert::From<ffi::NulError> for Error { > + fn from(error: ffi::NulError) -> Self { > + Error::IllegalString(error) > + } > +} > + > +impl convert::From<str::Utf8Error> for Error { > + fn from(error: str::Utf8Error) -> Self { > + Error::Utf8Error(error) > + } > +} > + > +impl Handle { > + pub(crate) fn get_error_from_handle(&self, operation: &'static str) > -> Error { > + let c_msg = unsafe { guestfs_last_error(self.g) }; > + let message = unsafe { > ffi::CStr::from_ptr(c_msg).to_str().unwrap().to_string() }; > + let errno = unsafe { guestfs_last_errno(self.g) }; > + Error::API(APIError { > + operation, > + message, > + errno, > + }) > + } > +} > diff --git a/rust/src/lib.rs b/rust/src/lib.rs > new file mode 100644 > index 000000000..5111e2546 > --- /dev/null > +++ b/rust/src/lib.rs > @@ -0,0 +1,7 @@ > +mod base; > +mod error; > +mod guestfs; > +mod utils; > + > +pub use crate::base::*; > +pub use crate::guestfs::*; > diff --git a/rust/src/utils.rs b/rust/src/utils.rs > new file mode 100644 > index 000000000..ac1996e91 > --- /dev/null > +++ b/rust/src/utils.rs > @@ -0,0 +1,136 @@ > +/* libguestfs Rust bindings > + * Copyright (C) 2009-2019 Red Hat Inc. > + * > + * 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::error::*; > +use std::collections; > +use std::convert::TryFrom; > +use std::ffi; > +use std::os::raw::{c_char, c_void}; > + > +extern "C" { > + fn free(buf: *const c_void); > +} > + > +pub(crate) struct NullTerminatedIter<T: Copy + Clone> { > + p: *const *const T, > +} > + > +impl<T: Copy + Clone> NullTerminatedIter<T> { > + pub(crate) fn new(p: *const *const T) -> NullTerminatedIter<T> { > + NullTerminatedIter { p } > + } > +} > + > +impl<T: Copy + Clone> Iterator for NullTerminatedIter<T> { > + type Item = *const T; > + fn next(&mut self) -> Option<*const T> { > + let r = unsafe { *(self.p) }; > + if r.is_null() { > + None > + } else { > + self.p = unsafe { self.p.offset(1) }; > + Some(r) > + } > + } > +} > + > +#[repr(C)] > +pub(crate) struct RawList<T> { > + size: u32, > + ptr: *const T, > +} > + > +pub(crate) struct RawListIter<'a, T> { > + current: u32, > + list: &'a RawList<T>, > +} > + > +impl<T> RawList<T> { > + fn iter<'a>(&'a self) -> RawListIter<'a, T> { > + RawListIter { > + current: 0, > + list: self, > + } > + } > +} > + > +impl<'a, T> Iterator for RawListIter<'a, T> { > + type Item = *const T; > + fn next(&mut self) -> Option<*const T> { > + if self.current >= self.list.size { > + None > + } else { > + let elem = unsafe { self.list.ptr.offset(self.current as > isize) }; > + self.current += 1; > + Some(elem) > + } > + } > +} > + > +pub(crate) fn arg_string_list(v: &[&str]) -> Result<Vec<ffi::CString>, > Error> { > + let mut w = Vec::new(); > + for x in v.iter() { > + let y: &str = x; > + w.push(ffi::CString::new(y)?); > + } > + Ok(w) > +} > + > +pub(crate) fn free_string_list(l: *const *const c_char) { > + for buf in NullTerminatedIter::new(l) { > + unsafe { free(buf as *const c_void) }; > + } > + unsafe { free(l as *const c_void) }; > +} > + > +pub(crate) fn hashmap( > + l: *const *const c_char, > +) -> Result<collections::HashMap<String, String>, Error> { > + let mut map = collections::HashMap::new(); > + let mut iter = NullTerminatedIter::new(l); > + while let Some(key) = iter.next() { > + if let Some(val) = iter.next() { > + let key = unsafe { ffi::CStr::from_ptr(key) }.to_str()?; > + let val = unsafe { ffi::CStr::from_ptr(val) }.to_str()?; > + map.insert(key.to_string(), val.to_string()); > + } else { > + // Internal Error -> panic > + panic!("odd number of items in hash table"); > + } > + } > + Ok(map) > +} > + > +pub(crate) fn struct_list<T, S: TryFrom<*const T, Error = Error>>( > + l: *const RawList<T>, > +) -> Result<Vec<S>, Error> { > + let mut v = Vec::new(); > + for x in unsafe { &*l }.iter() { > + v.push(S::try_from(x)?); > + } > + Ok(v) > +} > + > +pub(crate) fn string_list(l: *const *const c_char) -> Result<Vec<String>, > Error> { > + let mut v = Vec::new(); > + for x in NullTerminatedIter::new(l) { > + let s = unsafe { ffi::CStr::from_ptr(x) }.to_str()?; > + v.push(s.to_string()); > + } > + Ok(v) > +} > diff --git a/rust/tests/.gitkeep b/rust/tests/.gitkeep > new file mode 100644 > index 000000000..e69de29bb > diff --git a/rust/tests/010_load.rs b/rust/tests/010_load.rs > new file mode 100644 > index 000000000..4cb43f2c1 > --- /dev/null > +++ b/rust/tests/010_load.rs > @@ -0,0 +1,24 @@ > +/* libguestfs Rust bindings > +Copyright (C) 2009-2019 Red Hat Inc. > + > +This program is free software; you can redistribute it and/or modify > +it under the terms of the GNU General Public License as published by > +the Free Software Foundation; either version 2 of the License, or > +(at your option) any later version. > + > +This program 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 General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with this program; if not, write to the Free Software > +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 > USA. > +*/ > + > +extern crate guestfs; > + > +#[test] > +fn load() { > + // nop > +} > diff --git a/rust/tests/020_create.rs b/rust/tests/020_create.rs > new file mode 100644 > index 000000000..13acbc7d7 > --- /dev/null > +++ b/rust/tests/020_create.rs > @@ -0,0 +1,24 @@ > +/* libguestfs Rust bindings > +Copyright (C) 2009-2019 Red Hat Inc. > + > +This program is free software; you can redistribute it and/or modify > +it under the terms of the GNU General Public License as published by > +the Free Software Foundation; either version 2 of the License, or > +(at your option) any later version. > + > +This program 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 General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with this program; if not, write to the Free Software > +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 > USA. > +*/ > + > +extern crate guestfs; > + > +#[test] > +fn create() { > + guestfs::Handle::create().unwrap(); > +} > diff --git a/rust/tests/030_create_flags.rs b/rust/tests/ > 030_create_flags.rs > new file mode 100644 > index 000000000..df3190d4c > --- /dev/null > +++ b/rust/tests/030_create_flags.rs > @@ -0,0 +1,29 @@ > +/* libguestfs Rust bindings > +Copyright (C) 2009-2019 Red Hat Inc. > + > +This program is free software; you can redistribute it and/or modify > +it under the terms of the GNU General Public License as published by the > Free Software Foundation; either version 2 of the License, or > +(at your option) any later version. > + > +This program 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 General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with this program; if not, write to the Free Software > +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 > USA. > +*/ > + > +extern crate guestfs; > + > +use guestfs::*; > + > +#[test] > +fn create_flags() { > + let _h > Handle::create_flags(CreateFlags::none()).expect("create_flags fail"); > + // TODO: Add parse_environment to check the flag is created correctly > + let flags = CreateFlags::new().create_no_environment(true); > + let _h = Handle::create_flags(flags).expect("create_flags fail"); > + // TODO: Add parse_environment to check the flag is created correctly > +} > diff --git a/rust/tests/040_create_multiple.rs b/rust/tests/ > 040_create_multiple.rs > new file mode 100644 > index 000000000..372fad7ee > --- /dev/null > +++ b/rust/tests/040_create_multiple.rs > @@ -0,0 +1,38 @@ > +/* libguestfs Rust bindings > +Copyright (C) 2009-2019 Red Hat Inc. > + > +This program is free software; you can redistribute it and/or modify > +it under the terms of the GNU General Public License as published by > +the Free Software Foundation; either version 2 of the License, or > +(at your option) any later version. > + > +This program 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 General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with this program; if not, write to the Free Software > +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 > USA. > +*/ > + > +extern crate guestfs; > + > +fn create() -> guestfs::Handle { > + match guestfs::Handle::create() { > + Ok(g) => g, > + Err(e) => panic!("fail: {}", e), > + } > +} > + > +fn ignore(_x: guestfs::Handle, _y: guestfs::Handle, _z: guestfs::Handle) { > + // drop > +} > + > +#[test] > +fn create_multiple() { > + let x = create(); > + let y = create(); > + let z = create(); > + ignore(x, y, z) > +} > diff --git a/rust/tests/050_handle_properties.rs b/rust/tests/ > 050_handle_properties.rs > new file mode 100644 > index 000000000..0b955d5cf > --- /dev/null > +++ b/rust/tests/050_handle_properties.rs > @@ -0,0 +1,62 @@ > +/* libguestfs Rust bindings > +Copyright (C) 2009-2019 Red Hat Inc. > + > +This program is free software; you can redistribute it and/or modify > +it under the terms of the GNU General Public License as published by > +the Free Software Foundation; either version 2 of the License, or > +(at your option) any later version. > + > +This program 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 General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with this program; if not, write to the Free Software > +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 > USA. > +*/ > + > +extern crate guestfs; > + > +use std::default::Default; > + > +#[test] > +fn verbose() { > + let g = guestfs::Handle::create().expect("create"); > + g.set_verbose(true).expect("set_verbose"); > + assert_eq!(g.get_verbose().expect("get_verbose"), true); > + g.set_verbose(false).expect("set_verbose"); > + assert_eq!(g.get_verbose().expect("get_verbose"), false); > +} > + > +#[test] > +fn trace() { > + let g = guestfs::Handle::create().expect("create"); > + g.set_trace(true).expect("set_trace"); > + assert_eq!(g.get_trace().expect("get_trace"), true); > + g.set_trace(false).expect("set_trace"); > + assert_eq!(g.get_trace().expect("get_trace"), false); > +} > + > +#[test] > +fn autosync() { > + let g = guestfs::Handle::create().expect("create"); > + g.set_autosync(true).expect("set_autosync"); > + assert_eq!(g.get_autosync().expect("get_autosync"), true); > + g.set_autosync(false).expect("set_autosync"); > + assert_eq!(g.get_autosync().expect("get_autosync"), false); > +} > + > +#[test] > +fn path() { > + let g = guestfs::Handle::create().expect("create"); > + g.set_path(Some(".")).expect("set_path"); > + assert_eq!(g.get_path().expect("get_path"), "."); > +} > + > +#[test] > +fn add_drive() { > + let g = guestfs::Handle::create().expect("create"); > + g.add_drive("/dev/null", Default::default()) > + .expect("add_drive"); > +} > diff --git a/rust/tests/070_opt_args.rs b/rust/tests/070_opt_args.rs > new file mode 100644 > index 000000000..04b4890c2 > --- /dev/null > +++ b/rust/tests/070_opt_args.rs > @@ -0,0 +1,41 @@ > +/* libguestfs Rust bindings > +Copyright (C) 2009-2019 Red Hat Inc. > + > +This program is free software; you can redistribute it and/or modify > +it under the terms of the GNU General Public License as published by > +the Free Software Foundation; either version 2 of the License, or > +(at your option) any later version. > + > +This program 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 General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with this program; if not, write to the Free Software > +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 > USA. > +*/ > + > +extern crate guestfs; > + > +use std::default::Default; > + > +#[test] > +fn no_optargs() { > + let g = guestfs::Handle::create().expect("create"); > + g.add_drive("/dev/null", Default::default()) > + .expect("add_drive"); > +} > + > +#[test] > +fn one_optarg() { > + let g = guestfs::Handle::create().expect("create"); > + g.add_drive( > + "/dev/null", > + guestfs::AddDriveOptArgs { > + readonly: Some(true), > + ..Default::default() > + }, > + ) > + .expect("add_drive"); > +} > diff --git a/rust/tests/080_version.rs b/rust/tests/080_version.rs > new file mode 100644 > index 000000000..19e441d67 > --- /dev/null > +++ b/rust/tests/080_version.rs > @@ -0,0 +1,26 @@ > +/* libguestfs Rust bindings > +Copyright (C) 2009-2019 Red Hat Inc. > + > +This program is free software; you can redistribute it and/or modify > +it under the terms of the GNU General Public License as published by > +the Free Software Foundation; either version 2 of the License, or > +(at your option) any later version. > + > +This program 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 General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with this program; if not, write to the Free Software > +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 > USA. > +*/ > + > +extern crate guestfs; > + > +#[test] > +fn version() { > + let g = guestfs::Handle::create().expect("create"); > + let v = g.version().expect("version"); > + assert_eq!(v.major, 1) > +} > diff --git a/rust/tests/090_ret_values.rs b/rust/tests/090_ret_values.rs > new file mode 100644 > index 000000000..d3e2e80da > --- /dev/null > +++ b/rust/tests/090_ret_values.rs > @@ -0,0 +1,61 @@ > +/* libguestfs Rust bindings > +Copyright (C) 2009-2019 Red Hat Inc. > + > +This program is free software; you can redistribute it and/or modify > +it under the terms of the GNU General Public License as published by > +the Free Software Foundation; either version 2 of the License, or > +(at your option) any later version. > + > +This program 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 General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with this program; if not, write to the Free Software > +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 > USA. > +*/ > + > +extern crate guestfs; > + > +#[test] > +fn rint() { > + let g = guestfs::Handle::create().expect("create"); > + assert_eq!(g.internal_test_rint("10").unwrap(), 10); > + assert!(g.internal_test_rinterr().is_err()) > +} > + > +#[test] > +fn rint64() { > + let g = guestfs::Handle::create().expect("create"); > + assert_eq!(g.internal_test_rint64("10").unwrap(), 10); > + assert!(g.internal_test_rint64err().is_err()) > +} > + > +#[test] > +fn rbool() { > + let g = guestfs::Handle::create().expect("create"); > + assert!(g.internal_test_rbool("true").unwrap()); > + assert!(!g.internal_test_rbool("false").unwrap()); > + assert!(g.internal_test_rboolerr().is_err()) > +} > + > +#[test] > +fn rconststring() { > + let g = guestfs::Handle::create().expect("create"); > + assert_eq!( > + g.internal_test_rconststring("test").unwrap(), > + "static string" > + ); > + assert!(g.internal_test_rconststringerr().is_err()) > +} > + > +#[test] > +fn rconstoptstring() { > + let g = guestfs::Handle::create().expect("create"); > + assert_eq!( > + g.internal_test_rconstoptstring("test").unwrap(), > + Some("static string") > + ); > + assert_eq!(g.internal_test_rconstoptstringerr().unwrap(), None) > +} > diff --git a/rust/tests/100_launch.rs b/rust/tests/100_launch.rs > new file mode 100644 > index 000000000..1c1d8146a > --- /dev/null > +++ b/rust/tests/100_launch.rs > @@ -0,0 +1,65 @@ > +/* libguestfs Rust bindings > +Copyright (C) 2009-2019 Red Hat Inc. > + > +This program is free software; you can redistribute it and/or modify > +it under the terms of the GNU General Public License as published by > +the Free Software Foundation; either version 2 of the License, or > +(at your option) any later version. > + > +This program 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 General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with this program; if not, write to the Free Software > +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 > USA. > +*/ > + > +extern crate guestfs; > + > +use std::default::Default; > + > +#[test] > +fn launch() { > + let g = guestfs::Handle::create().expect("create"); > + g.add_drive_scratch(500 * 1024 * 1024, Default::default()) > + .expect("add_drive_scratch"); > + g.launch().expect("launch"); > + g.pvcreate("/dev/sda").expect("pvcreate"); > + g.vgcreate("VG", &["/dev/sda"]).expect("vgcreate"); > + g.lvcreate("LV1", "VG", 200).expect("lvcreate"); > + g.lvcreate("LV2", "VG", 200).expect("lvcreate"); > + > + let lvs = g.lvs().expect("lvs"); > + assert_eq!( > + lvs, > + vec!["/dev/VG/LV1".to_string(), "/dev/VG/LV2".to_string()] > + ); > + > + g.mkfs("ext2", "/dev/VG/LV1", Default::default()) > + .expect("mkfs"); > + g.mount("/dev/VG/LV1", "/").expect("mount"); > + g.mkdir("/p").expect("mkdir"); > + g.touch("/q").expect("touch"); > + > + let mut dirs = g.readdir("/").expect("readdir"); > + > + dirs.sort_by(|a, b| a.name.cmp(&b.name)); > + > + let mut v = Vec::new(); > + for x in &dirs { > + v.push((x.name.as_str(), x.ftyp as u8)); > + } > + assert_eq!( > + v, > + vec![ > + (".", b'd'), > + ("..", b'd'), > + ("lost+found", b'd'), > + ("p", b'd'), > + ("q", b'r') > + ] > + ); > + g.shutdown().expect("shutdown"); > +} > -- > 2.20.1 (Apple Git-117) > >
Hiroyuki Katsura
2019-Jul-23 08:37 UTC
Re: [Libguestfs] [PATCH] Rust bindings: Add Rust bindings
From: Hiroyuki_Katsura <hiroyuki.katsura.0513@gmail.com> Rust bindings: Add create / close functions Rust bindings: Add 4 bindings tests Rust bindings: Add generator of structs Rust bindings: Add generator of structs for optional arguments Rust bindings: Add generator of function signatures Rust bindings: Complete actions Rust bindings: Fix memory management Rust bindings: Add bindtests Rust bindings: Add additional 4 bindings tests Rust bindings: Format test files Rust bindings: Incorporate bindings to build system --- Makefile.am | 3 + configure.ac | 6 + generator/Makefile.am | 3 + generator/bindtests.ml | 66 ++++ generator/bindtests.mli | 1 + generator/main.ml | 5 + generator/rust.ml | 558 ++++++++++++++++++++++++++++ generator/rust.mli | 22 ++ m4/guestfs-rust.m4 | 33 ++ run.in | 9 + rust/.gitignore | 3 + rust/Cargo.toml.in | 6 + rust/Makefile.am | 42 +++ rust/run-bindtests | 23 ++ rust/run-tests | 21 ++ rust/src/.gitkeep | 0 rust/src/base.rs | 125 +++++++ rust/src/bin/.gitkeep | 0 rust/src/error.rs | 70 ++++ rust/src/lib.rs | 8 + rust/src/utils.rs | 146 ++++++++ rust/tests/.gitkeep | 0 rust/tests/010_load.rs | 24 ++ rust/tests/020_create.rs | 24 ++ rust/tests/030_create_flags.rs | 29 ++ rust/tests/040_create_multiple.rs | 38 ++ rust/tests/050_handle_properties.rs | 62 ++++ rust/tests/070_opt_args.rs | 41 ++ rust/tests/080_version.rs | 26 ++ rust/tests/090_ret_values.rs | 61 +++ rust/tests/100_launch.rs | 65 ++++ 31 files changed, 1520 insertions(+) create mode 100644 generator/rust.ml create mode 100644 generator/rust.mli create mode 100644 m4/guestfs-rust.m4 create mode 100644 rust/.gitignore create mode 100644 rust/Cargo.toml.in create mode 100644 rust/Makefile.am create mode 100755 rust/run-bindtests create mode 100755 rust/run-tests create mode 100644 rust/src/.gitkeep create mode 100644 rust/src/base.rs create mode 100644 rust/src/bin/.gitkeep create mode 100644 rust/src/error.rs create mode 100644 rust/src/lib.rs create mode 100644 rust/src/utils.rs create mode 100644 rust/tests/.gitkeep create mode 100644 rust/tests/010_load.rs create mode 100644 rust/tests/020_create.rs create mode 100644 rust/tests/030_create_flags.rs create mode 100644 rust/tests/040_create_multiple.rs create mode 100644 rust/tests/050_handle_properties.rs create mode 100644 rust/tests/070_opt_args.rs create mode 100644 rust/tests/080_version.rs create mode 100644 rust/tests/090_ret_values.rs create mode 100644 rust/tests/100_launch.rs diff --git a/Makefile.am b/Makefile.am index e76ea6daf..c1690386d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -151,6 +151,9 @@ endif if HAVE_GOLANG SUBDIRS += golang golang/examples endif +if HAVE_RUST +SUBDIRS += rust +endif # Unconditional because nothing is built yet. SUBDIRS += csharp diff --git a/configure.ac b/configure.ac index 46bb7684a..4b445953d 100644 --- a/configure.ac +++ b/configure.ac @@ -161,6 +161,8 @@ HEADING([Checking for Go]) m4_include([m4/guestfs-golang.m4]) HEADING([Checking for GObject Introspection]) m4_include([m4/guestfs-gobject.m4]) +HEADING([Checking for Rust]) +m4_include([m4/guestfs-rust.m4]) HEADING([Checking for Vala]) VAPIGEN_CHECK @@ -315,6 +317,8 @@ AC_CONFIG_FILES([Makefile ruby/Rakefile ruby/examples/Makefile ruby/ext/guestfs/extconf.rb + rust/Makefile + rust/Cargo.toml sparsify/Makefile sysprep/Makefile test-data/Makefile @@ -433,6 +437,8 @@ AS_ECHO_N(["Vala bindings ....................... "]) if test "x$ENABLE_VAPIGEN_TRUE" = "x"; then echo "yes"; else echo "no"; fi AS_ECHO_N(["bash completion ..................... "]) if test "x$HAVE_BASH_COMPLETION_TRUE" = "x"; then echo "yes"; else echo "no"; fi +AS_ECHO_N(["Rust bindings ....................... "]) +if test "x$HAVE_RUST_TRUE" = "x"; then echo "yes"; else echo "no"; fi echo echo "If any optional component is configured 'no' when you expected 'yes'" echo "then you should check the preceding messages." diff --git a/generator/Makefile.am b/generator/Makefile.am index 0322a7561..283cf3769 100644 --- a/generator/Makefile.am +++ b/generator/Makefile.am @@ -103,6 +103,8 @@ sources = \ python.mli \ ruby.ml \ ruby.mli \ + rust.ml \ + rust.mli \ structs.ml \ structs.mli \ tests_c_api.ml \ @@ -161,6 +163,7 @@ objects = \ lua.cmo \ GObject.cmo \ golang.cmo \ + rust.cmo \ bindtests.cmo \ errnostring.cmo \ customize.cmo \ diff --git a/generator/bindtests.ml b/generator/bindtests.ml index 58d7897b3..7f99fcdae 100644 --- a/generator/bindtests.ml +++ b/generator/bindtests.ml @@ -983,6 +983,72 @@ and generate_php_bindtests () dump "bindtests" +and generate_rust_bindtests () + generate_header CStyle GPLv2plus; + + pr "extern crate guestfs;\n"; + pr "use guestfs::*;\n"; + pr "use std::default::Default;\n"; + pr "\n"; + pr "fn main() {\n"; + pr " let g = match Handle::create() {\n"; + pr " Ok(g) => g,\n"; + pr " Err(e) => panic!(format!(\" could not create handle {:?}\", e)),\n"; + pr " };\n"; + generate_lang_bindtests ( + fun f args optargs -> + pr " g.%s(" f; + let needs_comma = ref false in + List.iter ( + fun arg -> + if !needs_comma then pr ", "; + needs_comma := true; + match arg with + | CallString s -> pr "\"%s\"" s + | CallOptString None -> pr "None" + | CallOptString (Some s) -> pr "Some(\"%s\")" s + | CallStringList xs -> + pr "&vec![%s]" + (String.concat ", " (List.map (sprintf "\"%s\"") xs)) + | CallInt i -> pr "%d" i + | CallInt64 i -> pr "%Ldi64" i + | CallBool b -> pr "%b" b + | CallBuffer s -> + let f = fun x -> sprintf "%d" (Char.code x) in + pr "&[%s]" + (String.concat ", " (List.map f (String.explode s))) + ) args; + if !needs_comma then pr ", "; + (match optargs with + | None -> pr "Default::default()" + | Some optargs -> + pr "%sOptArgs{" (Rust.snake2caml f); + needs_comma := false; + List.iter ( + fun optarg -> + if !needs_comma then pr ", "; + needs_comma := true; + match optarg with + | CallOBool (n, v) -> + pr "%s: Some(%b)" n v + | CallOInt (n, v) -> + pr "%s: Some(%d)" n v + | CallOInt64 (n, v) -> + pr "%s: Some(%Ldi64)" n v + | CallOString (n, v) -> + pr "%s: Some(\"%s\")" n v + | CallOStringList (n, xs) -> + pr "%s: Some(&[%s])" + n (String.concat ", " (List.map (sprintf "\"%s\"") xs)) + ) optargs; + if !needs_comma then pr ", "; + pr ".. Default::default()}"; + ); + pr ").expect(\"failed to run\");\n"; + ); + pr " println!(\"EOF\");\n"; + pr "}\n"; + (* Language-independent bindings tests - we do it this way to * ensure there is parity in testing bindings across all languages. *) diff --git a/generator/bindtests.mli b/generator/bindtests.mli index 6f469b3a1..0e18a4c44 100644 --- a/generator/bindtests.mli +++ b/generator/bindtests.mli @@ -28,3 +28,4 @@ val generate_perl_bindtests : unit -> unit val generate_php_bindtests : unit -> unit val generate_python_bindtests : unit -> unit val generate_ruby_bindtests : unit -> unit +val generate_rust_bindtests : unit -> unit diff --git a/generator/main.ml b/generator/main.ml index acacfb9e4..80000b1e3 100644 --- a/generator/main.ml +++ b/generator/main.ml @@ -363,6 +363,11 @@ Run it from the top source directory using the command output_to "customize/customize-options.pod" Customize.generate_customize_options_pod; + output_to "rust/src/guestfs.rs" + Rust.generate_rust; + output_to "rust/src/bin/bindtests.rs" + Bindtests.generate_rust_bindtests; + (* Generate the list of files generated -- last. *) printf "generated %d lines of code\n" (get_lines_generated ()); let files = List.sort compare (get_files_generated ()) in diff --git a/generator/rust.ml b/generator/rust.ml new file mode 100644 index 000000000..bd0e98e34 --- /dev/null +++ b/generator/rust.ml @@ -0,0 +1,558 @@ +(* libguestfs + * Copyright (C) 2019 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*) + +(* Please read generator/README first. *) + +open Std_utils +open Types +open Utils +open Pr +open Docstrings +open Optgroups +open Actions +open Structs +open C +open Events + +(* Utilities for Rust *) +(* Are there corresponding functions to them? *) +(* Should they be placed in utils.ml? *) +let rec indent n = match n with + | x when x > 0 -> pr " "; indent (x - 1) + | _ -> () + +(* split_on_char exists since OCaml 4.04 *) +(* but current requirements: >=4.01 *) +let split_on_char c = Str.split (Str.regexp (String.make 1 c)) + +let snake2caml name + let l = split_on_char '_' name in + let l = List.map (fun x -> String.capitalize_ascii x) l in + String.concat "" l + +(* because there is a function which contains 'unsafe' field *) +let black_list = ["unsafe"] + +let translate_bad_symbols s + if List.exists (fun x -> s = x) black_list then + s ^ "_" + else + s + +let generate_rust () + generate_header CStyle LGPLv2plus; + + pr " +use crate::base::*; +use crate::utils::*; +use crate::error::*; +use std::collections; +use std::convert; +use std::convert::TryFrom; +use std::ffi; +use std::os::raw::{c_char, c_int, c_void}; +use std::ptr; +use std::slice; + +extern \"C\" { + fn free(buf: *const c_void); +} +"; + + List.iter ( + fun { s_camel_name = name; s_name = c_name; s_cols = cols } -> + pr "\n"; + pr "pub struct %s {\n" name; + List.iter ( + function + | n, FChar -> pr " pub %s: i8,\n" n + | n, FString -> pr " pub %s: String,\n" n + | n, FBuffer -> pr " pub %s: Vec<u8>,\n" n + | n, FUInt32 -> pr " pub %s: u32,\n" n + | n, FInt32 -> pr " pub %s: i32,\n" n + | n, (FUInt64 | FBytes) -> pr " pub %s: u64,\n" n + | n, FInt64 -> pr " pub %s: i64,\n" n + | n, FUUID -> pr " pub %s: UUID,\n" n + | n, FOptPercent -> pr " pub %s: Option<f32>,\n" n + ) cols; + pr "}\n"; + pr "#[repr(C)]\n"; + pr "struct Raw%s {\n" name; + List.iter ( + function + | n, FChar -> pr " %s: c_char,\n" n + | n, FString -> pr " %s: *const c_char,\n" n + | n, FBuffer -> + pr " %s_len: usize,\n" n; + pr " %s: *const c_char,\n" n; + | n, FUUID -> pr " %s: [u8; 32],\n" n + | n, FUInt32 -> pr " %s: u32,\n" n + | n, FInt32 -> pr " %s: i32,\n" n + | n, (FUInt64 | FBytes) -> pr " %s: u64,\n" n + | n, FInt64 -> pr " %s: i64,\n" n + | n, FOptPercent -> pr " %s: f32,\n" n + ) cols; + pr "}\n"; + pr "\n"; + pr "impl TryFrom<*const Raw%s> for %s {\n" name name; + pr " type Error = Error;\n"; + pr " fn try_from(raw: *const Raw%s) -> Result<Self, Self::Error> {\n" name; + pr " Ok(unsafe {\n"; + pr " %s {\n" name; + List.iter ( + fun x -> + indent 4; + match x with + | n, FChar -> + pr "%s: (*raw).%s as i8,\n" n n; + | n, FString -> + pr "%s: char_ptr_to_string((*raw).%s)?,\n" n n; + | n, FBuffer -> + pr "%s: slice::from_raw_parts((*raw).%s as *const u8, (*raw).%s_len).to_vec(),\n" n n n + | n, FUUID -> + pr "%s: UUID::new((*raw).%s),\n" n n + | n, (FUInt32 | FInt32 | FUInt64 | FInt64 | FBytes) -> + pr "%s: (*raw).%s,\n" n n + | n, FOptPercent -> + pr "%s: if (*raw).%s < 0.0 {\n" n n; + indent 4; pr " None\n"; + indent 4; pr "} else {\n"; + indent 4; pr " Some((*raw).%s)\n" n; + indent 4; pr"},\n" + ) cols; + pr " }\n"; + pr " })\n"; + pr " }\n"; + pr "}\n" + ) external_structs; + + (* generate free functionf of structs *) + pr "\n"; + pr "extern \"C\" {\n"; + List.iter ( + fun { s_camel_name = name; s_name = c_name; } -> + pr " #[allow(dead_code)]\n"; + pr " fn guestfs_free_%s(v: *const Raw%s);\n" c_name name; + pr " #[allow(dead_code)]\n"; + pr " fn guestfs_free_%s_list(l: *const RawList<Raw%s>);\n" c_name name; + ) external_structs; + pr "}\n"; + + (* [Outline] There are three types for each optional structs: SOptArgs, + * CExprSOptArgs, RawSOptArgs. + * SOptArgs: for Rust bindings' API. This can be seen by bindings' users. + * CExprSOptArgs: Each field has C expression(e.g. CString, *const c_char) + * RawSOptArgs: Each field has raw pointers or integer values + * + * SOptArgs ---try_into()---> CExprSOptArgs ---to_raw()---> RawSOptArgs + * + * Note: direct translation from SOptArgs to RawSOptArgs will cause a memory + * management problem. Using into_raw/from_raw, this problem can be avoided, + * but it is complex to handle freeing memories manually in Rust because of + * panic/?/etc. + *) + (* generate structs for optional arguments *) + List.iter ( + fun ({ name = name; shortdesc = shortdesc; + style = (ret, args, optargs) }) -> + let cname = snake2caml name in + let rec contains_ptr args = match args with + | [] -> false + | OString _ ::_ + | OStringList _::_ -> true + | _::xs -> contains_ptr xs + in + let opt_life_parameter = if contains_ptr optargs then "<'a>" else "" in + if optargs <> [] then ( + pr "\n"; + pr "/* Optional Structs */\n"; + pr "#[derive(Default)]\n"; + pr "pub struct %sOptArgs%s {\n" cname opt_life_parameter; + List.iter ( + fun optarg -> + let n = translate_bad_symbols (name_of_optargt optarg) in + match optarg with + | OBool _ -> + pr " pub %s: Option<bool>,\n" n + | OInt _ -> + pr " pub %s: Option<i32>,\n" n + | OInt64 _ -> + pr " pub %s: Option<i64>,\n" n + | OString _ -> + pr " pub %s: Option<&'a str>,\n" n + | OStringList _ -> + pr " pub %s: Option<&'a [&'a str]>,\n" n + ) optargs; + pr "}\n\n"; + + pr "struct CExpr%sOptArgs {\n" cname; + List.iter ( + fun optarg -> + let n = translate_bad_symbols (name_of_optargt optarg) in + match optarg with + | OBool _ | OInt _ -> + pr " %s: Option<c_int>,\n" n + | OInt64 _ -> + pr " %s: Option<i64>,\n" n + | OString _ -> + pr " %s: Option<ffi::CString>,\n" n + | OStringList _ -> + (* buffers and their pointer vector *) + pr " %s: Option<(Vec<ffi::CString>, Vec<*const c_char>)>,\n" n + ) optargs; + pr "}\n\n"; + + pr "impl%s TryFrom<%sOptArgs%s> for CExpr%sOptArgs {\n" + opt_life_parameter cname opt_life_parameter cname; + pr " type Error = Error;\n"; + pr " fn try_from(optargs: %sOptArgs%s) -> Result<Self, Self::Error> {\n" cname opt_life_parameter; + pr " Ok(CExpr%sOptArgs {\n" cname; + List.iteri ( + fun index optarg -> + let n = translate_bad_symbols (name_of_optargt optarg) in + match optarg with + | OBool _ -> + pr " %s: optargs.%s.map(|b| if b { 1 } else { 0 }),\n" n n; + | OInt _ | OInt64 _ -> + pr " %s: optargs.%s, \n" n n; + | OString _ -> + pr " %s: optargs.%s.map(|v| ffi::CString::new(v)).transpose()?,\n" n n; + | OStringList _ -> + pr " %s: optargs.%s.map(\n" n n; + pr " |v| Ok::<_, Error>({\n"; + pr " let v = arg_string_list(v)?;\n"; + pr " let mut w = (&v).into_iter()\n"; + pr " .map(|v| v.as_ptr())\n"; + pr " .collect::<Vec<_>>();\n"; + pr " w.push(ptr::null());\n"; + pr " (v, w)\n"; + pr " })\n"; + pr " ).transpose()?,\n"; + ) optargs; + pr " })\n"; + pr " }\n"; + pr "}\n"; + + (* raw struct for C bindings *) + pr "#[repr(C)]\n"; + pr "struct Raw%sOptArgs {\n" cname; + pr " bitmask: u64,\n"; + List.iter ( + fun optarg -> + let n = translate_bad_symbols (name_of_optargt optarg) in + match optarg with + | OBool _ -> + pr " %s: c_int,\n" n + | OInt _ -> + pr " %s: c_int,\n" n + | OInt64 _ -> + pr " %s: i64,\n" n + | OString _ -> + pr " %s: *const c_char,\n" n + | OStringList _ -> + pr " %s: *const *const c_char,\n" n + ) optargs; + pr "}\n\n"; + + pr "impl convert::From<&CExpr%sOptArgs> for Raw%sOptArgs {\n" + cname cname; + pr " fn from(optargs: &CExpr%sOptArgs) -> Self {\n" cname; + pr " let mut bitmask = 0;\n"; + pr " Raw%sOptArgs {\n" cname; + List.iteri ( + fun index optarg -> + let n = translate_bad_symbols (name_of_optargt optarg) in + match optarg with + | OBool _ | OInt _ | OInt64 _ -> + pr " %s: if let Some(v) = optargs.%s {\n" n n; + pr " bitmask |= 1 << %d;\n" index; + pr " v\n"; + pr " } else {\n"; + pr " 0\n"; + pr " },\n"; + | OString _ -> + pr " %s: if let Some(ref v) = optargs.%s {\n" n n; + pr " bitmask |= 1 << %d;\n" index; + pr " v.as_ptr()\n"; + pr " } else {\n"; + pr " ptr::null()\n"; + pr " },\n"; + | OStringList _ -> + pr " %s: if let Some((_, ref v)) = optargs.%s {\n" n n; + pr " bitmask |= 1 << %d;\n" index; + pr " v.as_ptr()\n"; + pr " } else {\n"; + pr " ptr::null()\n"; + pr " },\n"; + ) optargs; + pr " bitmask,\n"; + pr " }\n"; + pr " }\n"; + pr "}\n"; + ); + ) (actions |> external_functions |> sort); + + (* extern C APIs *) + pr "extern \"C\" {\n"; + List.iter ( + fun ({ name = name; shortdesc = shortdesc; + style = (ret, args, optargs) } as f) -> + let cname = snake2caml name in + pr " #[allow(non_snake_case)]\n"; + pr " fn %s(g: *const guestfs_h" f.c_function; + List.iter ( + fun arg -> + pr ", "; + match arg with + | Bool n -> pr "%s: c_int" n + | String (_, n) -> pr "%s: *const c_char" n + | OptString n -> pr "%s: *const c_char" n + | Int n -> pr "%s: c_int" n + | Int64 n -> pr "%s: i64" n + | Pointer (_, n) -> pr "%s: *const ffi::c_void" n + | StringList (_, n) -> pr "%s: *const *const c_char" n + | BufferIn n -> pr "%s: *const c_char, %s_len: usize" n n + ) args; + (match ret with + | RBufferOut _ -> pr ", size: *const usize" + | _ -> () + ); + if optargs <> [] then + pr ", optarg: *const Raw%sOptArgs" cname; + + pr ") -> "; + + (match ret with + | RErr | RInt _ | RBool _ -> pr "c_int" + | RInt64 _ -> pr "i64" + | RConstString _ | RString _ | RConstOptString _ -> pr "*const c_char" + | RBufferOut _ -> pr "*const u8" + | RStringList _ | RHashtable _-> pr "*const *const c_char" + | RStruct (_, n) -> + let n = camel_name_of_struct n in + pr "*const Raw%s" n + | RStructList (_, n) -> + let n = camel_name_of_struct n in + pr "*const RawList<Raw%s>" n + ); + pr ";\n"; + + ) (actions |> external_functions |> sort); + pr "}\n"; + + + pr "impl Handle {\n"; + List.iter ( + fun ({ name = name; shortdesc = shortdesc; longdesc = longdesc; + style = (ret, args, optargs) } as f) -> + let cname = snake2caml name in + pr " /// %s\n" shortdesc; + pr " #[allow(non_snake_case)]\n"; + pr " pub fn %s" name; + + (* generate arguments *) + pr "(&self, "; + + let comma = ref false in + List.iter ( + fun arg -> + if !comma then pr ", "; + comma := true; + match arg with + | Bool n -> pr "%s: bool" n + | Int n -> pr "%s: i32" n + | Int64 n -> pr "%s: i64" n + | String (_, n) -> pr "%s: &str" n + | OptString n -> pr "%s: Option<&str>" n + | StringList (_, n) -> pr "%s: &[&str]" n + | BufferIn n -> pr "%s: &[u8]" n + | Pointer (_, n) -> pr "%s: *mut c_void" n + ) args; + if optargs <> [] then ( + if !comma then pr ", "; + comma := true; + pr "optargs: %sOptArgs" cname + ); + pr ")"; + + (* generate return type *) + pr " -> Result<"; + (match ret with + | RErr -> pr "()" + | RInt _ -> pr "i32" + | RInt64 _ -> pr "i64" + | RBool _ -> pr "bool" + | RConstString _ -> pr "&'static str" + | RString _ -> pr "String" + | RConstOptString _ -> pr "Option<&'static str>" + | RStringList _ -> pr "Vec<String>" + | RStruct (_, sn) -> + let sn = camel_name_of_struct sn in + pr "%s" sn + | RStructList (_, sn) -> + let sn = camel_name_of_struct sn in + pr "Vec<%s>" sn + | RHashtable _ -> pr "collections::HashMap<String, String>" + | RBufferOut _ -> pr "Vec<u8>"); + pr ", Error> {\n"; + + + let _pr = pr in + let pr fs = indent 2; pr fs in + List.iter ( + function + | Bool n -> + pr "let %s = if %s { 1 } else { 0 };\n" n n + | String (_, n) -> + pr "let c_%s = ffi::CString::new(%s)?;\n" n n; + | OptString n -> + pr "let c_%s = %s.map(|s| ffi::CString::new(s)).transpose()?;\n" n n; + | StringList (_, n) -> + pr "let c_%s_v = arg_string_list(%s)?;\n" n n; + pr "let mut c_%s = (&c_%s_v).into_iter().map(|v| v.as_ptr()).collect::<Vec<_>>();\n" n n; + pr "c_%s.push(ptr::null());\n" n; + | BufferIn n -> + pr "let c_%s_len = %s.len();\n" n n; + pr "let c_%s = unsafe { ffi::CString::from_vec_unchecked(%s.to_vec())};\n" n n; + | Int _ | Int64 _ | Pointer _ -> () + ) args; + + (match ret with + | RBufferOut _ -> + pr "let mut size = 0usize;\n" + | _ -> () + ); + + if optargs <> [] then ( + pr "let optargs_cexpr = CExpr%sOptArgs::try_from(optargs)?;\n" cname; + ); + + pr "\n"; + + pr "let r = unsafe { %s(self.g" f.c_function; + let pr = _pr in + List.iter ( + fun arg -> + pr ", "; + match arg with + | String (_, n) -> pr "(&c_%s).as_ptr()" n + | OptString n -> pr "match &c_%s { Some(ref s) => s.as_ptr(), None => ptr::null() }\n" n + | StringList (_, n) -> pr "(&c_%s).as_ptr() as *const *const c_char" n + | Bool n | Int n | Int64 n | Pointer (_, n) -> pr "%s" n + | BufferIn n -> pr "(&c_%s).as_ptr(), c_%s_len" n n + ) args; + (match ret with + | RBufferOut _ -> pr ", &mut size as *mut usize" + | _ -> () + ); + if optargs <> [] then ( + pr ", &(Raw%sOptArgs::from(&optargs_cexpr)) as *const Raw%sOptArgs" + cname cname; + ); + pr ") };\n"; + + let _pr = pr in + let pr fs = indent 2; pr fs in + (match errcode_of_ret ret with + | `CannotReturnError -> () + | `ErrorIsMinusOne -> + pr "if r == -1 {\n"; + pr " return Err(self.get_error_from_handle(\"%s\"));\n" name; + pr "}\n" + | `ErrorIsNULL -> + pr "if r.is_null() {\n"; + pr " return Err(self.get_error_from_handle(\"%s\"));\n" name; + pr "}\n" + ); + + (* This part is not required, but type system will guarantee that + * the buffers are still alive. This is useful because Rust cannot + * know whether raw pointers used above are alive or not. + *) + List.iter ( + function + | Bool _ | Int _ | Int64 _ | Pointer _ -> () + | String (_, n) + | OptString n + | BufferIn n -> pr "drop(c_%s);\n" n; + | StringList (_, n) -> + pr "drop(c_%s);\n" n; + pr "drop(c_%s_v);\n" n; + ) args; + if optargs <> [] then ( + pr "drop(optargs_cexpr);\n"; + ); + + pr "Ok("; + let pr = _pr in + let pr3 fs = indent 3; pr fs in + (match ret with + | RErr -> pr "()" + | RInt _ | RInt64 _ -> pr "r" + | RBool _ -> pr "r != 0" + | RConstString _ -> + pr "unsafe{ ffi::CStr::from_ptr(r) }.to_str()?" + | RString _ -> + pr "{\n"; + pr3 "let s = unsafe { char_ptr_to_string(r) };\n"; + pr3 "unsafe { free(r as *const c_void) };"; + pr3 "s?\n"; + indent 2; pr "}"; + | RConstOptString _ -> + pr "if r.is_null() {\n"; + pr3 "None\n"; + indent 2; pr "} else {\n"; + pr3 "Some(unsafe { ffi::CStr::from_ptr(r) }.to_str()?)\n"; + indent 2; pr "}"; + | RStringList _ -> + pr "{\n"; + pr3 "let s = string_list(r);\n"; + pr3 "free_string_list(r);\n"; + pr3 "s?\n"; + indent 2; pr "}"; + | RStruct (_, n) -> + let sn = camel_name_of_struct n in + pr "{\n"; + pr3 "let s = %s::try_from(r);\n" sn; + pr3 "unsafe { guestfs_free_%s(r) };\n" n; + pr3 "s?\n"; + indent 2; pr "}"; + | RStructList (_, n) -> + let sn = camel_name_of_struct n in + pr "{\n"; + pr3 "let l = struct_list::<Raw%s, %s>(r);\n" sn sn; + pr3 "unsafe { guestfs_free_%s_list(r) };\n" n; + pr3 "l?\n"; + indent 2; pr "}"; + | RHashtable _ -> + pr "{\n"; + pr3 "let h = hashmap(r);\n"; + pr3 "free_string_list(r);\n"; + pr3 "h?\n"; + indent 2; pr "}"; + | RBufferOut _ -> + pr "{\n"; + pr3 "let s = unsafe { slice::from_raw_parts(r, size) }.to_vec();\n"; + pr3 "unsafe { free(r as *const c_void) } ;\n"; + pr3 "s\n"; + indent 2; pr "}"; + ); + pr ")\n"; + pr " }\n\n" + ) (actions |> external_functions |> sort); + pr "}\n" diff --git a/generator/rust.mli b/generator/rust.mli new file mode 100644 index 000000000..5410286c8 --- /dev/null +++ b/generator/rust.mli @@ -0,0 +1,22 @@ +(* libguestfs + * Copyright (C) 2009-2019 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*) + +val generate_rust: unit -> unit + +(* for bindtests.ml *) +val snake2caml: string -> string diff --git a/m4/guestfs-rust.m4 b/m4/guestfs-rust.m4 new file mode 100644 index 000000000..48eee433e --- /dev/null +++ b/m4/guestfs-rust.m4 @@ -0,0 +1,33 @@ +# libguestfs +# Copyright (C) 2009-2019 Red Hat Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +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([RUSTC],[rustc],[rustc],[no]) + AC_CHECK_PROG([CARGO],[cargo],[cargo],[no]) + + AS_IF([test "x$RUSTC" == "xno"], [AC_MSG_WARN([rustc not found])]) + AS_IF([test "x$CARGO" == "xno"], [AC_MSG_WARN([cargo not found])]) +],[ + RUSTC=no + CARGO=no + ]) +AM_CONDITIONAL([HAVE_RUST],[test "x$RUSTC" != "xno" && test "x$CARGO" != "xno"]) diff --git a/run.in b/run.in index 488e1b937..301b02664 100755 --- a/run.in +++ b/run.in @@ -201,6 +201,15 @@ else fi export CGO_LDFLAGS +# For rust +export RUST="@RUST@" +if [ -z "$RUSTFLAGS" ]; then + RUSTFLAGS="-C link-args=-L$b/lib/.libs" +else + RUSTFLAGS="$RUSTFLAGS -C link-args=-L$b/lib/.libs" +fi +export RUSTFLAGS + # For GObject, Javascript and friends. export GJS="@GJS@" prepend GI_TYPELIB_PATH "$b/gobject" diff --git a/rust/.gitignore b/rust/.gitignore new file mode 100644 index 000000000..693699042 --- /dev/null +++ b/rust/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in new file mode 100644 index 000000000..e25dfe768 --- /dev/null +++ b/rust/Cargo.toml.in @@ -0,0 +1,6 @@ +[package] +name = "guestfs" +version = "@VERSION@" +edition = "2018" + +[dependencies] diff --git a/rust/Makefile.am b/rust/Makefile.am new file mode 100644 index 000000000..2ec4f7d08 --- /dev/null +++ b/rust/Makefile.am @@ -0,0 +1,42 @@ +# libguestfs rust bindings +# Copyright (C) 2019 Red Hat Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; 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/bin/bindtests.rs \ + src/lib.rs + +EXTRA_DIST = \ + .gitignore \ + $(generator_built) \ + tests/*.rs \ + Cargo.toml \ + Cargo.lock \ + run-bindtests \ + run-tests + +if HAVE_RUST + +all: src/lib.rs + $(top_builddir)/run $(CARGO) build --release + +TESTS = run-bindtests run-tests + +CLEANFILES += target/*~ + +endif diff --git a/rust/run-bindtests b/rust/run-bindtests new file mode 100755 index 000000000..55484a2c7 --- /dev/null +++ b/rust/run-bindtests @@ -0,0 +1,23 @@ +#!/bin/sh - +# libguestfs Rust bindings +# Copyright (C) 2013 Red Hat Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +set -e + +$CARGO run --bin bindtests > bindtests.tmp +diff -u $srcdir/../bindtests bindtests.tmp +rm bindtests.tmp diff --git a/rust/run-tests b/rust/run-tests new file mode 100755 index 000000000..9a5e7a1e4 --- /dev/null +++ b/rust/run-tests @@ -0,0 +1,21 @@ +#!/bin/sh - +# libguestfs Rust tests +# Copyright (C) 2013 Red Hat Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +set -e + +$CARGO test diff --git a/rust/src/.gitkeep b/rust/src/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/rust/src/base.rs b/rust/src/base.rs new file mode 100644 index 000000000..2dfad91a1 --- /dev/null +++ b/rust/src/base.rs @@ -0,0 +1,125 @@ +/* libguestfs generated file + * WARNING: THIS FILE IS GENERATED + * from the code in the generator/ subdirectory. + * ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST. + * + * Copyright (C) 2009-2019 Red Hat Inc. + * + * 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::error; + +#[allow(non_camel_case_types)] +#[repr(C)] +pub(crate) struct guestfs_h { + _unused: [u32; 0], +} + +#[link(name = "guestfs")] +extern "C" { + fn guestfs_create() -> *mut guestfs_h; + fn guestfs_create_flags(flags: i64) -> *mut guestfs_h; + fn guestfs_close(g: *mut guestfs_h); +} + +const GUESTFS_CREATE_NO_ENVIRONMENT: i64 = 1; +const GUESTFS_CREATE_NO_CLOSE_ON_EXIT: i64 = 2; + +pub struct Handle { + pub(crate) g: *mut guestfs_h, +} + +impl Handle { + pub fn create() -> Result<Handle, error::Error> { + let g = unsafe { guestfs_create() }; + if g.is_null() { + Err(error::Error::Create) + } else { + Ok(Handle { g }) + } + } + + pub fn create_flags(flags: CreateFlags) -> Result<Handle, error::Error> { + let g = unsafe { guestfs_create_flags(flags.to_libc_int()) }; + if g.is_null() { + Err(error::Error::Create) + } else { + Ok(Handle { g }) + } + } +} + +impl Drop for Handle { + fn drop(&mut self) { + unsafe { guestfs_close(self.g) } + } +} + +pub struct CreateFlags { + create_no_environment_flag: bool, + create_no_close_on_exit_flag: bool, +} + +impl CreateFlags { + pub fn none() -> CreateFlags { + CreateFlags { + create_no_environment_flag: false, + create_no_close_on_exit_flag: false, + } + } + + pub fn new() -> CreateFlags { + CreateFlags::none() + } + + pub fn create_no_environment(mut self, flag: bool) -> CreateFlags { + self.create_no_environment_flag = flag; + self + } + + pub fn create_no_close_on_exit_flag(mut self, flag: bool) -> CreateFlags { + self.create_no_close_on_exit_flag = flag; + self + } + + unsafe fn to_libc_int(self) -> i64 { + let mut flag = 0; + flag |= if self.create_no_environment_flag { + GUESTFS_CREATE_NO_ENVIRONMENT + } else { + 0 + }; + flag |= if self.create_no_close_on_exit_flag { + GUESTFS_CREATE_NO_CLOSE_ON_EXIT + } else { + 0 + }; + flag + } +} + +pub struct UUID { + uuid: [u8; 32], +} + +impl UUID { + pub(crate) fn new(uuid: [u8; 32]) -> UUID { + UUID { uuid } + } + pub fn to_bytes(self) -> [u8; 32] { + self.uuid + } +} diff --git a/rust/src/bin/.gitkeep b/rust/src/bin/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/rust/src/error.rs b/rust/src/error.rs new file mode 100644 index 000000000..16246b93f --- /dev/null +++ b/rust/src/error.rs @@ -0,0 +1,70 @@ +/* libguestfs Rust bindings + * Copyright (C) 2009-2019 Red Hat Inc. + * + * 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::base; +use crate::utils; +use std::convert; +use std::ffi; +use std::os::raw::{c_char, c_int}; +use std::str; + +#[link(name = "guestfs")] +extern "C" { + fn guestfs_last_error(g: *mut base::guestfs_h) -> *const c_char; + fn guestfs_last_errno(g: *mut base::guestfs_h) -> c_int; +} + +#[derive(Debug)] +pub struct APIError { + operation: &'static str, + message: String, + errno: i32, +} + +#[derive(Debug)] +pub enum Error { + API(APIError), + IllegalString(ffi::NulError), + Utf8Error(str::Utf8Error), + Create, +} + +impl convert::From<ffi::NulError> for Error { + fn from(error: ffi::NulError) -> Self { + Error::IllegalString(error) + } +} + +impl convert::From<str::Utf8Error> for Error { + fn from(error: str::Utf8Error) -> Self { + Error::Utf8Error(error) + } +} + +impl base::Handle { + pub(crate) fn get_error_from_handle(&self, operation: &'static str) -> Error { + let c_msg = unsafe { guestfs_last_error(self.g) }; + let message = unsafe { utils::char_ptr_to_string(c_msg).unwrap() }; + let errno = unsafe { guestfs_last_errno(self.g) }; + Error::API(APIError { + operation, + message, + errno, + }) + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 000000000..f2d610e69 --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,8 @@ +mod base; +mod error; +mod guestfs; +mod utils; + +pub use crate::base::*; +pub use crate::guestfs::*; +pub use crate::error::*; diff --git a/rust/src/utils.rs b/rust/src/utils.rs new file mode 100644 index 000000000..7ec2d2dc2 --- /dev/null +++ b/rust/src/utils.rs @@ -0,0 +1,146 @@ +/* libguestfs Rust bindings + * Copyright (C) 2009-2019 Red Hat Inc. + * + * 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::error; +use std::collections; +use std::convert::TryFrom; +use std::ffi; +use std::os::raw::{c_char, c_void}; +use std::str; + +extern "C" { + fn free(buf: *const c_void); +} + +pub(crate) struct NullTerminatedIter<T: Copy + Clone> { + p: *const *const T, +} + +impl<T: Copy + Clone> NullTerminatedIter<T> { + pub(crate) fn new(p: *const *const T) -> NullTerminatedIter<T> { + NullTerminatedIter { p } + } +} + +impl<T: Copy + Clone> Iterator for NullTerminatedIter<T> { + type Item = *const T; + fn next(&mut self) -> Option<*const T> { + let r = unsafe { *(self.p) }; + if r.is_null() { + None + } else { + self.p = unsafe { self.p.offset(1) }; + Some(r) + } + } +} + +#[repr(C)] +pub(crate) struct RawList<T> { + size: u32, + ptr: *const T, +} + +pub(crate) struct RawListIter<'a, T> { + current: u32, + list: &'a RawList<T>, +} + +impl<T> RawList<T> { + fn iter<'a>(&'a self) -> RawListIter<'a, T> { + RawListIter { + current: 0, + list: self, + } + } +} + +impl<'a, T> Iterator for RawListIter<'a, T> { + type Item = *const T; + fn next(&mut self) -> Option<*const T> { + if self.current >= self.list.size { + None + } else { + let elem = unsafe { self.list.ptr.offset(self.current as isize) }; + self.current += 1; + Some(elem) + } + } +} + +pub(crate) fn arg_string_list(v: &[&str]) -> Result<Vec<ffi::CString>, error::Error> { + let mut w = Vec::new(); + for x in v.iter() { + let y: &str = x; + w.push(ffi::CString::new(y)?); + } + Ok(w) +} + +pub(crate) fn free_string_list(l: *const *const c_char) { + for buf in NullTerminatedIter::new(l) { + unsafe { free(buf as *const c_void) }; + } + unsafe { free(l as *const c_void) }; +} + +pub(crate) fn hashmap( + l: *const *const c_char, +) -> Result<collections::HashMap<String, String>, error::Error> { + let mut map = collections::HashMap::new(); + let mut iter = NullTerminatedIter::new(l); + while let Some(key) = iter.next() { + if let Some(val) = iter.next() { + let key = unsafe { char_ptr_to_string(key) }?; + let val = unsafe { char_ptr_to_string(val) }?; + map.insert(key, val); + } else { + // Internal Error -> panic + panic!("odd number of items in hash table"); + } + } + Ok(map) +} + +pub(crate) fn struct_list<T, S: TryFrom<*const T, Error = error::Error>>( + l: *const RawList<T>, +) -> Result<Vec<S>, error::Error> { + let mut v = Vec::new(); + for x in unsafe { &*l }.iter() { + v.push(S::try_from(x)?); + } + Ok(v) +} + +pub(crate) fn string_list(l: *const *const c_char) -> Result<Vec<String>, error::Error> { + let mut v = Vec::new(); + for x in NullTerminatedIter::new(l) { + let s = unsafe { char_ptr_to_string(x) }?; + v.push(s); + } + Ok(v) +} + +pub(crate) unsafe fn char_ptr_to_string(ptr: *const c_char) -> Result<String, str::Utf8Error> { + fn char_ptr_to_string_inner(ptr: *const c_char) -> Result<String, str::Utf8Error> { + let s = unsafe { ffi::CStr::from_ptr(ptr) }; + let s = s.to_str()?.to_string(); + Ok(s) + } + char_ptr_to_string_inner(ptr) +} diff --git a/rust/tests/.gitkeep b/rust/tests/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/rust/tests/010_load.rs b/rust/tests/010_load.rs new file mode 100644 index 000000000..4cb43f2c1 --- /dev/null +++ b/rust/tests/010_load.rs @@ -0,0 +1,24 @@ +/* libguestfs Rust bindings +Copyright (C) 2009-2019 Red Hat Inc. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +extern crate guestfs; + +#[test] +fn load() { + // nop +} diff --git a/rust/tests/020_create.rs b/rust/tests/020_create.rs new file mode 100644 index 000000000..13acbc7d7 --- /dev/null +++ b/rust/tests/020_create.rs @@ -0,0 +1,24 @@ +/* libguestfs Rust bindings +Copyright (C) 2009-2019 Red Hat Inc. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +extern crate guestfs; + +#[test] +fn create() { + guestfs::Handle::create().unwrap(); +} diff --git a/rust/tests/030_create_flags.rs b/rust/tests/030_create_flags.rs new file mode 100644 index 000000000..df3190d4c --- /dev/null +++ b/rust/tests/030_create_flags.rs @@ -0,0 +1,29 @@ +/* libguestfs Rust bindings +Copyright (C) 2009-2019 Red Hat Inc. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +extern crate guestfs; + +use guestfs::*; + +#[test] +fn create_flags() { + let _h = Handle::create_flags(CreateFlags::none()).expect("create_flags fail"); + // TODO: Add parse_environment to check the flag is created correctly + let flags = CreateFlags::new().create_no_environment(true); + let _h = Handle::create_flags(flags).expect("create_flags fail"); + // TODO: Add parse_environment to check the flag is created correctly +} diff --git a/rust/tests/040_create_multiple.rs b/rust/tests/040_create_multiple.rs new file mode 100644 index 000000000..024320198 --- /dev/null +++ b/rust/tests/040_create_multiple.rs @@ -0,0 +1,38 @@ +/* libguestfs Rust bindings +Copyright (C) 2009-2019 Red Hat Inc. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +extern crate guestfs; + +fn create() -> guestfs::Handle { + match guestfs::Handle::create() { + Ok(g) => g, + Err(e) => panic!("fail: {:?}", e), + } +} + +fn ignore(_x: guestfs::Handle, _y: guestfs::Handle, _z: guestfs::Handle) { + // drop +} + +#[test] +fn create_multiple() { + let x = create(); + let y = create(); + let z = create(); + ignore(x, y, z) +} diff --git a/rust/tests/050_handle_properties.rs b/rust/tests/050_handle_properties.rs new file mode 100644 index 000000000..0b955d5cf --- /dev/null +++ b/rust/tests/050_handle_properties.rs @@ -0,0 +1,62 @@ +/* libguestfs Rust bindings +Copyright (C) 2009-2019 Red Hat Inc. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +extern crate guestfs; + +use std::default::Default; + +#[test] +fn verbose() { + let g = guestfs::Handle::create().expect("create"); + g.set_verbose(true).expect("set_verbose"); + assert_eq!(g.get_verbose().expect("get_verbose"), true); + g.set_verbose(false).expect("set_verbose"); + assert_eq!(g.get_verbose().expect("get_verbose"), false); +} + +#[test] +fn trace() { + let g = guestfs::Handle::create().expect("create"); + g.set_trace(true).expect("set_trace"); + assert_eq!(g.get_trace().expect("get_trace"), true); + g.set_trace(false).expect("set_trace"); + assert_eq!(g.get_trace().expect("get_trace"), false); +} + +#[test] +fn autosync() { + let g = guestfs::Handle::create().expect("create"); + g.set_autosync(true).expect("set_autosync"); + assert_eq!(g.get_autosync().expect("get_autosync"), true); + g.set_autosync(false).expect("set_autosync"); + assert_eq!(g.get_autosync().expect("get_autosync"), false); +} + +#[test] +fn path() { + let g = guestfs::Handle::create().expect("create"); + g.set_path(Some(".")).expect("set_path"); + assert_eq!(g.get_path().expect("get_path"), "."); +} + +#[test] +fn add_drive() { + let g = guestfs::Handle::create().expect("create"); + g.add_drive("/dev/null", Default::default()) + .expect("add_drive"); +} diff --git a/rust/tests/070_opt_args.rs b/rust/tests/070_opt_args.rs new file mode 100644 index 000000000..04b4890c2 --- /dev/null +++ b/rust/tests/070_opt_args.rs @@ -0,0 +1,41 @@ +/* libguestfs Rust bindings +Copyright (C) 2009-2019 Red Hat Inc. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +extern crate guestfs; + +use std::default::Default; + +#[test] +fn no_optargs() { + let g = guestfs::Handle::create().expect("create"); + g.add_drive("/dev/null", Default::default()) + .expect("add_drive"); +} + +#[test] +fn one_optarg() { + let g = guestfs::Handle::create().expect("create"); + g.add_drive( + "/dev/null", + guestfs::AddDriveOptArgs { + readonly: Some(true), + ..Default::default() + }, + ) + .expect("add_drive"); +} diff --git a/rust/tests/080_version.rs b/rust/tests/080_version.rs new file mode 100644 index 000000000..19e441d67 --- /dev/null +++ b/rust/tests/080_version.rs @@ -0,0 +1,26 @@ +/* libguestfs Rust bindings +Copyright (C) 2009-2019 Red Hat Inc. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +extern crate guestfs; + +#[test] +fn version() { + let g = guestfs::Handle::create().expect("create"); + let v = g.version().expect("version"); + assert_eq!(v.major, 1) +} diff --git a/rust/tests/090_ret_values.rs b/rust/tests/090_ret_values.rs new file mode 100644 index 000000000..d3e2e80da --- /dev/null +++ b/rust/tests/090_ret_values.rs @@ -0,0 +1,61 @@ +/* libguestfs Rust bindings +Copyright (C) 2009-2019 Red Hat Inc. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +extern crate guestfs; + +#[test] +fn rint() { + let g = guestfs::Handle::create().expect("create"); + assert_eq!(g.internal_test_rint("10").unwrap(), 10); + assert!(g.internal_test_rinterr().is_err()) +} + +#[test] +fn rint64() { + let g = guestfs::Handle::create().expect("create"); + assert_eq!(g.internal_test_rint64("10").unwrap(), 10); + assert!(g.internal_test_rint64err().is_err()) +} + +#[test] +fn rbool() { + let g = guestfs::Handle::create().expect("create"); + assert!(g.internal_test_rbool("true").unwrap()); + assert!(!g.internal_test_rbool("false").unwrap()); + assert!(g.internal_test_rboolerr().is_err()) +} + +#[test] +fn rconststring() { + let g = guestfs::Handle::create().expect("create"); + assert_eq!( + g.internal_test_rconststring("test").unwrap(), + "static string" + ); + assert!(g.internal_test_rconststringerr().is_err()) +} + +#[test] +fn rconstoptstring() { + let g = guestfs::Handle::create().expect("create"); + assert_eq!( + g.internal_test_rconstoptstring("test").unwrap(), + Some("static string") + ); + assert_eq!(g.internal_test_rconstoptstringerr().unwrap(), None) +} diff --git a/rust/tests/100_launch.rs b/rust/tests/100_launch.rs new file mode 100644 index 000000000..1c1d8146a --- /dev/null +++ b/rust/tests/100_launch.rs @@ -0,0 +1,65 @@ +/* libguestfs Rust bindings +Copyright (C) 2009-2019 Red Hat Inc. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +extern crate guestfs; + +use std::default::Default; + +#[test] +fn launch() { + let g = guestfs::Handle::create().expect("create"); + g.add_drive_scratch(500 * 1024 * 1024, Default::default()) + .expect("add_drive_scratch"); + g.launch().expect("launch"); + g.pvcreate("/dev/sda").expect("pvcreate"); + g.vgcreate("VG", &["/dev/sda"]).expect("vgcreate"); + g.lvcreate("LV1", "VG", 200).expect("lvcreate"); + g.lvcreate("LV2", "VG", 200).expect("lvcreate"); + + let lvs = g.lvs().expect("lvs"); + assert_eq!( + lvs, + vec!["/dev/VG/LV1".to_string(), "/dev/VG/LV2".to_string()] + ); + + g.mkfs("ext2", "/dev/VG/LV1", Default::default()) + .expect("mkfs"); + g.mount("/dev/VG/LV1", "/").expect("mount"); + g.mkdir("/p").expect("mkdir"); + g.touch("/q").expect("touch"); + + let mut dirs = g.readdir("/").expect("readdir"); + + dirs.sort_by(|a, b| a.name.cmp(&b.name)); + + let mut v = Vec::new(); + for x in &dirs { + v.push((x.name.as_str(), x.ftyp as u8)); + } + assert_eq!( + v, + vec![ + (".", b'd'), + ("..", b'd'), + ("lost+found", b'd'), + ("p", b'd'), + ("q", b'r') + ] + ); + g.shutdown().expect("shutdown"); +} -- 2.20.1 (Apple Git-117)
Pino Toscano
2019-Jul-26 16:41 UTC
Re: [Libguestfs] [PATCH] Rust bindings: Add Rust bindings
Hi Hiroyuki, sorry for the late reply. Most of the work is definitely nice! There are few notes below, although they are not big issues. I will check this patch once more on monday, especially the rust parts. Otherwise, I'd say that we are close to merging this :) On Tuesday, 23 July 2019 10:37:17 CEST Hiroyuki Katsura wrote:> From: Hiroyuki_Katsura <hiroyuki.katsura.0513@gmail.com> > > Rust bindings: Add create / close functions > > Rust bindings: Add 4 bindings tests > > Rust bindings: Add generator of structs > > Rust bindings: Add generator of structs for optional arguments > > Rust bindings: Add generator of function signatures > > Rust bindings: Complete actions > > Rust bindings: Fix memory management > > Rust bindings: Add bindtests > > Rust bindings: Add additional 4 bindings tests > > Rust bindings: Format test files > > Rust bindings: Incorporate bindings to build system > ---IMHO there should be a commit message saying that this is new binding for Rust and its name, and what is included (actions & tests, not events, not examples). Also, as we talked about, make sure to fix the copyright to properly credit yourself.> rust/src/.gitkeep | 0This .gitkeep file is not needed, as there are other files in src/.> rust/tests/.gitkeep | 0Ditto.> +(* Utilities for Rust *) > +(* Are there corresponding functions to them? *) > +(* Should they be placed in utils.ml? *)Usually we add generic functions to common places only when we know in advance that there will be multiple users. Otherwise, like in this case, having utilities only where used is perfectly fine. In any case, if any of these utilities will be needed in more places in the future, it is easy to do a simple no-op commit to just move some code.> +let rec indent n = match n with > + | x when x > 0 -> pr " "; indent (x - 1) > + | _ -> ()A small nit here: let rec indent = function | x when x > 0 -> pr " "; indent (x - 1) | _ -> ()> + > +(* split_on_char exists since OCaml 4.04 *) > +(* but current requirements: >=4.01 *) > +let split_on_char c = Str.split (Str.regexp (String.make 1 c))The generator uses the internal mlstdutils shared code, see common/mlstdutils. One of the things provided are extra functions for the String module, and one in particular can help here: String.nsplit. Also...> +let snake2caml name > + let l = split_on_char '_' name in > + let l = List.map (fun x -> String.capitalize_ascii x) l in > + String.concat "" l... this can be simplified a bit using String.nsplit, and currying: let snake2caml name let l = String.nsplit "_" name in let l = List.map String.capitalize_ascii l in String.concat "" l> +(* because there is a function which contains 'unsafe' field *) > +let black_list = ["unsafe"] > + > +let translate_bad_symbols s > + if List.exists (fun x -> s = x) black_list then > + s ^ "_" > + else > + sHm IMHO the condition in the if sounds like List.mem :) What about: if List.mem s black_list then> + let cname = snake2caml name in > + let rec contains_ptr args = match args with > + | [] -> false > + | OString _ ::_ > + | OStringList _::_ -> true > + | _::xs -> contains_ptr xsAs above, you can avoid the explicit match on the last parameter using the function syntax. OTOH, I think you can use List.exists here: let contains_ptr List.exists ( function | OString _ | OStringList _ -> true | _ -> false )> diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in > new file mode 100644 > index 000000000..e25dfe768 > --- /dev/null > +++ b/rust/Cargo.toml.in > @@ -0,0 +1,6 @@ > +[package] > +name = "guestfs" > +version = "@VERSION@" > +edition = "2018">From what I remember about the Rust naming conventions, maybe the nameshould be guestfs-sys? Martin?> +EXTRA_DIST = \ > + .gitignore \ > + $(generator_built) \ > + tests/*.rs \ > + Cargo.toml \ > + Cargo.lock \ > + run-bindtests \ > + run-testsMost probably also src/*.rs, as they are not automake sources (which are distributed automatically). The same also for the various .gitkeep files, as they need to be in the distribution tarball to ensure the directories exist. Also, you need to use TESTS_ENVIRONMENT like done elsewhere, so the in-built stuff is used when running the tests.> diff --git a/rust/run-bindtests b/rust/run-bindtests > new file mode 100755 > index 000000000..55484a2c7 > --- /dev/null > +++ b/rust/run-bindtests > @@ -0,0 +1,23 @@ > +#!/bin/sh - > +# libguestfs Rust bindings > +# Copyright (C) 2013 Red Hat Inc. > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; either version 2 of the License, or > +# (at your option) any later version. > +# > +# This program 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 General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. > + > +set -e > + > +$CARGO run --bin bindtests > bindtests.tmpHmm I don't think $CARGO is exported. A simple way is to make it available is to export it as test environment, via TESTS_ENVIRONMENT.> diff --git a/rust/src/base.rs b/rust/src/base.rs > new file mode 100644 > index 000000000..2dfad91a1 > --- /dev/null > +++ b/rust/src/base.rs > @@ -0,0 +1,125 @@ > +/* libguestfs generated file > + * WARNING: THIS FILE IS GENERATED > + * from the code in the generator/ subdirectory. > + * ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST.Definitely not ;-) -- Pino Toscano