Hiroyuki Katsura
2019-Jul-20 06:55 UTC
Re: [Libguestfs] [PATCH] Rust bindings: Add Rust bindings
> Is this just trying if the guestfs can be linked with?Yes. In OCaml bindings, there is the corresponding test( https://github.com/libguestfs/libguestfs/blob/master/ocaml/t/guestfs_010_load.ml). I just mimicked it. If it is not required, I will remove it. divided the generated files and handmade files in rust/src/ directory. I'll send this fixed patch to this mailing list. I'm not sure about the license problems. Can you teach me that? Regards, Hiroyuki 2019年7月17日(水) 22:40 Martin Kletzander <mkletzan@redhat.com>:> On Wed, Jul 17, 2019 at 06:49:39PM +0900, 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 > >--- > > 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 | 806 ++++++++++++++++++++++++++++ > > 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/bin/.gitkeep | 0 > > 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 +++ > > 27 files changed, 1419 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/bin/.gitkeep > > 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/generator/rust.ml b/generator/rust.ml > >new file mode 100644 > >index 000000000..b7bc76da8 > >--- /dev/null > >+++ b/generator/rust.ml > >@@ -0,0 +1,806 @@ > >+(* 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 " > > I started with this as well (for the libnbd bindings), but it was a PITA > for me > to modify parts of the generator. I then realized that I found out two > things, > each one should help you move part of this outside the generator: > > 1) you can have impls for the same struct in different files > > 2) you can scope the `pub`, for example `pub(crate)` would make the > definition > public but only for this particular crate. That way you can share > things > between modules without exposing it to consumers of this crate > > >+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; > >+use std::str; > >+ > >+#[allow(non_camel_case_types)] > >+enum guestfs_h {} // opaque struct > >+ > > You should not use empty enums for ffi opaque structs as they can be > optimized > out. Also #[repr(C)] it. > > You should rather have a struct with an empty member: > > struct guestfs_handle { > _unused: [u32; 0], > } > > I'll try to find the official info in rust docs, I just do not know on > which > docs page it was. > > /me goes looking > > Oh yeah, it was in the nomicon: > > https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs > > It will be even nicer when RFC 1861 gets stabilized, but that's long long > in the > future, I guess: > > https://github.com/rust-lang/rust/issues/43467 > > Otherwise it looks very similar to what I started, so that should be good > (hopefully) =) > > I hope I'll get some more time to go over the whole posting here and try > it out > as well. > > [...] > > >diff --git a/rust/Makefile.am b/rust/Makefile.am > >new file mode 100644 > >index 000000000..ff284ec00 > >--- /dev/null > >+++ b/rust/Makefile.am > >@@ -0,0 +1,42 @@ > >+# libguestfs golang bindings > > golang? =) > > >+# Copyright (C) 2019 Red Hat Inc. > > I'll let Rich figure out what is supposed to be here :) > > >diff --git a/rust/run-bindtests b/rust/run-bindtests > >new file mode 100755 > >index 000000000..2986e898d > >--- /dev/null > >+++ b/rust/run-bindtests > >@@ -0,0 +1,23 @@ > >+#!/bin/sh - > >+# libguestfs Golang bindings > >+# Copyright (C) 2013 Red Hat Inc. > > dtto > > and some other files as well > > [...] > > >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 > >+} > > Is this just trying if the guestfs can be linked with? > > >diff --git a/rust/tests/020_create.rs b/rust/tests/020_create.rs > >new file mode 100644 > >index 000000000..017dbbac0 > >--- /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() { > >+ assert!(!guestfs::Handle::create().is_err(), "create fail"); > > Isn't Result.is_ok() same as !Result.is_err()? Maybe even unwrap() would > work > here as it would actually show the error that was returned in the > backtrace. > > [...] > > >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. > > Oh, you're also using the older version of the license here (with the old > "logistics" mentioned), but I guess that's fine. I, for one, would ban > these, > but it's a recommendation to put them in each file (although I think it's > an > outdated one, but you never know with lawyers), so... > </rant> > > Anyway, it looks good, although I just skimmed it and I haven't tested it. > > Martin >
Hiroyuki Katsura
2019-Jul-20 07:23 UTC
[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 | 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: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) > >