Send trace output to stderr.
[libguestfs.git] / src / generator.ml
index 2b833c1..e065cc8 100755 (executable)
@@ -164,9 +164,8 @@ and argt =
      *)
   | FileIn of string
   | FileOut of string
      *)
   | FileIn of string
   | FileOut of string
-(* Not implemented:
     (* Opaque buffer which can contain arbitrary 8 bit data.
     (* Opaque buffer which can contain arbitrary 8 bit data.
-     * In the C API, this is expressed as <char *, int> pair.
+     * In the C API, this is expressed as <const char *, size_t> pair.
      * Most other languages have a string type which can contain
      * ASCII NUL.  We use whatever type is appropriate for each
      * language.
      * Most other languages have a string type which can contain
      * ASCII NUL.  We use whatever type is appropriate for each
      * language.
@@ -175,18 +174,29 @@ and argt =
      * To return an arbitrary buffer, use RBufferOut.
      *)
   | BufferIn of string
      * 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 *)
   | DangerWillRobinson   (* flags particularly dangerous commands *)
   | FishAlias of string          (* provide an alias for this cmd in guestfish *)
 
 type flags =
   | ProtocolLimitWarning  (* display warning about protocol size limits *)
   | DangerWillRobinson   (* flags particularly dangerous commands *)
   | FishAlias of string          (* provide an alias for this cmd in guestfish *)
-  | FishAction of string  (* call this function in guestfish *)
+  | FishOutput of fish_output_t (* how to display output in guestfish *)
   | NotInFish            (* do not export via guestfish *)
   | NotInDocs            (* do not add this function to documentation *)
   | DeprecatedBy of string (* function is deprecated, use .. instead *)
   | Optional of string   (* function is part of an optional group *)
 
   | NotInFish            (* do not export via guestfish *)
   | NotInDocs            (* do not add this function to documentation *)
   | DeprecatedBy of string (* function is deprecated, use .. instead *)
   | Optional of string   (* function is part of an optional group *)
 
+and fish_output_t =
+  | FishOutputOctal       (* for int return, print in octal *)
+  | FishOutputHexadecimal (* for int return, print in hex *)
+
 (* You can supply zero or as many tests as you want per API call.
  *
  * Note that the test environment has 3 block devices, of size 500MB,
 (* You can supply zero or as many tests as you want per API call.
  *
  * Note that the test environment has 3 block devices, of size 500MB,
@@ -307,6 +317,9 @@ and test_prereq =
     (* As for 'If' but the test runs _unless_ the code returns true. *)
   | Unless of string
 
     (* 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
 (* Some initial scenarios for testing. *)
 and test_init =
     (* Do nothing, block devices could contain random stuff including
@@ -380,6 +393,7 @@ let test_all_args = [
   Int64 "integer64";
   FileIn "filein";
   FileOut "fileout";
   Int64 "integer64";
   FileIn "filein";
   FileOut "fileout";
+  BufferIn "bufferin";
 ]
 
 let test_all_rets = [
 ]
 
 let test_all_rets = [
@@ -442,7 +456,7 @@ You probably don't want to call this function.")]
  *)
 
 let non_daemon_functions = test_functions @ [
  *)
 
 let non_daemon_functions = test_functions @ [
-  ("launch", (RErr, []), -1, [FishAlias "run"; FishAction "launch"],
+  ("launch", (RErr, []), -1, [FishAlias "run"],
    [],
    "launch the qemu subprocess",
    "\
    [],
    "launch the qemu subprocess",
    "\
@@ -545,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
 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<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
 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
@@ -572,7 +584,7 @@ The first character of C<param> string must be a C<-> (dash).
 
 C<value> can be NULL.");
 
 
 C<value> can be NULL.");
 
-  ("set_qemu", (RErr, [String "qemu"]), -1, [FishAlias "qemu"],
+  ("set_qemu", (RErr, [OptString "qemu"]), -1, [FishAlias "qemu"],
    [],
    "set the qemu binary",
    "\
    [],
    "set the qemu binary",
    "\
@@ -604,7 +616,7 @@ Return the current qemu binary.
 This is always non-NULL.  If it wasn't set already, then this will
 return the default qemu binary name.");
 
 This is always non-NULL.  If it wasn't set already, then this will
 return the default qemu binary name.");
 
-  ("set_path", (RErr, [String "searchpath"]), -1, [FishAlias "path"],
+  ("set_path", (RErr, [OptString "searchpath"]), -1, [FishAlias "path"],
    [],
    "set the search path",
    "\
    [],
    "set the search path",
    "\
@@ -791,8 +803,9 @@ against a completely different C<libguestfs.so> library.
 
 This call was added in version C<1.0.58>.  In previous
 versions of libguestfs there was no way to get the version
 
 This call was added in version C<1.0.58>.  In previous
 versions of libguestfs there was no way to get the version
-number.  From C code you can use ELF weak linking tricks to find out if
-this symbol exists (if it doesn't, then it's an earlier version).
+number.  From C code you can use dynamic linker functions
+to find out if this symbol exists (if it doesn't, then
+it's an earlier version).
 
 The call returns a structure with four elements.  The first
 three (C<major>, C<minor> and C<release>) are numbers and
 
 The call returns a structure with four elements.  The first
 three (C<major>, C<minor> and C<release>) are numbers and
@@ -803,9 +816,13 @@ used for distro-specific information.
 To construct the original version string:
 C<$major.$minor.$release$extra>
 
 To construct the original version string:
 C<$major.$minor.$release$extra>
 
+See also: L<guestfs(3)/LIBGUESTFS VERSION NUMBERS>.
+
 I<Note:> Don't use this call to test for availability
 I<Note:> Don't use this call to test for availability
-of features.  Distro backports makes this unreliable.  Use
-C<guestfs_available> instead.");
+of features.  In enterprise distributions we backport
+features from later versions into earlier versions,
+making this an unreliable way to test for features.
+Use C<guestfs_available> instead.");
 
   ("set_selinux", (RErr, [Bool "selinux"]), -1, [FishAlias "selinux"],
    [InitNone, Always, TestOutputTrue (
 
   ("set_selinux", (RErr, [Bool "selinux"]), -1, [FishAlias "selinux"],
    [InitNone, Always, TestOutputTrue (
@@ -839,7 +856,7 @@ see L<guestfs(3)>.");
    "enable or disable command traces",
    "\
 If the command trace flag is set to 1, then commands are
    "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
 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
@@ -935,7 +952,7 @@ let daemon_functions = [
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext2"; "/dev/sda1"];
        ["mount"; "/dev/sda1"; "/"];
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext2"; "/dev/sda1"];
        ["mount"; "/dev/sda1"; "/"];
-       ["write_file"; "/new"; "new file contents"; "0"];
+       ["write"; "/new"; "new file contents"];
        ["cat"; "/new"]], "new file contents")],
    "mount a guest disk at a position in the filesystem",
    "\
        ["cat"; "/new"]], "new file contents")],
    "mount a guest disk at a position in the filesystem",
    "\
@@ -953,8 +970,15 @@ exist.
 The mounted filesystem is writable, if we have sufficient permissions
 on the underlying device.
 
 The mounted filesystem is writable, if we have sufficient permissions
 on the underlying device.
 
-The filesystem options C<sync> and C<noatime> are set with this
-call, in order to improve reliability.");
+B<Important note:>
+When you use this call, the filesystem options C<sync> and C<noatime>
+are set implicitly.  This was originally done because we thought it
+would improve reliability, but it turns out that I<-o sync> has a
+very large negative performance impact and negligible effect on
+reliability.  Therefore we recommend that you avoid using
+C<guestfs_mount> in any code that needs performance, and instead
+use C<guestfs_mount_options> (use an empty string for the first
+parameter if you don't want any options).");
 
   ("sync", (RErr, []), 2, [],
    [ InitEmpty, Always, TestRun [["sync"]]],
 
   ("sync", (RErr, []), 2, [],
    [ InitEmpty, Always, TestRun [["sync"]]],
@@ -974,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,
    "\
 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 (
 
   ("cat", (RString "content", [Pathname "path"]), 4, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutput (
@@ -1238,7 +1265,12 @@ matches exactly one node, the C<value> is returned.");
    [], (* XXX Augeas code needs tests. *)
    "set Augeas path to value",
    "\
    [], (* XXX Augeas code needs tests. *)
    "set Augeas path to value",
    "\
-Set the value associated with C<path> to C<value>.");
+Set the value associated with C<path> to C<val>.
+
+In the Augeas API, it is possible to clear a node by setting
+the value to NULL.  Due to an oversight in the libguestfs API
+you cannot do that with this call.  Instead you must use the
+C<guestfs_aug_clear> call.");
 
   ("aug_insert", (RErr, [String "augpath"; String "label"; Bool "before"]), 21, [Optional "augeas"],
    [], (* XXX Augeas code needs tests. *)
 
   ("aug_insert", (RErr, [String "augpath"; String "label"; Bool "before"]), 21, [Optional "augeas"],
    [], (* XXX Augeas code needs tests. *)
@@ -1380,7 +1412,9 @@ numeric modes are supported.
 
 I<Note>: When using this command from guestfish, C<mode>
 by default would be decimal, unless you prefix it with
 
 I<Note>: When using this command from guestfish, C<mode>
 by default would be decimal, unless you prefix it with
-C<0> to get octal, ie. use C<0700> not C<700>.");
+C<0> to get octal, ie. use C<0700> not C<700>.
+
+The mode actually set is affected by the umask.");
 
   ("chown", (RErr, [Int "owner"; Int "group"; Pathname "path"]), 35, [],
    [], (* XXX Need stat command to test *)
 
   ("chown", (RErr, [Int "owner"; Int "group"; Pathname "path"]), 35, [],
    [], (* XXX Need stat command to test *)
@@ -1473,9 +1507,9 @@ from the non-empty list of physical volumes C<physvols>.");
        ["lvs"]],
       ["/dev/VG1/LV1"; "/dev/VG1/LV2";
        "/dev/VG2/LV3"; "/dev/VG2/LV4"; "/dev/VG2/LV5"])],
        ["lvs"]],
       ["/dev/VG1/LV1"; "/dev/VG1/LV2";
        "/dev/VG2/LV3"; "/dev/VG2/LV4"; "/dev/VG2/LV5"])],
-   "create an LVM volume group",
+   "create an LVM logical volume",
    "\
    "\
-This creates an LVM volume group called C<logvol>
+This creates an LVM logical volume called C<logvol>
 on the volume group C<volgroup>, with C<size> megabytes.");
 
   ("mkfs", (RErr, [String "fstype"; Device "device"]), 42, [],
 on the volume group C<volgroup>, with C<size> megabytes.");
 
   ("mkfs", (RErr, [String "fstype"; Device "device"]), 42, [],
@@ -1483,7 +1517,7 @@ on the volume group C<volgroup>, with C<size> megabytes.");
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext2"; "/dev/sda1"];
        ["mount_options"; ""; "/dev/sda1"; "/"];
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext2"; "/dev/sda1"];
        ["mount_options"; ""; "/dev/sda1"; "/"];
-       ["write_file"; "/new"; "new file contents"; "0"];
+       ["write"; "/new"; "new file contents"];
        ["cat"; "/new"]], "new file contents")],
    "make a filesystem",
    "\
        ["cat"; "/new"]], "new file contents")],
    "make a filesystem",
    "\
@@ -1520,25 +1554,10 @@ the string C<,> (comma).
 See also: C<guestfs_sfdisk_l>, C<guestfs_sfdisk_N>,
 C<guestfs_part_init>");
 
 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],
-   [InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "new file contents"; "0"];
-       ["cat"; "/new"]], "new file contents");
-    InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "\nnew file contents\n"; "0"];
-       ["cat"; "/new"]], "\nnew file contents\n");
-    InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "\n\n"; "0"];
-       ["cat"; "/new"]], "\n\n");
-    InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; ""; "0"];
-       ["cat"; "/new"]], "");
-    InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "\n\n\n"; "0"];
-       ["cat"; "/new"]], "\n\n\n");
-    InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "\n"; "0"];
-       ["cat"; "/new"]], "\n")],
+  ("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
    "create a file",
    "\
 This call creates a file called C<path>.  The contents of the
@@ -1550,9 +1569,7 @@ then the length is calculated using C<strlen> (so in this case
 the content cannot contain embedded ASCII NULs).
 
 I<NB.> Owing to a bug, writing content containing ASCII NUL
 the content cannot contain embedded ASCII NULs).
 
 I<NB.> Owing to a bug, writing content containing ASCII NUL
-characters does I<not> work, even if the length is specified.
-We hope to resolve this bug in a future version.  In the meantime
-use C<guestfs_upload>.");
+characters does I<not> work, even if the length is specified.");
 
   ("umount", (RErr, [String "pathordevice"]), 45, [FishAlias "unmount"],
    [InitEmpty, Always, TestOutputListOfDevices (
 
   ("umount", (RErr, [String "pathordevice"]), 45, [FishAlias "unmount"],
    [InitEmpty, Always, TestOutputListOfDevices (
@@ -1621,19 +1638,32 @@ and physical volumes.");
     InitISOFS, Always, TestOutput (
       [["file"; "/known-1"]], "ASCII text");
     InitISOFS, Always, TestLastFail (
     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
    "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.
 
 
 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
 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 (
 
   ("command", (RString "output", [StringList "arguments"]), 50, [ProtocolLimitWarning],
    [InitBasicFS, Always, TestOutput (
@@ -1972,7 +2002,10 @@ See also C<guestfs_upload>, C<guestfs_cat>.");
     InitISOFS, Always, TestOutput (
       [["checksum"; "sha384"; "/known-3"]], "5fa7883430f357b5d7b7271d3a1d2872b51d73cba72731de6863d3dea55f30646af2799bef44d5ea776a5ec7941ac640");
     InitISOFS, Always, TestOutput (
     InitISOFS, Always, TestOutput (
       [["checksum"; "sha384"; "/known-3"]], "5fa7883430f357b5d7b7271d3a1d2872b51d73cba72731de6863d3dea55f30646af2799bef44d5ea776a5ec7941ac640");
     InitISOFS, Always, TestOutput (
-      [["checksum"; "sha512"; "/known-3"]], "2794062c328c6b216dca90443b7f7134c5f40e56bd0ed7853123275a09982a6f992e6ca682f9d2fba34a4c5e870d8fe077694ff831e3032a004ee077e00603f6")],
+      [["checksum"; "sha512"; "/known-3"]], "2794062c328c6b216dca90443b7f7134c5f40e56bd0ed7853123275a09982a6f992e6ca682f9d2fba34a4c5e870d8fe077694ff831e3032a004ee077e00603f6");
+    (* Test for RHBZ#579608, absolute symbolic links. *)
+    InitISOFS, Always, TestOutput (
+      [["checksum"; "sha512"; "/abssymlink"]], "5f57d0639bc95081c53afc63a449403883818edc64da48930ad6b1a4fb49be90404686877743fbcd7c99811f3def7df7bc22635c885c6a8cf79c806b43451c1a")],
    "compute MD5, SHAx or CRC checksum of file",
    "\
 This call computes the MD5, SHAx or CRC checksum of the
    "compute MD5, SHAx or CRC checksum of file",
    "\
 This call computes the MD5, SHAx or CRC checksum of the
@@ -2014,9 +2047,13 @@ Compute the SHA512 hash (using the C<sha512sum> program).
 
 =back
 
 
 =back
 
-The checksum is returned as a printable string.");
+The checksum is returned as a printable string.
 
 
-  ("tar_in", (RErr, [FileIn "tarfile"; String "directory"]), 69, [],
+To get the checksum for a device, use C<guestfs_checksum_device>.
+
+To get the checksums for many files, use C<guestfs_checksums_out>.");
+
+  ("tar_in", (RErr, [FileIn "tarfile"; Pathname "directory"]), 69, [],
    [InitBasicFS, Always, TestOutput (
       [["tar_in"; "../images/helloworld.tar"; "/"];
        ["cat"; "/hello"]], "hello\n")],
    [InitBasicFS, Always, TestOutput (
       [["tar_in"; "../images/helloworld.tar"; "/"];
        ["cat"; "/hello"]], "hello\n")],
@@ -2038,7 +2075,7 @@ it to local file C<tarfile>.
 To download a compressed tarball, use C<guestfs_tgz_out>
 or C<guestfs_txz_out>.");
 
 To download a compressed tarball, use C<guestfs_tgz_out>
 or C<guestfs_txz_out>.");
 
-  ("tgz_in", (RErr, [FileIn "tarball"; String "directory"]), 71, [],
+  ("tgz_in", (RErr, [FileIn "tarball"; Pathname "directory"]), 71, [],
    [InitBasicFS, Always, TestOutput (
       [["tgz_in"; "../images/helloworld.tar.gz"; "/"];
        ["cat"; "/hello"]], "hello\n")],
    [InitBasicFS, Always, TestOutput (
       [["tgz_in"; "../images/helloworld.tar.gz"; "/"];
        ["cat"; "/hello"]], "hello\n")],
@@ -2064,7 +2101,7 @@ To download an uncompressed tarball, use C<guestfs_tar_out>.");
        ["mount_ro"; "/dev/sda1"; "/"];
        ["touch"; "/new"]]);
     InitBasicFS, Always, TestOutput (
        ["mount_ro"; "/dev/sda1"; "/"];
        ["touch"; "/new"]]);
     InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "data"; "0"];
+      [["write"; "/new"; "data"];
        ["umount"; "/"];
        ["mount_ro"; "/dev/sda1"; "/"];
        ["cat"; "/new"]], "data")],
        ["umount"; "/"];
        ["mount_ro"; "/dev/sda1"; "/"];
        ["cat"; "/new"]], "data")],
@@ -2079,7 +2116,11 @@ mounts the filesystem with the read-only (I<-o ro>) flag.");
    "\
 This is the same as the C<guestfs_mount> command, but it
 allows you to set the mount options as for the
    "\
 This is the same as the C<guestfs_mount> command, but it
 allows you to set the mount options as for the
-L<mount(8)> I<-o> flag.");
+L<mount(8)> I<-o> flag.
+
+If the C<options> parameter is an empty string, then
+no options are passed (all options default to whatever
+the filesystem uses).");
 
   ("mount_vfs", (RErr, [String "options"; String "vfstype"; Device "device"; String "mountpoint"]), 75, [],
    [],
 
   ("mount_vfs", (RErr, [String "options"; String "vfstype"; Device "device"; String "mountpoint"]), 75, [],
    [],
@@ -2208,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.");
 
 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",
    "\
    [],
    "get the ext2/3/4 filesystem label",
    "\
@@ -2238,14 +2279,19 @@ 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.");
 
 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
 C<device>.");
 
    "get the ext2/3/4 filesystem UUID",
    "\
 This returns the ext2/3/4 filesystem UUID of the filesystem on
 C<device>.");
 
-  ("fsck", (RInt "status", [String "fstype"; Device "device"]), 84, [],
+  ("fsck", (RInt "status", [String "fstype"; Device "device"]), 84, [FishOutput FishOutputHexadecimal],
    [InitBasicFS, Always, TestOutputInt (
       [["umount"; "/dev/sda1"];
        ["fsck"; "ext2"; "/dev/sda1"]], 0);
    [InitBasicFS, Always, TestOutputInt (
       [["umount"; "/dev/sda1"];
        ["fsck"; "ext2"; "/dev/sda1"]], 0);
@@ -2299,28 +2345,42 @@ 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, [],
 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
        ["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 (
 
   ("cp", (RErr, [Pathname "src"; Pathname "dest"]), 87, [],
    [InitBasicFS, Always, TestOutput (
-      [["write_file"; "/old"; "file content"; "0"];
+      [["write"; "/old"; "file content"];
        ["cp"; "/old"; "/new"];
        ["cat"; "/new"]], "file content");
     InitBasicFS, Always, TestOutputTrue (
        ["cp"; "/old"; "/new"];
        ["cat"; "/new"]], "file content");
     InitBasicFS, Always, TestOutputTrue (
-      [["write_file"; "/old"; "file content"; "0"];
+      [["write"; "/old"; "file content"];
        ["cp"; "/old"; "/new"];
        ["is_file"; "/old"]]);
     InitBasicFS, Always, TestOutput (
        ["cp"; "/old"; "/new"];
        ["is_file"; "/old"]]);
     InitBasicFS, Always, TestOutput (
-      [["write_file"; "/old"; "file content"; "0"];
+      [["write"; "/old"; "file content"];
        ["mkdir"; "/dir"];
        ["cp"; "/old"; "/dir/new"];
        ["cat"; "/dir/new"]], "file content")],
        ["mkdir"; "/dir"];
        ["cp"; "/old"; "/dir/new"];
        ["cat"; "/dir/new"]], "file content")],
@@ -2333,7 +2393,7 @@ either a destination filename or destination directory.");
    [InitBasicFS, Always, TestOutput (
       [["mkdir"; "/olddir"];
        ["mkdir"; "/newdir"];
    [InitBasicFS, Always, TestOutput (
       [["mkdir"; "/olddir"];
        ["mkdir"; "/newdir"];
-       ["write_file"; "/olddir/file"; "file content"; "0"];
+       ["write"; "/olddir/file"; "file content"];
        ["cp_a"; "/olddir"; "/newdir"];
        ["cat"; "/newdir/olddir/file"]], "file content")],
    "copy a file or directory recursively",
        ["cp_a"; "/olddir"; "/newdir"];
        ["cat"; "/newdir/olddir/file"]], "file content")],
    "copy a file or directory recursively",
@@ -2343,11 +2403,11 @@ recursively using the C<cp -a> command.");
 
   ("mv", (RErr, [Pathname "src"; Pathname "dest"]), 89, [],
    [InitBasicFS, Always, TestOutput (
 
   ("mv", (RErr, [Pathname "src"; Pathname "dest"]), 89, [],
    [InitBasicFS, Always, TestOutput (
-      [["write_file"; "/old"; "file content"; "0"];
+      [["write"; "/old"; "file content"];
        ["mv"; "/old"; "/new"];
        ["cat"; "/new"]], "file content");
     InitBasicFS, Always, TestOutputFalse (
        ["mv"; "/old"; "/new"];
        ["cat"; "/new"]], "file content");
     InitBasicFS, Always, TestOutputFalse (
-      [["write_file"; "/old"; "file content"; "0"];
+      [["write"; "/old"; "file content"];
        ["mv"; "/old"; "/new"];
        ["is_file"; "/old"]])],
    "move a file",
        ["mv"; "/old"; "/new"];
        ["is_file"; "/old"]])],
    "move a file",
@@ -2396,12 +2456,12 @@ or attached block device(s) in any other way.");
 
   ("equal", (RBool "equality", [Pathname "file1"; Pathname "file2"]), 93, [],
    [InitBasicFS, Always, TestOutputTrue (
 
   ("equal", (RBool "equality", [Pathname "file1"; Pathname "file2"]), 93, [],
    [InitBasicFS, Always, TestOutputTrue (
-      [["write_file"; "/file1"; "contents of a file"; "0"];
+      [["write"; "/file1"; "contents of a file"];
        ["cp"; "/file1"; "/file2"];
        ["equal"; "/file1"; "/file2"]]);
     InitBasicFS, Always, TestOutputFalse (
        ["cp"; "/file1"; "/file2"];
        ["equal"; "/file1"; "/file2"]]);
     InitBasicFS, Always, TestOutputFalse (
-      [["write_file"; "/file1"; "contents of a file"; "0"];
-       ["write_file"; "/file2"; "contents of another file"; "0"];
+      [["write"; "/file1"; "contents of a file"];
+       ["write"; "/file2"; "contents of another file"];
        ["equal"; "/file1"; "/file2"]]);
     InitBasicFS, Always, TestLastFail (
       [["equal"; "/file1"; "/file2"]])],
        ["equal"; "/file1"; "/file2"]]);
     InitBasicFS, Always, TestLastFail (
       [["equal"; "/file1"; "/file2"]])],
@@ -2416,7 +2476,10 @@ The external L<cmp(1)> program is used for the comparison.");
    [InitISOFS, Always, TestOutputList (
       [["strings"; "/known-5"]], ["abcdefghi"; "jklmnopqr"]);
     InitISOFS, Always, TestOutputList (
    [InitISOFS, Always, TestOutputList (
       [["strings"; "/known-5"]], ["abcdefghi"; "jklmnopqr"]);
     InitISOFS, Always, TestOutputList (
-      [["strings"; "/empty"]], [])],
+      [["strings"; "/empty"]], []);
+    (* Test for RHBZ#579608, absolute symbolic links. *)
+    InitISOFS, Always, TestRun (
+      [["strings"; "/abssymlink"]])],
    "print the printable strings in a file",
    "\
 This runs the L<strings(1)> command on a file and returns
    "print the printable strings in a file",
    "\
 This runs the L<strings(1)> command on a file and returns
@@ -2425,18 +2488,47 @@ the list of printable strings found.");
   ("strings_e", (RStringList "stringsout", [String "encoding"; Pathname "path"]), 95, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["strings_e"; "b"; "/known-5"]], []);
   ("strings_e", (RStringList "stringsout", [String "encoding"; Pathname "path"]), 95, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["strings_e"; "b"; "/known-5"]], []);
-    InitBasicFS, Disabled, TestOutputList (
-      [["write_file"; "/new"; "\000h\000e\000l\000l\000o\000\n\000w\000o\000r\000l\000d\000\n"; "24"];
+    InitBasicFS, Always, TestOutputList (
+      [["write"; "/new"; "\000h\000e\000l\000l\000o\000\n\000w\000o\000r\000l\000d\000\n"];
        ["strings_e"; "b"; "/new"]], ["hello"; "world"])],
    "print the printable strings in a file",
    "\
 This is like the C<guestfs_strings> command, but allows you to
        ["strings_e"; "b"; "/new"]], ["hello"; "world"])],
    "print the printable strings in a file",
    "\
 This is like the C<guestfs_strings> command, but allows you to
-specify the encoding.
+specify the encoding of strings that are looked for in
+the source file C<path>.
+
+Allowed encodings are:
+
+=over 4
+
+=item s
+
+Single 7-bit-byte characters like ASCII and the ASCII-compatible
+parts of ISO-8859-X (this is what C<guestfs_strings> uses).
+
+=item S
+
+Single 8-bit-byte characters.
+
+=item b
+
+16-bit big endian strings such as those encoded in
+UTF-16BE or UCS-2BE.
 
 
-See the L<strings(1)> manpage for the full list of encodings.
+=item l (lower case letter L)
 
 
-Commonly useful encodings are C<l> (lower case L) which will
-show strings inside Windows/x86 files.
+16-bit little endian such as UTF-16LE and UCS-2LE.
+This is useful for examining binaries in Windows guests.
+
+=item B
+
+32-bit big endian such as UCS-4BE.
+
+=item L
+
+32-bit little endian such as UCS-4LE.
+
+=back
 
 The returned strings are transcoded to UTF-8.");
 
 
 The returned strings are transcoded to UTF-8.");
 
@@ -2447,7 +2539,10 @@ The returned strings are transcoded to UTF-8.");
      * commands to segfault.
      *)
     InitISOFS, Always, TestRun (
      * commands to segfault.
      *)
     InitISOFS, Always, TestRun (
-      [["hexdump"; "/100krandom"]])],
+      [["hexdump"; "/100krandom"]]);
+    (* Test for RHBZ#579608, absolute symbolic links. *)
+    InitISOFS, Always, TestRun (
+      [["hexdump"; "/abssymlink"]])],
    "dump a file in hexadecimal",
    "\
 This runs C<hexdump -C> on the given C<path>.  The result is
    "dump a file in hexadecimal",
    "\
 This runs C<hexdump -C> on the given C<path>.  The result is
@@ -2458,7 +2553,7 @@ the human-readable, canonical hex dump of the file.");
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext3"; "/dev/sda1"];
        ["mount_options"; ""; "/dev/sda1"; "/"];
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext3"; "/dev/sda1"];
        ["mount_options"; ""; "/dev/sda1"; "/"];
-       ["write_file"; "/new"; "test file"; "0"];
+       ["write"; "/new"; "test file"];
        ["umount"; "/dev/sda1"];
        ["zerofree"; "/dev/sda1"];
        ["mount_options"; ""; "/dev/sda1"; "/"];
        ["umount"; "/dev/sda1"];
        ["zerofree"; "/dev/sda1"];
        ["mount_options"; ""; "/dev/sda1"; "/"];
@@ -2563,13 +2658,20 @@ are activated or deactivated.");
        ["lvcreate"; "LV"; "VG"; "10"];
        ["mkfs"; "ext2"; "/dev/VG/LV"];
        ["mount_options"; ""; "/dev/VG/LV"; "/"];
        ["lvcreate"; "LV"; "VG"; "10"];
        ["mkfs"; "ext2"; "/dev/VG/LV"];
        ["mount_options"; ""; "/dev/VG/LV"; "/"];
-       ["write_file"; "/new"; "test content"; "0"];
+       ["write"; "/new"; "test content"];
        ["umount"; "/"];
        ["lvresize"; "/dev/VG/LV"; "20"];
        ["e2fsck_f"; "/dev/VG/LV"];
        ["resize2fs"; "/dev/VG/LV"];
        ["mount_options"; ""; "/dev/VG/LV"; "/"];
        ["umount"; "/"];
        ["lvresize"; "/dev/VG/LV"; "20"];
        ["e2fsck_f"; "/dev/VG/LV"];
        ["resize2fs"; "/dev/VG/LV"];
        ["mount_options"; ""; "/dev/VG/LV"; "/"];
-       ["cat"; "/new"]], "test content")],
+       ["cat"; "/new"]], "test content");
+    InitNone, Always, TestRun (
+      (* Make an LV smaller to test RHBZ#587484. *)
+      [["part_disk"; "/dev/sda"; "mbr"];
+       ["pvcreate"; "/dev/sda1"];
+       ["vgcreate"; "VG"; "/dev/sda1"];
+       ["lvcreate"; "LV"; "VG"; "20"];
+       ["lvresize"; "/dev/VG/LV"; "10"]])],
    "resize an LVM logical volume",
    "\
 This resizes (expands or shrinks) an existing LVM logical
    "resize an LVM logical volume",
    "\
 This resizes (expands or shrinks) an existing LVM logical
@@ -2578,9 +2680,9 @@ is lost.");
 
   ("resize2fs", (RErr, [Device "device"]), 106, [],
    [], (* lvresize tests this *)
 
   ("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>
 the underlying device.
 
 I<Note:> It is sometimes required that you run C<guestfs_e2fsck_f>
@@ -2743,7 +2845,7 @@ manual page for more details.");
 
   ("scrub_file", (RErr, [Pathname "file"]), 115, [Optional "scrub"],
    [InitBasicFS, Always, TestRun (
 
   ("scrub_file", (RErr, [Pathname "file"]), 115, [Optional "scrub"],
    [InitBasicFS, Always, TestRun (
-      [["write_file"; "/file"; "content"; "0"];
+      [["write"; "/file"; "content"];
        ["scrub_file"; "/file"]])],
    "scrub (securely wipe) a file",
    "\
        ["scrub_file"; "/file"]])],
    "scrub (securely wipe) a file",
    "\
@@ -2795,7 +2897,10 @@ See also: L<mkdtemp(3)>");
 
   ("wc_l", (RInt "lines", [Pathname "path"]), 118, [],
    [InitISOFS, Always, TestOutputInt (
 
   ("wc_l", (RInt "lines", [Pathname "path"]), 118, [],
    [InitISOFS, Always, TestOutputInt (
-      [["wc_l"; "/10klines"]], 10000)],
+      [["wc_l"; "/10klines"]], 10000);
+    (* Test for RHBZ#579608, absolute symbolic links. *)
+    InitISOFS, Always, TestOutputInt (
+      [["wc_l"; "/abssymlink"]], 10000)],
    "count lines in a file",
    "\
 This command counts the lines in a file, using the
    "count lines in a file",
    "\
 This command counts the lines in a file, using the
@@ -2819,7 +2924,10 @@ C<wc -c> external command.");
 
   ("head", (RStringList "lines", [Pathname "path"]), 121, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
 
   ("head", (RStringList "lines", [Pathname "path"]), 121, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
-      [["head"; "/10klines"]], ["0abcdefghijklmnopqrstuvwxyz";"1abcdefghijklmnopqrstuvwxyz";"2abcdefghijklmnopqrstuvwxyz";"3abcdefghijklmnopqrstuvwxyz";"4abcdefghijklmnopqrstuvwxyz";"5abcdefghijklmnopqrstuvwxyz";"6abcdefghijklmnopqrstuvwxyz";"7abcdefghijklmnopqrstuvwxyz";"8abcdefghijklmnopqrstuvwxyz";"9abcdefghijklmnopqrstuvwxyz"])],
+      [["head"; "/10klines"]], ["0abcdefghijklmnopqrstuvwxyz";"1abcdefghijklmnopqrstuvwxyz";"2abcdefghijklmnopqrstuvwxyz";"3abcdefghijklmnopqrstuvwxyz";"4abcdefghijklmnopqrstuvwxyz";"5abcdefghijklmnopqrstuvwxyz";"6abcdefghijklmnopqrstuvwxyz";"7abcdefghijklmnopqrstuvwxyz";"8abcdefghijklmnopqrstuvwxyz";"9abcdefghijklmnopqrstuvwxyz"]);
+    (* Test for RHBZ#579608, absolute symbolic links. *)
+    InitISOFS, Always, TestOutputList (
+      [["head"; "/abssymlink"]], ["0abcdefghijklmnopqrstuvwxyz";"1abcdefghijklmnopqrstuvwxyz";"2abcdefghijklmnopqrstuvwxyz";"3abcdefghijklmnopqrstuvwxyz";"4abcdefghijklmnopqrstuvwxyz";"5abcdefghijklmnopqrstuvwxyz";"6abcdefghijklmnopqrstuvwxyz";"7abcdefghijklmnopqrstuvwxyz";"8abcdefghijklmnopqrstuvwxyz";"9abcdefghijklmnopqrstuvwxyz"])],
    "return first 10 lines of a file",
    "\
 This command returns up to the first 10 lines of a file as
    "return first 10 lines of a file",
    "\
 This command returns up to the first 10 lines of a file as
@@ -2975,7 +3083,17 @@ named pipes (FIFOs).
 The C<mode> parameter should be the mode, using the standard
 constants.  C<devmajor> and C<devminor> are the
 device major and minor numbers, only used when creating block
 The C<mode> parameter should be the mode, using the standard
 constants.  C<devmajor> and C<devminor> are the
 device major and minor numbers, only used when creating block
-and character special devices.");
+and character special devices.
+
+Note that, just like L<mknod(2)>, the mode must be bitwise
+OR'd with S_IFBLK, S_IFCHR, S_IFIFO or S_IFSOCK (otherwise this call
+just creates a regular file).  These constants are
+available in the standard Linux header files, or you can use
+C<guestfs_mknod_b>, C<guestfs_mknod_c> or C<guestfs_mkfifo>
+which are wrappers around this command which bitwise OR
+in the appropriate constant for you.
+
+The mode actually set is affected by the umask.");
 
   ("mkfifo", (RErr, [Int "mode"; Pathname "path"]), 134, [Optional "mknod"],
    [InitBasicFS, Always, TestOutputStruct (
 
   ("mkfifo", (RErr, [Int "mode"; Pathname "path"]), 134, [Optional "mknod"],
    [InitBasicFS, Always, TestOutputStruct (
@@ -2985,7 +3103,9 @@ and character special devices.");
    "\
 This call creates a FIFO (named pipe) called C<path> with
 mode C<mode>.  It is just a convenient wrapper around
    "\
 This call creates a FIFO (named pipe) called C<path> with
 mode C<mode>.  It is just a convenient wrapper around
-C<guestfs_mknod>.");
+C<guestfs_mknod>.
+
+The mode actually set is affected by the umask.");
 
   ("mknod_b", (RErr, [Int "mode"; Int "devmajor"; Int "devminor"; Pathname "path"]), 135, [Optional "mknod"],
    [InitBasicFS, Always, TestOutputStruct (
 
   ("mknod_b", (RErr, [Int "mode"; Int "devmajor"; Int "devminor"; Pathname "path"]), 135, [Optional "mknod"],
    [InitBasicFS, Always, TestOutputStruct (
@@ -2995,7 +3115,9 @@ C<guestfs_mknod>.");
    "\
 This call creates a block device node called C<path> with
 mode C<mode> and device major/minor C<devmajor> and C<devminor>.
    "\
 This call creates a block device node called C<path> with
 mode C<mode> and device major/minor C<devmajor> and C<devminor>.
-It is just a convenient wrapper around C<guestfs_mknod>.");
+It is just a convenient wrapper around C<guestfs_mknod>.
+
+The mode actually set is affected by the umask.");
 
   ("mknod_c", (RErr, [Int "mode"; Int "devmajor"; Int "devminor"; Pathname "path"]), 136, [Optional "mknod"],
    [InitBasicFS, Always, TestOutputStruct (
 
   ("mknod_c", (RErr, [Int "mode"; Int "devmajor"; Int "devminor"; Pathname "path"]), 136, [Optional "mknod"],
    [InitBasicFS, Always, TestOutputStruct (
@@ -3005,12 +3127,13 @@ It is just a convenient wrapper around C<guestfs_mknod>.");
    "\
 This call creates a char device node called C<path> with
 mode C<mode> and device major/minor C<devmajor> and C<devminor>.
    "\
 This call creates a char device node called C<path> with
 mode C<mode> and device major/minor C<devmajor> and C<devminor>.
-It is just a convenient wrapper around C<guestfs_mknod>.");
+It is just a convenient wrapper around C<guestfs_mknod>.
 
 
-  ("umask", (RInt "oldmask", [Int "mask"]), 137, [],
-   [], (* XXX umask is one of those stateful things that we should
-        * reset between each test.
-        *)
+The mode actually set is affected by the umask.");
+
+  ("umask", (RInt "oldmask", [Int "mask"]), 137, [FishOutput FishOutputOctal],
+   [InitEmpty, Always, TestOutputInt (
+      [["umask"; "0o22"]], 0o22)],
    "set file mode creation mask (umask)",
    "\
 This function sets the mask used for creating new files and
    "set file mode creation mask (umask)",
    "\
 This function sets the mask used for creating new files and
@@ -3025,7 +3148,8 @@ The default umask is C<022>.  This is important because it
 means that directories and device nodes will be created with
 C<0644> or C<0755> mode even if you specify C<0777>.
 
 means that directories and device nodes will be created with
 C<0644> or C<0755> mode even if you specify C<0777>.
 
-See also L<umask(2)>, C<guestfs_mknod>, C<guestfs_mkdir>.
+See also C<guestfs_get_umask>,
+L<umask(2)>, C<guestfs_mknod>, C<guestfs_mkdir>.
 
 This call returns the previous umask.");
 
 
 This call returns the previous umask.");
 
@@ -3078,7 +3202,7 @@ Unknown file type
 
 =item '?'
 
 
 =item '?'
 
-The L<readdir(3)> returned a C<d_type> field with an
+The L<readdir(3)> call returned a C<d_type> field with an
 unexpected value
 
 =back
 unexpected value
 
 =back
@@ -3222,7 +3346,20 @@ for full details.");
 
   ("read_file", (RBufferOut "content", [Pathname "path"]), 150, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputBuffer (
 
   ("read_file", (RBufferOut "content", [Pathname "path"]), 150, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputBuffer (
-      [["read_file"; "/known-4"]], "abc\ndef\nghi")],
+      [["read_file"; "/known-4"]], "abc\ndef\nghi");
+    (* Test various near large, large and too large files (RHBZ#589039). *)
+    InitBasicFS, Always, TestLastFail (
+      [["touch"; "/a"];
+       ["truncate_size"; "/a"; "4194303"]; (* GUESTFS_MESSAGE_MAX - 1 *)
+       ["read_file"; "/a"]]);
+    InitBasicFS, Always, TestLastFail (
+      [["touch"; "/a"];
+       ["truncate_size"; "/a"; "4194304"]; (* GUESTFS_MESSAGE_MAX *)
+       ["read_file"; "/a"]]);
+    InitBasicFS, Always, TestLastFail (
+      [["touch"; "/a"];
+       ["truncate_size"; "/a"; "41943040"]; (* GUESTFS_MESSAGE_MAX * 10 *)
+       ["read_file"; "/a"]])],
    "read a file",
    "\
 This calls returns the contents of the file C<path> as a
    "read a file",
    "\
 This calls returns the contents of the file C<path> as a
@@ -3237,7 +3374,10 @@ in the total size of file that can be handled.");
    [InitISOFS, Always, TestOutputList (
       [["grep"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"]);
     InitISOFS, Always, TestOutputList (
    [InitISOFS, Always, TestOutputList (
       [["grep"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"]);
     InitISOFS, Always, TestOutputList (
-      [["grep"; "nomatch"; "/test-grep.txt"]], [])],
+      [["grep"; "nomatch"; "/test-grep.txt"]], []);
+    (* Test for RHBZ#579608, absolute symbolic links. *)
+    InitISOFS, Always, TestOutputList (
+      [["grep"; "nomatch"; "/abssymlink"]], [])],
    "return lines matching a pattern",
    "\
 This calls the external C<grep> program and returns the
    "return lines matching a pattern",
    "\
 This calls the external C<grep> program and returns the
@@ -3385,7 +3525,7 @@ The C<-f> option removes the link (C<linkname>) if it exists already.");
    "\
 This command reads the target of a symbolic link.");
 
    "\
 This command reads the target of a symbolic link.");
 
-  ("fallocate", (RErr, [Pathname "path"; Int "len"]), 169, [],
+  ("fallocate", (RErr, [Pathname "path"; Int "len"]), 169, [DeprecatedBy "fallocate64"],
    [InitBasicFS, Always, TestOutputStruct (
       [["fallocate"; "/a"; "1000000"];
        ["stat"; "/a"]], [CompareWithInt ("size", 1_000_000)])],
    [InitBasicFS, Always, TestOutputStruct (
       [["fallocate"; "/a"; "1000000"];
        ["stat"; "/a"]], [CompareWithInt ("size", 1_000_000)])],
@@ -3613,14 +3753,29 @@ and C<guestfs_setcon>");
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs_b"; "ext2"; "4096"; "/dev/sda1"];
        ["mount_options"; ""; "/dev/sda1"; "/"];
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs_b"; "ext2"; "4096"; "/dev/sda1"];
        ["mount_options"; ""; "/dev/sda1"; "/"];
-       ["write_file"; "/new"; "new file contents"; "0"];
-       ["cat"; "/new"]], "new file contents")],
+       ["write"; "/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
    "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 (
 
   ("mke2journal", (RErr, [Int "blocksize"; Device "device"]), 188, [],
    [InitEmpty, Always, TestOutput (
@@ -3628,7 +3783,7 @@ are C<1024>, C<2048> or C<4096> only.");
        ["mke2journal"; "4096"; "/dev/sda1"];
        ["mke2fs_J"; "ext2"; "4096"; "/dev/sda2"; "/dev/sda1"];
        ["mount_options"; ""; "/dev/sda2"; "/"];
        ["mke2journal"; "4096"; "/dev/sda1"];
        ["mke2fs_J"; "ext2"; "4096"; "/dev/sda2"; "/dev/sda1"];
        ["mount_options"; ""; "/dev/sda2"; "/"];
-       ["write_file"; "/new"; "new file contents"; "0"];
+       ["write"; "/new"; "new file contents"];
        ["cat"; "/new"]], "new file contents")],
    "make ext2/3/4 external journal",
    "\
        ["cat"; "/new"]], "new file contents")],
    "make ext2/3/4 external journal",
    "\
@@ -3643,7 +3798,7 @@ to the command:
        ["mke2journal_L"; "4096"; "JOURNAL"; "/dev/sda1"];
        ["mke2fs_JL"; "ext2"; "4096"; "/dev/sda2"; "JOURNAL"];
        ["mount_options"; ""; "/dev/sda2"; "/"];
        ["mke2journal_L"; "4096"; "JOURNAL"; "/dev/sda1"];
        ["mke2fs_JL"; "ext2"; "4096"; "/dev/sda2"; "JOURNAL"];
        ["mount_options"; ""; "/dev/sda2"; "/"];
-       ["write_file"; "/new"; "new file contents"; "0"];
+       ["write"; "/new"; "new file contents"];
        ["cat"; "/new"]], "new file contents")],
    "make ext2/3/4 external journal with label",
    "\
        ["cat"; "/new"]], "new file contents")],
    "make ext2/3/4 external journal with label",
    "\
@@ -3656,7 +3811,7 @@ This creates an ext2 external journal on C<device> with label C<label>.");
         ["mke2journal_U"; "4096"; uuid; "/dev/sda1"];
         ["mke2fs_JU"; "ext2"; "4096"; "/dev/sda2"; uuid];
         ["mount_options"; ""; "/dev/sda2"; "/"];
         ["mke2journal_U"; "4096"; uuid; "/dev/sda1"];
         ["mke2fs_JU"; "ext2"; "4096"; "/dev/sda2"; uuid];
         ["mount_options"; ""; "/dev/sda2"; "/"];
-        ["write_file"; "/new"; "new file contents"; "0"];
+        ["write"; "/new"; "new file contents"];
         ["cat"; "/new"]], "new file contents")]),
    "make ext2/3/4 external journal with UUID",
    "\
         ["cat"; "/new"]], "new file contents")]),
    "make ext2/3/4 external journal with UUID",
    "\
@@ -3707,8 +3862,8 @@ was built (see C<appliance/kmod.whitelist.in> in the source).");
     )],
    "echo arguments back to the client",
    "\
     )],
    "echo arguments back to the client",
    "\
-This command concatenate the list of C<words> passed with single spaces between
-them and returns the resulting string.
+This command concatenates the list of C<words> passed with single spaces
+between them and returns the resulting string.
 
 You can use this command to test the connection through to the daemon.
 
 
 You can use this command to test the connection through to the daemon.
 
@@ -3810,16 +3965,17 @@ See also C<guestfs_realpath>.");
       [["vfs_type"; "/dev/sda1"]], "ext2")],
    "get the Linux VFS type corresponding to a mounted device",
    "\
       [["vfs_type"; "/dev/sda1"]], "ext2")],
    "get the Linux VFS type corresponding to a mounted device",
    "\
-This command gets the block device type corresponding to
-a mounted device called C<device>.
+This command gets the filesystem type corresponding to
+the filesystem on C<device>.
 
 
-Usually the result is the name of the Linux VFS module that
-is used to mount this device (probably determined automatically
-if you used the C<guestfs_mount> call).");
+For most filesystems, the result is the name of the Linux
+VFS module which would be used to mount this filesystem
+if you mounted it without specifying the filesystem type.
+For example a string such as C<ext3> or C<ntfs>.");
 
   ("truncate", (RErr, [Pathname "path"]), 199, [],
    [InitBasicFS, Always, TestOutputStruct (
 
   ("truncate", (RErr, [Pathname "path"]), 199, [],
    [InitBasicFS, Always, TestOutputStruct (
-      [["write_file"; "/test"; "some stuff so size is not zero"; "0"];
+      [["write"; "/test"; "some stuff so size is not zero"];
        ["truncate"; "/test"];
        ["stat"; "/test"]], [CompareWithInt ("size", 0)])],
    "truncate a file to zero size",
        ["truncate"; "/test"];
        ["stat"; "/test"]], [CompareWithInt ("size", 0)])],
    "truncate a file to zero size",
@@ -3835,8 +3991,13 @@ file must exist already.");
    "truncate a file to a particular size",
    "\
 This command truncates C<path> to size C<size> bytes.  The file
    "truncate a file to a particular size",
    "\
 This command truncates C<path> to size C<size> bytes.  The file
-must exist already.  If the file is smaller than C<size> then
-the file is extended to the required size with null bytes.");
+must exist already.
+
+If the current file size is less than C<size> then
+the file is extended to the required size with zero bytes.
+This creates a sparse file (ie. disk blocks are not allocated
+for the file until you write to it).  To create a non-sparse
+file of zeroes, use C<guestfs_fallocate64> instead.");
 
   ("utimens", (RErr, [Pathname "path"; Int64 "atsecs"; Int64 "atnsecs"; Int64 "mtsecs"; Int64 "mtnsecs"]), 201, [],
    [InitBasicFS, Always, TestOutputStruct (
 
   ("utimens", (RErr, [Pathname "path"; Int64 "atsecs"; Int64 "atnsecs"; Int64 "mtsecs"; Int64 "mtnsecs"]), 201, [],
    [InitBasicFS, Always, TestOutputStruct (
@@ -3869,7 +4030,13 @@ C<*secs> field is ignored in this case).");
    "create a directory with a particular mode",
    "\
 This command creates a directory, setting the initial permissions
    "create a directory with a particular mode",
    "\
 This command creates a directory, setting the initial permissions
-of the directory to C<mode>.  See also C<guestfs_mkdir>.");
+of the directory to C<mode>.
+
+For common Linux filesystems, the actual mode which is set will
+be C<mode & ~umask & 01777>.  Non-native-Linux filesystems may
+interpret the mode in other ways.
+
+See also C<guestfs_mkdir>, C<guestfs_umask>");
 
   ("lchown", (RErr, [Int "owner"; Int "group"; Pathname "path"]), 203, [],
    [], (* XXX *)
 
   ("lchown", (RErr, [Int "owner"; Int "group"; Pathname "path"]), 203, [],
    [], (* XXX *)
@@ -3940,7 +4107,7 @@ C<names> is the list of files from this directory.
 
 On return you get a list of strings, with a one-to-one
 correspondence to the C<names> list.  Each string is the
 
 On return you get a list of strings, with a one-to-one
 correspondence to the C<names> list.  Each string is the
-value of the symbol link.
+value of the symbolic link.
 
 If the C<readlink(2)> operation fails on any name, then
 the corresponding result string is the empty string C<\"\">.
 
 If the C<readlink(2)> operation fails on any name, then
 the corresponding result string is the empty string C<\"\">.
@@ -3967,7 +4134,9 @@ This command lets you read part of a file.  It reads C<count>
 bytes of the file, starting at C<offset>, from file C<path>.
 
 This may read fewer bytes than requested.  For further details
 bytes of the file, starting at C<offset>, from file C<path>.
 
 This may read fewer bytes than requested.  For further details
-see the L<pread(2)> system call.");
+see the L<pread(2)> system call.
+
+See also C<guestfs_pwrite>.");
 
   ("part_init", (RErr, [Device "device"; String "parttype"]), 208, [],
    [InitEmpty, Always, TestRun (
 
   ("part_init", (RErr, [Device "device"; String "parttype"]), 208, [],
    [InitEmpty, Always, TestRun (
@@ -4166,7 +4335,9 @@ content of the file is C<len> octets of C<c>, where C<c>
 must be a number in the range C<[0..255]>.
 
 To fill a file with zero bytes (sparsely), it is
 must be a number in the range C<[0..255]>.
 
 To fill a file with zero bytes (sparsely), it is
-much more efficient to use C<guestfs_truncate_size>.");
+much more efficient to use C<guestfs_truncate_size>.
+To create a file with a pattern of repeating bytes
+use C<guestfs_fill_pattern>.");
 
   ("available", (RErr, [StringList "groups"]), 216, [],
    [InitNone, Always, TestRun [["available"; ""]]],
 
   ("available", (RErr, [StringList "groups"]), 216, [],
    [InitNone, Always, TestRun [["available"; ""]]],
@@ -4178,6 +4349,8 @@ the libguestfs appliance will be able to provide.
 
 The libguestfs groups, and the functions that those
 groups correspond to, are listed in L<guestfs(3)/AVAILABILITY>.
 
 The libguestfs groups, and the functions that those
 groups correspond to, are listed in L<guestfs(3)/AVAILABILITY>.
+You can also fetch this list at runtime by calling
+C<guestfs_available_all_groups>.
 
 The argument C<groups> is a list of group names, eg:
 C<[\"inotify\", \"augeas\"]> would check for the availability of
 
 The argument C<groups> is a list of group names, eg:
 C<[\"inotify\", \"augeas\"]> would check for the availability of
@@ -4229,7 +4402,7 @@ See also C<guestfs_version>.
 
   ("dd", (RErr, [Dev_or_Path "src"; Dev_or_Path "dest"]), 217, [],
    [InitBasicFS, Always, TestOutputBuffer (
 
   ("dd", (RErr, [Dev_or_Path "src"; Dev_or_Path "dest"]), 217, [],
    [InitBasicFS, Always, TestOutputBuffer (
-      [["write_file"; "/src"; "hello, world"; "0"];
+      [["write"; "/src"; "hello, world"];
        ["dd"; "/src"; "/dest"];
        ["read_file"; "/dest"]], "hello, world")],
    "copy from source to destination using dd",
        ["dd"; "/src"; "/dest"];
        ["read_file"; "/dest"]], "hello, world")],
    "copy from source to destination using dd",
@@ -4245,7 +4418,7 @@ This command cannot do partial copies (see C<guestfs_copy_size>).");
 
   ("filesize", (RInt64 "size", [Pathname "file"]), 218, [],
    [InitBasicFS, Always, TestOutputInt (
 
   ("filesize", (RInt64 "size", [Pathname "file"]), 218, [],
    [InitBasicFS, Always, TestOutputInt (
-      [["write_file"; "/file"; "hello, world"; "0"];
+      [["write"; "/file"; "hello, world"];
        ["filesize"; "/file"]], 12)],
    "return the size of the file in bytes",
    "\
        ["filesize"; "/file"]], 12)],
    "return the size of the file in bytes",
    "\
@@ -4336,7 +4509,7 @@ See also C<guestfs_vgpvuuids>.");
 
   ("copy_size", (RErr, [Dev_or_Path "src"; Dev_or_Path "dest"; Int64 "size"]), 227, [],
    [InitBasicFS, Always, TestOutputBuffer (
 
   ("copy_size", (RErr, [Dev_or_Path "src"; Dev_or_Path "dest"; Int64 "size"]), 227, [],
    [InitBasicFS, Always, TestOutputBuffer (
-      [["write_file"; "/src"; "hello, world"; "0"];
+      [["write"; "/src"; "hello, world"];
        ["copy_size"; "/src"; "/dest"; "5"];
        ["read_file"; "/dest"]], "hello")],
    "copy size bytes from source to destination using dd",
        ["copy_size"; "/src"; "/dest"; "5"];
        ["read_file"; "/dest"]], "hello")],
    "copy size bytes from source to destination using dd",
@@ -4356,7 +4529,7 @@ This command writes zeroes over the entire C<device>.  Compare
 with C<guestfs_zero> which just zeroes the first few blocks of
 a device.");
 
 with C<guestfs_zero> which just zeroes the first few blocks of
 a device.");
 
-  ("txz_in", (RErr, [FileIn "tarball"; String "directory"]), 229, [],
+  ("txz_in", (RErr, [FileIn "tarball"; Pathname "directory"]), 229, [Optional "xz"],
    [InitBasicFS, Always, TestOutput (
       [["txz_in"; "../images/helloworld.tar.xz"; "/"];
        ["cat"; "/hello"]], "hello\n")],
    [InitBasicFS, Always, TestOutput (
       [["txz_in"; "../images/helloworld.tar.xz"; "/"];
        ["cat"; "/hello"]], "hello\n")],
@@ -4365,7 +4538,7 @@ a device.");
 This command uploads and unpacks local file C<tarball> (an
 I<xz compressed> tar file) into C<directory>.");
 
 This command uploads and unpacks local file C<tarball> (an
 I<xz compressed> tar file) into C<directory>.");
 
-  ("txz_out", (RErr, [Pathname "directory"; FileOut "tarball"]), 230, [],
+  ("txz_out", (RErr, [Pathname "directory"; FileOut "tarball"]), 230, [Optional "xz"],
    [],
    "pack directory into compressed tarball",
    "\
    [],
    "pack directory into compressed tarball",
    "\
@@ -4388,6 +4561,408 @@ See also L<ntfsresize(8)>.");
 This rescans all block devices and rebuilds the list of LVM
 physical volumes, volume groups and logical volumes.");
 
 This rescans all block devices and rebuilds the list of LVM
 physical volumes, volume groups and logical volumes.");
 
+  ("part_del", (RErr, [Device "device"; Int "partnum"]), 233, [],
+   [InitEmpty, Always, TestRun (
+      [["part_init"; "/dev/sda"; "mbr"];
+       ["part_add"; "/dev/sda"; "primary"; "1"; "-1"];
+       ["part_del"; "/dev/sda"; "1"]])],
+   "delete a partition",
+   "\
+This command deletes the partition numbered C<partnum> on C<device>.
+
+Note that in the case of MBR partitioning, deleting an
+extended partition also deletes any logical partitions
+it contains.");
+
+  ("part_get_bootable", (RBool "bootable", [Device "device"; Int "partnum"]), 234, [],
+   [InitEmpty, Always, TestOutputTrue (
+      [["part_init"; "/dev/sda"; "mbr"];
+       ["part_add"; "/dev/sda"; "primary"; "1"; "-1"];
+       ["part_set_bootable"; "/dev/sda"; "1"; "true"];
+       ["part_get_bootable"; "/dev/sda"; "1"]])],
+   "return true if a partition is bootable",
+   "\
+This command returns true if the partition C<partnum> on
+C<device> has the bootable flag set.
+
+See also C<guestfs_part_set_bootable>.");
+
+  ("part_get_mbr_id", (RInt "idbyte", [Device "device"; Int "partnum"]), 235, [FishOutput FishOutputHexadecimal],
+   [InitEmpty, Always, TestOutputInt (
+      [["part_init"; "/dev/sda"; "mbr"];
+       ["part_add"; "/dev/sda"; "primary"; "1"; "-1"];
+       ["part_set_mbr_id"; "/dev/sda"; "1"; "0x7f"];
+       ["part_get_mbr_id"; "/dev/sda"; "1"]], 0x7f)],
+   "get the MBR type byte (ID byte) from a partition",
+   "\
+Returns the MBR type byte (also known as the ID byte) from
+the numbered partition C<partnum>.
+
+Note that only MBR (old DOS-style) partitions have type bytes.
+You will get undefined results for other partition table
+types (see C<guestfs_part_get_parttype>).");
+
+  ("part_set_mbr_id", (RErr, [Device "device"; Int "partnum"; Int "idbyte"]), 236, [],
+   [], (* tested by part_get_mbr_id *)
+   "set the MBR type byte (ID byte) of a partition",
+   "\
+Sets the MBR type byte (also known as the ID byte) of
+the numbered partition C<partnum> to C<idbyte>.  Note
+that the type bytes quoted in most documentation are
+in fact hexadecimal numbers, but usually documented
+without any leading \"0x\" which might be confusing.
+
+Note that only MBR (old DOS-style) partitions have type bytes.
+You will get undefined results for other partition table
+types (see C<guestfs_part_get_parttype>).");
+
+  ("checksum_device", (RString "checksum", [String "csumtype"; Device "device"]), 237, [],
+   [InitISOFS, Always, TestOutput (
+      [["checksum_device"; "md5"; "/dev/sdd"]],
+      (Digest.to_hex (Digest.file "images/test.iso")))],
+   "compute MD5, SHAx or CRC checksum of the contents of a device",
+   "\
+This call computes the MD5, SHAx or CRC checksum of the
+contents of the device named C<device>.  For the types of
+checksums supported see the C<guestfs_checksum> command.");
+
+  ("lvresize_free", (RErr, [Device "lv"; Int "percent"]), 238, [Optional "lvm2"],
+   [InitNone, Always, TestRun (
+      [["part_disk"; "/dev/sda"; "mbr"];
+       ["pvcreate"; "/dev/sda1"];
+       ["vgcreate"; "VG"; "/dev/sda1"];
+       ["lvcreate"; "LV"; "VG"; "10"];
+       ["lvresize_free"; "/dev/VG/LV"; "100"]])],
+   "expand an LV to fill free space",
+   "\
+This expands an existing logical volume C<lv> so that it fills
+C<pc>% of the remaining free space in the volume group.  Commonly
+you would call this with pc = 100 which expands the logical volume
+as much as possible, using all remaining free space in the volume
+group.");
+
+  ("aug_clear", (RErr, [String "augpath"]), 239, [Optional "augeas"],
+   [], (* XXX Augeas code needs tests. *)
+   "clear Augeas path",
+   "\
+Set the value associated with C<path> to C<NULL>.  This
+is the same as the L<augtool(1)> C<clear> command.");
+
+  ("get_umask", (RInt "mask", []), 240, [FishOutput FishOutputOctal],
+   [InitEmpty, Always, TestOutputInt (
+      [["get_umask"]], 0o22)],
+   "get the current umask",
+   "\
+Return the current umask.  By default the umask is C<022>
+unless it has been set by calling C<guestfs_umask>.");
+
+  ("debug_upload", (RErr, [FileIn "filename"; String "tmpname"; Int "mode"]), 241, [],
+   [],
+   "upload a file to the appliance (internal use only)",
+   "\
+The C<guestfs_debug_upload> command uploads a file to
+the libguestfs appliance.
+
+There is no comprehensive help for this command.  You have
+to look at the file C<daemon/debug.c> in the libguestfs source
+to find out what it is for.");
+
+  ("base64_in", (RErr, [FileIn "base64file"; Pathname "filename"]), 242, [],
+   [InitBasicFS, Always, TestOutput (
+      [["base64_in"; "../images/hello.b64"; "/hello"];
+       ["cat"; "/hello"]], "hello\n")],
+   "upload base64-encoded data to file",
+   "\
+This command uploads base64-encoded data from C<base64file>
+to C<filename>.");
+
+  ("base64_out", (RErr, [Pathname "filename"; FileOut "base64file"]), 243, [],
+   [],
+   "download file and encode as base64",
+   "\
+This command downloads the contents of C<filename>, writing
+it out to local file C<base64file> encoded as base64.");
+
+  ("checksums_out", (RErr, [String "csumtype"; Pathname "directory"; FileOut "sumsfile"]), 244, [],
+   [],
+   "compute MD5, SHAx or CRC checksum of files in a directory",
+   "\
+This command computes the checksums of all regular files in
+C<directory> and then emits a list of those checksums to
+the local output file C<sumsfile>.
+
+This can be used for verifying the integrity of a virtual
+machine.  However to be properly secure you should pay
+attention to the output of the checksum command (it uses
+the ones from GNU coreutils).  In particular when the
+filename is not printable, coreutils uses a special
+backslash syntax.  For more information, see the GNU
+coreutils info file.");
+
+  ("fill_pattern", (RErr, [String "pattern"; Int "len"; Pathname "path"]), 245, [],
+   [InitBasicFS, Always, TestOutputBuffer (
+      [["fill_pattern"; "abcdefghijklmnopqrstuvwxyz"; "28"; "/test"];
+       ["read_file"; "/test"]], "abcdefghijklmnopqrstuvwxyzab")],
+   "fill a file with a repeating pattern of bytes",
+   "\
+This function is like C<guestfs_fill> except that it creates
+a new file of length C<len> containing the repeating pattern
+of bytes in C<pattern>.  The pattern is truncated if necessary
+to ensure the length of the file is exactly C<len> bytes.");
+
+  ("write", (RErr, [Pathname "path"; BufferIn "content"]), 246, [ProtocolLimitWarning],
+   [InitBasicFS, Always, TestOutput (
+      [["write"; "/new"; "new file contents"];
+       ["cat"; "/new"]], "new file contents");
+    InitBasicFS, Always, TestOutput (
+      [["write"; "/new"; "\nnew file contents\n"];
+       ["cat"; "/new"]], "\nnew file contents\n");
+    InitBasicFS, Always, TestOutput (
+      [["write"; "/new"; "\n\n"];
+       ["cat"; "/new"]], "\n\n");
+    InitBasicFS, Always, TestOutput (
+      [["write"; "/new"; ""];
+       ["cat"; "/new"]], "");
+    InitBasicFS, Always, TestOutput (
+      [["write"; "/new"; "\n\n\n"];
+       ["cat"; "/new"]], "\n\n\n");
+    InitBasicFS, Always, TestOutput (
+      [["write"; "/new"; "\n"];
+       ["cat"; "/new"]], "\n")],
+   "create a new file",
+   "\
+This call creates a file called C<path>.  The content of the
+file is the string C<content> (which can contain any 8 bit data).");
+
+  ("pwrite", (RInt "nbytes", [Pathname "path"; BufferIn "content"; Int64 "offset"]), 247, [ProtocolLimitWarning],
+   [InitBasicFS, Always, TestOutput (
+      [["write"; "/new"; "new file contents"];
+       ["pwrite"; "/new"; "data"; "4"];
+       ["cat"; "/new"]], "new data contents");
+    InitBasicFS, Always, TestOutput (
+      [["write"; "/new"; "new file contents"];
+       ["pwrite"; "/new"; "is extended"; "9"];
+       ["cat"; "/new"]], "new file is extended");
+    InitBasicFS, Always, TestOutput (
+      [["write"; "/new"; "new file contents"];
+       ["pwrite"; "/new"; ""; "4"];
+       ["cat"; "/new"]], "new file contents")],
+   "write to part of a file",
+   "\
+This command writes to part of a file.  It writes the data
+buffer C<content> to the file C<path> starting at offset C<offset>.
+
+This command implements the L<pwrite(2)> system call, and like
+that system call it may not write the full data requested.  The
+return value is the number of bytes that were actually written
+to the file.  This could even be 0, although short writes are
+unlikely for regular files in ordinary circumstances.
+
+See also C<guestfs_pread>.");
+
+  ("resize2fs_size", (RErr, [Device "device"; Int64 "size"]), 248, [],
+   [],
+   "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.");
+
+  ("pvresize_size", (RErr, [Device "device"; Int64 "size"]), 249, [Optional "lvm2"],
+   [],
+   "resize an LVM physical volume (with size)",
+   "\
+This command is the same as C<guestfs_pvresize> except that it
+allows you to specify the new size (in bytes) explicitly.");
+
+  ("ntfsresize_size", (RErr, [Device "device"; Int64 "size"]), 250, [Optional "ntfsprogs"],
+   [],
+   "resize an NTFS filesystem (with size)",
+   "\
+This command is the same as C<guestfs_ntfsresize> except that it
+allows you to specify the new size (in bytes) explicitly.");
+
+  ("available_all_groups", (RStringList "groups", []), 251, [],
+   [InitNone, Always, TestRun [["available_all_groups"]]],
+   "return a list of all optional groups",
+   "\
+This command returns a list of all optional groups that this
+daemon knows about.  Note this returns both supported and unsupported
+groups.  To find out which ones the daemon can actually support
+you have to call C<guestfs_available> on each member of the
+returned list.
+
+See also C<guestfs_available> and L<guestfs(3)/AVAILABILITY>.");
+
+  ("fallocate64", (RErr, [Pathname "path"; Int64 "len"]), 252, [],
+   [InitBasicFS, Always, TestOutputStruct (
+      [["fallocate64"; "/a"; "1000000"];
+       ["stat"; "/a"]], [CompareWithInt ("size", 1_000_000)])],
+   "preallocate a file in the guest filesystem",
+   "\
+This command preallocates a file (containing zero bytes) named
+C<path> of size C<len> bytes.  If the file exists already, it
+is overwritten.
+
+Note that this call allocates disk blocks for the file.
+To create a sparse file use C<guestfs_truncate_size> instead.
+
+The deprecated call C<guestfs_fallocate> does the same,
+but owing to an oversight it only allowed 30 bit lengths
+to be specified, effectively limiting the maximum size
+of files created through that call to 1GB.
+
+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
 ]
 
 let all_functions = non_daemon_functions @ daemon_functions
@@ -4399,6 +4974,16 @@ let all_functions_sorted =
   List.sort (fun (n1,_,_,_,_,_,_) (n2,_,_,_,_,_,_) ->
                compare n1 n2) all_functions
 
   List.sort (fun (n1,_,_,_,_,_,_) (n2,_,_,_,_,_,_) ->
                compare n1 n2) all_functions
 
+(* This is used to generate the src/MAX_PROC_NR file which
+ * contains the maximum procedure number, a surrogate for the
+ * ABI version number.  See src/Makefile.am for the details.
+ *)
+let max_proc_nr =
+  let proc_nrs = List.map (
+    fun (_, _, proc_nr, _, _, _, _) -> proc_nr
+  ) daemon_functions in
+  List.fold_left max 0 proc_nrs
+
 (* Field types for structures. *)
 type field =
   | FChar                      (* C 'char' (really, a 7 bit byte). *)
 (* Field types for structures. *)
 type field =
   | FChar                      (* C 'char' (really, a 7 bit byte). *)
@@ -4639,6 +5224,7 @@ type callt =
   | CallInt of int
   | CallInt64 of int64
   | CallBool of bool
   | CallInt of int
   | CallInt64 of int64
   | CallBool of bool
+  | CallBuffer of string
 
 (* Used to memoize the result of pod2text. *)
 let pod2text_memo_filename = "src/.pod2text.data"
 
 (* Used to memoize the result of pod2text. *)
 let pod2text_memo_filename = "src/.pod2text.data"
@@ -4784,10 +5370,21 @@ let count_chars c str =
   done;
   !count
 
   done;
   !count
 
+let explode str =
+  let r = ref [] in
+  for i = 0 to String.length str - 1 do
+    let c = String.unsafe_get str i in
+    r := c :: !r;
+  done;
+  List.rev !r
+
+let map_chars f str =
+  List.map f (explode 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
 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 -> n
+  | FileIn n | FileOut n | BufferIn n | Key n -> n
 
 let java_name_of_struct typ =
   try List.assoc typ java_structs
 
 let java_name_of_struct typ =
   try List.assoc typ java_structs
@@ -4953,7 +5550,7 @@ let check_functions () =
         failwithf "short description of %s should not end with . or \\n." name
   ) all_functions;
 
         failwithf "short description of %s should not end with . or \\n." name
   ) all_functions;
 
-  (* Check long dscriptions. *)
+  (* Check long descriptions. *)
   List.iter (
     fun (name, _, _, _, _, _, longdesc) ->
       if longdesc.[String.length longdesc-1] = '\n' then
   List.iter (
     fun (name, _, _, _, _, _, longdesc) ->
       if longdesc.[String.length longdesc-1] = '\n' then
@@ -5098,7 +5695,7 @@ let rec generate_actions_pod () =
         let name = "guestfs_" ^ shortname in
         pr "=head2 %s\n\n" name;
         pr " ";
         let name = "guestfs_" ^ shortname in
         pr "=head2 %s\n\n" name;
         pr " ";
-        generate_prototype ~extern:false ~handle:"handle" name style;
+        generate_prototype ~extern:false ~handle:"g" name style;
         pr "\n\n";
         pr "%s\n\n" longdesc;
         (match fst style with
         pr "\n\n";
         pr "%s\n\n" longdesc;
         (match fst style with
@@ -5115,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.
 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.
 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.
@@ -5148,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;
           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
         match deprecation_notice flags with
         | None -> ()
         | Some txt -> pr "%s\n\n" txt
@@ -5253,13 +5854,15 @@ and generate_xdr () =
            pr "struct %s_args {\n" name;
            List.iter (
              function
            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
              | Bool n -> pr "  bool %s;\n" n
              | Int n -> pr "  int %s;\n" n
              | Int64 n -> pr "  hyper %s;\n" n
                  pr "  string %s<>;\n" n
              | OptString n -> pr "  str *%s;\n" n
              | StringList n | DeviceList n -> pr "  str %s<>;\n" n
              | Bool n -> pr "  bool %s;\n" n
              | Int n -> pr "  int %s;\n" n
              | Int64 n -> pr "  hyper %s;\n" n
+             | BufferIn n ->
+                 pr "  opaque %s<>;\n" n
              | FileIn _ | FileOut _ -> ()
            ) args;
            pr "};\n\n"
              | FileIn _ | FileOut _ -> ()
            ) args;
            pr "};\n\n"
@@ -5426,7 +6029,7 @@ and generate_actions_h () =
   List.iter (
     fun (shortname, style, _, _, _, _, _) ->
       let name = "guestfs_" ^ shortname in
   List.iter (
     fun (shortname, style, _, _, _, _, _) ->
       let name = "guestfs_" ^ shortname in
-      generate_prototype ~single_line:true ~newline:true ~handle:"handle"
+      generate_prototype ~single_line:true ~newline:true ~handle:"g"
         name style
   ) all_functions
 
         name style
   ) all_functions
 
@@ -5436,7 +6039,7 @@ and generate_internal_actions_h () =
   List.iter (
     fun (shortname, style, _, _, _, _, _) ->
       let name = "guestfs__" ^ shortname in
   List.iter (
     fun (shortname, style, _, _, _, _, _) ->
       let name = "guestfs__" ^ shortname in
-      generate_prototype ~single_line:true ~newline:true ~handle:"handle"
+      generate_prototype ~single_line:true ~newline:true ~handle:"g"
         name style
   ) non_daemon_functions
 
         name style
   ) non_daemon_functions
 
@@ -5513,6 +6116,51 @@ check_state (guestfs_h *g, const char *caller)
 
 ";
 
 
 ";
 
+  let error_code_of = function
+    | RErr | RInt _ | RInt64 _ | RBool _ -> "-1"
+    | RConstString _ | RConstOptString _
+    | RString _ | RStringList _
+    | RStruct _ | RStructList _
+    | RHashtable _ | RBufferOut _ -> "NULL"
+  in
+
+  (* Generate code to check String-like parameters are not passed in
+   * as NULL (returning an error if they are).
+   *)
+  let check_null_strings shortname style =
+    let pr_newline = ref false in
+    List.iter (
+      function
+      (* parameters which should not be NULL *)
+      | String n
+      | Device n
+      | Pathname n
+      | Dev_or_Path n
+      | FileIn n
+      | FileOut n
+      | BufferIn n
+      | StringList 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;
+          pr "    return %s;\n" (error_code_of (fst style));
+          pr "  }\n";
+          pr_newline := true
+
+      (* can be NULL *)
+      | OptString _
+
+      (* not applicable *)
+      | Bool _
+      | Int _
+      | Int64 _ -> ()
+    ) (snd style);
+
+    if !pr_newline then pr "\n";
+  in
+
   (* Generate code to generate guestfish call traces. *)
   let trace_call shortname style =
     pr "  if (guestfs__get_trace (g)) {\n";
   (* Generate code to generate guestfish call traces. *)
   let trace_call shortname style =
     pr "  if (guestfs__get_trace (g)) {\n";
@@ -5522,11 +6170,11 @@ check_state (guestfs_h *g, const char *caller)
                    | StringList _ | DeviceList _ -> true
                    | _ -> false) (snd style) in
     if needs_i then (
                    | StringList _ | DeviceList _ -> true
                    | _ -> false) (snd style) in
     if needs_i then (
-      pr "    int i;\n";
+      pr "    size_t i;\n";
       pr "\n"
     );
 
       pr "\n"
     );
 
-    pr "    printf (\"%s\");\n" shortname;
+    pr "    fprintf (stderr, \"%s\");\n" shortname;
     List.iter (
       function
       | String n                       (* strings *)
     List.iter (
       function
       | String n                       (* strings *)
@@ -5534,29 +6182,31 @@ check_state (guestfs_h *g, const char *caller)
       | Pathname n
       | Dev_or_Path n
       | FileIn n
       | Pathname n
       | Dev_or_Path n
       | FileIn n
-      | FileOut n ->
+      | FileOut n
+      | BufferIn n
+      | Key n ->
           (* guestfish doesn't support string escaping, so neither do we *)
           (* 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 *)
       | 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 *)
       | 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 "    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 "    }\n";
-          pr "    putchar ('\"');\n";
+          pr "    fputc ('\"', stderr);\n";
       | Bool n ->                      (* boolean *)
       | Bool n ->                      (* boolean *)
-          pr "    fputs (%s ? \" true\" : \" false\", stdout);\n" n
+          pr "    fputs (%s ? \" true\" : \" false\", stderr);\n" n
       | Int n ->                       (* int *)
       | Int n ->                       (* int *)
-          pr "    printf (\" %%d\", %s);\n" n
+          pr "    fprintf (stderr, \" %%d\", %s);\n" n
       | Int64 n ->
       | Int64 n ->
-          pr "    printf (\" %%\" PRIi64, %s);\n" n
+          pr "    fprintf (stderr, \" %%\" PRIi64, %s);\n" n
     ) (snd style);
     ) (snd style);
-    pr "    putchar ('\\n');\n";
+    pr "    fputc ('\\n', stderr);\n";
     pr "  }\n";
     pr "\n";
   in
     pr "  }\n";
     pr "\n";
   in
@@ -5569,6 +6219,7 @@ check_state (guestfs_h *g, const char *caller)
       generate_prototype ~extern:false ~semicolon:false ~newline:true
         ~handle:"g" name style;
       pr "{\n";
       generate_prototype ~extern:false ~semicolon:false ~newline:true
         ~handle:"g" name style;
       pr "{\n";
+      check_null_strings shortname style;
       trace_call shortname style;
       pr "  return guestfs__%s " shortname;
       generate_c_call_args ~handle:"g" style;
       trace_call shortname style;
       pr "  return guestfs__%s " shortname;
       generate_c_call_args ~handle:"g" style;
@@ -5581,21 +6232,12 @@ check_state (guestfs_h *g, const char *caller)
   List.iter (
     fun (shortname, style, _, _, _, _, _) ->
       let name = "guestfs_" ^ shortname in
   List.iter (
     fun (shortname, style, _, _, _, _, _) ->
       let name = "guestfs_" ^ shortname in
+      let error_code = error_code_of (fst style) in
 
       (* Generate the action stub. *)
       generate_prototype ~extern:false ~semicolon:false ~newline:true
         ~handle:"g" name style;
 
 
       (* Generate the action stub. *)
       generate_prototype ~extern:false ~semicolon:false ~newline:true
         ~handle:"g" name style;
 
-      let error_code =
-        match fst style with
-        | RErr | RInt _ | RInt64 _ | RBool _ -> "-1"
-        | RConstString _ | RConstOptString _ ->
-            failwithf "RConstString|RConstOptString cannot be used by daemon functions"
-        | RString _ | RStringList _
-        | RStruct _ | RStructList _
-        | RHashtable _ | RBufferOut _ ->
-            "NULL" in
-
       pr "{\n";
 
       (match snd style with
       pr "{\n";
 
       (match snd style with
@@ -5620,8 +6262,10 @@ check_state (guestfs_h *g, const char *caller)
       pr "  int serial;\n";
       pr "  int r;\n";
       pr "\n";
       pr "  int serial;\n";
       pr "  int r;\n";
       pr "\n";
+      check_null_strings shortname style;
       trace_call shortname style;
       trace_call shortname style;
-      pr "  if (check_state (g, \"%s\") == -1) return %s;\n" name error_code;
+      pr "  if (check_state (g, \"%s\") == -1) return %s;\n"
+        shortname error_code;
       pr "  guestfs___set_busy (g);\n";
       pr "\n";
 
       pr "  guestfs___set_busy (g);\n";
       pr "\n";
 
@@ -5633,7 +6277,7 @@ check_state (guestfs_h *g, const char *caller)
        | args ->
            List.iter (
              function
        | 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
                  pr "  args.%s = (char *) %s;\n" n n
              | OptString n ->
                  pr "  args.%s = %s ? (char **) &%s : NULL;\n" n n n
@@ -5647,6 +6291,16 @@ check_state (guestfs_h *g, const char *caller)
              | Int64 n ->
                  pr "  args.%s = %s;\n" n n
              | FileIn _ | FileOut _ -> ()
              | Int64 n ->
                  pr "  args.%s = %s;\n" n n
              | FileIn _ | FileOut _ -> ()
+             | BufferIn n ->
+                 pr "  /* Just catch grossly large sizes. XDR encoding will make this precise. */\n";
+                 pr "  if (%s_size >= GUESTFS_MESSAGE_MAX) {\n" n;
+                 pr "    error (g, \"%%s: size of input buffer too large\", \"%s\");\n"
+                   shortname;
+                 pr "    guestfs___end_busy (g);\n";
+                 pr "    return %s;\n" error_code;
+                 pr "  }\n";
+                 pr "  args.%s.%s_val = (char *) %s;\n" n n n;
+                 pr "  args.%s.%s_len = %s_size;\n" n n n
            ) args;
            pr "  serial = guestfs___send (g, GUESTFS_PROC_%s,\n"
              (String.uppercase shortname);
            ) args;
            pr "  serial = guestfs___send (g, GUESTFS_PROC_%s,\n"
              (String.uppercase shortname);
@@ -5820,6 +6474,7 @@ and generate_linker_script () =
     "guestfs_get_error_handler";
     "guestfs_get_out_of_memory_handler";
     "guestfs_last_error";
     "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";
     "guestfs_set_error_handler";
     "guestfs_set_launch_done_callback";
     "guestfs_set_log_message_callback";
@@ -5831,6 +6486,8 @@ and generate_linker_script () =
      *)
     "guestfs_safe_calloc";
     "guestfs_safe_malloc";
      *)
     "guestfs_safe_calloc";
     "guestfs_safe_malloc";
+    "guestfs_safe_strdup";
+    "guestfs_safe_memdup";
   ] in
   let functions =
     List.map (fun (name, _, _, _, _, _, _) -> "guestfs_" ^ name)
   ] in
   let functions =
     List.map (fun (name, _, _, _, _, _, _) -> "guestfs_" ^ name)
@@ -5900,25 +6557,34 @@ and generate_daemon_actions () =
              function
              | Device n | Dev_or_Path n
              | Pathname n
              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
              | Int n -> pr "  int %s;\n" n
              | Int64 n -> pr "  int64_t %s;\n" n
              | FileIn _ | FileOut _ -> ()
              | OptString n -> pr "  char *%s;\n" n
              | StringList n | DeviceList n -> pr "  char **%s;\n" n
              | Bool n -> pr "  int %s;\n" n
              | Int n -> pr "  int %s;\n" n
              | Int64 n -> pr "  int64_t %s;\n" n
              | FileIn _ | FileOut _ -> ()
+             | BufferIn n ->
+                 pr "  const char *%s;\n" n;
+                 pr "  size_t %s_size;\n" n
            ) args
       );
       pr "\n";
 
            ) args
       );
       pr "\n";
 
+      let is_filein =
+        List.exists (function FileIn _ -> true | _ -> false) (snd style) in
+
       (match snd style with
        | [] -> ()
        | args ->
            pr "  memset (&args, 0, sizeof args);\n";
            pr "\n";
            pr "  if (!xdr_guestfs_%s_args (xdr_in, &args)) {\n" name;
       (match snd style with
        | [] -> ()
        | args ->
            pr "  memset (&args, 0, sizeof args);\n";
            pr "\n";
            pr "  if (!xdr_guestfs_%s_args (xdr_in, &args)) {\n" name;
-           pr "    reply_with_error (\"daemon failed to decode procedure arguments\");\n";
-           pr "    return;\n";
+           if is_filein then
+             pr "    if (cancel_receive () != -2)\n";
+           pr "      reply_with_error (\"daemon failed to decode procedure arguments\");\n";
+           pr "    goto done;\n";
            pr "  }\n";
            let pr_args n =
              pr "  char *%s = args.%s;\n" n n
            pr "  }\n";
            let pr_args n =
              pr "  char *%s = args.%s;\n" n n
@@ -5927,7 +6593,9 @@ and generate_daemon_actions () =
              pr "  %s = realloc (args.%s.%s_val,\n" n n n;
              pr "                sizeof (char *) * (args.%s.%s_len+1));\n" n n;
              pr "  if (%s == NULL) {\n" n;
              pr "  %s = realloc (args.%s.%s_val,\n" n n n;
              pr "                sizeof (char *) * (args.%s.%s_len+1));\n" n n;
              pr "  if (%s == NULL) {\n" n;
-             pr "    reply_with_perror (\"realloc\");\n";
+             if is_filein then
+               pr "    if (cancel_receive () != -2)\n";
+             pr "      reply_with_perror (\"realloc\");\n";
              pr "    goto done;\n";
              pr "  }\n";
              pr "  %s[args.%s.%s_len] = NULL;\n" n n n;
              pr "    goto done;\n";
              pr "  }\n";
              pr "  %s[args.%s.%s_len] = NULL;\n" n n n;
@@ -5937,38 +6605,48 @@ and generate_daemon_actions () =
              function
              | Pathname n ->
                  pr_args n;
              function
              | Pathname n ->
                  pr_args n;
-                 pr "  ABS_PATH (%s, goto done);\n" n;
+                 pr "  ABS_PATH (%s, %s, goto done);\n"
+                   n (if is_filein then "cancel_receive ()" else "0");
              | Device n ->
                  pr_args n;
              | Device n ->
                  pr_args n;
-                 pr "  RESOLVE_DEVICE (%s, goto done);\n" n;
+                 pr "  RESOLVE_DEVICE (%s, %s, goto done);\n"
+                   n (if is_filein then "cancel_receive ()" else "0");
              | Dev_or_Path n ->
                  pr_args n;
              | Dev_or_Path n ->
                  pr_args n;
-                 pr "  REQUIRE_ROOT_OR_RESOLVE_DEVICE (%s, goto done);\n" n;
-             | String n -> 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 | 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";
              | 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], 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
              | Int n -> pr "  %s = args.%s;\n" n n
              | Int64 n -> pr "  %s = args.%s;\n" n n
              | FileIn _ | FileOut _ -> ()
                  pr "  }\n";
              | Bool n -> pr "  %s = args.%s;\n" n n
              | Int n -> pr "  %s = args.%s;\n" n n
              | Int64 n -> pr "  %s = args.%s;\n" n n
              | FileIn _ | FileOut _ -> ()
+             | BufferIn n ->
+                 pr "  %s = args.%s.%s_val;\n" n n n;
+                 pr "  %s_size = args.%s.%s_len;\n" n n n
            ) args;
            pr "\n"
       );
 
            ) args;
            pr "\n"
       );
 
-
       (* this is used at least for do_equal *)
       if List.exists (function Pathname _ -> true | _ -> false) (snd style) then (
         (* Emit NEED_ROOT just once, even when there are two or
            more Pathname args *)
       (* this is used at least for do_equal *)
       if List.exists (function Pathname _ -> true | _ -> false) (snd style) then (
         (* Emit NEED_ROOT just once, even when there are two or
            more Pathname args *)
-        pr "  NEED_ROOT (goto done);\n";
+        pr "  NEED_ROOT (%s, goto done);\n"
+          (if is_filein then "cancel_receive ()" else "0");
       );
 
       (* Don't want to call the impl with any FileIn or FileOut
       );
 
       (* Don't want to call the impl with any FileIn or FileOut
@@ -6054,15 +6732,14 @@ and generate_daemon_actions () =
       );
 
       (* Free the args. *)
       );
 
       (* Free the args. *)
+      pr "done:\n";
       (match snd style with
       (match snd style with
-       | [] ->
-           pr "done: ;\n";
+       | [] -> ()
        | _ ->
        | _ ->
-           pr "done:\n";
            pr "  xdr_free ((xdrproc_t) xdr_guestfs_%s_args, (char *) &args);\n"
              name
       );
            pr "  xdr_free ((xdrproc_t) xdr_guestfs_%s_args, (char *) &args);\n"
              name
       );
-
+      pr "  return;\n";
       pr "}\n\n";
   ) daemon_functions;
 
       pr "}\n\n";
   ) daemon_functions;
 
@@ -6098,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 "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";
         pr "\n";
         (*
           pr "  fprintf (stderr, \"%%s: <<%%s>>\\n\", __func__, str);\n";
@@ -6323,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)
 {
 /* 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]);
 
   for (argc = 0; argv[argc] != NULL; ++argc)
     printf (\"\\t%%s\\n\", argv[argc]);
@@ -6332,13 +7009,33 @@ static void print_strings (char *const *argv)
 /*
 static void print_table (char const *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]);
 }
 */
 
 
   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. *)
 ";
 
   (* Generate a list of commands which are not tested anywhere. *)
@@ -6350,7 +7047,7 @@ static void print_table (char const *const *argv)
     fun (_, _, _, _, tests, _, _) ->
       let tests = filter_map (
         function
     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
         | (_, Disabled, _) -> None
       ) tests in
       let seq = List.concat (List.map seq_of_test tests) in
@@ -6510,6 +7207,8 @@ int main (int argc, char *argv[])
   iteri (
     fun i test_name ->
       pr "  test_num++;\n";
   iteri (
     fun i test_name ->
       pr "  test_num++;\n";
+      pr "  if (guestfs_get_verbose (g))\n";
+      pr "    printf (\"-------------------------------------------------------------------------------\\n\");\n";
       pr "  printf (\"%%3d/%%3d %s\\n\", test_num, nr_tests);\n" test_name;
       pr "  if (%s () == -1) {\n" test_name;
       pr "    printf (\"%s FAILED\\n\");\n" test_name;
       pr "  printf (\"%%3d/%%3d %s\\n\", test_num, nr_tests);\n" test_name;
       pr "  if (%s () == -1) {\n" test_name;
       pr "    printf (\"%s FAILED\\n\");\n" test_name;
@@ -6518,11 +7217,22 @@ int main (int argc, char *argv[])
   ) test_names;
   pr "\n";
 
   ) 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";
 
   pr "  if (n_failed > 0) {\n";
   pr "    printf (\"***** %%lu / %%d tests FAILED *****\\n\", n_failed, nr_tests);\n";
@@ -6554,7 +7264,7 @@ static int %s_skip (void)
 " test_name name (String.uppercase test_name) (String.uppercase name);
 
   (match prereq with
 " 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";
    | If code | Unless code ->
        pr "static int %s_prereq (void)\n" test_name;
        pr "{\n";
@@ -6579,16 +7289,9 @@ static int %s (void)
   List.iter (
     function
     | Optional group ->
   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;
         pr "  }\n";
     | _ -> ()
   ) flags;
@@ -6610,6 +7313,13 @@ static int %s (void)
        pr "  }\n";
        pr "\n";
        generate_one_test_body name i test_name init test;
        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
   );
    | Always ->
        generate_one_test_body name i test_name init test
   );
@@ -6920,8 +7630,12 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
         | Device n, arg
         | Dev_or_Path n, arg
         | String n, arg
         | 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);
             pr "    const char *%s = \"%s\";\n" n (c_quote arg);
+            pr "    size_t %s_size = %d;\n" n (String.length arg)
         | Int _, _
         | Int64 _, _
         | Bool _, _
         | Int _, _
         | Int64 _, _
         | Bool _, _
@@ -6951,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";
         | 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"
             "NULL"
         | RStruct (_, typ) ->
             pr "    struct guestfs_%s *r;\n" typ; "NULL"
@@ -6972,8 +7686,11 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
         | Pathname n, _
         | Device n, _ | Dev_or_Path n, _
         | String n, _
         | Pathname n, _
         | Device n, _ | Dev_or_Path n, _
         | String n, _
-        | OptString n, _ ->
+        | OptString n, _
+        | Key n, _ ->
             pr ", %s" n
             pr ", %s" n
+        | BufferIn n, _ ->
+            pr ", %s, %s_size" n n
         | FileIn _, arg | FileOut _, arg ->
             pr ", \"%s\"" (c_quote arg)
         | StringList n, _ | DeviceList n, _ ->
         | FileIn _, arg | FileOut _, arg ->
             pr ", \"%s\"" (c_quote arg)
         | StringList n, _ | DeviceList n, _ ->
@@ -7062,6 +7779,9 @@ and generate_fish_cmds () =
   pr "#include \"xstrtol.h\"\n";
   pr "#include \"fish.h\"\n";
   pr "\n";
   pr "#include \"xstrtol.h\"\n";
   pr "#include \"fish.h\"\n";
   pr "\n";
+  pr "/* Valid suffixes allowed for numbers.  See Gnulib xstrtol function. */\n";
+  pr "static const char *xstrtol_suffixes = \"0kKMGTPEZY\";\n";
+  pr "\n";
 
   (* list_commands function, which implements guestfish -h *)
   pr "void list_commands (void)\n";
 
   (* list_commands function, which implements guestfish -h *)
   pr "void list_commands (void)\n";
@@ -7080,7 +7800,7 @@ and generate_fish_cmds () =
   pr "\n";
 
   (* display_command function, which implements guestfish -h cmd *)
   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) ->
   pr "{\n";
   List.iter (
     fun (name, style, _, flags, _, shortdesc, longdesc) ->
@@ -7093,14 +7813,22 @@ and generate_fish_cmds () =
         match snd style with
         | [] -> name2
         | args ->
         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 =
             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
 
         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
       (* For DangerWillRobinson commands, we should probably have
        * guestfish prompt before allowing you to use them (especially
        * in interactive mode). XXX
@@ -7128,15 +7856,17 @@ and generate_fish_cmds () =
         pr " || STRCASEEQ (cmd, \"%s\")" name2;
       if name <> alias then
         pr " || STRCASEEQ (cmd, \"%s\")" alias;
         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 "    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 "  else\n"
   ) all_functions;
-  pr "    display_builtin_command (cmd);\n";
+  pr "    return display_builtin_command (cmd);\n";
   pr "}\n";
   pr "\n";
 
   pr "}\n";
   pr "\n";
 
@@ -7252,11 +7982,15 @@ and generate_fish_cmds () =
         function
         | Device n
         | String n
         function
         | Device n
         | String n
-        | OptString n
-        | FileIn n
-        | FileOut n -> pr "  const char *%s;\n" n
+        | OptString n -> pr "  const char *%s;\n" n
         | Pathname n
         | Pathname n
-        | Dev_or_Path n -> pr "  char *%s;\n" n
+        | Dev_or_Path n
+        | FileIn 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
         | StringList n | DeviceList n -> pr "  char **%s;\n" n
         | Bool n -> pr "  int %s;\n" n
         | Int n -> pr "  int %s;\n" n
         | StringList n | DeviceList n -> pr "  char **%s;\n" n
         | Bool n -> pr "  int %s;\n" n
         | Int n -> pr "  int %s;\n" n
@@ -7264,7 +7998,10 @@ and generate_fish_cmds () =
       ) (snd style);
 
       (* Check and convert parameters. *)
       ) (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;
       pr "  if (argc != %d) {\n" argc_expected;
       pr "    fprintf (stderr, _(\"%%s should have %%d parameter(s)\\n\"), cmd, %d);\n"
         argc_expected;
@@ -7272,12 +8009,12 @@ and generate_fish_cmds () =
       pr "    return -1;\n";
       pr "  }\n";
 
       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 "  {\n";
         pr "    strtol_error xerr;\n";
         pr "    %s r;\n" fntyp;
         pr "\n";
-        pr "    xerr = %s (argv[%d], NULL, 0, &r, \"\");\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";
         pr "    if (xerr != LONGINT_OK) {\n";
         pr "      fprintf (stderr,\n";
         pr "               _(\"%%s: %%s: invalid integer parameter (%%s returned %%d)\\n\"),\n";
@@ -7299,71 +8036,104 @@ and generate_fish_cmds () =
         pr "  }\n";
       in
 
         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
-          | FileIn name ->
-              pr "  %s = STRNEQ (argv[%d], \"-\") ? argv[%d] : \"/dev/stdin\";\n"
-                name i i
-          | FileOut name ->
-              pr "  %s = STRNEQ (argv[%d], \"-\") ? argv[%d] : \"/dev/stdout\";\n"
-                name i i
-          | 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. *)
       ) (snd style);
 
       (* Call C API function. *)
-      let fn =
-        try find_map (function FishAction n -> Some n | _ -> None) flags
-        with Not_found -> sprintf "guestfs_%s" name in
-      pr "  r = %s " fn;
+      pr "  r = guestfs_%s " name;
       generate_c_call_args ~handle:"g" style;
       pr ";\n";
 
       List.iter (
         function
       generate_c_call_args ~handle:"g" style;
       pr ";\n";
 
       List.iter (
         function
-        | Device name | String name
-        | OptString name | FileIn name | FileOut name | Bool name
-        | Int name | Int64 name -> ()
-        | Pathname name | Dev_or_Path name ->
+        | Device _ | String _
+        | OptString _ | Bool _
+        | Int _ | Int64 _
+        | BufferIn _ -> ()
+        | Pathname name | Dev_or_Path name | FileOut name
+        | Key name ->
             pr "  free (%s);\n" name
             pr "  free (%s);\n" name
+        | FileIn name ->
+            pr "  free_file_in (%s);\n" name
         | StringList name | DeviceList name ->
             pr "  free_strings (%s);\n" name
       ) (snd style);
 
         | StringList name | DeviceList name ->
             pr "  free_strings (%s);\n" name
       ) (snd style);
 
+      (* Any output flags? *)
+      let fish_output =
+        let flags = filter_map (
+          function FishOutput flag -> Some flag | _ -> None
+        ) flags in
+        match flags with
+        | [] -> None
+        | [f] -> Some f
+        | _ ->
+            failwithf "%s: more than one FishOutput flag is not allowed" name in
+
       (* Check return value for errors and display command results. *)
       (match fst style with
        | RErr -> pr "  return r;\n"
        | RInt _ ->
            pr "  if (r == -1) return -1;\n";
       (* Check return value for errors and display command results. *)
       (match fst style with
        | RErr -> pr "  return r;\n"
        | RInt _ ->
            pr "  if (r == -1) return -1;\n";
-           pr "  printf (\"%%d\\n\", r);\n";
+           (match fish_output with
+            | None ->
+                pr "  printf (\"%%d\\n\", r);\n";
+            | Some FishOutputOctal ->
+                pr "  printf (\"%%s%%o\\n\", r != 0 ? \"0\" : \"\", r);\n";
+            | Some FishOutputHexadecimal ->
+                pr "  printf (\"%%s%%x\\n\", r != 0 ? \"0x\" : \"\", r);\n");
            pr "  return 0;\n"
        | RInt64 _ ->
            pr "  if (r == -1) return -1;\n";
            pr "  return 0;\n"
        | RInt64 _ ->
            pr "  if (r == -1) return -1;\n";
-           pr "  printf (\"%%\" PRIi64 \"\\n\", r);\n";
+           (match fish_output with
+            | None ->
+                pr "  printf (\"%%\" PRIi64 \"\\n\", r);\n";
+            | Some FishOutputOctal ->
+                pr "  printf (\"%%s%%\" PRIo64 \"\\n\", r != 0 ? \"0\" : \"\", r);\n";
+            | Some FishOutputHexadecimal ->
+                pr "  printf (\"%%s%%\" PRIx64 \"\\n\", r != 0 ? \"0x\" : \"\", r);\n");
            pr "  return 0;\n"
        | RBool _ ->
            pr "  if (r == -1) return -1;\n";
            pr "  return 0;\n"
        | RBool _ ->
            pr "  if (r == -1) return -1;\n";
@@ -7495,7 +8265,7 @@ static const char *const commands[] = {
 static char *
 generator (const char *text, int state)
 {
 static char *
 generator (const char *text, int state)
 {
-  static int index, len;
+  static size_t index, len;
   const char *name;
 
   if (!state) {
   const char *name;
 
   if (!state) {
@@ -7576,13 +8346,16 @@ and generate_fish_actions_pod () =
       pr " %s" name;
       List.iter (
         function
       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"
         | Int n -> pr " %s" n
         | Int64 n -> pr " %s" n
         | FileIn n | FileOut n -> pr " (%s|-)" n
         | OptString n -> pr " %s" n
         | StringList n | DeviceList n -> pr " '%s ...'" n
         | Bool _ -> pr " true|false"
         | Int n -> pr " %s" n
         | 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";
       ) (snd style);
       pr "\n";
       pr "\n";
@@ -7592,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";
 
                       | _ -> 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;
 
       if List.mem ProtocolLimitWarning flags then
         pr "%s\n\n" protocol_limit_warning;
 
@@ -7646,7 +8423,8 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
       | Pathname n
       | Device n | Dev_or_Path n
       | String n
       | 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 ->
           next ();
           pr "const char *%s" n
       | StringList n | DeviceList n ->
@@ -7658,6 +8436,11 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
       | FileIn n
       | FileOut n ->
           if not in_daemon then (next (); pr "const char *%s" n)
       | FileIn n
       | FileOut n ->
           if not in_daemon then (next (); pr "const char *%s" n)
+      | BufferIn n ->
+          next ();
+          pr "const char *%s" n;
+          next ();
+          pr "size_t %s_size" n
     ) (snd style);
     if is_RBufferOut then (next (); pr "size_t *size_r");
   );
     ) (snd style);
     if is_RBufferOut then (next (); pr "size_t *size_r");
   );
@@ -7678,9 +8461,13 @@ and generate_c_call_args ?handle ?(decl = false) style =
    | Some handle -> pr "%s" handle; comma := true
   );
   List.iter (
    | Some handle -> pr "%s" handle; comma := true
   );
   List.iter (
-    fun arg ->
-      next ();
-      pr "%s" (name_of_argt arg)
+    function
+    | BufferIn n ->
+        next ();
+        pr "%s, %s_size" n n
+    | arg ->
+        next ();
+        pr "%s" (name_of_argt arg)
   ) (snd style);
   (* For RBufferOut calls, add implicit &size parameter. *)
   if not decl then (
   ) (snd style);
   (* For RBufferOut calls, add implicit &size parameter. *)
   if not decl then (
@@ -7779,7 +8566,7 @@ and generate_ocaml_c () =
 #include <caml/mlvalues.h>
 #include <caml/signals.h>
 
 #include <caml/mlvalues.h>
 #include <caml/signals.h>
 
-#include <guestfs.h>
+#include \"guestfs.h\"
 
 #include \"guestfs_c.h\"
 
 
 #include \"guestfs_c.h\"
 
@@ -7792,7 +8579,7 @@ copy_table (char * const * argv)
 {
   CAMLparam0 ();
   CAMLlocal5 (rv, pairv, kv, vv, cons);
 {
   CAMLparam0 ();
   CAMLlocal5 (rv, pairv, kv, vv, cons);
-  int i;
+  size_t i;
 
   rv = Val_int (0);
   for (i = 0; argv[i] != NULL; i += 2) {
 
   rv = Val_int (0);
   for (i = 0; argv[i] != NULL; i += 2) {
@@ -7946,12 +8733,17 @@ copy_table (char * const * argv)
         | Device n | Dev_or_Path n
         | String n
         | FileIn n
         | 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 ->
         | 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 "  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 ->
         | StringList n | DeviceList n ->
             pr "  char **%s = ocaml_guestfs_strings_val (g, %sv);\n" n n
         | Bool n ->
@@ -7971,7 +8763,7 @@ copy_table (char * const * argv)
             pr "  const char *r;\n"; "NULL"
         | RString _ -> pr "  char *r;\n"; "NULL"
         | RStringList _ ->
             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) ->
             pr "  char **r;\n";
             "NULL"
         | RStruct (_, typ) ->
@@ -7979,7 +8771,7 @@ copy_table (char * const * argv)
         | RStructList (_, typ) ->
             pr "  struct guestfs_%s_list *r;\n" typ; "NULL"
         | RHashtable _ ->
         | 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 _ ->
             pr "  char **r;\n";
             "NULL"
         | RBufferOut _ ->
@@ -7994,13 +8786,15 @@ copy_table (char * const * argv)
       pr ";\n";
       pr "  caml_leave_blocking_section ();\n";
 
       pr ";\n";
       pr "  caml_leave_blocking_section ();\n";
 
+      (* Free strings if we copied them above. *)
       List.iter (
         function
       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;
         | StringList n | DeviceList n ->
             pr "  ocaml_guestfs_free_strings (%s);\n" n;
-        | Pathname _ | Device _ | Dev_or_Path _ | String _ | OptString _
-        | Bool _ | Int _ | Int64 _
-        | FileIn _ | FileOut _ -> ()
+        | Bool _ | Int _ | Int64 _ -> ()
       ) (snd style);
 
       pr "  if (r == %s)\n" error_code;
       ) (snd style);
 
       pr "  if (r == %s)\n" error_code;
@@ -8086,7 +8880,8 @@ and generate_ocaml_prototype ?(is_external = false) name style =
   pr "%s : t -> " name;
   List.iter (
     function
   pr "%s : t -> " name;
   List.iter (
     function
-    | Pathname _ | Device _ | Dev_or_Path _ | String _ | FileIn _ | FileOut _ -> pr "string -> "
+    | Pathname _ | Device _ | Dev_or_Path _ | String _ | FileIn _ | FileOut _
+    | BufferIn _ | Key _ -> pr "string -> "
     | OptString _ -> pr "string option -> "
     | StringList _ | DeviceList _ -> pr "string array -> "
     | Bool _ -> pr "bool -> "
     | OptString _ -> pr "string option -> "
     | StringList _ | DeviceList _ -> pr "string array -> "
     | Bool _ -> pr "bool -> "
@@ -8201,10 +8996,30 @@ _create ()
       RETVAL
 
 void
       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);
       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);
 
 ";
 
 
 ";
 
@@ -8225,15 +9040,21 @@ DESTROY (g)
            pr "void\n" (* all lists returned implictly on the stack *)
       );
       (* Call and arguments. *)
            pr "void\n" (* all lists returned implictly on the stack *)
       );
       (* Call and arguments. *)
-      pr "%s " name;
-      generate_c_call_args ~handle:"g" ~decl:true style;
-      pr "\n";
+      pr "%s (g" name;
+      List.iter (
+        fun arg -> pr ", %s" (name_of_argt arg)
+      ) (snd style);
+      pr ")\n";
       pr "      guestfs_h *g;\n";
       iteri (
         fun i ->
           function
       pr "      guestfs_h *g;\n";
       iteri (
         fun i ->
           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
+          | FileIn n | FileOut n | Key n ->
               pr "      char *%s;\n" n
               pr "      char *%s;\n" n
+          | BufferIn n ->
+              pr "      char *%s;\n" n;
+              pr "      size_t %s_size = SvCUR (ST(%d));\n" n (i+1)
           | OptString n ->
               (* http://www.perlmonks.org/?node_id=554277
                * Note that the implicit handle argument means we have
           | OptString n ->
               (* http://www.perlmonks.org/?node_id=554277
                * Note that the implicit handle argument means we have
@@ -8251,7 +9072,8 @@ DESTROY (g)
           function
           | Pathname _ | Device _ | Dev_or_Path _ | String _ | OptString _
           | Bool _ | Int _ | Int64 _
           function
           | Pathname _ | Device _ | Dev_or_Path _ | String _ | OptString _
           | Bool _ | Int _ | Int64 _
-          | FileIn _ | FileOut _ -> ()
+          | FileIn _ | FileOut _
+          | BufferIn _ | Key _ -> ()
           | StringList n | DeviceList n -> pr "      free (%s);\n" n
         ) (snd style)
       in
           | StringList n | DeviceList n -> pr "      free (%s);\n" n
         ) (snd style)
       in
@@ -8339,7 +9161,7 @@ DESTROY (g)
        | RStringList n | RHashtable n ->
            pr "PREINIT:\n";
            pr "      char **%s;\n" n;
        | 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;
            pr " PPCODE:\n";
            pr "      %s = guestfs_%s " n name;
            generate_c_call_args ~handle:"g" style;
@@ -8383,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;
 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;
   pr "      HV *hv;\n";
   pr " PPCODE:\n";
   pr "      %s = guestfs_%s " n name;
@@ -8533,6 +9355,12 @@ package Sys::Guestfs;
 use strict;
 use warnings;
 
 use strict;
 use warnings;
 
+# This version number changes whenever a new function
+# is added to the libguestfs API.  It is not directly
+# related to the libguestfs version number.
+use vars qw($VERSION);
+$VERSION = '0.%d';
+
 require XSLoader;
 XSLoader::load ('Sys::Guestfs');
 
 require XSLoader;
 XSLoader::load ('Sys::Guestfs');
 
@@ -8546,12 +9374,29 @@ sub new {
   my $proto = shift;
   my $class = ref ($proto) || $proto;
 
   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;
 }
 
   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
    * they are pulled in from the XS code automatically.
 
   (* Actions.  We only need to print documentation for these as
    * they are pulled in from the XS code automatically.
@@ -8623,7 +9468,8 @@ and generate_perl_prototype name style =
       comma := true;
       match arg with
       | Pathname n | Device n | Dev_or_Path n | String n
       comma := true;
       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 ->
+      | OptString n | Bool n | Int n | Int64 n | FileIn n | FileOut n
+      | BufferIn n | Key n ->
           pr "$%s" n
       | StringList n | DeviceList n ->
           pr "\\@%s" n
           pr "$%s" n
       | StringList n | DeviceList n ->
           pr "\\@%s" n
@@ -8635,40 +9481,57 @@ and generate_python_c () =
   generate_header CStyle LGPLv2plus;
 
   pr "\
   generate_header CStyle LGPLv2plus;
 
   pr "\
+#define PY_SSIZE_T_CLEAN 1
 #include <Python.h>
 
 #include <Python.h>
 
+#if PY_VERSION_HEX < 0x02050000
+typedef int Py_ssize_t;
+#define PY_SSIZE_T_MAX INT_MAX
+#define PY_SSIZE_T_MIN INT_MIN
+#endif
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <assert.h>
 
 #include \"guestfs.h\"
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <assert.h>
 
 #include \"guestfs.h\"
 
+#ifndef HAVE_PYCAPSULE_NEW
 typedef struct {
   PyObject_HEAD
   guestfs_h *g;
 } Pyguestfs_Object;
 typedef struct {
   PyObject_HEAD
   guestfs_h *g;
 } Pyguestfs_Object;
+#endif
 
 static guestfs_h *
 get_handle (PyObject *obj)
 {
   assert (obj);
   assert (obj != Py_None);
 
 static guestfs_h *
 get_handle (PyObject *obj)
 {
   assert (obj);
   assert (obj != Py_None);
+#ifndef HAVE_PYCAPSULE_NEW
   return ((Pyguestfs_Object *) obj)->g;
   return ((Pyguestfs_Object *) obj)->g;
+#else
+  return (guestfs_h*) PyCapsule_GetPointer(obj, \"guestfs_h\");
+#endif
 }
 
 static PyObject *
 put_handle (guestfs_h *g)
 {
   assert (g);
 }
 
 static PyObject *
 put_handle (guestfs_h *g)
 {
   assert (g);
+#ifndef HAVE_PYCAPSULE_NEW
   return
     PyCObject_FromVoidPtrAndDesc ((void *) g, (char *) \"guestfs_h\", NULL);
   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)
 {
 }
 
 /* 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);
   char **r;
 
   assert (obj);
@@ -8678,7 +9541,12 @@ get_string_list (PyObject *obj)
     return NULL;
   }
 
     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\");
   r = malloc (sizeof (char *) * (len+1));
   if (r == NULL) {
     PyErr_SetString (PyExc_RuntimeError, \"get_string_list: out of memory\");
@@ -8750,6 +9618,9 @@ py_guestfs_create (PyObject *self, PyObject *args)
     return NULL;
   }
   guestfs_set_error_handler (g, NULL, NULL);
     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);
 }
 
   return put_handle (g);
 }
 
@@ -8776,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 "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;
     pr "\n";
     pr "  list = PyList_New (%ss->len);\n" typ;
     pr "  for (i = 0; i < %ss->len; ++i)\n" typ;
@@ -8882,9 +9753,13 @@ py_guestfs_close (PyObject *self, PyObject *args)
 
       List.iter (
         function
 
       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 "  const char *%s;\n" n
         | OptString n -> pr "  const char *%s;\n" n
             pr "  const char *%s;\n" n
         | OptString n -> pr "  const char *%s;\n" n
+        | BufferIn n ->
+            pr "  const char *%s;\n" n;
+            pr "  Py_ssize_t %s_size;\n" n
         | StringList n | DeviceList n ->
             pr "  PyObject *py_%s;\n" n;
             pr "  char **%s;\n" n
         | StringList n | DeviceList n ->
             pr "  PyObject *py_%s;\n" n;
             pr "  char **%s;\n" n
@@ -8899,7 +9774,8 @@ py_guestfs_close (PyObject *self, PyObject *args)
       pr "  if (!PyArg_ParseTuple (args, (char *) \"O";
       List.iter (
         function
       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? *)
         | OptString _ -> pr "z"
         | StringList _ | DeviceList _ -> pr "O"
         | Bool _ -> pr "i" (* XXX Python has booleans? *)
@@ -8907,17 +9783,20 @@ py_guestfs_close (PyObject *self, PyObject *args)
         | Int64 _ -> pr "L" (* XXX Whoever thought it was a good idea to
                              * emulate C's int/long/long long in Python?
                              *)
         | Int64 _ -> pr "L" (* XXX Whoever thought it was a good idea to
                              * emulate C's int/long/long long in Python?
                              *)
+        | BufferIn _ -> pr "s#"
       ) (snd style);
       pr ":guestfs_%s\",\n" name;
       pr "                         &py_g";
       List.iter (
         function
       ) (snd style);
       pr ":guestfs_%s\",\n" name;
       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
         | Int n -> pr ", &%s" n
         | Int64 n -> pr ", &%s" n
         | OptString n -> pr ", &%s" n
         | StringList n | DeviceList n -> pr ", &py_%s" n
         | Bool n -> pr ", &%s" n
         | Int n -> pr ", &%s" n
         | Int64 n -> pr ", &%s" n
+        | BufferIn n -> pr ", &%s, &%s_size" n n
       ) (snd style);
 
       pr "))\n";
       ) (snd style);
 
       pr "))\n";
@@ -8926,8 +9805,9 @@ py_guestfs_close (PyObject *self, PyObject *args)
       pr "  g = get_handle (py_g);\n";
       List.iter (
         function
       pr "  g = get_handle (py_g);\n";
       List.iter (
         function
-        | Pathname _ | Device _ | Dev_or_Path _ | String _
-        | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _ -> ()
+        | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _
+        | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _
+        | BufferIn _ -> ()
         | StringList n | DeviceList n ->
             pr "  %s = get_string_list (py_%s);\n" n n;
             pr "  if (!%s) return NULL;\n" n
         | StringList n | DeviceList n ->
             pr "  %s = get_string_list (py_%s);\n" n n;
             pr "  if (!%s) return NULL;\n" n
@@ -8941,8 +9821,9 @@ py_guestfs_close (PyObject *self, PyObject *args)
 
       List.iter (
         function
 
       List.iter (
         function
-        | Pathname _ | Device _ | Dev_or_Path _ | String _
-        | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _ -> ()
+        | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _
+        | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _
+        | BufferIn _ -> ()
         | StringList n | DeviceList n ->
             pr "  free (%s);\n" n
       ) (snd style);
         | StringList n | DeviceList n ->
             pr "  free (%s);\n" n
       ) (snd style);
@@ -9247,19 +10128,27 @@ static VALUE ruby_guestfs_close (VALUE gv)
 
       List.iter (
         function
 
       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;
             pr "    rb_raise (rb_eTypeError, \"expected string for parameter %%s of %%s\",\n";
             pr "              \"%s\", \"%s\");\n" n name
             pr "  Check_Type (%sv, T_STRING);\n" n;
             pr "  const char *%s = StringValueCStr (%sv);\n" n n;
             pr "  if (!%s)\n" n;
             pr "    rb_raise (rb_eTypeError, \"expected string for parameter %%s of %%s\",\n";
             pr "              \"%s\", \"%s\");\n" n name
+        | BufferIn n ->
+            pr "  Check_Type (%sv, T_STRING);\n" n;
+            pr "  const char *%s = RSTRING (%sv)->ptr;\n" n n;
+            pr "  if (!%s)\n" n;
+            pr "    rb_raise (rb_eTypeError, \"expected string for parameter %%s of %%s\",\n";
+            pr "              \"%s\", \"%s\");\n" n name;
+            pr "  size_t %s_size = RSTRING (%sv)->len;\n" n n
         | OptString n ->
             pr "  const char *%s = !NIL_P (%sv) ? StringValueCStr (%sv) : NULL;\n" n n n
         | StringList n | DeviceList n ->
             pr "  char **%s;\n" n;
             pr "  Check_Type (%sv, T_ARRAY);\n" n;
             pr "  {\n";
         | OptString n ->
             pr "  const char *%s = !NIL_P (%sv) ? StringValueCStr (%sv) : NULL;\n" n n n
         | StringList n | DeviceList n ->
             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;
             pr "    len = RARRAY_LEN (%sv);\n" n;
             pr "    %s = guestfs_safe_malloc (g, sizeof (char *) * (len+1));\n"
               n;
@@ -9301,8 +10190,9 @@ static VALUE ruby_guestfs_close (VALUE gv)
 
       List.iter (
         function
 
       List.iter (
         function
-        | Pathname _ | Device _ | Dev_or_Path _ | String _
-        | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _ -> ()
+        | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _
+        | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _
+        | BufferIn _ -> ()
         | StringList n | DeviceList n ->
             pr "  free (%s);\n" n
       ) (snd style);
         | StringList n | DeviceList n ->
             pr "  free (%s);\n" n
       ) (snd style);
@@ -9330,7 +10220,7 @@ static VALUE ruby_guestfs_close (VALUE gv)
            pr "  free (r);\n";
            pr "  return rv;\n";
        | RStringList _ ->
            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";
            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";
@@ -9347,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";
            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";
            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";
@@ -9416,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";
 (* 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 (
   pr "  for (i = 0; i < r->len; ++i) {\n";
   pr "    VALUE hv = rb_hash_new ();\n";
   List.iter (
@@ -9617,8 +10507,11 @@ and generate_java_prototype ?(public=false) ?(privat=false) ?(native=false)
       | String n
       | OptString n
       | FileIn n
       | String n
       | OptString n
       | FileIn n
-      | FileOut n ->
+      | FileOut n
+      | Key n ->
           pr "String %s" n
           pr "String %s" n
+      | BufferIn n ->
+          pr "byte[] %s" n
       | StringList n | DeviceList n ->
           pr "String[] %s" n
       | Bool n ->
       | StringList n | DeviceList n ->
           pr "String[] %s" n
       | Bool n ->
@@ -9738,8 +10631,11 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
         | String n
         | OptString n
         | FileIn n
         | String n
         | OptString n
         | FileIn n
-        | FileOut n ->
+        | FileOut n
+        | Key n ->
             pr ", jstring j%s" n
             pr ", jstring j%s" n
+        | BufferIn n ->
+            pr ", jbyteArray j%s" n
         | StringList n | DeviceList n ->
             pr ", jobjectArray j%s" n
         | Bool n ->
         | StringList n | DeviceList n ->
             pr ", jobjectArray j%s" n
         | Bool n ->
@@ -9793,8 +10689,12 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
         | String n
         | OptString n
         | FileIn n
         | String n
         | OptString n
         | FileIn n
-        | FileOut n ->
+        | FileOut n
+        | Key n ->
             pr "  const char *%s;\n" n
             pr "  const char *%s;\n" n
+        | BufferIn n ->
+            pr "  jbyte *%s;\n" n;
+            pr "  size_t %s_size;\n" n
         | StringList n | DeviceList n ->
             pr "  int %s_len;\n" n;
             pr "  const char **%s;\n" n
         | StringList n | DeviceList n ->
             pr "  int %s_len;\n" n;
             pr "  const char **%s;\n" n
@@ -9816,7 +10716,7 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
                        | DeviceList _ -> true
                        | _ -> false) (snd style) in
       if needs_i then
                        | DeviceList _ -> true
                        | _ -> false) (snd style) in
       if needs_i then
-        pr "  int i;\n";
+        pr "  size_t i;\n";
 
       pr "\n";
 
 
       pr "\n";
 
@@ -9827,13 +10727,17 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
         | Device n | Dev_or_Path n
         | String n
         | FileIn n
         | 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
              * a NULL parameter.
              *)
             pr "  %s = j%s ? (*env)->GetStringUTFChars (env, j%s, NULL) : NULL;\n" n n n
             pr "  %s = (*env)->GetStringUTFChars (env, j%s, NULL);\n" n n
         | OptString n ->
             (* This is completely undocumented, but Java null becomes
              * a NULL parameter.
              *)
             pr "  %s = j%s ? (*env)->GetStringUTFChars (env, j%s, NULL) : NULL;\n" n n n
+        | BufferIn n ->
+            pr "  %s = (*env)->GetByteArrayElements (env, j%s, NULL);\n" n n;
+            pr "  %s_size = (*env)->GetArrayLength (env, j%s);\n" n n
         | StringList n | DeviceList n ->
             pr "  %s_len = (*env)->GetArrayLength (env, j%s);\n" n n;
             pr "  %s = guestfs_safe_malloc (g, sizeof (char *) * (%s_len+1));\n" n n;
         | StringList n | DeviceList n ->
             pr "  %s_len = (*env)->GetArrayLength (env, j%s);\n" n n;
             pr "  %s = guestfs_safe_malloc (g, sizeof (char *) * (%s_len+1));\n" n n;
@@ -9861,11 +10765,14 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
         | Device n | Dev_or_Path n
         | String n
         | FileIn n
         | 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;
             pr "    (*env)->ReleaseStringUTFChars (env, j%s, %s);\n" n n
             pr "  (*env)->ReleaseStringUTFChars (env, j%s, %s);\n" n n
         | OptString n ->
             pr "  if (j%s)\n" n;
             pr "    (*env)->ReleaseStringUTFChars (env, j%s, %s);\n" n n
+        | BufferIn n ->
+            pr "  (*env)->ReleaseByteArrayElements (env, j%s, %s, 0);\n" n n
         | StringList n | DeviceList n ->
             pr "  for (i = 0; i < %s_len; ++i) {\n" n;
             pr "    jobject o = (*env)->GetObjectArrayElement (env, j%s, i);\n"
         | StringList n | DeviceList n ->
             pr "  for (i = 0; i < %s_len; ++i) {\n" n;
             pr "    jobject o = (*env)->GetObjectArrayElement (env, j%s, i);\n"
@@ -10140,7 +11047,10 @@ last_error h = do
           function
           | FileIn n
           | FileOut n
           function
           | FileIn n
           | FileOut n
-          | Pathname n | Device n | Dev_or_Path n | String n -> pr "withCString %s $ \\%s -> " n 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
           | OptString n -> pr "maybeWith withCString %s $ \\%s -> " n n
           | StringList n | DeviceList n -> pr "withMany withCString %s $ \\%s -> withArray0 nullPtr %s $ \\%s -> " n n n n
           | Bool _ | Int _ | Int64 _ -> ()
           | OptString n -> pr "maybeWith withCString %s $ \\%s -> " n n
           | StringList n | DeviceList n -> pr "withMany withCString %s $ \\%s -> withArray0 nullPtr %s $ \\%s -> " n n n n
           | Bool _ | Int _ | Int64 _ -> ()
@@ -10153,7 +11063,11 @@ last_error h = do
             | Int n -> sprintf "(fromIntegral %s)" n
             | Int64 n -> sprintf "(fromIntegral %s)" n
             | FileIn n | FileOut n
             | 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
           (String.concat " " ("p" :: args));
           ) (snd style) in
         pr "withForeignPtr h (\\p -> c_%s %s)\n" name
           (String.concat " " ("p" :: args));
@@ -10203,7 +11117,11 @@ and generate_haskell_prototype ~handle ?(hs = false) style =
   List.iter (
     fun arg ->
       (match arg with
   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"
        | OptString _ -> if hs then pr "Maybe String" else pr "CString"
        | StringList _ | DeviceList _ -> if hs then pr "[String]" else pr "Ptr CString"
        | Bool _ -> pr "%s" bool
        | OptString _ -> if hs then pr "Maybe String" else pr "CString"
        | StringList _ | DeviceList _ -> if hs then pr "[String]" else pr "Ptr CString"
        | Bool _ -> pr "%s" bool
@@ -10393,7 +11311,9 @@ namespace Guestfs
         List.iter (
           function
           | Pathname n | Device n | Dev_or_Path n | String n | OptString n
         List.iter (
           function
           | Pathname n | Device n | Dev_or_Path n | String n | OptString n
-          | FileIn n | FileOut n ->
+          | FileIn n | FileOut n
+          | Key n
+          | BufferIn n ->
               pr ", [In] string %s" n
           | StringList n | DeviceList n ->
               pr ", [In] string[] %s" n
               pr ", [In] string %s" n
           | StringList n | DeviceList n ->
               pr ", [In] string[] %s" n
@@ -10416,7 +11336,9 @@ namespace Guestfs
         List.iter (
           function
           | Pathname n | Device n | Dev_or_Path n | String n | OptString n
         List.iter (
           function
           | Pathname n | Device n | Dev_or_Path n | String n | OptString n
-          | FileIn n | FileOut n ->
+          | FileIn n | FileOut n
+          | Key n
+          | BufferIn n ->
               next (); pr "string %s" n
           | StringList n | DeviceList n ->
               next (); pr "string[] %s" n
               next (); pr "string %s" n
           | StringList n | DeviceList n ->
               next (); pr "string[] %s" n
@@ -10454,7 +11376,7 @@ namespace Guestfs
            pr "      return r != 0 ? true : false;\n"
        | RHashtable _ ->
            pr "      Hashtable rr = new Hashtable ();\n";
            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 _
            pr "        rr.Add (r[i], r[i+1]);\n";
            pr "      return rr;\n"
        | RInt _ | RInt64 _ | RConstString _ | RConstOptString _
@@ -10491,7 +11413,7 @@ and generate_bindtests () =
 static void
 print_strings (char *const *argv)
 {
 static void
 print_strings (char *const *argv)
 {
-  int argc;
+  size_t argc;
 
   printf (\"[\");
   for (argc = 0; argv[argc] != NULL; ++argc) {
 
   printf (\"[\");
   for (argc = 0; argv[argc] != NULL; ++argc) {
@@ -10520,7 +11442,15 @@ print_strings (char *const *argv)
       | Device n | Dev_or_Path n
       | String n
       | FileIn n
       | 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";
+          pr "    for (i = 0; i < %s_size; ++i)\n" n;
+          pr "      printf (\"<%%02x>\", %s[i]);\n" n;
+          pr "    printf (\"\\n\");\n";
+         pr "  }\n";
       | OptString n -> pr "  printf (\"%%s\\n\", %s ? %s : \"null\");\n" n n
       | StringList n | DeviceList n -> pr "  print_strings (%s);\n" n
       | Bool n -> pr "  printf (\"%%s\\n\", %s ? \"true\" : \"false\");\n" n
       | OptString n -> pr "  printf (\"%%s\\n\", %s ? %s : \"null\");\n" n n
       | StringList n | DeviceList n -> pr "  print_strings (%s);\n" n
       | Bool n -> pr "  printf (\"%%s\\n\", %s ? \"true\" : \"false\");\n" n
@@ -10644,6 +11574,7 @@ let () =
         | CallInt64 i when i >= 0L -> Int64.to_string i ^ "L"
         | CallInt64 i (* when i < 0L *) -> "(" ^ Int64.to_string i ^ "L)"
         | CallBool b -> string_of_bool b
         | CallInt64 i when i >= 0L -> Int64.to_string i ^ "L"
         | CallInt64 i (* when i < 0L *) -> "(" ^ Int64.to_string i ^ "L)"
         | CallBool b -> string_of_bool b
+        | CallBuffer s -> sprintf "%S" s
       ) args
     )
   in
       ) args
     )
   in
@@ -10678,6 +11609,7 @@ my $g = Sys::Guestfs->new ();
         | CallInt i -> string_of_int i
         | CallInt64 i -> Int64.to_string i
         | CallBool b -> if b then "1" else "0"
         | CallInt i -> string_of_int i
         | CallInt64 i -> Int64.to_string i
         | CallBool b -> if b then "1" else "0"
+        | CallBuffer s -> "\"" ^ c_quote s ^ "\""
       ) args
     )
   in
       ) args
     )
   in
@@ -10709,6 +11641,7 @@ g = guestfs.GuestFS ()
         | CallInt i -> string_of_int i
         | CallInt64 i -> Int64.to_string i
         | CallBool b -> if b then "1" else "0"
         | CallInt i -> string_of_int i
         | CallInt64 i -> Int64.to_string i
         | CallBool b -> if b then "1" else "0"
+        | CallBuffer s -> "\"" ^ c_quote s ^ "\""
       ) args
     )
   in
       ) args
     )
   in
@@ -10740,6 +11673,7 @@ g = Guestfs::create()
         | CallInt i -> string_of_int i
         | CallInt64 i -> Int64.to_string i
         | CallBool b -> string_of_bool b
         | CallInt i -> string_of_int i
         | CallInt64 i -> Int64.to_string i
         | CallBool b -> string_of_bool b
+        | CallBuffer s -> "\"" ^ c_quote s ^ "\""
       ) args
     )
   in
       ) args
     )
   in
@@ -10776,6 +11710,10 @@ public class Bindtests {
         | CallInt i -> string_of_int i
         | CallInt64 i -> Int64.to_string i
         | CallBool b -> string_of_bool b
         | CallInt i -> string_of_int i
         | CallInt64 i -> Int64.to_string i
         | CallBool b -> string_of_bool b
+        | CallBuffer s ->
+            "new byte[] { " ^ String.concat "," (
+              map_chars (fun c -> string_of_int (Char.code c)) s
+            ) ^ " }"
       ) args
     )
   in
       ) args
     )
   in
@@ -10821,6 +11759,7 @@ main = do
         | CallInt64 i -> Int64.to_string i
         | CallBool true -> "True"
         | CallBool false -> "False"
         | CallInt64 i -> Int64.to_string i
         | CallBool true -> "True"
         | CallBool false -> "False"
+        | CallBuffer s -> "\"" ^ c_quote s ^ "\""
       ) args
     )
   in
       ) args
     )
   in
@@ -10837,43 +11776,56 @@ main = do
 and generate_lang_bindtests call =
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList []; CallBool false;
 and generate_lang_bindtests call =
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList []; CallBool false;
-                CallInt 0; CallInt64 0L; CallString "123"; CallString "456"];
+                CallInt 0; CallInt64 0L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString None;
                 CallStringList []; CallBool false;
   call "test0" [CallString "abc"; CallOptString None;
                 CallStringList []; CallBool false;
-                CallInt 0; CallInt64 0L; CallString "123"; CallString "456"];
+                CallInt 0; CallInt64 0L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString ""; CallOptString (Some "def");
                 CallStringList []; CallBool false;
   call "test0" [CallString ""; CallOptString (Some "def");
                 CallStringList []; CallBool false;
-                CallInt 0; CallInt64 0L; CallString "123"; CallString "456"];
+                CallInt 0; CallInt64 0L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString ""; CallOptString (Some "");
                 CallStringList []; CallBool false;
   call "test0" [CallString ""; CallOptString (Some "");
                 CallStringList []; CallBool false;
-                CallInt 0; CallInt64 0L; CallString "123"; CallString "456"];
+                CallInt 0; CallInt64 0L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
-                CallInt 0; CallInt64 0L; CallString "123"; CallString "456"];
+                CallInt 0; CallInt64 0L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"; "2"]; CallBool false;
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"; "2"]; CallBool false;
-                CallInt 0; CallInt64 0L; CallString "123"; CallString "456"];
+                CallInt 0; CallInt64 0L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool true;
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool true;
-                CallInt 0; CallInt64 0L; CallString "123"; CallString "456"];
+                CallInt 0; CallInt64 0L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
-                CallInt (-1); CallInt64 (-1L); CallString "123"; CallString "456"];
+                CallInt (-1); CallInt64 (-1L); CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
-                CallInt (-2); CallInt64 (-2L); CallString "123"; CallString "456"];
+                CallInt (-2); CallInt64 (-2L); CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
-                CallInt 1; CallInt64 1L; CallString "123"; CallString "456"];
+                CallInt 1; CallInt64 1L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
-                CallInt 2; CallInt64 2L; CallString "123"; CallString "456"];
+                CallInt 2; CallInt64 2L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
-                CallInt 4095; CallInt64 4095L; CallString "123"; CallString "456"];
+                CallInt 4095; CallInt64 4095L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
-                CallInt 0; CallInt64 0L; CallString ""; CallString ""]
+                CallInt 0; CallInt64 0L; CallString ""; CallString "";
+                CallBuffer "abc\000abc"]
 
 (* XXX Add here tests of the return and error functions. *)
 
 
 (* XXX Add here tests of the return and error functions. *)
 
@@ -11365,17 +12317,7 @@ let inspect ?connect ?xml names =
   parse_operatingsystems xml
 "
 
   parse_operatingsystems xml
 "
 
-(* This is used to generate the src/MAX_PROC_NR file which
- * contains the maximum procedure number, a surrogate for the
- * ABI version number.  See src/Makefile.am for the details.
- *)
 and generate_max_proc_nr () =
 and generate_max_proc_nr () =
-  let proc_nrs = List.map (
-    fun (_, _, proc_nr, _, _, _, _) -> proc_nr
-  ) daemon_functions in
-
-  let max_proc_nr = List.fold_left max 0 proc_nrs in
-
   pr "%d\n" max_proc_nr
 
 let output_to filename k =
   pr "%d\n" max_proc_nr
 
 let output_to filename k =
@@ -11434,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-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;
   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;