Send trace output to stderr.
[libguestfs.git] / src / generator.ml
index 2fb3f48..e065cc8 100755 (executable)
@@ -174,6 +174,14 @@ and argt =
      * To return an arbitrary buffer, use RBufferOut.
      *)
   | BufferIn of string
+    (* Key material / passphrase.  Eventually we should treat this
+     * as sensitive and mlock it into physical RAM.  However this
+     * is highly complex because of all the places that XDR-encoded
+     * strings can end up.  So currently the only difference from
+     * 'String' is the way that guestfish requests these parameters
+     * from the user.
+     *)
+  | Key of string
 
 type flags =
   | ProtocolLimitWarning  (* display warning about protocol size limits *)
@@ -551,15 +559,13 @@ handle is closed.  We don't currently have any method to enable
 changes to be committed, although qemu can support this.
 
 This is equivalent to the qemu parameter
-C<-drive file=filename,snapshot=on,readonly=on,if=...>.
+C<-drive file=filename,snapshot=on,if=...>.
 
 C<if=...> is set at compile time by the configuration option
 C<./configure --with-drive-if=...>.  In the rare case where you
 might need to change this at run time, use C<guestfs_add_drive_with_if>
 or C<guestfs_add_drive_ro_with_if>.
 
-C<readonly=on> is only added where qemu supports this option.
-
 Note that this call checks for the existence of C<filename>.  This
 stops you from specifying other types of drive which are supported
 by qemu such as C<nbd:> and C<http:> URLs.  To specify those, use
@@ -850,7 +856,7 @@ see L<guestfs(3)>.");
    "enable or disable command traces",
    "\
 If the command trace flag is set to 1, then commands are
-printed on stdout before they are executed in a format
+printed on stderr before they are executed in a format
 which is very similar to the one used by guestfish.  In
 other words, you can run a program with this enabled, and
 you will get out a script which you can feed to guestfish
@@ -4873,6 +4879,90 @@ will be able to see every block device.
 This command also clears the LVM cache and performs a volume
 group scan.");
 
+  ("luks_open", (RErr, [Device "device"; Key "key"; String "mapname"]), 257, [Optional "luks"],
+   [],
+   "open a LUKS-encrypted block device",
+   "\
+This command opens a block device which has been encrypted
+according to the Linux Unified Key Setup (LUKS) standard.
+
+C<device> is the encrypted block device or partition.
+
+The caller must supply one of the keys associated with the
+LUKS block device, in the C<key> parameter.
+
+This creates a new block device called C</dev/mapper/mapname>.
+Reads and writes to this block device are decrypted from and
+encrypted to the underlying C<device> respectively.
+
+If this block device contains LVM volume groups, then
+calling C<guestfs_vgscan> followed by C<guestfs_vg_activate_all>
+will make them visible.");
+
+  ("luks_open_ro", (RErr, [Device "device"; Key "key"; String "mapname"]), 258, [Optional "luks"],
+   [],
+   "open a LUKS-encrypted block device read-only",
+   "\
+This is the same as C<guestfs_luks_open> except that a read-only
+mapping is created.");
+
+  ("luks_close", (RErr, [Device "device"]), 259, [Optional "luks"],
+   [],
+   "close a LUKS device",
+   "\
+This closes a LUKS device that was created earlier by
+C<guestfs_luks_open> or C<guestfs_luks_open_ro>.  The
+C<device> parameter must be the name of the LUKS mapping
+device (ie. C</dev/mapper/mapname>) and I<not> the name
+of the underlying block device.");
+
+  ("luks_format", (RErr, [Device "device"; Key "key"; Int "keyslot"]), 260, [Optional "luks"; DangerWillRobinson],
+   [],
+   "format a block device as a LUKS encrypted device",
+   "\
+This command erases existing data on C<device> and formats
+the device as a LUKS encrypted device.  C<key> is the
+initial key, which is added to key slot C<slot>.  (LUKS
+supports 8 key slots, numbered 0-7).");
+
+  ("luks_format_cipher", (RErr, [Device "device"; Key "key"; Int "keyslot"; String "cipher"]), 261, [Optional "luks"; DangerWillRobinson],
+   [],
+   "format a block device as a LUKS encrypted device",
+   "\
+This command is the same as C<guestfs_luks_format> but
+it also allows you to set the C<cipher> used.");
+
+  ("luks_add_key", (RErr, [Device "device"; Key "key"; Key "newkey"; Int "keyslot"]), 262, [Optional "luks"],
+   [],
+   "add a key on a LUKS encrypted device",
+   "\
+This command adds a new key on LUKS device C<device>.
+C<key> is any existing key, and is used to access the device.
+C<newkey> is the new key to add.  C<keyslot> is the key slot
+that will be replaced.
+
+Note that if C<keyslot> already contains a key, then this
+command will fail.  You have to use C<guestfs_luks_kill_slot>
+first to remove that key.");
+
+  ("luks_kill_slot", (RErr, [Device "device"; Key "key"; Int "keyslot"]), 263, [Optional "luks"],
+   [],
+   "remove a key from a LUKS encrypted device",
+   "\
+This command deletes the key in key slot C<keyslot> from the
+encrypted LUKS device C<device>.  C<key> must be one of the
+I<other> keys.");
+
+  ("is_lv", (RBool "lvflag", [Device "device"]), 264, [Optional "lvm2"],
+   [InitBasicFSonLVM, IfAvailable "lvm2", TestOutputTrue (
+      [["is_lv"; "/dev/VG/LV"]]);
+    InitBasicFSonLVM, IfAvailable "lvm2", TestOutputFalse (
+      [["is_lv"; "/dev/sda1"]])],
+   "test if device is a logical volume",
+   "\
+This command tests whether C<device> is a logical volume, and
+returns true iff this is the case.");
+
 ]
 
 let all_functions = non_daemon_functions @ daemon_functions
@@ -5294,7 +5384,7 @@ let map_chars f str =
 let name_of_argt = function
   | Pathname n | Device n | Dev_or_Path n | String n | OptString n
   | StringList n | DeviceList n | Bool n | Int n | Int64 n
-  | FileIn n | FileOut n | BufferIn n -> n
+  | FileIn n | FileOut n | BufferIn n | Key n -> n
 
 let java_name_of_struct typ =
   try List.assoc typ java_structs
@@ -5622,7 +5712,7 @@ let rec generate_actions_pod () =
 The string is owned by the guest handle and must I<not> be freed.\n\n"
          | RConstOptString _ ->
              pr "This function returns a string which may be NULL.
-There is way to return an error from this function.
+There is no way to return an error from this function.
 The string is owned by the guest handle and must I<not> be freed.\n\n"
          | RString _ ->
              pr "This function returns a string, or NULL on error.
@@ -5655,6 +5745,10 @@ I<The caller must free the returned buffer after use>.\n\n"
           pr "%s\n\n" protocol_limit_warning;
         if List.mem DangerWillRobinson flags then
           pr "%s\n\n" danger_will_robinson;
+        if List.exists (function Key _ -> true | _ -> false) (snd style) then
+          pr "This function takes a key or passphrase parameter which
+could contain sensitive material.  Read the section
+L</KEYS AND PASSPHRASES> for more information.\n\n";
         match deprecation_notice flags with
         | None -> ()
         | Some txt -> pr "%s\n\n" txt
@@ -5760,7 +5854,7 @@ and generate_xdr () =
            pr "struct %s_args {\n" name;
            List.iter (
              function
-             | Pathname n | Device n | Dev_or_Path n | String n ->
+             | Pathname n | Device n | Dev_or_Path n | String n | Key n ->
                  pr "  string %s<>;\n" n
              | OptString n -> pr "  str *%s;\n" n
              | StringList n | DeviceList n -> pr "  str %s<>;\n" n
@@ -6046,7 +6140,8 @@ check_state (guestfs_h *g, const char *caller)
       | FileOut n
       | BufferIn n
       | StringList n
-      | DeviceList n ->
+      | DeviceList n
+      | Key n ->
           pr "  if (%s == NULL) {\n" n;
           pr "    error (g, \"%%s: %%s: parameter cannot be NULL\",\n";
           pr "           \"%s\", \"%s\");\n" shortname n;
@@ -6079,7 +6174,7 @@ check_state (guestfs_h *g, const char *caller)
       pr "\n"
     );
 
-    pr "    printf (\"%s\");\n" shortname;
+    pr "    fprintf (stderr, \"%s\");\n" shortname;
     List.iter (
       function
       | String n                       (* strings *)
@@ -6088,29 +6183,30 @@ check_state (guestfs_h *g, const char *caller)
       | Dev_or_Path n
       | FileIn n
       | FileOut n
-      | BufferIn n ->
+      | BufferIn n
+      | Key n ->
           (* guestfish doesn't support string escaping, so neither do we *)
-          pr "    printf (\" \\\"%%s\\\"\", %s);\n" n
+          pr "    fprintf (stderr, \" \\\"%%s\\\"\", %s);\n" n
       | OptString n ->                 (* string option *)
-          pr "    if (%s) printf (\" \\\"%%s\\\"\", %s);\n" n n;
-          pr "    else printf (\" null\");\n"
+          pr "    if (%s) fprintf (stderr, \" \\\"%%s\\\"\", %s);\n" n n;
+          pr "    else fprintf (stderr, \" null\");\n"
       | StringList n
       | DeviceList n ->                        (* string list *)
-          pr "    putchar (' ');\n";
-          pr "    putchar ('\"');\n";
+          pr "    fputc (' ', stderr);\n";
+          pr "    fputc ('\"', stderr);\n";
           pr "    for (i = 0; %s[i]; ++i) {\n" n;
-          pr "      if (i > 0) putchar (' ');\n";
-          pr "      fputs (%s[i], stdout);\n" n;
+          pr "      if (i > 0) fputc (' ', stderr);\n";
+          pr "      fputs (%s[i], stderr);\n" n;
           pr "    }\n";
-          pr "    putchar ('\"');\n";
+          pr "    fputc ('\"', stderr);\n";
       | Bool n ->                      (* boolean *)
-          pr "    fputs (%s ? \" true\" : \" false\", stdout);\n" n
+          pr "    fputs (%s ? \" true\" : \" false\", stderr);\n" n
       | Int n ->                       (* int *)
-          pr "    printf (\" %%d\", %s);\n" n
+          pr "    fprintf (stderr, \" %%d\", %s);\n" n
       | Int64 n ->
-          pr "    printf (\" %%\" PRIi64, %s);\n" n
+          pr "    fprintf (stderr, \" %%\" PRIi64, %s);\n" n
     ) (snd style);
-    pr "    putchar ('\\n');\n";
+    pr "    fputc ('\\n', stderr);\n";
     pr "  }\n";
     pr "\n";
   in
@@ -6181,7 +6277,7 @@ check_state (guestfs_h *g, const char *caller)
        | args ->
            List.iter (
              function
-             | Pathname n | Device n | Dev_or_Path n | String n ->
+             | Pathname n | Device n | Dev_or_Path n | String n | Key n ->
                  pr "  args.%s = (char *) %s;\n" n n
              | OptString n ->
                  pr "  args.%s = %s ? (char **) &%s : NULL;\n" n n n
@@ -6461,7 +6557,8 @@ and generate_daemon_actions () =
              function
              | Device n | Dev_or_Path n
              | Pathname n
-             | String n -> ()
+             | String n
+             | Key n -> ()
              | OptString n -> pr "  char *%s;\n" n
              | StringList n | DeviceList n -> pr "  char **%s;\n" n
              | Bool n -> pr "  int %s;\n" n
@@ -6518,7 +6615,7 @@ and generate_daemon_actions () =
                  pr_args n;
                  pr "  REQUIRE_ROOT_OR_RESOLVE_DEVICE (%s, %s, goto done);\n"
                    n (if is_filein then "cancel_receive ()" else "0");
-             | String n -> pr_args n
+             | String n | Key n -> pr_args n
              | OptString n -> pr "  %s = args.%s ? *args.%s : NULL;\n" n n n
              | StringList n ->
                  pr_list_handling_code n;
@@ -7533,7 +7630,8 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
         | Device n, arg
         | Dev_or_Path n, arg
         | String n, arg
-        | OptString n, arg ->
+        | OptString n, arg
+        | Key n, arg ->
             pr "    const char *%s = \"%s\";\n" n (c_quote arg);
         | BufferIn n, arg ->
             pr "    const char *%s = \"%s\";\n" n (c_quote arg);
@@ -7588,7 +7686,8 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
         | Pathname n, _
         | Device n, _ | Dev_or_Path n, _
         | String n, _
-        | OptString n, _ ->
+        | OptString n, _
+        | Key n, _ ->
             pr ", %s" n
         | BufferIn n, _ ->
             pr ", %s, %s_size" n n
@@ -7714,14 +7813,22 @@ and generate_fish_cmds () =
         match snd style with
         | [] -> name2
         | args ->
+            let args = List.filter (function Key _ -> false | _ -> true) args in
             sprintf "%s %s"
               name2 (String.concat " " (List.map name_of_argt args)) in
 
       let warnings =
-        if List.mem ProtocolLimitWarning flags then
-          ("\n\n" ^ protocol_limit_warning)
+        if List.exists (function Key _ -> true | _ -> false) (snd style) then
+          "\n\nThis command has one or more key or passphrase parameters.
+Guestfish will prompt for these separately."
         else "" in
 
+      let warnings =
+        warnings ^
+          if List.mem ProtocolLimitWarning flags then
+            ("\n\n" ^ protocol_limit_warning)
+          else "" in
+
       (* For DangerWillRobinson commands, we should probably have
        * guestfish prompt before allowing you to use them (especially
        * in interactive mode). XXX
@@ -7879,7 +7986,8 @@ and generate_fish_cmds () =
         | Pathname n
         | Dev_or_Path n
         | FileIn n
-        | FileOut n -> pr "  char *%s;\n" n
+        | FileOut n
+        | Key n -> pr "  char *%s;\n" n
         | BufferIn n ->
             pr "  const char *%s;\n" n;
             pr "  size_t %s_size;\n" n
@@ -7890,7 +7998,10 @@ and generate_fish_cmds () =
       ) (snd style);
 
       (* Check and convert parameters. *)
-      let argc_expected = List.length (snd style) in
+      let argc_expected =
+        let args_no_keys =
+          List.filter (function Key _ -> false | _ -> true) (snd style) in
+        List.length args_no_keys in
       pr "  if (argc != %d) {\n" argc_expected;
       pr "    fprintf (stderr, _(\"%%s should have %%d parameter(s)\\n\"), cmd, %d);\n"
         argc_expected;
@@ -7898,12 +8009,12 @@ and generate_fish_cmds () =
       pr "    return -1;\n";
       pr "  }\n";
 
-      let parse_integer fn fntyp rtyp range name =
+      let parse_integer fn fntyp rtyp range name =
         pr "  {\n";
         pr "    strtol_error xerr;\n";
         pr "    %s r;\n" fntyp;
         pr "\n";
-        pr "    xerr = %s (argv[%d], NULL, 0, &r, xstrtol_suffixes);\n" fn i;
+        pr "    xerr = %s (argv[i++], NULL, 0, &r, xstrtol_suffixes);\n" fn;
         pr "    if (xerr != LONGINT_OK) {\n";
         pr "      fprintf (stderr,\n";
         pr "               _(\"%%s: %%s: invalid integer parameter (%%s returned %%d)\\n\"),\n";
@@ -7925,43 +8036,49 @@ and generate_fish_cmds () =
         pr "  }\n";
       in
 
-      iteri (
-        fun i ->
-          function
-          | Device name
-          | String name ->
-              pr "  %s = argv[%d];\n" name i
-          | Pathname name
-          | Dev_or_Path name ->
-              pr "  %s = resolve_win_path (argv[%d]);\n" name i;
-              pr "  if (%s == NULL) return -1;\n" name
-          | OptString name ->
-              pr "  %s = STRNEQ (argv[%d], \"\") ? argv[%d] : NULL;\n"
-                name i i
-          | BufferIn name ->
-              pr "  %s = argv[%d];\n" name i;
-              pr "  %s_size = strlen (argv[%d]);\n" name i
-          | FileIn name ->
-              pr "  %s = file_in (argv[%d]);\n" name i;
-              pr "  if (%s == NULL) return -1;\n" name
-          | FileOut name ->
-              pr "  %s = file_out (argv[%d]);\n" name i;
-              pr "  if (%s == NULL) return -1;\n" name
-          | StringList name | DeviceList name ->
-              pr "  %s = parse_string_list (argv[%d]);\n" name i;
-              pr "  if (%s == NULL) return -1;\n" name;
-          | Bool name ->
-              pr "  %s = is_true (argv[%d]) ? 1 : 0;\n" name i
-          | Int name ->
-              let range =
-                let min = "(-(2LL<<30))"
-                and max = "((2LL<<30)-1)"
-                and comment =
-                  "The Int type in the generator is a signed 31 bit int." in
-                Some (min, max, comment) in
-              parse_integer "xstrtoll" "long long" "int" range name i
-          | Int64 name ->
-              parse_integer "xstrtoll" "long long" "int64_t" None name i
+      if snd style <> [] then
+        pr "  size_t i = 0;\n";
+
+      List.iter (
+        function
+        | Device name
+        | String name ->
+            pr "  %s = argv[i++];\n" name
+        | Pathname name
+        | Dev_or_Path name ->
+            pr "  %s = resolve_win_path (argv[i++]);\n" name;
+            pr "  if (%s == NULL) return -1;\n" name
+        | OptString name ->
+            pr "  %s = STRNEQ (argv[i], \"\") ? argv[i] : NULL;\n" name;
+            pr "  i++;\n"
+        | BufferIn name ->
+            pr "  %s = argv[i];\n" name;
+            pr "  %s_size = strlen (argv[i]);\n" name;
+            pr "  i++;\n"
+        | FileIn name ->
+            pr "  %s = file_in (argv[i++]);\n" name;
+            pr "  if (%s == NULL) return -1;\n" name
+        | FileOut name ->
+            pr "  %s = file_out (argv[i++]);\n" name;
+            pr "  if (%s == NULL) return -1;\n" name
+        | StringList name | DeviceList name ->
+            pr "  %s = parse_string_list (argv[i++]);\n" name;
+            pr "  if (%s == NULL) return -1;\n" name
+        | Key name ->
+            pr "  %s = read_key (\"%s\");\n" name name;
+            pr "  if (%s == NULL) return -1;\n" name
+        | Bool name ->
+            pr "  %s = is_true (argv[i++]) ? 1 : 0;\n" name
+        | Int name ->
+            let range =
+              let min = "(-(2LL<<30))"
+              and max = "((2LL<<30)-1)"
+              and comment =
+                "The Int type in the generator is a signed 31 bit int." in
+              Some (min, max, comment) in
+            parse_integer "xstrtoll" "long long" "int" range name
+        | Int64 name ->
+            parse_integer "xstrtoll" "long long" "int64_t" None name
       ) (snd style);
 
       (* Call C API function. *)
@@ -7975,7 +8092,8 @@ and generate_fish_cmds () =
         | OptString _ | Bool _
         | Int _ | Int64 _
         | BufferIn _ -> ()
-        | Pathname name | Dev_or_Path name | FileOut name ->
+        | Pathname name | Dev_or_Path name | FileOut name
+        | Key name ->
             pr "  free (%s);\n" name
         | FileIn name ->
             pr "  free_file_in (%s);\n" name
@@ -8228,7 +8346,8 @@ and generate_fish_actions_pod () =
       pr " %s" name;
       List.iter (
         function
-        | Pathname n | Device n | Dev_or_Path n | String n -> pr " %s" n
+        | Pathname n | Device n | Dev_or_Path n | String n ->
+            pr " %s" n
         | OptString n -> pr " %s" n
         | StringList n | DeviceList n -> pr " '%s ...'" n
         | Bool _ -> pr " true|false"
@@ -8236,6 +8355,7 @@ and generate_fish_actions_pod () =
         | Int64 n -> pr " %s" n
         | FileIn n | FileOut n -> pr " (%s|-)" n
         | BufferIn n -> pr " %s" n
+        | Key _ -> () (* keys are entered at a prompt *)
       ) (snd style);
       pr "\n";
       pr "\n";
@@ -8245,6 +8365,10 @@ and generate_fish_actions_pod () =
                       | _ -> false) (snd style) then
         pr "Use C<-> instead of a filename to read/write from stdin/stdout.\n\n";
 
+      if List.exists (function Key _ -> true | _ -> false) (snd style) then
+        pr "This command has one or more key or passphrase parameters.
+Guestfish will prompt for these separately.\n\n";
+
       if List.mem ProtocolLimitWarning flags then
         pr "%s\n\n" protocol_limit_warning;
 
@@ -8299,7 +8423,8 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
       | Pathname n
       | Device n | Dev_or_Path n
       | String n
-      | OptString n ->
+      | OptString n
+      | Key n ->
           next ();
           pr "const char *%s" n
       | StringList n | DeviceList n ->
@@ -8608,7 +8733,8 @@ copy_table (char * const * argv)
         | Device n | Dev_or_Path n
         | String n
         | FileIn n
-        | FileOut n ->
+        | FileOut n
+        | Key n ->
             (* Copy strings in case the GC moves them: RHBZ#604691 *)
             pr "  char *%s = guestfs_safe_strdup (g, String_val (%sv));\n" n n
         | OptString n ->
@@ -8664,7 +8790,7 @@ copy_table (char * const * argv)
       List.iter (
         function
         | Pathname n | Device n | Dev_or_Path n | String n | OptString n
-        | FileIn n | FileOut n | BufferIn n ->
+        | FileIn n | FileOut n | BufferIn n | Key n ->
             pr "  free (%s);\n" n
         | StringList n | DeviceList n ->
             pr "  ocaml_guestfs_free_strings (%s);\n" n;
@@ -8755,7 +8881,7 @@ and generate_ocaml_prototype ?(is_external = false) name style =
   List.iter (
     function
     | Pathname _ | Device _ | Dev_or_Path _ | String _ | FileIn _ | FileOut _
-    | BufferIn _ -> pr "string -> "
+    | BufferIn _ | Key _ -> pr "string -> "
     | OptString _ -> pr "string option -> "
     | StringList _ | DeviceList _ -> pr "string array -> "
     | Bool _ -> pr "bool -> "
@@ -8924,7 +9050,7 @@ close (g)
         fun i ->
           function
           | Pathname n | Device n | Dev_or_Path n | String n
-          | FileIn n | FileOut n ->
+          | FileIn n | FileOut n | Key n ->
               pr "      char *%s;\n" n
           | BufferIn n ->
               pr "      char *%s;\n" n;
@@ -8947,7 +9073,7 @@ close (g)
           | Pathname _ | Device _ | Dev_or_Path _ | String _ | OptString _
           | Bool _ | Int _ | Int64 _
           | FileIn _ | FileOut _
-          | BufferIn _ -> ()
+          | BufferIn _ | Key _ -> ()
           | StringList n | DeviceList n -> pr "      free (%s);\n" n
         ) (snd style)
       in
@@ -9343,7 +9469,7 @@ and generate_perl_prototype name style =
       match arg with
       | Pathname n | Device n | Dev_or_Path n | String n
       | OptString n | Bool n | Int n | Int64 n | FileIn n | FileOut n
-      | BufferIn n ->
+      | BufferIn n | Key n ->
           pr "$%s" n
       | StringList n | DeviceList n ->
           pr "\\@%s" n
@@ -9370,25 +9496,35 @@ typedef int Py_ssize_t;
 
 #include \"guestfs.h\"
 
+#ifndef HAVE_PYCAPSULE_NEW
 typedef struct {
   PyObject_HEAD
   guestfs_h *g;
 } Pyguestfs_Object;
+#endif
 
 static guestfs_h *
 get_handle (PyObject *obj)
 {
   assert (obj);
   assert (obj != Py_None);
+#ifndef HAVE_PYCAPSULE_NEW
   return ((Pyguestfs_Object *) obj)->g;
+#else
+  return (guestfs_h*) PyCapsule_GetPointer(obj, \"guestfs_h\");
+#endif
 }
 
 static PyObject *
 put_handle (guestfs_h *g)
 {
   assert (g);
+#ifndef HAVE_PYCAPSULE_NEW
   return
     PyCObject_FromVoidPtrAndDesc ((void *) g, (char *) \"guestfs_h\", NULL);
+#else
+  return PyCapsule_New ((void *) g, \"guestfs_h\", NULL);
+#endif
 }
 
 /* This list should be freed (but not the strings) after use. */
@@ -9482,6 +9618,9 @@ py_guestfs_create (PyObject *self, PyObject *args)
     return NULL;
   }
   guestfs_set_error_handler (g, NULL, NULL);
+  /* This can return NULL, but in that case put_handle will have
+   * set the Python error string.
+   */
   return put_handle (g);
 }
 
@@ -9614,7 +9753,7 @@ py_guestfs_close (PyObject *self, PyObject *args)
 
       List.iter (
         function
-        | Pathname n | Device n | Dev_or_Path n | String n
+        | Pathname n | Device n | Dev_or_Path n | String n | Key n
         | FileIn n | FileOut n ->
             pr "  const char *%s;\n" n
         | OptString n -> pr "  const char *%s;\n" n
@@ -9635,7 +9774,8 @@ py_guestfs_close (PyObject *self, PyObject *args)
       pr "  if (!PyArg_ParseTuple (args, (char *) \"O";
       List.iter (
         function
-        | Pathname _ | Device _ | Dev_or_Path _ | String _ | FileIn _ | FileOut _ -> pr "s"
+        | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _
+        | FileIn _ | FileOut _ -> pr "s"
         | OptString _ -> pr "z"
         | StringList _ | DeviceList _ -> pr "O"
         | Bool _ -> pr "i" (* XXX Python has booleans? *)
@@ -9649,7 +9789,8 @@ py_guestfs_close (PyObject *self, PyObject *args)
       pr "                         &py_g";
       List.iter (
         function
-        | Pathname n | Device n | Dev_or_Path n | String n | FileIn n | FileOut n -> pr ", &%s" n
+        | Pathname n | Device n | Dev_or_Path n | String n | Key n
+        | FileIn n | FileOut n -> pr ", &%s" n
         | OptString n -> pr ", &%s" n
         | StringList n | DeviceList n -> pr ", &py_%s" n
         | Bool n -> pr ", &%s" n
@@ -9664,7 +9805,7 @@ py_guestfs_close (PyObject *self, PyObject *args)
       pr "  g = get_handle (py_g);\n";
       List.iter (
         function
-        | Pathname _ | Device _ | Dev_or_Path _ | String _
+        | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _
         | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _
         | BufferIn _ -> ()
         | StringList n | DeviceList n ->
@@ -9680,7 +9821,7 @@ py_guestfs_close (PyObject *self, PyObject *args)
 
       List.iter (
         function
-        | Pathname _ | Device _ | Dev_or_Path _ | String _
+        | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _
         | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _
         | BufferIn _ -> ()
         | StringList n | DeviceList n ->
@@ -9987,7 +10128,8 @@ static VALUE ruby_guestfs_close (VALUE gv)
 
       List.iter (
         function
-        | Pathname n | Device n | Dev_or_Path n | String n | FileIn n | FileOut n ->
+        | Pathname n | Device n | Dev_or_Path n | String n | Key n
+        | FileIn n | FileOut n ->
             pr "  Check_Type (%sv, T_STRING);\n" n;
             pr "  const char *%s = StringValueCStr (%sv);\n" n n;
             pr "  if (!%s)\n" n;
@@ -10048,7 +10190,7 @@ static VALUE ruby_guestfs_close (VALUE gv)
 
       List.iter (
         function
-        | Pathname _ | Device _ | Dev_or_Path _ | String _
+        | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _
         | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _
         | BufferIn _ -> ()
         | StringList n | DeviceList n ->
@@ -10365,7 +10507,8 @@ and generate_java_prototype ?(public=false) ?(privat=false) ?(native=false)
       | String n
       | OptString n
       | FileIn n
-      | FileOut n ->
+      | FileOut n
+      | Key n ->
           pr "String %s" n
       | BufferIn n ->
           pr "byte[] %s" n
@@ -10488,7 +10631,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
         | String n
         | OptString n
         | FileIn n
-        | FileOut n ->
+        | FileOut n
+        | Key n ->
             pr ", jstring j%s" n
         | BufferIn n ->
             pr ", jbyteArray j%s" n
@@ -10545,7 +10689,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
         | String n
         | OptString n
         | FileIn n
-        | FileOut n ->
+        | FileOut n
+        | Key n ->
             pr "  const char *%s;\n" n
         | BufferIn n ->
             pr "  jbyte *%s;\n" n;
@@ -10582,7 +10727,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
         | Device n | Dev_or_Path n
         | String n
         | FileIn n
-        | FileOut n ->
+        | FileOut n
+        | Key n ->
             pr "  %s = (*env)->GetStringUTFChars (env, j%s, NULL);\n" n n
         | OptString n ->
             (* This is completely undocumented, but Java null becomes
@@ -10619,7 +10765,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
         | Device n | Dev_or_Path n
         | String n
         | FileIn n
-        | FileOut n ->
+        | FileOut n
+        | Key n ->
             pr "  (*env)->ReleaseStringUTFChars (env, j%s, %s);\n" n n
         | OptString n ->
             pr "  if (j%s)\n" n;
@@ -10900,7 +11047,7 @@ last_error h = do
           function
           | FileIn n
           | FileOut n
-          | Pathname n | Device n | Dev_or_Path n | String n ->
+          | Pathname n | Device n | Dev_or_Path n | String n | Key n ->
               pr "withCString %s $ \\%s -> " n n
           | BufferIn n ->
               pr "withCStringLen %s $ \\(%s, %s_size) -> " n n n
@@ -10916,7 +11063,10 @@ last_error h = do
             | Int n -> sprintf "(fromIntegral %s)" n
             | Int64 n -> sprintf "(fromIntegral %s)" n
             | FileIn n | FileOut n
-            | Pathname n | Device n | Dev_or_Path n | String n | OptString n | StringList n | DeviceList n -> n
+            | Pathname n | Device n | Dev_or_Path n
+            | String n | OptString n
+            | StringList n | DeviceList n
+            | Key n -> n
             | BufferIn n -> sprintf "%s (fromIntegral %s_size)" n n
           ) (snd style) in
         pr "withForeignPtr h (\\p -> c_%s %s)\n" name
@@ -10967,7 +11117,8 @@ and generate_haskell_prototype ~handle ?(hs = false) style =
   List.iter (
     fun arg ->
       (match arg with
-       | Pathname _ | Device _ | Dev_or_Path _ | String _ -> pr "%s" string
+       | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _ ->
+           pr "%s" string
        | BufferIn _ ->
            if hs then pr "String"
            else pr "CString -> CInt"
@@ -11161,6 +11312,7 @@ namespace Guestfs
           function
           | Pathname n | Device n | Dev_or_Path n | String n | OptString n
           | FileIn n | FileOut n
+          | Key n
           | BufferIn n ->
               pr ", [In] string %s" n
           | StringList n | DeviceList n ->
@@ -11185,6 +11337,7 @@ namespace Guestfs
           function
           | Pathname n | Device n | Dev_or_Path n | String n | OptString n
           | FileIn n | FileOut n
+          | Key n
           | BufferIn n ->
               next (); pr "string %s" n
           | StringList n | DeviceList n ->
@@ -11289,7 +11442,8 @@ print_strings (char *const *argv)
       | Device n | Dev_or_Path n
       | String n
       | FileIn n
-      | FileOut n -> pr "  printf (\"%%s\\n\", %s);\n" n
+      | FileOut n
+      | Key n -> pr "  printf (\"%%s\\n\", %s);\n" n
       | BufferIn n ->
          pr "  {\n";
          pr "    size_t i;\n";
@@ -12222,8 +12376,8 @@ Run it from the top source directory using the command
   output_to "src/guestfs-structs.h" generate_structs_h;
   output_to "src/guestfs-actions.h" generate_actions_h;
   output_to "src/guestfs-internal-actions.h" generate_internal_actions_h;
-  output_to "src/guestfs-actions.c" generate_client_actions;
-  output_to "src/guestfs-bindtests.c" generate_bindtests;
+  output_to "src/actions.c" generate_client_actions;
+  output_to "src/bindtests.c" generate_bindtests;
   output_to "src/guestfs-structs.pod" generate_structs_pod;
   output_to "src/guestfs-actions.pod" generate_actions_pod;
   output_to "src/guestfs-availability.pod" generate_availability_pod;