Send trace output to stderr.
[libguestfs.git] / src / generator.ml
index 58eaee4..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 *)
@@ -309,6 +317,9 @@ and test_prereq =
     (* As for 'If' but the test runs _unless_ the code returns true. *)
   | Unless of string
 
+    (* Run the test only if 'string' is available in the daemon. *)
+  | IfAvailable of string
+
 (* Some initial scenarios for testing. *)
 and test_init =
     (* Do nothing, block devices could contain random stuff including
@@ -548,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
@@ -847,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
@@ -989,7 +998,10 @@ closing the handle.");
    "\
 Touch acts like the L<touch(1)> command.  It can be used to
 update the timestamps on a file, or, if the file does not exist,
-to create a new zero-length file.");
+to create a new zero-length file.
+
+This command only works on regular files, and will fail on other
+file types such as directories, symbolic links, block special etc.");
 
   ("cat", (RString "content", [Pathname "path"]), 4, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutput (
@@ -1543,7 +1555,9 @@ See also: C<guestfs_sfdisk_l>, C<guestfs_sfdisk_N>,
 C<guestfs_part_init>");
 
   ("write_file", (RErr, [Pathname "path"; String "content"; Int "size"]), 44, [ProtocolLimitWarning; DeprecatedBy "write"],
-   [],
+   (* Regression test for RHBZ#597135. *)
+   [InitBasicFS, Always, TestLastFail
+      [["write_file"; "/new"; "abc"; "10000"]]],
    "create a file",
    "\
 This call creates a file called C<path>.  The contents of the
@@ -1624,19 +1638,32 @@ and physical volumes.");
     InitISOFS, Always, TestOutput (
       [["file"; "/known-1"]], "ASCII text");
     InitISOFS, Always, TestLastFail (
-      [["file"; "/notexists"]])],
+      [["file"; "/notexists"]]);
+    InitISOFS, Always, TestOutput (
+      [["file"; "/abssymlink"]], "symbolic link");
+    InitISOFS, Always, TestOutput (
+      [["file"; "/directory"]], "directory")],
    "determine file type",
    "\
 This call uses the standard L<file(1)> command to determine
-the type or contents of the file.  This also works on devices,
-for example to find out whether a partition contains a filesystem.
+the type or contents of the file.
 
 This call will also transparently look inside various types
 of compressed file.
 
-The exact command which runs is C<file -zbsL path>.  Note in
+The exact command which runs is C<file -zb path>.  Note in
 particular that the filename is not prepended to the output
-(the C<-b> option).");
+(the C<-b> option).
+
+This command can also be used on C</dev/> devices
+(and partitions, LV names).  You can for example use this
+to determine if a device contains a filesystem, although
+it's usually better to use C<guestfs_vfs_type>.
+
+If the C<path> does not begin with C</dev/> then
+this command only works for the content of regular files.
+For other file types (directory, symbolic link etc) it
+will just return the string C<directory> etc.");
 
   ("command", (RString "output", [StringList "arguments"]), 50, [ProtocolLimitWarning],
    [InitBasicFS, Always, TestOutput (
@@ -2222,7 +2249,7 @@ C<device> to C<label>.  Filesystem labels are limited to
 You can use either C<guestfs_tune2fs_l> or C<guestfs_get_e2label>
 to return the existing label on a filesystem.");
 
-  ("get_e2label", (RString "label", [Device "device"]), 81, [],
+  ("get_e2label", (RString "label", [Device "device"]), 81, [DeprecatedBy "vfs_label"],
    [],
    "get the ext2/3/4 filesystem label",
    "\
@@ -2252,8 +2279,13 @@ L<tune2fs(8)> manpage.
 You can use either C<guestfs_tune2fs_l> or C<guestfs_get_e2uuid>
 to return the existing UUID of a filesystem.");
 
-  ("get_e2uuid", (RString "uuid", [Device "device"]), 83, [],
-   [],
+  ("get_e2uuid", (RString "uuid", [Device "device"]), 83, [DeprecatedBy "vfs_uuid"],
+   (* Regression test for RHBZ#597112. *)
+   (let uuid = uuidgen () in
+    [InitBasicFS, Always, TestOutput (
+       [["mke2journal"; "1024"; "/dev/sdb"];
+        ["set_e2uuid"; "/dev/sdb"; uuid];
+        ["get_e2uuid"; "/dev/sdb"]], uuid)]),
    "get the ext2/3/4 filesystem UUID",
    "\
 This returns the ext2/3/4 filesystem UUID of the filesystem on
@@ -2313,16 +2345,30 @@ any partition tables, filesystem superblocks and so on.
 See also: C<guestfs_zero_device>, C<guestfs_scrub_device>.");
 
   ("grub_install", (RErr, [Pathname "root"; Device "device"]), 86, [],
-   (* Test disabled because grub-install incompatible with virtio-blk driver.
-    * See also: https://bugzilla.redhat.com/show_bug.cgi?id=479760
+   (* See:
+    * https://bugzilla.redhat.com/show_bug.cgi?id=484986
+    * https://bugzilla.redhat.com/show_bug.cgi?id=479760
     *)
-   [InitBasicFS, Disabled, TestOutputTrue (
-      [["grub_install"; "/"; "/dev/sda1"];
+   [InitBasicFS, Always, TestOutputTrue (
+      [["mkdir_p"; "/boot/grub"];
+       ["write"; "/boot/grub/device.map"; "(hd0) /dev/vda"];
+       ["grub_install"; "/"; "/dev/vda"];
        ["is_dir"; "/boot"]])],
    "install GRUB",
    "\
 This command installs GRUB (the Grand Unified Bootloader) on
-C<device>, with the root directory being C<root>.");
+C<device>, with the root directory being C<root>.
+
+Note: If grub-install reports the error
+\"No suitable drive was found in the generated device map.\"
+it may be that you need to create a C</boot/grub/device.map>
+file first that contains the mapping between grub device names
+and Linux device names.  It is usually sufficient to create
+a file containing:
+
+ (hd0) /dev/vda
+
+replacing C</dev/vda> with the name of the installation device.");
 
   ("cp", (RErr, [Pathname "src"; Pathname "dest"]), 87, [],
    [InitBasicFS, Always, TestOutput (
@@ -2634,9 +2680,9 @@ is lost.");
 
   ("resize2fs", (RErr, [Device "device"]), 106, [],
    [], (* lvresize tests this *)
-   "resize an ext2/ext3 filesystem",
+   "resize an ext2, ext3 or ext4 filesystem",
    "\
-This resizes an ext2 or ext3 filesystem to match the size of
+This resizes an ext2, ext3 or ext4 filesystem to match the size of
 the underlying device.
 
 I<Note:> It is sometimes required that you run C<guestfs_e2fsck_f>
@@ -3708,13 +3754,28 @@ and C<guestfs_setcon>");
        ["mkfs_b"; "ext2"; "4096"; "/dev/sda1"];
        ["mount_options"; ""; "/dev/sda1"; "/"];
        ["write"; "/new"; "new file contents"];
-       ["cat"; "/new"]], "new file contents")],
+       ["cat"; "/new"]], "new file contents");
+    InitEmpty, Always, TestRun (
+      [["part_disk"; "/dev/sda"; "mbr"];
+       ["mkfs_b"; "vfat"; "32768"; "/dev/sda1"]]);
+    InitEmpty, Always, TestLastFail (
+      [["part_disk"; "/dev/sda"; "mbr"];
+       ["mkfs_b"; "vfat"; "32769"; "/dev/sda1"]]);
+    InitEmpty, Always, TestLastFail (
+      [["part_disk"; "/dev/sda"; "mbr"];
+       ["mkfs_b"; "vfat"; "33280"; "/dev/sda1"]]);
+    InitEmpty, IfAvailable "ntfsprogs", TestRun (
+      [["part_disk"; "/dev/sda"; "mbr"];
+       ["mkfs_b"; "ntfs"; "32768"; "/dev/sda1"]])],
    "make a filesystem with block size",
    "\
 This call is similar to C<guestfs_mkfs>, but it allows you to
 control the block size of the resulting filesystem.  Supported
 block sizes depend on the filesystem type, but typically they
-are C<1024>, C<2048> or C<4096> only.");
+are C<1024>, C<2048> or C<4096> only.
+
+For VFAT and NTFS the C<blocksize> parameter is treated as
+the requested cluster size.");
 
   ("mke2journal", (RErr, [Int "blocksize"; Device "device"]), 188, [],
    [InitEmpty, Always, TestOutput (
@@ -4701,7 +4762,7 @@ See also C<guestfs_pread>.");
 
   ("resize2fs_size", (RErr, [Device "device"; Int64 "size"]), 248, [],
    [],
-   "resize an ext2/ext3 filesystem (with size)",
+   "resize an ext2, ext3 or ext4 filesystem (with size)",
    "\
 This command is the same as C<guestfs_resize2fs> except that it
 allows you to specify the new size (in bytes) explicitly.");
@@ -4754,6 +4815,154 @@ Do not confuse this with the guestfish-specific
 C<alloc> and C<sparse> commands which create
 a file in the host and attach it as a device.");
 
+  ("vfs_label", (RString "label", [Device "device"]), 253, [],
+   [InitBasicFS, Always, TestOutput (
+       [["set_e2label"; "/dev/sda1"; "LTEST"];
+        ["vfs_label"; "/dev/sda1"]], "LTEST")],
+   "get the filesystem label",
+   "\
+This returns the filesystem label of the filesystem on
+C<device>.
+
+If the filesystem is unlabeled, this returns the empty string.");
+
+  ("vfs_uuid", (RString "uuid", [Device "device"]), 254, [],
+   (let uuid = uuidgen () in
+    [InitBasicFS, Always, TestOutput (
+       [["set_e2uuid"; "/dev/sda1"; uuid];
+        ["vfs_uuid"; "/dev/sda1"]], uuid)]),
+   "get the filesystem UUID",
+   "\
+This returns the filesystem UUID of the filesystem on
+C<device>.
+
+If the filesystem does not have a UUID, this returns the empty string.");
+
+  ("lvm_set_filter", (RErr, [DeviceList "devices"]), 255, [Optional "lvm2"],
+   (* Can't be tested with the current framework because
+    * the VG is being used by the mounted filesystem, so
+    * the vgchange -an command we do first will fail.
+    *)
+    [],
+   "set LVM device filter",
+   "\
+This sets the LVM device filter so that LVM will only be
+able to \"see\" the block devices in the list C<devices>,
+and will ignore all other attached block devices.
+
+Where disk image(s) contain duplicate PVs or VGs, this
+command is useful to get LVM to ignore the duplicates, otherwise
+LVM can get confused.  Note also there are two types
+of duplication possible: either cloned PVs/VGs which have
+identical UUIDs; or VGs that are not cloned but just happen
+to have the same name.  In normal operation you cannot
+create this situation, but you can do it outside LVM, eg.
+by cloning disk images or by bit twiddling inside the LVM
+metadata.
+
+This command also clears the LVM cache and performs a volume
+group scan.
+
+You can filter whole block devices or individual partitions.
+
+You cannot use this if any VG is currently in use (eg.
+contains a mounted filesystem), even if you are not
+filtering out that VG.");
+
+  ("lvm_clear_filter", (RErr, []), 256, [],
+   [], (* see note on lvm_set_filter *)
+   "clear LVM device filter",
+   "\
+This undoes the effect of C<guestfs_lvm_set_filter>.  LVM
+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
@@ -5175,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
@@ -5503,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.
@@ -5536,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
@@ -5641,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
@@ -5927,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;
@@ -5956,11 +6170,11 @@ check_state (guestfs_h *g, const char *caller)
                    | StringList _ | DeviceList _ -> true
                    | _ -> false) (snd style) in
     if needs_i then (
-      pr "    int i;\n";
+      pr "    size_t i;\n";
       pr "\n"
     );
 
-    pr "    printf (\"%s\");\n" shortname;
+    pr "    fprintf (stderr, \"%s\");\n" shortname;
     List.iter (
       function
       | String n                       (* strings *)
@@ -5969,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
@@ -6062,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
@@ -6259,6 +6474,7 @@ and generate_linker_script () =
     "guestfs_get_error_handler";
     "guestfs_get_out_of_memory_handler";
     "guestfs_last_error";
+    "guestfs_set_close_callback";
     "guestfs_set_error_handler";
     "guestfs_set_launch_done_callback";
     "guestfs_set_log_message_callback";
@@ -6270,6 +6486,8 @@ and generate_linker_script () =
      *)
     "guestfs_safe_calloc";
     "guestfs_safe_malloc";
+    "guestfs_safe_strdup";
+    "guestfs_safe_memdup";
   ] in
   let functions =
     List.map (fun (name, _, _, _, _, _, _) -> "guestfs_" ^ name)
@@ -6339,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
@@ -6396,16 +6615,19 @@ 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;
              | DeviceList n ->
                  pr_list_handling_code n;
                  pr "  /* Ensure that each is a device,\n";
-                 pr "   * and perform device name translation. */\n";
-                 pr "  { int pvi; for (pvi = 0; physvols[pvi] != NULL; ++pvi)\n";
-                 pr "    RESOLVE_DEVICE (physvols[pvi], %s, goto done);\n"
+                 pr "   * and perform device name translation.\n";
+                 pr "   */\n";
+                 pr "  {\n";
+                 pr "    size_t i;\n";
+                 pr "    for (i = 0; %s[i] != NULL; ++i)\n" n;
+                 pr "      RESOLVE_DEVICE (%s[i], %s, goto done);\n" n
                    (if is_filein then "cancel_receive ()" else "0");
                  pr "  }\n";
              | Bool n -> pr "  %s = args.%s;\n" n n
@@ -6553,7 +6775,7 @@ and generate_daemon_actions () =
         pr "static int lvm_tokenize_%s (char *str, guestfs_int_lvm_%s *r)\n" typ typ;
         pr "{\n";
         pr "  char *tok, *p, *next;\n";
-        pr "  int i, j;\n";
+        pr "  size_t i, j;\n";
         pr "\n";
         (*
           pr "  fprintf (stderr, \"%%s: <<%%s>>\\n\", __func__, str);\n";
@@ -6778,7 +7000,7 @@ static void print_error (guestfs_h *g, void *data, const char *msg)
 /* FIXME: nearly identical code appears in fish.c */
 static void print_strings (char *const *argv)
 {
-  int argc;
+  size_t argc;
 
   for (argc = 0; argv[argc] != NULL; ++argc)
     printf (\"\\t%%s\\n\", argv[argc]);
@@ -6787,13 +7009,33 @@ static void print_strings (char *const *argv)
 /*
 static void print_table (char const *const *argv)
 {
-  int i;
+  size_t i;
 
   for (i = 0; argv[i] != NULL; i += 2)
     printf (\"%%s: %%s\\n\", argv[i], argv[i+1]);
 }
 */
 
+static int
+is_available (const char *group)
+{
+  const char *groups[] = { group, NULL };
+  int r;
+
+  suppress_error = 1;
+  r = guestfs_available (g, (char **) groups);
+  suppress_error = 0;
+
+  return r == 0;
+}
+
+static void
+incr (guestfs_h *g, void *iv)
+{
+  int *i = (int *) iv;
+  (*i)++;
+}
+
 ";
 
   (* Generate a list of commands which are not tested anywhere. *)
@@ -6805,7 +7047,7 @@ static void print_table (char const *const *argv)
     fun (_, _, _, _, tests, _, _) ->
       let tests = filter_map (
         function
-        | (_, (Always|If _|Unless _), test) -> Some test
+        | (_, (Always|If _|Unless _|IfAvailable _), test) -> Some test
         | (_, Disabled, _) -> None
       ) tests in
       let seq = List.concat (List.map seq_of_test tests) in
@@ -6975,11 +7217,22 @@ int main (int argc, char *argv[])
   ) test_names;
   pr "\n";
 
-  pr "  guestfs_close (g);\n";
-  pr "  unlink (\"test1.img\");\n";
-  pr "  unlink (\"test2.img\");\n";
-  pr "  unlink (\"test3.img\");\n";
-  pr "\n";
+  pr "  /* Check close callback is called. */
+  int close_sentinel = 1;
+  guestfs_set_close_callback (g, incr, &close_sentinel);
+
+  guestfs_close (g);
+
+  if (close_sentinel != 2) {
+    fprintf (stderr, \"close callback was not called\\n\");
+    exit (EXIT_FAILURE);
+  }
+
+  unlink (\"test1.img\");
+  unlink (\"test2.img\");
+  unlink (\"test3.img\");
+
+";
 
   pr "  if (n_failed > 0) {\n";
   pr "    printf (\"***** %%lu / %%d tests FAILED *****\\n\", n_failed, nr_tests);\n";
@@ -7011,7 +7264,7 @@ static int %s_skip (void)
 " test_name name (String.uppercase test_name) (String.uppercase name);
 
   (match prereq with
-   | Disabled | Always -> ()
+   | Disabled | Always | IfAvailable _ -> ()
    | If code | Unless code ->
        pr "static int %s_prereq (void)\n" test_name;
        pr "{\n";
@@ -7036,16 +7289,9 @@ static int %s (void)
   List.iter (
     function
     | Optional group ->
-        pr "  {\n";
-        pr "    const char *groups[] = { \"%s\", NULL };\n" group;
-        pr "    int r;\n";
-        pr "    suppress_error = 1;\n";
-        pr "    r = guestfs_available (g, (char **) groups);\n";
-        pr "    suppress_error = 0;\n";
-        pr "    if (r == -1) {\n";
-        pr "      printf (\"        %%s skipped (reason: group %%s not available in daemon)\\n\", \"%s\", groups[0]);\n" test_name;
-        pr "      return 0;\n";
-        pr "    }\n";
+        pr "  if (!is_available (\"%s\")) {\n" group;
+        pr "    printf (\"        %%s skipped (reason: group %%s not available in daemon)\\n\", \"%s\", \"%s\");\n" test_name group;
+        pr "    return 0;\n";
         pr "  }\n";
     | _ -> ()
   ) flags;
@@ -7067,6 +7313,13 @@ static int %s (void)
        pr "  }\n";
        pr "\n";
        generate_one_test_body name i test_name init test;
+   | IfAvailable group ->
+       pr "  if (!is_available (\"%s\")) {\n" group;
+       pr "    printf (\"        %%s skipped (reason: %%s not available)\\n\", \"%s\", \"%s\");\n" test_name group;
+       pr "    return 0;\n";
+       pr "  }\n";
+       pr "\n";
+       generate_one_test_body name i test_name init test;
    | Always ->
        generate_one_test_body name i test_name init test
   );
@@ -7377,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);
@@ -7411,7 +7665,7 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
         | RString _ -> pr "    char *r;\n"; "NULL"
         | RStringList _ | RHashtable _ ->
             pr "    char **r;\n";
-            pr "    int i;\n";
+            pr "    size_t i;\n";
             "NULL"
         | RStruct (_, typ) ->
             pr "    struct guestfs_%s *r;\n" typ; "NULL"
@@ -7432,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
@@ -7545,7 +7800,7 @@ and generate_fish_cmds () =
   pr "\n";
 
   (* display_command function, which implements guestfish -h cmd *)
-  pr "void display_command (const char *cmd)\n";
+  pr "int display_command (const char *cmd)\n";
   pr "{\n";
   List.iter (
     fun (name, style, _, flags, _, shortdesc, longdesc) ->
@@ -7558,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
@@ -7593,15 +7856,17 @@ and generate_fish_cmds () =
         pr " || STRCASEEQ (cmd, \"%s\")" name2;
       if name <> alias then
         pr " || STRCASEEQ (cmd, \"%s\")" alias;
-      pr ")\n";
+      pr ") {\n";
       pr "    pod2text (\"%s\", _(\"%s\"), %S);\n"
         name2 shortdesc
         ("=head1 SYNOPSIS\n\n " ^ synopsis ^ "\n\n" ^
          "=head1 DESCRIPTION\n\n" ^
          longdesc ^ warnings ^ describe_alias);
+      pr "    return 0;\n";
+      pr "  }\n";
       pr "  else\n"
   ) all_functions;
-  pr "    display_builtin_command (cmd);\n";
+  pr "    return display_builtin_command (cmd);\n";
   pr "}\n";
   pr "\n";
 
@@ -7721,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
@@ -7732,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;
@@ -7740,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";
@@ -7767,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. *)
@@ -7813,11 +8088,12 @@ and generate_fish_cmds () =
 
       List.iter (
         function
-        | Device name | String name
-        | OptString name | Bool name
-        | Int name | Int64 name
-        | BufferIn name -> ()
-        | Pathname name | Dev_or_Path name | FileOut name ->
+        | Device _ | String _
+        | OptString _ | Bool _
+        | Int _ | Int64 _
+        | BufferIn _ -> ()
+        | Pathname name | Dev_or_Path name | FileOut name
+        | Key name ->
             pr "  free (%s);\n" name
         | FileIn name ->
             pr "  free_file_in (%s);\n" name
@@ -7989,7 +8265,7 @@ static const char *const commands[] = {
 static char *
 generator (const char *text, int state)
 {
-  static int index, len;
+  static size_t index, len;
   const char *name;
 
   if (!state) {
@@ -8070,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"
@@ -8078,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";
@@ -8087,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;
 
@@ -8141,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 ->
@@ -8283,7 +8566,7 @@ and generate_ocaml_c () =
 #include <caml/mlvalues.h>
 #include <caml/signals.h>
 
-#include <guestfs.h>
+#include \"guestfs.h\"
 
 #include \"guestfs_c.h\"
 
@@ -8296,7 +8579,7 @@ copy_table (char * const * argv)
 {
   CAMLparam0 ();
   CAMLlocal5 (rv, pairv, kv, vv, cons);
-  int i;
+  size_t i;
 
   rv = Val_int (0);
   for (i = 0; argv[i] != NULL; i += 2) {
@@ -8450,15 +8733,17 @@ copy_table (char * const * argv)
         | Device n | Dev_or_Path n
         | String n
         | FileIn n
-        | FileOut n ->
-            pr "  const char *%s = String_val (%sv);\n" n 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 ->
-            pr "  const char *%s =\n" n;
-            pr "    %sv != Val_int (0) ? String_val (Field (%sv, 0)) : NULL;\n"
-              n n
+            pr "  char *%s =\n" n;
+            pr "    %sv != Val_int (0) ?" n;
+            pr "      guestfs_safe_strdup (g, String_val (Field (%sv, 0))) : NULL;\n" n
         | BufferIn n ->
-            pr "  const char *%s = String_val (%sv);\n" n n;
-            pr "  size_t %s_size = caml_string_length (%sv);\n" n n
+            pr "  size_t %s_size = caml_string_length (%sv);\n" n n;
+            pr "  char *%s = guestfs_safe_memdup (g, String_val (%sv), %s_size);\n" n n n
         | StringList n | DeviceList n ->
             pr "  char **%s = ocaml_guestfs_strings_val (g, %sv);\n" n n
         | Bool n ->
@@ -8478,7 +8763,7 @@ copy_table (char * const * argv)
             pr "  const char *r;\n"; "NULL"
         | RString _ -> pr "  char *r;\n"; "NULL"
         | RStringList _ ->
-            pr "  int i;\n";
+            pr "  size_t i;\n";
             pr "  char **r;\n";
             "NULL"
         | RStruct (_, typ) ->
@@ -8486,7 +8771,7 @@ copy_table (char * const * argv)
         | RStructList (_, typ) ->
             pr "  struct guestfs_%s_list *r;\n" typ; "NULL"
         | RHashtable _ ->
-            pr "  int i;\n";
+            pr "  size_t i;\n";
             pr "  char **r;\n";
             "NULL"
         | RBufferOut _ ->
@@ -8501,13 +8786,15 @@ copy_table (char * const * argv)
       pr ";\n";
       pr "  caml_leave_blocking_section ();\n";
 
+      (* Free strings if we copied them above. *)
       List.iter (
         function
+        | Pathname n | Device n | Dev_or_Path n | String n | OptString 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;
-        | Pathname _ | Device _ | Dev_or_Path _ | String _ | OptString _
-        | Bool _ | Int _ | Int64 _
-        | FileIn _ | FileOut _ | BufferIn _ -> ()
+        | Bool _ | Int _ | Int64 _ -> ()
       ) (snd style);
 
       pr "  if (r == %s)\n" error_code;
@@ -8594,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 -> "
@@ -8709,10 +8996,30 @@ _create ()
       RETVAL
 
 void
-DESTROY (g)
+DESTROY (sv)
+      SV *sv;
+ PPCODE:
+      /* For the 'g' argument above we do the conversion explicitly and
+       * don't rely on the typemap, because if the handle has been
+       * explicitly closed we don't want the typemap conversion to
+       * display an error.
+       */
+      HV *hv = (HV *) SvRV (sv);
+      SV **svp = hv_fetch (hv, \"_g\", 2, 0);
+      if (svp != NULL) {
+        guestfs_h *g = (guestfs_h *) SvIV (*svp);
+        assert (g != NULL);
+        guestfs_close (g);
+      }
+
+void
+close (g)
       guestfs_h *g;
  PPCODE:
       guestfs_close (g);
+      /* Avoid double-free in DESTROY method. */
+      HV *hv = (HV *) SvRV (ST(0));
+      (void) hv_delete (hv, \"_g\", 2, G_DISCARD);
 
 ";
 
@@ -8743,7 +9050,7 @@ DESTROY (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;
@@ -8766,7 +9073,7 @@ DESTROY (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
@@ -8854,7 +9161,7 @@ DESTROY (g)
        | RStringList n | RHashtable n ->
            pr "PREINIT:\n";
            pr "      char **%s;\n" n;
-           pr "      int i, n;\n";
+           pr "      size_t i, n;\n";
            pr " PPCODE:\n";
            pr "      %s = guestfs_%s " n name;
            generate_c_call_args ~handle:"g" style;
@@ -8898,7 +9205,7 @@ DESTROY (g)
 and generate_perl_struct_list_code typ cols name style n do_cleanups =
   pr "PREINIT:\n";
   pr "      struct guestfs_%s_list *%s;\n" typ n;
-  pr "      int i;\n";
+  pr "      size_t i;\n";
   pr "      HV *hv;\n";
   pr " PPCODE:\n";
   pr "      %s = guestfs_%s " n name;
@@ -9067,11 +9374,28 @@ sub new {
   my $proto = shift;
   my $class = ref ($proto) || $proto;
 
-  my $self = Sys::Guestfs::_create ();
+  my $g = Sys::Guestfs::_create ();
+  my $self = { _g => $g };
   bless $self, $class;
   return $self;
 }
 
+=item $h->close ();
+
+Explicitly close the guestfs handle.
+
+B<Note:> You should not usually call this function.  The handle will
+be closed implicitly when its reference count goes to zero (eg.
+when it goes out of scope or the program ends).  This call is
+only required in some exceptional cases, such as where the program
+may contain cached references to the handle 'somewhere' and you
+really have to have the close happen right away.  After calling
+C<close> the program must not call any method (including C<close>)
+on the handle (but the implicit call to C<DESTROY> that happens
+when the final reference is cleaned up is OK).
+
+=cut
+
 " max_proc_nr;
 
   (* Actions.  We only need to print documentation for these as
@@ -9145,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
@@ -9172,32 +9496,42 @@ 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. */
 static char **
 get_string_list (PyObject *obj)
 {
-  int i, len;
+  size_t i, len;
   char **r;
 
   assert (obj);
@@ -9207,7 +9541,12 @@ get_string_list (PyObject *obj)
     return NULL;
   }
 
-  len = PyList_Size (obj);
+  Py_ssize_t slen = PyList_Size (obj);
+  if (slen == -1) {
+    PyErr_SetString (PyExc_RuntimeError, \"get_string_list: PyList_Size failure\");
+    return NULL;
+  }
+  len = (size_t) slen;
   r = malloc (sizeof (char *) * (len+1));
   if (r == NULL) {
     PyErr_SetString (PyExc_RuntimeError, \"get_string_list: out of memory\");
@@ -9279,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);
 }
 
@@ -9305,7 +9647,7 @@ py_guestfs_close (PyObject *self, PyObject *args)
     pr "put_%s_list (struct guestfs_%s_list *%ss)\n" typ typ typ;
     pr "{\n";
     pr "  PyObject *list;\n";
-    pr "  int i;\n";
+    pr "  size_t i;\n";
     pr "\n";
     pr "  list = PyList_New (%ss->len);\n" typ;
     pr "  for (i = 0; i < %ss->len; ++i)\n" typ;
@@ -9411,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
@@ -9432,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? *)
@@ -9446,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
@@ -9461,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 ->
@@ -9477,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 ->
@@ -9784,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;
@@ -9803,7 +10148,7 @@ static VALUE ruby_guestfs_close (VALUE gv)
             pr "  char **%s;\n" n;
             pr "  Check_Type (%sv, T_ARRAY);\n" n;
             pr "  {\n";
-            pr "    int i, len;\n";
+            pr "    size_t i, len;\n";
             pr "    len = RARRAY_LEN (%sv);\n" n;
             pr "    %s = guestfs_safe_malloc (g, sizeof (char *) * (len+1));\n"
               n;
@@ -9845,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 ->
@@ -9875,7 +10220,7 @@ static VALUE ruby_guestfs_close (VALUE gv)
            pr "  free (r);\n";
            pr "  return rv;\n";
        | RStringList _ ->
-           pr "  int i, len = 0;\n";
+           pr "  size_t i, len = 0;\n";
            pr "  for (i = 0; r[i] != NULL; ++i) len++;\n";
            pr "  VALUE rv = rb_ary_new2 (len);\n";
            pr "  for (i = 0; r[i] != NULL; ++i) {\n";
@@ -9892,7 +10237,7 @@ static VALUE ruby_guestfs_close (VALUE gv)
            generate_ruby_struct_list_code typ cols
        | RHashtable _ ->
            pr "  VALUE rv = rb_hash_new ();\n";
-           pr "  int i;\n";
+           pr "  size_t i;\n";
            pr "  for (i = 0; r[i] != NULL; i+=2) {\n";
            pr "    rb_hash_aset (rv, rb_str_new2 (r[i]), rb_str_new2 (r[i+1]));\n";
            pr "    free (r[i]);\n";
@@ -9961,7 +10306,7 @@ and generate_ruby_struct_code typ cols =
 (* Ruby code to return a struct list. *)
 and generate_ruby_struct_list_code typ cols =
   pr "  VALUE rv = rb_ary_new2 (r->len);\n";
-  pr "  int i;\n";
+  pr "  size_t i;\n";
   pr "  for (i = 0; i < r->len; ++i) {\n";
   pr "    VALUE hv = rb_hash_new ();\n";
   List.iter (
@@ -10162,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
@@ -10285,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
@@ -10342,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;
@@ -10368,7 +10716,7 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
                        | DeviceList _ -> true
                        | _ -> false) (snd style) in
       if needs_i then
-        pr "  int i;\n";
+        pr "  size_t i;\n";
 
       pr "\n";
 
@@ -10379,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
@@ -10416,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;
@@ -10697,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
@@ -10713,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
@@ -10764,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"
@@ -10958,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 ->
@@ -10982,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 ->
@@ -11020,7 +11376,7 @@ namespace Guestfs
            pr "      return r != 0 ? true : false;\n"
        | RHashtable _ ->
            pr "      Hashtable rr = new Hashtable ();\n";
-           pr "      for (int i = 0; i < r.Length; i += 2)\n";
+           pr "      for (size_t i = 0; i < r.Length; i += 2)\n";
            pr "        rr.Add (r[i], r[i+1]);\n";
            pr "      return rr;\n"
        | RInt _ | RInt64 _ | RConstString _ | RConstOptString _
@@ -11057,7 +11413,7 @@ and generate_bindtests () =
 static void
 print_strings (char *const *argv)
 {
-  int argc;
+  size_t argc;
 
   printf (\"[\");
   for (argc = 0; argv[argc] != NULL; ++argc) {
@@ -11086,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";
@@ -12019,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;