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(a)gmail.com>:
 From: Hiroyuki_Katsura <hiroyuki.katsura.0513(a)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)