[Libguestfs] [PATCH] Rust bindings: Add Rust bindings

Hiroyuki Katsura hiroyuki.katsura.0513 at gmail.com
Tue Jul 23 08:36:23 UTC 2019


I found a bug in the bindings, so I fixed it. I'm sorry about sending you
the patch many times.

Regards,
Hiroyuki

2019年7月20日(土) 16:23 Hiroyuki Katsura <hiroyuki.katsura.0513 at gmail.com>:

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


More information about the Libguestfs mailing list