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