New API: pvresize-size to allow shrinking PVs (RHBZ#585222).
[libguestfs.git] / src / generator.ml
index 4f569ba..1032a1a 100755 (executable)
@@ -42,6 +42,7 @@
 #load "unix.cma";;
 #load "str.cma";;
 #directory "+xml-light";;
+#directory "+../pkg-lib/xml-light";; (* for GODI users *)
 #load "xml-light.cma";;
 
 open Unix
@@ -163,9 +164,8 @@ and argt =
      *)
   | FileIn of string
   | FileOut of string
-(* Not implemented:
     (* 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.
@@ -174,18 +174,21 @@ and argt =
      * To return an arbitrary buffer, use RBufferOut.
      *)
   | BufferIn 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 *)
-  | 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 *)
 
+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,
@@ -379,6 +382,7 @@ let test_all_args = [
   Int64 "integer64";
   FileIn "filein";
   FileOut "fileout";
+  BufferIn "bufferin";
 ]
 
 let test_all_rets = [
@@ -441,7 +445,7 @@ You probably don't want to call this function.")]
  *)
 
 let non_daemon_functions = test_functions @ [
-  ("launch", (RErr, []), -1, [FishAlias "run"; FishAction "launch"],
+  ("launch", (RErr, []), -1, [FishAlias "run"],
    [],
    "launch the qemu subprocess",
    "\
@@ -544,13 +548,15 @@ handle is closed.  We don't currently have any method to enable
 changes to be committed, although qemu can support this.
 
 This is equivalent to the qemu parameter
-C<-drive file=filename,snapshot=on,if=...>.
+C<-drive file=filename,snapshot=on,readonly=on,if=...>.
 
 C<if=...> is set at compile time by the configuration option
 C<./configure --with-drive-if=...>.  In the rare case where you
 might need to change this at run time, use C<guestfs_add_drive_with_if>
 or C<guestfs_add_drive_ro_with_if>.
 
+C<readonly=on> is only added where qemu supports this option.
+
 Note that this call checks for the existence of C<filename>.  This
 stops you from specifying other types of drive which are supported
 by qemu such as C<nbd:> and C<http:> URLs.  To specify those, use
@@ -569,7 +575,7 @@ The first character of C<param> string must be a C<-> (dash).
 
 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",
    "\
@@ -601,7 +607,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.");
 
-  ("set_path", (RErr, [String "searchpath"]), -1, [FishAlias "path"],
+  ("set_path", (RErr, [OptString "searchpath"]), -1, [FishAlias "path"],
    [],
    "set the search path",
    "\
@@ -932,7 +938,7 @@ let daemon_functions = [
       [["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",
    "\
@@ -950,8 +956,15 @@ exist.
 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"]]],
@@ -1235,7 +1248,12 @@ matches exactly one node, the C<value> is returned.");
    [], (* 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. *)
@@ -1377,7 +1395,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
-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 *)
@@ -1470,9 +1490,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"])],
-   "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, [],
@@ -1480,7 +1500,7 @@ on the volume group C<volgroup>, with C<size> megabytes.");
       [["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",
    "\
@@ -1517,25 +1537,8 @@ the string C<,> (comma).
 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"],
+   [],
    "create a file",
    "\
 This call creates a file called C<path>.  The contents of the
@@ -1547,9 +1550,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
-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 (
@@ -1969,7 +1970,10 @@ See also C<guestfs_upload>, C<guestfs_cat>.");
     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
@@ -2011,9 +2015,13 @@ Compute the SHA512 hash (using the C<sha512sum> program).
 
 =back
 
-The checksum is returned as a printable string.");
+The checksum is returned as a printable string.
+
+To get the checksum for a device, use C<guestfs_checksum_device>.
 
-  ("tar_in", (RErr, [FileIn "tarfile"; String "directory"]), 69, [],
+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")],
@@ -2022,7 +2030,8 @@ The checksum is returned as a printable string.");
 This command uploads and unpacks local file C<tarfile> (an
 I<uncompressed> tar file) into C<directory>.
 
-To upload a compressed tarball, use C<guestfs_tgz_in>.");
+To upload a compressed tarball, use C<guestfs_tgz_in>
+or C<guestfs_txz_in>.");
 
   ("tar_out", (RErr, [String "directory"; FileOut "tarfile"]), 70, [],
    [],
@@ -2031,9 +2040,10 @@ To upload a compressed tarball, use C<guestfs_tgz_in>.");
 This command packs the contents of C<directory> and downloads
 it to local file C<tarfile>.
 
-To download a compressed tarball, use C<guestfs_tgz_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")],
@@ -2059,7 +2069,7 @@ To download an uncompressed tarball, use C<guestfs_tar_out>.");
        ["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")],
@@ -2074,7 +2084,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
-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, [],
    [],
@@ -2240,7 +2254,7 @@ to return the existing UUID of a filesystem.");
 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);
@@ -2291,7 +2305,7 @@ How many blocks are zeroed isn't specified (but it's I<not> enough
 to securely wipe the device).  It should be sufficient to remove
 any partition tables, filesystem superblocks and so on.
 
-See also: C<guestfs_scrub_device>.");
+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.
@@ -2307,15 +2321,15 @@ C<device>, with the root directory being C<root>.");
 
   ("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 (
-      [["write_file"; "/old"; "file content"; "0"];
+      [["write"; "/old"; "file content"];
        ["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")],
@@ -2328,7 +2342,7 @@ either a destination filename or destination directory.");
    [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",
@@ -2338,11 +2352,11 @@ recursively using the C<cp -a> command.");
 
   ("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 (
-      [["write_file"; "/old"; "file content"; "0"];
+      [["write"; "/old"; "file content"];
        ["mv"; "/old"; "/new"];
        ["is_file"; "/old"]])],
    "move a file",
@@ -2391,12 +2405,12 @@ or attached block device(s) in any other way.");
 
   ("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 (
-      [["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"]])],
@@ -2411,7 +2425,10 @@ The external L<cmp(1)> program is used for the comparison.");
    [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
@@ -2420,18 +2437,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"]], []);
-    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
-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.
+
+=item l (lower case letter L)
 
-See the L<strings(1)> manpage for the full list of encodings.
+16-bit little endian such as UTF-16LE and UCS-2LE.
+This is useful for examining binaries in Windows guests.
 
-Commonly useful encodings are C<l> (lower case L) which will
-show strings inside Windows/x86 files.
+=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.");
 
@@ -2442,7 +2488,10 @@ The returned strings are transcoded to UTF-8.");
      * 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
@@ -2453,7 +2502,7 @@ the human-readable, canonical hex dump of the file.");
       [["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"; "/"];
@@ -2558,13 +2607,20 @@ are activated or deactivated.");
        ["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"; "/"];
-       ["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
@@ -2738,7 +2794,7 @@ manual page for more details.");
 
   ("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",
    "\
@@ -2790,7 +2846,10 @@ See also: L<mkdtemp(3)>");
 
   ("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
@@ -2814,7 +2873,10 @@ C<wc -c> external command.");
 
   ("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
@@ -2970,7 +3032,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
-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 (
@@ -2980,7 +3052,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
-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 (
@@ -2990,7 +3064,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>.
-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 (
@@ -3000,12 +3076,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>.
-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
@@ -3020,7 +3097,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>.
 
-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.");
 
@@ -3217,7 +3295,20 @@ for full details.");
 
   ("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
@@ -3232,7 +3323,10 @@ in the total size of file that can be handled.");
    [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
@@ -3608,7 +3702,7 @@ and C<guestfs_setcon>");
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs_b"; "ext2"; "4096"; "/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 with block size",
    "\
@@ -3623,7 +3717,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"; "/"];
-       ["write_file"; "/new"; "new file contents"; "0"];
+       ["write"; "/new"; "new file contents"];
        ["cat"; "/new"]], "new file contents")],
    "make ext2/3/4 external journal",
    "\
@@ -3638,7 +3732,7 @@ to the command:
        ["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",
    "\
@@ -3651,7 +3745,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"; "/"];
-        ["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",
    "\
@@ -3814,7 +3908,7 @@ if you used the C<guestfs_mount> call).");
 
   ("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",
@@ -3864,7 +3958,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
-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 *)
@@ -3962,7 +4062,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
-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 (
@@ -4090,10 +4192,9 @@ but other possible values are described in C<guestfs_part_init>.");
 This sets the bootable flag on partition numbered C<partnum> on
 device C<device>.  Note that partitions are numbered from 1.
 
-The bootable flag is used by some PC BIOSes to determine which
-partition to boot from.  It is by no means universally recognized,
-and in any case if your operating system installed a boot
-sector on the device itself, then that takes precedence.");
+The bootable flag is used by some operating systems (notably
+Windows) to determine which partition to boot from.  It is by
+no means universally recognized.");
 
   ("part_set_name", (RErr, [Device "device"; Int "partnum"; String "name"]), 212, [],
    [InitEmpty, Always, TestRun (
@@ -4162,7 +4263,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
-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"; ""]]],
@@ -4225,7 +4328,7 @@ See also C<guestfs_version>.
 
   ("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",
@@ -4237,11 +4340,11 @@ example to duplicate a filesystem.
 
 If the destination is a device, it must be as large or larger
 than the source file or device, otherwise the copy will fail.
-This command cannot do partial copies.");
+This command cannot do partial copies (see C<guestfs_copy_size>).");
 
   ("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",
    "\
@@ -4288,6 +4391,315 @@ contained in a Linux initrd or initramfs image:
 
 See also C<guestfs_initrd_list>.");
 
+  ("pvuuid", (RString "uuid", [Device "device"]), 222, [],
+   [],
+   "get the UUID of a physical volume",
+   "\
+This command returns the UUID of the LVM PV C<device>.");
+
+  ("vguuid", (RString "uuid", [String "vgname"]), 223, [],
+   [],
+   "get the UUID of a volume group",
+   "\
+This command returns the UUID of the LVM VG named C<vgname>.");
+
+  ("lvuuid", (RString "uuid", [Device "device"]), 224, [],
+   [],
+   "get the UUID of a logical volume",
+   "\
+This command returns the UUID of the LVM LV C<device>.");
+
+  ("vgpvuuids", (RStringList "uuids", [String "vgname"]), 225, [],
+   [],
+   "get the PV UUIDs containing the volume group",
+   "\
+Given a VG called C<vgname>, this returns the UUIDs of all
+the physical volumes that this volume group resides on.
+
+You can use this along with C<guestfs_pvs> and C<guestfs_pvuuid>
+calls to associate physical volumes and volume groups.
+
+See also C<guestfs_vglvuuids>.");
+
+  ("vglvuuids", (RStringList "uuids", [String "vgname"]), 226, [],
+   [],
+   "get the LV UUIDs of all LVs in the volume group",
+   "\
+Given a VG called C<vgname>, this returns the UUIDs of all
+the logical volumes created in this volume group.
+
+You can use this along with C<guestfs_lvs> and C<guestfs_lvuuid>
+calls to associate logical volumes and volume groups.
+
+See also C<guestfs_vgpvuuids>.");
+
+  ("copy_size", (RErr, [Dev_or_Path "src"; Dev_or_Path "dest"; Int64 "size"]), 227, [],
+   [InitBasicFS, Always, TestOutputBuffer (
+      [["write"; "/src"; "hello, world"];
+       ["copy_size"; "/src"; "/dest"; "5"];
+       ["read_file"; "/dest"]], "hello")],
+   "copy size bytes from source to destination using dd",
+   "\
+This command copies exactly C<size> bytes from one source device
+or file C<src> to another destination device or file C<dest>.
+
+Note this will fail if the source is too short or if the destination
+is not large enough.");
+
+  ("zero_device", (RErr, [Device "device"]), 228, [DangerWillRobinson],
+   [InitBasicFSonLVM, Always, TestRun (
+      [["zero_device"; "/dev/VG/LV"]])],
+   "write zeroes to an entire device",
+   "\
+This command writes zeroes over the entire C<device>.  Compare
+with C<guestfs_zero> which just zeroes the first few blocks of
+a device.");
+
+  ("txz_in", (RErr, [FileIn "tarball"; Pathname "directory"]), 229, [Optional "xz"],
+   [InitBasicFS, Always, TestOutput (
+      [["txz_in"; "../images/helloworld.tar.xz"; "/"];
+       ["cat"; "/hello"]], "hello\n")],
+   "unpack compressed tarball to 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, [Optional "xz"],
+   [],
+   "pack directory into compressed tarball",
+   "\
+This command packs the contents of C<directory> and downloads
+it to local file C<tarball> (as an xz compressed tar archive).");
+
+  ("ntfsresize", (RErr, [Device "device"]), 231, [Optional "ntfsprogs"],
+   [],
+   "resize an NTFS filesystem",
+   "\
+This command resizes an NTFS filesystem, expanding or
+shrinking it to the size of the underlying device.
+See also L<ntfsresize(8)>.");
+
+  ("vgscan", (RErr, []), 232, [],
+   [InitEmpty, Always, TestRun (
+      [["vgscan"]])],
+   "rescan for 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 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.");
+
 ]
 
 let all_functions = non_daemon_functions @ daemon_functions
@@ -4299,6 +4711,16 @@ let all_functions_sorted =
   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). *)
@@ -4539,6 +4961,7 @@ type callt =
   | 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"
@@ -4684,10 +5107,21 @@ let count_chars c str =
   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
-  | FileIn n | FileOut n -> n
+  | FileIn n | FileOut n | BufferIn n -> n
 
 let java_name_of_struct typ =
   try List.assoc typ java_structs
@@ -4853,7 +5287,7 @@ let check_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
@@ -4998,7 +5432,7 @@ let rec generate_actions_pod () =
         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
@@ -5160,6 +5594,8 @@ and generate_xdr () =
              | 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"
@@ -5326,7 +5762,7 @@ and generate_actions_h () =
   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
 
@@ -5336,7 +5772,7 @@ and generate_internal_actions_h () =
   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
 
@@ -5348,6 +5784,7 @@ and generate_client_actions () =
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdint.h>
+#include <string.h>
 #include <inttypes.h>
 
 #include \"guestfs.h\"
@@ -5412,6 +5849,50 @@ 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 ->
+          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";
@@ -5433,7 +5914,8 @@ check_state (guestfs_h *g, const char *caller)
       | Pathname n
       | Dev_or_Path n
       | FileIn n
-      | FileOut n ->
+      | FileOut n
+      | BufferIn n ->
           (* guestfish doesn't support string escaping, so neither do we *)
           pr "    printf (\" \\\"%%s\\\"\", %s);\n" n
       | OptString n ->                 (* string option *)
@@ -5468,6 +5950,7 @@ check_state (guestfs_h *g, const char *caller)
       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;
@@ -5480,21 +5963,12 @@ check_state (guestfs_h *g, const char *caller)
   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;
 
-      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
@@ -5519,8 +5993,10 @@ check_state (guestfs_h *g, const char *caller)
       pr "  int serial;\n";
       pr "  int r;\n";
       pr "\n";
+      check_null_strings 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";
 
@@ -5546,6 +6022,16 @@ check_state (guestfs_h *g, const char *caller)
              | 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);
@@ -5806,18 +6292,26 @@ and generate_daemon_actions () =
              | 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";
 
+      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;
-           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
@@ -5826,7 +6320,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 "    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;
@@ -5836,13 +6332,16 @@ and generate_daemon_actions () =
              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;
-                 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;
-                 pr "  REQUIRE_ROOT_OR_RESOLVE_DEVICE (%s, goto done);\n" n;
+                 pr "  REQUIRE_ROOT_OR_RESOLVE_DEVICE (%s, %s, goto done);\n"
+                   n (if is_filein then "cancel_receive ()" else "0");
              | String n -> pr_args n
              | OptString n -> pr "  %s = args.%s ? *args.%s : NULL;\n" n n n
              | StringList n ->
@@ -5852,22 +6351,26 @@ and generate_daemon_actions () =
                  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 "    RESOLVE_DEVICE (physvols[pvi], %s, goto done);\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 _ -> ()
+             | 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"
       );
 
-
       (* 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
@@ -5953,15 +6456,14 @@ and generate_daemon_actions () =
       );
 
       (* Free the args. *)
+      pr "done:\n";
       (match snd style with
-       | [] ->
-           pr "done: ;\n";
+       | [] -> ()
        | _ ->
-           pr "done:\n";
            pr "  xdr_free ((xdrproc_t) xdr_guestfs_%s_args, (char *) &args);\n"
              name
       );
-
+      pr "  return;\n";
       pr "}\n\n";
   ) daemon_functions;
 
@@ -6086,7 +6588,7 @@ and generate_daemon_actions () =
         pr "  ret->guestfs_int_lvm_%s_list_val = NULL;\n" typ;
         pr "\n";
         pr "  r = command (&out, &err,\n";
-        pr "          \"/sbin/lvm\", \"%ss\",\n" typ;
+        pr "          \"lvm\", \"%ss\",\n" typ;
         pr "          \"-o\", lvm_%s_cols, \"--unbuffered\", \"--noheadings\",\n" typ;
         pr "          \"--nosuffix\", \"--separator\", \",\", \"--units\", \"b\", NULL);\n";
         pr "  if (r == -1) {\n";
@@ -6391,14 +6893,14 @@ int main (int argc, char *argv[])
     exit (EXIT_FAILURE);
   }
 
+  /* Set a timeout in case qemu hangs during launch (RHBZ#505329). */
+  alarm (600);
+
   if (guestfs_launch (g) == -1) {
     printf (\"guestfs_launch FAILED\\n\");
     exit (EXIT_FAILURE);
   }
 
-  /* Set a timeout in case qemu hangs during launch (RHBZ#505329). */
-  alarm (600);
-
   /* Cancel previous alarm. */
   alarm (0);
 
@@ -6409,6 +6911,8 @@ int main (int argc, char *argv[])
   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;
@@ -6821,6 +7325,9 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
         | String n, arg
         | OptString 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 "    size_t %s_size = %d;\n" n (String.length arg)
         | Int _, _
         | Int64 _, _
         | Bool _, _
@@ -6873,6 +7380,8 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
         | String n, _
         | OptString 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, _ ->
@@ -6961,6 +7470,9 @@ and generate_fish_cmds () =
   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";
@@ -7151,11 +7663,14 @@ and generate_fish_cmds () =
         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
-        | Dev_or_Path n -> pr "  char *%s;\n" n
+        | Dev_or_Path n
+        | FileIn n
+        | FileOut 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
@@ -7176,7 +7691,7 @@ and generate_fish_cmds () =
         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[%d], NULL, 0, &r, xstrtol_suffixes);\n" fn i;
         pr "    if (xerr != LONGINT_OK) {\n";
         pr "      fprintf (stderr,\n";
         pr "               _(\"%%s: %%s: invalid integer parameter (%%s returned %%d)\\n\"),\n";
@@ -7211,12 +7726,15 @@ and generate_fish_cmds () =
           | OptString name ->
               pr "  %s = STRNEQ (argv[%d], \"\") ? argv[%d] : NULL;\n"
                 name i i
+          | BufferIn name ->
+              pr "  %s = argv[%d];\n" name i;
+              pr "  %s_size = strlen (argv[%d]);\n" name i
           | FileIn name ->
-              pr "  %s = STRNEQ (argv[%d], \"-\") ? argv[%d] : \"/dev/stdin\";\n"
-                name i i
+              pr "  %s = file_in (argv[%d]);\n" name i;
+              pr "  if (%s == NULL) return -1;\n" name
           | FileOut name ->
-              pr "  %s = STRNEQ (argv[%d], \"-\") ? argv[%d] : \"/dev/stdout\";\n"
-                name i i
+              pr "  %s = file_out (argv[%d]);\n" name i;
+              pr "  if (%s == NULL) return -1;\n" name
           | StringList name | DeviceList name ->
               pr "  %s = parse_string_list (argv[%d]);\n" name i;
               pr "  if (%s == NULL) return -1;\n" name;
@@ -7235,34 +7753,57 @@ and generate_fish_cmds () =
       ) (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
         | Device name | String name
-        | OptString name | FileIn name | FileOut name | Bool name
-        | Int name | Int64 name -> ()
-        | Pathname name | Dev_or_Path name ->
+        | OptString name | Bool name
+        | Int name | Int64 name
+        | BufferIn name -> ()
+        | Pathname name | Dev_or_Path name | FileOut 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);
 
+      (* 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";
-           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 "  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";
@@ -7335,6 +7876,8 @@ and generate_fish_cmds () =
   ) all_functions;
   pr "    {\n";
   pr "      fprintf (stderr, _(\"%%s: unknown command\\n\"), cmd);\n";
+  pr "      if (command_num == 1)\n";
+  pr "        extended_help_message ();\n";
   pr "      return -1;\n";
   pr "    }\n";
   pr "  return 0;\n";
@@ -7413,7 +7956,16 @@ generator (const char *text, int state)
 
 #endif /* HAVE_LIBREADLINE */
 
-char **do_completion (const char *text, int start, int end)
+#ifdef HAVE_RL_COMPLETION_MATCHES
+#define RL_COMPLETION_MATCHES rl_completion_matches
+#else
+#ifdef HAVE_COMPLETION_MATCHES
+#define RL_COMPLETION_MATCHES completion_matches
+#endif
+#endif /* else just fail if we don't have either symbol */
+
+char **
+do_completion (const char *text, int start, int end)
 {
   char **matches = NULL;
 
@@ -7421,9 +7973,9 @@ char **do_completion (const char *text, int start, int end)
   rl_completion_append_character = ' ';
 
   if (start == 0)
-    matches = rl_completion_matches (text, generator);
+    matches = RL_COMPLETION_MATCHES (text, generator);
   else if (complete_dest_paths)
-    matches = rl_completion_matches (text, complete_dest_paths_generator);
+    matches = RL_COMPLETION_MATCHES (text, complete_dest_paths_generator);
 #endif
 
   return matches;
@@ -7471,6 +8023,7 @@ and generate_fish_actions_pod () =
         | Int n -> pr " %s" n
         | Int64 n -> pr " %s" n
         | FileIn n | FileOut n -> pr " (%s|-)" n
+        | BufferIn n -> pr " %s" n
       ) (snd style);
       pr "\n";
       pr "\n";
@@ -7546,6 +8099,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)
+      | 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");
   );
@@ -7566,9 +8124,13 @@ and generate_c_call_args ?handle ?(decl = false) style =
    | 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 (
@@ -7840,6 +8402,9 @@ copy_table (char * const * argv)
             pr "  const char *%s =\n" n;
             pr "    %sv != Val_int (0) ? String_val (Field (%sv, 0)) : NULL;\n"
               n n
+        | BufferIn n ->
+            pr "  const char *%s = String_val (%sv);\n" n n;
+            pr "  size_t %s_size = caml_string_length (%sv);\n" n n
         | StringList n | DeviceList n ->
             pr "  char **%s = ocaml_guestfs_strings_val (g, %sv);\n" n n
         | Bool n ->
@@ -7888,7 +8453,7 @@ copy_table (char * const * argv)
             pr "  ocaml_guestfs_free_strings (%s);\n" n;
         | Pathname _ | Device _ | Dev_or_Path _ | String _ | OptString _
         | Bool _ | Int _ | Int64 _
-        | FileIn _ | FileOut _ -> ()
+        | FileIn _ | FileOut _ | BufferIn _ -> ()
       ) (snd style);
 
       pr "  if (r == %s)\n" error_code;
@@ -7974,7 +8539,8 @@ and generate_ocaml_prototype ?(is_external = false) name style =
   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 _ -> pr "string -> "
     | OptString _ -> pr "string option -> "
     | StringList _ | DeviceList _ -> pr "string array -> "
     | Bool _ -> pr "bool -> "
@@ -8113,15 +8679,21 @@ DESTROY (g)
            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
-          | 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 ->
               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
@@ -8139,7 +8711,8 @@ DESTROY (g)
           function
           | Pathname _ | Device _ | Dev_or_Path _ | String _ | OptString _
           | Bool _ | Int _ | Int64 _
-          | FileIn _ | FileOut _ -> ()
+          | FileIn _ | FileOut _
+          | BufferIn _ -> ()
           | StringList n | DeviceList n -> pr "      free (%s);\n" n
         ) (snd style)
       in
@@ -8259,7 +8832,7 @@ DESTROY (g)
            do_cleanups ();
            pr "      if (%s == NULL)\n" n;
            pr "        croak (\"%%s\", guestfs_last_error (g));\n";
-           pr "      RETVAL = newSVpv (%s, size);\n" n;
+           pr "      RETVAL = newSVpvn (%s, size);\n" n;
            pr "      free (%s);\n" n;
            pr " OUTPUT:\n";
            pr "      RETVAL\n"
@@ -8292,7 +8865,7 @@ and generate_perl_struct_list_code typ cols name style n do_cleanups =
         pr "        (void) hv_store (hv, \"%s\", %d, newSVpv (%s->val[i].%s, 32), 0);\n"
           name (String.length name) n name
     | name, FBuffer ->
-        pr "        (void) hv_store (hv, \"%s\", %d, newSVpv (%s->val[i].%s, %s->val[i].%s_len), 0);\n"
+        pr "        (void) hv_store (hv, \"%s\", %d, newSVpvn (%s->val[i].%s, %s->val[i].%s_len), 0);\n"
           name (String.length name) n name n name
     | name, (FBytes|FUInt64) ->
         pr "        (void) hv_store (hv, \"%s\", %d, my_newSVull (%s->val[i].%s), 0);\n"
@@ -8334,7 +8907,7 @@ and generate_perl_struct_code typ cols name style n do_cleanups =
           pr "      PUSHs (sv_2mortal (newSVpv (%s->%s, 0)));\n"
             n name
       | name, FBuffer ->
-          pr "      PUSHs (sv_2mortal (newSVpv (%s->%s, %s->%s_len)));\n"
+          pr "      PUSHs (sv_2mortal (newSVpvn (%s->%s, %s->%s_len)));\n"
             n name n name
       | name, FUUID ->
           pr "      PUSHs (sv_2mortal (newSVpv (%s->%s, 32)));\n"
@@ -8421,6 +8994,12 @@ package Sys::Guestfs;
 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');
 
@@ -8439,7 +9018,7 @@ sub new {
   return $self;
 }
 
-";
+" max_proc_nr;
 
   (* Actions.  We only need to print documentation for these as
    * they are pulled in from the XS code automatically.
@@ -8511,7 +9090,8 @@ and generate_perl_prototype name style =
       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 ->
           pr "$%s" n
       | StringList n | DeviceList n ->
           pr "\\@%s" n
@@ -8523,6 +9103,7 @@ and generate_python_c () =
   generate_header CStyle LGPLv2plus;
 
   pr "\
+#define PY_SSIZE_T_CLEAN 1
 #include <Python.h>
 
 #include <stdio.h>
@@ -8770,9 +9351,13 @@ py_guestfs_close (PyObject *self, PyObject *args)
 
       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
+        | FileIn n | FileOut 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
@@ -8795,6 +9380,7 @@ 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?
                              *)
+        | BufferIn _ -> pr "s#"
       ) (snd style);
       pr ":guestfs_%s\",\n" name;
       pr "                         &py_g";
@@ -8806,6 +9392,7 @@ py_guestfs_close (PyObject *self, PyObject *args)
         | 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";
@@ -8815,7 +9402,8 @@ py_guestfs_close (PyObject *self, PyObject *args)
       List.iter (
         function
         | Pathname _ | Device _ | Dev_or_Path _ | String _
-        | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _ -> ()
+        | 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
@@ -8830,7 +9418,8 @@ py_guestfs_close (PyObject *self, PyObject *args)
       List.iter (
         function
         | Pathname _ | Device _ | Dev_or_Path _ | String _
-        | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _ -> ()
+        | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _
+        | BufferIn _ -> ()
         | StringList n | DeviceList n ->
             pr "  free (%s);\n" n
       ) (snd style);
@@ -9141,6 +9730,13 @@ static VALUE ruby_guestfs_close (VALUE gv)
             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 ->
@@ -9190,7 +9786,8 @@ static VALUE ruby_guestfs_close (VALUE gv)
       List.iter (
         function
         | Pathname _ | Device _ | Dev_or_Path _ | String _
-        | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _ -> ()
+        | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _
+        | BufferIn _ -> ()
         | StringList n | DeviceList n ->
             pr "  free (%s);\n" n
       ) (snd style);
@@ -9507,6 +10104,8 @@ and generate_java_prototype ?(public=false) ?(privat=false) ?(native=false)
       | FileIn n
       | FileOut n ->
           pr "String %s" n
+      | BufferIn n ->
+          pr "byte[] %s" n
       | StringList n | DeviceList n ->
           pr "String[] %s" n
       | Bool n ->
@@ -9628,6 +10227,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
         | FileIn n
         | FileOut n ->
             pr ", jstring j%s" n
+        | BufferIn n ->
+            pr ", jbyteArray j%s" n
         | StringList n | DeviceList n ->
             pr ", jobjectArray j%s" n
         | Bool n ->
@@ -9683,6 +10284,9 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
         | FileIn n
         | FileOut 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
@@ -9722,6 +10326,9 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
              * 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;
@@ -9754,6 +10361,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
         | 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"
@@ -10028,7 +10637,10 @@ last_error h = do
           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 ->
+              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 _ -> ()
@@ -10042,6 +10654,7 @@ last_error h = do
             | 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
+            | 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));
@@ -10092,6 +10705,9 @@ and generate_haskell_prototype ~handle ?(hs = false) style =
     fun arg ->
       (match arg with
        | Pathname _ | Device _ | Dev_or_Path _ | String _ -> 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
@@ -10281,7 +10897,8 @@ namespace Guestfs
         List.iter (
           function
           | Pathname n | Device n | Dev_or_Path n | String n | OptString n
-          | FileIn n | FileOut n ->
+          | FileIn n | FileOut n
+          | BufferIn n ->
               pr ", [In] string %s" n
           | StringList n | DeviceList n ->
               pr ", [In] string[] %s" n
@@ -10304,7 +10921,8 @@ namespace Guestfs
         List.iter (
           function
           | Pathname n | Device n | Dev_or_Path n | String n | OptString n
-          | FileIn n | FileOut n ->
+          | FileIn n | FileOut n
+          | BufferIn n ->
               next (); pr "string %s" n
           | StringList n | DeviceList n ->
               next (); pr "string[] %s" n
@@ -10409,6 +11027,10 @@ print_strings (char *const *argv)
       | String n
       | FileIn n
       | FileOut n -> pr "  printf (\"%%s\\n\", %s);\n" n
+      | BufferIn n ->
+          pr "  for (size_t i = 0; i < %s_size; ++i)\n" n;
+          pr "    printf (\"<%%02x>\", %s[i]);\n" n;
+          pr "  printf (\"\\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
@@ -10532,6 +11154,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
+        | CallBuffer s -> sprintf "%S" s
       ) args
     )
   in
@@ -10566,6 +11189,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"
+        | CallBuffer s -> "\"" ^ c_quote s ^ "\""
       ) args
     )
   in
@@ -10597,6 +11221,7 @@ g = guestfs.GuestFS ()
         | 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
@@ -10628,6 +11253,7 @@ g = Guestfs::create()
         | 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
@@ -10664,6 +11290,10 @@ public class Bindtests {
         | 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
@@ -10709,6 +11339,7 @@ main = do
         | CallInt64 i -> Int64.to_string i
         | CallBool true -> "True"
         | CallBool false -> "False"
+        | CallBuffer s -> "\"" ^ c_quote s ^ "\""
       ) args
     )
   in
@@ -10725,43 +11356,56 @@ main = do
 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;
-                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;
-                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;
-                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;
-                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;
-                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;
-                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;
-                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;
-                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;
-                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;
-                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;
-                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;
-                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. *)
 
@@ -11253,17 +11897,7 @@ let inspect ?connect ?xml names =
   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 () =
-  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 =