On Mon, Jul 08, 2019 at 10:04:57AM +0100, Richard W.M. Jones wrote:>On Mon, Jul 08, 2019 at 10:49:55AM +0200, Martin Kletzander wrote: >> On Mon, Jul 08, 2019 at 10:10:10AM +0200, Pino Toscano wrote: >> >On Saturday, 6 July 2019 13:03:24 CEST Martin Kletzander wrote: >> >>Just one thing, the Cargo.toml includes a version under which the crate would be >> >>published. I presume the version would be the same as the one of the project >> >>itself, i.e. when releasing libguestfs-x.y.z, we publish guestfs-rs-x.y.z to >> >>crates.io. >> > >> >Speaking of naming: it seems like libraries that interface/wrap a >> >foreign C/C++/etc library are usually called foo-sys -- so should our >> >binding be named guestfs-sys? >> > >> >> So you've seen my RFC? =) >> >> Just to guestfs-sys would be a crate that does only two things: >> >> 1) exposes the C functions using `extern` >> >> 2) links to the library >> >> And then another crate (e.g. guestfs) would expose the higher-level, safe, >> hopefully idiomatic API. More information (reasoning etc.) see: >> >> https://doc.rust-lang.org/cargo/reference/build-scripts.html#a-sys-packages > >For the Python guestfs bindings we originally planned to write an >idiomatic (ie. OO in that case) API on top of the raw generated >bindings, but never really got around to it. I'm not convinced it's >really a good idea because you end up chasing new APIs. Remember the >whole point of the generator is that new APIs are instantly available >in all languages as soon as they are added upstream. >For the libnbd use-case I went with a middle ground. That is libnbd-sys is a crated with just the linkage and list of extern functions, but libnbd is supposed to just expose this as methods on the Nbd struct, which take Rust types (as opposed to C types), ensure safety and idiomatically handle errors. No other extra redesign would be there, the code handles functions that are not allowed in a particular state nicely, so that should be fine. The only thing what would be really nice to utilize in libnbd rust crate is the fact that the states there can be encoded into the way the structs are used, so that you could only call those APIs that are accessible at a current state (if that state is controllable by the client, of course), so for example you could use the builder pattern to create a handle prepared to be connected (or even connect it): let h = Nbd::new() .add_meta_context("base:allocation") .set.export_name("/") .set_debug(true) .connect_uri("nbd://") And then you would be able to do, e.g.: h.pread(); h.set_debug(false) But not: h.set_export_name("/exp"); as the object `h` would not even support that function. Anyway, even though it would be useful for libguestfs as well, thanks to the way these projects handle errors it is just something I consider "nice-to-have". Have a nice day, Martin>Rich. > >-- >Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones >Read my programming and virtualization blog: http://rwmj.wordpress.com >virt-top is 'top' for virtual machines. Tiny program with many >powerful monitoring features, net stats, disk stats, logging, etc. >http://people.redhat.com/~rjones/virt-top
I fixed this patch based on comments. I merged all commits to a single commit. I'll send this final patch to this list. Regards, Hiroyuki 2019年7月8日(月) 19:36 Martin Kletzander <mkletzan@redhat.com>:> On Mon, Jul 08, 2019 at 10:04:57AM +0100, Richard W.M. Jones wrote: > >On Mon, Jul 08, 2019 at 10:49:55AM +0200, Martin Kletzander wrote: > >> On Mon, Jul 08, 2019 at 10:10:10AM +0200, Pino Toscano wrote: > >> >On Saturday, 6 July 2019 13:03:24 CEST Martin Kletzander wrote: > >> >>Just one thing, the Cargo.toml includes a version under which the > crate would be > >> >>published. I presume the version would be the same as the one of the > project > >> >>itself, i.e. when releasing libguestfs-x.y.z, we publish > guestfs-rs-x.y.z to > >> >>crates.io. > >> > > >> >Speaking of naming: it seems like libraries that interface/wrap a > >> >foreign C/C++/etc library are usually called foo-sys -- so should our > >> >binding be named guestfs-sys? > >> > > >> > >> So you've seen my RFC? =) > >> > >> Just to guestfs-sys would be a crate that does only two things: > >> > >> 1) exposes the C functions using `extern` > >> > >> 2) links to the library > >> > >> And then another crate (e.g. guestfs) would expose the higher-level, > safe, > >> hopefully idiomatic API. More information (reasoning etc.) see: > >> > >> > https://doc.rust-lang.org/cargo/reference/build-scripts.html#a-sys-packages > > > >For the Python guestfs bindings we originally planned to write an > >idiomatic (ie. OO in that case) API on top of the raw generated > >bindings, but never really got around to it. I'm not convinced it's > >really a good idea because you end up chasing new APIs. Remember the > >whole point of the generator is that new APIs are instantly available > >in all languages as soon as they are added upstream. > > > > For the libnbd use-case I went with a middle ground. That is libnbd-sys > is a > crated with just the linkage and list of extern functions, but libnbd is > supposed to just expose this as methods on the Nbd struct, which take Rust > types > (as opposed to C types), ensure safety and idiomatically handle errors. > > No other extra redesign would be there, the code handles functions that > are not > allowed in a particular state nicely, so that should be fine. The only > thing > what would be really nice to utilize in libnbd rust crate is the fact that > the > states there can be encoded into the way the structs are used, so that you > could > only call those APIs that are accessible at a current state (if that state > is > controllable by the client, of course), so for example you could use the > builder > pattern to create a handle prepared to be connected (or even connect it): > > let h = Nbd::new() > .add_meta_context("base:allocation") > .set.export_name("/") > .set_debug(true) > .connect_uri("nbd://") > > And then you would be able to do, e.g.: > > h.pread(); > h.set_debug(false) > > But not: > > h.set_export_name("/exp"); > > as the object `h` would not even support that function. > > Anyway, even though it would be useful for libguestfs as well, thanks to > the way > these projects handle errors it is just something I consider > "nice-to-have". > > Have a nice day, > Martin > > >Rich. > > > >-- > >Richard Jones, Virtualization Group, Red Hat > http://people.redhat.com/~rjones > >Read my programming and virtualization blog: http://rwmj.wordpress.com > >virt-top is 'top' for virtual machines. Tiny program with many > >powerful monitoring features, net stats, disk stats, logging, etc. > >http://people.redhat.com/~rjones/virt-top >
Hiroyuki Katsura
2019-Jul-17 09:49 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 | 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/Makefile.am b/Makefile.am index b5f33f62b..71cee94f6 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 6b701bef2..dc4ead709 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 @@ -428,6 +432,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 07872d49c..676c08ce5 100644 --- a/generator/Makefile.am +++ b/generator/Makefile.am @@ -105,6 +105,8 @@ sources = \ p2v_config.mli \ ruby.ml \ ruby.mli \ + rust.ml \ + rust.mli \ structs.ml \ structs.mli \ tests_c_api.ml \ @@ -163,6 +165,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 7974550c5..ffe1bb95c 100644 --- a/generator/main.ml +++ b/generator/main.ml @@ -372,6 +372,11 @@ Run it from the top source directory using the command output_to "p2v/virt-p2v-kernel-config.pod" P2v_config.generate_p2v_kernel_config_pod; + output_to "rust/src/lib.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..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 " +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 + +#[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); + fn guestfs_last_error(g: *mut guestfs_h) -> *const c_char; + fn guestfs_last_errno(g: *mut guestfs_h) -> c_int; +} + +extern \"C\" { + fn free(buf: *const c_void); +} + +const GUESTFS_CREATE_NO_ENVIRONMENT: i64 = 1; +const GUESTFS_CREATE_NO_CLOSE_ON_EXIT: i64 = 2; + +pub struct Handle { + g: *mut guestfs_h, +} + +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 + } +} + +struct NullTerminatedIter<T: Copy + Clone> { + p: *const *const T, +} + +impl<T: Copy + Clone> NullTerminatedIter<T> { + 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)] +struct RawList<T> { + size: u32, + ptr: *const T, +} + +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) + } + } +} + +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) +} + +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) }; +} + +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) +} + +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) +} + +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) +} + +#[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 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 }) + } + } + + 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, + }) + } +} + +pub struct UUID { + uuid: [u8; 32], +} + +impl UUID { + fn new(uuid: [u8; 32]) -> UUID { + UUID { uuid } + } + pub fn to_bytes(self) -> [u8; 32] { + self.uuid + } +} +"; + 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..ff284ec00 --- /dev/null +++ b/rust/Makefile.am @@ -0,0 +1,42 @@ +# libguestfs golang 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..2986e898d --- /dev/null +++ b/rust/run-bindtests @@ -0,0 +1,23 @@ +#!/bin/sh - +# libguestfs Golang 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..694ecc9dd --- /dev/null +++ b/rust/run-tests @@ -0,0 +1,21 @@ +#!/bin/sh - +# libguestfs Golang 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/bin/.gitkeep b/rust/src/bin/.gitkeep new file mode 100644 index 000000000..e69de29bb 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..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"); +} 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)