fish: Make 'launch' function static.
[libguestfs.git] / src / generator.ml
index 8d2a075..31ef313 100755 (executable)
 
 (* This script generates a large amount of code and documentation for
  * all the daemon actions.
 
 (* This script generates a large amount of code and documentation for
  * all the daemon actions.
- * 
+ *
  * To add a new action there are only two files you need to change,
  * this one to describe the interface (see the big table of
  * 'daemon_functions' below), and daemon/<somefile>.c to write the
  * implementation.
  * To add a new action there are only two files you need to change,
  * this one to describe the interface (see the big table of
  * 'daemon_functions' below), and daemon/<somefile>.c to write the
  * implementation.
- * 
+ *
  * After editing this file, run it (./src/generator.ml) to regenerate
  * all the output files.  'make' will rerun this automatically when
  * necessary.  Note that if you are using a separate build directory
  * you must run generator.ml from the _source_ directory.
  * After editing this file, run it (./src/generator.ml) to regenerate
  * all the output files.  'make' will rerun this automatically when
  * necessary.  Note that if you are using a separate build directory
  * you must run generator.ml from the _source_ directory.
- * 
+ *
  * IMPORTANT: This script should NOT print any warnings.  If it prints
  * warnings, you should treat them as errors.
  *
  * IMPORTANT: This script should NOT print any warnings.  If it prints
  * warnings, you should treat them as errors.
  *
@@ -42,6 +42,7 @@
 #load "unix.cma";;
 #load "str.cma";;
 #directory "+xml-light";;
 #load "unix.cma";;
 #load "str.cma";;
 #directory "+xml-light";;
+#directory "+../pkg-lib/xml-light";; (* for GODI users *)
 #load "xml-light.cma";;
 
 open Unix
 #load "xml-light.cma";;
 
 open Unix
@@ -181,11 +182,16 @@ type flags =
   | DangerWillRobinson   (* flags particularly dangerous commands *)
   | FishAlias of string          (* provide an alias for this cmd in guestfish *)
   | FishAction of string  (* call this function in guestfish *)
   | 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,
@@ -441,7 +447,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",
    "\
@@ -489,9 +495,15 @@ image).
 
 This is equivalent to the qemu parameter
 C<-drive file=filename,cache=off,if=...>.
 
 This is equivalent to the qemu parameter
 C<-drive file=filename,cache=off,if=...>.
+
 C<cache=off> is omitted in cases where it is not supported by
 the underlying filesystem.
 
 C<cache=off> is omitted in cases where it is not supported by
 the underlying filesystem.
 
+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>.
+
 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
@@ -505,10 +517,24 @@ This function adds a virtual CD-ROM disk image to the guest.
 
 This is equivalent to the qemu parameter C<-cdrom filename>.
 
 
 This is equivalent to the qemu parameter C<-cdrom filename>.
 
-Note that this call checks for the existence of C<filename>.  This
+Notes:
+
+=over 4
+
+=item *
+
+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
 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
-the general C<guestfs_config> call instead.");
+the general C<guestfs_config> call instead.
+
+=item *
+
+If you just want to add an ISO file (often you use this as an
+efficient way to transfer large files into the guest), then you
+should probably use C<guestfs_add_drive_ro> instead.
+
+=back");
 
   ("add_drive_ro", (RErr, [String "filename"]), -1, [FishAlias "add-ro"],
    [],
 
   ("add_drive_ro", (RErr, [String "filename"]), -1, [FishAlias "add-ro"],
    [],
@@ -524,7 +550,14 @@ 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,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
 
 Note that this call checks for the existence of C<filename>.  This
 stops you from specifying other types of drive which are supported
@@ -556,7 +589,15 @@ configure script.
 You can also override this by setting the C<LIBGUESTFS_QEMU>
 environment variable.
 
 You can also override this by setting the C<LIBGUESTFS_QEMU>
 environment variable.
 
-Setting C<qemu> to C<NULL> restores the default qemu binary.");
+Setting C<qemu> to C<NULL> restores the default qemu binary.
+
+Note that you should call this function as early as possible
+after creating the handle.  This is because some pre-launch
+operations depend on testing qemu features (by running C<qemu -help>).
+If the qemu binary changes, we don't retest features, and
+so you might see inconsistent results.  Using the environment
+variable C<LIBGUESTFS_QEMU> is safest of all since that picks
+the qemu binary at the same time as the handle is created.");
 
   ("get_qemu", (RConstString "qemu", []), -1, [],
    [InitNone, Always, TestRun (
 
   ("get_qemu", (RConstString "qemu", []), -1, [],
    [InitNone, Always, TestRun (
@@ -873,6 +914,20 @@ qemu, which is not very helpful.");
    "\
 Return the recovery process enabled flag.");
 
    "\
 Return the recovery process enabled flag.");
 
+  ("add_drive_with_if", (RErr, [String "filename"; String "iface"]), -1, [],
+   [],
+   "add a drive specifying the QEMU block emulation to use",
+   "\
+This is the same as C<guestfs_add_drive> but it allows you
+to specify the QEMU interface emulation to use at run time.");
+
+  ("add_drive_ro_with_if", (RErr, [String "filename"; String "iface"]), -1, [],
+   [],
+   "add a drive read-only specifying the QEMU block emulation to use",
+   "\
+This is the same as C<guestfs_add_drive_ro> but it allows you
+to specify the QEMU interface emulation to use at run time.");
+
 ]
 
 (* daemon_functions are any functions which cause some action
 ]
 
 (* daemon_functions are any functions which cause some action
@@ -903,8 +958,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"]]],
@@ -1188,7 +1250,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. *)
@@ -1326,7 +1393,13 @@ as necessary.  This is like the C<mkdir -p> shell command.");
    "change file mode",
    "\
 Change the mode (permissions) of C<path> to C<mode>.  Only
    "change file mode",
    "\
 Change the mode (permissions) of C<path> to C<mode>.  Only
-numeric modes are supported.");
+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>.
+
+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 *)
@@ -1419,16 +1492,16 @@ 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, [],
    [InitEmpty, Always, TestOutput (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext2"; "/dev/sda1"];
 on the volume group C<volgroup>, with C<size> megabytes.");
 
   ("mkfs", (RErr, [String "fstype"; Device "device"]), 42, [],
    [InitEmpty, Always, TestOutput (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext2"; "/dev/sda1"];
-       ["mount"; "/dev/sda1"; "/"];
+       ["mount_options"; ""; "/dev/sda1"; "/"];
        ["write_file"; "/new"; "new file contents"; "0"];
        ["cat"; "/new"]], "new file contents")],
    "make a filesystem",
        ["write_file"; "/new"; "new file contents"; "0"];
        ["cat"; "/new"]], "new file contents")],
    "make a filesystem",
@@ -1504,12 +1577,12 @@ use C<guestfs_upload>.");
    [InitEmpty, Always, TestOutputListOfDevices (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext2"; "/dev/sda1"];
    [InitEmpty, Always, TestOutputListOfDevices (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext2"; "/dev/sda1"];
-       ["mount"; "/dev/sda1"; "/"];
+       ["mount_options"; ""; "/dev/sda1"; "/"];
        ["mounts"]], ["/dev/sda1"]);
     InitEmpty, Always, TestOutputList (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext2"; "/dev/sda1"];
        ["mounts"]], ["/dev/sda1"]);
     InitEmpty, Always, TestOutputList (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext2"; "/dev/sda1"];
-       ["mount"; "/dev/sda1"; "/"];
+       ["mount_options"; ""; "/dev/sda1"; "/"];
        ["umount"; "/"];
        ["mounts"]], [])],
    "unmount a filesystem",
        ["umount"; "/"];
        ["mounts"]], [])],
    "unmount a filesystem",
@@ -1540,11 +1613,11 @@ See also: C<guestfs_mountpoints>");
        ["mkfs"; "ext2"; "/dev/sda1"];
        ["mkfs"; "ext2"; "/dev/sda2"];
        ["mkfs"; "ext2"; "/dev/sda3"];
        ["mkfs"; "ext2"; "/dev/sda1"];
        ["mkfs"; "ext2"; "/dev/sda2"];
        ["mkfs"; "ext2"; "/dev/sda3"];
-       ["mount"; "/dev/sda1"; "/"];
+       ["mount_options"; ""; "/dev/sda1"; "/"];
        ["mkdir"; "/mp1"];
        ["mkdir"; "/mp1"];
-       ["mount"; "/dev/sda2"; "/mp1"];
+       ["mount_options"; ""; "/dev/sda2"; "/mp1"];
        ["mkdir"; "/mp1/mp2"];
        ["mkdir"; "/mp1/mp2"];
-       ["mount"; "/dev/sda3"; "/mp1/mp2"];
+       ["mount_options"; ""; "/dev/sda3"; "/mp1/mp2"];
        ["mkdir"; "/mp1/mp2/mp3"];
        ["umount_all"];
        ["mounts"]], [])],
        ["mkdir"; "/mp1/mp2/mp3"];
        ["umount_all"];
        ["mounts"]], [])],
@@ -1918,7 +1991,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
@@ -1960,9 +2036,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.
+
+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"; String "directory"]), 69, [],
+  ("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")],
@@ -1971,7 +2051,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>.
 
 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, [],
    [],
 
   ("tar_out", (RErr, [String "directory"; FileOut "tarfile"]), 70, [],
    [],
@@ -1980,9 +2061,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>.
 
 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")],
    [InitBasicFS, Always, TestOutput (
       [["tgz_in"; "../images/helloworld.tar.gz"; "/"];
        ["cat"; "/hello"]], "hello\n")],
@@ -2023,7 +2105,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, [],
    [],
@@ -2189,7 +2275,7 @@ to return the existing UUID of a filesystem.");
 This returns the ext2/3/4 filesystem UUID of the filesystem on
 C<device>.");
 
 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);
@@ -2240,7 +2326,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.
 
 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.
 
   ("grub_install", (RErr, [Pathname "root"; Device "device"]), 86, [],
    (* Test disabled because grub-install incompatible with virtio-blk driver.
@@ -2360,7 +2446,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
@@ -2391,7 +2480,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
@@ -2401,11 +2493,11 @@ the human-readable, canonical hex dump of the file.");
    [InitNone, Always, TestOutput (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext3"; "/dev/sda1"];
    [InitNone, Always, TestOutput (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext3"; "/dev/sda1"];
-       ["mount"; "/dev/sda1"; "/"];
+       ["mount_options"; ""; "/dev/sda1"; "/"];
        ["write_file"; "/new"; "test file"; "0"];
        ["umount"; "/dev/sda1"];
        ["zerofree"; "/dev/sda1"];
        ["write_file"; "/new"; "test file"; "0"];
        ["umount"; "/dev/sda1"];
        ["zerofree"; "/dev/sda1"];
-       ["mount"; "/dev/sda1"; "/"];
+       ["mount_options"; ""; "/dev/sda1"; "/"];
        ["cat"; "/new"]], "test file")],
    "zero unused inodes and disk blocks on ext2/3 filesystem",
    "\
        ["cat"; "/new"]], "test file")],
    "zero unused inodes and disk blocks on ext2/3 filesystem",
    "\
@@ -2506,14 +2598,21 @@ are activated or deactivated.");
        ["vgcreate"; "VG"; "/dev/sda1"];
        ["lvcreate"; "LV"; "VG"; "10"];
        ["mkfs"; "ext2"; "/dev/VG/LV"];
        ["vgcreate"; "VG"; "/dev/sda1"];
        ["lvcreate"; "LV"; "VG"; "10"];
        ["mkfs"; "ext2"; "/dev/VG/LV"];
-       ["mount"; "/dev/VG/LV"; "/"];
+       ["mount_options"; ""; "/dev/VG/LV"; "/"];
        ["write_file"; "/new"; "test content"; "0"];
        ["umount"; "/"];
        ["lvresize"; "/dev/VG/LV"; "20"];
        ["e2fsck_f"; "/dev/VG/LV"];
        ["resize2fs"; "/dev/VG/LV"];
        ["write_file"; "/new"; "test content"; "0"];
        ["umount"; "/"];
        ["lvresize"; "/dev/VG/LV"; "20"];
        ["e2fsck_f"; "/dev/VG/LV"];
        ["resize2fs"; "/dev/VG/LV"];
-       ["mount"; "/dev/VG/LV"; "/"];
-       ["cat"; "/new"]], "test content")],
+       ["mount_options"; ""; "/dev/VG/LV"; "/"];
+       ["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
@@ -2739,7 +2838,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
@@ -2763,7 +2865,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
@@ -2919,7 +3024,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 (
@@ -2929,7 +3044,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 (
@@ -2939,7 +3056,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 (
@@ -2949,12 +3068,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
@@ -2969,7 +3089,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.");
 
@@ -3166,7 +3287,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
@@ -3181,7 +3315,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
@@ -3556,7 +3693,7 @@ and C<guestfs_setcon>");
    [InitEmpty, Always, TestOutput (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs_b"; "ext2"; "4096"; "/dev/sda1"];
    [InitEmpty, Always, TestOutput (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs_b"; "ext2"; "4096"; "/dev/sda1"];
-       ["mount"; "/dev/sda1"; "/"];
+       ["mount_options"; ""; "/dev/sda1"; "/"];
        ["write_file"; "/new"; "new file contents"; "0"];
        ["cat"; "/new"]], "new file contents")],
    "make a filesystem with block size",
        ["write_file"; "/new"; "new file contents"; "0"];
        ["cat"; "/new"]], "new file contents")],
    "make a filesystem with block size",
@@ -3571,7 +3708,7 @@ are C<1024>, C<2048> or C<4096> only.");
       [["sfdiskM"; "/dev/sda"; ",100 ,"];
        ["mke2journal"; "4096"; "/dev/sda1"];
        ["mke2fs_J"; "ext2"; "4096"; "/dev/sda2"; "/dev/sda1"];
       [["sfdiskM"; "/dev/sda"; ",100 ,"];
        ["mke2journal"; "4096"; "/dev/sda1"];
        ["mke2fs_J"; "ext2"; "4096"; "/dev/sda2"; "/dev/sda1"];
-       ["mount"; "/dev/sda2"; "/"];
+       ["mount_options"; ""; "/dev/sda2"; "/"];
        ["write_file"; "/new"; "new file contents"; "0"];
        ["cat"; "/new"]], "new file contents")],
    "make ext2/3/4 external journal",
        ["write_file"; "/new"; "new file contents"; "0"];
        ["cat"; "/new"]], "new file contents")],
    "make ext2/3/4 external journal",
@@ -3586,7 +3723,7 @@ to the command:
       [["sfdiskM"; "/dev/sda"; ",100 ,"];
        ["mke2journal_L"; "4096"; "JOURNAL"; "/dev/sda1"];
        ["mke2fs_JL"; "ext2"; "4096"; "/dev/sda2"; "JOURNAL"];
       [["sfdiskM"; "/dev/sda"; ",100 ,"];
        ["mke2journal_L"; "4096"; "JOURNAL"; "/dev/sda1"];
        ["mke2fs_JL"; "ext2"; "4096"; "/dev/sda2"; "JOURNAL"];
-       ["mount"; "/dev/sda2"; "/"];
+       ["mount_options"; ""; "/dev/sda2"; "/"];
        ["write_file"; "/new"; "new file contents"; "0"];
        ["cat"; "/new"]], "new file contents")],
    "make ext2/3/4 external journal with label",
        ["write_file"; "/new"; "new file contents"; "0"];
        ["cat"; "/new"]], "new file contents")],
    "make ext2/3/4 external journal with label",
@@ -3599,7 +3736,7 @@ This creates an ext2 external journal on C<device> with label C<label>.");
        [["sfdiskM"; "/dev/sda"; ",100 ,"];
         ["mke2journal_U"; "4096"; uuid; "/dev/sda1"];
         ["mke2fs_JU"; "ext2"; "4096"; "/dev/sda2"; uuid];
        [["sfdiskM"; "/dev/sda"; ",100 ,"];
         ["mke2journal_U"; "4096"; uuid; "/dev/sda1"];
         ["mke2fs_JU"; "ext2"; "4096"; "/dev/sda2"; uuid];
-        ["mount"; "/dev/sda2"; "/"];
+        ["mount_options"; ""; "/dev/sda2"; "/"];
         ["write_file"; "/new"; "new file contents"; "0"];
         ["cat"; "/new"]], "new file contents")]),
    "make ext2/3/4 external journal with UUID",
         ["write_file"; "/new"; "new file contents"; "0"];
         ["cat"; "/new"]], "new file contents")]),
    "make ext2/3/4 external journal with UUID",
@@ -3813,7 +3950,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 *)
@@ -4039,10 +4182,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.
 
 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 (
 
   ("part_set_name", (RErr, [Device "device"; Int "partnum"; String "name"]), 212, [],
    [InitEmpty, Always, TestRun (
@@ -4186,7 +4328,290 @@ 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.
 
 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"];
+       ["filesize"; "/file"]], 12)],
+   "return the size of the file in bytes",
+   "\
+This command returns the size of C<file> in bytes.
+
+To get other stats about a file, use C<guestfs_stat>, C<guestfs_lstat>,
+C<guestfs_is_dir>, C<guestfs_is_file> etc.
+To get the size of block devices, use C<guestfs_blockdev_getsize64>.");
+
+  ("lvrename", (RErr, [String "logvol"; String "newlogvol"]), 219, [],
+   [InitBasicFSonLVM, Always, TestOutputList (
+      [["lvrename"; "/dev/VG/LV"; "/dev/VG/LV2"];
+       ["lvs"]], ["/dev/VG/LV2"])],
+   "rename an LVM logical volume",
+   "\
+Rename a logical volume C<logvol> with the new name C<newlogvol>.");
+
+  ("vgrename", (RErr, [String "volgroup"; String "newvolgroup"]), 220, [],
+   [InitBasicFSonLVM, Always, TestOutputList (
+      [["umount"; "/"];
+       ["vg_activate"; "false"; "VG"];
+       ["vgrename"; "VG"; "VG2"];
+       ["vg_activate"; "true"; "VG2"];
+       ["mount_options"; ""; "/dev/VG2/LV"; "/"];
+       ["vgs"]], ["VG2"])],
+   "rename an LVM volume group",
+   "\
+Rename a volume group C<volgroup> with the new name C<newvolgroup>.");
+
+  ("initrd_cat", (RBufferOut "content", [Pathname "initrdpath"; String "filename"]), 221, [ProtocolLimitWarning],
+   [InitISOFS, Always, TestOutputBuffer (
+      [["initrd_cat"; "/initrd"; "known-4"]], "abc\ndef\nghi")],
+   "list the contents of a single file in an initrd",
+   "\
+This command unpacks the file C<filename> from the initrd file
+called C<initrdpath>.  The filename must be given I<without> the
+initial C</> character.
+
+For example, in guestfish you could use the following command
+to examine the boot script (usually called C</init>)
+contained in a Linux initrd or initramfs image:
+
+ initrd-cat /boot/initrd-<version>.img init
+
+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_file"; "/src"; "hello, world"; "0"];
+       ["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, [],
+   [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, [],
+   [],
+   "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.");
 
 ]
 
 
 ]
 
@@ -4577,6 +5002,13 @@ let mapi f xs =
   in
   loop 0 xs
 
   in
   loop 0 xs
 
+let count_chars c str =
+  let count = ref 0 in
+  for i = 0 to String.length str - 1 do
+    if c = String.unsafe_get str i then incr count
+  done;
+  !count
+
 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
@@ -4605,8 +5037,7 @@ let seq_of_test = function
 (* Handling for function flags. *)
 let protocol_limit_warning =
   "Because of the message protocol, there is a transfer limit
 (* Handling for function flags. *)
 let protocol_limit_warning =
   "Because of the message protocol, there is a transfer limit
-of somewhere between 2MB and 4MB.  To transfer large files you should use
-FTP."
+of somewhere between 2MB and 4MB.  See L<guestfs(3)/PROTOCOL LIMITS>."
 
 let danger_will_robinson =
   "B<This command is dangerous.  Without careful use you
 
 let danger_will_robinson =
   "B<This command is dangerous.  Without careful use you
@@ -4713,6 +5144,7 @@ let check_functions () =
           "for"; "forall"; "foreign"; "fun"; "function"; "functor"; "goto";
           "hiding"; "if"; "import"; "in"; "include"; "infix"; "infixl";
           "infixr"; "inherit"; "initializer"; "inline"; "instance"; "int";
           "for"; "forall"; "foreign"; "fun"; "function"; "functor"; "goto";
           "hiding"; "if"; "import"; "in"; "include"; "infix"; "infixl";
           "infixr"; "inherit"; "initializer"; "inline"; "instance"; "int";
+          "interface";
           "land"; "lazy"; "let"; "long"; "lor"; "lsl"; "lsr"; "lxor";
           "match"; "mdo"; "method"; "mod"; "module"; "mutable"; "new";
           "newtype"; "object"; "of"; "open"; "or"; "private"; "qualified";
           "land"; "lazy"; "let"; "long"; "lor"; "lsl"; "lsr"; "lxor";
           "match"; "mdo"; "method"; "mod"; "module"; "mutable"; "new";
           "newtype"; "object"; "of"; "open"; "or"; "private"; "qualified";
@@ -4746,7 +5178,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
@@ -4808,23 +5240,32 @@ let check_functions () =
 
 (* 'pr' prints to the current output file. *)
 let chan = ref Pervasives.stdout
 
 (* 'pr' prints to the current output file. *)
 let chan = ref Pervasives.stdout
-let pr fs = ksprintf (output_string !chan) fs
+let lines = ref 0
+let pr fs =
+  ksprintf
+    (fun str ->
+       let i = count_chars '\n' str in
+       lines := !lines + i;
+       output_string !chan str
+    ) fs
 
 let copyright_years =
   let this_year = 1900 + (localtime (time ())).tm_year in
   if this_year > 2009 then sprintf "2009-%04d" this_year else "2009"
 
 (* Generate a header block in a number of standard styles. *)
 
 let copyright_years =
   let this_year = 1900 + (localtime (time ())).tm_year in
   if this_year > 2009 then sprintf "2009-%04d" this_year else "2009"
 
 (* Generate a header block in a number of standard styles. *)
-type comment_style = CStyle | HashStyle | OCamlStyle | HaskellStyle
+type comment_style =
+    CStyle | CPlusPlusStyle | HashStyle | OCamlStyle | HaskellStyle
 type license = GPLv2plus | LGPLv2plus
 
 let generate_header ?(extra_inputs = []) comment license =
   let inputs = "src/generator.ml" :: extra_inputs in
   let c = match comment with
 type license = GPLv2plus | LGPLv2plus
 
 let generate_header ?(extra_inputs = []) comment license =
   let inputs = "src/generator.ml" :: extra_inputs in
   let c = match comment with
-    | CStyle ->     pr "/* "; " *"
-    | HashStyle ->  pr "# ";  "#"
-    | OCamlStyle -> pr "(* "; " *"
-    | HaskellStyle -> pr "{- "; "  " in
+    | CStyle ->         pr "/* "; " *"
+    | CPlusPlusStyle -> pr "// "; "//"
+    | HashStyle ->      pr "# ";  "#"
+    | OCamlStyle ->     pr "(* "; " *"
+    | HaskellStyle ->   pr "{- "; "  " in
   pr "libguestfs generated file\n";
   pr "%s WARNING: THIS FILE IS GENERATED FROM:\n" c;
   List.iter (pr "%s   %s\n" c) inputs;
   pr "libguestfs generated file\n";
   pr "%s WARNING: THIS FILE IS GENERATED FROM:\n" c;
   List.iter (pr "%s   %s\n" c) inputs;
@@ -4865,6 +5306,7 @@ let generate_header ?(extra_inputs = []) comment license =
   );
   (match comment with
    | CStyle -> pr " */\n"
   );
   (match comment with
    | CStyle -> pr " */\n"
+   | CPlusPlusStyle
    | HashStyle -> ()
    | OCamlStyle -> pr " *)\n"
    | HaskellStyle -> pr "-}\n"
    | HashStyle -> ()
    | OCamlStyle -> pr " *)\n"
    | HaskellStyle -> pr "-}\n"
@@ -4881,7 +5323,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
@@ -5103,8 +5545,7 @@ and generate_xdr () =
   (* Having to choose a maximum message size is annoying for several
    * reasons (it limits what we can do in the API), but it (a) makes
    * the protocol a lot simpler, and (b) provides a bound on the size
   (* Having to choose a maximum message size is annoying for several
    * reasons (it limits what we can do in the API), but it (a) makes
    * the protocol a lot simpler, and (b) provides a bound on the size
-   * of the daemon which operates in limited memory space.  For large
-   * file transfers you should use FTP.
+   * of the daemon which operates in limited memory space.
    *)
   pr "const GUESTFS_MESSAGE_MAX = %d;\n" (4 * 1024 * 1024);
   pr "\n";
    *)
   pr "const GUESTFS_MESSAGE_MAX = %d;\n" (4 * 1024 * 1024);
   pr "\n";
@@ -5210,7 +5651,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
 
@@ -5220,7 +5661,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
 
@@ -5232,6 +5673,7 @@ and generate_client_actions () =
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdint.h>
+#include <string.h>
 #include <inttypes.h>
 
 #include \"guestfs.h\"
 #include <inttypes.h>
 
 #include \"guestfs.h\"
@@ -5404,7 +5846,8 @@ check_state (guestfs_h *g, const char *caller)
       pr "  int r;\n";
       pr "\n";
       trace_call shortname style;
       pr "  int r;\n";
       pr "\n";
       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";
 
@@ -5590,6 +6033,51 @@ and generate_daemon_actions_h () =
         name style;
   ) daemon_functions
 
         name style;
   ) daemon_functions
 
+(* Generate the linker script which controls the visibility of
+ * symbols in the public ABI and ensures no other symbols get
+ * exported accidentally.
+ *)
+and generate_linker_script () =
+  generate_header HashStyle GPLv2plus;
+
+  let globals = [
+    "guestfs_create";
+    "guestfs_close";
+    "guestfs_get_error_handler";
+    "guestfs_get_out_of_memory_handler";
+    "guestfs_last_error";
+    "guestfs_set_error_handler";
+    "guestfs_set_launch_done_callback";
+    "guestfs_set_log_message_callback";
+    "guestfs_set_out_of_memory_handler";
+    "guestfs_set_subprocess_quit_callback";
+
+    (* Unofficial parts of the API: the bindings code use these
+     * functions, so it is useful to export them.
+     *)
+    "guestfs_safe_calloc";
+    "guestfs_safe_malloc";
+  ] in
+  let functions =
+    List.map (fun (name, _, _, _, _, _, _) -> "guestfs_" ^ name)
+      all_functions in
+  let structs =
+    List.concat (
+      List.map (fun (typ, _) ->
+                  ["guestfs_free_" ^ typ; "guestfs_free_" ^ typ ^ "_list"])
+        structs
+    ) in
+  let globals = List.sort compare (globals @ functions @ structs) in
+
+  pr "{\n";
+  pr "    global:\n";
+  List.iter (pr "        %s;\n") globals;
+  pr "\n";
+
+  pr "    local:\n";
+  pr "        *;\n";
+  pr "};\n"
+
 (* Generate the server-side stubs. *)
 and generate_daemon_actions () =
   generate_header CStyle GPLv2plus;
 (* Generate the server-side stubs. *)
 and generate_daemon_actions () =
   generate_header CStyle GPLv2plus;
@@ -5649,14 +6137,19 @@ and generate_daemon_actions () =
       );
       pr "\n";
 
       );
       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 (\"%%s: daemon failed to decode procedure arguments\", \"%s\");\n" name;
-           pr "    return;\n";
+           if is_filein then
+             pr "    cancel_receive ();\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
@@ -5665,6 +6158,8 @@ 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;
+             if is_filein then
+               pr "    cancel_receive ();\n";
              pr "    reply_with_perror (\"realloc\");\n";
              pr "    goto done;\n";
              pr "  }\n";
              pr "    reply_with_perror (\"realloc\");\n";
              pr "    goto done;\n";
              pr "  }\n";
@@ -5675,13 +6170,16 @@ 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 "");
              | 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 "");
              | 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;
+                 pr "  REQUIRE_ROOT_OR_RESOLVE_DEVICE (%s, %s, goto done);\n"
+                   n (if is_filein then "cancel_receive ()" else "");
              | String n -> pr_args n
              | OptString n -> pr "  %s = args.%s ? *args.%s : NULL;\n" n n n
              | StringList n ->
              | String n -> pr_args n
              | OptString n -> pr "  %s = args.%s ? *args.%s : NULL;\n" n n n
              | StringList n ->
@@ -5691,7 +6189,8 @@ 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 "  /* 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 "");
                  pr "  }\n";
              | Bool n -> pr "  %s = args.%s;\n" n n
              | Int n -> pr "  %s = args.%s;\n" n n
                  pr "  }\n";
              | Bool n -> pr "  %s = args.%s;\n" n n
              | Int n -> pr "  %s = args.%s;\n" n n
@@ -5706,7 +6205,8 @@ and generate_daemon_actions () =
       if List.exists (function Pathname _ -> true | _ -> false) (snd style) then (
         (* Emit NEED_ROOT just once, even when there are two or
            more Pathname args *)
       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 "");
       );
 
       (* Don't want to call the impl with any FileIn or FileOut
       );
 
       (* Don't want to call the impl with any FileIn or FileOut
@@ -5792,15 +6292,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;
 
@@ -5925,7 +6424,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 "  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";
         pr "          \"-o\", lvm_%s_cols, \"--unbuffered\", \"--noheadings\",\n" typ;
         pr "          \"--nosuffix\", \"--separator\", \",\", \"--units\", \"b\", NULL);\n";
         pr "  if (r == -1) {\n";
@@ -6230,14 +6729,14 @@ int main (int argc, char *argv[])
     exit (EXIT_FAILURE);
   }
 
     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);
   }
 
   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);
 
   /* Cancel previous alarm. */
   alarm (0);
 
@@ -6248,6 +6747,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;
@@ -6385,7 +6886,7 @@ and generate_one_test_body name i test_name init test =
           ["lvm_remove_all"];
           ["part_disk"; "/dev/sda"; "mbr"];
           ["mkfs"; "ext2"; "/dev/sda1"];
           ["lvm_remove_all"];
           ["part_disk"; "/dev/sda"; "mbr"];
           ["mkfs"; "ext2"; "/dev/sda1"];
-          ["mount"; "/dev/sda1"; "/"]]
+          ["mount_options"; ""; "/dev/sda1"; "/"]]
    | InitBasicFSonLVM ->
        pr "  /* InitBasicFSonLVM for %s: create ext2 on /dev/VG/LV */\n"
          test_name;
    | InitBasicFSonLVM ->
        pr "  /* InitBasicFSonLVM for %s: create ext2 on /dev/VG/LV */\n"
          test_name;
@@ -6398,7 +6899,7 @@ and generate_one_test_body name i test_name init test =
           ["vgcreate"; "VG"; "/dev/sda1"];
           ["lvcreate"; "LV"; "VG"; "8"];
           ["mkfs"; "ext2"; "/dev/VG/LV"];
           ["vgcreate"; "VG"; "/dev/sda1"];
           ["lvcreate"; "LV"; "VG"; "8"];
           ["mkfs"; "ext2"; "/dev/VG/LV"];
-          ["mount"; "/dev/VG/LV"; "/"]]
+          ["mount_options"; ""; "/dev/VG/LV"; "/"]]
    | InitISOFS ->
        pr "  /* InitISOFS for %s */\n" test_name;
        List.iter (generate_test_command_call test_name)
    | InitISOFS ->
        pr "  /* InitISOFS for %s */\n" test_name;
        List.iter (generate_test_command_call test_name)
@@ -6665,7 +7166,7 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
         | Bool _, _
         | FileIn _, _ | FileOut _, _ -> ()
         | StringList n, "" | DeviceList n, "" ->
         | Bool _, _
         | FileIn _, _ | FileOut _, _ -> ()
         | StringList n, "" | DeviceList n, "" ->
-           pr "    const char *const %s[1] = { NULL };\n" n
+            pr "    const char *const %s[1] = { NULL };\n" n
         | StringList n, arg | DeviceList n, arg ->
             let strs = string_split " " arg in
             iteri (
         | StringList n, arg | DeviceList n, arg ->
             let strs = string_split " " arg in
             iteri (
@@ -6787,6 +7288,8 @@ and generate_fish_cmds () =
       fun (_, _, _, flags, _, _, _) -> not (List.mem NotInFish flags)
     ) all_functions_sorted in
 
       fun (_, _, _, flags, _, _, _) -> not (List.mem NotInFish flags)
     ) all_functions_sorted in
 
+  pr "#include <config.h>\n";
+  pr "\n";
   pr "#include <stdio.h>\n";
   pr "#include <stdlib.h>\n";
   pr "#include <string.h>\n";
   pr "#include <stdio.h>\n";
   pr "#include <stdlib.h>\n";
   pr "#include <string.h>\n";
@@ -6794,6 +7297,8 @@ and generate_fish_cmds () =
   pr "\n";
   pr "#include <guestfs.h>\n";
   pr "#include \"c-ctype.h\"\n";
   pr "\n";
   pr "#include <guestfs.h>\n";
   pr "#include \"c-ctype.h\"\n";
+  pr "#include \"full-write.h\"\n";
+  pr "#include \"xstrtol.h\"\n";
   pr "#include \"fish.h\"\n";
   pr "\n";
 
   pr "#include \"fish.h\"\n";
   pr "\n";
 
@@ -6986,11 +7491,11 @@ 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 -> 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
         | StringList n | DeviceList n -> pr "  char **%s;\n" n
         | Bool n -> pr "  int %s;\n" n
         | Int n -> pr "  int %s;\n" n
@@ -7005,6 +7510,34 @@ and generate_fish_cmds () =
       pr "    fprintf (stderr, _(\"type 'help %%s' for help on %%s\\n\"), cmd, cmd);\n";
       pr "    return -1;\n";
       pr "  }\n";
       pr "    fprintf (stderr, _(\"type 'help %%s' for help on %%s\\n\"), cmd, cmd);\n";
       pr "    return -1;\n";
       pr "  }\n";
+
+      let parse_integer fn fntyp rtyp range name i =
+        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 "    if (xerr != LONGINT_OK) {\n";
+        pr "      fprintf (stderr,\n";
+        pr "               _(\"%%s: %%s: invalid integer parameter (%%s returned %%d)\\n\"),\n";
+        pr "               cmd, \"%s\", \"%s\", xerr);\n" name fn;
+        pr "      return -1;\n";
+        pr "    }\n";
+        (match range with
+         | None -> ()
+         | Some (min, max, comment) ->
+             pr "    /* %s */\n" comment;
+             pr "    if (r < %s || r > %s) {\n" min max;
+             pr "      fprintf (stderr, _(\"%%s: %%s: integer out of range\\n\"), cmd, \"%s\");\n"
+               name;
+             pr "      return -1;\n";
+             pr "    }\n";
+             pr "    /* The check above should ensure this assignment does not overflow. */\n";
+        );
+        pr "    %s = r;\n" name;
+        pr "  }\n";
+      in
+
       iteri (
         fun i ->
           function
       iteri (
         fun i ->
           function
@@ -7019,20 +7552,26 @@ and generate_fish_cmds () =
               pr "  %s = STRNEQ (argv[%d], \"\") ? argv[%d] : NULL;\n"
                 name i i
           | FileIn 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
+              pr "  %s = file_in (argv[%d]);\n" name i;
+              pr "  if (%s == NULL) return -1;\n" name
           | FileOut 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;
           | Bool name ->
               pr "  %s = is_true (argv[%d]) ? 1 : 0;\n" name i
           | Int name ->
           | StringList name | DeviceList name ->
               pr "  %s = parse_string_list (argv[%d]);\n" name i;
               pr "  if (%s == NULL) return -1;\n" name;
           | Bool name ->
               pr "  %s = is_true (argv[%d]) ? 1 : 0;\n" name i
           | Int name ->
-              pr "  %s = atoi (argv[%d]);\n" name i
+              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 ->
           | Int64 name ->
-              pr "  %s = atoll (argv[%d]);\n" name i
+              parse_integer "xstrtoll" "long long" "int64_t" None name i
       ) (snd style);
 
       (* Call C API function. *)
       ) (snd style);
 
       (* Call C API function. *)
@@ -7046,24 +7585,49 @@ and generate_fish_cmds () =
       List.iter (
         function
         | Device name | String name
       List.iter (
         function
         | Device name | String name
-        | OptString name | FileIn name | FileOut name | Bool name
+        | OptString name | Bool name
         | Int name | Int64 name -> ()
         | Int name | Int64 name -> ()
-        | Pathname name | Dev_or_Path name ->
+        | Pathname name | Dev_or_Path name | FileOut 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";
@@ -7103,7 +7667,11 @@ and generate_fish_cmds () =
            pr "  return 0;\n"
        | RBufferOut _ ->
            pr "  if (r == NULL) return -1;\n";
            pr "  return 0;\n"
        | RBufferOut _ ->
            pr "  if (r == NULL) return -1;\n";
-           pr "  fwrite (r, size, 1, stdout);\n";
+           pr "  if (full_write (1, r, size) != size) {\n";
+           pr "    perror (\"write\");\n";
+           pr "    free (r);\n";
+           pr "    return -1;\n";
+           pr "  }\n";
            pr "  free (r);\n";
            pr "  return 0;\n"
       );
            pr "  free (r);\n";
            pr "  return 0;\n"
       );
@@ -7132,6 +7700,8 @@ and generate_fish_cmds () =
   ) all_functions;
   pr "    {\n";
   pr "      fprintf (stderr, _(\"%%s: unknown command\\n\"), cmd);\n";
   ) 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";
   pr "      return -1;\n";
   pr "    }\n";
   pr "  return 0;\n";
@@ -7210,7 +7780,16 @@ generator (const char *text, int state)
 
 #endif /* HAVE_LIBREADLINE */
 
 
 #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;
 
 {
   char **matches = NULL;
 
@@ -7218,9 +7797,9 @@ char **do_completion (const char *text, int start, int end)
   rl_completion_append_character = ' ';
 
   if (start == 0)
   rl_completion_append_character = ' ';
 
   if (start == 0)
-    matches = rl_completion_matches (text, generator);
+    matches = RL_COMPLETION_MATCHES (text, generator);
   else if (complete_dest_paths)
   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;
 #endif
 
   return matches;
@@ -7952,7 +8531,7 @@ DESTROY (g)
            pr ";\n";
            do_cleanups ();
            pr "      if (r == -1)\n";
            pr ";\n";
            do_cleanups ();
            pr "      if (r == -1)\n";
-           pr "        croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+           pr "        croak (\"%%s\", guestfs_last_error (g));\n";
        | RInt n
        | RBool n ->
            pr "PREINIT:\n";
        | RInt n
        | RBool n ->
            pr "PREINIT:\n";
@@ -7963,7 +8542,7 @@ DESTROY (g)
            pr ";\n";
            do_cleanups ();
            pr "      if (%s == -1)\n" n;
            pr ";\n";
            do_cleanups ();
            pr "      if (%s == -1)\n" n;
-           pr "        croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+           pr "        croak (\"%%s\", guestfs_last_error (g));\n";
            pr "      RETVAL = newSViv (%s);\n" n;
            pr " OUTPUT:\n";
            pr "      RETVAL\n"
            pr "      RETVAL = newSViv (%s);\n" n;
            pr " OUTPUT:\n";
            pr "      RETVAL\n"
@@ -7976,7 +8555,7 @@ DESTROY (g)
            pr ";\n";
            do_cleanups ();
            pr "      if (%s == -1)\n" n;
            pr ";\n";
            do_cleanups ();
            pr "      if (%s == -1)\n" n;
-           pr "        croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+           pr "        croak (\"%%s\", guestfs_last_error (g));\n";
            pr "      RETVAL = my_newSVll (%s);\n" n;
            pr " OUTPUT:\n";
            pr "      RETVAL\n"
            pr "      RETVAL = my_newSVll (%s);\n" n;
            pr " OUTPUT:\n";
            pr "      RETVAL\n"
@@ -7989,7 +8568,7 @@ DESTROY (g)
            pr ";\n";
            do_cleanups ();
            pr "      if (%s == NULL)\n" n;
            pr ";\n";
            do_cleanups ();
            pr "      if (%s == NULL)\n" n;
-           pr "        croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+           pr "        croak (\"%%s\", guestfs_last_error (g));\n";
            pr "      RETVAL = newSVpv (%s, 0);\n" n;
            pr " OUTPUT:\n";
            pr "      RETVAL\n"
            pr "      RETVAL = newSVpv (%s, 0);\n" n;
            pr " OUTPUT:\n";
            pr "      RETVAL\n"
@@ -8016,7 +8595,7 @@ DESTROY (g)
            pr ";\n";
            do_cleanups ();
            pr "      if (%s == NULL)\n" n;
            pr ";\n";
            do_cleanups ();
            pr "      if (%s == NULL)\n" n;
-           pr "        croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+           pr "        croak (\"%%s\", guestfs_last_error (g));\n";
            pr "      RETVAL = newSVpv (%s, 0);\n" n;
            pr "      free (%s);\n" n;
            pr " OUTPUT:\n";
            pr "      RETVAL = newSVpv (%s, 0);\n" n;
            pr "      free (%s);\n" n;
            pr " OUTPUT:\n";
@@ -8031,7 +8610,7 @@ DESTROY (g)
            pr ";\n";
            do_cleanups ();
            pr "      if (%s == NULL)\n" n;
            pr ";\n";
            do_cleanups ();
            pr "      if (%s == NULL)\n" n;
-           pr "        croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+           pr "        croak (\"%%s\", guestfs_last_error (g));\n";
            pr "      for (n = 0; %s[n] != NULL; ++n) /**/;\n" n;
            pr "      EXTEND (SP, n);\n";
            pr "      for (i = 0; i < n; ++i) {\n";
            pr "      for (n = 0; %s[n] != NULL; ++n) /**/;\n" n;
            pr "      EXTEND (SP, n);\n";
            pr "      for (i = 0; i < n; ++i) {\n";
@@ -8055,8 +8634,8 @@ DESTROY (g)
            pr ";\n";
            do_cleanups ();
            pr "      if (%s == NULL)\n" n;
            pr ";\n";
            do_cleanups ();
            pr "      if (%s == NULL)\n" n;
-           pr "        croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
-           pr "      RETVAL = newSVpv (%s, size);\n" n;
+           pr "        croak (\"%%s\", guestfs_last_error (g));\n";
+           pr "      RETVAL = newSVpvn (%s, size);\n" n;
            pr "      free (%s);\n" n;
            pr " OUTPUT:\n";
            pr "      RETVAL\n"
            pr "      free (%s);\n" n;
            pr " OUTPUT:\n";
            pr "      RETVAL\n"
@@ -8076,7 +8655,7 @@ and generate_perl_struct_list_code typ cols name style n do_cleanups =
   pr ";\n";
   do_cleanups ();
   pr "      if (%s == NULL)\n" n;
   pr ";\n";
   do_cleanups ();
   pr "      if (%s == NULL)\n" n;
-  pr "        croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+  pr "        croak (\"%%s\", guestfs_last_error (g));\n";
   pr "      EXTEND (SP, %s->len);\n" n;
   pr "      for (i = 0; i < %s->len; ++i) {\n" n;
   pr "        hv = newHV ();\n";
   pr "      EXTEND (SP, %s->len);\n" n;
   pr "      for (i = 0; i < %s->len; ++i) {\n" n;
   pr "        hv = newHV ();\n";
@@ -8089,7 +8668,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, 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"
           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"
@@ -8120,7 +8699,7 @@ and generate_perl_struct_code typ cols name style n do_cleanups =
   pr ";\n";
   do_cleanups ();
   pr "      if (%s == NULL)\n" n;
   pr ";\n";
   do_cleanups ();
   pr "      if (%s == NULL)\n" n;
-  pr "        croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+  pr "        croak (\"%%s\", guestfs_last_error (g));\n";
   pr "      EXTEND (SP, 2 * %d);\n" (List.length cols);
   List.iter (
     fun ((name, _) as col) ->
   pr "      EXTEND (SP, 2 * %d);\n" (List.length cols);
   List.iter (
     fun ((name, _) as col) ->
@@ -8131,7 +8710,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, 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"
             n name n name
       | name, FUUID ->
           pr "      PUSHs (sv_2mortal (newSVpv (%s->%s, 32)));\n"
@@ -8196,7 +8775,8 @@ schemes, qcow, qcow2, vmdk.
 
 Libguestfs provides ways to enumerate guest storage (eg. partitions,
 LVs, what filesystem is in each LV, etc.).  It can also run commands
 
 Libguestfs provides ways to enumerate guest storage (eg. partitions,
 LVs, what filesystem is in each LV, etc.).  It can also run commands
-in the context of the guest.  Also you can access filesystems over FTP.
+in the context of the guest.  Also you can access filesystems over
+FUSE.
 
 See also L<Sys::Guestfs::Lib(3)> for a set of useful library
 functions for using libguestfs from Perl, including integration
 
 See also L<Sys::Guestfs::Lib(3)> for a set of useful library
 functions for using libguestfs from Perl, including integration
@@ -8733,7 +9313,8 @@ schemes, qcow, qcow2, vmdk.
 
 Libguestfs provides ways to enumerate guest storage (eg. partitions,
 LVs, what filesystem is in each LV, etc.).  It can also run commands
 
 Libguestfs provides ways to enumerate guest storage (eg. partitions,
 LVs, what filesystem is in each LV, etc.).  It can also run commands
-in the context of the guest.  Also you can access filesystems over FTP.
+in the context of the guest.  Also you can access filesystems over
+FUSE.
 
 Errors which happen while using the API are turned into Python
 RuntimeError exceptions.
 
 Errors which happen while using the API are turned into Python
 RuntimeError exceptions.
@@ -9918,6 +10499,241 @@ and generate_haskell_prototype ~handle ?(hs = false) style =
   );
   pr ")"
 
   );
   pr ")"
 
+and generate_csharp () =
+  generate_header CPlusPlusStyle LGPLv2plus;
+
+  (* XXX Make this configurable by the C# assembly users. *)
+  let library = "libguestfs.so.0" in
+
+  pr "\
+// These C# bindings are highly experimental at present.
+//
+// Firstly they only work on Linux (ie. Mono).  In order to get them
+// to work on Windows (ie. .Net) you would need to port the library
+// itself to Windows first.
+//
+// The second issue is that some calls are known to be incorrect and
+// can cause Mono to segfault.  Particularly: calls which pass or
+// return string[], or return any structure value.  This is because
+// we haven't worked out the correct way to do this from C#.
+//
+// The third issue is that when compiling you get a lot of warnings.
+// We are not sure whether the warnings are important or not.
+//
+// Fourthly we do not routinely build or test these bindings as part
+// of the make && make check cycle, which means that regressions might
+// go unnoticed.
+//
+// Suggestions and patches are welcome.
+
+// To compile:
+//
+// gmcs Libguestfs.cs
+// mono Libguestfs.exe
+//
+// (You'll probably want to add a Test class / static main function
+// otherwise this won't do anything useful).
+
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+using System.Collections;
+
+namespace Guestfs
+{
+  class Error : System.ApplicationException
+  {
+    public Error (string message) : base (message) {}
+    protected Error (SerializationInfo info, StreamingContext context) {}
+  }
+
+  class Guestfs
+  {
+    IntPtr _handle;
+
+    [DllImport (\"%s\")]
+    static extern IntPtr guestfs_create ();
+
+    public Guestfs ()
+    {
+      _handle = guestfs_create ();
+      if (_handle == IntPtr.Zero)
+        throw new Error (\"could not create guestfs handle\");
+    }
+
+    [DllImport (\"%s\")]
+    static extern void guestfs_close (IntPtr h);
+
+    ~Guestfs ()
+    {
+      guestfs_close (_handle);
+    }
+
+    [DllImport (\"%s\")]
+    static extern string guestfs_last_error (IntPtr h);
+
+" library library library;
+
+  (* Generate C# structure bindings.  We prefix struct names with
+   * underscore because C# cannot have conflicting struct names and
+   * method names (eg. "class stat" and "stat").
+   *)
+  List.iter (
+    fun (typ, cols) ->
+      pr "    [StructLayout (LayoutKind.Sequential)]\n";
+      pr "    public class _%s {\n" typ;
+      List.iter (
+        function
+        | name, FChar -> pr "      char %s;\n" name
+        | name, FString -> pr "      string %s;\n" name
+        | name, FBuffer ->
+            pr "      uint %s_len;\n" name;
+            pr "      string %s;\n" name
+        | name, FUUID ->
+            pr "      [MarshalAs (UnmanagedType.ByValTStr, SizeConst=16)]\n";
+            pr "      string %s;\n" name
+        | name, FUInt32 -> pr "      uint %s;\n" name
+        | name, FInt32 -> pr "      int %s;\n" name
+        | name, (FUInt64|FBytes) -> pr "      ulong %s;\n" name
+        | name, FInt64 -> pr "      long %s;\n" name
+        | name, FOptPercent -> pr "      float %s; /* [0..100] or -1 */\n" name
+      ) cols;
+      pr "    }\n";
+      pr "\n"
+  ) structs;
+
+  (* Generate C# function bindings. *)
+  List.iter (
+    fun (name, style, _, _, _, shortdesc, _) ->
+      let rec csharp_return_type () =
+        match fst style with
+        | RErr -> "void"
+        | RBool n -> "bool"
+        | RInt n -> "int"
+        | RInt64 n -> "long"
+        | RConstString n
+        | RConstOptString n
+        | RString n
+        | RBufferOut n -> "string"
+        | RStruct (_,n) -> "_" ^ n
+        | RHashtable n -> "Hashtable"
+        | RStringList n -> "string[]"
+        | RStructList (_,n) -> sprintf "_%s[]" n
+
+      and c_return_type () =
+        match fst style with
+        | RErr
+        | RBool _
+        | RInt _ -> "int"
+        | RInt64 _ -> "long"
+        | RConstString _
+        | RConstOptString _
+        | RString _
+        | RBufferOut _ -> "string"
+        | RStruct (_,n) -> "_" ^ n
+        | RHashtable _
+        | RStringList _ -> "string[]"
+        | RStructList (_,n) -> sprintf "_%s[]" n
+
+      and c_error_comparison () =
+        match fst style with
+        | RErr
+        | RBool _
+        | RInt _
+        | RInt64 _ -> "== -1"
+        | RConstString _
+        | RConstOptString _
+        | RString _
+        | RBufferOut _
+        | RStruct (_,_)
+        | RHashtable _
+        | RStringList _
+        | RStructList (_,_) -> "== null"
+
+      and generate_extern_prototype () =
+        pr "    static extern %s guestfs_%s (IntPtr h"
+          (c_return_type ()) name;
+        List.iter (
+          function
+          | Pathname n | Device n | Dev_or_Path n | String n | OptString n
+          | FileIn n | FileOut n ->
+              pr ", [In] string %s" n
+          | StringList n | DeviceList n ->
+              pr ", [In] string[] %s" n
+          | Bool n ->
+              pr ", bool %s" n
+          | Int n ->
+              pr ", int %s" n
+          | Int64 n ->
+              pr ", long %s" n
+        ) (snd style);
+        pr ");\n"
+
+      and generate_public_prototype () =
+        pr "    public %s %s (" (csharp_return_type ()) name;
+        let comma = ref false in
+        let next () =
+          if !comma then pr ", ";
+          comma := true
+        in
+        List.iter (
+          function
+          | Pathname n | Device n | Dev_or_Path n | String n | OptString n
+          | FileIn n | FileOut n ->
+              next (); pr "string %s" n
+          | StringList n | DeviceList n ->
+              next (); pr "string[] %s" n
+          | Bool n ->
+              next (); pr "bool %s" n
+          | Int n ->
+              next (); pr "int %s" n
+          | Int64 n ->
+              next (); pr "long %s" n
+        ) (snd style);
+        pr ")\n"
+
+      and generate_call () =
+        pr "guestfs_%s (_handle" name;
+        List.iter (fun arg -> pr ", %s" (name_of_argt arg)) (snd style);
+        pr ");\n";
+      in
+
+      pr "    [DllImport (\"%s\")]\n" library;
+      generate_extern_prototype ();
+      pr "\n";
+      pr "    /// <summary>\n";
+      pr "    /// %s\n" shortdesc;
+      pr "    /// </summary>\n";
+      generate_public_prototype ();
+      pr "    {\n";
+      pr "      %s r;\n" (c_return_type ());
+      pr "      r = ";
+      generate_call ();
+      pr "      if (r %s)\n" (c_error_comparison ());
+      pr "        throw new Error (guestfs_last_error (_handle));\n";
+      (match fst style with
+       | RErr -> ()
+       | RBool _ ->
+           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 "        rr.Add (r[i], r[i+1]);\n";
+           pr "      return rr;\n"
+       | RInt _ | RInt64 _ | RConstString _ | RConstOptString _
+       | RString _ | RBufferOut _ | RStruct _ | RStringList _
+       | RStructList _ ->
+           pr "      return r;\n"
+      );
+      pr "    }\n";
+      pr "\n";
+  ) all_functions_sorted;
+
+  pr "  }
+}
+"
+
 and generate_bindtests () =
   generate_header CStyle LGPLv2plus;
 
 and generate_bindtests () =
   generate_header CStyle LGPLv2plus;
 
@@ -10884,6 +11700,11 @@ Run it from the top source directory using the command
   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/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/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/MAX_PROC_NR" generate_max_proc_nr;
+  output_to "src/libguestfs.syms" generate_linker_script;
   output_to "daemon/actions.h" generate_daemon_actions_h;
   output_to "daemon/stubs.c" generate_daemon_actions;
   output_to "daemon/names.c" generate_daemon_names;
   output_to "daemon/actions.h" generate_daemon_actions_h;
   output_to "daemon/stubs.c" generate_daemon_actions;
   output_to "daemon/names.c" generate_daemon_names;
@@ -10892,10 +11713,7 @@ Run it from the top source directory using the command
   output_to "capitests/tests.c" generate_tests;
   output_to "fish/cmds.c" generate_fish_cmds;
   output_to "fish/completion.c" generate_fish_completion;
   output_to "capitests/tests.c" generate_tests;
   output_to "fish/cmds.c" generate_fish_cmds;
   output_to "fish/completion.c" generate_fish_completion;
-  output_to "guestfs-structs.pod" generate_structs_pod;
-  output_to "guestfs-actions.pod" generate_actions_pod;
-  output_to "guestfs-availability.pod" generate_availability_pod;
-  output_to "guestfish-actions.pod" generate_fish_actions_pod;
+  output_to "fish/guestfish-actions.pod" generate_fish_actions_pod;
   output_to "ocaml/guestfs.mli" generate_ocaml_mli;
   output_to "ocaml/guestfs.ml" generate_ocaml_ml;
   output_to "ocaml/guestfs_c_actions.c" generate_ocaml_c;
   output_to "ocaml/guestfs.mli" generate_ocaml_mli;
   output_to "ocaml/guestfs.ml" generate_ocaml_ml;
   output_to "ocaml/guestfs_c_actions.c" generate_ocaml_c;
@@ -10924,11 +11742,13 @@ Run it from the top source directory using the command
   output_to "java/Bindtests.java" generate_java_bindtests;
   output_to "haskell/Guestfs.hs" generate_haskell_hs;
   output_to "haskell/Bindtests.hs" generate_haskell_bindtests;
   output_to "java/Bindtests.java" generate_java_bindtests;
   output_to "haskell/Guestfs.hs" generate_haskell_hs;
   output_to "haskell/Bindtests.hs" generate_haskell_bindtests;
-  output_to "src/MAX_PROC_NR" generate_max_proc_nr;
+  output_to "csharp/Libguestfs.cs" generate_csharp;
 
   (* Always generate this file last, and unconditionally.  It's used
    * by the Makefile to know when we must re-run the generator.
    *)
   let chan = open_out "src/stamp-generator" in
   fprintf chan "1\n";
 
   (* Always generate this file last, and unconditionally.  It's used
    * by the Makefile to know when we must re-run the generator.
    *)
   let chan = open_out "src/stamp-generator" in
   fprintf chan "1\n";
-  close_out chan
+  close_out chan;
+
+  printf "generated %d lines of code\n" !lines