Pino Toscano
2019-Jul-26 16:41 UTC
Re: [Libguestfs] [PATCH] Rust bindings: Add Rust bindings
Hi Hiroyuki, sorry for the late reply. Most of the work is definitely nice! There are few notes below, although they are not big issues. I will check this patch once more on monday, especially the rust parts. Otherwise, I'd say that we are close to merging this :) On Tuesday, 23 July 2019 10:37:17 CEST Hiroyuki Katsura wrote:> From: Hiroyuki_Katsura <hiroyuki.katsura.0513@gmail.com> > > Rust bindings: Add create / close functions > > Rust bindings: Add 4 bindings tests > > Rust bindings: Add generator of structs > > Rust bindings: Add generator of structs for optional arguments > > Rust bindings: Add generator of function signatures > > Rust bindings: Complete actions > > Rust bindings: Fix memory management > > Rust bindings: Add bindtests > > Rust bindings: Add additional 4 bindings tests > > Rust bindings: Format test files > > Rust bindings: Incorporate bindings to build system > ---IMHO there should be a commit message saying that this is new binding for Rust and its name, and what is included (actions & tests, not events, not examples). Also, as we talked about, make sure to fix the copyright to properly credit yourself.> rust/src/.gitkeep | 0This .gitkeep file is not needed, as there are other files in src/.> rust/tests/.gitkeep | 0Ditto.> +(* Utilities for Rust *) > +(* Are there corresponding functions to them? *) > +(* Should they be placed in utils.ml? *)Usually we add generic functions to common places only when we know in advance that there will be multiple users. Otherwise, like in this case, having utilities only where used is perfectly fine. In any case, if any of these utilities will be needed in more places in the future, it is easy to do a simple no-op commit to just move some code.> +let rec indent n = match n with > + | x when x > 0 -> pr " "; indent (x - 1) > + | _ -> ()A small nit here: let rec indent = function | x when x > 0 -> pr " "; indent (x - 1) | _ -> ()> + > +(* split_on_char exists since OCaml 4.04 *) > +(* but current requirements: >=4.01 *) > +let split_on_char c = Str.split (Str.regexp (String.make 1 c))The generator uses the internal mlstdutils shared code, see common/mlstdutils. One of the things provided are extra functions for the String module, and one in particular can help here: String.nsplit. Also...> +let snake2caml name > + let l = split_on_char '_' name in > + let l = List.map (fun x -> String.capitalize_ascii x) l in > + String.concat "" l... this can be simplified a bit using String.nsplit, and currying: let snake2caml name let l = String.nsplit "_" name in let l = List.map String.capitalize_ascii l in String.concat "" l> +(* because there is a function which contains 'unsafe' field *) > +let black_list = ["unsafe"] > + > +let translate_bad_symbols s > + if List.exists (fun x -> s = x) black_list then > + s ^ "_" > + else > + sHm IMHO the condition in the if sounds like List.mem :) What about: if List.mem s black_list then> + let cname = snake2caml name in > + let rec contains_ptr args = match args with > + | [] -> false > + | OString _ ::_ > + | OStringList _::_ -> true > + | _::xs -> contains_ptr xsAs above, you can avoid the explicit match on the last parameter using the function syntax. OTOH, I think you can use List.exists here: let contains_ptr List.exists ( function | OString _ | OStringList _ -> true | _ -> false )> diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in > new file mode 100644 > index 000000000..e25dfe768 > --- /dev/null > +++ b/rust/Cargo.toml.in > @@ -0,0 +1,6 @@ > +[package] > +name = "guestfs" > +version = "@VERSION@" > +edition = "2018">From what I remember about the Rust naming conventions, maybe the nameshould be guestfs-sys? Martin?> +EXTRA_DIST = \ > + .gitignore \ > + $(generator_built) \ > + tests/*.rs \ > + Cargo.toml \ > + Cargo.lock \ > + run-bindtests \ > + run-testsMost probably also src/*.rs, as they are not automake sources (which are distributed automatically). The same also for the various .gitkeep files, as they need to be in the distribution tarball to ensure the directories exist. Also, you need to use TESTS_ENVIRONMENT like done elsewhere, so the in-built stuff is used when running the tests.> diff --git a/rust/run-bindtests b/rust/run-bindtests > new file mode 100755 > index 000000000..55484a2c7 > --- /dev/null > +++ b/rust/run-bindtests > @@ -0,0 +1,23 @@ > +#!/bin/sh - > +# libguestfs Rust bindings > +# Copyright (C) 2013 Red Hat Inc. > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; either version 2 of the License, or > +# (at your option) any later version. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. > + > +set -e > + > +$CARGO run --bin bindtests > bindtests.tmpHmm I don't think $CARGO is exported. A simple way is to make it available is to export it as test environment, via TESTS_ENVIRONMENT.> diff --git a/rust/src/base.rs b/rust/src/base.rs > new file mode 100644 > index 000000000..2dfad91a1 > --- /dev/null > +++ b/rust/src/base.rs > @@ -0,0 +1,125 @@ > +/* libguestfs generated file > + * WARNING: THIS FILE IS GENERATED > + * from the code in the generator/ subdirectory. > + * ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST.Definitely not ;-) -- Pino Toscano
Hiroyuki Katsura
2019-Jul-29 03:08 UTC
Re: [Libguestfs] [PATCH] Rust bindings: Add Rust bindings
Dear Pino, Thank you for your helpful review. I fixed the patch based on your comments. I’ll send it later.> The same also for the various .gitkeep files, as they need to be in the > distribution tarball to ensure the directories exist.Is ’src/bin/.gitkeep’ required in EXTRA_DIST? I think because src/bin/ bindtests.rs is included in EXTRA_DIST, ’src/bin/.gitkeep’ is not required to make sure the directory exists. Is this idea is correct?> From what I remember about the Rust naming conventions, maybe the name > should be guestfs-sys?>From the previous conversation, I thought it was finally concluded that‘xxxx-sys’ should be used when the crate had only externs of linked libraries. And, this rust bindings has a little higher abstractions. Therefore, the name of this crate should not be ‘guestfs-sys.’ I also fixed the license of each file. Could you give me some advice if there are some mistakes? Regards, Hiroyuki 2019年7月27日(土) 1:41 Pino Toscano <ptoscano@redhat.com>:> Hi Hiroyuki, > > sorry for the late reply. > > Most of the work is definitely nice! There are few notes below, > although they are not big issues. I will check this patch once more > on monday, especially the rust parts. > > Otherwise, I'd say that we are close to merging this :) > > On Tuesday, 23 July 2019 10:37:17 CEST Hiroyuki Katsura wrote: > > From: Hiroyuki_Katsura <hiroyuki.katsura.0513@gmail.com> > > > > Rust bindings: Add create / close functions > > > > Rust bindings: Add 4 bindings tests > > > > Rust bindings: Add generator of structs > > > > Rust bindings: Add generator of structs for optional arguments > > > > Rust bindings: Add generator of function signatures > > > > Rust bindings: Complete actions > > > > Rust bindings: Fix memory management > > > > Rust bindings: Add bindtests > > > > Rust bindings: Add additional 4 bindings tests > > > > Rust bindings: Format test files > > > > Rust bindings: Incorporate bindings to build system > > --- > > IMHO there should be a commit message saying that this is new binding > for Rust and its name, and what is included (actions & tests, not > events, not examples). > > Also, as we talked about, make sure to fix the copyright to properly > credit yourself. > > > rust/src/.gitkeep | 0 > > This .gitkeep file is not needed, as there are other files in src/. > > > rust/tests/.gitkeep | 0 > > Ditto. > > > +(* Utilities for Rust *) > > +(* Are there corresponding functions to them? *) > > +(* Should they be placed in utils.ml? *) > > Usually we add generic functions to common places only when we know in > advance that there will be multiple users. Otherwise, like in this > case, having utilities only where used is perfectly fine. > In any case, if any of these utilities will be needed in more places in > the future, it is easy to do a simple no-op commit to just move some > code. > > > +let rec indent n = match n with > > + | x when x > 0 -> pr " "; indent (x - 1) > > + | _ -> () > > A small nit here: > > let rec indent = function > | x when x > 0 -> pr " "; indent (x - 1) > | _ -> () > > > + > > +(* split_on_char exists since OCaml 4.04 *) > > +(* but current requirements: >=4.01 *) > > +let split_on_char c = Str.split (Str.regexp (String.make 1 c)) > > The generator uses the internal mlstdutils shared code, see > common/mlstdutils. One of the things provided are extra functions for > the String module, and one in particular can help here: String.nsplit. > Also... > > > +let snake2caml name > > + let l = split_on_char '_' name in > > + let l = List.map (fun x -> String.capitalize_ascii x) l in > > + String.concat "" l > > ... this can be simplified a bit using String.nsplit, and currying: > > let snake2caml name > let l = String.nsplit "_" name in > let l = List.map String.capitalize_ascii l in > String.concat "" l > > > +(* because there is a function which contains 'unsafe' field *) > > +let black_list = ["unsafe"] > > + > > +let translate_bad_symbols s > > + if List.exists (fun x -> s = x) black_list then > > + s ^ "_" > > + else > > + s > > Hm IMHO the condition in the if sounds like List.mem :) What about: > > if List.mem s black_list then > > > + let cname = snake2caml name in > > + let rec contains_ptr args = match args with > > + | [] -> false > > + | OString _ ::_ > > + | OStringList _::_ -> true > > + | _::xs -> contains_ptr xs > > As above, you can avoid the explicit match on the last parameter using > the function syntax. > > OTOH, I think you can use List.exists here: > > let contains_ptr > List.exists ( > function > | OString _ > | OStringList _ -> true > | _ -> false > ) > > > diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in > > new file mode 100644 > > index 000000000..e25dfe768 > > --- /dev/null > > +++ b/rust/Cargo.toml.in > > @@ -0,0 +1,6 @@ > > +[package] > > +name = "guestfs" > > +version = "@VERSION@" > > +edition = "2018" > > From what I remember about the Rust naming conventions, maybe the name > should be guestfs-sys? Martin? > > > +EXTRA_DIST = \ > > + .gitignore \ > > + $(generator_built) \ > > + tests/*.rs \ > > + Cargo.toml \ > > + Cargo.lock \ > > + run-bindtests \ > > + run-tests > > Most probably also src/*.rs, as they are not automake sources (which > are distributed automatically). > > The same also for the various .gitkeep files, as they need to be in the > distribution tarball to ensure the directories exist. > > Also, you need to use TESTS_ENVIRONMENT like done elsewhere, so the > in-built stuff is used when running the tests. > > > diff --git a/rust/run-bindtests b/rust/run-bindtests > > new file mode 100755 > > index 000000000..55484a2c7 > > --- /dev/null > > +++ b/rust/run-bindtests > > @@ -0,0 +1,23 @@ > > +#!/bin/sh - > > +# libguestfs Rust bindings > > +# Copyright (C) 2013 Red Hat Inc. > > +# > > +# This program is free software; you can redistribute it and/or modify > > +# it under the terms of the GNU General Public License as published by > > +# the Free Software Foundation; either version 2 of the License, or > > +# (at your option) any later version. > > +# > > +# This program is distributed in the hope that it will be useful, > > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > > +# GNU General Public License for more details. > > +# > > +# You should have received a copy of the GNU General Public License > > +# along with this program; if not, write to the Free Software > > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > 02110-1301 USA. > > + > > +set -e > > + > > +$CARGO run --bin bindtests > bindtests.tmp > > Hmm I don't think $CARGO is exported. A simple way is to make it > available is to export it as test environment, via TESTS_ENVIRONMENT. > > > diff --git a/rust/src/base.rs b/rust/src/base.rs > > new file mode 100644 > > index 000000000..2dfad91a1 > > --- /dev/null > > +++ b/rust/src/base.rs > > @@ -0,0 +1,125 @@ > > +/* libguestfs generated file > > + * WARNING: THIS FILE IS GENERATED > > + * from the code in the generator/ subdirectory. > > + * ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST. > > Definitely not ;-) > > -- > Pino Toscano
Hiroyuki Katsura
2019-Jul-29 03:10 UTC
Re: [Libguestfs] [PATCH] Rust bindings: Add Rust bindings
From: Hiroyuki_Katsura <hiroyuki.katsura.0513@gmail.com> This patch includes Actions and their tests. Missing: - Events - Examples 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 | 67 ++++ generator/bindtests.mli | 1 + generator/docstrings.ml | 6 +- generator/docstrings.mli | 2 +- generator/main.ml | 5 + generator/rust.ml | 558 ++++++++++++++++++++++++++++ generator/rust.mli | 22 ++ m4/guestfs-rust.m4 | 33 ++ run.in | 10 + rust/.gitignore | 3 + rust/Cargo.toml.in | 23 ++ rust/Makefile.am | 45 +++ rust/run-bindtests | 23 ++ rust/run-tests | 21 ++ rust/src/base.rs | 121 ++++++ rust/src/bin/.gitkeep | 0 rust/src/error.rs | 70 ++++ rust/src/lib.rs | 26 ++ rust/src/utils.rs | 146 ++++++++ rust/tests/010_load.rs | 24 ++ rust/tests/020_create.rs | 24 ++ rust/tests/030_create_flags.rs | 30 ++ 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, 1562 insertions(+), 3 deletions(-) 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/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/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..bc00cdfe2 100644 --- a/generator/bindtests.ml +++ b/generator/bindtests.ml @@ -983,6 +983,73 @@ and generate_php_bindtests () dump "bindtests" +and generate_rust_bindtests () + let copywrites = ["Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com>"] in + generate_header ~copywrites:copywrites 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/docstrings.ml b/generator/docstrings.ml index 0606c923b..09c460091 100644 --- a/generator/docstrings.ml +++ b/generator/docstrings.ml @@ -76,7 +76,7 @@ type comment_style | ErlangStyle | LuaStyle | PODStyle type license = GPLv2plus | LGPLv2plus -let generate_header ?(inputs = []) ?emacs_mode comment license +let generate_header ?(copywrites = ["Red Hat Inc."]) ?(inputs = []) ?emacs_mode comment license let c = match comment with | CStyle -> pr "/* "; " *" | CPlusPlusStyle -> pr "// "; "//" @@ -102,7 +102,9 @@ let generate_header ?(inputs = []) ?emacs_mode comment license ); pr "%s ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST.\n" c; pr "%s\n" c; - pr "%s Copyright (C) %s Red Hat Inc.\n" c copyright_years; + List.iter (fun x -> + pr "%s Copyright (C) %s %s\n" c copyright_years x; + ) copywrites; pr "%s\n" c; (match license with | GPLv2plus -> diff --git a/generator/docstrings.mli b/generator/docstrings.mli index 18d1ae255..e8c13d6ea 100644 --- a/generator/docstrings.mli +++ b/generator/docstrings.mli @@ -31,4 +31,4 @@ val version_added : Types.action -> string option val copyright_years : string -val generate_header : ?inputs:string list -> ?emacs_mode:string -> comment_style -> license -> unit +val generate_header : ?copywrites:string list -> ?inputs:string list -> ?emacs_mode:string -> comment_style -> license -> 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..25948bdf0 --- /dev/null +++ b/generator/rust.ml @@ -0,0 +1,558 @@ +(* libguestfs + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * 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 + +let copywrites = ["Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com>"] + +(* Utilities for Rust *) +(* Are there corresponding functions to them? *) +(* Should they be placed in utils.ml? *) +let rec indent = function + | x when x > 0 -> pr " "; indent (x - 1) + | _ -> () + +let snake2caml name + let l = String.nsplit "_" name in + let l = List.map String.capitalize_ascii l in + String.concat "" l + +(* because there is a function which contains 'unsafe' field *) +let black_list = ["unsafe"] + +let translate_bad_symbols s + if List.mem s black_list then + s ^ "_" + else + s + +let generate_rust () + generate_header ~copywrites:copywrites CStyle LGPLv2plus; + + pr " +use crate::base::*; +use crate::utils::*; +use crate::error::*; +use std::collections; +use std::convert; +use std::convert::TryFrom; +use std::ffi; +use std::os::raw::{c_char, c_int, c_void}; +use std::ptr; +use std::slice; + +extern \"C\" { + fn free(buf: *const c_void); +} +"; + + List.iter ( + fun { s_camel_name = name; s_name = c_name; s_cols = cols } -> + pr "\n"; + pr "pub struct %s {\n" name; + List.iter ( + function + | n, FChar -> pr " pub %s: i8,\n" n + | n, FString -> pr " pub %s: String,\n" n + | n, FBuffer -> pr " pub %s: Vec<u8>,\n" n + | n, FUInt32 -> pr " pub %s: u32,\n" n + | n, FInt32 -> pr " pub %s: i32,\n" n + | n, (FUInt64 | FBytes) -> pr " pub %s: u64,\n" n + | n, FInt64 -> pr " pub %s: i64,\n" n + | n, FUUID -> pr " pub %s: UUID,\n" n + | n, FOptPercent -> pr " pub %s: Option<f32>,\n" n + ) cols; + pr "}\n"; + pr "#[repr(C)]\n"; + pr "struct Raw%s {\n" name; + List.iter ( + function + | n, FChar -> pr " %s: c_char,\n" n + | n, FString -> pr " %s: *const c_char,\n" n + | n, FBuffer -> + pr " %s_len: usize,\n" n; + pr " %s: *const c_char,\n" n; + | n, FUUID -> pr " %s: [u8; 32],\n" n + | n, FUInt32 -> pr " %s: u32,\n" n + | n, FInt32 -> pr " %s: i32,\n" n + | n, (FUInt64 | FBytes) -> pr " %s: u64,\n" n + | n, FInt64 -> pr " %s: i64,\n" n + | n, FOptPercent -> pr " %s: f32,\n" n + ) cols; + pr "}\n"; + pr "\n"; + pr "impl TryFrom<*const Raw%s> for %s {\n" name name; + pr " type Error = Error;\n"; + pr " fn try_from(raw: *const Raw%s) -> Result<Self, Self::Error> {\n" name; + pr " Ok(unsafe {\n"; + pr " %s {\n" name; + List.iter ( + fun x -> + indent 4; + match x with + | n, FChar -> + pr "%s: (*raw).%s as i8,\n" n n; + | n, FString -> + pr "%s: char_ptr_to_string((*raw).%s)?,\n" n n; + | n, FBuffer -> + pr "%s: slice::from_raw_parts((*raw).%s as *const u8, (*raw).%s_len).to_vec(),\n" n n n + | n, FUUID -> + pr "%s: UUID::new((*raw).%s),\n" n n + | n, (FUInt32 | FInt32 | FUInt64 | FInt64 | FBytes) -> + pr "%s: (*raw).%s,\n" n n + | n, FOptPercent -> + pr "%s: if (*raw).%s < 0.0 {\n" n n; + indent 4; pr " None\n"; + indent 4; pr "} else {\n"; + indent 4; pr " Some((*raw).%s)\n" n; + indent 4; pr"},\n" + ) cols; + pr " }\n"; + pr " })\n"; + pr " }\n"; + pr "}\n" + ) external_structs; + + (* generate free functionf of structs *) + pr "\n"; + pr "extern \"C\" {\n"; + List.iter ( + fun { s_camel_name = name; s_name = c_name; } -> + pr " #[allow(dead_code)]\n"; + pr " fn guestfs_free_%s(v: *const Raw%s);\n" c_name name; + pr " #[allow(dead_code)]\n"; + pr " fn guestfs_free_%s_list(l: *const RawList<Raw%s>);\n" c_name name; + ) external_structs; + pr "}\n"; + + (* [Outline] There are three types for each optional structs: SOptArgs, + * CExprSOptArgs, RawSOptArgs. + * SOptArgs: for Rust bindings' API. This can be seen by bindings' users. + * CExprSOptArgs: Each field has C expression(e.g. CString, *const c_char) + * RawSOptArgs: Each field has raw pointers or integer values + * + * SOptArgs ---try_into()---> CExprSOptArgs ---to_raw()---> RawSOptArgs + * + * Note: direct translation from SOptArgs to RawSOptArgs will cause a memory + * management problem. Using into_raw/from_raw, this problem can be avoided, + * but it is complex to handle freeing memories manually in Rust because of + * panic/?/etc. + *) + (* generate structs for optional arguments *) + List.iter ( + fun ({ name = name; shortdesc = shortdesc; + style = (ret, args, optargs) }) -> + let cname = snake2caml name in + let contains_ptr + List.exists ( + function + | OString _ + | OStringList _ -> true + | _ -> false + ) + in + let opt_life_parameter = if contains_ptr optargs then "<'a>" else "" in + if optargs <> [] then ( + pr "\n"; + pr "/* Optional Structs */\n"; + pr "#[derive(Default)]\n"; + pr "pub struct %sOptArgs%s {\n" cname opt_life_parameter; + List.iter ( + fun optarg -> + let n = translate_bad_symbols (name_of_optargt optarg) in + match optarg with + | OBool _ -> + pr " pub %s: Option<bool>,\n" n + | OInt _ -> + pr " pub %s: Option<i32>,\n" n + | OInt64 _ -> + pr " pub %s: Option<i64>,\n" n + | OString _ -> + pr " pub %s: Option<&'a str>,\n" n + | OStringList _ -> + pr " pub %s: Option<&'a [&'a str]>,\n" n + ) optargs; + pr "}\n\n"; + + pr "struct CExpr%sOptArgs {\n" cname; + List.iter ( + fun optarg -> + let n = translate_bad_symbols (name_of_optargt optarg) in + match optarg with + | OBool _ | OInt _ -> + pr " %s: Option<c_int>,\n" n + | OInt64 _ -> + pr " %s: Option<i64>,\n" n + | OString _ -> + pr " %s: Option<ffi::CString>,\n" n + | OStringList _ -> + (* buffers and their pointer vector *) + pr " %s: Option<(Vec<ffi::CString>, Vec<*const c_char>)>,\n" n + ) optargs; + pr "}\n\n"; + + pr "impl%s TryFrom<%sOptArgs%s> for CExpr%sOptArgs {\n" + opt_life_parameter cname opt_life_parameter cname; + pr " type Error = Error;\n"; + pr " fn try_from(optargs: %sOptArgs%s) -> Result<Self, Self::Error> {\n" cname opt_life_parameter; + pr " Ok(CExpr%sOptArgs {\n" cname; + List.iteri ( + fun index optarg -> + let n = translate_bad_symbols (name_of_optargt optarg) in + match optarg with + | OBool _ -> + pr " %s: optargs.%s.map(|b| if b { 1 } else { 0 }),\n" n n; + | OInt _ | OInt64 _ -> + pr " %s: optargs.%s, \n" n n; + | OString _ -> + pr " %s: optargs.%s.map(|v| ffi::CString::new(v)).transpose()?,\n" n n; + | OStringList _ -> + pr " %s: optargs.%s.map(\n" n n; + pr " |v| Ok::<_, Error>({\n"; + pr " let v = arg_string_list(v)?;\n"; + pr " let mut w = (&v).into_iter()\n"; + pr " .map(|v| v.as_ptr())\n"; + pr " .collect::<Vec<_>>();\n"; + pr " w.push(ptr::null());\n"; + pr " (v, w)\n"; + pr " })\n"; + pr " ).transpose()?,\n"; + ) optargs; + pr " })\n"; + pr " }\n"; + pr "}\n"; + + (* raw struct for C bindings *) + pr "#[repr(C)]\n"; + pr "struct Raw%sOptArgs {\n" cname; + pr " bitmask: u64,\n"; + List.iter ( + fun optarg -> + let n = translate_bad_symbols (name_of_optargt optarg) in + match optarg with + | OBool _ -> + pr " %s: c_int,\n" n + | OInt _ -> + pr " %s: c_int,\n" n + | OInt64 _ -> + pr " %s: i64,\n" n + | OString _ -> + pr " %s: *const c_char,\n" n + | OStringList _ -> + pr " %s: *const *const c_char,\n" n + ) optargs; + pr "}\n\n"; + + pr "impl convert::From<&CExpr%sOptArgs> for Raw%sOptArgs {\n" + cname cname; + pr " fn from(optargs: &CExpr%sOptArgs) -> Self {\n" cname; + pr " let mut bitmask = 0;\n"; + pr " Raw%sOptArgs {\n" cname; + List.iteri ( + fun index optarg -> + let n = translate_bad_symbols (name_of_optargt optarg) in + match optarg with + | OBool _ | OInt _ | OInt64 _ -> + pr " %s: if let Some(v) = optargs.%s {\n" n n; + pr " bitmask |= 1 << %d;\n" index; + pr " v\n"; + pr " } else {\n"; + pr " 0\n"; + pr " },\n"; + | OString _ -> + pr " %s: if let Some(ref v) = optargs.%s {\n" n n; + pr " bitmask |= 1 << %d;\n" index; + pr " v.as_ptr()\n"; + pr " } else {\n"; + pr " ptr::null()\n"; + pr " },\n"; + | OStringList _ -> + pr " %s: if let Some((_, ref v)) = optargs.%s {\n" n n; + pr " bitmask |= 1 << %d;\n" index; + pr " v.as_ptr()\n"; + pr " } else {\n"; + pr " ptr::null()\n"; + pr " },\n"; + ) optargs; + pr " bitmask,\n"; + pr " }\n"; + pr " }\n"; + pr "}\n"; + ); + ) (actions |> external_functions |> sort); + + (* extern C APIs *) + pr "extern \"C\" {\n"; + List.iter ( + fun ({ name = name; shortdesc = shortdesc; + style = (ret, args, optargs) } as f) -> + let cname = snake2caml name in + pr " #[allow(non_snake_case)]\n"; + pr " fn %s(g: *const guestfs_h" f.c_function; + List.iter ( + fun arg -> + pr ", "; + match arg with + | Bool n -> pr "%s: c_int" n + | String (_, n) -> pr "%s: *const c_char" n + | OptString n -> pr "%s: *const c_char" n + | Int n -> pr "%s: c_int" n + | Int64 n -> pr "%s: i64" n + | Pointer (_, n) -> pr "%s: *const ffi::c_void" n + | StringList (_, n) -> pr "%s: *const *const c_char" n + | BufferIn n -> pr "%s: *const c_char, %s_len: usize" n n + ) args; + (match ret with + | RBufferOut _ -> pr ", size: *const usize" + | _ -> () + ); + if optargs <> [] then + pr ", optarg: *const Raw%sOptArgs" cname; + + pr ") -> "; + + (match ret with + | RErr | RInt _ | RBool _ -> pr "c_int" + | RInt64 _ -> pr "i64" + | RConstString _ | RString _ | RConstOptString _ -> pr "*const c_char" + | RBufferOut _ -> pr "*const u8" + | RStringList _ | RHashtable _-> pr "*const *const c_char" + | RStruct (_, n) -> + let n = camel_name_of_struct n in + pr "*const Raw%s" n + | RStructList (_, n) -> + let n = camel_name_of_struct n in + pr "*const RawList<Raw%s>" n + ); + pr ";\n"; + + ) (actions |> external_functions |> sort); + pr "}\n"; + + + pr "impl Handle {\n"; + List.iter ( + fun ({ name = name; shortdesc = shortdesc; longdesc = longdesc; + style = (ret, args, optargs) } as f) -> + let cname = snake2caml name in + pr " /// %s\n" shortdesc; + pr " #[allow(non_snake_case)]\n"; + pr " pub fn %s" name; + + (* generate arguments *) + pr "(&self, "; + + let comma = ref false in + List.iter ( + fun arg -> + if !comma then pr ", "; + comma := true; + match arg with + | Bool n -> pr "%s: bool" n + | Int n -> pr "%s: i32" n + | Int64 n -> pr "%s: i64" n + | String (_, n) -> pr "%s: &str" n + | OptString n -> pr "%s: Option<&str>" n + | StringList (_, n) -> pr "%s: &[&str]" n + | BufferIn n -> pr "%s: &[u8]" n + | Pointer (_, n) -> pr "%s: *mut c_void" n + ) args; + if optargs <> [] then ( + if !comma then pr ", "; + comma := true; + pr "optargs: %sOptArgs" cname + ); + pr ")"; + + (* generate return type *) + pr " -> Result<"; + (match ret with + | RErr -> pr "()" + | RInt _ -> pr "i32" + | RInt64 _ -> pr "i64" + | RBool _ -> pr "bool" + | RConstString _ -> pr "&'static str" + | RString _ -> pr "String" + | RConstOptString _ -> pr "Option<&'static str>" + | RStringList _ -> pr "Vec<String>" + | RStruct (_, sn) -> + let sn = camel_name_of_struct sn in + pr "%s" sn + | RStructList (_, sn) -> + let sn = camel_name_of_struct sn in + pr "Vec<%s>" sn + | RHashtable _ -> pr "collections::HashMap<String, String>" + | RBufferOut _ -> pr "Vec<u8>"); + pr ", Error> {\n"; + + + let _pr = pr in + let pr fs = indent 2; pr fs in + List.iter ( + function + | Bool n -> + pr "let %s = if %s { 1 } else { 0 };\n" n n + | String (_, n) -> + pr "let c_%s = ffi::CString::new(%s)?;\n" n n; + | OptString n -> + pr "let c_%s = %s.map(|s| ffi::CString::new(s)).transpose()?;\n" n n; + | StringList (_, n) -> + pr "let c_%s_v = arg_string_list(%s)?;\n" n n; + pr "let mut c_%s = (&c_%s_v).into_iter().map(|v| v.as_ptr()).collect::<Vec<_>>();\n" n n; + pr "c_%s.push(ptr::null());\n" n; + | BufferIn n -> + pr "let c_%s_len = %s.len();\n" n n; + pr "let c_%s = unsafe { ffi::CString::from_vec_unchecked(%s.to_vec())};\n" n n; + | Int _ | Int64 _ | Pointer _ -> () + ) args; + + (match ret with + | RBufferOut _ -> + pr "let mut size = 0usize;\n" + | _ -> () + ); + + if optargs <> [] then ( + pr "let optargs_cexpr = CExpr%sOptArgs::try_from(optargs)?;\n" cname; + ); + + pr "\n"; + + pr "let r = unsafe { %s(self.g" f.c_function; + let pr = _pr in + List.iter ( + fun arg -> + pr ", "; + match arg with + | String (_, n) -> pr "(&c_%s).as_ptr()" n + | OptString n -> pr "match &c_%s { Some(ref s) => s.as_ptr(), None => ptr::null() }\n" n + | StringList (_, n) -> pr "(&c_%s).as_ptr() as *const *const c_char" n + | Bool n | Int n | Int64 n | Pointer (_, n) -> pr "%s" n + | BufferIn n -> pr "(&c_%s).as_ptr(), c_%s_len" n n + ) args; + (match ret with + | RBufferOut _ -> pr ", &mut size as *mut usize" + | _ -> () + ); + if optargs <> [] then ( + pr ", &(Raw%sOptArgs::from(&optargs_cexpr)) as *const Raw%sOptArgs" + cname cname; + ); + pr ") };\n"; + + let _pr = pr in + let pr fs = indent 2; pr fs in + (match errcode_of_ret ret with + | `CannotReturnError -> () + | `ErrorIsMinusOne -> + pr "if r == -1 {\n"; + pr " return Err(self.get_error_from_handle(\"%s\"));\n" name; + pr "}\n" + | `ErrorIsNULL -> + pr "if r.is_null() {\n"; + pr " return Err(self.get_error_from_handle(\"%s\"));\n" name; + pr "}\n" + ); + + (* This part is not required, but type system will guarantee that + * the buffers are still alive. This is useful because Rust cannot + * know whether raw pointers used above are alive or not. + *) + List.iter ( + function + | Bool _ | Int _ | Int64 _ | Pointer _ -> () + | String (_, n) + | OptString n + | BufferIn n -> pr "drop(c_%s);\n" n; + | StringList (_, n) -> + pr "drop(c_%s);\n" n; + pr "drop(c_%s_v);\n" n; + ) args; + if optargs <> [] then ( + pr "drop(optargs_cexpr);\n"; + ); + + pr "Ok("; + let pr = _pr in + let pr3 fs = indent 3; pr fs in + (match ret with + | RErr -> pr "()" + | RInt _ | RInt64 _ -> pr "r" + | RBool _ -> pr "r != 0" + | RConstString _ -> + pr "unsafe{ ffi::CStr::from_ptr(r) }.to_str()?" + | RString _ -> + pr "{\n"; + pr3 "let s = unsafe { char_ptr_to_string(r) };\n"; + pr3 "unsafe { free(r as *const c_void) };"; + pr3 "s?\n"; + indent 2; pr "}"; + | RConstOptString _ -> + pr "if r.is_null() {\n"; + pr3 "None\n"; + indent 2; pr "} else {\n"; + pr3 "Some(unsafe { ffi::CStr::from_ptr(r) }.to_str()?)\n"; + indent 2; pr "}"; + | RStringList _ -> + pr "{\n"; + pr3 "let s = string_list(r);\n"; + pr3 "free_string_list(r);\n"; + pr3 "s?\n"; + indent 2; pr "}"; + | RStruct (_, n) -> + let sn = camel_name_of_struct n in + pr "{\n"; + pr3 "let s = %s::try_from(r);\n" sn; + pr3 "unsafe { guestfs_free_%s(r) };\n" n; + pr3 "s?\n"; + indent 2; pr "}"; + | RStructList (_, n) -> + let sn = camel_name_of_struct n in + pr "{\n"; + pr3 "let l = struct_list::<Raw%s, %s>(r);\n" sn sn; + pr3 "unsafe { guestfs_free_%s_list(r) };\n" n; + pr3 "l?\n"; + indent 2; pr "}"; + | RHashtable _ -> + pr "{\n"; + pr3 "let h = hashmap(r);\n"; + pr3 "free_string_list(r);\n"; + pr3 "h?\n"; + indent 2; pr "}"; + | RBufferOut _ -> + pr "{\n"; + pr3 "let s = unsafe { slice::from_raw_parts(r, size) }.to_vec();\n"; + pr3 "unsafe { free(r as *const c_void) } ;\n"; + pr3 "s\n"; + indent 2; pr "}"; + ); + pr ")\n"; + pr " }\n\n" + ) (actions |> external_functions |> sort); + pr "}\n" diff --git a/generator/rust.mli b/generator/rust.mli new file mode 100644 index 000000000..9378f5c34 --- /dev/null +++ b/generator/rust.mli @@ -0,0 +1,22 @@ +(* libguestfs + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * 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..aa12a9ef5 --- /dev/null +++ b/m4/guestfs-rust.m4 @@ -0,0 +1,33 @@ +# libguestfs +# Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> +# +# 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..f484f1b76 100755 --- a/run.in +++ b/run.in @@ -201,6 +201,16 @@ else fi export CGO_LDFLAGS +# For rust +export RUST="@RUST@" +export CARGO="@CARGO@" +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..b61e3ec7f --- /dev/null +++ b/rust/Cargo.toml.in @@ -0,0 +1,23 @@ +# libguestfs Rust tests +# Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> +# +# 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. + +[package] +name = "guestfs" +version = "@VERSION@" +edition = "2018" + +[dependencies] diff --git a/rust/Makefile.am b/rust/Makefile.am new file mode 100644 index 000000000..3fed08fe1 --- /dev/null +++ b/rust/Makefile.am @@ -0,0 +1,45 @@ +# libguestfs rust bindings +# Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> +# +# 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 \ + src/*.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 + +TESTS_ENVIRONMENT = $(top_builddir)/run --test diff --git a/rust/run-bindtests b/rust/run-bindtests new file mode 100755 index 000000000..6a5a6901b --- /dev/null +++ b/rust/run-bindtests @@ -0,0 +1,23 @@ +#!/bin/sh - +# libguestfs Rust bindings +# Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> +# +# 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..5b6ef184a --- /dev/null +++ b/rust/run-tests @@ -0,0 +1,21 @@ +#!/bin/sh - +# libguestfs Rust tests +# Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> +# +# 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/base.rs b/rust/src/base.rs new file mode 100644 index 000000000..02ad33535 --- /dev/null +++ b/rust/src/base.rs @@ -0,0 +1,121 @@ +/* libguestfs Rust bindings + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +use crate::error; + +#[allow(non_camel_case_types)] +#[repr(C)] +pub(crate) struct guestfs_h { + _unused: [u32; 0], +} + +#[link(name = "guestfs")] +extern "C" { + fn guestfs_create() -> *mut guestfs_h; + fn guestfs_create_flags(flags: i64) -> *mut guestfs_h; + fn guestfs_close(g: *mut guestfs_h); +} + +const GUESTFS_CREATE_NO_ENVIRONMENT: i64 = 1; +const GUESTFS_CREATE_NO_CLOSE_ON_EXIT: i64 = 2; + +pub struct Handle { + pub(crate) g: *mut guestfs_h, +} + +impl Handle { + pub fn create() -> Result<Handle, error::Error> { + let g = unsafe { guestfs_create() }; + if g.is_null() { + Err(error::Error::Create) + } else { + Ok(Handle { g }) + } + } + + pub fn create_flags(flags: CreateFlags) -> Result<Handle, error::Error> { + let g = unsafe { guestfs_create_flags(flags.to_libc_int()) }; + if g.is_null() { + Err(error::Error::Create) + } else { + Ok(Handle { g }) + } + } +} + +impl Drop for Handle { + fn drop(&mut self) { + unsafe { guestfs_close(self.g) } + } +} + +pub struct CreateFlags { + create_no_environment_flag: bool, + create_no_close_on_exit_flag: bool, +} + +impl CreateFlags { + pub fn none() -> CreateFlags { + CreateFlags { + create_no_environment_flag: false, + create_no_close_on_exit_flag: false, + } + } + + pub fn new() -> CreateFlags { + CreateFlags::none() + } + + pub fn create_no_environment(mut self, flag: bool) -> CreateFlags { + self.create_no_environment_flag = flag; + self + } + + pub fn create_no_close_on_exit_flag(mut self, flag: bool) -> CreateFlags { + self.create_no_close_on_exit_flag = flag; + self + } + + unsafe fn to_libc_int(self) -> i64 { + let mut flag = 0; + flag |= if self.create_no_environment_flag { + GUESTFS_CREATE_NO_ENVIRONMENT + } else { + 0 + }; + flag |= if self.create_no_close_on_exit_flag { + GUESTFS_CREATE_NO_CLOSE_ON_EXIT + } else { + 0 + }; + flag + } +} + +pub struct UUID { + uuid: [u8; 32], +} + +impl UUID { + pub(crate) fn new(uuid: [u8; 32]) -> UUID { + UUID { uuid } + } + pub fn to_bytes(self) -> [u8; 32] { + self.uuid + } +} diff --git a/rust/src/bin/.gitkeep b/rust/src/bin/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/rust/src/error.rs b/rust/src/error.rs new file mode 100644 index 000000000..705ee1735 --- /dev/null +++ b/rust/src/error.rs @@ -0,0 +1,70 @@ +/* libguestfs Rust bindings + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +use crate::base; +use crate::utils; +use std::convert; +use std::ffi; +use std::os::raw::{c_char, c_int}; +use std::str; + +#[link(name = "guestfs")] +extern "C" { + fn guestfs_last_error(g: *mut base::guestfs_h) -> *const c_char; + fn guestfs_last_errno(g: *mut base::guestfs_h) -> c_int; +} + +#[derive(Debug)] +pub struct APIError { + operation: &'static str, + message: String, + errno: i32, +} + +#[derive(Debug)] +pub enum Error { + API(APIError), + IllegalString(ffi::NulError), + Utf8Error(str::Utf8Error), + Create, +} + +impl convert::From<ffi::NulError> for Error { + fn from(error: ffi::NulError) -> Self { + Error::IllegalString(error) + } +} + +impl convert::From<str::Utf8Error> for Error { + fn from(error: str::Utf8Error) -> Self { + Error::Utf8Error(error) + } +} + +impl base::Handle { + pub(crate) fn get_error_from_handle(&self, operation: &'static str) -> Error { + let c_msg = unsafe { guestfs_last_error(self.g) }; + let message = unsafe { utils::char_ptr_to_string(c_msg).unwrap() }; + let errno = unsafe { guestfs_last_errno(self.g) }; + Error::API(APIError { + operation, + message, + errno, + }) + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 000000000..cc41a99f8 --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,26 @@ +/* libguestfs Rust bindings + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * 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 + */ + +mod base; +mod error; +mod guestfs; +mod utils; + +pub use crate::base::*; +pub use crate::error::*; +pub use crate::guestfs::*; diff --git a/rust/src/utils.rs b/rust/src/utils.rs new file mode 100644 index 000000000..c8cec2601 --- /dev/null +++ b/rust/src/utils.rs @@ -0,0 +1,146 @@ +/* libguestfs Rust bindings + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +use crate::error; +use std::collections; +use std::convert::TryFrom; +use std::ffi; +use std::os::raw::{c_char, c_void}; +use std::str; + +extern "C" { + fn free(buf: *const c_void); +} + +pub(crate) struct NullTerminatedIter<T: Copy + Clone> { + p: *const *const T, +} + +impl<T: Copy + Clone> NullTerminatedIter<T> { + pub(crate) fn new(p: *const *const T) -> NullTerminatedIter<T> { + NullTerminatedIter { p } + } +} + +impl<T: Copy + Clone> Iterator for NullTerminatedIter<T> { + type Item = *const T; + fn next(&mut self) -> Option<*const T> { + let r = unsafe { *(self.p) }; + if r.is_null() { + None + } else { + self.p = unsafe { self.p.offset(1) }; + Some(r) + } + } +} + +#[repr(C)] +pub(crate) struct RawList<T> { + size: u32, + ptr: *const T, +} + +pub(crate) struct RawListIter<'a, T> { + current: u32, + list: &'a RawList<T>, +} + +impl<T> RawList<T> { + fn iter<'a>(&'a self) -> RawListIter<'a, T> { + RawListIter { + current: 0, + list: self, + } + } +} + +impl<'a, T> Iterator for RawListIter<'a, T> { + type Item = *const T; + fn next(&mut self) -> Option<*const T> { + if self.current >= self.list.size { + None + } else { + let elem = unsafe { self.list.ptr.offset(self.current as isize) }; + self.current += 1; + Some(elem) + } + } +} + +pub(crate) fn arg_string_list(v: &[&str]) -> Result<Vec<ffi::CString>, error::Error> { + let mut w = Vec::new(); + for x in v.iter() { + let y: &str = x; + w.push(ffi::CString::new(y)?); + } + Ok(w) +} + +pub(crate) fn free_string_list(l: *const *const c_char) { + for buf in NullTerminatedIter::new(l) { + unsafe { free(buf as *const c_void) }; + } + unsafe { free(l as *const c_void) }; +} + +pub(crate) fn hashmap( + l: *const *const c_char, +) -> Result<collections::HashMap<String, String>, error::Error> { + let mut map = collections::HashMap::new(); + let mut iter = NullTerminatedIter::new(l); + while let Some(key) = iter.next() { + if let Some(val) = iter.next() { + let key = unsafe { char_ptr_to_string(key) }?; + let val = unsafe { char_ptr_to_string(val) }?; + map.insert(key, val); + } else { + // Internal Error -> panic + panic!("odd number of items in hash table"); + } + } + Ok(map) +} + +pub(crate) fn struct_list<T, S: TryFrom<*const T, Error = error::Error>>( + l: *const RawList<T>, +) -> Result<Vec<S>, error::Error> { + let mut v = Vec::new(); + for x in unsafe { &*l }.iter() { + v.push(S::try_from(x)?); + } + Ok(v) +} + +pub(crate) fn string_list(l: *const *const c_char) -> Result<Vec<String>, error::Error> { + let mut v = Vec::new(); + for x in NullTerminatedIter::new(l) { + let s = unsafe { char_ptr_to_string(x) }?; + v.push(s); + } + Ok(v) +} + +pub(crate) unsafe fn char_ptr_to_string(ptr: *const c_char) -> Result<String, str::Utf8Error> { + fn char_ptr_to_string_inner(ptr: *const c_char) -> Result<String, str::Utf8Error> { + let s = unsafe { ffi::CStr::from_ptr(ptr) }; + let s = s.to_str()?.to_string(); + Ok(s) + } + char_ptr_to_string_inner(ptr) +} diff --git a/rust/tests/010_load.rs b/rust/tests/010_load.rs new file mode 100644 index 000000000..8667e0144 --- /dev/null +++ b/rust/tests/010_load.rs @@ -0,0 +1,24 @@ +/* libguestfs Rust bindings + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * 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..d1abda86a --- /dev/null +++ b/rust/tests/020_create.rs @@ -0,0 +1,24 @@ +/* libguestfs Rust bindings + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * 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..f5fc9f964 --- /dev/null +++ b/rust/tests/030_create_flags.rs @@ -0,0 +1,30 @@ +/* libguestfs Rust bindings + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * 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..9fc428605 --- /dev/null +++ b/rust/tests/040_create_multiple.rs @@ -0,0 +1,38 @@ +/* libguestfs Rust bindings + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * 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..efdb34873 --- /dev/null +++ b/rust/tests/050_handle_properties.rs @@ -0,0 +1,62 @@ +/* libguestfs Rust bindings + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * 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..93ec5c65f --- /dev/null +++ b/rust/tests/070_opt_args.rs @@ -0,0 +1,41 @@ +/* libguestfs Rust bindings + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * 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..e68a05418 --- /dev/null +++ b/rust/tests/080_version.rs @@ -0,0 +1,26 @@ +/* libguestfs Rust bindings + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * 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..c64f9f20f --- /dev/null +++ b/rust/tests/090_ret_values.rs @@ -0,0 +1,61 @@ +/* libguestfs Rust bindings + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * 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..f5a17fad5 --- /dev/null +++ b/rust/tests/100_launch.rs @@ -0,0 +1,65 @@ +/* libguestfs Rust bindings + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * 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)
Martin Kletzander
2019-Jul-29 08:28 UTC
Re: [Libguestfs] [PATCH] Rust bindings: Add Rust bindings
On Fri, Jul 26, 2019 at 06:41:14PM +0200, Pino Toscano wrote:>Hi Hiroyuki, > >sorry for the late reply. > >Most of the work is definitely nice! There are few notes below, >although they are not big issues. I will check this patch once more >on monday, especially the rust parts. > >Otherwise, I'd say that we are close to merging this :) > >On Tuesday, 23 July 2019 10:37:17 CEST Hiroyuki Katsura wrote: >> From: Hiroyuki_Katsura <hiroyuki.katsura.0513@gmail.com> >> >> Rust bindings: Add create / close functions >> >> Rust bindings: Add 4 bindings tests >> >> Rust bindings: Add generator of structs >> >> Rust bindings: Add generator of structs for optional arguments >> >> Rust bindings: Add generator of function signatures >> >> Rust bindings: Complete actions >> >> Rust bindings: Fix memory management >> >> Rust bindings: Add bindtests >> >> Rust bindings: Add additional 4 bindings tests >> >> Rust bindings: Format test files >> >> Rust bindings: Incorporate bindings to build system >> --- > >IMHO there should be a commit message saying that this is new binding >for Rust and its name, and what is included (actions & tests, not >events, not examples). > >Also, as we talked about, make sure to fix the copyright to properly >credit yourself. > >> rust/src/.gitkeep | 0 > >This .gitkeep file is not needed, as there are other files in src/. > >> rust/tests/.gitkeep | 0 > >Ditto. > >> +(* Utilities for Rust *) >> +(* Are there corresponding functions to them? *) >> +(* Should they be placed in utils.ml? *) > >Usually we add generic functions to common places only when we know in >advance that there will be multiple users. Otherwise, like in this >case, having utilities only where used is perfectly fine. >In any case, if any of these utilities will be needed in more places in >the future, it is easy to do a simple no-op commit to just move some >code. > >> +let rec indent n = match n with >> + | x when x > 0 -> pr " "; indent (x - 1) >> + | _ -> () > >A small nit here: > >let rec indent = function > | x when x > 0 -> pr " "; indent (x - 1) > | _ -> () > >> + >> +(* split_on_char exists since OCaml 4.04 *) >> +(* but current requirements: >=4.01 *) >> +let split_on_char c = Str.split (Str.regexp (String.make 1 c)) > >The generator uses the internal mlstdutils shared code, see >common/mlstdutils. One of the things provided are extra functions for >the String module, and one in particular can help here: String.nsplit. >Also... > >> +let snake2caml name >> + let l = split_on_char '_' name in >> + let l = List.map (fun x -> String.capitalize_ascii x) l in >> + String.concat "" l > >... this can be simplified a bit using String.nsplit, and currying: > >let snake2caml name > let l = String.nsplit "_" name in > let l = List.map String.capitalize_ascii l in > String.concat "" l > >> +(* because there is a function which contains 'unsafe' field *) >> +let black_list = ["unsafe"] >> + >> +let translate_bad_symbols s >> + if List.exists (fun x -> s = x) black_list then >> + s ^ "_" >> + else >> + s > >Hm IMHO the condition in the if sounds like List.mem :) What about: > > if List.mem s black_list then > >> + let cname = snake2caml name in >> + let rec contains_ptr args = match args with >> + | [] -> false >> + | OString _ ::_ >> + | OStringList _::_ -> true >> + | _::xs -> contains_ptr xs > >As above, you can avoid the explicit match on the last parameter using >the function syntax. > >OTOH, I think you can use List.exists here: > >let contains_ptr > List.exists ( > function > | OString _ > | OStringList _ -> true > | _ -> false > ) > >> diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in >> new file mode 100644 >> index 000000000..e25dfe768 >> --- /dev/null >> +++ b/rust/Cargo.toml.in >> @@ -0,0 +1,6 @@ >> +[package] >> +name = "guestfs" >> +version = "@VERSION@" >> +edition = "2018" > >From what I remember about the Rust naming conventions, maybe the name >should be guestfs-sys? Martin? >That would be for a crate that does nothing else then just export the C functions without any code. This is fine as it is, guestfs is not going to have two different stages, it probably does not make sense for this library and is probably not worth the hassle.>> +EXTRA_DIST = \ >> + .gitignore \ >> + $(generator_built) \ >> + tests/*.rs \ >> + Cargo.toml \ >> + Cargo.lock \ >> + run-bindtests \ >> + run-tests > >Most probably also src/*.rs, as they are not automake sources (which >are distributed automatically). > >The same also for the various .gitkeep files, as they need to be in the >distribution tarball to ensure the directories exist. > >Also, you need to use TESTS_ENVIRONMENT like done elsewhere, so the >in-built stuff is used when running the tests. > >> diff --git a/rust/run-bindtests b/rust/run-bindtests >> new file mode 100755 >> index 000000000..55484a2c7 >> --- /dev/null >> +++ b/rust/run-bindtests >> @@ -0,0 +1,23 @@ >> +#!/bin/sh - >> +# libguestfs Rust bindings >> +# Copyright (C) 2013 Red Hat Inc. >> +# >> +# This program is free software; you can redistribute it and/or modify >> +# it under the terms of the GNU General Public License as published by >> +# the Free Software Foundation; either version 2 of the License, or >> +# (at your option) any later version. >> +# >> +# This program is distributed in the hope that it will be useful, >> +# but WITHOUT ANY WARRANTY; without even the implied warranty of >> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >> +# GNU General Public License for more details. >> +# >> +# You should have received a copy of the GNU General Public License >> +# along with this program; if not, write to the Free Software >> +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >> + >> +set -e >> + >> +$CARGO run --bin bindtests > bindtests.tmp > >Hmm I don't think $CARGO is exported. A simple way is to make it >available is to export it as test environment, via TESTS_ENVIRONMENT. >It is, automatically, thanks to the way it was searched for. At least it worked for me.>> diff --git a/rust/src/base.rs b/rust/src/base.rs >> new file mode 100644 >> index 000000000..2dfad91a1 >> --- /dev/null >> +++ b/rust/src/base.rs >> @@ -0,0 +1,125 @@ >> +/* libguestfs generated file >> + * WARNING: THIS FILE IS GENERATED >> + * from the code in the generator/ subdirectory. >> + * ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST. > >Definitely not ;-) > >-- >Pino Toscano
Richard W.M. Jones
2019-Jul-29 12:04 UTC
Re: [Libguestfs] [PATCH] Rust bindings: Add Rust bindings
On Mon, Jul 29, 2019 at 12:08:19PM +0900, Hiroyuki Katsura wrote:> Is ’src/bin/.gitkeep’ required in EXTRA_DIST? I think because src/bin/ > bindtests.rs is included in EXTRA_DIST, ’src/bin/.gitkeep’ is not required > to make sure the directory exists. Is this idea is correct?One tip for checking if your EXTRA_DIST is complete: make && make dist && make maintainer-check-extra-dist The last step will list any differences between what is listed in EXTRA_DIST and what is included in git, plus generated files which we normally include in the tarball. I have fixed all outstanding issues. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://people.redhat.com/~rjones/virt-df/