New APIs: is-chardev, is-blockdev, is-fifo, is-symlink, is-socket
[libguestfs.git] / src / generator.ml
index 89c1925..da01f12 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env ocaml
 (* libguestfs
- * Copyright (C) 2009 Red Hat Inc.
+ * Copyright (C) 2009-2010 Red Hat Inc.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * 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 below), and
- * daemon/<somefile>.c to write the implementation.
+ * 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.  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.
+ *
+ * OCaml tips:
+ * (1) In emacs, install tuareg-mode to display and format OCaml code
+ * correctly.  'vim' comes with a good OCaml editing mode by default.
+ * (2) Read the resources at http://ocaml-tutorial.org/
  *)
 
 #load "unix.cma";;
 #load "str.cma";;
+#directory "+xml-light";;
+#directory "+../pkg-lib/xml-light";; (* for GODI users *)
+#load "xml-light.cma";;
 
 open Unix
 open Printf
@@ -154,9 +164,8 @@ and argt =
      *)
   | FileIn of string
   | FileOut of string
-(* Not implemented:
     (* Opaque buffer which can contain arbitrary 8 bit data.
-     * In the C API, this is expressed as <char *, int> pair.
+     * In the C API, this is expressed as <const char *, size_t> pair.
      * Most other languages have a string type which can contain
      * ASCII NUL.  We use whatever type is appropriate for each
      * language.
@@ -165,17 +174,29 @@ and argt =
      * To return an arbitrary buffer, use RBufferOut.
      *)
   | BufferIn of string
-*)
+    (* Key material / passphrase.  Eventually we should treat this
+     * as sensitive and mlock it into physical RAM.  However this
+     * is highly complex because of all the places that XDR-encoded
+     * strings can end up.  So currently the only difference from
+     * 'String' is the way that guestfish requests these parameters
+     * from the user.
+     *)
+  | Key of string
 
 type flags =
   | ProtocolLimitWarning  (* display warning about protocol size limits *)
   | 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 *)
+  | Progress              (* function can generate progress messages *)
+
+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.
  *
@@ -297,6 +318,9 @@ and test_prereq =
     (* As for 'If' but the test runs _unless_ the code returns true. *)
   | Unless of string
 
+    (* Run the test only if 'string' is available in the daemon. *)
+  | IfAvailable of string
+
 (* Some initial scenarios for testing. *)
 and test_init =
     (* Do nothing, block devices could contain random stuff including
@@ -370,6 +394,7 @@ let test_all_args = [
   Int64 "integer64";
   FileIn "filein";
   FileOut "fileout";
+  BufferIn "bufferin";
 ]
 
 let test_all_rets = [
@@ -432,7 +457,7 @@ You probably don't want to call this function.")]
  *)
 
 let non_daemon_functions = test_functions @ [
-  ("launch", (RErr, []), -1, [FishAlias "run"; FishAction "launch"],
+  ("launch", (RErr, []), -1, [FishAlias "run"],
    [],
    "launch the qemu subprocess",
    "\
@@ -480,9 +505,15 @@ image).
 
 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<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
@@ -496,10 +527,24 @@ This function adds a virtual CD-ROM disk image to the guest.
 
 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
-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"],
    [],
@@ -517,6 +562,11 @@ changes to be committed, although qemu can support this.
 This is equivalent to the qemu parameter
 C<-drive file=filename,snapshot=on,if=...>.
 
+C<if=...> is set at compile time by the configuration option
+C<./configure --with-drive-if=...>.  In the rare case where you
+might need to change this at run time, use C<guestfs_add_drive_with_if>
+or C<guestfs_add_drive_ro_with_if>.
+
 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
@@ -535,7 +585,7 @@ The first character of C<param> string must be a C<-> (dash).
 
 C<value> can be NULL.");
 
-  ("set_qemu", (RErr, [String "qemu"]), -1, [FishAlias "qemu"],
+  ("set_qemu", (RErr, [OptString "qemu"]), -1, [FishAlias "qemu"],
    [],
    "set the qemu binary",
    "\
@@ -547,7 +597,15 @@ configure script.
 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 (
@@ -559,7 +617,7 @@ Return the current qemu binary.
 This is always non-NULL.  If it wasn't set already, then this will
 return the default qemu binary name.");
 
-  ("set_path", (RErr, [String "searchpath"]), -1, [FishAlias "path"],
+  ("set_path", (RErr, [OptString "searchpath"]), -1, [FishAlias "path"],
    [],
    "set the search path",
    "\
@@ -746,8 +804,9 @@ against a completely different C<libguestfs.so> library.
 
 This call was added in version C<1.0.58>.  In previous
 versions of libguestfs there was no way to get the version
-number.  From C code you can use ELF weak linking tricks to find out if
-this symbol exists (if it doesn't, then it's an earlier version).
+number.  From C code you can use dynamic linker functions
+to find out if this symbol exists (if it doesn't, then
+it's an earlier version).
 
 The call returns a structure with four elements.  The first
 three (C<major>, C<minor> and C<release>) are numbers and
@@ -758,9 +817,13 @@ used for distro-specific information.
 To construct the original version string:
 C<$major.$minor.$release$extra>
 
+See also: L<guestfs(3)/LIBGUESTFS VERSION NUMBERS>.
+
 I<Note:> Don't use this call to test for availability
-of features.  Distro backports makes this unreliable.  Use
-C<guestfs_available> instead.");
+of features.  In enterprise distributions we backport
+features from later versions into earlier versions,
+making this an unreliable way to test for features.
+Use C<guestfs_available> instead.");
 
   ("set_selinux", (RErr, [Bool "selinux"]), -1, [FishAlias "selinux"],
    [InitNone, Always, TestOutputTrue (
@@ -794,7 +857,7 @@ see L<guestfs(3)>.");
    "enable or disable command traces",
    "\
 If the command trace flag is set to 1, then commands are
-printed on stdout before they are executed in a format
+printed on stderr before they are executed in a format
 which is very similar to the one used by guestfish.  In
 other words, you can run a program with this enabled, and
 you will get out a script which you can feed to guestfish
@@ -864,6 +927,388 @@ qemu, which is not very helpful.");
    "\
 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.");
+
+  ("file_architecture", (RString "arch", [Pathname "filename"]), -1, [],
+   [InitISOFS, Always, TestOutput (
+      [["file_architecture"; "/bin-i586-dynamic"]], "i386");
+    InitISOFS, Always, TestOutput (
+      [["file_architecture"; "/bin-sparc-dynamic"]], "sparc");
+    InitISOFS, Always, TestOutput (
+      [["file_architecture"; "/bin-win32.exe"]], "i386");
+    InitISOFS, Always, TestOutput (
+      [["file_architecture"; "/bin-win64.exe"]], "x86_64");
+    InitISOFS, Always, TestOutput (
+      [["file_architecture"; "/bin-x86_64-dynamic"]], "x86_64");
+    InitISOFS, Always, TestOutput (
+      [["file_architecture"; "/lib-i586.so"]], "i386");
+    InitISOFS, Always, TestOutput (
+      [["file_architecture"; "/lib-sparc.so"]], "sparc");
+    InitISOFS, Always, TestOutput (
+      [["file_architecture"; "/lib-win32.dll"]], "i386");
+    InitISOFS, Always, TestOutput (
+      [["file_architecture"; "/lib-win64.dll"]], "x86_64");
+    InitISOFS, Always, TestOutput (
+      [["file_architecture"; "/lib-x86_64.so"]], "x86_64");
+    InitISOFS, Always, TestOutput (
+      [["file_architecture"; "/initrd-x86_64.img"]], "x86_64");
+    InitISOFS, Always, TestOutput (
+      [["file_architecture"; "/initrd-x86_64.img.gz"]], "x86_64");],
+   "detect the architecture of a binary file",
+   "\
+This detects the architecture of the binary C<filename>,
+and returns it if known.
+
+Currently defined architectures are:
+
+=over 4
+
+=item \"i386\"
+
+This string is returned for all 32 bit i386, i486, i586, i686 binaries
+irrespective of the precise processor requirements of the binary.
+
+=item \"x86_64\"
+
+64 bit x86-64.
+
+=item \"sparc\"
+
+32 bit SPARC.
+
+=item \"sparc64\"
+
+64 bit SPARC V9 and above.
+
+=item \"ia64\"
+
+Intel Itanium.
+
+=item \"ppc\"
+
+32 bit Power PC.
+
+=item \"ppc64\"
+
+64 bit Power PC.
+
+=back
+
+Libguestfs may return other architecture strings in future.
+
+The function works on at least the following types of files:
+
+=over 4
+
+=item *
+
+many types of Un*x and Linux binary
+
+=item *
+
+many types of Un*x and Linux shared library
+
+=item *
+
+Windows Win32 and Win64 binaries
+
+=item *
+
+Windows Win32 and Win64 DLLs
+
+Win32 binaries and DLLs return C<i386>.
+
+Win64 binaries and DLLs return C<x86_64>.
+
+=item *
+
+Linux kernel modules
+
+=item *
+
+Linux new-style initrd images
+
+=item *
+
+some non-x86 Linux vmlinuz kernels
+
+=back
+
+What it can't do currently:
+
+=over 4
+
+=item *
+
+static libraries (libfoo.a)
+
+=item *
+
+Linux old-style initrd as compressed ext2 filesystem (RHEL 3)
+
+=item *
+
+x86 Linux vmlinuz kernels
+
+x86 vmlinuz images (bzImage format) consist of a mix of 16-, 32- and
+compressed code, and are horribly hard to unpack.  If you want to find
+the architecture of a kernel, use the architecture of the associated
+initrd or kernel module(s) instead.
+
+=back");
+
+  ("inspect_os", (RStringList "roots", []), -1, [],
+   [],
+   "inspect disk and return list of operating systems found",
+   "\
+This function uses other libguestfs functions and certain
+heuristics to inspect the disk(s) (usually disks belonging to
+a virtual machine), looking for operating systems.
+
+The list returned is empty if no operating systems were found.
+
+If one operating system was found, then this returns a list with
+a single element, which is the name of the root filesystem of
+this operating system.  It is also possible for this function
+to return a list containing more than one element, indicating
+a dual-boot or multi-boot virtual machine, with each element being
+the root filesystem of one of the operating systems.
+
+You can pass the root string(s) returned to other
+C<guestfs_inspect_get_*> functions in order to query further
+information about each operating system, such as the name
+and version.
+
+This function uses other libguestfs features such as
+C<guestfs_mount_ro> and C<guestfs_umount_all> in order to mount
+and unmount filesystems and look at the contents.  This should
+be called with no disks currently mounted.  The function may also
+use Augeas, so any existing Augeas handle will be closed.
+
+This function cannot decrypt encrypted disks.  The caller
+must do that first (supplying the necessary keys) if the
+disk is encrypted.
+
+Please read L<guestfs(3)/INSPECTION> for more details.");
+
+  ("inspect_get_type", (RString "name", [Device "root"]), -1, [],
+   [],
+   "get type of inspected operating system",
+   "\
+This function should only be called with a root device string
+as returned by C<guestfs_inspect_os>.
+
+This returns the type of the inspected operating system.
+Currently defined types are:
+
+=over 4
+
+=item \"linux\"
+
+Any Linux-based operating system.
+
+=item \"windows\"
+
+Any Microsoft Windows operating system.
+
+=item \"unknown\"
+
+The operating system type could not be determined.
+
+=back
+
+Future versions of libguestfs may return other strings here.
+The caller should be prepared to handle any string.
+
+Please read L<guestfs(3)/INSPECTION> for more details.");
+
+  ("inspect_get_arch", (RString "arch", [Device "root"]), -1, [],
+   [],
+   "get architecture of inspected operating system",
+   "\
+This function should only be called with a root device string
+as returned by C<guestfs_inspect_os>.
+
+This returns the architecture of the inspected operating system.
+The possible return values are listed under
+C<guestfs_file_architecture>.
+
+If the architecture could not be determined, then the
+string C<unknown> is returned.
+
+Please read L<guestfs(3)/INSPECTION> for more details.");
+
+  ("inspect_get_distro", (RString "distro", [Device "root"]), -1, [],
+   [],
+   "get distro of inspected operating system",
+   "\
+This function should only be called with a root device string
+as returned by C<guestfs_inspect_os>.
+
+This returns the distro (distribution) of the inspected operating
+system.
+
+Currently defined distros are:
+
+=over 4
+
+=item \"debian\"
+
+Debian or a Debian-derived distro such as Ubuntu.
+
+=item \"fedora\"
+
+Fedora.
+
+=item \"redhat-based\"
+
+Some Red Hat-derived distro.
+
+=item \"rhel\"
+
+Red Hat Enterprise Linux and some derivatives.
+
+=item \"windows\"
+
+Windows does not have distributions.  This string is
+returned if the OS type is Windows.
+
+=item \"unknown\"
+
+The distro could not be determined.
+
+=back
+
+Future versions of libguestfs may return other strings here.
+The caller should be prepared to handle any string.
+
+Please read L<guestfs(3)/INSPECTION> for more details.");
+
+  ("inspect_get_major_version", (RInt "major", [Device "root"]), -1, [],
+   [],
+   "get major version of inspected operating system",
+   "\
+This function should only be called with a root device string
+as returned by C<guestfs_inspect_os>.
+
+This returns the major version number of the inspected operating
+system.
+
+Windows uses a consistent versioning scheme which is I<not>
+reflected in the popular public names used by the operating system.
+Notably the operating system known as \"Windows 7\" is really
+version 6.1 (ie. major = 6, minor = 1).  You can find out the
+real versions corresponding to releases of Windows by consulting
+Wikipedia or MSDN.
+
+If the version could not be determined, then C<0> is returned.
+
+Please read L<guestfs(3)/INSPECTION> for more details.");
+
+  ("inspect_get_minor_version", (RInt "minor", [Device "root"]), -1, [],
+   [],
+   "get minor version of inspected operating system",
+   "\
+This function should only be called with a root device string
+as returned by C<guestfs_inspect_os>.
+
+This returns the minor version number of the inspected operating
+system.
+
+If the version could not be determined, then C<0> is returned.
+
+Please read L<guestfs(3)/INSPECTION> for more details.
+See also C<guestfs_inspect_get_major_version>.");
+
+  ("inspect_get_product_name", (RString "product", [Device "root"]), -1, [],
+   [],
+   "get product name of inspected operating system",
+   "\
+This function should only be called with a root device string
+as returned by C<guestfs_inspect_os>.
+
+This returns the product name of the inspected operating
+system.  The product name is generally some freeform string
+which can be displayed to the user, but should not be
+parsed by programs.
+
+If the product name could not be determined, then the
+string C<unknown> is returned.
+
+Please read L<guestfs(3)/INSPECTION> for more details.");
+
+  ("inspect_get_mountpoints", (RHashtable "mountpoints", [Device "root"]), -1, [],
+   [],
+   "get mountpoints of inspected operating system",
+   "\
+This function should only be called with a root device string
+as returned by C<guestfs_inspect_os>.
+
+This returns a hash of where we think the filesystems
+associated with this operating system should be mounted.
+Callers should note that this is at best an educated guess
+made by reading configuration files such as C</etc/fstab>.
+
+Each element in the returned hashtable has a key which
+is the path of the mountpoint (eg. C</boot>) and a value
+which is the filesystem that would be mounted there
+(eg. C</dev/sda1>).
+
+Non-mounted devices such as swap devices are I<not>
+returned in this list.
+
+Please read L<guestfs(3)/INSPECTION> for more details.
+See also C<guestfs_inspect_get_filesystems>.");
+
+  ("inspect_get_filesystems", (RStringList "filesystems", [Device "root"]), -1, [],
+   [],
+   "get filesystems associated with inspected operating system",
+   "\
+This function should only be called with a root device string
+as returned by C<guestfs_inspect_os>.
+
+This returns a list of all the filesystems that we think
+are associated with this operating system.  This includes
+the root filesystem, other ordinary filesystems, and
+non-mounted devices like swap partitions.
+
+In the case of a multi-boot virtual machine, it is possible
+for a filesystem to be shared between operating systems.
+
+Please read L<guestfs(3)/INSPECTION> for more details.
+See also C<guestfs_inspect_get_mountpoints>.");
+
+  ("set_network", (RErr, [Bool "network"]), -1, [FishAlias "network"],
+   [],
+   "set enable network flag",
+   "\
+If C<network> is true, then the network is enabled in the
+libguestfs appliance.  The default is false.
+
+This affects whether commands are able to access the network
+(see L<guestfs(3)/RUNNING COMMANDS>).
+
+You must call this before calling C<guestfs_launch>, otherwise
+it has no effect.");
+
+  ("get_network", (RBool "network", []), -1, [],
+   [],
+   "get enable network flag",
+   "\
+This returns the enable network flag.");
+
 ]
 
 (* daemon_functions are any functions which cause some action
@@ -876,7 +1321,7 @@ let daemon_functions = [
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext2"; "/dev/sda1"];
        ["mount"; "/dev/sda1"; "/"];
-       ["write_file"; "/new"; "new file contents"; "0"];
+       ["write"; "/new"; "new file contents"];
        ["cat"; "/new"]], "new file contents")],
    "mount a guest disk at a position in the filesystem",
    "\
@@ -894,8 +1339,15 @@ exist.
 The mounted filesystem is writable, if we have sufficient permissions
 on the underlying device.
 
-The filesystem options C<sync> and C<noatime> are set with this
-call, in order to improve reliability.");
+B<Important note:>
+When you use this call, the filesystem options C<sync> and C<noatime>
+are set implicitly.  This was originally done because we thought it
+would improve reliability, but it turns out that I<-o sync> has a
+very large negative performance impact and negligible effect on
+reliability.  Therefore we recommend that you avoid using
+C<guestfs_mount> in any code that needs performance, and instead
+use C<guestfs_mount_options> (use an empty string for the first
+parameter if you don't want any options).");
 
   ("sync", (RErr, []), 2, [],
    [ InitEmpty, Always, TestRun [["sync"]]],
@@ -915,7 +1367,10 @@ closing the handle.");
    "\
 Touch acts like the L<touch(1)> command.  It can be used to
 update the timestamps on a file, or, if the file does not exist,
-to create a new zero-length file.");
+to create a new zero-length file.
+
+This command only works on regular files, and will fail on other
+file types such as directories, symbolic links, block special etc.");
 
   ("cat", (RString "content", [Pathname "path"]), 4, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutput (
@@ -1179,7 +1634,12 @@ matches exactly one node, the C<value> is returned.");
    [], (* XXX Augeas code needs tests. *)
    "set Augeas path to value",
    "\
-Set the value associated with C<path> to C<value>.");
+Set the value associated with C<path> to C<val>.
+
+In the Augeas API, it is possible to clear a node by setting
+the value to NULL.  Due to an oversight in the libguestfs API
+you cannot do that with this call.  Instead you must use the
+C<guestfs_aug_clear> call.");
 
   ("aug_insert", (RErr, [String "augpath"; String "label"; Bool "before"]), 21, [Optional "augeas"],
    [], (* XXX Augeas code needs tests. *)
@@ -1317,7 +1777,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
-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 *)
@@ -1346,9 +1812,9 @@ See also C<guestfs_is_file>, C<guestfs_is_dir>, C<guestfs_stat>.");
       [["is_file"; "/known-1"]]);
     InitISOFS, Always, TestOutputFalse (
       [["is_file"; "/directory"]])],
-   "test if file exists",
+   "test if a regular file",
    "\
-This returns C<true> if and only if there is a file
+This returns C<true> if and only if there is a regular file
 with the given C<path> name.  Note that it returns false for
 other objects like directories.
 
@@ -1359,7 +1825,7 @@ See also C<guestfs_stat>.");
       [["is_dir"; "/known-3"]]);
     InitISOFS, Always, TestOutputTrue (
       [["is_dir"; "/directory"]])],
-   "test if file exists",
+   "test if a directory",
    "\
 This returns C<true> if and only if there is a directory
 with the given C<path> name.  Note that it returns false for
@@ -1410,17 +1876,17 @@ from the non-empty list of physical volumes C<physvols>.");
        ["lvs"]],
       ["/dev/VG1/LV1"; "/dev/VG1/LV2";
        "/dev/VG2/LV3"; "/dev/VG2/LV4"; "/dev/VG2/LV5"])],
-   "create an LVM volume group",
+   "create an LVM logical volume",
    "\
-This creates an LVM volume group called C<logvol>
+This creates an LVM logical volume called C<logvol>
 on the volume group C<volgroup>, with C<size> megabytes.");
 
   ("mkfs", (RErr, [String "fstype"; Device "device"]), 42, [],
    [InitEmpty, Always, TestOutput (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext2"; "/dev/sda1"];
-       ["mount"; "/dev/sda1"; "/"];
-       ["write_file"; "/new"; "new file contents"; "0"];
+       ["mount_options"; ""; "/dev/sda1"; "/"];
+       ["write"; "/new"; "new file contents"];
        ["cat"; "/new"]], "new file contents")],
    "make a filesystem",
    "\
@@ -1457,25 +1923,10 @@ the string C<,> (comma).
 See also: C<guestfs_sfdisk_l>, C<guestfs_sfdisk_N>,
 C<guestfs_part_init>");
 
-  ("write_file", (RErr, [Pathname "path"; String "content"; Int "size"]), 44, [ProtocolLimitWarning],
-   [InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "new file contents"; "0"];
-       ["cat"; "/new"]], "new file contents");
-    InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "\nnew file contents\n"; "0"];
-       ["cat"; "/new"]], "\nnew file contents\n");
-    InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "\n\n"; "0"];
-       ["cat"; "/new"]], "\n\n");
-    InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; ""; "0"];
-       ["cat"; "/new"]], "");
-    InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "\n\n\n"; "0"];
-       ["cat"; "/new"]], "\n\n\n");
-    InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "\n"; "0"];
-       ["cat"; "/new"]], "\n")],
+  ("write_file", (RErr, [Pathname "path"; String "content"; Int "size"]), 44, [ProtocolLimitWarning; DeprecatedBy "write"],
+   (* Regression test for RHBZ#597135. *)
+   [InitBasicFS, Always, TestLastFail
+      [["write_file"; "/new"; "abc"; "10000"]]],
    "create a file",
    "\
 This call creates a file called C<path>.  The contents of the
@@ -1487,20 +1938,18 @@ then the length is calculated using C<strlen> (so in this case
 the content cannot contain embedded ASCII NULs).
 
 I<NB.> Owing to a bug, writing content containing ASCII NUL
-characters does I<not> work, even if the length is specified.
-We hope to resolve this bug in a future version.  In the meantime
-use C<guestfs_upload>.");
+characters does I<not> work, even if the length is specified.");
 
   ("umount", (RErr, [String "pathordevice"]), 45, [FishAlias "unmount"],
    [InitEmpty, Always, TestOutputListOfDevices (
       [["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"];
-       ["mount"; "/dev/sda1"; "/"];
+       ["mount_options"; ""; "/dev/sda1"; "/"];
        ["umount"; "/"];
        ["mounts"]], [])],
    "unmount a filesystem",
@@ -1531,11 +1980,11 @@ See also: C<guestfs_mountpoints>");
        ["mkfs"; "ext2"; "/dev/sda1"];
        ["mkfs"; "ext2"; "/dev/sda2"];
        ["mkfs"; "ext2"; "/dev/sda3"];
-       ["mount"; "/dev/sda1"; "/"];
+       ["mount_options"; ""; "/dev/sda1"; "/"];
        ["mkdir"; "/mp1"];
-       ["mount"; "/dev/sda2"; "/mp1"];
+       ["mount_options"; ""; "/dev/sda2"; "/mp1"];
        ["mkdir"; "/mp1/mp2"];
-       ["mount"; "/dev/sda3"; "/mp1/mp2"];
+       ["mount_options"; ""; "/dev/sda3"; "/mp1/mp2"];
        ["mkdir"; "/mp1/mp2/mp3"];
        ["umount_all"];
        ["mounts"]], [])],
@@ -1558,19 +2007,32 @@ and physical volumes.");
     InitISOFS, Always, TestOutput (
       [["file"; "/known-1"]], "ASCII text");
     InitISOFS, Always, TestLastFail (
-      [["file"; "/notexists"]])],
+      [["file"; "/notexists"]]);
+    InitISOFS, Always, TestOutput (
+      [["file"; "/abssymlink"]], "symbolic link");
+    InitISOFS, Always, TestOutput (
+      [["file"; "/directory"]], "directory")],
    "determine file type",
    "\
 This call uses the standard L<file(1)> command to determine
-the type or contents of the file.  This also works on devices,
-for example to find out whether a partition contains a filesystem.
+the type or contents of the file.
 
 This call will also transparently look inside various types
 of compressed file.
 
-The exact command which runs is C<file -zbsL path>.  Note in
+The exact command which runs is C<file -zb path>.  Note in
 particular that the filename is not prepended to the output
-(the C<-b> option).");
+(the C<-b> option).
+
+This command can also be used on C</dev/> devices
+(and partitions, LV names).  You can for example use this
+to determine if a device contains a filesystem, although
+it's usually better to use C<guestfs_vfs_type>.
+
+If the C<path> does not begin with C</dev/> then
+this command only works for the content of regular files.
+For other file types (directory, symbolic link etc) it
+will just return the string C<directory> etc.");
 
   ("command", (RString "output", [StringList "arguments"]), 50, [ProtocolLimitWarning],
    [InitBasicFS, Always, TestOutput (
@@ -1861,12 +2323,12 @@ Reread the partition table on C<device>.
 
 This uses the L<blockdev(8)> command.");
 
-  ("upload", (RErr, [FileIn "filename"; String "remotefilename"]), 66, [],
+  ("upload", (RErr, [FileIn "filename"; Dev_or_Path "remotefilename"]), 66, [],
    [InitBasicFS, Always, TestOutput (
       (* Pick a file from cwd which isn't likely to change. *)
       [["upload"; "../COPYING.LIB"; "/COPYING.LIB"];
        ["checksum"; "md5"; "/COPYING.LIB"]],
-        Digest.to_hex (Digest.file "COPYING.LIB"))],
+      Digest.to_hex (Digest.file "COPYING.LIB"))],
    "upload a file from the local machine",
    "\
 Upload local file C<filename> to C<remotefilename> on the
@@ -1876,14 +2338,14 @@ C<filename> can also be a named pipe.
 
 See also C<guestfs_download>.");
 
-  ("download", (RErr, [Dev_or_Path "remotefilename"; FileOut "filename"]), 67, [],
+  ("download", (RErr, [Dev_or_Path "remotefilename"; FileOut "filename"]), 67, [Progress],
    [InitBasicFS, Always, TestOutput (
       (* Pick a file from cwd which isn't likely to change. *)
       [["upload"; "../COPYING.LIB"; "/COPYING.LIB"];
        ["download"; "/COPYING.LIB"; "testdownload.tmp"];
        ["upload"; "testdownload.tmp"; "/upload"];
        ["checksum"; "md5"; "/upload"]],
-        Digest.to_hex (Digest.file "COPYING.LIB"))],
+      Digest.to_hex (Digest.file "COPYING.LIB"))],
    "download a file to the local machine",
    "\
 Download file C<remotefilename> and save it as C<filename>
@@ -1909,7 +2371,10 @@ See also C<guestfs_upload>, C<guestfs_cat>.");
     InitISOFS, Always, TestOutput (
       [["checksum"; "sha384"; "/known-3"]], "5fa7883430f357b5d7b7271d3a1d2872b51d73cba72731de6863d3dea55f30646af2799bef44d5ea776a5ec7941ac640");
     InitISOFS, Always, TestOutput (
-      [["checksum"; "sha512"; "/known-3"]], "2794062c328c6b216dca90443b7f7134c5f40e56bd0ed7853123275a09982a6f992e6ca682f9d2fba34a4c5e870d8fe077694ff831e3032a004ee077e00603f6")],
+      [["checksum"; "sha512"; "/known-3"]], "2794062c328c6b216dca90443b7f7134c5f40e56bd0ed7853123275a09982a6f992e6ca682f9d2fba34a4c5e870d8fe077694ff831e3032a004ee077e00603f6");
+    (* Test for RHBZ#579608, absolute symbolic links. *)
+    InitISOFS, Always, TestOutput (
+      [["checksum"; "sha512"; "/abssymlink"]], "5f57d0639bc95081c53afc63a449403883818edc64da48930ad6b1a4fb49be90404686877743fbcd7c99811f3def7df7bc22635c885c6a8cf79c806b43451c1a")],
    "compute MD5, SHAx or CRC checksum of file",
    "\
 This call computes the MD5, SHAx or CRC checksum of the
@@ -1951,9 +2416,13 @@ Compute the SHA512 hash (using the C<sha512sum> program).
 
 =back
 
-The checksum is returned as a printable string.");
+The checksum is returned as a printable string.
+
+To get the checksum for a device, use C<guestfs_checksum_device>.
 
-  ("tar_in", (RErr, [FileIn "tarfile"; String "directory"]), 69, [],
+To get the checksums for many files, use C<guestfs_checksums_out>.");
+
+  ("tar_in", (RErr, [FileIn "tarfile"; Pathname "directory"]), 69, [],
    [InitBasicFS, Always, TestOutput (
       [["tar_in"; "../images/helloworld.tar"; "/"];
        ["cat"; "/hello"]], "hello\n")],
@@ -1962,7 +2431,8 @@ The checksum is returned as a printable string.");
 This command uploads and unpacks local file C<tarfile> (an
 I<uncompressed> tar file) into C<directory>.
 
-To upload a compressed tarball, use C<guestfs_tgz_in>.");
+To upload a compressed tarball, use C<guestfs_tgz_in>
+or C<guestfs_txz_in>.");
 
   ("tar_out", (RErr, [String "directory"; FileOut "tarfile"]), 70, [],
    [],
@@ -1971,9 +2441,10 @@ To upload a compressed tarball, use C<guestfs_tgz_in>.");
 This command packs the contents of C<directory> and downloads
 it to local file C<tarfile>.
 
-To download a compressed tarball, use C<guestfs_tgz_out>.");
+To download a compressed tarball, use C<guestfs_tgz_out>
+or C<guestfs_txz_out>.");
 
-  ("tgz_in", (RErr, [FileIn "tarball"; String "directory"]), 71, [],
+  ("tgz_in", (RErr, [FileIn "tarball"; Pathname "directory"]), 71, [],
    [InitBasicFS, Always, TestOutput (
       [["tgz_in"; "../images/helloworld.tar.gz"; "/"];
        ["cat"; "/hello"]], "hello\n")],
@@ -1999,7 +2470,7 @@ To download an uncompressed tarball, use C<guestfs_tar_out>.");
        ["mount_ro"; "/dev/sda1"; "/"];
        ["touch"; "/new"]]);
     InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "data"; "0"];
+      [["write"; "/new"; "data"];
        ["umount"; "/"];
        ["mount_ro"; "/dev/sda1"; "/"];
        ["cat"; "/new"]], "data")],
@@ -2014,7 +2485,11 @@ mounts the filesystem with the read-only (I<-o ro>) flag.");
    "\
 This is the same as the C<guestfs_mount> command, but it
 allows you to set the mount options as for the
-L<mount(8)> I<-o> flag.");
+L<mount(8)> I<-o> flag.
+
+If the C<options> parameter is an empty string, then
+no options are passed (all options default to whatever
+the filesystem uses).");
 
   ("mount_vfs", (RErr, [String "options"; String "vfstype"; Device "device"; String "mountpoint"]), 75, [],
    [],
@@ -2143,7 +2618,7 @@ C<device> to C<label>.  Filesystem labels are limited to
 You can use either C<guestfs_tune2fs_l> or C<guestfs_get_e2label>
 to return the existing label on a filesystem.");
 
-  ("get_e2label", (RString "label", [Device "device"]), 81, [],
+  ("get_e2label", (RString "label", [Device "device"]), 81, [DeprecatedBy "vfs_label"],
    [],
    "get the ext2/3/4 filesystem label",
    "\
@@ -2173,14 +2648,19 @@ L<tune2fs(8)> manpage.
 You can use either C<guestfs_tune2fs_l> or C<guestfs_get_e2uuid>
 to return the existing UUID of a filesystem.");
 
-  ("get_e2uuid", (RString "uuid", [Device "device"]), 83, [],
-   [],
+  ("get_e2uuid", (RString "uuid", [Device "device"]), 83, [DeprecatedBy "vfs_uuid"],
+   (* Regression test for RHBZ#597112. *)
+   (let uuid = uuidgen () in
+    [InitBasicFS, Always, TestOutput (
+       [["mke2journal"; "1024"; "/dev/sdb"];
+        ["set_e2uuid"; "/dev/sdb"; uuid];
+        ["get_e2uuid"; "/dev/sdb"]], uuid)]),
    "get the ext2/3/4 filesystem UUID",
    "\
 This returns the ext2/3/4 filesystem UUID of the filesystem on
 C<device>.");
 
-  ("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);
@@ -2218,7 +2698,7 @@ Checking or repairing NTFS volumes is not supported
 
 This command is entirely equivalent to running C<fsck -a -t fstype device>.");
 
-  ("zero", (RErr, [Device "device"]), 85, [],
+  ("zero", (RErr, [Device "device"]), 85, [Progress],
    [InitBasicFS, Always, TestOutput (
       [["umount"; "/dev/sda1"];
        ["zero"; "/dev/sda1"];
@@ -2231,31 +2711,45 @@ How many blocks are zeroed isn't specified (but it's I<not> enough
 to securely wipe the device).  It should be sufficient to remove
 any partition tables, filesystem superblocks and so on.
 
-See also: C<guestfs_scrub_device>.");
+See also: C<guestfs_zero_device>, C<guestfs_scrub_device>.");
 
   ("grub_install", (RErr, [Pathname "root"; Device "device"]), 86, [],
-   (* Test disabled because grub-install incompatible with virtio-blk driver.
-    * See also: https://bugzilla.redhat.com/show_bug.cgi?id=479760
+   (* See:
+    * https://bugzilla.redhat.com/show_bug.cgi?id=484986
+    * https://bugzilla.redhat.com/show_bug.cgi?id=479760
     *)
-   [InitBasicFS, Disabled, TestOutputTrue (
-      [["grub_install"; "/"; "/dev/sda1"];
+   [InitBasicFS, Always, TestOutputTrue (
+      [["mkdir_p"; "/boot/grub"];
+       ["write"; "/boot/grub/device.map"; "(hd0) /dev/vda"];
+       ["grub_install"; "/"; "/dev/vda"];
        ["is_dir"; "/boot"]])],
    "install GRUB",
    "\
 This command installs GRUB (the Grand Unified Bootloader) on
-C<device>, with the root directory being C<root>.");
+C<device>, with the root directory being C<root>.
+
+Note: If grub-install reports the error
+\"No suitable drive was found in the generated device map.\"
+it may be that you need to create a C</boot/grub/device.map>
+file first that contains the mapping between grub device names
+and Linux device names.  It is usually sufficient to create
+a file containing:
+
+ (hd0) /dev/vda
+
+replacing C</dev/vda> with the name of the installation device.");
 
   ("cp", (RErr, [Pathname "src"; Pathname "dest"]), 87, [],
    [InitBasicFS, Always, TestOutput (
-      [["write_file"; "/old"; "file content"; "0"];
+      [["write"; "/old"; "file content"];
        ["cp"; "/old"; "/new"];
        ["cat"; "/new"]], "file content");
     InitBasicFS, Always, TestOutputTrue (
-      [["write_file"; "/old"; "file content"; "0"];
+      [["write"; "/old"; "file content"];
        ["cp"; "/old"; "/new"];
        ["is_file"; "/old"]]);
     InitBasicFS, Always, TestOutput (
-      [["write_file"; "/old"; "file content"; "0"];
+      [["write"; "/old"; "file content"];
        ["mkdir"; "/dir"];
        ["cp"; "/old"; "/dir/new"];
        ["cat"; "/dir/new"]], "file content")],
@@ -2268,7 +2762,7 @@ either a destination filename or destination directory.");
    [InitBasicFS, Always, TestOutput (
       [["mkdir"; "/olddir"];
        ["mkdir"; "/newdir"];
-       ["write_file"; "/olddir/file"; "file content"; "0"];
+       ["write"; "/olddir/file"; "file content"];
        ["cp_a"; "/olddir"; "/newdir"];
        ["cat"; "/newdir/olddir/file"]], "file content")],
    "copy a file or directory recursively",
@@ -2278,11 +2772,11 @@ recursively using the C<cp -a> command.");
 
   ("mv", (RErr, [Pathname "src"; Pathname "dest"]), 89, [],
    [InitBasicFS, Always, TestOutput (
-      [["write_file"; "/old"; "file content"; "0"];
+      [["write"; "/old"; "file content"];
        ["mv"; "/old"; "/new"];
        ["cat"; "/new"]], "file content");
     InitBasicFS, Always, TestOutputFalse (
-      [["write_file"; "/old"; "file content"; "0"];
+      [["write"; "/old"; "file content"];
        ["mv"; "/old"; "/new"];
        ["is_file"; "/old"]])],
    "move a file",
@@ -2331,12 +2825,12 @@ or attached block device(s) in any other way.");
 
   ("equal", (RBool "equality", [Pathname "file1"; Pathname "file2"]), 93, [],
    [InitBasicFS, Always, TestOutputTrue (
-      [["write_file"; "/file1"; "contents of a file"; "0"];
+      [["write"; "/file1"; "contents of a file"];
        ["cp"; "/file1"; "/file2"];
        ["equal"; "/file1"; "/file2"]]);
     InitBasicFS, Always, TestOutputFalse (
-      [["write_file"; "/file1"; "contents of a file"; "0"];
-       ["write_file"; "/file2"; "contents of another file"; "0"];
+      [["write"; "/file1"; "contents of a file"];
+       ["write"; "/file2"; "contents of another file"];
        ["equal"; "/file1"; "/file2"]]);
     InitBasicFS, Always, TestLastFail (
       [["equal"; "/file1"; "/file2"]])],
@@ -2351,7 +2845,10 @@ The external L<cmp(1)> program is used for the comparison.");
    [InitISOFS, Always, TestOutputList (
       [["strings"; "/known-5"]], ["abcdefghi"; "jklmnopqr"]);
     InitISOFS, Always, TestOutputList (
-      [["strings"; "/empty"]], [])],
+      [["strings"; "/empty"]], []);
+    (* Test for RHBZ#579608, absolute symbolic links. *)
+    InitISOFS, Always, TestRun (
+      [["strings"; "/abssymlink"]])],
    "print the printable strings in a file",
    "\
 This runs the L<strings(1)> command on a file and returns
@@ -2360,18 +2857,47 @@ the list of printable strings found.");
   ("strings_e", (RStringList "stringsout", [String "encoding"; Pathname "path"]), 95, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["strings_e"; "b"; "/known-5"]], []);
-    InitBasicFS, Disabled, TestOutputList (
-      [["write_file"; "/new"; "\000h\000e\000l\000l\000o\000\n\000w\000o\000r\000l\000d\000\n"; "24"];
+    InitBasicFS, Always, TestOutputList (
+      [["write"; "/new"; "\000h\000e\000l\000l\000o\000\n\000w\000o\000r\000l\000d\000\n"];
        ["strings_e"; "b"; "/new"]], ["hello"; "world"])],
    "print the printable strings in a file",
    "\
 This is like the C<guestfs_strings> command, but allows you to
-specify the encoding.
+specify the encoding of strings that are looked for in
+the source file C<path>.
+
+Allowed encodings are:
+
+=over 4
 
-See the L<strings(1)> manpage for the full list of encodings.
+=item s
 
-Commonly useful encodings are C<l> (lower case L) which will
-show strings inside Windows/x86 files.
+Single 7-bit-byte characters like ASCII and the ASCII-compatible
+parts of ISO-8859-X (this is what C<guestfs_strings> uses).
+
+=item S
+
+Single 8-bit-byte characters.
+
+=item b
+
+16-bit big endian strings such as those encoded in
+UTF-16BE or UCS-2BE.
+
+=item l (lower case letter L)
+
+16-bit little endian such as UTF-16LE and UCS-2LE.
+This is useful for examining binaries in Windows guests.
+
+=item B
+
+32-bit big endian such as UCS-4BE.
+
+=item L
+
+32-bit little endian such as UCS-4LE.
+
+=back
 
 The returned strings are transcoded to UTF-8.");
 
@@ -2382,7 +2908,10 @@ The returned strings are transcoded to UTF-8.");
      * commands to segfault.
      *)
     InitISOFS, Always, TestRun (
-      [["hexdump"; "/100krandom"]])],
+      [["hexdump"; "/100krandom"]]);
+    (* Test for RHBZ#579608, absolute symbolic links. *)
+    InitISOFS, Always, TestRun (
+      [["hexdump"; "/abssymlink"]])],
    "dump a file in hexadecimal",
    "\
 This runs C<hexdump -C> on the given C<path>.  The result is
@@ -2392,11 +2921,11 @@ the human-readable, canonical hex dump of the file.");
    [InitNone, Always, TestOutput (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext3"; "/dev/sda1"];
-       ["mount"; "/dev/sda1"; "/"];
-       ["write_file"; "/new"; "test file"; "0"];
+       ["mount_options"; ""; "/dev/sda1"; "/"];
+       ["write"; "/new"; "test file"];
        ["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",
    "\
@@ -2497,15 +3026,22 @@ are activated or deactivated.");
        ["vgcreate"; "VG"; "/dev/sda1"];
        ["lvcreate"; "LV"; "VG"; "10"];
        ["mkfs"; "ext2"; "/dev/VG/LV"];
-       ["mount"; "/dev/VG/LV"; "/"];
-       ["write_file"; "/new"; "test content"; "0"];
+       ["mount_options"; ""; "/dev/VG/LV"; "/"];
+       ["write"; "/new"; "test content"];
        ["umount"; "/"];
        ["lvresize"; "/dev/VG/LV"; "20"];
        ["e2fsck_f"; "/dev/VG/LV"];
        ["resize2fs"; "/dev/VG/LV"];
-       ["mount"; "/dev/VG/LV"; "/"];
-       ["cat"; "/new"]], "test content")],
-   "resize an LVM logical volume",
+       ["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
 volume to C<mbytes>.  When reducing, data in the reduced part
@@ -2513,9 +3049,9 @@ is lost.");
 
   ("resize2fs", (RErr, [Device "device"]), 106, [],
    [], (* lvresize tests this *)
-   "resize an ext2/ext3 filesystem",
+   "resize an ext2, ext3 or ext4 filesystem",
    "\
-This resizes an ext2 or ext3 filesystem to match the size of
+This resizes an ext2, ext3 or ext4 filesystem to match the size of
 the underlying device.
 
 I<Note:> It is sometimes required that you run C<guestfs_e2fsck_f>
@@ -2678,7 +3214,7 @@ manual page for more details.");
 
   ("scrub_file", (RErr, [Pathname "file"]), 115, [Optional "scrub"],
    [InitBasicFS, Always, TestRun (
-      [["write_file"; "/file"; "content"; "0"];
+      [["write"; "/file"; "content"];
        ["scrub_file"; "/file"]])],
    "scrub (securely wipe) a file",
    "\
@@ -2730,7 +3266,10 @@ See also: L<mkdtemp(3)>");
 
   ("wc_l", (RInt "lines", [Pathname "path"]), 118, [],
    [InitISOFS, Always, TestOutputInt (
-      [["wc_l"; "/10klines"]], 10000)],
+      [["wc_l"; "/10klines"]], 10000);
+    (* Test for RHBZ#579608, absolute symbolic links. *)
+    InitISOFS, Always, TestOutputInt (
+      [["wc_l"; "/abssymlink"]], 10000)],
    "count lines in a file",
    "\
 This command counts the lines in a file, using the
@@ -2754,7 +3293,10 @@ C<wc -c> external command.");
 
   ("head", (RStringList "lines", [Pathname "path"]), 121, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
-      [["head"; "/10klines"]], ["0abcdefghijklmnopqrstuvwxyz";"1abcdefghijklmnopqrstuvwxyz";"2abcdefghijklmnopqrstuvwxyz";"3abcdefghijklmnopqrstuvwxyz";"4abcdefghijklmnopqrstuvwxyz";"5abcdefghijklmnopqrstuvwxyz";"6abcdefghijklmnopqrstuvwxyz";"7abcdefghijklmnopqrstuvwxyz";"8abcdefghijklmnopqrstuvwxyz";"9abcdefghijklmnopqrstuvwxyz"])],
+      [["head"; "/10klines"]], ["0abcdefghijklmnopqrstuvwxyz";"1abcdefghijklmnopqrstuvwxyz";"2abcdefghijklmnopqrstuvwxyz";"3abcdefghijklmnopqrstuvwxyz";"4abcdefghijklmnopqrstuvwxyz";"5abcdefghijklmnopqrstuvwxyz";"6abcdefghijklmnopqrstuvwxyz";"7abcdefghijklmnopqrstuvwxyz";"8abcdefghijklmnopqrstuvwxyz";"9abcdefghijklmnopqrstuvwxyz"]);
+    (* Test for RHBZ#579608, absolute symbolic links. *)
+    InitISOFS, Always, TestOutputList (
+      [["head"; "/abssymlink"]], ["0abcdefghijklmnopqrstuvwxyz";"1abcdefghijklmnopqrstuvwxyz";"2abcdefghijklmnopqrstuvwxyz";"3abcdefghijklmnopqrstuvwxyz";"4abcdefghijklmnopqrstuvwxyz";"5abcdefghijklmnopqrstuvwxyz";"6abcdefghijklmnopqrstuvwxyz";"7abcdefghijklmnopqrstuvwxyz";"8abcdefghijklmnopqrstuvwxyz";"9abcdefghijklmnopqrstuvwxyz"])],
    "return first 10 lines of a file",
    "\
 This command returns up to the first 10 lines of a file as
@@ -2910,7 +3452,17 @@ named pipes (FIFOs).
 The C<mode> parameter should be the mode, using the standard
 constants.  C<devmajor> and C<devminor> are the
 device major and minor numbers, only used when creating block
-and character special devices.");
+and character special devices.
+
+Note that, just like L<mknod(2)>, the mode must be bitwise
+OR'd with S_IFBLK, S_IFCHR, S_IFIFO or S_IFSOCK (otherwise this call
+just creates a regular file).  These constants are
+available in the standard Linux header files, or you can use
+C<guestfs_mknod_b>, C<guestfs_mknod_c> or C<guestfs_mkfifo>
+which are wrappers around this command which bitwise OR
+in the appropriate constant for you.
+
+The mode actually set is affected by the umask.");
 
   ("mkfifo", (RErr, [Int "mode"; Pathname "path"]), 134, [Optional "mknod"],
    [InitBasicFS, Always, TestOutputStruct (
@@ -2920,7 +3472,9 @@ and character special devices.");
    "\
 This call creates a FIFO (named pipe) called C<path> with
 mode C<mode>.  It is just a convenient wrapper around
-C<guestfs_mknod>.");
+C<guestfs_mknod>.
+
+The mode actually set is affected by the umask.");
 
   ("mknod_b", (RErr, [Int "mode"; Int "devmajor"; Int "devminor"; Pathname "path"]), 135, [Optional "mknod"],
    [InitBasicFS, Always, TestOutputStruct (
@@ -2930,7 +3484,9 @@ C<guestfs_mknod>.");
    "\
 This call creates a block device node called C<path> with
 mode C<mode> and device major/minor C<devmajor> and C<devminor>.
-It is just a convenient wrapper around C<guestfs_mknod>.");
+It is just a convenient wrapper around C<guestfs_mknod>.
+
+The mode actually set is affected by the umask.");
 
   ("mknod_c", (RErr, [Int "mode"; Int "devmajor"; Int "devminor"; Pathname "path"]), 136, [Optional "mknod"],
    [InitBasicFS, Always, TestOutputStruct (
@@ -2940,12 +3496,13 @@ It is just a convenient wrapper around C<guestfs_mknod>.");
    "\
 This call creates a char device node called C<path> with
 mode C<mode> and device major/minor C<devmajor> and C<devminor>.
-It is just a convenient wrapper around C<guestfs_mknod>.");
+It is just a convenient wrapper around C<guestfs_mknod>.
 
-  ("umask", (RInt "oldmask", [Int "mask"]), 137, [],
-   [], (* XXX umask is one of those stateful things that we should
-        * reset between each test.
-        *)
+The mode actually set is affected by the umask.");
+
+  ("umask", (RInt "oldmask", [Int "mask"]), 137, [FishOutput FishOutputOctal],
+   [InitEmpty, Always, TestOutputInt (
+      [["umask"; "0o22"]], 0o22)],
    "set file mode creation mask (umask)",
    "\
 This function sets the mask used for creating new files and
@@ -2960,7 +3517,8 @@ The default umask is C<022>.  This is important because it
 means that directories and device nodes will be created with
 C<0644> or C<0755> mode even if you specify C<0777>.
 
-See also L<umask(2)>, C<guestfs_mknod>, C<guestfs_mkdir>.
+See also C<guestfs_get_umask>,
+L<umask(2)>, C<guestfs_mknod>, C<guestfs_mkdir>.
 
 This call returns the previous umask.");
 
@@ -3013,7 +3571,7 @@ Unknown file type
 
 =item '?'
 
-The L<readdir(3)> returned a C<d_type> field with an
+The L<readdir(3)> call returned a C<d_type> field with an
 unexpected value
 
 =back
@@ -3115,11 +3673,11 @@ a list of devices.  This one returns a hash table (map) of
 device name to directory where the device is mounted.");
 
   ("mkmountpoint", (RErr, [String "exemptpath"]), 148, [],
-  (* This is a special case: while you would expect a parameter
-   * of type "Pathname", that doesn't work, because it implies
-   * NEED_ROOT in the generated calling code in stubs.c, and
-   * this function cannot use NEED_ROOT.
-   *)
+   (* This is a special case: while you would expect a parameter
+    * of type "Pathname", that doesn't work, because it implies
+    * NEED_ROOT in the generated calling code in stubs.c, and
+    * this function cannot use NEED_ROOT.
+    *)
    [],
    "create a mountpoint",
    "\
@@ -3157,7 +3715,20 @@ for full details.");
 
   ("read_file", (RBufferOut "content", [Pathname "path"]), 150, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputBuffer (
-      [["read_file"; "/known-4"]], "abc\ndef\nghi")],
+      [["read_file"; "/known-4"]], "abc\ndef\nghi");
+    (* Test various near large, large and too large files (RHBZ#589039). *)
+    InitBasicFS, Always, TestLastFail (
+      [["touch"; "/a"];
+       ["truncate_size"; "/a"; "4194303"]; (* GUESTFS_MESSAGE_MAX - 1 *)
+       ["read_file"; "/a"]]);
+    InitBasicFS, Always, TestLastFail (
+      [["touch"; "/a"];
+       ["truncate_size"; "/a"; "4194304"]; (* GUESTFS_MESSAGE_MAX *)
+       ["read_file"; "/a"]]);
+    InitBasicFS, Always, TestLastFail (
+      [["touch"; "/a"];
+       ["truncate_size"; "/a"; "41943040"]; (* GUESTFS_MESSAGE_MAX * 10 *)
+       ["read_file"; "/a"]])],
    "read a file",
    "\
 This calls returns the contents of the file C<path> as a
@@ -3172,7 +3743,10 @@ in the total size of file that can be handled.");
    [InitISOFS, Always, TestOutputList (
       [["grep"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"]);
     InitISOFS, Always, TestOutputList (
-      [["grep"; "nomatch"; "/test-grep.txt"]], [])],
+      [["grep"; "nomatch"; "/test-grep.txt"]], []);
+    (* Test for RHBZ#579608, absolute symbolic links. *)
+    InitISOFS, Always, TestOutputList (
+      [["grep"; "nomatch"; "/abssymlink"]], [])],
    "return lines matching a pattern",
    "\
 This calls the external C<grep> program and returns the
@@ -3266,7 +3840,7 @@ matching lines.");
 This calls the external C<zfgrep -i> program and returns the
 matching lines.");
 
-  ("realpath", (RString "rpath", [Pathname "path"]), 163, [],
+  ("realpath", (RString "rpath", [Pathname "path"]), 163, [Optional "realpath"],
    [InitISOFS, Always, TestOutput (
       [["realpath"; "/../directory"]], "/directory")],
    "canonicalized absolute pathname",
@@ -3320,7 +3894,7 @@ The C<-f> option removes the link (C<linkname>) if it exists already.");
    "\
 This command reads the target of a symbolic link.");
 
-  ("fallocate", (RErr, [Pathname "path"; Int "len"]), 169, [],
+  ("fallocate", (RErr, [Pathname "path"; Int "len"]), 169, [DeprecatedBy "fallocate64"],
    [InitBasicFS, Always, TestOutputStruct (
       [["fallocate"; "/a"; "1000000"];
        ["stat"; "/a"]], [CompareWithInt ("size", 1_000_000)])],
@@ -3547,23 +4121,38 @@ and C<guestfs_setcon>");
    [InitEmpty, Always, TestOutput (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs_b"; "ext2"; "4096"; "/dev/sda1"];
-       ["mount"; "/dev/sda1"; "/"];
-       ["write_file"; "/new"; "new file contents"; "0"];
-       ["cat"; "/new"]], "new file contents")],
+       ["mount_options"; ""; "/dev/sda1"; "/"];
+       ["write"; "/new"; "new file contents"];
+       ["cat"; "/new"]], "new file contents");
+    InitEmpty, Always, TestRun (
+      [["part_disk"; "/dev/sda"; "mbr"];
+       ["mkfs_b"; "vfat"; "32768"; "/dev/sda1"]]);
+    InitEmpty, Always, TestLastFail (
+      [["part_disk"; "/dev/sda"; "mbr"];
+       ["mkfs_b"; "vfat"; "32769"; "/dev/sda1"]]);
+    InitEmpty, Always, TestLastFail (
+      [["part_disk"; "/dev/sda"; "mbr"];
+       ["mkfs_b"; "vfat"; "33280"; "/dev/sda1"]]);
+    InitEmpty, IfAvailable "ntfsprogs", TestRun (
+      [["part_disk"; "/dev/sda"; "mbr"];
+       ["mkfs_b"; "ntfs"; "32768"; "/dev/sda1"]])],
    "make a filesystem with block size",
    "\
 This call is similar to C<guestfs_mkfs>, but it allows you to
 control the block size of the resulting filesystem.  Supported
 block sizes depend on the filesystem type, but typically they
-are C<1024>, C<2048> or C<4096> only.");
+are C<1024>, C<2048> or C<4096> only.
+
+For VFAT and NTFS the C<blocksize> parameter is treated as
+the requested cluster size.");
 
   ("mke2journal", (RErr, [Int "blocksize"; Device "device"]), 188, [],
    [InitEmpty, Always, TestOutput (
       [["sfdiskM"; "/dev/sda"; ",100 ,"];
        ["mke2journal"; "4096"; "/dev/sda1"];
        ["mke2fs_J"; "ext2"; "4096"; "/dev/sda2"; "/dev/sda1"];
-       ["mount"; "/dev/sda2"; "/"];
-       ["write_file"; "/new"; "new file contents"; "0"];
+       ["mount_options"; ""; "/dev/sda2"; "/"];
+       ["write"; "/new"; "new file contents"];
        ["cat"; "/new"]], "new file contents")],
    "make ext2/3/4 external journal",
    "\
@@ -3577,8 +4166,8 @@ to the command:
       [["sfdiskM"; "/dev/sda"; ",100 ,"];
        ["mke2journal_L"; "4096"; "JOURNAL"; "/dev/sda1"];
        ["mke2fs_JL"; "ext2"; "4096"; "/dev/sda2"; "JOURNAL"];
-       ["mount"; "/dev/sda2"; "/"];
-       ["write_file"; "/new"; "new file contents"; "0"];
+       ["mount_options"; ""; "/dev/sda2"; "/"];
+       ["write"; "/new"; "new file contents"];
        ["cat"; "/new"]], "new file contents")],
    "make ext2/3/4 external journal with label",
    "\
@@ -3590,8 +4179,8 @@ 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];
-        ["mount"; "/dev/sda2"; "/"];
-        ["write_file"; "/new"; "new file contents"; "0"];
+        ["mount_options"; ""; "/dev/sda2"; "/"];
+        ["write"; "/new"; "new file contents"];
         ["cat"; "/new"]], "new file contents")]),
    "make ext2/3/4 external journal with UUID",
    "\
@@ -3638,12 +4227,12 @@ was built (see C<appliance/kmod.whitelist.in> in the source).");
 
   ("echo_daemon", (RString "output", [StringList "words"]), 195, [],
    [InitNone, Always, TestOutput (
-     [["echo_daemon"; "This is a test"]], "This is a test"
-   )],
+      [["echo_daemon"; "This is a test"]], "This is a test"
+    )],
    "echo arguments back to the client",
    "\
-This command concatenate the list of C<words> passed with single spaces between
-them and returns the resulting string.
+This command concatenates the list of C<words> passed with single spaces
+between them and returns the resulting string.
 
 You can use this command to test the connection through to the daemon.
 
@@ -3745,16 +4334,17 @@ See also C<guestfs_realpath>.");
       [["vfs_type"; "/dev/sda1"]], "ext2")],
    "get the Linux VFS type corresponding to a mounted device",
    "\
-This command gets the block device type corresponding to
-a mounted device called C<device>.
+This command gets the filesystem type corresponding to
+the filesystem on C<device>.
 
-Usually the result is the name of the Linux VFS module that
-is used to mount this device (probably determined automatically
-if you used the C<guestfs_mount> call).");
+For most filesystems, the result is the name of the Linux
+VFS module which would be used to mount this filesystem
+if you mounted it without specifying the filesystem type.
+For example a string such as C<ext3> or C<ntfs>.");
 
   ("truncate", (RErr, [Pathname "path"]), 199, [],
    [InitBasicFS, Always, TestOutputStruct (
-      [["write_file"; "/test"; "some stuff so size is not zero"; "0"];
+      [["write"; "/test"; "some stuff so size is not zero"];
        ["truncate"; "/test"];
        ["stat"; "/test"]], [CompareWithInt ("size", 0)])],
    "truncate a file to zero size",
@@ -3770,8 +4360,13 @@ file must exist already.");
    "truncate a file to a particular size",
    "\
 This command truncates C<path> to size C<size> bytes.  The file
-must exist already.  If the file is smaller than C<size> then
-the file is extended to the required size with null bytes.");
+must exist already.
+
+If the current file size is less than C<size> then
+the file is extended to the required size with zero bytes.
+This creates a sparse file (ie. disk blocks are not allocated
+for the file until you write to it).  To create a non-sparse
+file of zeroes, use C<guestfs_fallocate64> instead.");
 
   ("utimens", (RErr, [Pathname "path"; Int64 "atsecs"; Int64 "atnsecs"; Int64 "mtsecs"; Int64 "mtnsecs"]), 201, [],
    [InitBasicFS, Always, TestOutputStruct (
@@ -3804,7 +4399,13 @@ C<*secs> field is ignored in this case).");
    "create a directory with a particular mode",
    "\
 This command creates a directory, setting the initial permissions
-of the directory to C<mode>.  See also C<guestfs_mkdir>.");
+of the directory to C<mode>.
+
+For common Linux filesystems, the actual mode which is set will
+be C<mode & ~umask & 01777>.  Non-native-Linux filesystems may
+interpret the mode in other ways.
+
+See also C<guestfs_mkdir>, C<guestfs_umask>");
 
   ("lchown", (RErr, [Int "owner"; Int "group"; Pathname "path"]), 203, [],
    [], (* XXX *)
@@ -3875,7 +4476,7 @@ C<names> is the list of files from this directory.
 
 On return you get a list of strings, with a one-to-one
 correspondence to the C<names> list.  Each string is the
-value of the symbol link.
+value of the symbolic link.
 
 If the C<readlink(2)> operation fails on any name, then
 the corresponding result string is the empty string C<\"\">.
@@ -3902,7 +4503,9 @@ This command lets you read part of a file.  It reads C<count>
 bytes of the file, starting at C<offset>, from file C<path>.
 
 This may read fewer bytes than requested.  For further details
-see the L<pread(2)> system call.");
+see the L<pread(2)> system call.
+
+See also C<guestfs_pwrite>.");
 
   ("part_init", (RErr, [Device "device"; String "parttype"]), 208, [],
    [InitEmpty, Always, TestRun (
@@ -4030,10 +4633,9 @@ but other possible values are described in C<guestfs_part_init>.");
 This sets the bootable flag on partition numbered C<partnum> on
 device C<device>.  Note that partitions are numbered from 1.
 
-The bootable flag is used by some PC BIOSes to determine which
-partition to boot from.  It is by no means universally recognized,
-and in any case if your operating system installed a boot
-sector on the device itself, then that takes precedence.");
+The bootable flag is used by some operating systems (notably
+Windows) to determine which partition to boot from.  It is by
+no means universally recognized.");
 
   ("part_set_name", (RErr, [Device "device"; Int "partnum"; String "name"]), 212, [],
    [InitEmpty, Always, TestRun (
@@ -4091,7 +4693,7 @@ partition table), C<gpt> (a GPT/EFI-style partition table).  Other
 values are possible, although unusual.  See C<guestfs_part_init>
 for a full list.");
 
-  ("fill", (RErr, [Int "c"; Int "len"; Pathname "path"]), 215, [],
+  ("fill", (RErr, [Int "c"; Int "len"; Pathname "path"]), 215, [Progress],
    [InitBasicFS, Always, TestOutputBuffer (
       [["fill"; "0x63"; "10"; "/test"];
        ["read_file"; "/test"]], "cccccccccc")],
@@ -4102,31 +4704,35 @@ content of the file is C<len> octets of C<c>, where C<c>
 must be a number in the range C<[0..255]>.
 
 To fill a file with zero bytes (sparsely), it is
-much more efficient to use C<guestfs_truncate_size>.");
+much more efficient to use C<guestfs_truncate_size>.
+To create a file with a pattern of repeating bytes
+use C<guestfs_fill_pattern>.");
 
   ("available", (RErr, [StringList "groups"]), 216, [],
-   [],
+   [InitNone, Always, TestRun [["available"; ""]]],
    "test availability of some parts of the API",
    "\
 This command is used to check the availability of some
-groups of libguestfs functions which not all builds of
-libguestfs will be able to provide.
+groups of functionality in the appliance, which not all builds of
+the libguestfs appliance will be able to provide.
 
-The precise libguestfs function groups that may be checked by this
-command are listed in L<guestfs(3)/AVAILABILITY>.
+The libguestfs groups, and the functions that those
+groups correspond to, are listed in L<guestfs(3)/AVAILABILITY>.
+You can also fetch this list at runtime by calling
+C<guestfs_available_all_groups>.
 
-The argument C<groups> is a list of API group names, eg:
+The argument C<groups> is a list of group names, eg:
 C<[\"inotify\", \"augeas\"]> would check for the availability of
-the C<guestfs_inotify_*> functions and C<guestfs_aug_*>
-(partition editing) functions.
+the Linux inotify functions and Augeas (configuration file
+editing) functions.
 
 The command returns no error if I<all> requested groups are available.
 
-It returns an error if one or more of the requested
-groups is unavailable.
+It fails with an error if one or more of the requested
+groups is unavailable in the appliance.
 
 If an unknown group name is included in the
-list of C<groups> then an error is always returned.
+list of groups then an error is always returned.
 
 I<Notes:>
 
@@ -4135,7 +4741,8 @@ I<Notes:>
 =item *
 
 You must call C<guestfs_launch> before calling this function.
-The reason is because we don't know what function groups are
+
+The reason is because we don't know what groups are
 supported by the appliance/daemon until it is running and can
 be queried.
 
@@ -4162,1231 +4769,2093 @@ See also C<guestfs_version>.
 
 =back");
 
-]
+  ("dd", (RErr, [Dev_or_Path "src"; Dev_or_Path "dest"]), 217, [],
+   [InitBasicFS, Always, TestOutputBuffer (
+      [["write"; "/src"; "hello, world"];
+       ["dd"; "/src"; "/dest"];
+       ["read_file"; "/dest"]], "hello, world")],
+   "copy from source to destination using dd",
+   "\
+This command copies from one source device or file C<src>
+to another destination device or file C<dest>.  Normally you
+would use this to copy to or from a device or partition, for
+example to duplicate a filesystem.
 
-let all_functions = non_daemon_functions @ daemon_functions
+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 (see C<guestfs_copy_size>).");
 
-(* In some places we want the functions to be displayed sorted
- * alphabetically, so this is useful:
- *)
-let all_functions_sorted =
-  List.sort (fun (n1,_,_,_,_,_,_) (n2,_,_,_,_,_,_) ->
-               compare n1 n2) all_functions
+  ("filesize", (RInt64 "size", [Pathname "file"]), 218, [],
+   [InitBasicFS, Always, TestOutputInt (
+      [["write"; "/file"; "hello, world"];
+       ["filesize"; "/file"]], 12)],
+   "return the size of the file in bytes",
+   "\
+This command returns the size of C<file> in bytes.
 
-(* Field types for structures. *)
-type field =
-  | FChar                      (* C 'char' (really, a 7 bit byte). *)
-  | FString                    (* nul-terminated ASCII string, NOT NULL. *)
-  | FBuffer                    (* opaque buffer of bytes, (char *, int) pair *)
-  | FUInt32
-  | FInt32
-  | FUInt64
-  | FInt64
-  | FBytes                     (* Any int measure that counts bytes. *)
-  | FUUID                      (* 32 bytes long, NOT nul-terminated. *)
-  | FOptPercent                        (* [0..100], or -1 meaning "not present". *)
+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>.");
 
-(* Because we generate extra parsing code for LVM command line tools,
- * we have to pull out the LVM columns separately here.
- *)
-let lvm_pv_cols = [
-  "pv_name", FString;
-  "pv_uuid", FUUID;
-  "pv_fmt", FString;
-  "pv_size", FBytes;
-  "dev_size", FBytes;
-  "pv_free", FBytes;
-  "pv_used", FBytes;
-  "pv_attr", FString (* XXX *);
-  "pv_pe_count", FInt64;
-  "pv_pe_alloc_count", FInt64;
-  "pv_tags", FString;
-  "pe_start", FBytes;
-  "pv_mda_count", FInt64;
-  "pv_mda_free", FBytes;
-  (* Not in Fedora 10:
-     "pv_mda_size", FBytes;
-  *)
-]
-let lvm_vg_cols = [
-  "vg_name", FString;
-  "vg_uuid", FUUID;
-  "vg_fmt", FString;
-  "vg_attr", FString (* XXX *);
-  "vg_size", FBytes;
-  "vg_free", FBytes;
-  "vg_sysid", FString;
-  "vg_extent_size", FBytes;
-  "vg_extent_count", FInt64;
-  "vg_free_count", FInt64;
-  "max_lv", FInt64;
-  "max_pv", FInt64;
-  "pv_count", FInt64;
-  "lv_count", FInt64;
-  "snap_count", FInt64;
-  "vg_seqno", FInt64;
-  "vg_tags", FString;
-  "vg_mda_count", FInt64;
-  "vg_mda_free", FBytes;
-  (* Not in Fedora 10:
-     "vg_mda_size", FBytes;
-  *)
-]
-let lvm_lv_cols = [
-  "lv_name", FString;
-  "lv_uuid", FUUID;
-  "lv_attr", FString (* XXX *);
-  "lv_major", FInt64;
-  "lv_minor", FInt64;
-  "lv_kernel_major", FInt64;
-  "lv_kernel_minor", FInt64;
-  "lv_size", FBytes;
-  "seg_count", FInt64;
-  "origin", FString;
-  "snap_percent", FOptPercent;
-  "copy_percent", FOptPercent;
-  "move_pv", FString;
-  "lv_tags", FString;
-  "mirror_log", FString;
-  "modules", FString;
-]
+  ("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>.");
 
-(* Names and fields in all structures (in RStruct and RStructList)
- * that we support.
- *)
-let structs = [
-  (* The old RIntBool return type, only ever used for aug_defnode.  Do
-   * not use this struct in any new code.
-   *)
-  "int_bool", [
-    "i", FInt32;               (* for historical compatibility *)
-    "b", FInt32;               (* for historical compatibility *)
-  ];
+  ("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>.");
 
-  (* LVM PVs, VGs, LVs. *)
-  "lvm_pv", lvm_pv_cols;
-  "lvm_vg", lvm_vg_cols;
-  "lvm_lv", lvm_lv_cols;
+  ("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.
 
-  (* Column names and types from stat structures.
-   * NB. Can't use things like 'st_atime' because glibc header files
-   * define some of these as macros.  Ugh.
-   *)
-  "stat", [
-    "dev", FInt64;
-    "ino", FInt64;
-    "mode", FInt64;
-    "nlink", FInt64;
-    "uid", FInt64;
-    "gid", FInt64;
-    "rdev", FInt64;
-    "size", FInt64;
-    "blksize", FInt64;
-    "blocks", FInt64;
-    "atime", FInt64;
-    "mtime", FInt64;
-    "ctime", FInt64;
-  ];
-  "statvfs", [
-    "bsize", FInt64;
-    "frsize", FInt64;
-    "blocks", FInt64;
-    "bfree", FInt64;
-    "bavail", FInt64;
-    "files", FInt64;
-    "ffree", FInt64;
-    "favail", FInt64;
-    "fsid", FInt64;
-    "flag", FInt64;
-    "namemax", FInt64;
-  ];
+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:
 
-  (* Column names in dirent structure. *)
-  "dirent", [
-    "ino", FInt64;
-    (* 'b' 'c' 'd' 'f' (FIFO) 'l' 'r' (regular file) 's' 'u' '?' *)
-    "ftyp", FChar;
-    "name", FString;
-  ];
+ initrd-cat /boot/initrd-<version>.img init
 
-  (* Version numbers. *)
-  "version", [
-    "major", FInt64;
-    "minor", FInt64;
-    "release", FInt64;
-    "extra", FString;
-  ];
+See also C<guestfs_initrd_list>.");
 
-  (* Extended attribute. *)
-  "xattr", [
-    "attrname", FString;
-    "attrval", FBuffer;
-  ];
+  ("pvuuid", (RString "uuid", [Device "device"]), 222, [],
+   [],
+   "get the UUID of a physical volume",
+   "\
+This command returns the UUID of the LVM PV C<device>.");
 
-  (* Inotify events. *)
-  "inotify_event", [
-    "in_wd", FInt64;
-    "in_mask", FUInt32;
-    "in_cookie", FUInt32;
-    "in_name", FString;
-  ];
+  ("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>.");
 
-  (* Partition table entry. *)
-  "partition", [
-    "part_num", FInt32;
-    "part_start", FBytes;
-    "part_end", FBytes;
-    "part_size", FBytes;
-  ];
-] (* end of structs *)
+  ("lvuuid", (RString "uuid", [Device "device"]), 224, [],
+   [],
+   "get the UUID of a logical volume",
+   "\
+This command returns the UUID of the LVM LV C<device>.");
 
-(* Ugh, Java has to be different ..
- * These names are also used by the Haskell bindings.
- *)
-let java_structs = [
-  "int_bool", "IntBool";
-  "lvm_pv", "PV";
-  "lvm_vg", "VG";
-  "lvm_lv", "LV";
-  "stat", "Stat";
-  "statvfs", "StatVFS";
-  "dirent", "Dirent";
-  "version", "Version";
-  "xattr", "XAttr";
-  "inotify_event", "INotifyEvent";
-  "partition", "Partition";
-]
+  ("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.
 
-(* What structs are actually returned. *)
-type rstructs_used_t = RStructOnly | RStructListOnly | RStructAndList
+You can use this along with C<guestfs_pvs> and C<guestfs_pvuuid>
+calls to associate physical volumes and volume groups.
 
-(* Returns a list of RStruct/RStructList structs that are returned
- * by any function.  Each element of returned list is a pair:
- *
- * (structname, RStructOnly)
- *    == there exists function which returns RStruct (_, structname)
- * (structname, RStructListOnly)
- *    == there exists function which returns RStructList (_, structname)
- * (structname, RStructAndList)
- *    == there are functions returning both RStruct (_, structname)
- *                                      and RStructList (_, structname)
- *)
-let rstructs_used_by functions =
-  (* ||| is a "logical OR" for rstructs_used_t *)
-  let (|||) a b =
-    match a, b with
-    | RStructAndList, _
-    | _, RStructAndList -> RStructAndList
-    | RStructOnly, RStructListOnly
-    | RStructListOnly, RStructOnly -> RStructAndList
-    | RStructOnly, RStructOnly -> RStructOnly
-    | RStructListOnly, RStructListOnly -> RStructListOnly
-  in
+See also C<guestfs_vglvuuids>.");
 
-  let h = Hashtbl.create 13 in
+  ("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.
 
-  (* if elem->oldv exists, update entry using ||| operator,
-   * else just add elem->newv to the hash
-   *)
-  let update elem newv =
-    try  let oldv = Hashtbl.find h elem in
-         Hashtbl.replace h elem (newv ||| oldv)
-    with Not_found -> Hashtbl.add h elem newv
-  in
+You can use this along with C<guestfs_lvs> and C<guestfs_lvuuid>
+calls to associate logical volumes and volume groups.
 
-  List.iter (
-    fun (_, style, _, _, _, _, _) ->
-      match fst style with
-      | RStruct (_, structname) -> update structname RStructOnly
-      | RStructList (_, structname) -> update structname RStructListOnly
-      | _ -> ()
-  ) functions;
+See also C<guestfs_vgpvuuids>.");
 
-  (* return key->values as a list of (key,value) *)
-  Hashtbl.fold (fun key value xs -> (key, value) :: xs) h []
+  ("copy_size", (RErr, [Dev_or_Path "src"; Dev_or_Path "dest"; Int64 "size"]), 227, [Progress],
+   [InitBasicFS, Always, TestOutputBuffer (
+      [["write"; "/src"; "hello, world"];
+       ["copy_size"; "/src"; "/dest"; "5"];
+       ["read_file"; "/dest"]], "hello")],
+   "copy size bytes from source to destination using dd",
+   "\
+This command copies exactly C<size> bytes from one source device
+or file C<src> to another destination device or file C<dest>.
 
-(* Used for testing language bindings. *)
-type callt =
-  | CallString of string
-  | CallOptString of string option
-  | CallStringList of string list
-  | CallInt of int
-  | CallInt64 of int64
-  | CallBool of bool
+Note this will fail if the source is too short or if the destination
+is not large enough.");
 
-(* Used to memoize the result of pod2text. *)
-let pod2text_memo_filename = "src/.pod2text.data"
-let pod2text_memo : ((int * string * string), string list) Hashtbl.t =
-  try
-    let chan = open_in pod2text_memo_filename in
-    let v = input_value chan in
-    close_in chan;
-    v
-  with
-    _ -> Hashtbl.create 13
-let pod2text_memo_updated () =
-  let chan = open_out pod2text_memo_filename in
-  output_value chan pod2text_memo;
-  close_out chan
+  ("zero_device", (RErr, [Device "device"]), 228, [DangerWillRobinson; Progress],
+   [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.");
 
-(* Useful functions.
- * Note we don't want to use any external OCaml libraries which
- * makes this a bit harder than it should be.
- *)
-let failwithf fs = ksprintf failwith fs
+  ("txz_in", (RErr, [FileIn "tarball"; Pathname "directory"]), 229, [Optional "xz"],
+   [InitBasicFS, Always, TestOutput (
+      [["txz_in"; "../images/helloworld.tar.xz"; "/"];
+       ["cat"; "/hello"]], "hello\n")],
+   "unpack compressed tarball to directory",
+   "\
+This command uploads and unpacks local file C<tarball> (an
+I<xz compressed> tar file) into C<directory>.");
 
-let replace_char s c1 c2 =
-  let s2 = String.copy s in
-  let r = ref false in
-  for i = 0 to String.length s2 - 1 do
-    if String.unsafe_get s2 i = c1 then (
-      String.unsafe_set s2 i c2;
-      r := true
-    )
-  done;
-  if not !r then s else s2
+  ("txz_out", (RErr, [Pathname "directory"; FileOut "tarball"]), 230, [Optional "xz"],
+   [],
+   "pack directory into compressed tarball",
+   "\
+This command packs the contents of C<directory> and downloads
+it to local file C<tarball> (as an xz compressed tar archive).");
 
-let isspace c =
-  c = ' '
-  (* || c = '\f' *) || c = '\n' || c = '\r' || c = '\t' (* || c = '\v' *)
+  ("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)>.");
 
-let triml ?(test = isspace) str =
-  let i = ref 0 in
-  let n = ref (String.length str) in
-  while !n > 0 && test str.[!i]; do
-    decr n;
-    incr i
-  done;
-  if !i = 0 then str
-  else String.sub str !i !n
+  ("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.");
 
-let trimr ?(test = isspace) str =
-  let n = ref (String.length str) in
-  while !n > 0 && test str.[!n-1]; do
-    decr n
-  done;
-  if !n = String.length str then str
-  else String.sub str 0 !n
+  ("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>.
 
-let trim ?(test = isspace) str =
-  trimr ~test (triml ~test str)
+Note that in the case of MBR partitioning, deleting an
+extended partition also deletes any logical partitions
+it contains.");
 
-let rec find s sub =
-  let len = String.length s in
-  let sublen = String.length sub in
-  let rec loop i =
-    if i <= len-sublen then (
-      let rec loop2 j =
-        if j < sublen then (
-          if s.[i+j] = sub.[j] then loop2 (j+1)
-          else -1
-        ) else
-          i (* found *)
-      in
-      let r = loop2 0 in
-      if r = -1 then loop (i+1) else r
-    ) else
-      -1 (* not found *)
-  in
-  loop 0
+  ("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.
 
-let rec replace_str s s1 s2 =
-  let len = String.length s in
-  let sublen = String.length s1 in
-  let i = find s s1 in
-  if i = -1 then s
-  else (
-    let s' = String.sub s 0 i in
-    let s'' = String.sub s (i+sublen) (len-i-sublen) in
-    s' ^ s2 ^ replace_str s'' s1 s2
-  )
+See also C<guestfs_part_set_bootable>.");
 
-let rec string_split sep str =
-  let len = String.length str in
-  let seplen = String.length sep in
-  let i = find str sep in
-  if i = -1 then [str]
-  else (
-    let s' = String.sub str 0 i in
-    let s'' = String.sub str (i+seplen) (len-i-seplen) in
-    s' :: string_split sep s''
-  )
+  ("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>.
 
-let files_equal n1 n2 =
-  let cmd = sprintf "cmp -s %s %s" (Filename.quote n1) (Filename.quote n2) in
-  match Sys.command cmd with
-  | 0 -> true
-  | 1 -> false
-  | i -> failwithf "%s: failed with error code %d" cmd i
+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>).");
 
-let rec filter_map f = function
-  | [] -> []
-  | x :: xs ->
-      match f x with
-      | Some y -> y :: filter_map f xs
-      | None -> filter_map f xs
+  ("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.
 
-let rec find_map f = function
-  | [] -> raise Not_found
-  | x :: xs ->
-      match f x with
-      | Some y -> y
-      | None -> find_map f xs
+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>).");
 
-let iteri f xs =
-  let rec loop i = function
-    | [] -> ()
-    | x :: xs -> f i x; loop (i+1) xs
-  in
-  loop 0 xs
+  ("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.");
 
-let mapi f xs =
-  let rec loop i = function
-    | [] -> []
-    | x :: xs -> let r = f i x in r :: loop (i+1) xs
-  in
-  loop 0 xs
+  ("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.");
 
-let name_of_argt = function
-  | Pathname n | Device n | Dev_or_Path n | String n | OptString n
-  | StringList n | DeviceList n | Bool n | Int n | Int64 n
-  | FileIn n | FileOut n -> n
+  ("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.");
 
-let java_name_of_struct typ =
-  try List.assoc typ java_structs
-  with Not_found ->
-    failwithf
-      "java_name_of_struct: no java_structs entry corresponding to %s" typ
+  ("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>.");
 
-let cols_of_struct typ =
-  try List.assoc typ structs
-  with Not_found ->
-    failwithf "cols_of_struct: unknown struct %s" typ
+  ("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.
 
-let seq_of_test = function
-  | TestRun s | TestOutput (s, _) | TestOutputList (s, _)
-  | TestOutputListOfDevices (s, _)
-  | TestOutputInt (s, _) | TestOutputIntOp (s, _, _)
-  | TestOutputTrue s | TestOutputFalse s
-  | TestOutputLength (s, _) | TestOutputBuffer (s, _)
-  | TestOutputStruct (s, _)
-  | TestLastFail s -> s
+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.");
 
-(* 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."
+  ("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>.");
 
-let danger_will_robinson =
-  "B<This command is dangerous.  Without careful use you
-can easily destroy all your data>."
+  ("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.");
 
-let deprecation_notice flags =
-  try
-    let alt =
-      find_map (function DeprecatedBy str -> Some str | _ -> None) flags in
-    let txt =
-      sprintf "This function is deprecated.
-In new code, use the C<%s> call instead.
+  ("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>.
 
-Deprecated functions will not be removed from the API, but the
-fact that they are deprecated indicates that there are problems
-with correct use of these functions." alt in
-    Some txt
-  with
-    Not_found -> None
+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.");
 
-(* Create list of optional groups. *)
-let optgroups =
-  let h = Hashtbl.create 13 in
-  List.iter (
-    fun (name, _, _, flags, _, _, _) ->
-      List.iter (
-        function
-        | Optional group ->
-            let names = try Hashtbl.find h group with Not_found -> [] in
-            Hashtbl.replace h group (name :: names)
-        | _ -> ()
-      ) flags
-  ) daemon_functions;
-  let groups = Hashtbl.fold (fun k _ ks -> k :: ks) h [] in
-  let groups =
-    List.map (
-      fun group -> group, List.sort compare (Hashtbl.find h group)
-    ) groups in
-  List.sort (fun x y -> compare (fst x) (fst y)) groups
+  ("fill_pattern", (RErr, [String "pattern"; Int "len"; Pathname "path"]), 245, [Progress],
+   [InitBasicFS, Always, TestOutputBuffer (
+      [["fill_pattern"; "abcdefghijklmnopqrstuvwxyz"; "28"; "/test"];
+       ["read_file"; "/test"]], "abcdefghijklmnopqrstuvwxyzab")],
+   "fill a file with a repeating pattern of bytes",
+   "\
+This function is like C<guestfs_fill> except that it creates
+a new file of length C<len> containing the repeating pattern
+of bytes in C<pattern>.  The pattern is truncated if necessary
+to ensure the length of the file is exactly C<len> bytes.");
 
-(* Check function names etc. for consistency. *)
-let check_functions () =
-  let contains_uppercase str =
-    let len = String.length str in
-    let rec loop i =
-      if i >= len then false
-      else (
-        let c = str.[i] in
-        if c >= 'A' && c <= 'Z' then true
-        else loop (i+1)
-      )
-    in
-    loop 0
-  in
+  ("write", (RErr, [Pathname "path"; BufferIn "content"]), 246, [ProtocolLimitWarning],
+   [InitBasicFS, Always, TestOutput (
+      [["write"; "/new"; "new file contents"];
+       ["cat"; "/new"]], "new file contents");
+    InitBasicFS, Always, TestOutput (
+      [["write"; "/new"; "\nnew file contents\n"];
+       ["cat"; "/new"]], "\nnew file contents\n");
+    InitBasicFS, Always, TestOutput (
+      [["write"; "/new"; "\n\n"];
+       ["cat"; "/new"]], "\n\n");
+    InitBasicFS, Always, TestOutput (
+      [["write"; "/new"; ""];
+       ["cat"; "/new"]], "");
+    InitBasicFS, Always, TestOutput (
+      [["write"; "/new"; "\n\n\n"];
+       ["cat"; "/new"]], "\n\n\n");
+    InitBasicFS, Always, TestOutput (
+      [["write"; "/new"; "\n"];
+       ["cat"; "/new"]], "\n")],
+   "create a new file",
+   "\
+This call creates a file called C<path>.  The content of the
+file is the string C<content> (which can contain any 8 bit data).");
 
-  (* Check function names. *)
-  List.iter (
-    fun (name, _, _, _, _, _, _) ->
-      if String.length name >= 7 && String.sub name 0 7 = "guestfs" then
-        failwithf "function name %s does not need 'guestfs' prefix" name;
-      if name = "" then
-        failwithf "function name is empty";
-      if name.[0] < 'a' || name.[0] > 'z' then
-        failwithf "function name %s must start with lowercase a-z" name;
-      if String.contains name '-' then
-        failwithf "function name %s should not contain '-', use '_' instead."
-          name
-  ) all_functions;
+  ("pwrite", (RInt "nbytes", [Pathname "path"; BufferIn "content"; Int64 "offset"]), 247, [ProtocolLimitWarning],
+   [InitBasicFS, Always, TestOutput (
+      [["write"; "/new"; "new file contents"];
+       ["pwrite"; "/new"; "data"; "4"];
+       ["cat"; "/new"]], "new data contents");
+    InitBasicFS, Always, TestOutput (
+      [["write"; "/new"; "new file contents"];
+       ["pwrite"; "/new"; "is extended"; "9"];
+       ["cat"; "/new"]], "new file is extended");
+    InitBasicFS, Always, TestOutput (
+      [["write"; "/new"; "new file contents"];
+       ["pwrite"; "/new"; ""; "4"];
+       ["cat"; "/new"]], "new file contents")],
+   "write to part of a file",
+   "\
+This command writes to part of a file.  It writes the data
+buffer C<content> to the file C<path> starting at offset C<offset>.
 
-  (* Check function parameter/return names. *)
-  List.iter (
-    fun (name, style, _, _, _, _, _) ->
-      let check_arg_ret_name n =
-        if contains_uppercase n then
-          failwithf "%s param/ret %s should not contain uppercase chars"
-            name n;
-        if String.contains n '-' || String.contains n '_' then
-          failwithf "%s param/ret %s should not contain '-' or '_'"
-            name n;
-        if n = "value" then
-          failwithf "%s has a param/ret called 'value', which causes conflicts in the OCaml bindings, use something like 'val' or a more descriptive name" name;
-        if n = "int" || n = "char" || n = "short" || n = "long" then
-          failwithf "%s has a param/ret which conflicts with a C type (eg. 'int', 'char' etc.)" name;
-        if n = "i" || n = "n" then
-          failwithf "%s has a param/ret called 'i' or 'n', which will cause some conflicts in the generated code" name;
-        if n = "argv" || n = "args" then
-          failwithf "%s has a param/ret called 'argv' or 'args', which will cause some conflicts in the generated code" name;
+This command implements the L<pwrite(2)> system call, and like
+that system call it may not write the full data requested.  The
+return value is the number of bytes that were actually written
+to the file.  This could even be 0, although short writes are
+unlikely for regular files in ordinary circumstances.
 
-        (* List Haskell, OCaml and C keywords here.
-         * http://www.haskell.org/haskellwiki/Keywords
-         * http://caml.inria.fr/pub/docs/manual-ocaml/lex.html#operator-char
-         * http://en.wikipedia.org/wiki/C_syntax#Reserved_keywords
-         * Formatted via: cat c haskell ocaml|sort -u|grep -vE '_|^val$' \
-         *   |perl -pe 's/(.+)/"$1";/'|fmt -70
-         * Omitting _-containing words, since they're handled above.
-         * Omitting the OCaml reserved word, "val", is ok,
-         * and saves us from renaming several parameters.
-         *)
-        let reserved = [
-          "and"; "as"; "asr"; "assert"; "auto"; "begin"; "break"; "case";
-          "char"; "class"; "const"; "constraint"; "continue"; "data";
-          "default"; "deriving"; "do"; "done"; "double"; "downto"; "else";
-          "end"; "enum"; "exception"; "extern"; "external"; "false"; "float";
-          "for"; "forall"; "foreign"; "fun"; "function"; "functor"; "goto";
-          "hiding"; "if"; "import"; "in"; "include"; "infix"; "infixl";
-          "infixr"; "inherit"; "initializer"; "inline"; "instance"; "int";
-          "land"; "lazy"; "let"; "long"; "lor"; "lsl"; "lsr"; "lxor";
-          "match"; "mdo"; "method"; "mod"; "module"; "mutable"; "new";
-          "newtype"; "object"; "of"; "open"; "or"; "private"; "qualified";
-          "rec"; "register"; "restrict"; "return"; "short"; "sig"; "signed";
-          "sizeof"; "static"; "struct"; "switch"; "then"; "to"; "true"; "try";
-          "type"; "typedef"; "union"; "unsigned"; "virtual"; "void";
-          "volatile"; "when"; "where"; "while";
-          ] in
-        if List.mem n reserved then
-          failwithf "%s has param/ret using reserved word %s" name n;
-      in
+See also C<guestfs_pread>.");
 
-      (match fst style with
-       | RErr -> ()
-       | RInt n | RInt64 n | RBool n
-       | RConstString n | RConstOptString n | RString n
-       | RStringList n | RStruct (n, _) | RStructList (n, _)
-       | RHashtable n | RBufferOut n ->
-           check_arg_ret_name n
-      );
-      List.iter (fun arg -> check_arg_ret_name (name_of_argt arg)) (snd style)
-  ) all_functions;
+  ("resize2fs_size", (RErr, [Device "device"; Int64 "size"]), 248, [],
+   [],
+   "resize an ext2, ext3 or ext4 filesystem (with size)",
+   "\
+This command is the same as C<guestfs_resize2fs> except that it
+allows you to specify the new size (in bytes) explicitly.");
 
-  (* Check short descriptions. *)
-  List.iter (
-    fun (name, _, _, _, _, shortdesc, _) ->
-      if shortdesc.[0] <> Char.lowercase shortdesc.[0] then
-        failwithf "short description of %s should begin with lowercase." name;
-      let c = shortdesc.[String.length shortdesc-1] in
-      if c = '\n' || c = '.' then
-        failwithf "short description of %s should not end with . or \\n." name
-  ) all_functions;
+  ("pvresize_size", (RErr, [Device "device"; Int64 "size"]), 249, [Optional "lvm2"],
+   [],
+   "resize an LVM physical volume (with size)",
+   "\
+This command is the same as C<guestfs_pvresize> except that it
+allows you to specify the new size (in bytes) explicitly.");
 
-  (* Check long dscriptions. *)
-  List.iter (
-    fun (name, _, _, _, _, _, longdesc) ->
-      if longdesc.[String.length longdesc-1] = '\n' then
-        failwithf "long description of %s should not end with \\n." name
-  ) all_functions;
+  ("ntfsresize_size", (RErr, [Device "device"; Int64 "size"]), 250, [Optional "ntfsprogs"],
+   [],
+   "resize an NTFS filesystem (with size)",
+   "\
+This command is the same as C<guestfs_ntfsresize> except that it
+allows you to specify the new size (in bytes) explicitly.");
 
-  (* Check proc_nrs. *)
-  List.iter (
-    fun (name, _, proc_nr, _, _, _, _) ->
-      if proc_nr <= 0 then
-        failwithf "daemon function %s should have proc_nr > 0" name
-  ) daemon_functions;
+  ("available_all_groups", (RStringList "groups", []), 251, [],
+   [InitNone, Always, TestRun [["available_all_groups"]]],
+   "return a list of all optional groups",
+   "\
+This command returns a list of all optional groups that this
+daemon knows about.  Note this returns both supported and unsupported
+groups.  To find out which ones the daemon can actually support
+you have to call C<guestfs_available> on each member of the
+returned list.
 
-  List.iter (
-    fun (name, _, proc_nr, _, _, _, _) ->
-      if proc_nr <> -1 then
-        failwithf "non-daemon function %s should have proc_nr -1" name
-  ) non_daemon_functions;
+See also C<guestfs_available> and L<guestfs(3)/AVAILABILITY>.");
 
-  let proc_nrs =
-    List.map (fun (name, _, proc_nr, _, _, _, _) -> name, proc_nr)
-      daemon_functions in
-  let proc_nrs =
-    List.sort (fun (_,nr1) (_,nr2) -> compare nr1 nr2) proc_nrs in
-  let rec loop = function
-    | [] -> ()
-    | [_] -> ()
-    | (name1,nr1) :: ((name2,nr2) :: _ as rest) when nr1 < nr2 ->
-        loop rest
-    | (name1,nr1) :: (name2,nr2) :: _ ->
-        failwithf "%s and %s have conflicting procedure numbers (%d, %d)"
-          name1 name2 nr1 nr2
-  in
-  loop proc_nrs;
+  ("fallocate64", (RErr, [Pathname "path"; Int64 "len"]), 252, [],
+   [InitBasicFS, Always, TestOutputStruct (
+      [["fallocate64"; "/a"; "1000000"];
+       ["stat"; "/a"]], [CompareWithInt ("size", 1_000_000)])],
+   "preallocate a file in the guest filesystem",
+   "\
+This command preallocates a file (containing zero bytes) named
+C<path> of size C<len> bytes.  If the file exists already, it
+is overwritten.
 
-  (* Check tests. *)
-  List.iter (
-    function
-      (* Ignore functions that have no tests.  We generate a
-       * warning when the user does 'make check' instead.
-       *)
-    | name, _, _, _, [], _, _ -> ()
-    | name, _, _, _, tests, _, _ ->
-        let funcs =
-          List.map (
-            fun (_, _, test) ->
-              match seq_of_test test with
-              | [] ->
-                  failwithf "%s has a test containing an empty sequence" name
-              | cmds -> List.map List.hd cmds
-          ) tests in
-        let funcs = List.flatten funcs in
+Note that this call allocates disk blocks for the file.
+To create a sparse file use C<guestfs_truncate_size> instead.
 
-        let tested = List.mem name funcs in
+The deprecated call C<guestfs_fallocate> does the same,
+but owing to an oversight it only allowed 30 bit lengths
+to be specified, effectively limiting the maximum size
+of files created through that call to 1GB.
 
-        if not tested then
-          failwithf "function %s has tests but does not test itself" name
-  ) all_functions
+Do not confuse this with the guestfish-specific
+C<alloc> and C<sparse> commands which create
+a file in the host and attach it as a device.");
 
-(* 'pr' prints to the current output file. *)
-let chan = ref Pervasives.stdout
-let pr fs = ksprintf (output_string !chan) fs
+  ("vfs_label", (RString "label", [Device "device"]), 253, [],
+   [InitBasicFS, Always, TestOutput (
+       [["set_e2label"; "/dev/sda1"; "LTEST"];
+        ["vfs_label"; "/dev/sda1"]], "LTEST")],
+   "get the filesystem label",
+   "\
+This returns the filesystem label of the filesystem on
+C<device>.
 
-(* Generate a header block in a number of standard styles. *)
-type comment_style = CStyle | HashStyle | OCamlStyle | HaskellStyle
-type license = GPLv2 | LGPLv2
+If the filesystem is unlabeled, this returns the empty string.
 
-let generate_header comment license =
-  let c = match comment with
-    | CStyle ->     pr "/* "; " *"
-    | HashStyle ->  pr "# ";  "#"
-    | OCamlStyle -> pr "(* "; " *"
-    | HaskellStyle -> pr "{- "; "  " in
-  pr "libguestfs generated file\n";
-  pr "%s WARNING: THIS FILE IS GENERATED BY 'src/generator.ml'.\n" c;
-  pr "%s ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST.\n" c;
-  pr "%s\n" c;
-  pr "%s Copyright (C) 2009 Red Hat Inc.\n" c;
-  pr "%s\n" c;
-  (match license with
-   | GPLv2 ->
-       pr "%s This program is free software; you can redistribute it and/or modify\n" c;
-       pr "%s it under the terms of the GNU General Public License as published by\n" c;
-       pr "%s the Free Software Foundation; either version 2 of the License, or\n" c;
-       pr "%s (at your option) any later version.\n" c;
-       pr "%s\n" c;
-       pr "%s This program is distributed in the hope that it will be useful,\n" c;
-       pr "%s but WITHOUT ANY WARRANTY; without even the implied warranty of\n" c;
-       pr "%s MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n" c;
-       pr "%s GNU General Public License for more details.\n" c;
-       pr "%s\n" c;
-       pr "%s You should have received a copy of the GNU General Public License along\n" c;
-       pr "%s with this program; if not, write to the Free Software Foundation, Inc.,\n" c;
-       pr "%s 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n" c;
+To find a filesystem from the label, use C<guestfs_findfs_label>.");
 
-   | LGPLv2 ->
-       pr "%s This library is free software; you can redistribute it and/or\n" c;
-       pr "%s modify it under the terms of the GNU Lesser General Public\n" c;
-       pr "%s License as published by the Free Software Foundation; either\n" c;
-       pr "%s version 2 of the License, or (at your option) any later version.\n" c;
-       pr "%s\n" c;
-       pr "%s This library is distributed in the hope that it will be useful,\n" c;
-       pr "%s but WITHOUT ANY WARRANTY; without even the implied warranty of\n" c;
-       pr "%s MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n" c;
-       pr "%s Lesser General Public License for more details.\n" c;
-       pr "%s\n" c;
-       pr "%s You should have received a copy of the GNU Lesser General Public\n" c;
-       pr "%s License along with this library; if not, write to the Free Software\n" c;
-       pr "%s Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n" c;
-  );
-  (match comment with
-   | CStyle -> pr " */\n"
-   | HashStyle -> ()
-   | OCamlStyle -> pr " *)\n"
-   | HaskellStyle -> pr "-}\n"
-  );
-  pr "\n"
+  ("vfs_uuid", (RString "uuid", [Device "device"]), 254, [],
+   (let uuid = uuidgen () in
+    [InitBasicFS, Always, TestOutput (
+       [["set_e2uuid"; "/dev/sda1"; uuid];
+        ["vfs_uuid"; "/dev/sda1"]], uuid)]),
+   "get the filesystem UUID",
+   "\
+This returns the filesystem UUID of the filesystem on
+C<device>.
 
-(* Start of main code generation functions below this line. *)
+If the filesystem does not have a UUID, this returns the empty string.
 
-(* Generate the pod documentation for the C API. *)
-let rec generate_actions_pod () =
-  List.iter (
-    fun (shortname, style, _, flags, _, _, longdesc) ->
-      if not (List.mem NotInDocs flags) then (
-        let name = "guestfs_" ^ shortname in
-        pr "=head2 %s\n\n" name;
-        pr " ";
-        generate_prototype ~extern:false ~handle:"handle" name style;
-        pr "\n\n";
-        pr "%s\n\n" longdesc;
-        (match fst style with
-         | RErr ->
-             pr "This function returns 0 on success or -1 on error.\n\n"
-         | RInt _ ->
-             pr "On error this function returns -1.\n\n"
-         | RInt64 _ ->
-             pr "On error this function returns -1.\n\n"
-         | RBool _ ->
-             pr "This function returns a C truth value on success or -1 on error.\n\n"
-         | RConstString _ ->
-             pr "This function returns a string, or NULL on error.
-The string is owned by the guest handle and must I<not> be freed.\n\n"
-         | RConstOptString _ ->
-             pr "This function returns a string which may be NULL.
-There is way to return an error from this function.
-The string is owned by the guest handle and must I<not> be freed.\n\n"
-         | RString _ ->
-             pr "This function returns a string, or NULL on error.
-I<The caller must free the returned string after use>.\n\n"
-         | RStringList _ ->
-             pr "This function returns a NULL-terminated array of strings
-(like L<environ(3)>), or NULL if there was an error.
-I<The caller must free the strings and the array after use>.\n\n"
-         | RStruct (_, typ) ->
-             pr "This function returns a C<struct guestfs_%s *>,
-or NULL if there was an error.
-I<The caller must call C<guestfs_free_%s> after use>.\n\n" typ typ
-         | RStructList (_, typ) ->
-             pr "This function returns a C<struct guestfs_%s_list *>
-(see E<lt>guestfs-structs.hE<gt>),
-or NULL if there was an error.
-I<The caller must call C<guestfs_free_%s_list> after use>.\n\n" typ typ
-         | RHashtable _ ->
-             pr "This function returns a NULL-terminated array of
-strings, or NULL if there was an error.
-The array of strings will always have length C<2n+1>, where
-C<n> keys and values alternate, followed by the trailing NULL entry.
-I<The caller must free the strings and the array after use>.\n\n"
-         | RBufferOut _ ->
-             pr "This function returns a buffer, or NULL on error.
-The size of the returned buffer is written to C<*size_r>.
-I<The caller must free the returned buffer after use>.\n\n"
-        );
-        if List.mem ProtocolLimitWarning flags then
-          pr "%s\n\n" protocol_limit_warning;
-        if List.mem DangerWillRobinson flags then
-          pr "%s\n\n" danger_will_robinson;
-        match deprecation_notice flags with
-        | None -> ()
-        | Some txt -> pr "%s\n\n" txt
-      )
-  ) all_functions_sorted
+To find a filesystem from the UUID, use C<guestfs_findfs_uuid>.");
 
-and generate_structs_pod () =
-  (* Structs documentation. *)
-  List.iter (
-    fun (typ, cols) ->
-      pr "=head2 guestfs_%s\n" typ;
-      pr "\n";
-      pr " struct guestfs_%s {\n" typ;
-      List.iter (
-        function
-        | name, FChar -> pr "   char %s;\n" name
-        | name, FUInt32 -> pr "   uint32_t %s;\n" name
-        | name, FInt32 -> pr "   int32_t %s;\n" name
-        | name, (FUInt64|FBytes) -> pr "   uint64_t %s;\n" name
-        | name, FInt64 -> pr "   int64_t %s;\n" name
-        | name, FString -> pr "   char *%s;\n" name
-        | name, FBuffer ->
-            pr "   /* The next two fields describe a byte array. */\n";
-            pr "   uint32_t %s_len;\n" name;
-            pr "   char *%s;\n" name
-        | name, FUUID ->
-            pr "   /* The next field is NOT nul-terminated, be careful when printing it: */\n";
-            pr "   char %s[32];\n" name
-        | name, FOptPercent ->
-            pr "   /* The next field is [0..100] or -1 meaning 'not present': */\n";
-            pr "   float %s;\n" name
-      ) cols;
-      pr " };\n";
-      pr " \n";
-      pr " struct guestfs_%s_list {\n" typ;
-      pr "   uint32_t len; /* Number of elements in list. */\n";
-      pr "   struct guestfs_%s *val; /* Elements. */\n" typ;
-      pr " };\n";
-      pr " \n";
-      pr " void guestfs_free_%s (struct guestfs_free_%s *);\n" typ typ;
-      pr " void guestfs_free_%s_list (struct guestfs_free_%s_list *);\n"
-        typ typ;
-      pr "\n"
-  ) structs
+  ("lvm_set_filter", (RErr, [DeviceList "devices"]), 255, [Optional "lvm2"],
+   (* Can't be tested with the current framework because
+    * the VG is being used by the mounted filesystem, so
+    * the vgchange -an command we do first will fail.
+    *)
+    [],
+   "set LVM device filter",
+   "\
+This sets the LVM device filter so that LVM will only be
+able to \"see\" the block devices in the list C<devices>,
+and will ignore all other attached block devices.
 
-and generate_availability_pod () =
-  (* Availability documentation. *)
-  pr "=over 4\n";
-  pr "\n";
-  List.iter (
-    fun (group, functions) ->
-      pr "=item B<%s>\n" group;
-      pr "\n";
-      pr "The following functions:\n";
-      List.iter (pr "L</guestfs_%s>\n") functions;
-      pr "\n"
-  ) optgroups;
-  pr "=back\n";
-  pr "\n"
+Where disk image(s) contain duplicate PVs or VGs, this
+command is useful to get LVM to ignore the duplicates, otherwise
+LVM can get confused.  Note also there are two types
+of duplication possible: either cloned PVs/VGs which have
+identical UUIDs; or VGs that are not cloned but just happen
+to have the same name.  In normal operation you cannot
+create this situation, but you can do it outside LVM, eg.
+by cloning disk images or by bit twiddling inside the LVM
+metadata.
 
-(* Generate the protocol (XDR) file, 'guestfs_protocol.x' and
- * indirectly 'guestfs_protocol.h' and 'guestfs_protocol.c'.
- *
- * We have to use an underscore instead of a dash because otherwise
- * rpcgen generates incorrect code.
- *
- * This header is NOT exported to clients, but see also generate_structs_h.
- *)
-and generate_xdr () =
-  generate_header CStyle LGPLv2;
+This command also clears the LVM cache and performs a volume
+group scan.
 
-  (* This has to be defined to get around a limitation in Sun's rpcgen. *)
-  pr "typedef string str<>;\n";
-  pr "\n";
+You can filter whole block devices or individual partitions.
 
-  (* Internal structures. *)
-  List.iter (
-    function
-    | typ, cols ->
-        pr "struct guestfs_int_%s {\n" typ;
-        List.iter (function
-                   | name, FChar -> pr "  char %s;\n" name
-                   | name, FString -> pr "  string %s<>;\n" name
-                   | name, FBuffer -> pr "  opaque %s<>;\n" name
-                   | name, FUUID -> pr "  opaque %s[32];\n" name
-                   | name, (FInt32|FUInt32) -> pr "  int %s;\n" name
-                   | name, (FInt64|FUInt64|FBytes) -> pr "  hyper %s;\n" name
-                   | name, FOptPercent -> pr "  float %s;\n" name
-                  ) cols;
-        pr "};\n";
-        pr "\n";
-        pr "typedef struct guestfs_int_%s guestfs_int_%s_list<>;\n" typ typ;
-        pr "\n";
-  ) structs;
+You cannot use this if any VG is currently in use (eg.
+contains a mounted filesystem), even if you are not
+filtering out that VG.");
 
-  List.iter (
-    fun (shortname, style, _, _, _, _, _) ->
-      let name = "guestfs_" ^ shortname in
+  ("lvm_clear_filter", (RErr, []), 256, [],
+   [], (* see note on lvm_set_filter *)
+   "clear LVM device filter",
+   "\
+This undoes the effect of C<guestfs_lvm_set_filter>.  LVM
+will be able to see every block device.
 
-      (match snd style with
-       | [] -> ()
-       | args ->
-           pr "struct %s_args {\n" name;
-           List.iter (
-             function
-             | Pathname n | Device n | Dev_or_Path n | String n ->
-                 pr "  string %s<>;\n" n
-             | OptString n -> pr "  str *%s;\n" n
-             | StringList n | DeviceList n -> pr "  str %s<>;\n" n
-             | Bool n -> pr "  bool %s;\n" n
-             | Int n -> pr "  int %s;\n" n
-             | Int64 n -> pr "  hyper %s;\n" n
-             | FileIn _ | FileOut _ -> ()
-           ) args;
-           pr "};\n\n"
-      );
-      (match fst style with
-       | RErr -> ()
-       | RInt n ->
-           pr "struct %s_ret {\n" name;
-           pr "  int %s;\n" n;
-           pr "};\n\n"
-       | RInt64 n ->
-           pr "struct %s_ret {\n" name;
-           pr "  hyper %s;\n" n;
-           pr "};\n\n"
-       | RBool n ->
-           pr "struct %s_ret {\n" name;
-           pr "  bool %s;\n" n;
-           pr "};\n\n"
-       | RConstString _ | RConstOptString _ ->
-           failwithf "RConstString|RConstOptString cannot be used by daemon functions"
-       | RString n ->
-           pr "struct %s_ret {\n" name;
-           pr "  string %s<>;\n" n;
-           pr "};\n\n"
-       | RStringList n ->
-           pr "struct %s_ret {\n" name;
-           pr "  str %s<>;\n" n;
-           pr "};\n\n"
-       | RStruct (n, typ) ->
-           pr "struct %s_ret {\n" name;
-           pr "  guestfs_int_%s %s;\n" typ n;
-           pr "};\n\n"
-       | RStructList (n, typ) ->
-           pr "struct %s_ret {\n" name;
-           pr "  guestfs_int_%s_list %s;\n" typ n;
-           pr "};\n\n"
-       | RHashtable n ->
-           pr "struct %s_ret {\n" name;
-           pr "  str %s<>;\n" n;
-           pr "};\n\n"
-       | RBufferOut n ->
-           pr "struct %s_ret {\n" name;
-           pr "  opaque %s<>;\n" n;
-           pr "};\n\n"
-      );
-  ) daemon_functions;
+This command also clears the LVM cache and performs a volume
+group scan.");
 
-  (* Table of procedure numbers. *)
-  pr "enum guestfs_procedure {\n";
-  List.iter (
-    fun (shortname, _, proc_nr, _, _, _, _) ->
-      pr "  GUESTFS_PROC_%s = %d,\n" (String.uppercase shortname) proc_nr
-  ) daemon_functions;
-  pr "  GUESTFS_PROC_NR_PROCS\n";
-  pr "};\n";
-  pr "\n";
+  ("luks_open", (RErr, [Device "device"; Key "key"; String "mapname"]), 257, [Optional "luks"],
+   [],
+   "open a LUKS-encrypted block device",
+   "\
+This command opens a block device which has been encrypted
+according to the Linux Unified Key Setup (LUKS) standard.
 
-  (* 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.
-   *)
-  pr "const GUESTFS_MESSAGE_MAX = %d;\n" (4 * 1024 * 1024);
-  pr "\n";
+C<device> is the encrypted block device or partition.
 
-  (* Message header, etc. *)
-  pr "\
-/* The communication protocol is now documented in the guestfs(3)
- * manpage.
- */
+The caller must supply one of the keys associated with the
+LUKS block device, in the C<key> parameter.
 
-const GUESTFS_PROGRAM = 0x2000F5F5;
-const GUESTFS_PROTOCOL_VERSION = 1;
+This creates a new block device called C</dev/mapper/mapname>.
+Reads and writes to this block device are decrypted from and
+encrypted to the underlying C<device> respectively.
 
-/* These constants must be larger than any possible message length. */
-const GUESTFS_LAUNCH_FLAG = 0xf5f55ff5;
-const GUESTFS_CANCEL_FLAG = 0xffffeeee;
+If this block device contains LVM volume groups, then
+calling C<guestfs_vgscan> followed by C<guestfs_vg_activate_all>
+will make them visible.");
 
-enum guestfs_message_direction {
-  GUESTFS_DIRECTION_CALL = 0,        /* client -> daemon */
-  GUESTFS_DIRECTION_REPLY = 1        /* daemon -> client */
-};
+  ("luks_open_ro", (RErr, [Device "device"; Key "key"; String "mapname"]), 258, [Optional "luks"],
+   [],
+   "open a LUKS-encrypted block device read-only",
+   "\
+This is the same as C<guestfs_luks_open> except that a read-only
+mapping is created.");
 
-enum guestfs_message_status {
-  GUESTFS_STATUS_OK = 0,
-  GUESTFS_STATUS_ERROR = 1
-};
+  ("luks_close", (RErr, [Device "device"]), 259, [Optional "luks"],
+   [],
+   "close a LUKS device",
+   "\
+This closes a LUKS device that was created earlier by
+C<guestfs_luks_open> or C<guestfs_luks_open_ro>.  The
+C<device> parameter must be the name of the LUKS mapping
+device (ie. C</dev/mapper/mapname>) and I<not> the name
+of the underlying block device.");
 
-const GUESTFS_ERROR_LEN = 256;
+  ("luks_format", (RErr, [Device "device"; Key "key"; Int "keyslot"]), 260, [Optional "luks"; DangerWillRobinson],
+   [],
+   "format a block device as a LUKS encrypted device",
+   "\
+This command erases existing data on C<device> and formats
+the device as a LUKS encrypted device.  C<key> is the
+initial key, which is added to key slot C<slot>.  (LUKS
+supports 8 key slots, numbered 0-7).");
 
-struct guestfs_message_error {
-  string error_message<GUESTFS_ERROR_LEN>;
-};
+  ("luks_format_cipher", (RErr, [Device "device"; Key "key"; Int "keyslot"; String "cipher"]), 261, [Optional "luks"; DangerWillRobinson],
+   [],
+   "format a block device as a LUKS encrypted device",
+   "\
+This command is the same as C<guestfs_luks_format> but
+it also allows you to set the C<cipher> used.");
 
-struct guestfs_message_header {
-  unsigned prog;                     /* GUESTFS_PROGRAM */
-  unsigned vers;                     /* GUESTFS_PROTOCOL_VERSION */
-  guestfs_procedure proc;            /* GUESTFS_PROC_x */
-  guestfs_message_direction direction;
-  unsigned serial;                   /* message serial number */
-  guestfs_message_status status;
-};
+  ("luks_add_key", (RErr, [Device "device"; Key "key"; Key "newkey"; Int "keyslot"]), 262, [Optional "luks"],
+   [],
+   "add a key on a LUKS encrypted device",
+   "\
+This command adds a new key on LUKS device C<device>.
+C<key> is any existing key, and is used to access the device.
+C<newkey> is the new key to add.  C<keyslot> is the key slot
+that will be replaced.
 
-const GUESTFS_MAX_CHUNK_SIZE = 8192;
+Note that if C<keyslot> already contains a key, then this
+command will fail.  You have to use C<guestfs_luks_kill_slot>
+first to remove that key.");
 
-struct guestfs_chunk {
-  int cancel;                       /* if non-zero, transfer is cancelled */
-  /* data size is 0 bytes if the transfer has finished successfully */
-  opaque data<GUESTFS_MAX_CHUNK_SIZE>;
-};
-"
+  ("luks_kill_slot", (RErr, [Device "device"; Key "key"; Int "keyslot"]), 263, [Optional "luks"],
+   [],
+   "remove a key from a LUKS encrypted device",
+   "\
+This command deletes the key in key slot C<keyslot> from the
+encrypted LUKS device C<device>.  C<key> must be one of the
+I<other> keys.");
 
-(* Generate the guestfs-structs.h file. *)
-and generate_structs_h () =
-  generate_header CStyle LGPLv2;
+  ("is_lv", (RBool "lvflag", [Device "device"]), 264, [Optional "lvm2"],
+   [InitBasicFSonLVM, IfAvailable "lvm2", TestOutputTrue (
+      [["is_lv"; "/dev/VG/LV"]]);
+    InitBasicFSonLVM, IfAvailable "lvm2", TestOutputFalse (
+      [["is_lv"; "/dev/sda1"]])],
+   "test if device is a logical volume",
+   "\
+This command tests whether C<device> is a logical volume, and
+returns true iff this is the case.");
 
-  (* This is a public exported header file containing various
-   * structures.  The structures are carefully written to have
-   * exactly the same in-memory format as the XDR structures that
-   * we use on the wire to the daemon.  The reason for creating
-   * copies of these structures here is just so we don't have to
-   * export the whole of guestfs_protocol.h (which includes much
-   * unrelated and XDR-dependent stuff that we don't want to be
-   * public, or required by clients).
-   *
-   * To reiterate, we will pass these structures to and from the
-   * client with a simple assignment or memcpy, so the format
-   * must be identical to what rpcgen / the RFC defines.
-   *)
-
-  (* Public structures. *)
-  List.iter (
-    fun (typ, cols) ->
-      pr "struct guestfs_%s {\n" typ;
-      List.iter (
-        function
-        | name, FChar -> pr "  char %s;\n" name
-        | name, FString -> pr "  char *%s;\n" name
-        | name, FBuffer ->
-            pr "  uint32_t %s_len;\n" name;
-            pr "  char *%s;\n" name
-        | name, FUUID -> pr "  char %s[32]; /* this is NOT nul-terminated, be careful when printing */\n" name
-        | name, FUInt32 -> pr "  uint32_t %s;\n" name
-        | name, FInt32 -> pr "  int32_t %s;\n" name
-        | name, (FUInt64|FBytes) -> pr "  uint64_t %s;\n" name
-        | name, FInt64 -> pr "  int64_t %s;\n" name
-        | name, FOptPercent -> pr "  float %s; /* [0..100] or -1 */\n" name
-      ) cols;
-      pr "};\n";
-      pr "\n";
-      pr "struct guestfs_%s_list {\n" typ;
-      pr "  uint32_t len;\n";
-      pr "  struct guestfs_%s *val;\n" typ;
-      pr "};\n";
-      pr "\n";
-      pr "extern void guestfs_free_%s (struct guestfs_%s *);\n" typ typ;
-      pr "extern void guestfs_free_%s_list (struct guestfs_%s_list *);\n" typ typ;
-      pr "\n"
-  ) structs
-
-(* Generate the guestfs-actions.h file. *)
-and generate_actions_h () =
-  generate_header CStyle LGPLv2;
-  List.iter (
-    fun (shortname, style, _, _, _, _, _) ->
-      let name = "guestfs_" ^ shortname in
-      generate_prototype ~single_line:true ~newline:true ~handle:"handle"
-        name style
-  ) all_functions
-
-(* Generate the guestfs-internal-actions.h file. *)
-and generate_internal_actions_h () =
-  generate_header CStyle LGPLv2;
-  List.iter (
-    fun (shortname, style, _, _, _, _, _) ->
-      let name = "guestfs__" ^ shortname in
-      generate_prototype ~single_line:true ~newline:true ~handle:"handle"
-        name style
-  ) non_daemon_functions
-
-(* Generate the client-side dispatch stubs. *)
-and generate_client_actions () =
-  generate_header CStyle LGPLv2;
-
-  pr "\
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <inttypes.h>
+  ("findfs_uuid", (RString "device", [String "uuid"]), 265, [],
+   [],
+   "find a filesystem by UUID",
+   "\
+This command searches the filesystems and returns the one
+which has the given UUID.  An error is returned if no such
+filesystem can be found.
 
-#include \"guestfs.h\"
-#include \"guestfs-internal.h\"
-#include \"guestfs-internal-actions.h\"
-#include \"guestfs_protocol.h\"
+To find the UUID of a filesystem, use C<guestfs_vfs_uuid>.");
 
-#define error guestfs_error
-//#define perrorf guestfs_perrorf
-#define safe_malloc guestfs_safe_malloc
-#define safe_realloc guestfs_safe_realloc
-//#define safe_strdup guestfs_safe_strdup
-#define safe_memdup guestfs_safe_memdup
+  ("findfs_label", (RString "device", [String "label"]), 266, [],
+   [],
+   "find a filesystem by label",
+   "\
+This command searches the filesystems and returns the one
+which has the given label.  An error is returned if no such
+filesystem can be found.
 
-/* Check the return message from a call for validity. */
-static int
-check_reply_header (guestfs_h *g,
-                    const struct guestfs_message_header *hdr,
-                    unsigned int proc_nr, unsigned int serial)
-{
-  if (hdr->prog != GUESTFS_PROGRAM) {
-    error (g, \"wrong program (%%d/%%d)\", hdr->prog, GUESTFS_PROGRAM);
-    return -1;
-  }
-  if (hdr->vers != GUESTFS_PROTOCOL_VERSION) {
-    error (g, \"wrong protocol version (%%d/%%d)\",
-           hdr->vers, GUESTFS_PROTOCOL_VERSION);
-    return -1;
-  }
-  if (hdr->direction != GUESTFS_DIRECTION_REPLY) {
-    error (g, \"unexpected message direction (%%d/%%d)\",
-           hdr->direction, GUESTFS_DIRECTION_REPLY);
-    return -1;
-  }
-  if (hdr->proc != proc_nr) {
-    error (g, \"unexpected procedure number (%%d/%%d)\", hdr->proc, proc_nr);
-    return -1;
-  }
-  if (hdr->serial != serial) {
-    error (g, \"unexpected serial (%%d/%%d)\", hdr->serial, serial);
-    return -1;
-  }
+To find the label of a filesystem, use C<guestfs_vfs_label>.");
 
-  return 0;
-}
+  ("is_chardev", (RBool "flag", [Pathname "path"]), 267, [],
+   [InitISOFS, Always, TestOutputFalse (
+      [["is_chardev"; "/directory"]]);
+    InitBasicFS, Always, TestOutputTrue (
+      [["mknod_c"; "0o777"; "99"; "66"; "/test"];
+       ["is_chardev"; "/test"]])],
+   "test if character device",
+   "\
+This returns C<true> if and only if there is a character device
+with the given C<path> name.
 
-/* Check we are in the right state to run a high-level action. */
-static int
-check_state (guestfs_h *g, const char *caller)
-{
-  if (!guestfs__is_ready (g)) {
-    if (guestfs__is_config (g) || guestfs__is_launching (g))
-      error (g, \"%%s: call launch before using this function\\n(in guestfish, don't forget to use the 'run' command)\",
-        caller);
-    else
-      error (g, \"%%s called from the wrong state, %%d != READY\",
-        caller, guestfs__get_state (g));
-    return -1;
-  }
-  return 0;
-}
+See also C<guestfs_stat>.");
 
-";
+  ("is_blockdev", (RBool "flag", [Pathname "path"]), 268, [],
+   [InitISOFS, Always, TestOutputFalse (
+      [["is_blockdev"; "/directory"]]);
+    InitBasicFS, Always, TestOutputTrue (
+      [["mknod_b"; "0o777"; "99"; "66"; "/test"];
+       ["is_blockdev"; "/test"]])],
+   "test if block device",
+   "\
+This returns C<true> if and only if there is a block device
+with the given C<path> name.
 
-  (* Generate code to generate guestfish call traces. *)
-  let trace_call shortname style =
-    pr "  if (guestfs__get_trace (g)) {\n";
+See also C<guestfs_stat>.");
 
-    let needs_i =
-      List.exists (function
-                   | StringList _ | DeviceList _ -> true
-                   | _ -> false) (snd style) in
-    if needs_i then (
-      pr "    int i;\n";
-      pr "\n"
-    );
+  ("is_fifo", (RBool "flag", [Pathname "path"]), 269, [],
+   [InitISOFS, Always, TestOutputFalse (
+      [["is_fifo"; "/directory"]]);
+    InitBasicFS, Always, TestOutputTrue (
+      [["mkfifo"; "0o777"; "/test"];
+       ["is_fifo"; "/test"]])],
+   "test if FIFO (named pipe)",
+   "\
+This returns C<true> if and only if there is a FIFO (named pipe)
+with the given C<path> name.
 
-    pr "    printf (\"%s\");\n" shortname;
-    List.iter (
-      function
-      | String n                       (* strings *)
-      | Device n
-      | Pathname n
-      | Dev_or_Path n
-      | FileIn n
-      | FileOut n ->
-          (* guestfish doesn't support string escaping, so neither do we *)
-          pr "    printf (\" \\\"%%s\\\"\", %s);\n" n
-      | OptString n ->                 (* string option *)
-          pr "    if (%s) printf (\" \\\"%%s\\\"\", %s);\n" n n;
-          pr "    else printf (\" null\");\n"
-      | StringList n
-      | DeviceList n ->                        (* string list *)
-          pr "    putchar (' ');\n";
-          pr "    putchar ('\"');\n";
-          pr "    for (i = 0; %s[i]; ++i) {\n" n;
-          pr "      if (i > 0) putchar (' ');\n";
-          pr "      fputs (%s[i], stdout);\n" n;
-          pr "    }\n";
-          pr "    putchar ('\"');\n";
-      | Bool n ->                      (* boolean *)
-          pr "    fputs (%s ? \" true\" : \" false\", stdout);\n" n
-      | Int n ->                       (* int *)
-          pr "    printf (\" %%d\", %s);\n" n
-      | Int64 n ->
-          pr "    printf (\" %%\" PRIi64, %s);\n" n
-    ) (snd style);
-    pr "    putchar ('\\n');\n";
-    pr "  }\n";
-    pr "\n";
-  in
+See also C<guestfs_stat>.");
 
-  (* For non-daemon functions, generate a wrapper around each function. *)
-  List.iter (
-    fun (shortname, style, _, _, _, _, _) ->
-      let name = "guestfs_" ^ shortname in
+  ("is_symlink", (RBool "flag", [Pathname "path"]), 270, [],
+   [InitISOFS, Always, TestOutputFalse (
+      [["is_symlink"; "/directory"]]);
+    InitISOFS, Always, TestOutputTrue (
+      [["is_symlink"; "/abssymlink"]])],
+   "test if symbolic link",
+   "\
+This returns C<true> if and only if there is a symbolic link
+with the given C<path> name.
 
-      generate_prototype ~extern:false ~semicolon:false ~newline:true
-        ~handle:"g" name style;
-      pr "{\n";
-      trace_call shortname style;
-      pr "  return guestfs__%s " shortname;
-      generate_c_call_args ~handle:"g" style;
-      pr ";\n";
-      pr "}\n";
-      pr "\n"
-  ) non_daemon_functions;
+See also C<guestfs_stat>.");
 
-  (* Client-side stubs for each function. *)
-  List.iter (
-    fun (shortname, style, _, _, _, _, _) ->
-      let name = "guestfs_" ^ shortname in
+  ("is_socket", (RBool "flag", [Pathname "path"]), 271, [],
+   (* XXX Need a positive test for sockets. *)
+   [InitISOFS, Always, TestOutputFalse (
+      [["is_socket"; "/directory"]])],
+   "test if socket",
+   "\
+This returns C<true> if and only if there is a Unix domain socket
+with the given C<path> name.
 
-      (* Generate the action stub. *)
-      generate_prototype ~extern:false ~semicolon:false ~newline:true
-        ~handle:"g" name style;
+See also C<guestfs_stat>.");
 
-      let error_code =
-        match fst style with
-        | RErr | RInt _ | RInt64 _ | RBool _ -> "-1"
-        | RConstString _ | RConstOptString _ ->
-            failwithf "RConstString|RConstOptString cannot be used by daemon functions"
-        | RString _ | RStringList _
-        | RStruct _ | RStructList _
-        | RHashtable _ | RBufferOut _ ->
-            "NULL" in
+]
 
-      pr "{\n";
+let all_functions = non_daemon_functions @ daemon_functions
 
-      (match snd style with
-       | [] -> ()
-       | _ -> pr "  struct %s_args args;\n" name
-      );
+(* In some places we want the functions to be displayed sorted
+ * alphabetically, so this is useful:
+ *)
+let all_functions_sorted =
+  List.sort (fun (n1,_,_,_,_,_,_) (n2,_,_,_,_,_,_) ->
+               compare n1 n2) all_functions
 
-      pr "  guestfs_message_header hdr;\n";
-      pr "  guestfs_message_error err;\n";
-      let has_ret =
-        match fst style with
-        | RErr -> false
-        | RConstString _ | RConstOptString _ ->
-            failwithf "RConstString|RConstOptString cannot be used by daemon functions"
-        | RInt _ | RInt64 _
-        | RBool _ | RString _ | RStringList _
-        | RStruct _ | RStructList _
-        | RHashtable _ | RBufferOut _ ->
-            pr "  struct %s_ret ret;\n" name;
-            true in
+(* This is used to generate the src/MAX_PROC_NR file which
+ * contains the maximum procedure number, a surrogate for the
+ * ABI version number.  See src/Makefile.am for the details.
+ *)
+let max_proc_nr =
+  let proc_nrs = List.map (
+    fun (_, _, proc_nr, _, _, _, _) -> proc_nr
+  ) daemon_functions in
+  List.fold_left max 0 proc_nrs
 
-      pr "  int serial;\n";
-      pr "  int r;\n";
-      pr "\n";
-      trace_call shortname style;
-      pr "  if (check_state (g, \"%s\") == -1) return %s;\n" name error_code;
-      pr "  guestfs___set_busy (g);\n";
-      pr "\n";
+(* Field types for structures. *)
+type field =
+  | FChar                      (* C 'char' (really, a 7 bit byte). *)
+  | FString                    (* nul-terminated ASCII string, NOT NULL. *)
+  | FBuffer                    (* opaque buffer of bytes, (char *, int) pair *)
+  | FUInt32
+  | FInt32
+  | FUInt64
+  | FInt64
+  | FBytes                     (* Any int measure that counts bytes. *)
+  | FUUID                      (* 32 bytes long, NOT nul-terminated. *)
+  | FOptPercent                        (* [0..100], or -1 meaning "not present". *)
 
-      (* Send the main header and arguments. *)
-      (match snd style with
-       | [] ->
-           pr "  serial = guestfs___send (g, GUESTFS_PROC_%s, NULL, NULL);\n"
-             (String.uppercase shortname)
-       | args ->
-           List.iter (
-             function
-             | Pathname n | Device n | Dev_or_Path n | String n ->
-                 pr "  args.%s = (char *) %s;\n" n n
-             | OptString n ->
-                 pr "  args.%s = %s ? (char **) &%s : NULL;\n" n n n
-             | StringList n | DeviceList n ->
-                 pr "  args.%s.%s_val = (char **) %s;\n" n n n;
-                 pr "  for (args.%s.%s_len = 0; %s[args.%s.%s_len]; args.%s.%s_len++) ;\n" n n n n n n n;
+(* Because we generate extra parsing code for LVM command line tools,
+ * we have to pull out the LVM columns separately here.
+ *)
+let lvm_pv_cols = [
+  "pv_name", FString;
+  "pv_uuid", FUUID;
+  "pv_fmt", FString;
+  "pv_size", FBytes;
+  "dev_size", FBytes;
+  "pv_free", FBytes;
+  "pv_used", FBytes;
+  "pv_attr", FString (* XXX *);
+  "pv_pe_count", FInt64;
+  "pv_pe_alloc_count", FInt64;
+  "pv_tags", FString;
+  "pe_start", FBytes;
+  "pv_mda_count", FInt64;
+  "pv_mda_free", FBytes;
+  (* Not in Fedora 10:
+     "pv_mda_size", FBytes;
+  *)
+]
+let lvm_vg_cols = [
+  "vg_name", FString;
+  "vg_uuid", FUUID;
+  "vg_fmt", FString;
+  "vg_attr", FString (* XXX *);
+  "vg_size", FBytes;
+  "vg_free", FBytes;
+  "vg_sysid", FString;
+  "vg_extent_size", FBytes;
+  "vg_extent_count", FInt64;
+  "vg_free_count", FInt64;
+  "max_lv", FInt64;
+  "max_pv", FInt64;
+  "pv_count", FInt64;
+  "lv_count", FInt64;
+  "snap_count", FInt64;
+  "vg_seqno", FInt64;
+  "vg_tags", FString;
+  "vg_mda_count", FInt64;
+  "vg_mda_free", FBytes;
+  (* Not in Fedora 10:
+     "vg_mda_size", FBytes;
+  *)
+]
+let lvm_lv_cols = [
+  "lv_name", FString;
+  "lv_uuid", FUUID;
+  "lv_attr", FString (* XXX *);
+  "lv_major", FInt64;
+  "lv_minor", FInt64;
+  "lv_kernel_major", FInt64;
+  "lv_kernel_minor", FInt64;
+  "lv_size", FBytes;
+  "seg_count", FInt64;
+  "origin", FString;
+  "snap_percent", FOptPercent;
+  "copy_percent", FOptPercent;
+  "move_pv", FString;
+  "lv_tags", FString;
+  "mirror_log", FString;
+  "modules", FString;
+]
+
+(* Names and fields in all structures (in RStruct and RStructList)
+ * that we support.
+ *)
+let structs = [
+  (* The old RIntBool return type, only ever used for aug_defnode.  Do
+   * not use this struct in any new code.
+   *)
+  "int_bool", [
+    "i", FInt32;               (* for historical compatibility *)
+    "b", FInt32;               (* for historical compatibility *)
+  ];
+
+  (* LVM PVs, VGs, LVs. *)
+  "lvm_pv", lvm_pv_cols;
+  "lvm_vg", lvm_vg_cols;
+  "lvm_lv", lvm_lv_cols;
+
+  (* Column names and types from stat structures.
+   * NB. Can't use things like 'st_atime' because glibc header files
+   * define some of these as macros.  Ugh.
+   *)
+  "stat", [
+    "dev", FInt64;
+    "ino", FInt64;
+    "mode", FInt64;
+    "nlink", FInt64;
+    "uid", FInt64;
+    "gid", FInt64;
+    "rdev", FInt64;
+    "size", FInt64;
+    "blksize", FInt64;
+    "blocks", FInt64;
+    "atime", FInt64;
+    "mtime", FInt64;
+    "ctime", FInt64;
+  ];
+  "statvfs", [
+    "bsize", FInt64;
+    "frsize", FInt64;
+    "blocks", FInt64;
+    "bfree", FInt64;
+    "bavail", FInt64;
+    "files", FInt64;
+    "ffree", FInt64;
+    "favail", FInt64;
+    "fsid", FInt64;
+    "flag", FInt64;
+    "namemax", FInt64;
+  ];
+
+  (* Column names in dirent structure. *)
+  "dirent", [
+    "ino", FInt64;
+    (* 'b' 'c' 'd' 'f' (FIFO) 'l' 'r' (regular file) 's' 'u' '?' *)
+    "ftyp", FChar;
+    "name", FString;
+  ];
+
+  (* Version numbers. *)
+  "version", [
+    "major", FInt64;
+    "minor", FInt64;
+    "release", FInt64;
+    "extra", FString;
+  ];
+
+  (* Extended attribute. *)
+  "xattr", [
+    "attrname", FString;
+    "attrval", FBuffer;
+  ];
+
+  (* Inotify events. *)
+  "inotify_event", [
+    "in_wd", FInt64;
+    "in_mask", FUInt32;
+    "in_cookie", FUInt32;
+    "in_name", FString;
+  ];
+
+  (* Partition table entry. *)
+  "partition", [
+    "part_num", FInt32;
+    "part_start", FBytes;
+    "part_end", FBytes;
+    "part_size", FBytes;
+  ];
+] (* end of structs *)
+
+(* Ugh, Java has to be different ..
+ * These names are also used by the Haskell bindings.
+ *)
+let java_structs = [
+  "int_bool", "IntBool";
+  "lvm_pv", "PV";
+  "lvm_vg", "VG";
+  "lvm_lv", "LV";
+  "stat", "Stat";
+  "statvfs", "StatVFS";
+  "dirent", "Dirent";
+  "version", "Version";
+  "xattr", "XAttr";
+  "inotify_event", "INotifyEvent";
+  "partition", "Partition";
+]
+
+(* What structs are actually returned. *)
+type rstructs_used_t = RStructOnly | RStructListOnly | RStructAndList
+
+(* Returns a list of RStruct/RStructList structs that are returned
+ * by any function.  Each element of returned list is a pair:
+ *
+ * (structname, RStructOnly)
+ *    == there exists function which returns RStruct (_, structname)
+ * (structname, RStructListOnly)
+ *    == there exists function which returns RStructList (_, structname)
+ * (structname, RStructAndList)
+ *    == there are functions returning both RStruct (_, structname)
+ *                                      and RStructList (_, structname)
+ *)
+let rstructs_used_by functions =
+  (* ||| is a "logical OR" for rstructs_used_t *)
+  let (|||) a b =
+    match a, b with
+    | RStructAndList, _
+    | _, RStructAndList -> RStructAndList
+    | RStructOnly, RStructListOnly
+    | RStructListOnly, RStructOnly -> RStructAndList
+    | RStructOnly, RStructOnly -> RStructOnly
+    | RStructListOnly, RStructListOnly -> RStructListOnly
+  in
+
+  let h = Hashtbl.create 13 in
+
+  (* if elem->oldv exists, update entry using ||| operator,
+   * else just add elem->newv to the hash
+   *)
+  let update elem newv =
+    try  let oldv = Hashtbl.find h elem in
+         Hashtbl.replace h elem (newv ||| oldv)
+    with Not_found -> Hashtbl.add h elem newv
+  in
+
+  List.iter (
+    fun (_, style, _, _, _, _, _) ->
+      match fst style with
+      | RStruct (_, structname) -> update structname RStructOnly
+      | RStructList (_, structname) -> update structname RStructListOnly
+      | _ -> ()
+  ) functions;
+
+  (* return key->values as a list of (key,value) *)
+  Hashtbl.fold (fun key value xs -> (key, value) :: xs) h []
+
+(* Used for testing language bindings. *)
+type callt =
+  | CallString of string
+  | CallOptString of string option
+  | CallStringList of string list
+  | CallInt of int
+  | CallInt64 of int64
+  | CallBool of bool
+  | CallBuffer of string
+
+(* Used for the guestfish -N (prepared disk images) option.
+ * Note that the longdescs are indented by 2 spaces.
+ *)
+let prepopts = [
+  ("disk",
+   "create a blank disk",
+   [ "size", "100M", "the size of the disk image" ],
+   "  Create a blank disk, size 100MB (by default).
+
+  The default size can be changed by supplying an optional parameter.");
+
+  ("part",
+   "create a partitioned disk",
+   [ "size", "100M", "the size of the disk image";
+     "partition", "mbr", "partition table type" ],
+   "  Create a disk with a single partition.  By default the size of the disk
+  is 100MB (the available space in the partition will be a tiny bit smaller)
+  and the partition table will be MBR (old DOS-style).
+
+  These defaults can be changed by supplying optional parameters.");
+
+  ("fs",
+   "create a filesystem",
+   [ "filesystem", "ext2", "the type of filesystem to use";
+     "size", "100M", "the size of the disk image";
+     "partition", "mbr", "partition table type" ],
+   "  Create a disk with a single partition, with the partition containing
+  an empty filesystem.  This defaults to creating a 100MB disk (the available
+  space in the filesystem will be a tiny bit smaller) with an MBR (old
+  DOS-style) partition table and an ext2 filesystem.
+
+  These defaults can be changed by supplying optional parameters.");
+
+  ("lv",
+   "create a disk with logical volume",
+   [ "name", "/dev/VG/LV", "the name of the VG and LV to use";
+     "size", "100M", "the size of the disk image";
+     "partition", "mbr", "partition table type" ],
+   "  Create a disk with a single partition, set up the partition as an
+  LVM2 physical volume, and place a volume group and logical volume
+  on there.  This defaults to creating a 100MB disk with the VG and
+  LV called /dev/VG/LV.  You can change the name of the VG and LV
+  by supplying an alternate name as the first optional parameter.
+
+  Note this does not create a filesystem.  Use 'lvfs' to do that.");
+
+  ("lvfs",
+   "create a disk with logical volume and filesystem",
+   [ "name", "/dev/VG/LV", "the name of the VG and LV to use";
+     "filesystem", "ext2", "the type of filesystem to use";
+     "size", "100M", "the size of the disk image";
+     "partition", "mbr", "partition table type" ],
+   "  Create a disk with a single partition, set up the partition as an
+  LVM2 physical volume, and place a volume group and logical volume
+  on there.  Then format the LV with a filesystem.  This defaults to
+  creating a 100MB disk with the VG and LV called /dev/VG/LV, with an
+  ext2 filesystem.");
+
+  ("bootroot",
+   "create a boot and root filesystem",
+   [ "bootfs", "ext2", "the type of filesystem to use for boot";
+     "rootfs", "ext2", "the type of filesystem to use for root";
+     "size", "100M", "the size of the disk image";
+     "bootsize", "32M", "the size of the boot filesystem";
+     "partition", "mbr", "partition table type" ],
+   "  Create a disk with two partitions, for boot and root filesystem.
+  Format the two filesystems independently.  There are several optional
+  parameters which control the exact layout and filesystem types.");
+
+  ("bootrootlv",
+   "create a boot and root filesystem using LVM",
+   [ "name", "/dev/VG/LV", "the name of the VG and LV for root";
+     "bootfs", "ext2", "the type of filesystem to use for boot";
+     "rootfs", "ext2", "the type of filesystem to use for root";
+     "size", "100M", "the size of the disk image";
+     "bootsize", "32M", "the size of the boot filesystem";
+     "partition", "mbr", "partition table type" ],
+   "  This is the same as 'bootroot' but the root filesystem (only) is
+  placed on a logical volume, named by default '/dev/VG/LV'.  There are
+  several optional parameters which control the exact layout.");
+
+]
+
+(* Used to memoize the result of pod2text. *)
+let pod2text_memo_filename = "src/.pod2text.data"
+let pod2text_memo : ((int * string * string), string list) Hashtbl.t =
+  try
+    let chan = open_in pod2text_memo_filename in
+    let v = input_value chan in
+    close_in chan;
+    v
+  with
+    _ -> Hashtbl.create 13
+let pod2text_memo_updated () =
+  let chan = open_out pod2text_memo_filename in
+  output_value chan pod2text_memo;
+  close_out chan
+
+(* Useful functions.
+ * Note we don't want to use any external OCaml libraries which
+ * makes this a bit harder than it should be.
+ *)
+module StringMap = Map.Make (String)
+
+let failwithf fs = ksprintf failwith fs
+
+let unique = let i = ref 0 in fun () -> incr i; !i
+
+let replace_char s c1 c2 =
+  let s2 = String.copy s in
+  let r = ref false in
+  for i = 0 to String.length s2 - 1 do
+    if String.unsafe_get s2 i = c1 then (
+      String.unsafe_set s2 i c2;
+      r := true
+    )
+  done;
+  if not !r then s else s2
+
+let isspace c =
+  c = ' '
+  (* || c = '\f' *) || c = '\n' || c = '\r' || c = '\t' (* || c = '\v' *)
+
+let triml ?(test = isspace) str =
+  let i = ref 0 in
+  let n = ref (String.length str) in
+  while !n > 0 && test str.[!i]; do
+    decr n;
+    incr i
+  done;
+  if !i = 0 then str
+  else String.sub str !i !n
+
+let trimr ?(test = isspace) str =
+  let n = ref (String.length str) in
+  while !n > 0 && test str.[!n-1]; do
+    decr n
+  done;
+  if !n = String.length str then str
+  else String.sub str 0 !n
+
+let trim ?(test = isspace) str =
+  trimr ~test (triml ~test str)
+
+let rec find s sub =
+  let len = String.length s in
+  let sublen = String.length sub in
+  let rec loop i =
+    if i <= len-sublen then (
+      let rec loop2 j =
+        if j < sublen then (
+          if s.[i+j] = sub.[j] then loop2 (j+1)
+          else -1
+        ) else
+          i (* found *)
+      in
+      let r = loop2 0 in
+      if r = -1 then loop (i+1) else r
+    ) else
+      -1 (* not found *)
+  in
+  loop 0
+
+let rec replace_str s s1 s2 =
+  let len = String.length s in
+  let sublen = String.length s1 in
+  let i = find s s1 in
+  if i = -1 then s
+  else (
+    let s' = String.sub s 0 i in
+    let s'' = String.sub s (i+sublen) (len-i-sublen) in
+    s' ^ s2 ^ replace_str s'' s1 s2
+  )
+
+let rec string_split sep str =
+  let len = String.length str in
+  let seplen = String.length sep in
+  let i = find str sep in
+  if i = -1 then [str]
+  else (
+    let s' = String.sub str 0 i in
+    let s'' = String.sub str (i+seplen) (len-i-seplen) in
+    s' :: string_split sep s''
+  )
+
+let files_equal n1 n2 =
+  let cmd = sprintf "cmp -s %s %s" (Filename.quote n1) (Filename.quote n2) in
+  match Sys.command cmd with
+  | 0 -> true
+  | 1 -> false
+  | i -> failwithf "%s: failed with error code %d" cmd i
+
+let rec filter_map f = function
+  | [] -> []
+  | x :: xs ->
+      match f x with
+      | Some y -> y :: filter_map f xs
+      | None -> filter_map f xs
+
+let rec find_map f = function
+  | [] -> raise Not_found
+  | x :: xs ->
+      match f x with
+      | Some y -> y
+      | None -> find_map f xs
+
+let iteri f xs =
+  let rec loop i = function
+    | [] -> ()
+    | x :: xs -> f i x; loop (i+1) xs
+  in
+  loop 0 xs
+
+let mapi f xs =
+  let rec loop i = function
+    | [] -> []
+    | x :: xs -> let r = f i x in r :: loop (i+1) 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 explode str =
+  let r = ref [] in
+  for i = 0 to String.length str - 1 do
+    let c = String.unsafe_get str i in
+    r := c :: !r;
+  done;
+  List.rev !r
+
+let map_chars f str =
+  List.map f (explode str)
+
+let name_of_argt = function
+  | Pathname n | Device n | Dev_or_Path n | String n | OptString n
+  | StringList n | DeviceList n | Bool n | Int n | Int64 n
+  | FileIn n | FileOut n | BufferIn n | Key n -> n
+
+let java_name_of_struct typ =
+  try List.assoc typ java_structs
+  with Not_found ->
+    failwithf
+      "java_name_of_struct: no java_structs entry corresponding to %s" typ
+
+let cols_of_struct typ =
+  try List.assoc typ structs
+  with Not_found ->
+    failwithf "cols_of_struct: unknown struct %s" typ
+
+let seq_of_test = function
+  | TestRun s | TestOutput (s, _) | TestOutputList (s, _)
+  | TestOutputListOfDevices (s, _)
+  | TestOutputInt (s, _) | TestOutputIntOp (s, _, _)
+  | TestOutputTrue s | TestOutputFalse s
+  | TestOutputLength (s, _) | TestOutputBuffer (s, _)
+  | TestOutputStruct (s, _)
+  | TestLastFail s -> s
+
+(* Handling for function flags. *)
+let progress_message =
+  "This long-running command can generate progress notification messages
+so that the caller can display a progress bar or indicator.
+To receive these messages, the caller must register a progress
+callback.  See L<guestfs(3)/guestfs_set_progress_callback>."
+
+let protocol_limit_warning =
+  "Because of the message protocol, there is a transfer limit
+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
+can easily destroy all your data>."
+
+let deprecation_notice flags =
+  try
+    let alt =
+      find_map (function DeprecatedBy str -> Some str | _ -> None) flags in
+    let txt =
+      sprintf "This function is deprecated.
+In new code, use the C<%s> call instead.
+
+Deprecated functions will not be removed from the API, but the
+fact that they are deprecated indicates that there are problems
+with correct use of these functions." alt in
+    Some txt
+  with
+    Not_found -> None
+
+(* Create list of optional groups. *)
+let optgroups =
+  let h = Hashtbl.create 13 in
+  List.iter (
+    fun (name, _, _, flags, _, _, _) ->
+      List.iter (
+        function
+        | Optional group ->
+            let names = try Hashtbl.find h group with Not_found -> [] in
+            Hashtbl.replace h group (name :: names)
+        | _ -> ()
+      ) flags
+  ) daemon_functions;
+  let groups = Hashtbl.fold (fun k _ ks -> k :: ks) h [] in
+  let groups =
+    List.map (
+      fun group -> group, List.sort compare (Hashtbl.find h group)
+    ) groups in
+  List.sort (fun x y -> compare (fst x) (fst y)) groups
+
+(* Check function names etc. for consistency. *)
+let check_functions () =
+  let contains_uppercase str =
+    let len = String.length str in
+    let rec loop i =
+      if i >= len then false
+      else (
+        let c = str.[i] in
+        if c >= 'A' && c <= 'Z' then true
+        else loop (i+1)
+      )
+    in
+    loop 0
+  in
+
+  (* Check function names. *)
+  List.iter (
+    fun (name, _, _, _, _, _, _) ->
+      if String.length name >= 7 && String.sub name 0 7 = "guestfs" then
+        failwithf "function name %s does not need 'guestfs' prefix" name;
+      if name = "" then
+        failwithf "function name is empty";
+      if name.[0] < 'a' || name.[0] > 'z' then
+        failwithf "function name %s must start with lowercase a-z" name;
+      if String.contains name '-' then
+        failwithf "function name %s should not contain '-', use '_' instead."
+          name
+  ) all_functions;
+
+  (* Check function parameter/return names. *)
+  List.iter (
+    fun (name, style, _, _, _, _, _) ->
+      let check_arg_ret_name n =
+        if contains_uppercase n then
+          failwithf "%s param/ret %s should not contain uppercase chars"
+            name n;
+        if String.contains n '-' || String.contains n '_' then
+          failwithf "%s param/ret %s should not contain '-' or '_'"
+            name n;
+        if n = "value" then
+          failwithf "%s has a param/ret called 'value', which causes conflicts in the OCaml bindings, use something like 'val' or a more descriptive name" name;
+        if n = "int" || n = "char" || n = "short" || n = "long" then
+          failwithf "%s has a param/ret which conflicts with a C type (eg. 'int', 'char' etc.)" name;
+        if n = "i" || n = "n" then
+          failwithf "%s has a param/ret called 'i' or 'n', which will cause some conflicts in the generated code" name;
+        if n = "argv" || n = "args" then
+          failwithf "%s has a param/ret called 'argv' or 'args', which will cause some conflicts in the generated code" name;
+
+        (* List Haskell, OCaml and C keywords here.
+         * http://www.haskell.org/haskellwiki/Keywords
+         * http://caml.inria.fr/pub/docs/manual-ocaml/lex.html#operator-char
+         * http://en.wikipedia.org/wiki/C_syntax#Reserved_keywords
+         * Formatted via: cat c haskell ocaml|sort -u|grep -vE '_|^val$' \
+         *   |perl -pe 's/(.+)/"$1";/'|fmt -70
+         * Omitting _-containing words, since they're handled above.
+         * Omitting the OCaml reserved word, "val", is ok,
+         * and saves us from renaming several parameters.
+         *)
+        let reserved = [
+          "and"; "as"; "asr"; "assert"; "auto"; "begin"; "break"; "case";
+          "char"; "class"; "const"; "constraint"; "continue"; "data";
+          "default"; "deriving"; "do"; "done"; "double"; "downto"; "else";
+          "end"; "enum"; "exception"; "extern"; "external"; "false"; "float";
+          "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";
+          "rec"; "register"; "restrict"; "return"; "short"; "sig"; "signed";
+          "sizeof"; "static"; "struct"; "switch"; "then"; "to"; "true"; "try";
+          "type"; "typedef"; "union"; "unsigned"; "virtual"; "void";
+          "volatile"; "when"; "where"; "while";
+          ] in
+        if List.mem n reserved then
+          failwithf "%s has param/ret using reserved word %s" name n;
+      in
+
+      (match fst style with
+       | RErr -> ()
+       | RInt n | RInt64 n | RBool n
+       | RConstString n | RConstOptString n | RString n
+       | RStringList n | RStruct (n, _) | RStructList (n, _)
+       | RHashtable n | RBufferOut n ->
+           check_arg_ret_name n
+      );
+      List.iter (fun arg -> check_arg_ret_name (name_of_argt arg)) (snd style)
+  ) all_functions;
+
+  (* Check short descriptions. *)
+  List.iter (
+    fun (name, _, _, _, _, shortdesc, _) ->
+      if shortdesc.[0] <> Char.lowercase shortdesc.[0] then
+        failwithf "short description of %s should begin with lowercase." name;
+      let c = shortdesc.[String.length shortdesc-1] in
+      if c = '\n' || c = '.' then
+        failwithf "short description of %s should not end with . or \\n." name
+  ) all_functions;
+
+  (* Check long descriptions. *)
+  List.iter (
+    fun (name, _, _, _, _, _, longdesc) ->
+      if longdesc.[String.length longdesc-1] = '\n' then
+        failwithf "long description of %s should not end with \\n." name
+  ) all_functions;
+
+  (* Check proc_nrs. *)
+  List.iter (
+    fun (name, _, proc_nr, _, _, _, _) ->
+      if proc_nr <= 0 then
+        failwithf "daemon function %s should have proc_nr > 0" name
+  ) daemon_functions;
+
+  List.iter (
+    fun (name, _, proc_nr, _, _, _, _) ->
+      if proc_nr <> -1 then
+        failwithf "non-daemon function %s should have proc_nr -1" name
+  ) non_daemon_functions;
+
+  let proc_nrs =
+    List.map (fun (name, _, proc_nr, _, _, _, _) -> name, proc_nr)
+      daemon_functions in
+  let proc_nrs =
+    List.sort (fun (_,nr1) (_,nr2) -> compare nr1 nr2) proc_nrs in
+  let rec loop = function
+    | [] -> ()
+    | [_] -> ()
+    | (name1,nr1) :: ((name2,nr2) :: _ as rest) when nr1 < nr2 ->
+        loop rest
+    | (name1,nr1) :: (name2,nr2) :: _ ->
+        failwithf "%s and %s have conflicting procedure numbers (%d, %d)"
+          name1 name2 nr1 nr2
+  in
+  loop proc_nrs;
+
+  (* Check tests. *)
+  List.iter (
+    function
+      (* Ignore functions that have no tests.  We generate a
+       * warning when the user does 'make check' instead.
+       *)
+    | name, _, _, _, [], _, _ -> ()
+    | name, _, _, _, tests, _, _ ->
+        let funcs =
+          List.map (
+            fun (_, _, test) ->
+              match seq_of_test test with
+              | [] ->
+                  failwithf "%s has a test containing an empty sequence" name
+              | cmds -> List.map List.hd cmds
+          ) tests in
+        let funcs = List.flatten funcs in
+
+        let tested = List.mem name funcs in
+
+        if not tested then
+          failwithf "function %s has tests but does not test itself" name
+  ) all_functions
+
+(* 'pr' prints to the current output file. *)
+let chan = ref Pervasives.stdout
+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. *)
+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
+    | 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 "%s ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST.\n" c;
+  pr "%s\n" c;
+  pr "%s Copyright (C) %s Red Hat Inc.\n" c copyright_years;
+  pr "%s\n" c;
+  (match license with
+   | GPLv2plus ->
+       pr "%s This program is free software; you can redistribute it and/or modify\n" c;
+       pr "%s it under the terms of the GNU General Public License as published by\n" c;
+       pr "%s the Free Software Foundation; either version 2 of the License, or\n" c;
+       pr "%s (at your option) any later version.\n" c;
+       pr "%s\n" c;
+       pr "%s This program is distributed in the hope that it will be useful,\n" c;
+       pr "%s but WITHOUT ANY WARRANTY; without even the implied warranty of\n" c;
+       pr "%s MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n" c;
+       pr "%s GNU General Public License for more details.\n" c;
+       pr "%s\n" c;
+       pr "%s You should have received a copy of the GNU General Public License along\n" c;
+       pr "%s with this program; if not, write to the Free Software Foundation, Inc.,\n" c;
+       pr "%s 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n" c;
+
+   | LGPLv2plus ->
+       pr "%s This library is free software; you can redistribute it and/or\n" c;
+       pr "%s modify it under the terms of the GNU Lesser General Public\n" c;
+       pr "%s License as published by the Free Software Foundation; either\n" c;
+       pr "%s version 2 of the License, or (at your option) any later version.\n" c;
+       pr "%s\n" c;
+       pr "%s This library is distributed in the hope that it will be useful,\n" c;
+       pr "%s but WITHOUT ANY WARRANTY; without even the implied warranty of\n" c;
+       pr "%s MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n" c;
+       pr "%s Lesser General Public License for more details.\n" c;
+       pr "%s\n" c;
+       pr "%s You should have received a copy of the GNU Lesser General Public\n" c;
+       pr "%s License along with this library; if not, write to the Free Software\n" c;
+       pr "%s Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n" c;
+  );
+  (match comment with
+   | CStyle -> pr " */\n"
+   | CPlusPlusStyle
+   | HashStyle -> ()
+   | OCamlStyle -> pr " *)\n"
+   | HaskellStyle -> pr "-}\n"
+  );
+  pr "\n"
+
+(* Start of main code generation functions below this line. *)
+
+(* Generate the pod documentation for the C API. *)
+let rec generate_actions_pod () =
+  List.iter (
+    fun (shortname, style, _, flags, _, _, longdesc) ->
+      if not (List.mem NotInDocs flags) then (
+        let name = "guestfs_" ^ shortname in
+        pr "=head2 %s\n\n" name;
+        pr " ";
+        generate_prototype ~extern:false ~handle:"g" name style;
+        pr "\n\n";
+        pr "%s\n\n" longdesc;
+        (match fst style with
+         | RErr ->
+             pr "This function returns 0 on success or -1 on error.\n\n"
+         | RInt _ ->
+             pr "On error this function returns -1.\n\n"
+         | RInt64 _ ->
+             pr "On error this function returns -1.\n\n"
+         | RBool _ ->
+             pr "This function returns a C truth value on success or -1 on error.\n\n"
+         | RConstString _ ->
+             pr "This function returns a string, or NULL on error.
+The string is owned by the guest handle and must I<not> be freed.\n\n"
+         | RConstOptString _ ->
+             pr "This function returns a string which may be NULL.
+There is no way to return an error from this function.
+The string is owned by the guest handle and must I<not> be freed.\n\n"
+         | RString _ ->
+             pr "This function returns a string, or NULL on error.
+I<The caller must free the returned string after use>.\n\n"
+         | RStringList _ ->
+             pr "This function returns a NULL-terminated array of strings
+(like L<environ(3)>), or NULL if there was an error.
+I<The caller must free the strings and the array after use>.\n\n"
+         | RStruct (_, typ) ->
+             pr "This function returns a C<struct guestfs_%s *>,
+or NULL if there was an error.
+I<The caller must call C<guestfs_free_%s> after use>.\n\n" typ typ
+         | RStructList (_, typ) ->
+             pr "This function returns a C<struct guestfs_%s_list *>
+(see E<lt>guestfs-structs.hE<gt>),
+or NULL if there was an error.
+I<The caller must call C<guestfs_free_%s_list> after use>.\n\n" typ typ
+         | RHashtable _ ->
+             pr "This function returns a NULL-terminated array of
+strings, or NULL if there was an error.
+The array of strings will always have length C<2n+1>, where
+C<n> keys and values alternate, followed by the trailing NULL entry.
+I<The caller must free the strings and the array after use>.\n\n"
+         | RBufferOut _ ->
+             pr "This function returns a buffer, or NULL on error.
+The size of the returned buffer is written to C<*size_r>.
+I<The caller must free the returned buffer after use>.\n\n"
+        );
+        if List.mem Progress flags then
+          pr "%s\n\n" progress_message;
+        if List.mem ProtocolLimitWarning flags then
+          pr "%s\n\n" protocol_limit_warning;
+        if List.mem DangerWillRobinson flags then
+          pr "%s\n\n" danger_will_robinson;
+        if List.exists (function Key _ -> true | _ -> false) (snd style) then
+          pr "This function takes a key or passphrase parameter which
+could contain sensitive material.  Read the section
+L</KEYS AND PASSPHRASES> for more information.\n\n";
+        match deprecation_notice flags with
+        | None -> ()
+        | Some txt -> pr "%s\n\n" txt
+      )
+  ) all_functions_sorted
+
+and generate_structs_pod () =
+  (* Structs documentation. *)
+  List.iter (
+    fun (typ, cols) ->
+      pr "=head2 guestfs_%s\n" typ;
+      pr "\n";
+      pr " struct guestfs_%s {\n" typ;
+      List.iter (
+        function
+        | name, FChar -> pr "   char %s;\n" name
+        | name, FUInt32 -> pr "   uint32_t %s;\n" name
+        | name, FInt32 -> pr "   int32_t %s;\n" name
+        | name, (FUInt64|FBytes) -> pr "   uint64_t %s;\n" name
+        | name, FInt64 -> pr "   int64_t %s;\n" name
+        | name, FString -> pr "   char *%s;\n" name
+        | name, FBuffer ->
+            pr "   /* The next two fields describe a byte array. */\n";
+            pr "   uint32_t %s_len;\n" name;
+            pr "   char *%s;\n" name
+        | name, FUUID ->
+            pr "   /* The next field is NOT nul-terminated, be careful when printing it: */\n";
+            pr "   char %s[32];\n" name
+        | name, FOptPercent ->
+            pr "   /* The next field is [0..100] or -1 meaning 'not present': */\n";
+            pr "   float %s;\n" name
+      ) cols;
+      pr " };\n";
+      pr " \n";
+      pr " struct guestfs_%s_list {\n" typ;
+      pr "   uint32_t len; /* Number of elements in list. */\n";
+      pr "   struct guestfs_%s *val; /* Elements. */\n" typ;
+      pr " };\n";
+      pr " \n";
+      pr " void guestfs_free_%s (struct guestfs_free_%s *);\n" typ typ;
+      pr " void guestfs_free_%s_list (struct guestfs_free_%s_list *);\n"
+        typ typ;
+      pr "\n"
+  ) structs
+
+and generate_availability_pod () =
+  (* Availability documentation. *)
+  pr "=over 4\n";
+  pr "\n";
+  List.iter (
+    fun (group, functions) ->
+      pr "=item B<%s>\n" group;
+      pr "\n";
+      pr "The following functions:\n";
+      List.iter (pr "L</guestfs_%s>\n") functions;
+      pr "\n"
+  ) optgroups;
+  pr "=back\n";
+  pr "\n"
+
+(* Generate the protocol (XDR) file, 'guestfs_protocol.x' and
+ * indirectly 'guestfs_protocol.h' and 'guestfs_protocol.c'.
+ *
+ * We have to use an underscore instead of a dash because otherwise
+ * rpcgen generates incorrect code.
+ *
+ * This header is NOT exported to clients, but see also generate_structs_h.
+ *)
+and generate_xdr () =
+  generate_header CStyle LGPLv2plus;
+
+  (* This has to be defined to get around a limitation in Sun's rpcgen. *)
+  pr "typedef string guestfs_str<>;\n";
+  pr "\n";
+
+  (* Internal structures. *)
+  List.iter (
+    function
+    | typ, cols ->
+        pr "struct guestfs_int_%s {\n" typ;
+        List.iter (function
+                   | name, FChar -> pr "  char %s;\n" name
+                   | name, FString -> pr "  string %s<>;\n" name
+                   | name, FBuffer -> pr "  opaque %s<>;\n" name
+                   | name, FUUID -> pr "  opaque %s[32];\n" name
+                   | name, (FInt32|FUInt32) -> pr "  int %s;\n" name
+                   | name, (FInt64|FUInt64|FBytes) -> pr "  hyper %s;\n" name
+                   | name, FOptPercent -> pr "  float %s;\n" name
+                  ) cols;
+        pr "};\n";
+        pr "\n";
+        pr "typedef struct guestfs_int_%s guestfs_int_%s_list<>;\n" typ typ;
+        pr "\n";
+  ) structs;
+
+  List.iter (
+    fun (shortname, style, _, _, _, _, _) ->
+      let name = "guestfs_" ^ shortname in
+
+      (match snd style with
+       | [] -> ()
+       | args ->
+           pr "struct %s_args {\n" name;
+           List.iter (
+             function
+             | Pathname n | Device n | Dev_or_Path n | String n | Key n ->
+                 pr "  string %s<>;\n" n
+             | OptString n -> pr "  guestfs_str *%s;\n" n
+             | StringList n | DeviceList n -> pr "  guestfs_str %s<>;\n" n
+             | Bool n -> pr "  bool %s;\n" n
+             | Int n -> pr "  int %s;\n" n
+             | Int64 n -> pr "  hyper %s;\n" n
+             | BufferIn n ->
+                 pr "  opaque %s<>;\n" n
+             | FileIn _ | FileOut _ -> ()
+           ) args;
+           pr "};\n\n"
+      );
+      (match fst style with
+       | RErr -> ()
+       | RInt n ->
+           pr "struct %s_ret {\n" name;
+           pr "  int %s;\n" n;
+           pr "};\n\n"
+       | RInt64 n ->
+           pr "struct %s_ret {\n" name;
+           pr "  hyper %s;\n" n;
+           pr "};\n\n"
+       | RBool n ->
+           pr "struct %s_ret {\n" name;
+           pr "  bool %s;\n" n;
+           pr "};\n\n"
+       | RConstString _ | RConstOptString _ ->
+           failwithf "RConstString|RConstOptString cannot be used by daemon functions"
+       | RString n ->
+           pr "struct %s_ret {\n" name;
+           pr "  string %s<>;\n" n;
+           pr "};\n\n"
+       | RStringList n ->
+           pr "struct %s_ret {\n" name;
+           pr "  guestfs_str %s<>;\n" n;
+           pr "};\n\n"
+       | RStruct (n, typ) ->
+           pr "struct %s_ret {\n" name;
+           pr "  guestfs_int_%s %s;\n" typ n;
+           pr "};\n\n"
+       | RStructList (n, typ) ->
+           pr "struct %s_ret {\n" name;
+           pr "  guestfs_int_%s_list %s;\n" typ n;
+           pr "};\n\n"
+       | RHashtable n ->
+           pr "struct %s_ret {\n" name;
+           pr "  guestfs_str %s<>;\n" n;
+           pr "};\n\n"
+       | RBufferOut n ->
+           pr "struct %s_ret {\n" name;
+           pr "  opaque %s<>;\n" n;
+           pr "};\n\n"
+      );
+  ) daemon_functions;
+
+  (* Table of procedure numbers. *)
+  pr "enum guestfs_procedure {\n";
+  List.iter (
+    fun (shortname, _, proc_nr, _, _, _, _) ->
+      pr "  GUESTFS_PROC_%s = %d,\n" (String.uppercase shortname) proc_nr
+  ) daemon_functions;
+  pr "  GUESTFS_PROC_NR_PROCS\n";
+  pr "};\n";
+  pr "\n";
+
+  (* 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.
+   *)
+  pr "const GUESTFS_MESSAGE_MAX = %d;\n" (4 * 1024 * 1024);
+  pr "\n";
+
+  (* Message header, etc. *)
+  pr "\
+/* The communication protocol is now documented in the guestfs(3)
+ * manpage.
+ */
+
+const GUESTFS_PROGRAM = 0x2000F5F5;
+const GUESTFS_PROTOCOL_VERSION = 2;
+
+/* These constants must be larger than any possible message length. */
+const GUESTFS_LAUNCH_FLAG = 0xf5f55ff5;
+const GUESTFS_CANCEL_FLAG = 0xffffeeee;
+const GUESTFS_PROGRESS_FLAG = 0xffff5555;
+
+enum guestfs_message_direction {
+  GUESTFS_DIRECTION_CALL = 0,        /* client -> daemon */
+  GUESTFS_DIRECTION_REPLY = 1        /* daemon -> client */
+};
+
+enum guestfs_message_status {
+  GUESTFS_STATUS_OK = 0,
+  GUESTFS_STATUS_ERROR = 1
+};
+
+";
+
+  pr "const GUESTFS_ERROR_LEN = %d;\n" (64 * 1024);
+  pr "\n";
+
+  pr "\
+struct guestfs_message_error {
+  int linux_errno;                   /* Linux errno if available. */
+  string error_message<GUESTFS_ERROR_LEN>;
+};
+
+struct guestfs_message_header {
+  unsigned prog;                     /* GUESTFS_PROGRAM */
+  unsigned vers;                     /* GUESTFS_PROTOCOL_VERSION */
+  guestfs_procedure proc;            /* GUESTFS_PROC_x */
+  guestfs_message_direction direction;
+  unsigned serial;                   /* message serial number */
+  guestfs_message_status status;
+};
+
+const GUESTFS_MAX_CHUNK_SIZE = 8192;
+
+struct guestfs_chunk {
+  int cancel;                       /* if non-zero, transfer is cancelled */
+  /* data size is 0 bytes if the transfer has finished successfully */
+  opaque data<GUESTFS_MAX_CHUNK_SIZE>;
+};
+
+/* Progress notifications.  Daemon self-limits these messages to
+ * at most one per second.  The daemon can send these messages
+ * at any time, and the caller should discard unexpected messages.
+ * 'position' and 'total' have undefined units; however they may
+ * have meaning for some calls.
+ *
+ * NB. guestfs___recv_from_daemon assumes the XDR-encoded
+ * structure is 24 bytes long.
+ */
+struct guestfs_progress {
+  guestfs_procedure proc;            /* @0:  GUESTFS_PROC_x */
+  unsigned serial;                   /* @4:  message serial number */
+  unsigned hyper position;           /* @8:  0 <= position <= total */
+  unsigned hyper total;              /* @16: total size of operation */
+                                     /* @24: size of structure */
+};
+"
+
+(* Generate the guestfs-structs.h file. *)
+and generate_structs_h () =
+  generate_header CStyle LGPLv2plus;
+
+  (* This is a public exported header file containing various
+   * structures.  The structures are carefully written to have
+   * exactly the same in-memory format as the XDR structures that
+   * we use on the wire to the daemon.  The reason for creating
+   * copies of these structures here is just so we don't have to
+   * export the whole of guestfs_protocol.h (which includes much
+   * unrelated and XDR-dependent stuff that we don't want to be
+   * public, or required by clients).
+   *
+   * To reiterate, we will pass these structures to and from the
+   * client with a simple assignment or memcpy, so the format
+   * must be identical to what rpcgen / the RFC defines.
+   *)
+
+  (* Public structures. *)
+  List.iter (
+    fun (typ, cols) ->
+      pr "struct guestfs_%s {\n" typ;
+      List.iter (
+        function
+        | name, FChar -> pr "  char %s;\n" name
+        | name, FString -> pr "  char *%s;\n" name
+        | name, FBuffer ->
+            pr "  uint32_t %s_len;\n" name;
+            pr "  char *%s;\n" name
+        | name, FUUID -> pr "  char %s[32]; /* this is NOT nul-terminated, be careful when printing */\n" name
+        | name, FUInt32 -> pr "  uint32_t %s;\n" name
+        | name, FInt32 -> pr "  int32_t %s;\n" name
+        | name, (FUInt64|FBytes) -> pr "  uint64_t %s;\n" name
+        | name, FInt64 -> pr "  int64_t %s;\n" name
+        | name, FOptPercent -> pr "  float %s; /* [0..100] or -1 */\n" name
+      ) cols;
+      pr "};\n";
+      pr "\n";
+      pr "struct guestfs_%s_list {\n" typ;
+      pr "  uint32_t len;\n";
+      pr "  struct guestfs_%s *val;\n" typ;
+      pr "};\n";
+      pr "\n";
+      pr "extern void guestfs_free_%s (struct guestfs_%s *);\n" typ typ;
+      pr "extern void guestfs_free_%s_list (struct guestfs_%s_list *);\n" typ typ;
+      pr "\n"
+  ) structs
+
+(* Generate the guestfs-actions.h file. *)
+and generate_actions_h () =
+  generate_header CStyle LGPLv2plus;
+  List.iter (
+    fun (shortname, style, _, flags, _, _, _) ->
+      let name = "guestfs_" ^ shortname in
+
+      let deprecated =
+        List.exists (function DeprecatedBy _ -> true | _ -> false) flags in
+      let test0 =
+        String.length shortname >= 5 && String.sub shortname 0 5 = "test0" in
+      let debug =
+        String.length shortname >= 5 && String.sub shortname 0 5 = "debug" in
+      if not deprecated && not test0 && not debug then
+        pr "#define LIBGUESTFS_HAVE_%s 1\n" (String.uppercase shortname);
+
+      generate_prototype ~single_line:true ~newline:true ~handle:"g"
+        name style
+  ) all_functions_sorted
+
+(* Generate the guestfs-internal-actions.h file. *)
+and generate_internal_actions_h () =
+  generate_header CStyle LGPLv2plus;
+  List.iter (
+    fun (shortname, style, _, _, _, _, _) ->
+      let name = "guestfs__" ^ shortname in
+      generate_prototype ~single_line:true ~newline:true ~handle:"g"
+        name style
+  ) non_daemon_functions
+
+(* Generate the client-side dispatch stubs. *)
+and generate_client_actions () =
+  generate_header CStyle LGPLv2plus;
+
+  pr "\
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include \"guestfs.h\"
+#include \"guestfs-internal.h\"
+#include \"guestfs-internal-actions.h\"
+#include \"guestfs_protocol.h\"
+
+/* Check the return message from a call for validity. */
+static int
+check_reply_header (guestfs_h *g,
+                    const struct guestfs_message_header *hdr,
+                    unsigned int proc_nr, unsigned int serial)
+{
+  if (hdr->prog != GUESTFS_PROGRAM) {
+    error (g, \"wrong program (%%d/%%d)\", hdr->prog, GUESTFS_PROGRAM);
+    return -1;
+  }
+  if (hdr->vers != GUESTFS_PROTOCOL_VERSION) {
+    error (g, \"wrong protocol version (%%d/%%d)\",
+           hdr->vers, GUESTFS_PROTOCOL_VERSION);
+    return -1;
+  }
+  if (hdr->direction != GUESTFS_DIRECTION_REPLY) {
+    error (g, \"unexpected message direction (%%d/%%d)\",
+           hdr->direction, GUESTFS_DIRECTION_REPLY);
+    return -1;
+  }
+  if (hdr->proc != proc_nr) {
+    error (g, \"unexpected procedure number (%%d/%%d)\", hdr->proc, proc_nr);
+    return -1;
+  }
+  if (hdr->serial != serial) {
+    error (g, \"unexpected serial (%%d/%%d)\", hdr->serial, serial);
+    return -1;
+  }
+
+  return 0;
+}
+
+/* Check we are in the right state to run a high-level action. */
+static int
+check_state (guestfs_h *g, const char *caller)
+{
+  if (!guestfs__is_ready (g)) {
+    if (guestfs__is_config (g) || guestfs__is_launching (g))
+      error (g, \"%%s: call launch before using this function\\n(in guestfish, don't forget to use the 'run' command)\",
+        caller);
+    else
+      error (g, \"%%s called from the wrong state, %%d != READY\",
+        caller, guestfs__get_state (g));
+    return -1;
+  }
+  return 0;
+}
+
+";
+
+  let error_code_of = function
+    | RErr | RInt _ | RInt64 _ | RBool _ -> "-1"
+    | RConstString _ | RConstOptString _
+    | RString _ | RStringList _
+    | RStruct _ | RStructList _
+    | RHashtable _ | RBufferOut _ -> "NULL"
+  in
+
+  (* Generate code to check String-like parameters are not passed in
+   * as NULL (returning an error if they are).
+   *)
+  let check_null_strings shortname style =
+    let pr_newline = ref false in
+    List.iter (
+      function
+      (* parameters which should not be NULL *)
+      | String n
+      | Device n
+      | Pathname n
+      | Dev_or_Path n
+      | FileIn n
+      | FileOut n
+      | BufferIn n
+      | StringList n
+      | DeviceList n
+      | Key n ->
+          pr "  if (%s == NULL) {\n" n;
+          pr "    error (g, \"%%s: %%s: parameter cannot be NULL\",\n";
+          pr "           \"%s\", \"%s\");\n" shortname n;
+          pr "    return %s;\n" (error_code_of (fst style));
+          pr "  }\n";
+          pr_newline := true
+
+      (* can be NULL *)
+      | OptString _
+
+      (* not applicable *)
+      | Bool _
+      | Int _
+      | Int64 _ -> ()
+    ) (snd style);
+
+    if !pr_newline then pr "\n";
+  in
+
+  (* Generate code to generate guestfish call traces. *)
+  let trace_call shortname style =
+    pr "  if (guestfs__get_trace (g)) {\n";
+
+    let needs_i =
+      List.exists (function
+                   | StringList _ | DeviceList _ -> true
+                   | _ -> false) (snd style) in
+    if needs_i then (
+      pr "    size_t i;\n";
+      pr "\n"
+    );
+
+    pr "    fprintf (stderr, \"%s\");\n" shortname;
+    List.iter (
+      function
+      | String n                       (* strings *)
+      | Device n
+      | Pathname n
+      | Dev_or_Path n
+      | FileIn n
+      | FileOut n
+      | BufferIn n
+      | Key n ->
+          (* guestfish doesn't support string escaping, so neither do we *)
+          pr "    fprintf (stderr, \" \\\"%%s\\\"\", %s);\n" n
+      | OptString n ->                 (* string option *)
+          pr "    if (%s) fprintf (stderr, \" \\\"%%s\\\"\", %s);\n" n n;
+          pr "    else fprintf (stderr, \" null\");\n"
+      | StringList n
+      | DeviceList n ->                        (* string list *)
+          pr "    fputc (' ', stderr);\n";
+          pr "    fputc ('\"', stderr);\n";
+          pr "    for (i = 0; %s[i]; ++i) {\n" n;
+          pr "      if (i > 0) fputc (' ', stderr);\n";
+          pr "      fputs (%s[i], stderr);\n" n;
+          pr "    }\n";
+          pr "    fputc ('\"', stderr);\n";
+      | Bool n ->                      (* boolean *)
+          pr "    fputs (%s ? \" true\" : \" false\", stderr);\n" n
+      | Int n ->                       (* int *)
+          pr "    fprintf (stderr, \" %%d\", %s);\n" n
+      | Int64 n ->
+          pr "    fprintf (stderr, \" %%\" PRIi64, %s);\n" n
+    ) (snd style);
+    pr "    fputc ('\\n', stderr);\n";
+    pr "  }\n";
+    pr "\n";
+  in
+
+  (* For non-daemon functions, generate a wrapper around each function. *)
+  List.iter (
+    fun (shortname, style, _, _, _, _, _) ->
+      let name = "guestfs_" ^ shortname in
+
+      generate_prototype ~extern:false ~semicolon:false ~newline:true
+        ~handle:"g" name style;
+      pr "{\n";
+      check_null_strings shortname style;
+      trace_call shortname style;
+      pr "  return guestfs__%s " shortname;
+      generate_c_call_args ~handle:"g" style;
+      pr ";\n";
+      pr "}\n";
+      pr "\n"
+  ) non_daemon_functions;
+
+  (* Client-side stubs for each function. *)
+  List.iter (
+    fun (shortname, style, _, _, _, _, _) ->
+      let name = "guestfs_" ^ shortname in
+      let error_code = error_code_of (fst style) in
+
+      (* Generate the action stub. *)
+      generate_prototype ~extern:false ~semicolon:false ~newline:true
+        ~handle:"g" name style;
+
+      pr "{\n";
+
+      (match snd style with
+       | [] -> ()
+       | _ -> pr "  struct %s_args args;\n" name
+      );
+
+      pr "  guestfs_message_header hdr;\n";
+      pr "  guestfs_message_error err;\n";
+      let has_ret =
+        match fst style with
+        | RErr -> false
+        | RConstString _ | RConstOptString _ ->
+            failwithf "RConstString|RConstOptString cannot be used by daemon functions"
+        | RInt _ | RInt64 _
+        | RBool _ | RString _ | RStringList _
+        | RStruct _ | RStructList _
+        | RHashtable _ | RBufferOut _ ->
+            pr "  struct %s_ret ret;\n" name;
+            true in
+
+      pr "  int serial;\n";
+      pr "  int r;\n";
+      pr "\n";
+      check_null_strings shortname style;
+      trace_call shortname style;
+      pr "  if (check_state (g, \"%s\") == -1) return %s;\n"
+        shortname error_code;
+      pr "  guestfs___set_busy (g);\n";
+      pr "\n";
+
+      (* Send the main header and arguments. *)
+      (match snd style with
+       | [] ->
+           pr "  serial = guestfs___send (g, GUESTFS_PROC_%s, NULL, NULL);\n"
+             (String.uppercase shortname)
+       | args ->
+           List.iter (
+             function
+             | Pathname n | Device n | Dev_or_Path n | String n | Key n ->
+                 pr "  args.%s = (char *) %s;\n" n n
+             | OptString n ->
+                 pr "  args.%s = %s ? (char **) &%s : NULL;\n" n n n
+             | StringList n | DeviceList n ->
+                 pr "  args.%s.%s_val = (char **) %s;\n" n n n;
+                 pr "  for (args.%s.%s_len = 0; %s[args.%s.%s_len]; args.%s.%s_len++) ;\n" n n n n n n n;
              | Bool n ->
                  pr "  args.%s = %s;\n" n n
              | Int n ->
@@ -5394,6 +6863,16 @@ check_state (guestfs_h *g, const char *caller)
              | Int64 n ->
                  pr "  args.%s = %s;\n" n n
              | FileIn _ | FileOut _ -> ()
+             | BufferIn n ->
+                 pr "  /* Just catch grossly large sizes. XDR encoding will make this precise. */\n";
+                 pr "  if (%s_size >= GUESTFS_MESSAGE_MAX) {\n" n;
+                 pr "    error (g, \"%%s: size of input buffer too large\", \"%s\");\n"
+                   shortname;
+                 pr "    guestfs___end_busy (g);\n";
+                 pr "    return %s;\n" error_code;
+                 pr "  }\n";
+                 pr "  args.%s.%s_val = (char *) %s;\n" n n n;
+                 pr "  args.%s.%s_len = %s_size;\n" n n n
            ) args;
            pr "  serial = guestfs___send (g, GUESTFS_PROC_%s,\n"
              (String.uppercase shortname);
@@ -5542,7 +7021,7 @@ check_state (guestfs_h *g, const char *caller)
 
 (* Generate daemon/actions.h. *)
 and generate_daemon_actions_h () =
-  generate_header CStyle GPLv2;
+  generate_header CStyle GPLv2plus;
 
   pr "#include \"../src/guestfs_protocol.h\"\n";
   pr "\n";
@@ -5554,9 +7033,60 @@ and generate_daemon_actions_h () =
         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_get_private";
+    "guestfs_last_error";
+    "guestfs_set_close_callback";
+    "guestfs_set_error_handler";
+    "guestfs_set_launch_done_callback";
+    "guestfs_set_log_message_callback";
+    "guestfs_set_out_of_memory_handler";
+    "guestfs_set_private";
+    "guestfs_set_progress_callback";
+    "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";
+    "guestfs_safe_strdup";
+    "guestfs_safe_memdup";
+  ] 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 GPLv2;
+  generate_header CStyle GPLv2plus;
 
   pr "#include <config.h>\n";
   pr "\n";
@@ -5602,25 +7132,34 @@ and generate_daemon_actions () =
              function
              | Device n | Dev_or_Path n
              | Pathname n
-             | String n -> ()
+             | String n
+             | Key n -> ()
              | OptString n -> pr "  char *%s;\n" n
              | StringList n | DeviceList n -> pr "  char **%s;\n" n
              | Bool n -> pr "  int %s;\n" n
              | Int n -> pr "  int %s;\n" n
              | Int64 n -> pr "  int64_t %s;\n" n
              | FileIn _ | FileOut _ -> ()
+             | BufferIn n ->
+                 pr "  const char *%s;\n" n;
+                 pr "  size_t %s_size;\n" n
            ) args
       );
       pr "\n";
 
+      let is_filein =
+        List.exists (function FileIn _ -> true | _ -> false) (snd style) in
+
       (match snd style with
        | [] -> ()
        | args ->
            pr "  memset (&args, 0, sizeof args);\n";
            pr "\n";
            pr "  if (!xdr_guestfs_%s_args (xdr_in, &args)) {\n" name;
-           pr "    reply_with_error (\"%%s: daemon failed to decode procedure arguments\", \"%s\");\n" name;
-           pr "    return;\n";
+           if is_filein then
+             pr "    if (cancel_receive () != -2)\n";
+           pr "      reply_with_error (\"daemon failed to decode procedure arguments\");\n";
+           pr "    goto done;\n";
            pr "  }\n";
            let pr_args n =
              pr "  char *%s = args.%s;\n" n n
@@ -5629,7 +7168,9 @@ and generate_daemon_actions () =
              pr "  %s = realloc (args.%s.%s_val,\n" n n n;
              pr "                sizeof (char *) * (args.%s.%s_len+1));\n" n n;
              pr "  if (%s == NULL) {\n" n;
-             pr "    reply_with_perror (\"realloc\");\n";
+             if is_filein then
+               pr "    if (cancel_receive () != -2)\n";
+             pr "      reply_with_perror (\"realloc\");\n";
              pr "    goto done;\n";
              pr "  }\n";
              pr "  %s[args.%s.%s_len] = NULL;\n" n n n;
@@ -5639,38 +7180,48 @@ and generate_daemon_actions () =
              function
              | Pathname n ->
                  pr_args n;
-                 pr "  ABS_PATH (%s, goto done);\n" n;
+                 pr "  ABS_PATH (%s, %s, goto done);\n"
+                   n (if is_filein then "cancel_receive ()" else "0");
              | Device n ->
                  pr_args n;
-                 pr "  RESOLVE_DEVICE (%s, goto done);\n" n;
+                 pr "  RESOLVE_DEVICE (%s, %s, goto done);\n"
+                   n (if is_filein then "cancel_receive ()" else "0");
              | Dev_or_Path n ->
                  pr_args n;
-                 pr "  REQUIRE_ROOT_OR_RESOLVE_DEVICE (%s, goto done);\n" n;
-             | String n -> pr_args n
+                 pr "  REQUIRE_ROOT_OR_RESOLVE_DEVICE (%s, %s, goto done);\n"
+                   n (if is_filein then "cancel_receive ()" else "0");
+             | String n | Key n -> pr_args n
              | OptString n -> pr "  %s = args.%s ? *args.%s : NULL;\n" n n n
              | StringList n ->
                  pr_list_handling_code n;
              | DeviceList n ->
                  pr_list_handling_code n;
                  pr "  /* Ensure that each is a device,\n";
-                 pr "   * and perform device name translation. */\n";
-                 pr "  { int pvi; for (pvi = 0; physvols[pvi] != NULL; ++pvi)\n";
-                 pr "    RESOLVE_DEVICE (physvols[pvi], goto done);\n";
+                 pr "   * and perform device name translation.\n";
+                 pr "   */\n";
+                 pr "  {\n";
+                 pr "    size_t i;\n";
+                 pr "    for (i = 0; %s[i] != NULL; ++i)\n" n;
+                 pr "      RESOLVE_DEVICE (%s[i], %s, goto done);\n" n
+                   (if is_filein then "cancel_receive ()" else "0");
                  pr "  }\n";
              | Bool n -> pr "  %s = args.%s;\n" n n
              | Int n -> pr "  %s = args.%s;\n" n n
              | Int64 n -> pr "  %s = args.%s;\n" n n
              | FileIn _ | FileOut _ -> ()
+             | BufferIn n ->
+                 pr "  %s = args.%s.%s_val;\n" n n n;
+                 pr "  %s_size = args.%s.%s_len;\n" n n n
            ) args;
            pr "\n"
       );
 
-
       (* this is used at least for do_equal *)
       if List.exists (function Pathname _ -> true | _ -> false) (snd style) then (
         (* Emit NEED_ROOT just once, even when there are two or
            more Pathname args *)
-        pr "  NEED_ROOT (goto done);\n";
+        pr "  NEED_ROOT (%s, goto done);\n"
+          (if is_filein then "cancel_receive ()" else "0");
       );
 
       (* Don't want to call the impl with any FileIn or FileOut
@@ -5756,15 +7307,14 @@ and generate_daemon_actions () =
       );
 
       (* Free the args. *)
+      pr "done:\n";
       (match snd style with
-       | [] ->
-           pr "done: ;\n";
+       | [] -> ()
        | _ ->
-           pr "done:\n";
            pr "  xdr_free ((xdrproc_t) xdr_guestfs_%s_args, (char *) &args);\n"
              name
       );
-
+      pr "  return;\n";
       pr "}\n\n";
   ) daemon_functions;
 
@@ -5800,7 +7350,7 @@ and generate_daemon_actions () =
         pr "static int lvm_tokenize_%s (char *str, guestfs_int_lvm_%s *r)\n" typ typ;
         pr "{\n";
         pr "  char *tok, *p, *next;\n";
-        pr "  int i, j;\n";
+        pr "  size_t i, j;\n";
         pr "\n";
         (*
           pr "  fprintf (stderr, \"%%s: <<%%s>>\\n\", __func__, str);\n";
@@ -5889,7 +7439,7 @@ and generate_daemon_actions () =
         pr "  ret->guestfs_int_lvm_%s_list_val = NULL;\n" typ;
         pr "\n";
         pr "  r = command (&out, &err,\n";
-        pr "          \"/sbin/lvm\", \"%ss\",\n" typ;
+        pr "          \"lvm\", \"%ss\",\n" typ;
         pr "          \"-o\", lvm_%s_cols, \"--unbuffered\", \"--noheadings\",\n" typ;
         pr "          \"--nosuffix\", \"--separator\", \",\", \"--units\", \"b\", NULL);\n";
         pr "  if (r == -1) {\n";
@@ -5956,7 +7506,7 @@ and generate_daemon_actions () =
 
 (* Generate a list of function names, for debugging in the daemon.. *)
 and generate_daemon_names () =
-  generate_header CStyle GPLv2;
+  generate_header CStyle GPLv2plus;
 
   pr "#include <config.h>\n";
   pr "\n";
@@ -5974,7 +7524,7 @@ and generate_daemon_names () =
  * guestfs_available.
  *)
 and generate_daemon_optgroups_c () =
-  generate_header CStyle GPLv2;
+  generate_header CStyle GPLv2plus;
 
   pr "#include <config.h>\n";
   pr "\n";
@@ -5991,7 +7541,7 @@ and generate_daemon_optgroups_c () =
   pr "};\n"
 
 and generate_daemon_optgroups_h () =
-  generate_header CStyle GPLv2;
+  generate_header CStyle GPLv2plus;
 
   List.iter (
     fun (group, _) ->
@@ -6000,7 +7550,7 @@ and generate_daemon_optgroups_h () =
 
 (* Generate the tests. *)
 and generate_tests () =
-  generate_header CStyle GPLv2;
+  generate_header CStyle GPLv2plus;
 
   pr "\
 #include <stdio.h>
@@ -6025,7 +7575,7 @@ static void print_error (guestfs_h *g, void *data, const char *msg)
 /* FIXME: nearly identical code appears in fish.c */
 static void print_strings (char *const *argv)
 {
-  int argc;
+  size_t argc;
 
   for (argc = 0; argv[argc] != NULL; ++argc)
     printf (\"\\t%%s\\n\", argv[argc]);
@@ -6034,13 +7584,33 @@ static void print_strings (char *const *argv)
 /*
 static void print_table (char const *const *argv)
 {
-  int i;
+  size_t i;
 
   for (i = 0; argv[i] != NULL; i += 2)
     printf (\"%%s: %%s\\n\", argv[i], argv[i+1]);
 }
 */
 
+static int
+is_available (const char *group)
+{
+  const char *groups[] = { group, NULL };
+  int r;
+
+  suppress_error = 1;
+  r = guestfs_available (g, (char **) groups);
+  suppress_error = 0;
+
+  return r == 0;
+}
+
+static void
+incr (guestfs_h *g, void *iv)
+{
+  int *i = (int *) iv;
+  (*i)++;
+}
+
 ";
 
   (* Generate a list of commands which are not tested anywhere. *)
@@ -6052,7 +7622,7 @@ static void print_table (char const *const *argv)
     fun (_, _, _, _, tests, _, _) ->
       let tests = filter_map (
         function
-        | (_, (Always|If _|Unless _), test) -> Some test
+        | (_, (Always|If _|Unless _|IfAvailable _), test) -> Some test
         | (_, Disabled, _) -> None
       ) tests in
       let seq = List.concat (List.map seq_of_test tests) in
@@ -6194,14 +7764,14 @@ int main (int argc, char *argv[])
     exit (EXIT_FAILURE);
   }
 
+  /* Set a timeout in case qemu hangs during launch (RHBZ#505329). */
+  alarm (600);
+
   if (guestfs_launch (g) == -1) {
     printf (\"guestfs_launch FAILED\\n\");
     exit (EXIT_FAILURE);
   }
 
-  /* Set a timeout in case qemu hangs during launch (RHBZ#505329). */
-  alarm (600);
-
   /* Cancel previous alarm. */
   alarm (0);
 
@@ -6212,6 +7782,8 @@ int main (int argc, char *argv[])
   iteri (
     fun i test_name ->
       pr "  test_num++;\n";
+      pr "  if (guestfs_get_verbose (g))\n";
+      pr "    printf (\"-------------------------------------------------------------------------------\\n\");\n";
       pr "  printf (\"%%3d/%%3d %s\\n\", test_num, nr_tests);\n" test_name;
       pr "  if (%s () == -1) {\n" test_name;
       pr "    printf (\"%s FAILED\\n\");\n" test_name;
@@ -6220,11 +7792,22 @@ int main (int argc, char *argv[])
   ) test_names;
   pr "\n";
 
-  pr "  guestfs_close (g);\n";
-  pr "  unlink (\"test1.img\");\n";
-  pr "  unlink (\"test2.img\");\n";
-  pr "  unlink (\"test3.img\");\n";
-  pr "\n";
+  pr "  /* Check close callback is called. */
+  int close_sentinel = 1;
+  guestfs_set_close_callback (g, incr, &close_sentinel);
+
+  guestfs_close (g);
+
+  if (close_sentinel != 2) {
+    fprintf (stderr, \"close callback was not called\\n\");
+    exit (EXIT_FAILURE);
+  }
+
+  unlink (\"test1.img\");
+  unlink (\"test2.img\");
+  unlink (\"test3.img\");
+
+";
 
   pr "  if (n_failed > 0) {\n";
   pr "    printf (\"***** %%lu / %%d tests FAILED *****\\n\", n_failed, nr_tests);\n";
@@ -6256,7 +7839,7 @@ static int %s_skip (void)
 " test_name name (String.uppercase test_name) (String.uppercase name);
 
   (match prereq with
-   | Disabled | Always -> ()
+   | Disabled | Always | IfAvailable _ -> ()
    | If code | Unless code ->
        pr "static int %s_prereq (void)\n" test_name;
        pr "{\n";
@@ -6281,16 +7864,9 @@ static int %s (void)
   List.iter (
     function
     | Optional group ->
-        pr "  {\n";
-        pr "    const char *groups[] = { \"%s\", NULL };\n" group;
-        pr "    int r;\n";
-        pr "    suppress_error = 1;\n";
-        pr "    r = guestfs_available (g, (char **) groups);\n";
-        pr "    suppress_error = 0;\n";
-        pr "    if (r == -1) {\n";
-        pr "      printf (\"        %%s skipped (reason: group %%s not available in daemon)\\n\", \"%s\", groups[0]);\n" test_name;
-        pr "      return 0;\n";
-        pr "    }\n";
+        pr "  if (!is_available (\"%s\")) {\n" group;
+        pr "    printf (\"        %%s skipped (reason: group %%s not available in daemon)\\n\", \"%s\", \"%s\");\n" test_name group;
+        pr "    return 0;\n";
         pr "  }\n";
     | _ -> ()
   ) flags;
@@ -6312,6 +7888,13 @@ static int %s (void)
        pr "  }\n";
        pr "\n";
        generate_one_test_body name i test_name init test;
+   | IfAvailable group ->
+       pr "  if (!is_available (\"%s\")) {\n" group;
+       pr "    printf (\"        %%s skipped (reason: %%s not available)\\n\", \"%s\", \"%s\");\n" test_name group;
+       pr "    return 0;\n";
+       pr "  }\n";
+       pr "\n";
+       generate_one_test_body name i test_name init test;
    | Always ->
        generate_one_test_body name i test_name init test
   );
@@ -6349,7 +7932,7 @@ and generate_one_test_body name i test_name init test =
           ["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;
@@ -6362,7 +7945,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"];
-          ["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)
@@ -6622,12 +8205,18 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
         | Device n, arg
         | Dev_or_Path n, arg
         | String n, arg
-        | OptString n, arg ->
+        | OptString n, arg
+        | Key n, arg ->
+            pr "    const char *%s = \"%s\";\n" n (c_quote arg);
+        | BufferIn n, arg ->
             pr "    const char *%s = \"%s\";\n" n (c_quote arg);
+            pr "    size_t %s_size = %d;\n" n (String.length arg)
         | Int _, _
         | Int64 _, _
         | Bool _, _
         | FileIn _, _ | FileOut _, _ -> ()
+        | StringList n, "" | DeviceList n, "" ->
+            pr "    const char *const %s[1] = { NULL };\n" n
         | StringList n, arg | DeviceList n, arg ->
             let strs = string_split " " arg in
             iteri (
@@ -6651,7 +8240,7 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
         | RString _ -> pr "    char *r;\n"; "NULL"
         | RStringList _ | RHashtable _ ->
             pr "    char **r;\n";
-            pr "    int i;\n";
+            pr "    size_t i;\n";
             "NULL"
         | RStruct (_, typ) ->
             pr "    struct guestfs_%s *r;\n" typ; "NULL"
@@ -6672,8 +8261,11 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
         | Pathname n, _
         | Device n, _ | Dev_or_Path n, _
         | String n, _
-        | OptString n, _ ->
+        | OptString n, _
+        | Key n, _ ->
             pr ", %s" n
+        | BufferIn n, _ ->
+            pr ", %s, %s_size" n n
         | FileIn _, arg | FileOut _, arg ->
             pr ", \"%s\"" (c_quote arg)
         | StringList n, _ | DeviceList n, _ ->
@@ -6738,7 +8330,7 @@ and c_quote str =
 
 (* Generate a lot of different functions for guestfish. *)
 and generate_fish_cmds () =
-  generate_header CStyle GPLv2;
+  generate_header CStyle GPLv2plus;
 
   let all_functions =
     List.filter (
@@ -6749,6 +8341,8 @@ and generate_fish_cmds () =
       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";
@@ -6756,8 +8350,13 @@ and generate_fish_cmds () =
   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 "/* Valid suffixes allowed for numbers.  See Gnulib xstrtol function. */\n";
+  pr "static const char *xstrtol_suffixes = \"0kKMGTPEZY\";\n";
+  pr "\n";
 
   (* list_commands function, which implements guestfish -h *)
   pr "void list_commands (void)\n";
@@ -6776,7 +8375,7 @@ and generate_fish_cmds () =
   pr "\n";
 
   (* display_command function, which implements guestfish -h cmd *)
-  pr "void display_command (const char *cmd)\n";
+  pr "int display_command (const char *cmd)\n";
   pr "{\n";
   List.iter (
     fun (name, style, _, flags, _, shortdesc, longdesc) ->
@@ -6789,14 +8388,22 @@ and generate_fish_cmds () =
         match snd style with
         | [] -> name2
         | args ->
+            let args = List.filter (function Key _ -> false | _ -> true) args in
             sprintf "%s %s"
               name2 (String.concat " " (List.map name_of_argt args)) in
 
       let warnings =
-        if List.mem ProtocolLimitWarning flags then
-          ("\n\n" ^ protocol_limit_warning)
+        if List.exists (function Key _ -> true | _ -> false) (snd style) then
+          "\n\nThis command has one or more key or passphrase parameters.
+Guestfish will prompt for these separately."
         else "" in
 
+      let warnings =
+        warnings ^
+          if List.mem ProtocolLimitWarning flags then
+            ("\n\n" ^ protocol_limit_warning)
+          else "" in
+
       (* For DangerWillRobinson commands, we should probably have
        * guestfish prompt before allowing you to use them (especially
        * in interactive mode). XXX
@@ -6824,15 +8431,17 @@ and generate_fish_cmds () =
         pr " || STRCASEEQ (cmd, \"%s\")" name2;
       if name <> alias then
         pr " || STRCASEEQ (cmd, \"%s\")" alias;
-      pr ")\n";
+      pr ") {\n";
       pr "    pod2text (\"%s\", _(\"%s\"), %S);\n"
         name2 shortdesc
         ("=head1 SYNOPSIS\n\n " ^ synopsis ^ "\n\n" ^
          "=head1 DESCRIPTION\n\n" ^
          longdesc ^ warnings ^ describe_alias);
+      pr "    return 0;\n";
+      pr "  }\n";
       pr "  else\n"
   ) all_functions;
-  pr "    display_builtin_command (cmd);\n";
+  pr "    return display_builtin_command (cmd);\n";
   pr "}\n";
   pr "\n";
 
@@ -6948,11 +8557,15 @@ and generate_fish_cmds () =
         function
         | Device n
         | String n
-        | OptString n
-        | FileIn n
-        | FileOut n -> pr "  const char *%s;\n" n
+        | OptString n -> pr "  const char *%s;\n" n
         | Pathname n
-        | Dev_or_Path n -> pr "  char *%s;\n" n
+        | Dev_or_Path n
+        | FileIn n
+        | FileOut n
+        | Key n -> pr "  char *%s;\n" n
+        | BufferIn n ->
+            pr "  const char *%s;\n" n;
+            pr "  size_t %s_size;\n" n
         | StringList n | DeviceList n -> pr "  char **%s;\n" n
         | Bool n -> pr "  int %s;\n" n
         | Int n -> pr "  int %s;\n" n
@@ -6960,72 +8573,142 @@ and generate_fish_cmds () =
       ) (snd style);
 
       (* Check and convert parameters. *)
-      let argc_expected = List.length (snd style) in
+      let argc_expected =
+        let args_no_keys =
+          List.filter (function Key _ -> false | _ -> true) (snd style) in
+        List.length args_no_keys in
       pr "  if (argc != %d) {\n" argc_expected;
       pr "    fprintf (stderr, _(\"%%s should have %%d parameter(s)\\n\"), cmd, %d);\n"
         argc_expected;
       pr "    fprintf (stderr, _(\"type 'help %%s' for help on %%s\\n\"), cmd, cmd);\n";
       pr "    return -1;\n";
       pr "  }\n";
-      iteri (
-        fun i ->
-          function
-          | Device name
-          | String name ->
-              pr "  %s = argv[%d];\n" name i
-          | Pathname name
-          | Dev_or_Path name ->
-              pr "  %s = resolve_win_path (argv[%d]);\n" name i;
-              pr "  if (%s == NULL) return -1;\n" name
-          | OptString name ->
-              pr "  %s = STRNEQ (argv[%d], \"\") ? argv[%d] : NULL;\n"
-                name i i
-          | FileIn name ->
-              pr "  %s = STRNEQ (argv[%d], \"-\") ? argv[%d] : \"/dev/stdin\";\n"
-                name i i
-          | FileOut name ->
-              pr "  %s = STRNEQ (argv[%d], \"-\") ? argv[%d] : \"/dev/stdout\";\n"
-                name i i
-          | StringList name | DeviceList name ->
-              pr "  %s = parse_string_list (argv[%d]);\n" name i;
-              pr "  if (%s == NULL) return -1;\n" name;
-          | Bool name ->
-              pr "  %s = is_true (argv[%d]) ? 1 : 0;\n" name i
-          | Int name ->
-              pr "  %s = atoi (argv[%d]);\n" name i
-          | Int64 name ->
-              pr "  %s = atoll (argv[%d]);\n" name i
+
+      let parse_integer fn fntyp rtyp range name =
+        pr "  {\n";
+        pr "    strtol_error xerr;\n";
+        pr "    %s r;\n" fntyp;
+        pr "\n";
+        pr "    xerr = %s (argv[i++], NULL, 0, &r, xstrtol_suffixes);\n" fn;
+        pr "    if (xerr != LONGINT_OK) {\n";
+        pr "      fprintf (stderr,\n";
+        pr "               _(\"%%s: %%s: invalid integer parameter (%%s returned %%d)\\n\"),\n";
+        pr "               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
+
+      if snd style <> [] then
+        pr "  size_t i = 0;\n";
+
+      List.iter (
+        function
+        | Device name
+        | String name ->
+            pr "  %s = argv[i++];\n" name
+        | Pathname name
+        | Dev_or_Path name ->
+            pr "  %s = resolve_win_path (argv[i++]);\n" name;
+            pr "  if (%s == NULL) return -1;\n" name
+        | OptString name ->
+            pr "  %s = STRNEQ (argv[i], \"\") ? argv[i] : NULL;\n" name;
+            pr "  i++;\n"
+        | BufferIn name ->
+            pr "  %s = argv[i];\n" name;
+            pr "  %s_size = strlen (argv[i]);\n" name;
+            pr "  i++;\n"
+        | FileIn name ->
+            pr "  %s = file_in (argv[i++]);\n" name;
+            pr "  if (%s == NULL) return -1;\n" name
+        | FileOut name ->
+            pr "  %s = file_out (argv[i++]);\n" name;
+            pr "  if (%s == NULL) return -1;\n" name
+        | StringList name | DeviceList name ->
+            pr "  %s = parse_string_list (argv[i++]);\n" name;
+            pr "  if (%s == NULL) return -1;\n" name
+        | Key name ->
+            pr "  %s = read_key (\"%s\");\n" name name;
+            pr "  if (%s == NULL) return -1;\n" name
+        | Bool name ->
+            pr "  %s = is_true (argv[i++]) ? 1 : 0;\n" name
+        | Int name ->
+            let range =
+              let min = "(-(2LL<<30))"
+              and max = "((2LL<<30)-1)"
+              and comment =
+                "The Int type in the generator is a signed 31 bit int." in
+              Some (min, max, comment) in
+            parse_integer "xstrtoll" "long long" "int" range name
+        | Int64 name ->
+            parse_integer "xstrtoll" "long long" "int64_t" None name
       ) (snd style);
 
       (* Call C API function. *)
-      let fn =
-        try find_map (function FishAction n -> Some n | _ -> None) flags
-        with Not_found -> sprintf "guestfs_%s" name in
-      pr "  r = %s " fn;
+      pr "  r = guestfs_%s " name;
       generate_c_call_args ~handle:"g" style;
       pr ";\n";
 
       List.iter (
         function
-        | Device name | String name
-        | OptString name | FileIn name | FileOut name | Bool name
-        | Int name | Int64 name -> ()
-        | Pathname name | Dev_or_Path name ->
+        | Device _ | String _
+        | OptString _ | Bool _
+        | Int _ | Int64 _
+        | BufferIn _ -> ()
+        | Pathname name | Dev_or_Path name | FileOut name
+        | Key name ->
             pr "  free (%s);\n" name
+        | FileIn name ->
+            pr "  free_file_in (%s);\n" name
         | StringList name | DeviceList name ->
             pr "  free_strings (%s);\n" name
       ) (snd style);
 
+      (* Any output flags? *)
+      let fish_output =
+        let flags = filter_map (
+          function FishOutput flag -> Some flag | _ -> None
+        ) flags in
+        match flags with
+        | [] -> None
+        | [f] -> Some f
+        | _ ->
+            failwithf "%s: more than one FishOutput flag is not allowed" name in
+
       (* Check return value for errors and display command results. *)
       (match fst style with
        | RErr -> pr "  return r;\n"
        | RInt _ ->
            pr "  if (r == -1) return -1;\n";
-           pr "  printf (\"%%d\\n\", r);\n";
+           (match fish_output with
+            | None ->
+                pr "  printf (\"%%d\\n\", r);\n";
+            | Some FishOutputOctal ->
+                pr "  printf (\"%%s%%o\\n\", r != 0 ? \"0\" : \"\", r);\n";
+            | Some FishOutputHexadecimal ->
+                pr "  printf (\"%%s%%x\\n\", r != 0 ? \"0x\" : \"\", r);\n");
            pr "  return 0;\n"
        | RInt64 _ ->
            pr "  if (r == -1) return -1;\n";
-           pr "  printf (\"%%\" PRIi64 \"\\n\", r);\n";
+           (match fish_output with
+            | None ->
+                pr "  printf (\"%%\" PRIi64 \"\\n\", r);\n";
+            | Some FishOutputOctal ->
+                pr "  printf (\"%%s%%\" PRIo64 \"\\n\", r != 0 ? \"0\" : \"\", r);\n";
+            | Some FishOutputHexadecimal ->
+                pr "  printf (\"%%s%%\" PRIx64 \"\\n\", r != 0 ? \"0x\" : \"\", r);\n");
            pr "  return 0;\n"
        | RBool _ ->
            pr "  if (r == -1) return -1;\n";
@@ -7065,7 +8748,11 @@ and generate_fish_cmds () =
            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"
       );
@@ -7094,6 +8781,8 @@ and generate_fish_cmds () =
   ) all_functions;
   pr "    {\n";
   pr "      fprintf (stderr, _(\"%%s: unknown command\\n\"), cmd);\n";
+  pr "      if (command_num == 1)\n";
+  pr "        extended_help_message ();\n";
   pr "      return -1;\n";
   pr "    }\n";
   pr "  return 0;\n";
@@ -7102,7 +8791,7 @@ and generate_fish_cmds () =
 
 (* Readline completion for guestfish. *)
 and generate_fish_completion () =
-  generate_header CStyle GPLv2;
+  generate_header CStyle GPLv2plus;
 
   let all_functions =
     List.filter (
@@ -7151,7 +8840,7 @@ static const char *const commands[] = {
 static char *
 generator (const char *text, int state)
 {
-  static int index, len;
+  static size_t index, len;
   const char *name;
 
   if (!state) {
@@ -7172,7 +8861,16 @@ generator (const char *text, int state)
 
 #endif /* HAVE_LIBREADLINE */
 
-char **do_completion (const char *text, int start, int end)
+#ifdef HAVE_RL_COMPLETION_MATCHES
+#define RL_COMPLETION_MATCHES rl_completion_matches
+#else
+#ifdef HAVE_COMPLETION_MATCHES
+#define RL_COMPLETION_MATCHES completion_matches
+#endif
+#endif /* else just fail if we don't have either symbol */
+
+char **
+do_completion (const char *text, int start, int end)
 {
   char **matches = NULL;
 
@@ -7180,9 +8878,9 @@ char **do_completion (const char *text, int start, int end)
   rl_completion_append_character = ' ';
 
   if (start == 0)
-    matches = rl_completion_matches (text, generator);
+    matches = RL_COMPLETION_MATCHES (text, generator);
   else if (complete_dest_paths)
-    matches = rl_completion_matches (text, complete_dest_paths_generator);
+    matches = RL_COMPLETION_MATCHES (text, complete_dest_paths_generator);
 #endif
 
   return matches;
@@ -7223,13 +8921,16 @@ and generate_fish_actions_pod () =
       pr " %s" name;
       List.iter (
         function
-        | Pathname n | Device n | Dev_or_Path n | String n -> pr " %s" n
+        | Pathname n | Device n | Dev_or_Path n | String n ->
+            pr " %s" n
         | OptString n -> pr " %s" n
         | StringList n | DeviceList n -> pr " '%s ...'" n
         | Bool _ -> pr " true|false"
         | Int n -> pr " %s" n
         | Int64 n -> pr " %s" n
         | FileIn n | FileOut n -> pr " (%s|-)" n
+        | BufferIn n -> pr " %s" n
+        | Key _ -> () (* keys are entered at a prompt *)
       ) (snd style);
       pr "\n";
       pr "\n";
@@ -7239,6 +8940,10 @@ and generate_fish_actions_pod () =
                       | _ -> false) (snd style) then
         pr "Use C<-> instead of a filename to read/write from stdin/stdout.\n\n";
 
+      if List.exists (function Key _ -> true | _ -> false) (snd style) then
+        pr "This command has one or more key or passphrase parameters.
+Guestfish will prompt for these separately.\n\n";
+
       if List.mem ProtocolLimitWarning flags then
         pr "%s\n\n" protocol_limit_warning;
 
@@ -7250,6 +8955,84 @@ and generate_fish_actions_pod () =
       | Some txt -> pr "%s\n\n" txt
   ) all_functions_sorted
 
+and generate_fish_prep_options_h () =
+  generate_header CStyle GPLv2plus;
+
+  pr "#ifndef PREPOPTS_H\n";
+  pr "\n";
+
+  pr "\
+struct prep {
+  const char *name;             /* eg. \"fs\" */
+
+  size_t nr_params;             /* optional parameters */
+  struct prep_param *params;
+
+  const char *shortdesc;        /* short description */
+  const char *longdesc;         /* long description */
+
+                                /* functions to implement it */
+  void (*prelaunch) (const char *filename, prep_data *);
+  void (*postlaunch) (const char *filename, prep_data *, const char *device);
+};
+
+struct prep_param {
+  const char *pname;            /* parameter name */
+  const char *pdefault;         /* parameter default */
+  const char *pdesc;            /* parameter description */
+};
+
+extern const struct prep preps[];
+#define NR_PREPS %d
+
+" (List.length prepopts);
+
+  List.iter (
+    fun (name, shortdesc, args, longdesc) ->
+      pr "\
+extern void prep_prelaunch_%s (const char *filename, prep_data *data);
+extern void prep_postlaunch_%s (const char *filename, prep_data *data, const char *device);
+
+" name name;
+  ) prepopts;
+
+  pr "\n";
+  pr "#endif /* PREPOPTS_H */\n"
+
+and generate_fish_prep_options_c () =
+  generate_header CStyle GPLv2plus;
+
+  pr "\
+#include \"fish.h\"
+#include \"prepopts.h\"
+
+";
+
+  List.iter (
+    fun (name, shortdesc, args, longdesc) ->
+      pr "static struct prep_param %s_args[] = {\n" name;
+      List.iter (
+        fun (n, default, desc) ->
+          pr "  { \"%s\", \"%s\", \"%s\" },\n" n default desc
+      ) args;
+      pr "};\n";
+      pr "\n";
+  ) prepopts;
+
+  pr "const struct prep preps[] = {\n";
+  List.iter (
+    fun (name, shortdesc, args, longdesc) ->
+      pr "  { \"%s\", %d, %s_args,
+    \"%s\",
+    \"%s\",
+    prep_prelaunch_%s, prep_postlaunch_%s },
+"
+        name (List.length args) name
+        (c_quote shortdesc) (c_quote longdesc)
+        name name;
+  ) prepopts;
+  pr "};\n"
+
 (* Generate a C function prototype. *)
 and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
     ?(single_line = false) ?(newline = false) ?(in_daemon = false)
@@ -7293,7 +9076,8 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
       | Pathname n
       | Device n | Dev_or_Path n
       | String n
-      | OptString n ->
+      | OptString n
+      | Key n ->
           next ();
           pr "const char *%s" n
       | StringList n | DeviceList n ->
@@ -7305,6 +9089,11 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
       | FileIn n
       | FileOut n ->
           if not in_daemon then (next (); pr "const char *%s" n)
+      | BufferIn n ->
+          next ();
+          pr "const char *%s" n;
+          next ();
+          pr "size_t %s_size" n
     ) (snd style);
     if is_RBufferOut then (next (); pr "size_t *size_r");
   );
@@ -7325,9 +9114,13 @@ and generate_c_call_args ?handle ?(decl = false) style =
    | Some handle -> pr "%s" handle; comma := true
   );
   List.iter (
-    fun arg ->
-      next ();
-      pr "%s" (name_of_argt arg)
+    function
+    | BufferIn n ->
+        next ();
+        pr "%s, %s_size" n n
+    | arg ->
+        next ();
+        pr "%s" (name_of_argt arg)
   ) (snd style);
   (* For RBufferOut calls, add implicit &size parameter. *)
   if not decl then (
@@ -7341,7 +9134,7 @@ and generate_c_call_args ?handle ?(decl = false) style =
 
 (* Generate the OCaml bindings interface. *)
 and generate_ocaml_mli () =
-  generate_header OCamlStyle LGPLv2;
+  generate_header OCamlStyle LGPLv2plus;
 
   pr "\
 (** For API documentation you should refer to the C API
@@ -7370,6 +9163,28 @@ val close : t -> unit
     unreferenced, but callers can call this in order to provide
     predictable cleanup. *)
 
+type progress_cb = int -> int -> int64 -> int64 -> unit
+
+val set_progress_callback : t -> progress_cb -> unit
+(** [set_progress_callback g f] sets [f] as the progress callback function.
+    For some long-running functions, [f] will be called repeatedly
+    during the function with progress updates.
+
+    The callback is [f proc_nr serial position total].  See
+    the description of [guestfs_set_progress_callback] in guestfs(3)
+    for the meaning of these four numbers.
+
+    Note that if the closure captures a reference to the handle,
+    this reference will prevent the handle from being
+    automatically closed by the garbage collector.  There are
+    three ways to avoid this: be careful not to capture the handle
+    in the closure, or use a weak reference, or call
+    {!Guestfs.clear_progress_callback} to remove the reference. *)
+
+val clear_progress_callback : t -> unit
+(** [clear_progress_callback g] removes any progress callback function
+    associated with the handle.  See {!Guestfs.set_progress_callback}. *)
+
 ";
   generate_ocaml_structure_decls ();
 
@@ -7383,7 +9198,7 @@ val close : t -> unit
 
 (* Generate the OCaml bindings implementation. *)
 and generate_ocaml_ml () =
-  generate_header OCamlStyle LGPLv2;
+  generate_header OCamlStyle LGPLv2plus;
 
   pr "\
 type t
@@ -7394,6 +9209,13 @@ exception Handle_closed of string
 external create : unit -> t = \"ocaml_guestfs_create\"
 external close : t -> unit = \"ocaml_guestfs_close\"
 
+type progress_cb = int -> int -> int64 -> int64 -> unit
+
+external set_progress_callback : t -> progress_cb -> unit
+  = \"ocaml_guestfs_set_progress_callback\"
+external clear_progress_callback : t -> unit
+  = \"ocaml_guestfs_clear_progress_callback\"
+
 (* Give the exceptions names, so they can be raised from the C code. *)
 let () =
   Callback.register_exception \"ocaml_guestfs_error\" (Error \"\");
@@ -7411,7 +9233,7 @@ let () =
 
 (* Generate the OCaml bindings C implementation. *)
 and generate_ocaml_c () =
-  generate_header CStyle LGPLv2;
+  generate_header CStyle LGPLv2plus;
 
   pr "\
 #include <stdio.h>
@@ -7426,7 +9248,7 @@ and generate_ocaml_c () =
 #include <caml/mlvalues.h>
 #include <caml/signals.h>
 
-#include <guestfs.h>
+#include \"guestfs.h\"
 
 #include \"guestfs_c.h\"
 
@@ -7439,7 +9261,7 @@ copy_table (char * const * argv)
 {
   CAMLparam0 ();
   CAMLlocal5 (rv, pairv, kv, vv, cons);
-  int i;
+  size_t i;
 
   rv = Val_int (0);
   for (i = 0; argv[i] != NULL; i += 2) {
@@ -7593,12 +9415,17 @@ copy_table (char * const * argv)
         | Device n | Dev_or_Path n
         | String n
         | FileIn n
-        | FileOut n ->
-            pr "  const char *%s = String_val (%sv);\n" n n
+        | FileOut n
+        | Key n ->
+            (* Copy strings in case the GC moves them: RHBZ#604691 *)
+            pr "  char *%s = guestfs_safe_strdup (g, String_val (%sv));\n" n n
         | OptString n ->
-            pr "  const char *%s =\n" n;
-            pr "    %sv != Val_int (0) ? String_val (Field (%sv, 0)) : NULL;\n"
-              n n
+            pr "  char *%s =\n" n;
+            pr "    %sv != Val_int (0) ?" n;
+            pr "      guestfs_safe_strdup (g, String_val (Field (%sv, 0))) : NULL;\n" n
+        | BufferIn n ->
+            pr "  size_t %s_size = caml_string_length (%sv);\n" n n;
+            pr "  char *%s = guestfs_safe_memdup (g, String_val (%sv), %s_size);\n" n n n
         | StringList n | DeviceList n ->
             pr "  char **%s = ocaml_guestfs_strings_val (g, %sv);\n" n n
         | Bool n ->
@@ -7618,7 +9445,7 @@ copy_table (char * const * argv)
             pr "  const char *r;\n"; "NULL"
         | RString _ -> pr "  char *r;\n"; "NULL"
         | RStringList _ ->
-            pr "  int i;\n";
+            pr "  size_t i;\n";
             pr "  char **r;\n";
             "NULL"
         | RStruct (_, typ) ->
@@ -7626,7 +9453,7 @@ copy_table (char * const * argv)
         | RStructList (_, typ) ->
             pr "  struct guestfs_%s_list *r;\n" typ; "NULL"
         | RHashtable _ ->
-            pr "  int i;\n";
+            pr "  size_t i;\n";
             pr "  char **r;\n";
             "NULL"
         | RBufferOut _ ->
@@ -7641,13 +9468,15 @@ copy_table (char * const * argv)
       pr ";\n";
       pr "  caml_leave_blocking_section ();\n";
 
+      (* Free strings if we copied them above. *)
       List.iter (
         function
+        | Pathname n | Device n | Dev_or_Path n | String n | OptString n
+        | FileIn n | FileOut n | BufferIn n | Key n ->
+            pr "  free (%s);\n" n
         | StringList n | DeviceList n ->
             pr "  ocaml_guestfs_free_strings (%s);\n" n;
-        | Pathname _ | Device _ | Dev_or_Path _ | String _ | OptString _
-        | Bool _ | Int _ | Int64 _
-        | FileIn _ | FileOut _ -> ()
+        | Bool _ | Int _ | Int64 _ -> ()
       ) (snd style);
 
       pr "  if (r == %s)\n" error_code;
@@ -7733,7 +9562,8 @@ and generate_ocaml_prototype ?(is_external = false) name style =
   pr "%s : t -> " name;
   List.iter (
     function
-    | Pathname _ | Device _ | Dev_or_Path _ | String _ | FileIn _ | FileOut _ -> pr "string -> "
+    | Pathname _ | Device _ | Dev_or_Path _ | String _ | FileIn _ | FileOut _
+    | BufferIn _ | Key _ -> pr "string -> "
     | OptString _ -> pr "string option -> "
     | StringList _ | DeviceList _ -> pr "string array -> "
     | Bool _ -> pr "bool -> "
@@ -7763,7 +9593,7 @@ and generate_ocaml_prototype ?(is_external = false) name style =
 
 (* Generate Perl xs code, a sort of crazy variation of C with macros. *)
 and generate_perl_xs () =
-  generate_header CStyle LGPLv2;
+  generate_header CStyle LGPLv2plus;
 
   pr "\
 #include \"EXTERN.h\"
@@ -7833,6 +9663,46 @@ XS_unpack_charPtrPtr (SV *arg) {
   return ret;
 }
 
+#define PROGRESS_KEY \"_perl_progress_cb\"
+
+static void
+_clear_progress_callback (guestfs_h *g)
+{
+  guestfs_set_progress_callback (g, NULL, NULL);
+  SV *cb = guestfs_get_private (g, PROGRESS_KEY);
+  if (cb) {
+    guestfs_set_private (g, PROGRESS_KEY, NULL);
+    SvREFCNT_dec (cb);
+  }
+}
+
+/* http://www.perlmonks.org/?node=338857 */
+static void
+_progress_callback (guestfs_h *g, void *cb,
+                    int proc_nr, int serial, uint64_t position, uint64_t total)
+{
+  dSP;
+  ENTER;
+  SAVETMPS;
+  PUSHMARK (SP);
+  XPUSHs (sv_2mortal (newSViv (proc_nr)));
+  XPUSHs (sv_2mortal (newSViv (serial)));
+  XPUSHs (sv_2mortal (my_newSVull (position)));
+  XPUSHs (sv_2mortal (my_newSVull (total)));
+  PUTBACK;
+  call_sv ((SV *) cb, G_VOID | G_DISCARD | G_EVAL);
+  FREETMPS;
+  LEAVE;
+}
+
+static void
+_close_handle (guestfs_h *g)
+{
+  assert (g != NULL);
+  _clear_progress_callback (g);
+  guestfs_close (g);
+}
+
 MODULE = Sys::Guestfs  PACKAGE = Sys::Guestfs
 
 PROTOTYPES: ENABLE
@@ -7848,10 +9718,45 @@ _create ()
       RETVAL
 
 void
-DESTROY (g)
+DESTROY (sv)
+      SV *sv;
+ PPCODE:
+      /* For the 'g' argument above we do the conversion explicitly and
+       * don't rely on the typemap, because if the handle has been
+       * explicitly closed we don't want the typemap conversion to
+       * display an error.
+       */
+      HV *hv = (HV *) SvRV (sv);
+      SV **svp = hv_fetch (hv, \"_g\", 2, 0);
+      if (svp != NULL) {
+        guestfs_h *g = (guestfs_h *) SvIV (*svp);
+        _close_handle (g);
+      }
+
+void
+close (g)
+      guestfs_h *g;
+ PPCODE:
+      _close_handle (g);
+      /* Avoid double-free in DESTROY method. */
+      HV *hv = (HV *) SvRV (ST(0));
+      (void) hv_delete (hv, \"_g\", 2, G_DISCARD);
+
+void
+set_progress_callback (g, cb)
+      guestfs_h *g;
+      SV *cb;
+ PPCODE:
+      _clear_progress_callback (g);
+      SvREFCNT_inc (cb);
+      guestfs_set_private (g, PROGRESS_KEY, cb);
+      guestfs_set_progress_callback (g, _progress_callback, cb);
+
+void
+clear_progress_callback (g)
       guestfs_h *g;
  PPCODE:
-      guestfs_close (g);
+      _clear_progress_callback (g);
 
 ";
 
@@ -7872,15 +9777,21 @@ DESTROY (g)
            pr "void\n" (* all lists returned implictly on the stack *)
       );
       (* Call and arguments. *)
-      pr "%s " name;
-      generate_c_call_args ~handle:"g" ~decl:true style;
-      pr "\n";
+      pr "%s (g" name;
+      List.iter (
+        fun arg -> pr ", %s" (name_of_argt arg)
+      ) (snd style);
+      pr ")\n";
       pr "      guestfs_h *g;\n";
       iteri (
         fun i ->
           function
-          | Pathname n | Device n | Dev_or_Path n | String n | FileIn n | FileOut n ->
+          | Pathname n | Device n | Dev_or_Path n | String n
+          | FileIn n | FileOut n | Key n ->
               pr "      char *%s;\n" n
+          | BufferIn n ->
+              pr "      char *%s;\n" n;
+              pr "      size_t %s_size = SvCUR (ST(%d));\n" n (i+1)
           | OptString n ->
               (* http://www.perlmonks.org/?node_id=554277
                * Note that the implicit handle argument means we have
@@ -7898,7 +9809,8 @@ DESTROY (g)
           function
           | Pathname _ | Device _ | Dev_or_Path _ | String _ | OptString _
           | Bool _ | Int _ | Int64 _
-          | FileIn _ | FileOut _ -> ()
+          | FileIn _ | FileOut _
+          | BufferIn _ | Key _ -> ()
           | StringList n | DeviceList n -> pr "      free (%s);\n" n
         ) (snd style)
       in
@@ -7914,7 +9826,7 @@ DESTROY (g)
            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";
@@ -7925,7 +9837,7 @@ DESTROY (g)
            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"
@@ -7938,7 +9850,7 @@ DESTROY (g)
            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"
@@ -7951,7 +9863,7 @@ DESTROY (g)
            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"
@@ -7978,7 +9890,7 @@ DESTROY (g)
            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";
@@ -7986,14 +9898,14 @@ DESTROY (g)
        | RStringList n | RHashtable n ->
            pr "PREINIT:\n";
            pr "      char **%s;\n" n;
-           pr "      int i, n;\n";
+           pr "      size_t i, n;\n";
            pr " PPCODE:\n";
            pr "      %s = guestfs_%s " n name;
            generate_c_call_args ~handle:"g" style;
            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";
@@ -8017,499 +9929,1049 @@ DESTROY (g)
            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 "\n"
-  ) all_functions
+      pr "\n"
+  ) all_functions
+
+and generate_perl_struct_list_code typ cols name style n do_cleanups =
+  pr "PREINIT:\n";
+  pr "      struct guestfs_%s_list *%s;\n" typ n;
+  pr "      size_t i;\n";
+  pr "      HV *hv;\n";
+  pr " PPCODE:\n";
+  pr "      %s = guestfs_%s " n name;
+  generate_c_call_args ~handle:"g" style;
+  pr ";\n";
+  do_cleanups ();
+  pr "      if (%s == NULL)\n" n;
+  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";
+  List.iter (
+    function
+    | name, FString ->
+        pr "        (void) hv_store (hv, \"%s\", %d, newSVpv (%s->val[i].%s, 0), 0);\n"
+          name (String.length name) n name
+    | name, FUUID ->
+        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, 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
+    | name, FInt64 ->
+        pr "        (void) hv_store (hv, \"%s\", %d, my_newSVll (%s->val[i].%s), 0);\n"
+          name (String.length name) n name
+    | name, (FInt32|FUInt32) ->
+        pr "        (void) hv_store (hv, \"%s\", %d, newSVnv (%s->val[i].%s), 0);\n"
+          name (String.length name) n name
+    | name, FChar ->
+        pr "        (void) hv_store (hv, \"%s\", %d, newSVpv (&%s->val[i].%s, 1), 0);\n"
+          name (String.length name) n name
+    | name, FOptPercent ->
+        pr "        (void) hv_store (hv, \"%s\", %d, newSVnv (%s->val[i].%s), 0);\n"
+          name (String.length name) n name
+  ) cols;
+  pr "        PUSHs (sv_2mortal (newRV ((SV *) hv)));\n";
+  pr "      }\n";
+  pr "      guestfs_free_%s_list (%s);\n" typ n
+
+and generate_perl_struct_code typ cols name style n do_cleanups =
+  pr "PREINIT:\n";
+  pr "      struct guestfs_%s *%s;\n" typ n;
+  pr " PPCODE:\n";
+  pr "      %s = guestfs_%s " n name;
+  generate_c_call_args ~handle:"g" style;
+  pr ";\n";
+  do_cleanups ();
+  pr "      if (%s == NULL)\n" n;
+  pr "        croak (\"%%s\", guestfs_last_error (g));\n";
+  pr "      EXTEND (SP, 2 * %d);\n" (List.length cols);
+  List.iter (
+    fun ((name, _) as col) ->
+      pr "      PUSHs (sv_2mortal (newSVpv (\"%s\", 0)));\n" name;
+
+      match col with
+      | name, FString ->
+          pr "      PUSHs (sv_2mortal (newSVpv (%s->%s, 0)));\n"
+            n name
+      | name, FBuffer ->
+          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
+      | name, (FBytes|FUInt64) ->
+          pr "      PUSHs (sv_2mortal (my_newSVull (%s->%s)));\n"
+            n name
+      | name, FInt64 ->
+          pr "      PUSHs (sv_2mortal (my_newSVll (%s->%s)));\n"
+            n name
+      | name, (FInt32|FUInt32) ->
+          pr "      PUSHs (sv_2mortal (newSVnv (%s->%s)));\n"
+            n name
+      | name, FChar ->
+          pr "      PUSHs (sv_2mortal (newSVpv (&%s->%s, 1)));\n"
+            n name
+      | name, FOptPercent ->
+          pr "      PUSHs (sv_2mortal (newSVnv (%s->%s)));\n"
+            n name
+  ) cols;
+  pr "      free (%s);\n" n
+
+(* Generate Sys/Guestfs.pm. *)
+and generate_perl_pm () =
+  generate_header HashStyle LGPLv2plus;
+
+  pr "\
+=pod
+
+=head1 NAME
+
+Sys::Guestfs - Perl bindings for libguestfs
+
+=head1 SYNOPSIS
+
+ use Sys::Guestfs;
+
+ my $h = Sys::Guestfs->new ();
+ $h->add_drive ('guest.img');
+ $h->launch ();
+ $h->mount ('/dev/sda1', '/');
+ $h->touch ('/hello');
+ $h->sync ();
+
+=head1 DESCRIPTION
+
+The C<Sys::Guestfs> module provides a Perl XS binding to the
+libguestfs API for examining and modifying virtual machine
+disk images.
+
+Amongst the things this is good for: making batch configuration
+changes to guests, getting disk used/free statistics (see also:
+virt-df), migrating between virtualization systems (see also:
+virt-p2v), performing partial backups, performing partial guest
+clones, cloning guests and changing registry/UUID/hostname info, and
+much else besides.
+
+Libguestfs uses Linux kernel and qemu code, and can access any type of
+guest filesystem that Linux and qemu can, including but not limited
+to: ext2/3/4, btrfs, FAT and NTFS, LVM, many different disk partition
+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
+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
+with libvirt.
+
+=head1 ERRORS
+
+All errors turn into calls to C<croak> (see L<Carp(3)>).
+
+=head1 METHODS
+
+=over 4
+
+=cut
+
+package Sys::Guestfs;
+
+use strict;
+use warnings;
+
+# This version number changes whenever a new function
+# is added to the libguestfs API.  It is not directly
+# related to the libguestfs version number.
+use vars qw($VERSION);
+$VERSION = '0.%d';
+
+require XSLoader;
+XSLoader::load ('Sys::Guestfs');
+
+=item $h = Sys::Guestfs->new ();
+
+Create a new guestfs handle.
+
+=cut
+
+sub new {
+  my $proto = shift;
+  my $class = ref ($proto) || $proto;
+
+  my $g = Sys::Guestfs::_create ();
+  my $self = { _g => $g };
+  bless $self, $class;
+  return $self;
+}
+
+=item $h->close ();
+
+Explicitly close the guestfs handle.
+
+B<Note:> You should not usually call this function.  The handle will
+be closed implicitly when its reference count goes to zero (eg.
+when it goes out of scope or the program ends).  This call is
+only required in some exceptional cases, such as where the program
+may contain cached references to the handle 'somewhere' and you
+really have to have the close happen right away.  After calling
+C<close> the program must not call any method (including C<close>)
+on the handle (but the implicit call to C<DESTROY> that happens
+when the final reference is cleaned up is OK).
+
+=item $h->set_progress_callback (\\&cb);
+
+Set the progress notification callback for this handle
+to the Perl closure C<cb>.
+
+C<cb> will be called whenever a long-running operation
+generates a progress notification message.  The 4 parameters
+to the function are: C<proc_nr>, C<serial>, C<position>
+and C<total>.
+
+You should carefully read the documentation for
+L<guestfs(3)/guestfs_set_progress_callback> before using
+this function.
+
+=item $h->clear_progress_callback ();
+
+This removes any progress callback function associated with
+the handle.
+
+=cut
+
+" max_proc_nr;
+
+  (* Actions.  We only need to print documentation for these as
+   * they are pulled in from the XS code automatically.
+   *)
+  List.iter (
+    fun (name, style, _, flags, _, _, longdesc) ->
+      if not (List.mem NotInDocs flags) then (
+        let longdesc = replace_str longdesc "C<guestfs_" "C<$h-E<gt>" in
+        pr "=item ";
+        generate_perl_prototype name style;
+        pr "\n\n";
+        pr "%s\n\n" longdesc;
+        if List.mem ProtocolLimitWarning flags then
+          pr "%s\n\n" protocol_limit_warning;
+        if List.mem DangerWillRobinson flags then
+          pr "%s\n\n" danger_will_robinson;
+        match deprecation_notice flags with
+        | None -> ()
+        | Some txt -> pr "%s\n\n" txt
+      )
+  ) all_functions_sorted;
+
+  (* End of file. *)
+  pr "\
+=cut
+
+1;
+
+=back
+
+=head1 AVAILABILITY
+
+From time to time we add new libguestfs APIs.  Also some libguestfs
+APIs won't be available in all builds of libguestfs (the Fedora
+build is full-featured, but other builds may disable features).
+How do you test whether the APIs that your Perl program needs are
+available in the version of C<Sys::Guestfs> that you are using?
+
+To test if a particular function is available in the C<Sys::Guestfs>
+class, use the ordinary Perl UNIVERSAL method C<can(METHOD)>
+(see L<perlobj(1)>).  For example:
+
+ use Sys::Guestfs;
+ if (defined (Sys::Guestfs->can (\"set_verbose\"))) {
+   print \"\\$h->set_verbose is available\\n\";
+ }
+
+To test if particular features are supported by the current
+build, use the L</available> method like the example below.  Note
+that the appliance must be launched first.
+
+ $h->available ( [\"augeas\"] );
+
+Since the L</available> method croaks if the feature is not supported,
+you might also want to wrap this in an eval and return a boolean.
+In fact this has already been done for you: use
+L<Sys::Guestfs::Lib(3)/feature_available>.
+
+For further discussion on this topic, refer to
+L<guestfs(3)/AVAILABILITY>.
+
+=head1 STORING DATA IN THE HANDLE
+
+The handle returned from L</new> is a hash reference.  The hash
+normally contains a single element:
+
+ {
+   _g => [private data used by libguestfs]
+ }
+
+Callers can add other elements to this hash to store data for their own
+purposes.  The data lasts for the lifetime of the handle.
+
+Any fields whose names begin with an underscore are reserved
+for private use by libguestfs.  We may add more in future.
+
+It is recommended that callers prefix the name of their field(s)
+with some unique string, to avoid conflicts with other users.
+
+=head1 COPYRIGHT
+
+Copyright (C) %s Red Hat Inc.
+
+=head1 LICENSE
+
+Please see the file COPYING.LIB for the full license.
+
+=head1 SEE ALSO
+
+L<guestfs(3)>,
+L<guestfish(1)>,
+L<http://libguestfs.org>,
+L<Sys::Guestfs::Lib(3)>.
+
+=cut
+" copyright_years
+
+and generate_perl_prototype name style =
+  (match fst style with
+   | RErr -> ()
+   | RBool n
+   | RInt n
+   | RInt64 n
+   | RConstString n
+   | RConstOptString n
+   | RString n
+   | RBufferOut n -> pr "$%s = " n
+   | RStruct (n,_)
+   | RHashtable n -> pr "%%%s = " n
+   | RStringList n
+   | RStructList (n,_) -> pr "@%s = " n
+  );
+  pr "$h->%s (" name;
+  let comma = ref false in
+  List.iter (
+    fun arg ->
+      if !comma then pr ", ";
+      comma := true;
+      match arg with
+      | Pathname n | Device n | Dev_or_Path n | String n
+      | OptString n | Bool n | Int n | Int64 n | FileIn n | FileOut n
+      | BufferIn n | Key n ->
+          pr "$%s" n
+      | StringList n | DeviceList n ->
+          pr "\\@%s" n
+  ) (snd style);
+  pr ");"
+
+(* Generate Python C module. *)
+and generate_python_c () =
+  generate_header CStyle LGPLv2plus;
 
-and generate_perl_struct_list_code typ cols name style n do_cleanups =
-  pr "PREINIT:\n";
-  pr "      struct guestfs_%s_list *%s;\n" typ n;
-  pr "      int i;\n";
-  pr "      HV *hv;\n";
-  pr " PPCODE:\n";
-  pr "      %s = guestfs_%s " n name;
-  generate_c_call_args ~handle:"g" style;
-  pr ";\n";
-  do_cleanups ();
-  pr "      if (%s == NULL)\n" n;
-  pr "        croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
-  pr "      EXTEND (SP, %s->len);\n" n;
-  pr "      for (i = 0; i < %s->len; ++i) {\n" n;
-  pr "        hv = newHV ();\n";
-  List.iter (
-    function
-    | name, FString ->
-        pr "        (void) hv_store (hv, \"%s\", %d, newSVpv (%s->val[i].%s, 0), 0);\n"
-          name (String.length name) n name
-    | name, FUUID ->
-        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"
-          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
-    | name, FInt64 ->
-        pr "        (void) hv_store (hv, \"%s\", %d, my_newSVll (%s->val[i].%s), 0);\n"
-          name (String.length name) n name
-    | name, (FInt32|FUInt32) ->
-        pr "        (void) hv_store (hv, \"%s\", %d, newSVnv (%s->val[i].%s), 0);\n"
-          name (String.length name) n name
-    | name, FChar ->
-        pr "        (void) hv_store (hv, \"%s\", %d, newSVpv (&%s->val[i].%s, 1), 0);\n"
-          name (String.length name) n name
-    | name, FOptPercent ->
-        pr "        (void) hv_store (hv, \"%s\", %d, newSVnv (%s->val[i].%s), 0);\n"
-          name (String.length name) n name
-  ) cols;
-  pr "        PUSHs (sv_2mortal (newRV ((SV *) hv)));\n";
-  pr "      }\n";
-  pr "      guestfs_free_%s_list (%s);\n" typ n
+  pr "\
+#define PY_SSIZE_T_CLEAN 1
+#include <Python.h>
 
-and generate_perl_struct_code typ cols name style n do_cleanups =
-  pr "PREINIT:\n";
-  pr "      struct guestfs_%s *%s;\n" typ n;
-  pr " PPCODE:\n";
-  pr "      %s = guestfs_%s " n name;
-  generate_c_call_args ~handle:"g" style;
-  pr ";\n";
-  do_cleanups ();
-  pr "      if (%s == NULL)\n" n;
-  pr "        croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
-  pr "      EXTEND (SP, 2 * %d);\n" (List.length cols);
-  List.iter (
-    fun ((name, _) as col) ->
-      pr "      PUSHs (sv_2mortal (newSVpv (\"%s\", 0)));\n" name;
+#if PY_VERSION_HEX < 0x02050000
+typedef int Py_ssize_t;
+#define PY_SSIZE_T_MAX INT_MAX
+#define PY_SSIZE_T_MIN INT_MIN
+#endif
 
-      match col with
-      | name, FString ->
-          pr "      PUSHs (sv_2mortal (newSVpv (%s->%s, 0)));\n"
-            n name
-      | name, FBuffer ->
-          pr "      PUSHs (sv_2mortal (newSVpv (%s->%s, %s->%s_len)));\n"
-            n name n name
-      | name, FUUID ->
-          pr "      PUSHs (sv_2mortal (newSVpv (%s->%s, 32)));\n"
-            n name
-      | name, (FBytes|FUInt64) ->
-          pr "      PUSHs (sv_2mortal (my_newSVull (%s->%s)));\n"
-            n name
-      | name, FInt64 ->
-          pr "      PUSHs (sv_2mortal (my_newSVll (%s->%s)));\n"
-            n name
-      | name, (FInt32|FUInt32) ->
-          pr "      PUSHs (sv_2mortal (newSVnv (%s->%s)));\n"
-            n name
-      | name, FChar ->
-          pr "      PUSHs (sv_2mortal (newSVpv (&%s->%s, 1)));\n"
-            n name
-      | name, FOptPercent ->
-          pr "      PUSHs (sv_2mortal (newSVnv (%s->%s)));\n"
-            n name
-  ) cols;
-  pr "      free (%s);\n" n
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
 
-(* Generate Sys/Guestfs.pm. *)
-and generate_perl_pm () =
-  generate_header HashStyle LGPLv2;
+#include \"guestfs.h\"
 
-  pr "\
-=pod
+#ifndef HAVE_PYCAPSULE_NEW
+typedef struct {
+  PyObject_HEAD
+  guestfs_h *g;
+} Pyguestfs_Object;
+#endif
 
-=head1 NAME
+static guestfs_h *
+get_handle (PyObject *obj)
+{
+  assert (obj);
+  assert (obj != Py_None);
+#ifndef HAVE_PYCAPSULE_NEW
+  return ((Pyguestfs_Object *) obj)->g;
+#else
+  return (guestfs_h*) PyCapsule_GetPointer(obj, \"guestfs_h\");
+#endif
+}
 
-Sys::Guestfs - Perl bindings for libguestfs
+static PyObject *
+put_handle (guestfs_h *g)
+{
+  assert (g);
+#ifndef HAVE_PYCAPSULE_NEW
+  return
+    PyCObject_FromVoidPtrAndDesc ((void *) g, (char *) \"guestfs_h\", NULL);
+#else
+  return PyCapsule_New ((void *) g, \"guestfs_h\", NULL);
+#endif
+}
 
-=head1 SYNOPSIS
+/* This list should be freed (but not the strings) after use. */
+static char **
+get_string_list (PyObject *obj)
+{
+  size_t i, len;
+  char **r;
 
use Sys::Guestfs;
 assert (obj);
 
- my $h = Sys::Guestfs->new ();
- $h->add_drive ('guest.img');
- $h->launch ();
- $h->mount ('/dev/sda1', '/');
- $h->touch ('/hello');
- $h->sync ();
+  if (!PyList_Check (obj)) {
+    PyErr_SetString (PyExc_RuntimeError, \"expecting a list parameter\");
+    return NULL;
+  }
 
-=head1 DESCRIPTION
+  Py_ssize_t slen = PyList_Size (obj);
+  if (slen == -1) {
+    PyErr_SetString (PyExc_RuntimeError, \"get_string_list: PyList_Size failure\");
+    return NULL;
+  }
+  len = (size_t) slen;
+  r = malloc (sizeof (char *) * (len+1));
+  if (r == NULL) {
+    PyErr_SetString (PyExc_RuntimeError, \"get_string_list: out of memory\");
+    return NULL;
+  }
 
-The C<Sys::Guestfs> module provides a Perl XS binding to the
-libguestfs API for examining and modifying virtual machine
-disk images.
+  for (i = 0; i < len; ++i)
+    r[i] = PyString_AsString (PyList_GetItem (obj, i));
+  r[len] = NULL;
 
-Amongst the things this is good for: making batch configuration
-changes to guests, getting disk used/free statistics (see also:
-virt-df), migrating between virtualization systems (see also:
-virt-p2v), performing partial backups, performing partial guest
-clones, cloning guests and changing registry/UUID/hostname info, and
-much else besides.
+  return r;
+}
 
-Libguestfs uses Linux kernel and qemu code, and can access any type of
-guest filesystem that Linux and qemu can, including but not limited
-to: ext2/3/4, btrfs, FAT and NTFS, LVM, many different disk partition
-schemes, qcow, qcow2, vmdk.
+static PyObject *
+put_string_list (char * const * const argv)
+{
+  PyObject *list;
+  int argc, i;
 
-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.
+  for (argc = 0; argv[argc] != NULL; ++argc)
+    ;
 
-See also L<Sys::Guestfs::Lib(3)> for a set of useful library
-functions for using libguestfs from Perl, including integration
-with libvirt.
+  list = PyList_New (argc);
+  for (i = 0; i < argc; ++i)
+    PyList_SetItem (list, i, PyString_FromString (argv[i]));
+
+  return list;
+}
+
+static PyObject *
+put_table (char * const * const argv)
+{
+  PyObject *list, *item;
+  int argc, i;
+
+  for (argc = 0; argv[argc] != NULL; ++argc)
+    ;
+
+  list = PyList_New (argc >> 1);
+  for (i = 0; i < argc; i += 2) {
+    item = PyTuple_New (2);
+    PyTuple_SetItem (item, 0, PyString_FromString (argv[i]));
+    PyTuple_SetItem (item, 1, PyString_FromString (argv[i+1]));
+    PyList_SetItem (list, i >> 1, item);
+  }
+
+  return list;
+}
+
+static void
+free_strings (char **argv)
+{
+  int argc;
+
+  for (argc = 0; argv[argc] != NULL; ++argc)
+    free (argv[argc]);
+  free (argv);
+}
+
+static PyObject *
+py_guestfs_create (PyObject *self, PyObject *args)
+{
+  guestfs_h *g;
+
+  g = guestfs_create ();
+  if (g == NULL) {
+    PyErr_SetString (PyExc_RuntimeError,
+                     \"guestfs.create: failed to allocate handle\");
+    return NULL;
+  }
+  guestfs_set_error_handler (g, NULL, NULL);
+  /* This can return NULL, but in that case put_handle will have
+   * set the Python error string.
+   */
+  return put_handle (g);
+}
+
+static PyObject *
+py_guestfs_close (PyObject *self, PyObject *args)
+{
+  PyObject *py_g;
+  guestfs_h *g;
+
+  if (!PyArg_ParseTuple (args, (char *) \"O:guestfs_close\", &py_g))
+    return NULL;
+  g = get_handle (py_g);
+
+  guestfs_close (g);
+
+  Py_INCREF (Py_None);
+  return Py_None;
+}
 
-=head1 ERRORS
+";
 
-All errors turn into calls to C<croak> (see L<Carp(3)>).
+  let emit_put_list_function typ =
+    pr "static PyObject *\n";
+    pr "put_%s_list (struct guestfs_%s_list *%ss)\n" typ typ typ;
+    pr "{\n";
+    pr "  PyObject *list;\n";
+    pr "  size_t i;\n";
+    pr "\n";
+    pr "  list = PyList_New (%ss->len);\n" typ;
+    pr "  for (i = 0; i < %ss->len; ++i)\n" typ;
+    pr "    PyList_SetItem (list, i, put_%s (&%ss->val[i]));\n" typ typ;
+    pr "  return list;\n";
+    pr "};\n";
+    pr "\n"
+  in
 
-=head1 METHODS
+  (* Structures, turned into Python dictionaries. *)
+  List.iter (
+    fun (typ, cols) ->
+      pr "static PyObject *\n";
+      pr "put_%s (struct guestfs_%s *%s)\n" typ typ typ;
+      pr "{\n";
+      pr "  PyObject *dict;\n";
+      pr "\n";
+      pr "  dict = PyDict_New ();\n";
+      List.iter (
+        function
+        | name, FString ->
+            pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
+            pr "                        PyString_FromString (%s->%s));\n"
+              typ name
+        | name, FBuffer ->
+            pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
+            pr "                        PyString_FromStringAndSize (%s->%s, %s->%s_len));\n"
+              typ name typ name
+        | name, FUUID ->
+            pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
+            pr "                        PyString_FromStringAndSize (%s->%s, 32));\n"
+              typ name
+        | name, (FBytes|FUInt64) ->
+            pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
+            pr "                        PyLong_FromUnsignedLongLong (%s->%s));\n"
+              typ name
+        | name, FInt64 ->
+            pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
+            pr "                        PyLong_FromLongLong (%s->%s));\n"
+              typ name
+        | name, FUInt32 ->
+            pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
+            pr "                        PyLong_FromUnsignedLong (%s->%s));\n"
+              typ name
+        | name, FInt32 ->
+            pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
+            pr "                        PyLong_FromLong (%s->%s));\n"
+              typ name
+        | name, FOptPercent ->
+            pr "  if (%s->%s >= 0)\n" typ name;
+            pr "    PyDict_SetItemString (dict, \"%s\",\n" name;
+            pr "                          PyFloat_FromDouble ((double) %s->%s));\n"
+              typ name;
+            pr "  else {\n";
+            pr "    Py_INCREF (Py_None);\n";
+            pr "    PyDict_SetItemString (dict, \"%s\", Py_None);\n" name;
+            pr "  }\n"
+        | name, FChar ->
+            pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
+            pr "                        PyString_FromStringAndSize (&dirent->%s, 1));\n" name
+      ) cols;
+      pr "  return dict;\n";
+      pr "};\n";
+      pr "\n";
 
-=over 4
+  ) structs;
 
-=cut
+  (* Emit a put_TYPE_list function definition only if that function is used. *)
+  List.iter (
+    function
+    | typ, (RStructListOnly | RStructAndList) ->
+        (* generate the function for typ *)
+        emit_put_list_function typ
+    | typ, _ -> () (* empty *)
+  ) (rstructs_used_by all_functions);
 
-package Sys::Guestfs;
+  (* Python wrapper functions. *)
+  List.iter (
+    fun (name, style, _, _, _, _, _) ->
+      pr "static PyObject *\n";
+      pr "py_guestfs_%s (PyObject *self, PyObject *args)\n" name;
+      pr "{\n";
 
-use strict;
-use warnings;
+      pr "  PyObject *py_g;\n";
+      pr "  guestfs_h *g;\n";
+      pr "  PyObject *py_r;\n";
 
-require XSLoader;
-XSLoader::load ('Sys::Guestfs');
+      let error_code =
+        match fst style with
+        | RErr | RInt _ | RBool _ -> pr "  int r;\n"; "-1"
+        | RInt64 _ -> pr "  int64_t r;\n"; "-1"
+        | RConstString _ | RConstOptString _ ->
+            pr "  const char *r;\n"; "NULL"
+        | RString _ -> pr "  char *r;\n"; "NULL"
+        | RStringList _ | RHashtable _ -> pr "  char **r;\n"; "NULL"
+        | RStruct (_, typ) -> pr "  struct guestfs_%s *r;\n" typ; "NULL"
+        | RStructList (_, typ) ->
+            pr "  struct guestfs_%s_list *r;\n" typ; "NULL"
+        | RBufferOut _ ->
+            pr "  char *r;\n";
+            pr "  size_t size;\n";
+            "NULL" in
 
-=item $h = Sys::Guestfs->new ();
+      List.iter (
+        function
+        | Pathname n | Device n | Dev_or_Path n | String n | Key n
+        | FileIn n | FileOut n ->
+            pr "  const char *%s;\n" n
+        | OptString n -> pr "  const char *%s;\n" n
+        | BufferIn n ->
+            pr "  const char *%s;\n" n;
+            pr "  Py_ssize_t %s_size;\n" n
+        | StringList n | DeviceList n ->
+            pr "  PyObject *py_%s;\n" n;
+            pr "  char **%s;\n" n
+        | Bool n -> pr "  int %s;\n" n
+        | Int n -> pr "  int %s;\n" n
+        | Int64 n -> pr "  long long %s;\n" n
+      ) (snd style);
 
-Create a new guestfs handle.
+      pr "\n";
 
-=cut
+      (* Convert the parameters. *)
+      pr "  if (!PyArg_ParseTuple (args, (char *) \"O";
+      List.iter (
+        function
+        | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _
+        | FileIn _ | FileOut _ -> pr "s"
+        | OptString _ -> pr "z"
+        | StringList _ | DeviceList _ -> pr "O"
+        | Bool _ -> pr "i" (* XXX Python has booleans? *)
+        | Int _ -> pr "i"
+        | Int64 _ -> pr "L" (* XXX Whoever thought it was a good idea to
+                             * emulate C's int/long/long long in Python?
+                             *)
+        | BufferIn _ -> pr "s#"
+      ) (snd style);
+      pr ":guestfs_%s\",\n" name;
+      pr "                         &py_g";
+      List.iter (
+        function
+        | Pathname n | Device n | Dev_or_Path n | String n | Key n
+        | FileIn n | FileOut n -> pr ", &%s" n
+        | OptString n -> pr ", &%s" n
+        | StringList n | DeviceList n -> pr ", &py_%s" n
+        | Bool n -> pr ", &%s" n
+        | Int n -> pr ", &%s" n
+        | Int64 n -> pr ", &%s" n
+        | BufferIn n -> pr ", &%s, &%s_size" n n
+      ) (snd style);
 
-sub new {
-  my $proto = shift;
-  my $class = ref ($proto) || $proto;
+      pr "))\n";
+      pr "    return NULL;\n";
 
-  my $self = Sys::Guestfs::_create ();
-  bless $self, $class;
-  return $self;
-}
+      pr "  g = get_handle (py_g);\n";
+      List.iter (
+        function
+        | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _
+        | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _
+        | BufferIn _ -> ()
+        | StringList n | DeviceList n ->
+            pr "  %s = get_string_list (py_%s);\n" n n;
+            pr "  if (!%s) return NULL;\n" n
+      ) (snd style);
 
-";
+      pr "\n";
 
-  (* Actions.  We only need to print documentation for these as
-   * they are pulled in from the XS code automatically.
-   *)
-  List.iter (
-    fun (name, style, _, flags, _, _, longdesc) ->
-      if not (List.mem NotInDocs flags) then (
-        let longdesc = replace_str longdesc "C<guestfs_" "C<$h-E<gt>" in
-        pr "=item ";
-        generate_perl_prototype name style;
-        pr "\n\n";
-        pr "%s\n\n" longdesc;
-        if List.mem ProtocolLimitWarning flags then
-          pr "%s\n\n" protocol_limit_warning;
-        if List.mem DangerWillRobinson flags then
-          pr "%s\n\n" danger_will_robinson;
-        match deprecation_notice flags with
-        | None -> ()
-        | Some txt -> pr "%s\n\n" txt
-      )
-  ) all_functions_sorted;
+      pr "  r = guestfs_%s " name;
+      generate_c_call_args ~handle:"g" style;
+      pr ";\n";
 
-  (* End of file. *)
-  pr "\
-=cut
+      List.iter (
+        function
+        | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _
+        | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _
+        | BufferIn _ -> ()
+        | StringList n | DeviceList n ->
+            pr "  free (%s);\n" n
+      ) (snd style);
 
-1;
+      pr "  if (r == %s) {\n" error_code;
+      pr "    PyErr_SetString (PyExc_RuntimeError, guestfs_last_error (g));\n";
+      pr "    return NULL;\n";
+      pr "  }\n";
+      pr "\n";
 
-=back
+      (match fst style with
+       | RErr ->
+           pr "  Py_INCREF (Py_None);\n";
+           pr "  py_r = Py_None;\n"
+       | RInt _
+       | RBool _ -> pr "  py_r = PyInt_FromLong ((long) r);\n"
+       | RInt64 _ -> pr "  py_r = PyLong_FromLongLong (r);\n"
+       | RConstString _ -> pr "  py_r = PyString_FromString (r);\n"
+       | RConstOptString _ ->
+           pr "  if (r)\n";
+           pr "    py_r = PyString_FromString (r);\n";
+           pr "  else {\n";
+           pr "    Py_INCREF (Py_None);\n";
+           pr "    py_r = Py_None;\n";
+           pr "  }\n"
+       | RString _ ->
+           pr "  py_r = PyString_FromString (r);\n";
+           pr "  free (r);\n"
+       | RStringList _ ->
+           pr "  py_r = put_string_list (r);\n";
+           pr "  free_strings (r);\n"
+       | RStruct (_, typ) ->
+           pr "  py_r = put_%s (r);\n" typ;
+           pr "  guestfs_free_%s (r);\n" typ
+       | RStructList (_, typ) ->
+           pr "  py_r = put_%s_list (r);\n" typ;
+           pr "  guestfs_free_%s_list (r);\n" typ
+       | RHashtable n ->
+           pr "  py_r = put_table (r);\n";
+           pr "  free_strings (r);\n"
+       | RBufferOut _ ->
+           pr "  py_r = PyString_FromStringAndSize (r, size);\n";
+           pr "  free (r);\n"
+      );
 
-=head1 COPYRIGHT
+      pr "  return py_r;\n";
+      pr "}\n";
+      pr "\n"
+  ) all_functions;
+
+  (* Table of functions. *)
+  pr "static PyMethodDef methods[] = {\n";
+  pr "  { (char *) \"create\", py_guestfs_create, METH_VARARGS, NULL },\n";
+  pr "  { (char *) \"close\", py_guestfs_close, METH_VARARGS, NULL },\n";
+  List.iter (
+    fun (name, _, _, _, _, _, _) ->
+      pr "  { (char *) \"%s\", py_guestfs_%s, METH_VARARGS, NULL },\n"
+        name name
+  ) all_functions;
+  pr "  { NULL, NULL, 0, NULL }\n";
+  pr "};\n";
+  pr "\n";
 
-Copyright (C) 2009 Red Hat Inc.
+  (* Init function. *)
+  pr "\
+void
+initlibguestfsmod (void)
+{
+  static int initialized = 0;
 
-=head1 LICENSE
+  if (initialized) return;
+  Py_InitModule ((char *) \"libguestfsmod\", methods);
+  initialized = 1;
+}
+"
 
-Please see the file COPYING.LIB for the full license.
+(* Generate Python module. *)
+and generate_python_py () =
+  generate_header HashStyle LGPLv2plus;
 
-=head1 SEE ALSO
+  pr "\
+u\"\"\"Python bindings for libguestfs
 
-L<guestfs(3)>,
-L<guestfish(1)>,
-L<http://libguestfs.org>,
-L<Sys::Guestfs::Lib(3)>.
+import guestfs
+g = guestfs.GuestFS ()
+g.add_drive (\"guest.img\")
+g.launch ()
+parts = g.list_partitions ()
 
-=cut
-"
+The guestfs module provides a Python binding to the libguestfs API
+for examining and modifying virtual machine disk images.
 
-and generate_perl_prototype name style =
-  (match fst style with
-   | RErr -> ()
-   | RBool n
-   | RInt n
-   | RInt64 n
-   | RConstString n
-   | RConstOptString n
-   | RString n
-   | RBufferOut n -> pr "$%s = " n
-   | RStruct (n,_)
-   | RHashtable n -> pr "%%%s = " n
-   | RStringList n
-   | RStructList (n,_) -> pr "@%s = " n
-  );
-  pr "$h->%s (" name;
-  let comma = ref false in
-  List.iter (
-    fun arg ->
-      if !comma then pr ", ";
-      comma := true;
-      match arg with
-      | Pathname n | Device n | Dev_or_Path n | String n
-      | OptString n | Bool n | Int n | Int64 n | FileIn n | FileOut n ->
-          pr "$%s" n
-      | StringList n | DeviceList n ->
-          pr "\\@%s" n
-  ) (snd style);
-  pr ");"
+Amongst the things this is good for: making batch configuration
+changes to guests, getting disk used/free statistics (see also:
+virt-df), migrating between virtualization systems (see also:
+virt-p2v), performing partial backups, performing partial guest
+clones, cloning guests and changing registry/UUID/hostname info, and
+much else besides.
 
-(* Generate Python C module. *)
-and generate_python_c () =
-  generate_header CStyle LGPLv2;
+Libguestfs uses Linux kernel and qemu code, and can access any type of
+guest filesystem that Linux and qemu can, including but not limited
+to: ext2/3/4, btrfs, FAT and NTFS, LVM, many different disk partition
+schemes, qcow, qcow2, vmdk.
 
-  pr "\
-#include <Python.h>
+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
+FUSE.
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
+Errors which happen while using the API are turned into Python
+RuntimeError exceptions.
 
-#include \"guestfs.h\"
+To create a guestfs handle you usually have to perform the following
+sequence of calls:
 
-typedef struct {
-  PyObject_HEAD
-  guestfs_h *g;
-} Pyguestfs_Object;
+# Create the handle, call add_drive at least once, and possibly
+# several times if the guest has multiple block devices:
+g = guestfs.GuestFS ()
+g.add_drive (\"guest.img\")
 
-static guestfs_h *
-get_handle (PyObject *obj)
-{
-  assert (obj);
-  assert (obj != Py_None);
-  return ((Pyguestfs_Object *) obj)->g;
-}
+# Launch the qemu subprocess and wait for it to become ready:
+g.launch ()
 
-static PyObject *
-put_handle (guestfs_h *g)
-{
-  assert (g);
-  return
-    PyCObject_FromVoidPtrAndDesc ((void *) g, (char *) \"guestfs_h\", NULL);
-}
+# Now you can issue commands, for example:
+logvols = g.lvs ()
 
-/* This list should be freed (but not the strings) after use. */
-static char **
-get_string_list (PyObject *obj)
-{
-  int i, len;
-  char **r;
+\"\"\"
 
-  assert (obj);
+import libguestfsmod
 
-  if (!PyList_Check (obj)) {
-    PyErr_SetString (PyExc_RuntimeError, \"expecting a list parameter\");
-    return NULL;
-  }
+class GuestFS:
+    \"\"\"Instances of this class are libguestfs API handles.\"\"\"
 
-  len = PyList_Size (obj);
-  r = malloc (sizeof (char *) * (len+1));
-  if (r == NULL) {
-    PyErr_SetString (PyExc_RuntimeError, \"get_string_list: out of memory\");
-    return NULL;
-  }
+    def __init__ (self):
+        \"\"\"Create a new libguestfs handle.\"\"\"
+        self._o = libguestfsmod.create ()
 
-  for (i = 0; i < len; ++i)
-    r[i] = PyString_AsString (PyList_GetItem (obj, i));
-  r[len] = NULL;
+    def __del__ (self):
+        libguestfsmod.close (self._o)
 
-  return r;
-}
+";
 
-static PyObject *
-put_string_list (char * const * const argv)
-{
-  PyObject *list;
-  int argc, i;
+  List.iter (
+    fun (name, style, _, flags, _, _, longdesc) ->
+      pr "    def %s " name;
+      generate_py_call_args ~handle:"self" (snd style);
+      pr ":\n";
 
-  for (argc = 0; argv[argc] != NULL; ++argc)
-    ;
+      if not (List.mem NotInDocs flags) then (
+        let doc = replace_str longdesc "C<guestfs_" "C<g." in
+        let doc =
+          match fst style with
+          | RErr | RInt _ | RInt64 _ | RBool _
+          | RConstOptString _ | RConstString _
+          | RString _ | RBufferOut _ -> doc
+          | RStringList _ ->
+              doc ^ "\n\nThis function returns a list of strings."
+          | RStruct (_, typ) ->
+              doc ^ sprintf "\n\nThis function returns a dictionary, with keys matching the various fields in the guestfs_%s structure." typ
+          | RStructList (_, typ) ->
+              doc ^ sprintf "\n\nThis function returns a list of %ss.  Each %s is represented as a dictionary." typ typ
+          | RHashtable _ ->
+              doc ^ "\n\nThis function returns a dictionary." in
+        let doc =
+          if List.mem ProtocolLimitWarning flags then
+            doc ^ "\n\n" ^ protocol_limit_warning
+          else doc in
+        let doc =
+          if List.mem DangerWillRobinson flags then
+            doc ^ "\n\n" ^ danger_will_robinson
+          else doc in
+        let doc =
+          match deprecation_notice flags with
+          | None -> doc
+          | Some txt -> doc ^ "\n\n" ^ txt in
+        let doc = pod2text ~width:60 name doc in
+        let doc = List.map (fun line -> replace_str line "\\" "\\\\") doc in
+        let doc = String.concat "\n        " doc in
+        pr "        u\"\"\"%s\"\"\"\n" doc;
+      );
+      pr "        return libguestfsmod.%s " name;
+      generate_py_call_args ~handle:"self._o" (snd style);
+      pr "\n";
+      pr "\n";
+  ) all_functions
 
-  list = PyList_New (argc);
-  for (i = 0; i < argc; ++i)
-    PyList_SetItem (list, i, PyString_FromString (argv[i]));
+(* Generate Python call arguments, eg "(handle, foo, bar)" *)
+and generate_py_call_args ~handle args =
+  pr "(%s" handle;
+  List.iter (fun arg -> pr ", %s" (name_of_argt arg)) args;
+  pr ")"
 
-  return list;
-}
+(* Useful if you need the longdesc POD text as plain text.  Returns a
+ * list of lines.
+ *
+ * Because this is very slow (the slowest part of autogeneration),
+ * we memoize the results.
+ *)
+and pod2text ~width name longdesc =
+  let key = width, name, longdesc in
+  try Hashtbl.find pod2text_memo key
+  with Not_found ->
+    let filename, chan = Filename.open_temp_file "gen" ".tmp" in
+    fprintf chan "=head1 %s\n\n%s\n" name longdesc;
+    close_out chan;
+    let cmd = sprintf "pod2text -w %d %s" width (Filename.quote filename) in
+    let chan = open_process_in cmd in
+    let lines = ref [] in
+    let rec loop i =
+      let line = input_line chan in
+      if i = 1 then            (* discard the first line of output *)
+        loop (i+1)
+      else (
+        let line = triml line in
+        lines := line :: !lines;
+        loop (i+1)
+      ) in
+    let lines = try loop 1 with End_of_file -> List.rev !lines in
+    unlink filename;
+    (match close_process_in chan with
+     | WEXITED 0 -> ()
+     | WEXITED i ->
+         failwithf "pod2text: process exited with non-zero status (%d)" i
+     | WSIGNALED i | WSTOPPED i ->
+         failwithf "pod2text: process signalled or stopped by signal %d" i
+    );
+    Hashtbl.add pod2text_memo key lines;
+    pod2text_memo_updated ();
+    lines
 
-static PyObject *
-put_table (char * const * const argv)
-{
-  PyObject *list, *item;
-  int argc, i;
+(* Generate ruby bindings. *)
+and generate_ruby_c () =
+  generate_header CStyle LGPLv2plus;
 
-  for (argc = 0; argv[argc] != NULL; ++argc)
-    ;
+  pr "\
+#include <stdio.h>
+#include <stdlib.h>
 
-  list = PyList_New (argc >> 1);
-  for (i = 0; i < argc; i += 2) {
-    item = PyTuple_New (2);
-    PyTuple_SetItem (item, 0, PyString_FromString (argv[i]));
-    PyTuple_SetItem (item, 1, PyString_FromString (argv[i+1]));
-    PyList_SetItem (list, i >> 1, item);
-  }
+#include <ruby.h>
 
-  return list;
-}
+#include \"guestfs.h\"
 
-static void
-free_strings (char **argv)
-{
-  int argc;
+#include \"extconf.h\"
 
-  for (argc = 0; argv[argc] != NULL; ++argc)
-    free (argv[argc]);
-  free (argv);
+/* For Ruby < 1.9 */
+#ifndef RARRAY_LEN
+#define RARRAY_LEN(r) (RARRAY((r))->len)
+#endif
+
+static VALUE m_guestfs;                        /* guestfs module */
+static VALUE c_guestfs;                        /* guestfs_h handle */
+static VALUE e_Error;                  /* used for all errors */
+
+static void ruby_guestfs_free (void *p)
+{
+  if (!p) return;
+  guestfs_close ((guestfs_h *) p);
 }
 
-static PyObject *
-py_guestfs_create (PyObject *self, PyObject *args)
+static VALUE ruby_guestfs_create (VALUE m)
 {
   guestfs_h *g;
 
   g = guestfs_create ();
-  if (g == NULL) {
-    PyErr_SetString (PyExc_RuntimeError,
-                     \"guestfs.create: failed to allocate handle\");
-    return NULL;
-  }
+  if (!g)
+    rb_raise (e_Error, \"failed to create guestfs handle\");
+
+  /* Don't print error messages to stderr by default. */
   guestfs_set_error_handler (g, NULL, NULL);
-  return put_handle (g);
+
+  /* Wrap it, and make sure the close function is called when the
+   * handle goes away.
+   */
+  return Data_Wrap_Struct (c_guestfs, NULL, ruby_guestfs_free, g);
 }
 
-static PyObject *
-py_guestfs_close (PyObject *self, PyObject *args)
+static VALUE ruby_guestfs_close (VALUE gv)
 {
-  PyObject *py_g;
   guestfs_h *g;
+  Data_Get_Struct (gv, guestfs_h, g);
 
-  if (!PyArg_ParseTuple (args, (char *) \"O:guestfs_close\", &py_g))
-    return NULL;
-  g = get_handle (py_g);
-
-  guestfs_close (g);
+  ruby_guestfs_free (g);
+  DATA_PTR (gv) = NULL;
 
-  Py_INCREF (Py_None);
-  return Py_None;
+  return Qnil;
 }
 
 ";
 
-  let emit_put_list_function typ =
-    pr "static PyObject *\n";
-    pr "put_%s_list (struct guestfs_%s_list *%ss)\n" typ typ typ;
-    pr "{\n";
-    pr "  PyObject *list;\n";
-    pr "  int i;\n";
-    pr "\n";
-    pr "  list = PyList_New (%ss->len);\n" typ;
-    pr "  for (i = 0; i < %ss->len; ++i)\n" typ;
-    pr "    PyList_SetItem (list, i, put_%s (&%ss->val[i]));\n" typ typ;
-    pr "  return list;\n";
-    pr "};\n";
-    pr "\n"
-  in
-
-  (* Structures, turned into Python dictionaries. *)
   List.iter (
-    fun (typ, cols) ->
-      pr "static PyObject *\n";
-      pr "put_%s (struct guestfs_%s *%s)\n" typ typ typ;
+    fun (name, style, _, _, _, _, _) ->
+      pr "static VALUE ruby_guestfs_%s (VALUE gv" name;
+      List.iter (fun arg -> pr ", VALUE %sv" (name_of_argt arg)) (snd style);
+      pr ")\n";
       pr "{\n";
-      pr "  PyObject *dict;\n";
+      pr "  guestfs_h *g;\n";
+      pr "  Data_Get_Struct (gv, guestfs_h, g);\n";
+      pr "  if (!g)\n";
+      pr "    rb_raise (rb_eArgError, \"%%s: used handle after closing it\", \"%s\");\n"
+        name;
       pr "\n";
-      pr "  dict = PyDict_New ();\n";
+
       List.iter (
         function
-        | name, FString ->
-            pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
-            pr "                        PyString_FromString (%s->%s));\n"
-              typ name
-        | name, FBuffer ->
-            pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
-            pr "                        PyString_FromStringAndSize (%s->%s, %s->%s_len));\n"
-              typ name typ name
-        | name, FUUID ->
-            pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
-            pr "                        PyString_FromStringAndSize (%s->%s, 32));\n"
-              typ name
-        | name, (FBytes|FUInt64) ->
-            pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
-            pr "                        PyLong_FromUnsignedLongLong (%s->%s));\n"
-              typ name
-        | name, FInt64 ->
-            pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
-            pr "                        PyLong_FromLongLong (%s->%s));\n"
-              typ name
-        | name, FUInt32 ->
-            pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
-            pr "                        PyLong_FromUnsignedLong (%s->%s));\n"
-              typ name
-        | name, FInt32 ->
-            pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
-            pr "                        PyLong_FromLong (%s->%s));\n"
-              typ name
-        | name, FOptPercent ->
-            pr "  if (%s->%s >= 0)\n" typ name;
-            pr "    PyDict_SetItemString (dict, \"%s\",\n" name;
-            pr "                          PyFloat_FromDouble ((double) %s->%s));\n"
-              typ name;
-            pr "  else {\n";
-            pr "    Py_INCREF (Py_None);\n";
-            pr "    PyDict_SetItemString (dict, \"%s\", Py_None);\n" name;
-            pr "  }\n"
-        | name, FChar ->
-            pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
-            pr "                        PyString_FromStringAndSize (&dirent->%s, 1));\n" name
-      ) cols;
-      pr "  return dict;\n";
-      pr "};\n";
+        | Pathname n | Device n | Dev_or_Path n | String n | Key n
+        | FileIn n | FileOut n ->
+            pr "  Check_Type (%sv, T_STRING);\n" n;
+            pr "  const char *%s = StringValueCStr (%sv);\n" n n;
+            pr "  if (!%s)\n" n;
+            pr "    rb_raise (rb_eTypeError, \"expected string for parameter %%s of %%s\",\n";
+            pr "              \"%s\", \"%s\");\n" n name
+        | BufferIn n ->
+            pr "  Check_Type (%sv, T_STRING);\n" n;
+            pr "  const char *%s = RSTRING (%sv)->ptr;\n" n n;
+            pr "  if (!%s)\n" n;
+            pr "    rb_raise (rb_eTypeError, \"expected string for parameter %%s of %%s\",\n";
+            pr "              \"%s\", \"%s\");\n" n name;
+            pr "  size_t %s_size = RSTRING (%sv)->len;\n" n n
+        | OptString n ->
+            pr "  const char *%s = !NIL_P (%sv) ? StringValueCStr (%sv) : NULL;\n" n n n
+        | StringList n | DeviceList n ->
+            pr "  char **%s;\n" n;
+            pr "  Check_Type (%sv, T_ARRAY);\n" n;
+            pr "  {\n";
+            pr "    size_t i, len;\n";
+            pr "    len = RARRAY_LEN (%sv);\n" n;
+            pr "    %s = guestfs_safe_malloc (g, sizeof (char *) * (len+1));\n"
+              n;
+            pr "    for (i = 0; i < len; ++i) {\n";
+            pr "      VALUE v = rb_ary_entry (%sv, i);\n" n;
+            pr "      %s[i] = StringValueCStr (v);\n" n;
+            pr "    }\n";
+            pr "    %s[len] = NULL;\n" n;
+            pr "  }\n";
+        | Bool n ->
+            pr "  int %s = RTEST (%sv);\n" n n
+        | Int n ->
+            pr "  int %s = NUM2INT (%sv);\n" n n
+        | Int64 n ->
+            pr "  long long %s = NUM2LL (%sv);\n" n n
+      ) (snd style);
       pr "\n";
 
-  ) structs;
-
-  (* Emit a put_TYPE_list function definition only if that function is used. *)
-  List.iter (
-    function
-    | typ, (RStructListOnly | RStructAndList) ->
-        (* generate the function for typ *)
-        emit_put_list_function typ
-    | typ, _ -> () (* empty *)
-  ) (rstructs_used_by all_functions);
-
-  (* Python wrapper functions. *)
-  List.iter (
-    fun (name, style, _, _, _, _, _) ->
-      pr "static PyObject *\n";
-      pr "py_guestfs_%s (PyObject *self, PyObject *args)\n" name;
-      pr "{\n";
-
-      pr "  PyObject *py_g;\n";
-      pr "  guestfs_h *g;\n";
-      pr "  PyObject *py_r;\n";
-
       let error_code =
         match fst style with
         | RErr | RInt _ | RBool _ -> pr "  int r;\n"; "-1"
@@ -8525,60 +10987,6 @@ py_guestfs_close (PyObject *self, PyObject *args)
             pr "  char *r;\n";
             pr "  size_t size;\n";
             "NULL" in
-
-      List.iter (
-        function
-        | Pathname n | Device n | Dev_or_Path n | String n | FileIn n | FileOut n ->
-            pr "  const char *%s;\n" n
-        | OptString n -> pr "  const char *%s;\n" n
-        | StringList n | DeviceList n ->
-            pr "  PyObject *py_%s;\n" n;
-            pr "  char **%s;\n" n
-        | Bool n -> pr "  int %s;\n" n
-        | Int n -> pr "  int %s;\n" n
-        | Int64 n -> pr "  long long %s;\n" n
-      ) (snd style);
-
-      pr "\n";
-
-      (* Convert the parameters. *)
-      pr "  if (!PyArg_ParseTuple (args, (char *) \"O";
-      List.iter (
-        function
-        | Pathname _ | Device _ | Dev_or_Path _ | String _ | FileIn _ | FileOut _ -> pr "s"
-        | OptString _ -> pr "z"
-        | StringList _ | DeviceList _ -> pr "O"
-        | Bool _ -> pr "i" (* XXX Python has booleans? *)
-        | Int _ -> pr "i"
-        | Int64 _ -> pr "L" (* XXX Whoever thought it was a good idea to
-                             * emulate C's int/long/long long in Python?
-                             *)
-      ) (snd style);
-      pr ":guestfs_%s\",\n" name;
-      pr "                         &py_g";
-      List.iter (
-        function
-        | Pathname n | Device n | Dev_or_Path n | String n | FileIn n | FileOut n -> pr ", &%s" n
-        | OptString n -> pr ", &%s" n
-        | StringList n | DeviceList n -> pr ", &py_%s" n
-        | Bool n -> pr ", &%s" n
-        | Int n -> pr ", &%s" n
-        | Int64 n -> pr ", &%s" n
-      ) (snd style);
-
-      pr "))\n";
-      pr "    return NULL;\n";
-
-      pr "  g = get_handle (py_g);\n";
-      List.iter (
-        function
-        | Pathname _ | Device _ | Dev_or_Path _ | String _
-        | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _ -> ()
-        | StringList n | DeviceList n ->
-            pr "  %s = get_string_list (py_%s);\n" n n;
-            pr "  if (!%s) return NULL;\n" n
-      ) (snd style);
-
       pr "\n";
 
       pr "  r = guestfs_%s " name;
@@ -8587,171 +10995,230 @@ py_guestfs_close (PyObject *self, PyObject *args)
 
       List.iter (
         function
-        | Pathname _ | Device _ | Dev_or_Path _ | String _
-        | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _ -> ()
+        | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _
+        | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _
+        | BufferIn _ -> ()
         | StringList n | DeviceList n ->
             pr "  free (%s);\n" n
       ) (snd style);
 
-      pr "  if (r == %s) {\n" error_code;
-      pr "    PyErr_SetString (PyExc_RuntimeError, guestfs_last_error (g));\n";
-      pr "    return NULL;\n";
-      pr "  }\n";
+      pr "  if (r == %s)\n" error_code;
+      pr "    rb_raise (e_Error, \"%%s\", guestfs_last_error (g));\n";
       pr "\n";
 
       (match fst style with
        | RErr ->
-           pr "  Py_INCREF (Py_None);\n";
-           pr "  py_r = Py_None;\n"
-       | RInt _
-       | RBool _ -> pr "  py_r = PyInt_FromLong ((long) r);\n"
-       | RInt64 _ -> pr "  py_r = PyLong_FromLongLong (r);\n"
-       | RConstString _ -> pr "  py_r = PyString_FromString (r);\n"
-       | RConstOptString _ ->
-           pr "  if (r)\n";
-           pr "    py_r = PyString_FromString (r);\n";
-           pr "  else {\n";
-           pr "    Py_INCREF (Py_None);\n";
-           pr "    py_r = Py_None;\n";
-           pr "  }\n"
+           pr "  return Qnil;\n"
+       | RInt _ | RBool _ ->
+           pr "  return INT2NUM (r);\n"
+       | RInt64 _ ->
+           pr "  return ULL2NUM (r);\n"
+       | RConstString _ ->
+           pr "  return rb_str_new2 (r);\n";
+       | RConstOptString _ ->
+           pr "  if (r)\n";
+           pr "    return rb_str_new2 (r);\n";
+           pr "  else\n";
+           pr "    return Qnil;\n";
        | RString _ ->
-           pr "  py_r = PyString_FromString (r);\n";
-           pr "  free (r);\n"
+           pr "  VALUE rv = rb_str_new2 (r);\n";
+           pr "  free (r);\n";
+           pr "  return rv;\n";
        | RStringList _ ->
-           pr "  py_r = put_string_list (r);\n";
-           pr "  free_strings (r);\n"
+           pr "  size_t i, len = 0;\n";
+           pr "  for (i = 0; r[i] != NULL; ++i) len++;\n";
+           pr "  VALUE rv = rb_ary_new2 (len);\n";
+           pr "  for (i = 0; r[i] != NULL; ++i) {\n";
+           pr "    rb_ary_push (rv, rb_str_new2 (r[i]));\n";
+           pr "    free (r[i]);\n";
+           pr "  }\n";
+           pr "  free (r);\n";
+           pr "  return rv;\n"
        | RStruct (_, typ) ->
-           pr "  py_r = put_%s (r);\n" typ;
-           pr "  guestfs_free_%s (r);\n" typ
+           let cols = cols_of_struct typ in
+           generate_ruby_struct_code typ cols
        | RStructList (_, typ) ->
-           pr "  py_r = put_%s_list (r);\n" typ;
-           pr "  guestfs_free_%s_list (r);\n" typ
-       | RHashtable n ->
-           pr "  py_r = put_table (r);\n";
-           pr "  free_strings (r);\n"
+           let cols = cols_of_struct typ in
+           generate_ruby_struct_list_code typ cols
+       | RHashtable _ ->
+           pr "  VALUE rv = rb_hash_new ();\n";
+           pr "  size_t i;\n";
+           pr "  for (i = 0; r[i] != NULL; i+=2) {\n";
+           pr "    rb_hash_aset (rv, rb_str_new2 (r[i]), rb_str_new2 (r[i+1]));\n";
+           pr "    free (r[i]);\n";
+           pr "    free (r[i+1]);\n";
+           pr "  }\n";
+           pr "  free (r);\n";
+           pr "  return rv;\n"
        | RBufferOut _ ->
-           pr "  py_r = PyString_FromStringAndSize (r, size);\n";
-           pr "  free (r);\n"
+           pr "  VALUE rv = rb_str_new (r, size);\n";
+           pr "  free (r);\n";
+           pr "  return rv;\n";
       );
 
-      pr "  return py_r;\n";
       pr "}\n";
       pr "\n"
   ) all_functions;
 
-  (* Table of functions. *)
-  pr "static PyMethodDef methods[] = {\n";
-  pr "  { (char *) \"create\", py_guestfs_create, METH_VARARGS, NULL },\n";
-  pr "  { (char *) \"close\", py_guestfs_close, METH_VARARGS, NULL },\n";
-  List.iter (
-    fun (name, _, _, _, _, _, _) ->
-      pr "  { (char *) \"%s\", py_guestfs_%s, METH_VARARGS, NULL },\n"
-        name name
-  ) all_functions;
-  pr "  { NULL, NULL, 0, NULL }\n";
-  pr "};\n";
-  pr "\n";
-
-  (* Init function. *)
   pr "\
-void
-initlibguestfsmod (void)
+/* Initialize the module. */
+void Init__guestfs ()
 {
-  static int initialized = 0;
-
-  if (initialized) return;
-  Py_InitModule ((char *) \"libguestfsmod\", methods);
-  initialized = 1;
-}
-"
-
-(* Generate Python module. *)
-and generate_python_py () =
-  generate_header HashStyle LGPLv2;
-
-  pr "\
-u\"\"\"Python bindings for libguestfs
-
-import guestfs
-g = guestfs.GuestFS ()
-g.add_drive (\"guest.img\")
-g.launch ()
-parts = g.list_partitions ()
-
-The guestfs module provides a Python binding to the libguestfs API
-for examining and modifying virtual machine disk images.
-
-Amongst the things this is good for: making batch configuration
-changes to guests, getting disk used/free statistics (see also:
-virt-df), migrating between virtualization systems (see also:
-virt-p2v), performing partial backups, performing partial guest
-clones, cloning guests and changing registry/UUID/hostname info, and
-much else besides.
+  m_guestfs = rb_define_module (\"Guestfs\");
+  c_guestfs = rb_define_class_under (m_guestfs, \"Guestfs\", rb_cObject);
+  e_Error = rb_define_class_under (m_guestfs, \"Error\", rb_eStandardError);
 
-Libguestfs uses Linux kernel and qemu code, and can access any type of
-guest filesystem that Linux and qemu can, including but not limited
-to: ext2/3/4, btrfs, FAT and NTFS, LVM, many different disk partition
-schemes, qcow, qcow2, vmdk.
+#ifdef HAVE_RB_DEFINE_ALLOC_FUNC
+  rb_define_alloc_func (c_guestfs, ruby_guestfs_create);
+#endif
 
-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.
+  rb_define_module_function (m_guestfs, \"create\", ruby_guestfs_create, 0);
+  rb_define_method (c_guestfs, \"close\", ruby_guestfs_close, 0);
 
-Errors which happen while using the API are turned into Python
-RuntimeError exceptions.
+";
+  (* Define the rest of the methods. *)
+  List.iter (
+    fun (name, style, _, _, _, _, _) ->
+      pr "  rb_define_method (c_guestfs, \"%s\",\n" name;
+      pr "        ruby_guestfs_%s, %d);\n" name (List.length (snd style))
+  ) all_functions;
 
-To create a guestfs handle you usually have to perform the following
-sequence of calls:
+  pr "}\n"
 
-# Create the handle, call add_drive at least once, and possibly
-# several times if the guest has multiple block devices:
-g = guestfs.GuestFS ()
-g.add_drive (\"guest.img\")
+(* Ruby code to return a struct. *)
+and generate_ruby_struct_code typ cols =
+  pr "  VALUE rv = rb_hash_new ();\n";
+  List.iter (
+    function
+    | name, FString ->
+        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), rb_str_new2 (r->%s));\n" name name
+    | name, FBuffer ->
+        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), rb_str_new (r->%s, r->%s_len));\n" name name name
+    | name, FUUID ->
+        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), rb_str_new (r->%s, 32));\n" name name
+    | name, (FBytes|FUInt64) ->
+        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), ULL2NUM (r->%s));\n" name name
+    | name, FInt64 ->
+        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), LL2NUM (r->%s));\n" name name
+    | name, FUInt32 ->
+        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), UINT2NUM (r->%s));\n" name name
+    | name, FInt32 ->
+        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), INT2NUM (r->%s));\n" name name
+    | name, FOptPercent ->
+        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), rb_dbl2big (r->%s));\n" name name
+    | name, FChar -> (* XXX wrong? *)
+        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), ULL2NUM (r->%s));\n" name name
+  ) cols;
+  pr "  guestfs_free_%s (r);\n" typ;
+  pr "  return rv;\n"
 
-# Launch the qemu subprocess and wait for it to become ready:
-g.launch ()
+(* Ruby code to return a struct list. *)
+and generate_ruby_struct_list_code typ cols =
+  pr "  VALUE rv = rb_ary_new2 (r->len);\n";
+  pr "  size_t i;\n";
+  pr "  for (i = 0; i < r->len; ++i) {\n";
+  pr "    VALUE hv = rb_hash_new ();\n";
+  List.iter (
+    function
+    | name, FString ->
+        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), rb_str_new2 (r->val[i].%s));\n" name name
+    | name, FBuffer ->
+        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), rb_str_new (r->val[i].%s, r->val[i].%s_len));\n" name name name
+    | name, FUUID ->
+        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), rb_str_new (r->val[i].%s, 32));\n" name name
+    | name, (FBytes|FUInt64) ->
+        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), ULL2NUM (r->val[i].%s));\n" name name
+    | name, FInt64 ->
+        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), LL2NUM (r->val[i].%s));\n" name name
+    | name, FUInt32 ->
+        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), UINT2NUM (r->val[i].%s));\n" name name
+    | name, FInt32 ->
+        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), INT2NUM (r->val[i].%s));\n" name name
+    | name, FOptPercent ->
+        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), rb_dbl2big (r->val[i].%s));\n" name name
+    | name, FChar -> (* XXX wrong? *)
+        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), ULL2NUM (r->val[i].%s));\n" name name
+  ) cols;
+  pr "    rb_ary_push (rv, hv);\n";
+  pr "  }\n";
+  pr "  guestfs_free_%s_list (r);\n" typ;
+  pr "  return rv;\n"
 
-# Now you can issue commands, for example:
-logvols = g.lvs ()
+(* Generate Java bindings GuestFS.java file. *)
+and generate_java_java () =
+  generate_header CStyle LGPLv2plus;
 
-\"\"\"
+  pr "\
+package com.redhat.et.libguestfs;
 
-import libguestfsmod
+import java.util.HashMap;
+import com.redhat.et.libguestfs.LibGuestFSException;
+import com.redhat.et.libguestfs.PV;
+import com.redhat.et.libguestfs.VG;
+import com.redhat.et.libguestfs.LV;
+import com.redhat.et.libguestfs.Stat;
+import com.redhat.et.libguestfs.StatVFS;
+import com.redhat.et.libguestfs.IntBool;
+import com.redhat.et.libguestfs.Dirent;
 
-class GuestFS:
-    \"\"\"Instances of this class are libguestfs API handles.\"\"\"
+/**
+ * The GuestFS object is a libguestfs handle.
+ *
+ * @author rjones
+ */
+public class GuestFS {
+  // Load the native code.
+  static {
+    System.loadLibrary (\"guestfs_jni\");
+  }
 
-    def __init__ (self):
-        \"\"\"Create a new libguestfs handle.\"\"\"
-        self._o = libguestfsmod.create ()
+  /**
+   * The native guestfs_h pointer.
+   */
+  long g;
 
-    def __del__ (self):
-        libguestfsmod.close (self._o)
+  /**
+   * Create a libguestfs handle.
+   *
+   * @throws LibGuestFSException
+   */
+  public GuestFS () throws LibGuestFSException
+  {
+    g = _create ();
+  }
+  private native long _create () throws LibGuestFSException;
 
-";
+  /**
+   * Close a libguestfs handle.
+   *
+   * You can also leave handles to be collected by the garbage
+   * collector, but this method ensures that the resources used
+   * by the handle are freed up immediately.  If you call any
+   * other methods after closing the handle, you will get an
+   * exception.
+   *
+   * @throws LibGuestFSException
+   */
+  public void close () throws LibGuestFSException
+  {
+    if (g != 0)
+      _close (g);
+    g = 0;
+  }
+  private native void _close (long g) throws LibGuestFSException;
 
-  List.iter (
-    fun (name, style, _, flags, _, _, longdesc) ->
-      pr "    def %s " name;
-      generate_py_call_args ~handle:"self" (snd style);
-      pr ":\n";
+  public void finalize () throws LibGuestFSException
+  {
+    close ();
+  }
 
-      if not (List.mem NotInDocs flags) then (
+";
+
+  List.iter (
+    fun (name, style, _, flags, _, shortdesc, longdesc) ->
+      if not (List.mem NotInDocs flags); then (
         let doc = replace_str longdesc "C<guestfs_" "C<g." in
         let doc =
-          match fst style with
-          | RErr | RInt _ | RInt64 _ | RBool _
-          | RConstOptString _ | RConstString _
-          | RString _ | RBufferOut _ -> doc
-          | RStringList _ ->
-              doc ^ "\n\nThis function returns a list of strings."
-          | RStruct (_, typ) ->
-              doc ^ sprintf "\n\nThis function returns a dictionary, with keys matching the various fields in the guestfs_%s structure." typ
-          | RStructList (_, typ) ->
-              doc ^ sprintf "\n\nThis function returns a list of %ss.  Each %s is represented as a dictionary." typ typ
-          | RHashtable _ ->
-              doc ^ "\n\nThis function returns a dictionary." in
-        let doc =
           if List.mem ProtocolLimitWarning flags then
             doc ^ "\n\n" ^ protocol_limit_warning
           else doc in
@@ -8764,1124 +11231,1403 @@ class GuestFS:
           | None -> doc
           | Some txt -> doc ^ "\n\n" ^ txt in
         let doc = pod2text ~width:60 name doc in
-        let doc = List.map (fun line -> replace_str line "\\" "\\\\") doc in
-        let doc = String.concat "\n        " doc in
-        pr "        u\"\"\"%s\"\"\"\n" doc;
+        let doc = List.map (           (* RHBZ#501883 *)
+          function
+          | "" -> "<p>"
+          | nonempty -> nonempty
+        ) doc in
+        let doc = String.concat "\n   * " doc in
+
+        pr "  /**\n";
+        pr "   * %s\n" shortdesc;
+        pr "   * <p>\n";
+        pr "   * %s\n" doc;
+        pr "   * @throws LibGuestFSException\n";
+        pr "   */\n";
+        pr "  ";
       );
-      pr "        return libguestfsmod.%s " name;
-      generate_py_call_args ~handle:"self._o" (snd style);
+      generate_java_prototype ~public:true ~semicolon:false name style;
+      pr "\n";
+      pr "  {\n";
+      pr "    if (g == 0)\n";
+      pr "      throw new LibGuestFSException (\"%s: handle is closed\");\n"
+        name;
+      pr "    ";
+      if fst style <> RErr then pr "return ";
+      pr "_%s " name;
+      generate_java_call_args ~handle:"g" (snd style);
+      pr ";\n";
+      pr "  }\n";
+      pr "  ";
+      generate_java_prototype ~privat:true ~native:true name style;
       pr "\n";
       pr "\n";
-  ) all_functions
+  ) all_functions;
 
-(* Generate Python call arguments, eg "(handle, foo, bar)" *)
-and generate_py_call_args ~handle args =
+  pr "}\n"
+
+(* Generate Java call arguments, eg "(handle, foo, bar)" *)
+and generate_java_call_args ~handle args =
   pr "(%s" handle;
   List.iter (fun arg -> pr ", %s" (name_of_argt arg)) args;
   pr ")"
 
-(* Useful if you need the longdesc POD text as plain text.  Returns a
- * list of lines.
- *
- * Because this is very slow (the slowest part of autogeneration),
- * we memoize the results.
- *)
-and pod2text ~width name longdesc =
-  let key = width, name, longdesc in
-  try Hashtbl.find pod2text_memo key
-  with Not_found ->
-    let filename, chan = Filename.open_temp_file "gen" ".tmp" in
-    fprintf chan "=head1 %s\n\n%s\n" name longdesc;
-    close_out chan;
-    let cmd = sprintf "pod2text -w %d %s" width (Filename.quote filename) in
-    let chan = open_process_in cmd in
-    let lines = ref [] in
-    let rec loop i =
-      let line = input_line chan in
-      if i = 1 then            (* discard the first line of output *)
-        loop (i+1)
-      else (
-        let line = triml line in
-        lines := line :: !lines;
-        loop (i+1)
-      ) in
-    let lines = try loop 1 with End_of_file -> List.rev !lines in
-    unlink filename;
-    (match close_process_in chan with
-     | WEXITED 0 -> ()
-     | WEXITED i ->
-         failwithf "pod2text: process exited with non-zero status (%d)" i
-     | WSIGNALED i | WSTOPPED i ->
-         failwithf "pod2text: process signalled or stopped by signal %d" i
-    );
-    Hashtbl.add pod2text_memo key lines;
-    pod2text_memo_updated ();
-    lines
+and generate_java_prototype ?(public=false) ?(privat=false) ?(native=false)
+    ?(semicolon=true) name style =
+  if privat then pr "private ";
+  if public then pr "public ";
+  if native then pr "native ";
 
-(* Generate ruby bindings. *)
-and generate_ruby_c () =
-  generate_header CStyle LGPLv2;
+  (* return type *)
+  (match fst style with
+   | RErr -> pr "void ";
+   | RInt _ -> pr "int ";
+   | RInt64 _ -> pr "long ";
+   | RBool _ -> pr "boolean ";
+   | RConstString _ | RConstOptString _ | RString _
+   | RBufferOut _ -> pr "String ";
+   | RStringList _ -> pr "String[] ";
+   | RStruct (_, typ) ->
+       let name = java_name_of_struct typ in
+       pr "%s " name;
+   | RStructList (_, typ) ->
+       let name = java_name_of_struct typ in
+       pr "%s[] " name;
+   | RHashtable _ -> pr "HashMap<String,String> ";
+  );
+
+  if native then pr "_%s " name else pr "%s " name;
+  pr "(";
+  let needs_comma = ref false in
+  if native then (
+    pr "long g";
+    needs_comma := true
+  );
+
+  (* args *)
+  List.iter (
+    fun arg ->
+      if !needs_comma then pr ", ";
+      needs_comma := true;
+
+      match arg with
+      | Pathname n
+      | Device n | Dev_or_Path n
+      | String n
+      | OptString n
+      | FileIn n
+      | FileOut n
+      | Key n ->
+          pr "String %s" n
+      | BufferIn n ->
+          pr "byte[] %s" n
+      | StringList n | DeviceList n ->
+          pr "String[] %s" n
+      | Bool n ->
+          pr "boolean %s" n
+      | Int n ->
+          pr "int %s" n
+      | Int64 n ->
+          pr "long %s" n
+  ) (snd style);
+
+  pr ")\n";
+  pr "    throws LibGuestFSException";
+  if semicolon then pr ";"
+
+and generate_java_struct jtyp cols () =
+  generate_header CStyle LGPLv2plus;
 
   pr "\
-#include <stdio.h>
-#include <stdlib.h>
+package com.redhat.et.libguestfs;
 
-#include <ruby.h>
+/**
+ * Libguestfs %s structure.
+ *
+ * @author rjones
+ * @see GuestFS
+ */
+public class %s {
+" jtyp jtyp;
 
-#include \"guestfs.h\"
+  List.iter (
+    function
+    | name, FString
+    | name, FUUID
+    | name, FBuffer -> pr "  public String %s;\n" name
+    | name, (FBytes|FUInt64|FInt64) -> pr "  public long %s;\n" name
+    | name, (FUInt32|FInt32) -> pr "  public int %s;\n" name
+    | name, FChar -> pr "  public char %s;\n" name
+    | name, FOptPercent ->
+        pr "  /* The next field is [0..100] or -1 meaning 'not present': */\n";
+        pr "  public float %s;\n" name
+  ) cols;
 
-#include \"extconf.h\"
+  pr "}\n"
 
-/* For Ruby < 1.9 */
-#ifndef RARRAY_LEN
-#define RARRAY_LEN(r) (RARRAY((r))->len)
-#endif
+and generate_java_c () =
+  generate_header CStyle LGPLv2plus;
 
-static VALUE m_guestfs;                        /* guestfs module */
-static VALUE c_guestfs;                        /* guestfs_h handle */
-static VALUE e_Error;                  /* used for all errors */
+  pr "\
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 
-static void ruby_guestfs_free (void *p)
+#include \"com_redhat_et_libguestfs_GuestFS.h\"
+#include \"guestfs.h\"
+
+/* Note that this function returns.  The exception is not thrown
+ * until after the wrapper function returns.
+ */
+static void
+throw_exception (JNIEnv *env, const char *msg)
 {
-  if (!p) return;
-  guestfs_close ((guestfs_h *) p);
+  jclass cl;
+  cl = (*env)->FindClass (env,
+                          \"com/redhat/et/libguestfs/LibGuestFSException\");
+  (*env)->ThrowNew (env, cl, msg);
 }
 
-static VALUE ruby_guestfs_create (VALUE m)
+JNIEXPORT jlong JNICALL
+Java_com_redhat_et_libguestfs_GuestFS__1create
+  (JNIEnv *env, jobject obj)
 {
   guestfs_h *g;
 
   g = guestfs_create ();
-  if (!g)
-    rb_raise (e_Error, \"failed to create guestfs handle\");
-
-  /* Don't print error messages to stderr by default. */
+  if (g == NULL) {
+    throw_exception (env, \"GuestFS.create: failed to allocate handle\");
+    return 0;
+  }
   guestfs_set_error_handler (g, NULL, NULL);
-
-  /* Wrap it, and make sure the close function is called when the
-   * handle goes away.
-   */
-  return Data_Wrap_Struct (c_guestfs, NULL, ruby_guestfs_free, g);
+  return (jlong) (long) g;
 }
 
-static VALUE ruby_guestfs_close (VALUE gv)
+JNIEXPORT void JNICALL
+Java_com_redhat_et_libguestfs_GuestFS__1close
+  (JNIEnv *env, jobject obj, jlong jg)
 {
-  guestfs_h *g;
-  Data_Get_Struct (gv, guestfs_h, g);
-
-  ruby_guestfs_free (g);
-  DATA_PTR (gv) = NULL;
-
-  return Qnil;
+  guestfs_h *g = (guestfs_h *) (long) jg;
+  guestfs_close (g);
 }
 
 ";
 
   List.iter (
     fun (name, style, _, _, _, _, _) ->
-      pr "static VALUE ruby_guestfs_%s (VALUE gv" name;
-      List.iter (fun arg -> pr ", VALUE %sv" (name_of_argt arg)) (snd style);
-      pr ")\n";
-      pr "{\n";
-      pr "  guestfs_h *g;\n";
-      pr "  Data_Get_Struct (gv, guestfs_h, g);\n";
-      pr "  if (!g)\n";
-      pr "    rb_raise (rb_eArgError, \"%%s: used handle after closing it\", \"%s\");\n"
-        name;
+      pr "JNIEXPORT ";
+      (match fst style with
+       | RErr -> pr "void ";
+       | RInt _ -> pr "jint ";
+       | RInt64 _ -> pr "jlong ";
+       | RBool _ -> pr "jboolean ";
+       | RConstString _ | RConstOptString _ | RString _
+       | RBufferOut _ -> pr "jstring ";
+       | RStruct _ | RHashtable _ ->
+           pr "jobject ";
+       | RStringList _ | RStructList _ ->
+           pr "jobjectArray ";
+      );
+      pr "JNICALL\n";
+      pr "Java_com_redhat_et_libguestfs_GuestFS_";
+      pr "%s" (replace_str ("_" ^ name) "_" "_1");
       pr "\n";
-
+      pr "  (JNIEnv *env, jobject obj, jlong jg";
       List.iter (
         function
-        | Pathname n | Device n | Dev_or_Path n | String n | FileIn n | FileOut n ->
-            pr "  Check_Type (%sv, T_STRING);\n" n;
-            pr "  const char *%s = StringValueCStr (%sv);\n" n n;
-            pr "  if (!%s)\n" n;
-            pr "    rb_raise (rb_eTypeError, \"expected string for parameter %%s of %%s\",\n";
-            pr "              \"%s\", \"%s\");\n" n name
-        | OptString n ->
-            pr "  const char *%s = !NIL_P (%sv) ? StringValueCStr (%sv) : NULL;\n" n n n
+        | Pathname n
+        | Device n | Dev_or_Path n
+        | String n
+        | OptString n
+        | FileIn n
+        | FileOut n
+        | Key n ->
+            pr ", jstring j%s" n
+        | BufferIn n ->
+            pr ", jbyteArray j%s" n
         | StringList n | DeviceList n ->
-            pr "  char **%s;\n" n;
-            pr "  Check_Type (%sv, T_ARRAY);\n" n;
-            pr "  {\n";
-            pr "    int i, len;\n";
-            pr "    len = RARRAY_LEN (%sv);\n" n;
-            pr "    %s = guestfs_safe_malloc (g, sizeof (char *) * (len+1));\n"
-              n;
-            pr "    for (i = 0; i < len; ++i) {\n";
-            pr "      VALUE v = rb_ary_entry (%sv, i);\n" n;
-            pr "      %s[i] = StringValueCStr (v);\n" n;
-            pr "    }\n";
-            pr "    %s[len] = NULL;\n" n;
-            pr "  }\n";
+            pr ", jobjectArray j%s" n
         | Bool n ->
-            pr "  int %s = RTEST (%sv);\n" n n
+            pr ", jboolean j%s" n
         | Int n ->
-            pr "  int %s = NUM2INT (%sv);\n" n n
+            pr ", jint j%s" n
         | Int64 n ->
-            pr "  long long %s = NUM2LL (%sv);\n" n n
+            pr ", jlong j%s" n
       ) (snd style);
-      pr "\n";
-
-      let error_code =
+      pr ")\n";
+      pr "{\n";
+      pr "  guestfs_h *g = (guestfs_h *) (long) jg;\n";
+      let error_code, no_ret =
         match fst style with
-        | RErr | RInt _ | RBool _ -> pr "  int r;\n"; "-1"
-        | RInt64 _ -> pr "  int64_t r;\n"; "-1"
-        | RConstString _ | RConstOptString _ ->
-            pr "  const char *r;\n"; "NULL"
-        | RString _ -> pr "  char *r;\n"; "NULL"
-        | RStringList _ | RHashtable _ -> pr "  char **r;\n"; "NULL"
-        | RStruct (_, typ) -> pr "  struct guestfs_%s *r;\n" typ; "NULL"
+        | RErr -> pr "  int r;\n"; "-1", ""
+        | RBool _
+        | RInt _ -> pr "  int r;\n"; "-1", "0"
+        | RInt64 _ -> pr "  int64_t r;\n"; "-1", "0"
+        | RConstString _ -> pr "  const char *r;\n"; "NULL", "NULL"
+        | RConstOptString _ -> pr "  const char *r;\n"; "NULL", "NULL"
+        | RString _ ->
+            pr "  jstring jr;\n";
+            pr "  char *r;\n"; "NULL", "NULL"
+        | RStringList _ ->
+            pr "  jobjectArray jr;\n";
+            pr "  int r_len;\n";
+            pr "  jclass cl;\n";
+            pr "  jstring jstr;\n";
+            pr "  char **r;\n"; "NULL", "NULL"
+        | RStruct (_, typ) ->
+            pr "  jobject jr;\n";
+            pr "  jclass cl;\n";
+            pr "  jfieldID fl;\n";
+            pr "  struct guestfs_%s *r;\n" typ; "NULL", "NULL"
         | RStructList (_, typ) ->
-            pr "  struct guestfs_%s_list *r;\n" typ; "NULL"
+            pr "  jobjectArray jr;\n";
+            pr "  jclass cl;\n";
+            pr "  jfieldID fl;\n";
+            pr "  jobject jfl;\n";
+            pr "  struct guestfs_%s_list *r;\n" typ; "NULL", "NULL"
+        | RHashtable _ -> pr "  char **r;\n"; "NULL", "NULL"
         | RBufferOut _ ->
+            pr "  jstring jr;\n";
             pr "  char *r;\n";
             pr "  size_t size;\n";
-            "NULL" in
+            "NULL", "NULL" in
+      List.iter (
+        function
+        | Pathname n
+        | Device n | Dev_or_Path n
+        | String n
+        | OptString n
+        | FileIn n
+        | FileOut n
+        | Key n ->
+            pr "  const char *%s;\n" n
+        | BufferIn n ->
+            pr "  jbyte *%s;\n" n;
+            pr "  size_t %s_size;\n" n
+        | StringList n | DeviceList n ->
+            pr "  int %s_len;\n" n;
+            pr "  const char **%s;\n" n
+        | Bool n
+        | Int n ->
+            pr "  int %s;\n" n
+        | Int64 n ->
+            pr "  int64_t %s;\n" n
+      ) (snd style);
+
+      let needs_i =
+        (match fst style with
+         | RStringList _ | RStructList _ -> true
+         | RErr | RBool _ | RInt _ | RInt64 _ | RConstString _
+         | RConstOptString _
+         | RString _ | RBufferOut _ | RStruct _ | RHashtable _ -> false) ||
+          List.exists (function
+                       | StringList _ -> true
+                       | DeviceList _ -> true
+                       | _ -> false) (snd style) in
+      if needs_i then
+        pr "  size_t i;\n";
+
       pr "\n";
 
+      (* Get the parameters. *)
+      List.iter (
+        function
+        | Pathname n
+        | Device n | Dev_or_Path n
+        | String n
+        | FileIn n
+        | FileOut n
+        | Key n ->
+            pr "  %s = (*env)->GetStringUTFChars (env, j%s, NULL);\n" n n
+        | OptString n ->
+            (* This is completely undocumented, but Java null becomes
+             * a NULL parameter.
+             *)
+            pr "  %s = j%s ? (*env)->GetStringUTFChars (env, j%s, NULL) : NULL;\n" n n n
+        | BufferIn n ->
+            pr "  %s = (*env)->GetByteArrayElements (env, j%s, NULL);\n" n n;
+            pr "  %s_size = (*env)->GetArrayLength (env, j%s);\n" n n
+        | StringList n | DeviceList n ->
+            pr "  %s_len = (*env)->GetArrayLength (env, j%s);\n" n n;
+            pr "  %s = guestfs_safe_malloc (g, sizeof (char *) * (%s_len+1));\n" n n;
+            pr "  for (i = 0; i < %s_len; ++i) {\n" n;
+            pr "    jobject o = (*env)->GetObjectArrayElement (env, j%s, i);\n"
+              n;
+            pr "    %s[i] = (*env)->GetStringUTFChars (env, o, NULL);\n" n;
+            pr "  }\n";
+            pr "  %s[%s_len] = NULL;\n" n n;
+        | Bool n
+        | Int n
+        | Int64 n ->
+            pr "  %s = j%s;\n" n n
+      ) (snd style);
+
+      (* Make the call. *)
       pr "  r = guestfs_%s " name;
       generate_c_call_args ~handle:"g" style;
       pr ";\n";
 
+      (* Release the parameters. *)
       List.iter (
         function
-        | Pathname _ | Device _ | Dev_or_Path _ | String _
-        | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _ -> ()
+        | Pathname n
+        | Device n | Dev_or_Path n
+        | String n
+        | FileIn n
+        | FileOut n
+        | Key n ->
+            pr "  (*env)->ReleaseStringUTFChars (env, j%s, %s);\n" n n
+        | OptString n ->
+            pr "  if (j%s)\n" n;
+            pr "    (*env)->ReleaseStringUTFChars (env, j%s, %s);\n" n n
+        | BufferIn n ->
+            pr "  (*env)->ReleaseByteArrayElements (env, j%s, %s, 0);\n" n n
         | StringList n | DeviceList n ->
+            pr "  for (i = 0; i < %s_len; ++i) {\n" n;
+            pr "    jobject o = (*env)->GetObjectArrayElement (env, j%s, i);\n"
+              n;
+            pr "    (*env)->ReleaseStringUTFChars (env, o, %s[i]);\n" n;
+            pr "  }\n";
             pr "  free (%s);\n" n
+        | Bool n
+        | Int n
+        | Int64 n -> ()
       ) (snd style);
 
-      pr "  if (r == %s)\n" error_code;
-      pr "    rb_raise (e_Error, \"%%s\", guestfs_last_error (g));\n";
-      pr "\n";
+      (* Check for errors. *)
+      pr "  if (r == %s) {\n" error_code;
+      pr "    throw_exception (env, guestfs_last_error (g));\n";
+      pr "    return %s;\n" no_ret;
+      pr "  }\n";
 
+      (* Return value. *)
       (match fst style with
-       | RErr ->
-           pr "  return Qnil;\n"
-       | RInt _ | RBool _ ->
-           pr "  return INT2NUM (r);\n"
-       | RInt64 _ ->
-           pr "  return ULL2NUM (r);\n"
-       | RConstString _ ->
-           pr "  return rb_str_new2 (r);\n";
+       | RErr -> ()
+       | RInt _ -> pr "  return (jint) r;\n"
+       | RBool _ -> pr "  return (jboolean) r;\n"
+       | RInt64 _ -> pr "  return (jlong) r;\n"
+       | RConstString _ -> pr "  return (*env)->NewStringUTF (env, r);\n"
        | RConstOptString _ ->
-           pr "  if (r)\n";
-           pr "    return rb_str_new2 (r);\n";
-           pr "  else\n";
-           pr "    return Qnil;\n";
+           pr "  return (*env)->NewStringUTF (env, r); /* XXX r NULL? */\n"
        | RString _ ->
-           pr "  VALUE rv = rb_str_new2 (r);\n";
+           pr "  jr = (*env)->NewStringUTF (env, r);\n";
            pr "  free (r);\n";
-           pr "  return rv;\n";
+           pr "  return jr;\n"
        | RStringList _ ->
-           pr "  int i, len = 0;\n";
-           pr "  for (i = 0; r[i] != NULL; ++i) len++;\n";
-           pr "  VALUE rv = rb_ary_new2 (len);\n";
-           pr "  for (i = 0; r[i] != NULL; ++i) {\n";
-           pr "    rb_ary_push (rv, rb_str_new2 (r[i]));\n";
+           pr "  for (r_len = 0; r[r_len] != NULL; ++r_len) ;\n";
+           pr "  cl = (*env)->FindClass (env, \"java/lang/String\");\n";
+           pr "  jstr = (*env)->NewStringUTF (env, \"\");\n";
+           pr "  jr = (*env)->NewObjectArray (env, r_len, cl, jstr);\n";
+           pr "  for (i = 0; i < r_len; ++i) {\n";
+           pr "    jstr = (*env)->NewStringUTF (env, r[i]);\n";
+           pr "    (*env)->SetObjectArrayElement (env, jr, i, jstr);\n";
            pr "    free (r[i]);\n";
            pr "  }\n";
            pr "  free (r);\n";
-           pr "  return rv;\n"
+           pr "  return jr;\n"
        | RStruct (_, typ) ->
+           let jtyp = java_name_of_struct typ in
            let cols = cols_of_struct typ in
-           generate_ruby_struct_code typ cols
+           generate_java_struct_return typ jtyp cols
        | RStructList (_, typ) ->
+           let jtyp = java_name_of_struct typ in
            let cols = cols_of_struct typ in
-           generate_ruby_struct_list_code typ cols
+           generate_java_struct_list_return typ jtyp cols
        | RHashtable _ ->
-           pr "  VALUE rv = rb_hash_new ();\n";
-           pr "  int i;\n";
-           pr "  for (i = 0; r[i] != NULL; i+=2) {\n";
-           pr "    rb_hash_aset (rv, rb_str_new2 (r[i]), rb_str_new2 (r[i+1]));\n";
-           pr "    free (r[i]);\n";
-           pr "    free (r[i+1]);\n";
-           pr "  }\n";
-           pr "  free (r);\n";
-           pr "  return rv;\n"
+           (* XXX *)
+           pr "  throw_exception (env, \"%s: internal error: please let us know how to make a Java HashMap from JNI bindings!\");\n" name;
+           pr "  return NULL;\n"
        | RBufferOut _ ->
-           pr "  VALUE rv = rb_str_new (r, size);\n";
+           pr "  jr = (*env)->NewStringUTF (env, r); /* XXX size */\n";
            pr "  free (r);\n";
-           pr "  return rv;\n";
+           pr "  return jr;\n"
       );
 
       pr "}\n";
       pr "\n"
-  ) all_functions;
-
-  pr "\
-/* Initialize the module. */
-void Init__guestfs ()
-{
-  m_guestfs = rb_define_module (\"Guestfs\");
-  c_guestfs = rb_define_class_under (m_guestfs, \"Guestfs\", rb_cObject);
-  e_Error = rb_define_class_under (m_guestfs, \"Error\", rb_eStandardError);
-
-  rb_define_module_function (m_guestfs, \"create\", ruby_guestfs_create, 0);
-  rb_define_method (c_guestfs, \"close\", ruby_guestfs_close, 0);
-
-";
-  (* Define the rest of the methods. *)
-  List.iter (
-    fun (name, style, _, _, _, _, _) ->
-      pr "  rb_define_method (c_guestfs, \"%s\",\n" name;
-      pr "        ruby_guestfs_%s, %d);\n" name (List.length (snd style))
-  ) all_functions;
-
-  pr "}\n"
-
-(* Ruby code to return a struct. *)
-and generate_ruby_struct_code typ cols =
-  pr "  VALUE rv = rb_hash_new ();\n";
+  ) all_functions
+
+and generate_java_struct_return typ jtyp cols =
+  pr "  cl = (*env)->FindClass (env, \"com/redhat/et/libguestfs/%s\");\n" jtyp;
+  pr "  jr = (*env)->AllocObject (env, cl);\n";
   List.iter (
     function
     | name, FString ->
-        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), rb_str_new2 (r->%s));\n" name name
-    | name, FBuffer ->
-        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), rb_str_new (r->%s, r->%s_len));\n" name name name
+        pr "  fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
+        pr "  (*env)->SetObjectField (env, jr, fl, (*env)->NewStringUTF (env, r->%s));\n" name;
     | name, FUUID ->
-        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), rb_str_new (r->%s, 32));\n" name name
-    | name, (FBytes|FUInt64) ->
-        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), ULL2NUM (r->%s));\n" name name
-    | name, FInt64 ->
-        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), LL2NUM (r->%s));\n" name name
-    | name, FUInt32 ->
-        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), UINT2NUM (r->%s));\n" name name
-    | name, FInt32 ->
-        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), INT2NUM (r->%s));\n" name name
+        pr "  {\n";
+        pr "    char s[33];\n";
+        pr "    memcpy (s, r->%s, 32);\n" name;
+        pr "    s[32] = 0;\n";
+        pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
+        pr "    (*env)->SetObjectField (env, jr, fl, (*env)->NewStringUTF (env, s));\n";
+        pr "  }\n";
+    | name, FBuffer ->
+        pr "  {\n";
+        pr "    int len = r->%s_len;\n" name;
+        pr "    char s[len+1];\n";
+        pr "    memcpy (s, r->%s, len);\n" name;
+        pr "    s[len] = 0;\n";
+        pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
+        pr "    (*env)->SetObjectField (env, jr, fl, (*env)->NewStringUTF (env, s));\n";
+        pr "  }\n";
+    | name, (FBytes|FUInt64|FInt64) ->
+        pr "  fl = (*env)->GetFieldID (env, cl, \"%s\", \"J\");\n" name;
+        pr "  (*env)->SetLongField (env, jr, fl, r->%s);\n" name;
+    | name, (FUInt32|FInt32) ->
+        pr "  fl = (*env)->GetFieldID (env, cl, \"%s\", \"I\");\n" name;
+        pr "  (*env)->SetLongField (env, jr, fl, r->%s);\n" name;
     | name, FOptPercent ->
-        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), rb_dbl2big (r->%s));\n" name name
-    | name, FChar -> (* XXX wrong? *)
-        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), ULL2NUM (r->%s));\n" name name
+        pr "  fl = (*env)->GetFieldID (env, cl, \"%s\", \"F\");\n" name;
+        pr "  (*env)->SetFloatField (env, jr, fl, r->%s);\n" name;
+    | name, FChar ->
+        pr "  fl = (*env)->GetFieldID (env, cl, \"%s\", \"C\");\n" name;
+        pr "  (*env)->SetLongField (env, jr, fl, r->%s);\n" name;
   ) cols;
-  pr "  guestfs_free_%s (r);\n" typ;
-  pr "  return rv;\n"
+  pr "  free (r);\n";
+  pr "  return jr;\n"
 
-(* Ruby code to return a struct list. *)
-and generate_ruby_struct_list_code typ cols =
-  pr "  VALUE rv = rb_ary_new2 (r->len);\n";
-  pr "  int i;\n";
+and generate_java_struct_list_return typ jtyp cols =
+  pr "  cl = (*env)->FindClass (env, \"com/redhat/et/libguestfs/%s\");\n" jtyp;
+  pr "  jr = (*env)->NewObjectArray (env, r->len, cl, NULL);\n";
   pr "  for (i = 0; i < r->len; ++i) {\n";
-  pr "    VALUE hv = rb_hash_new ();\n";
+  pr "    jfl = (*env)->AllocObject (env, cl);\n";
   List.iter (
     function
     | name, FString ->
-        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), rb_str_new2 (r->val[i].%s));\n" name name
-    | name, FBuffer ->
-        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), rb_str_new (r->val[i].%s, r->val[i].%s_len));\n" name name name
+        pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
+        pr "    (*env)->SetObjectField (env, jfl, fl, (*env)->NewStringUTF (env, r->val[i].%s));\n" name;
     | name, FUUID ->
-        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), rb_str_new (r->val[i].%s, 32));\n" name name
-    | name, (FBytes|FUInt64) ->
-        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), ULL2NUM (r->val[i].%s));\n" name name
-    | name, FInt64 ->
-        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), LL2NUM (r->val[i].%s));\n" name name
-    | name, FUInt32 ->
-        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), UINT2NUM (r->val[i].%s));\n" name name
-    | name, FInt32 ->
-        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), INT2NUM (r->val[i].%s));\n" name name
+        pr "    {\n";
+        pr "      char s[33];\n";
+        pr "      memcpy (s, r->val[i].%s, 32);\n" name;
+        pr "      s[32] = 0;\n";
+        pr "      fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
+        pr "      (*env)->SetObjectField (env, jfl, fl, (*env)->NewStringUTF (env, s));\n";
+        pr "    }\n";
+    | name, FBuffer ->
+        pr "    {\n";
+        pr "      int len = r->val[i].%s_len;\n" name;
+        pr "      char s[len+1];\n";
+        pr "      memcpy (s, r->val[i].%s, len);\n" name;
+        pr "      s[len] = 0;\n";
+        pr "      fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
+        pr "      (*env)->SetObjectField (env, jfl, fl, (*env)->NewStringUTF (env, s));\n";
+        pr "    }\n";
+    | name, (FBytes|FUInt64|FInt64) ->
+        pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"J\");\n" name;
+        pr "    (*env)->SetLongField (env, jfl, fl, r->val[i].%s);\n" name;
+    | name, (FUInt32|FInt32) ->
+        pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"I\");\n" name;
+        pr "    (*env)->SetLongField (env, jfl, fl, r->val[i].%s);\n" name;
     | name, FOptPercent ->
-        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), rb_dbl2big (r->val[i].%s));\n" name name
-    | name, FChar -> (* XXX wrong? *)
-        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), ULL2NUM (r->val[i].%s));\n" name name
+        pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"F\");\n" name;
+        pr "    (*env)->SetFloatField (env, jfl, fl, r->val[i].%s);\n" name;
+    | name, FChar ->
+        pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"C\");\n" name;
+        pr "    (*env)->SetLongField (env, jfl, fl, r->val[i].%s);\n" name;
   ) cols;
-  pr "    rb_ary_push (rv, hv);\n";
+  pr "    (*env)->SetObjectArrayElement (env, jfl, i, jfl);\n";
   pr "  }\n";
   pr "  guestfs_free_%s_list (r);\n" typ;
-  pr "  return rv;\n"
-
-(* Generate Java bindings GuestFS.java file. *)
-and generate_java_java () =
-  generate_header CStyle LGPLv2;
-
-  pr "\
-package com.redhat.et.libguestfs;
-
-import java.util.HashMap;
-import com.redhat.et.libguestfs.LibGuestFSException;
-import com.redhat.et.libguestfs.PV;
-import com.redhat.et.libguestfs.VG;
-import com.redhat.et.libguestfs.LV;
-import com.redhat.et.libguestfs.Stat;
-import com.redhat.et.libguestfs.StatVFS;
-import com.redhat.et.libguestfs.IntBool;
-import com.redhat.et.libguestfs.Dirent;
-
-/**
- * The GuestFS object is a libguestfs handle.
- *
- * @author rjones
- */
-public class GuestFS {
-  // Load the native code.
-  static {
-    System.loadLibrary (\"guestfs_jni\");
-  }
-
-  /**
-   * The native guestfs_h pointer.
-   */
-  long g;
-
-  /**
-   * Create a libguestfs handle.
-   *
-   * @throws LibGuestFSException
-   */
-  public GuestFS () throws LibGuestFSException
-  {
-    g = _create ();
-  }
-  private native long _create () throws LibGuestFSException;
-
-  /**
-   * Close a libguestfs handle.
-   *
-   * You can also leave handles to be collected by the garbage
-   * collector, but this method ensures that the resources used
-   * by the handle are freed up immediately.  If you call any
-   * other methods after closing the handle, you will get an
-   * exception.
-   *
-   * @throws LibGuestFSException
-   */
-  public void close () throws LibGuestFSException
-  {
-    if (g != 0)
-      _close (g);
-    g = 0;
-  }
-  private native void _close (long g) throws LibGuestFSException;
-
-  public void finalize () throws LibGuestFSException
-  {
-    close ();
-  }
+  pr "  return jr;\n"
 
-";
+and generate_java_makefile_inc () =
+  generate_header HashStyle GPLv2plus;
 
+  pr "java_built_sources = \\\n";
   List.iter (
-    fun (name, style, _, flags, _, shortdesc, longdesc) ->
-      if not (List.mem NotInDocs flags); then (
-        let doc = replace_str longdesc "C<guestfs_" "C<g." in
-        let doc =
-          if List.mem ProtocolLimitWarning flags then
-            doc ^ "\n\n" ^ protocol_limit_warning
-          else doc in
-        let doc =
-          if List.mem DangerWillRobinson flags then
-            doc ^ "\n\n" ^ danger_will_robinson
-          else doc in
-        let doc =
-          match deprecation_notice flags with
-          | None -> doc
-          | Some txt -> doc ^ "\n\n" ^ txt in
-        let doc = pod2text ~width:60 name doc in
-        let doc = List.map (           (* RHBZ#501883 *)
-          function
-          | "" -> "<p>"
-          | nonempty -> nonempty
-        ) doc in
-        let doc = String.concat "\n   * " doc in
-
-        pr "  /**\n";
-        pr "   * %s\n" shortdesc;
-        pr "   * <p>\n";
-        pr "   * %s\n" doc;
-        pr "   * @throws LibGuestFSException\n";
-        pr "   */\n";
-        pr "  ";
-      );
-      generate_java_prototype ~public:true ~semicolon:false name style;
-      pr "\n";
-      pr "  {\n";
-      pr "    if (g == 0)\n";
-      pr "      throw new LibGuestFSException (\"%s: handle is closed\");\n"
-        name;
-      pr "    ";
-      if fst style <> RErr then pr "return ";
-      pr "_%s " name;
-      generate_java_call_args ~handle:"g" (snd style);
-      pr ";\n";
-      pr "  }\n";
-      pr "  ";
-      generate_java_prototype ~privat:true ~native:true name style;
-      pr "\n";
-      pr "\n";
-  ) all_functions;
-
-  pr "}\n"
+    fun (typ, jtyp) ->
+        pr "\tcom/redhat/et/libguestfs/%s.java \\\n" jtyp;
+  ) java_structs;
+  pr "\tcom/redhat/et/libguestfs/GuestFS.java\n"
 
-(* Generate Java call arguments, eg "(handle, foo, bar)" *)
-and generate_java_call_args ~handle args =
-  pr "(%s" handle;
-  List.iter (fun arg -> pr ", %s" (name_of_argt arg)) args;
-  pr ")"
+and generate_haskell_hs () =
+  generate_header HaskellStyle LGPLv2plus;
 
-and generate_java_prototype ?(public=false) ?(privat=false) ?(native=false)
-    ?(semicolon=true) name style =
-  if privat then pr "private ";
-  if public then pr "public ";
-  if native then pr "native ";
+  (* XXX We only know how to generate partial FFI for Haskell
+   * at the moment.  Please help out!
+   *)
+  let can_generate style =
+    match style with
+    | RErr, _
+    | RInt _, _
+    | RInt64 _, _ -> true
+    | RBool _, _
+    | RConstString _, _
+    | RConstOptString _, _
+    | RString _, _
+    | RStringList _, _
+    | RStruct _, _
+    | RStructList _, _
+    | RHashtable _, _
+    | RBufferOut _, _ -> false in
 
-  (* return type *)
-  (match fst style with
-   | RErr -> pr "void ";
-   | RInt _ -> pr "int ";
-   | RInt64 _ -> pr "long ";
-   | RBool _ -> pr "boolean ";
-   | RConstString _ | RConstOptString _ | RString _
-   | RBufferOut _ -> pr "String ";
-   | RStringList _ -> pr "String[] ";
-   | RStruct (_, typ) ->
-       let name = java_name_of_struct typ in
-       pr "%s " name;
-   | RStructList (_, typ) ->
-       let name = java_name_of_struct typ in
-       pr "%s[] " name;
-   | RHashtable _ -> pr "HashMap<String,String> ";
-  );
+  pr "\
+{-# INCLUDE <guestfs.h> #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
 
-  if native then pr "_%s " name else pr "%s " name;
-  pr "(";
-  let needs_comma = ref false in
-  if native then (
-    pr "long g";
-    needs_comma := true
-  );
+module Guestfs (
+  create";
 
-  (* args *)
+  (* List out the names of the actions we want to export. *)
   List.iter (
-    fun arg ->
-      if !needs_comma then pr ", ";
-      needs_comma := true;
+    fun (name, style, _, _, _, _, _) ->
+      if can_generate style then pr ",\n  %s" name
+  ) all_functions;
 
-      match arg with
-      | Pathname n
-      | Device n | Dev_or_Path n
-      | String n
-      | OptString n
-      | FileIn n
-      | FileOut n ->
-          pr "String %s" n
-      | StringList n | DeviceList n ->
-          pr "String[] %s" n
-      | Bool n ->
-          pr "boolean %s" n
-      | Int n ->
-          pr "int %s" n
-      | Int64 n ->
-          pr "long %s" n
-  ) (snd style);
+  pr "
+  ) where
 
-  pr ")\n";
-  pr "    throws LibGuestFSException";
-  if semicolon then pr ";"
+-- Unfortunately some symbols duplicate ones already present
+-- in Prelude.  We don't know which, so we hard-code a list
+-- here.
+import Prelude hiding (truncate)
 
-and generate_java_struct jtyp cols =
-  generate_header CStyle LGPLv2;
+import Foreign
+import Foreign.C
+import Foreign.C.Types
+import IO
+import Control.Exception
+import Data.Typeable
 
-  pr "\
-package com.redhat.et.libguestfs;
+data GuestfsS = GuestfsS            -- represents the opaque C struct
+type GuestfsP = Ptr GuestfsS        -- guestfs_h *
+type GuestfsH = ForeignPtr GuestfsS -- guestfs_h * with attached finalizer
 
-/**
- * Libguestfs %s structure.
- *
- * @author rjones
- * @see GuestFS
- */
-public class %s {
-" jtyp jtyp;
+-- XXX define properly later XXX
+data PV = PV
+data VG = VG
+data LV = LV
+data IntBool = IntBool
+data Stat = Stat
+data StatVFS = StatVFS
+data Hashtable = Hashtable
 
-  List.iter (
-    function
-    | name, FString
-    | name, FUUID
-    | name, FBuffer -> pr "  public String %s;\n" name
-    | name, (FBytes|FUInt64|FInt64) -> pr "  public long %s;\n" name
-    | name, (FUInt32|FInt32) -> pr "  public int %s;\n" name
-    | name, FChar -> pr "  public char %s;\n" name
-    | name, FOptPercent ->
-        pr "  /* The next field is [0..100] or -1 meaning 'not present': */\n";
-        pr "  public float %s;\n" name
-  ) cols;
+foreign import ccall unsafe \"guestfs_create\" c_create
+  :: IO GuestfsP
+foreign import ccall unsafe \"&guestfs_close\" c_close
+  :: FunPtr (GuestfsP -> IO ())
+foreign import ccall unsafe \"guestfs_set_error_handler\" c_set_error_handler
+  :: GuestfsP -> Ptr CInt -> Ptr CInt -> IO ()
 
-  pr "}\n"
+create :: IO GuestfsH
+create = do
+  p <- c_create
+  c_set_error_handler p nullPtr nullPtr
+  h <- newForeignPtr c_close p
+  return h
 
-and generate_java_c () =
-  generate_header CStyle LGPLv2;
+foreign import ccall unsafe \"guestfs_last_error\" c_last_error
+  :: GuestfsP -> IO CString
 
-  pr "\
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
+-- last_error :: GuestfsH -> IO (Maybe String)
+-- last_error h = do
+--   str <- withForeignPtr h (\\p -> c_last_error p)
+--   maybePeek peekCString str
 
-#include \"com_redhat_et_libguestfs_GuestFS.h\"
-#include \"guestfs.h\"
+last_error :: GuestfsH -> IO (String)
+last_error h = do
+  str <- withForeignPtr h (\\p -> c_last_error p)
+  if (str == nullPtr)
+    then return \"no error\"
+    else peekCString str
 
-/* Note that this function returns.  The exception is not thrown
- * until after the wrapper function returns.
- */
-static void
-throw_exception (JNIEnv *env, const char *msg)
-{
-  jclass cl;
-  cl = (*env)->FindClass (env,
-                          \"com/redhat/et/libguestfs/LibGuestFSException\");
-  (*env)->ThrowNew (env, cl, msg);
-}
+";
+
+  (* Generate wrappers for each foreign function. *)
+  List.iter (
+    fun (name, style, _, _, _, _, _) ->
+      if can_generate style then (
+        pr "foreign import ccall unsafe \"guestfs_%s\" c_%s\n" name name;
+        pr "  :: ";
+        generate_haskell_prototype ~handle:"GuestfsP" style;
+        pr "\n";
+        pr "\n";
+        pr "%s :: " name;
+        generate_haskell_prototype ~handle:"GuestfsH" ~hs:true style;
+        pr "\n";
+        pr "%s %s = do\n" name
+          (String.concat " " ("h" :: List.map name_of_argt (snd style)));
+        pr "  r <- ";
+        (* Convert pointer arguments using with* functions. *)
+        List.iter (
+          function
+          | FileIn n
+          | FileOut n
+          | Pathname n | Device n | Dev_or_Path n | String n | Key n ->
+              pr "withCString %s $ \\%s -> " n n
+          | BufferIn n ->
+              pr "withCStringLen %s $ \\(%s, %s_size) -> " n n n
+          | OptString n -> pr "maybeWith withCString %s $ \\%s -> " n n
+          | StringList n | DeviceList n -> pr "withMany withCString %s $ \\%s -> withArray0 nullPtr %s $ \\%s -> " n n n n
+          | Bool _ | Int _ | Int64 _ -> ()
+        ) (snd style);
+        (* Convert integer arguments. *)
+        let args =
+          List.map (
+            function
+            | Bool n -> sprintf "(fromBool %s)" n
+            | Int n -> sprintf "(fromIntegral %s)" n
+            | Int64 n -> sprintf "(fromIntegral %s)" n
+            | FileIn n | FileOut n
+            | Pathname n | Device n | Dev_or_Path n
+            | String n | OptString n
+            | StringList n | DeviceList n
+            | Key n -> n
+            | BufferIn n -> sprintf "%s (fromIntegral %s_size)" n n
+          ) (snd style) in
+        pr "withForeignPtr h (\\p -> c_%s %s)\n" name
+          (String.concat " " ("p" :: args));
+        (match fst style with
+         | RErr | RInt _ | RInt64 _ | RBool _ ->
+             pr "  if (r == -1)\n";
+             pr "    then do\n";
+             pr "      err <- last_error h\n";
+             pr "      fail err\n";
+         | RConstString _ | RConstOptString _ | RString _
+         | RStringList _ | RStruct _
+         | RStructList _ | RHashtable _ | RBufferOut _ ->
+             pr "  if (r == nullPtr)\n";
+             pr "    then do\n";
+             pr "      err <- last_error h\n";
+             pr "      fail err\n";
+        );
+        (match fst style with
+         | RErr ->
+             pr "    else return ()\n"
+         | RInt _ ->
+             pr "    else return (fromIntegral r)\n"
+         | RInt64 _ ->
+             pr "    else return (fromIntegral r)\n"
+         | RBool _ ->
+             pr "    else return (toBool r)\n"
+         | RConstString _
+         | RConstOptString _
+         | RString _
+         | RStringList _
+         | RStruct _
+         | RStructList _
+         | RHashtable _
+         | RBufferOut _ ->
+             pr "    else return ()\n" (* XXXXXXXXXXXXXXXXXXXX *)
+        );
+        pr "\n";
+      )
+  ) all_functions
+
+and generate_haskell_prototype ~handle ?(hs = false) style =
+  pr "%s -> " handle;
+  let string = if hs then "String" else "CString" in
+  let int = if hs then "Int" else "CInt" in
+  let bool = if hs then "Bool" else "CInt" in
+  let int64 = if hs then "Integer" else "Int64" in
+  List.iter (
+    fun arg ->
+      (match arg with
+       | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _ ->
+           pr "%s" string
+       | BufferIn _ ->
+           if hs then pr "String"
+           else pr "CString -> CInt"
+       | OptString _ -> if hs then pr "Maybe String" else pr "CString"
+       | StringList _ | DeviceList _ -> if hs then pr "[String]" else pr "Ptr CString"
+       | Bool _ -> pr "%s" bool
+       | Int _ -> pr "%s" int
+       | Int64 _ -> pr "%s" int
+       | FileIn _ -> pr "%s" string
+       | FileOut _ -> pr "%s" string
+      );
+      pr " -> ";
+  ) (snd style);
+  pr "IO (";
+  (match fst style with
+   | RErr -> if not hs then pr "CInt"
+   | RInt _ -> pr "%s" int
+   | RInt64 _ -> pr "%s" int64
+   | RBool _ -> pr "%s" bool
+   | RConstString _ -> pr "%s" string
+   | RConstOptString _ -> pr "Maybe %s" string
+   | RString _ -> pr "%s" string
+   | RStringList _ -> pr "[%s]" string
+   | RStruct (_, typ) ->
+       let name = java_name_of_struct typ in
+       pr "%s" name
+   | RStructList (_, typ) ->
+       let name = java_name_of_struct typ in
+       pr "[%s]" name
+   | RHashtable _ -> pr "Hashtable"
+   | RBufferOut _ -> pr "%s" string
+  );
+  pr ")"
 
-JNIEXPORT jlong JNICALL
-Java_com_redhat_et_libguestfs_GuestFS__1create
-  (JNIEnv *env, jobject obj)
-{
-  guestfs_h *g;
+and generate_csharp () =
+  generate_header CPlusPlusStyle LGPLv2plus;
 
-  g = guestfs_create ();
-  if (g == NULL) {
-    throw_exception (env, \"GuestFS.create: failed to allocate handle\");
-    return 0;
-  }
-  guestfs_set_error_handler (g, NULL, NULL);
-  return (jlong) (long) g;
-}
+  (* XXX Make this configurable by the C# assembly users. *)
+  let library = "libguestfs.so.0" in
 
-JNIEXPORT void JNICALL
-Java_com_redhat_et_libguestfs_GuestFS__1close
-  (JNIEnv *env, jobject obj, jlong jg)
+  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
 {
-  guestfs_h *g = (guestfs_h *) (long) jg;
-  guestfs_close (g);
-}
+  class Error : System.ApplicationException
+  {
+    public Error (string message) : base (message) {}
+    protected Error (SerializationInfo info, StreamingContext context) {}
+  }
 
-";
+  class Guestfs
+  {
+    IntPtr _handle;
 
-  List.iter (
-    fun (name, style, _, _, _, _, _) ->
-      pr "JNIEXPORT ";
-      (match fst style with
-       | RErr -> pr "void ";
-       | RInt _ -> pr "jint ";
-       | RInt64 _ -> pr "jlong ";
-       | RBool _ -> pr "jboolean ";
-       | RConstString _ | RConstOptString _ | RString _
-       | RBufferOut _ -> pr "jstring ";
-       | RStruct _ | RHashtable _ ->
-           pr "jobject ";
-       | RStringList _ | RStructList _ ->
-           pr "jobjectArray ";
-      );
-      pr "JNICALL\n";
-      pr "Java_com_redhat_et_libguestfs_GuestFS_";
-      pr "%s" (replace_str ("_" ^ name) "_" "_1");
-      pr "\n";
-      pr "  (JNIEnv *env, jobject obj, jlong jg";
-      List.iter (
-        function
-        | Pathname n
-        | Device n | Dev_or_Path n
-        | String n
-        | OptString n
-        | FileIn n
-        | FileOut n ->
-            pr ", jstring j%s" n
-        | StringList n | DeviceList n ->
-            pr ", jobjectArray j%s" n
-        | Bool n ->
-            pr ", jboolean j%s" n
-        | Int n ->
-            pr ", jint j%s" n
-        | Int64 n ->
-            pr ", jlong j%s" n
-      ) (snd style);
-      pr ")\n";
-      pr "{\n";
-      pr "  guestfs_h *g = (guestfs_h *) (long) jg;\n";
-      let error_code, no_ret =
-        match fst style with
-        | RErr -> pr "  int r;\n"; "-1", ""
-        | RBool _
-        | RInt _ -> pr "  int r;\n"; "-1", "0"
-        | RInt64 _ -> pr "  int64_t r;\n"; "-1", "0"
-        | RConstString _ -> pr "  const char *r;\n"; "NULL", "NULL"
-        | RConstOptString _ -> pr "  const char *r;\n"; "NULL", "NULL"
-        | RString _ ->
-            pr "  jstring jr;\n";
-            pr "  char *r;\n"; "NULL", "NULL"
-        | RStringList _ ->
-            pr "  jobjectArray jr;\n";
-            pr "  int r_len;\n";
-            pr "  jclass cl;\n";
-            pr "  jstring jstr;\n";
-            pr "  char **r;\n"; "NULL", "NULL"
-        | RStruct (_, typ) ->
-            pr "  jobject jr;\n";
-            pr "  jclass cl;\n";
-            pr "  jfieldID fl;\n";
-            pr "  struct guestfs_%s *r;\n" typ; "NULL", "NULL"
-        | RStructList (_, typ) ->
-            pr "  jobjectArray jr;\n";
-            pr "  jclass cl;\n";
-            pr "  jfieldID fl;\n";
-            pr "  jobject jfl;\n";
-            pr "  struct guestfs_%s_list *r;\n" typ; "NULL", "NULL"
-        | RHashtable _ -> pr "  char **r;\n"; "NULL", "NULL"
-        | RBufferOut _ ->
-            pr "  jstring jr;\n";
-            pr "  char *r;\n";
-            pr "  size_t size;\n";
-            "NULL", "NULL" in
-      List.iter (
-        function
-        | Pathname n
-        | Device n | Dev_or_Path n
-        | String n
-        | OptString n
-        | FileIn n
-        | FileOut n ->
-            pr "  const char *%s;\n" n
-        | StringList n | DeviceList n ->
-            pr "  int %s_len;\n" n;
-            pr "  const char **%s;\n" n
-        | Bool n
-        | Int n ->
-            pr "  int %s;\n" n
-        | Int64 n ->
-            pr "  int64_t %s;\n" n
-      ) (snd style);
+    [DllImport (\"%s\")]
+    static extern IntPtr guestfs_create ();
 
-      let needs_i =
-        (match fst style with
-         | RStringList _ | RStructList _ -> true
-         | RErr | RBool _ | RInt _ | RInt64 _ | RConstString _
-         | RConstOptString _
-         | RString _ | RBufferOut _ | RStruct _ | RHashtable _ -> false) ||
-          List.exists (function
-                       | StringList _ -> true
-                       | DeviceList _ -> true
-                       | _ -> false) (snd style) in
-      if needs_i then
-        pr "  int i;\n";
+    public Guestfs ()
+    {
+      _handle = guestfs_create ();
+      if (_handle == IntPtr.Zero)
+        throw new Error (\"could not create guestfs handle\");
+    }
 
-      pr "\n";
+    [DllImport (\"%s\")]
+    static extern void guestfs_close (IntPtr h);
 
-      (* Get the parameters. *)
-      List.iter (
-        function
-        | Pathname n
-        | Device n | Dev_or_Path n
-        | String n
-        | FileIn n
-        | FileOut n ->
-            pr "  %s = (*env)->GetStringUTFChars (env, j%s, NULL);\n" n n
-        | OptString n ->
-            (* This is completely undocumented, but Java null becomes
-             * a NULL parameter.
-             *)
-            pr "  %s = j%s ? (*env)->GetStringUTFChars (env, j%s, NULL) : NULL;\n" n n n
-        | StringList n | DeviceList n ->
-            pr "  %s_len = (*env)->GetArrayLength (env, j%s);\n" n n;
-            pr "  %s = guestfs_safe_malloc (g, sizeof (char *) * (%s_len+1));\n" n n;
-            pr "  for (i = 0; i < %s_len; ++i) {\n" n;
-            pr "    jobject o = (*env)->GetObjectArrayElement (env, j%s, i);\n"
-              n;
-            pr "    %s[i] = (*env)->GetStringUTFChars (env, o, NULL);\n" n;
-            pr "  }\n";
-            pr "  %s[%s_len] = NULL;\n" n n;
-        | Bool n
-        | Int n
-        | Int64 n ->
-            pr "  %s = j%s;\n" n n
-      ) (snd style);
+    ~Guestfs ()
+    {
+      guestfs_close (_handle);
+    }
 
-      (* Make the call. *)
-      pr "  r = guestfs_%s " name;
-      generate_c_call_args ~handle:"g" style;
-      pr ";\n";
+    [DllImport (\"%s\")]
+    static extern string guestfs_last_error (IntPtr h);
 
-      (* Release the parameters. *)
+" 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
-        | Pathname n
-        | Device n | Dev_or_Path n
-        | String n
-        | FileIn n
-        | FileOut n ->
-            pr "  (*env)->ReleaseStringUTFChars (env, j%s, %s);\n" n n
-        | OptString n ->
-            pr "  if (j%s)\n" n;
-            pr "    (*env)->ReleaseStringUTFChars (env, j%s, %s);\n" n n
-        | StringList n | DeviceList n ->
-            pr "  for (i = 0; i < %s_len; ++i) {\n" n;
-            pr "    jobject o = (*env)->GetObjectArrayElement (env, j%s, i);\n"
-              n;
-            pr "    (*env)->ReleaseStringUTFChars (env, o, %s[i]);\n" n;
-            pr "  }\n";
-            pr "  free (%s);\n" n
-        | Bool n
-        | Int n
-        | Int64 n -> ()
-      ) (snd style);
+        | 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;
 
-      (* Check for errors. *)
-      pr "  if (r == %s) {\n" error_code;
-      pr "    throw_exception (env, guestfs_last_error (g));\n";
-      pr "    return %s;\n" no_ret;
-      pr "  }\n";
+  (* 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
+          | Key n
+          | BufferIn 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
+          | Key n
+          | BufferIn 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"
 
-      (* Return value. *)
+      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 -> ()
-       | RInt _ -> pr "  return (jint) r;\n"
-       | RBool _ -> pr "  return (jboolean) r;\n"
-       | RInt64 _ -> pr "  return (jlong) r;\n"
-       | RConstString _ -> pr "  return (*env)->NewStringUTF (env, r);\n"
-       | RConstOptString _ ->
-           pr "  return (*env)->NewStringUTF (env, r); /* XXX r NULL? */\n"
-       | RString _ ->
-           pr "  jr = (*env)->NewStringUTF (env, r);\n";
-           pr "  free (r);\n";
-           pr "  return jr;\n"
-       | RStringList _ ->
-           pr "  for (r_len = 0; r[r_len] != NULL; ++r_len) ;\n";
-           pr "  cl = (*env)->FindClass (env, \"java/lang/String\");\n";
-           pr "  jstr = (*env)->NewStringUTF (env, \"\");\n";
-           pr "  jr = (*env)->NewObjectArray (env, r_len, cl, jstr);\n";
-           pr "  for (i = 0; i < r_len; ++i) {\n";
-           pr "    jstr = (*env)->NewStringUTF (env, r[i]);\n";
-           pr "    (*env)->SetObjectArrayElement (env, jr, i, jstr);\n";
-           pr "    free (r[i]);\n";
-           pr "  }\n";
-           pr "  free (r);\n";
-           pr "  return jr;\n"
-       | RStruct (_, typ) ->
-           let jtyp = java_name_of_struct typ in
-           let cols = cols_of_struct typ in
-           generate_java_struct_return typ jtyp cols
-       | RStructList (_, typ) ->
-           let jtyp = java_name_of_struct typ in
-           let cols = cols_of_struct typ in
-           generate_java_struct_list_return typ jtyp cols
+       | RErr -> ()
+       | RBool _ ->
+           pr "      return r != 0 ? true : false;\n"
        | RHashtable _ ->
-           (* XXX *)
-           pr "  throw_exception (env, \"%s: internal error: please let us know how to make a Java HashMap from JNI bindings!\");\n" name;
-           pr "  return NULL;\n"
-       | RBufferOut _ ->
-           pr "  jr = (*env)->NewStringUTF (env, r); /* XXX size */\n";
-           pr "  free (r);\n";
-           pr "  return jr;\n"
+           pr "      Hashtable rr = new Hashtable ();\n";
+           pr "      for (size_t i = 0; i < r.Length; i += 2)\n";
+           pr "        rr.Add (r[i], r[i+1]);\n";
+           pr "      return rr;\n"
+       | RInt _ | RInt64 _ | RConstString _ | RConstOptString _
+       | RString _ | RBufferOut _ | RStruct _ | RStringList _
+       | RStructList _ ->
+           pr "      return r;\n"
       );
+      pr "    }\n";
+      pr "\n";
+  ) all_functions_sorted;
 
-      pr "}\n";
-      pr "\n"
-  ) all_functions
+  pr "  }
+}
+"
 
-and generate_java_struct_return typ jtyp cols =
-  pr "  cl = (*env)->FindClass (env, \"com/redhat/et/libguestfs/%s\");\n" jtyp;
-  pr "  jr = (*env)->AllocObject (env, cl);\n";
-  List.iter (
-    function
-    | name, FString ->
-        pr "  fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
-        pr "  (*env)->SetObjectField (env, jr, fl, (*env)->NewStringUTF (env, r->%s));\n" name;
-    | name, FUUID ->
-        pr "  {\n";
-        pr "    char s[33];\n";
-        pr "    memcpy (s, r->%s, 32);\n" name;
-        pr "    s[32] = 0;\n";
-        pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
-        pr "    (*env)->SetObjectField (env, jr, fl, (*env)->NewStringUTF (env, s));\n";
-        pr "  }\n";
-    | name, FBuffer ->
-        pr "  {\n";
-        pr "    int len = r->%s_len;\n" name;
-        pr "    char s[len+1];\n";
-        pr "    memcpy (s, r->%s, len);\n" name;
-        pr "    s[len] = 0;\n";
-        pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
-        pr "    (*env)->SetObjectField (env, jr, fl, (*env)->NewStringUTF (env, s));\n";
-        pr "  }\n";
-    | name, (FBytes|FUInt64|FInt64) ->
-        pr "  fl = (*env)->GetFieldID (env, cl, \"%s\", \"J\");\n" name;
-        pr "  (*env)->SetLongField (env, jr, fl, r->%s);\n" name;
-    | name, (FUInt32|FInt32) ->
-        pr "  fl = (*env)->GetFieldID (env, cl, \"%s\", \"I\");\n" name;
-        pr "  (*env)->SetLongField (env, jr, fl, r->%s);\n" name;
-    | name, FOptPercent ->
-        pr "  fl = (*env)->GetFieldID (env, cl, \"%s\", \"F\");\n" name;
-        pr "  (*env)->SetFloatField (env, jr, fl, r->%s);\n" name;
-    | name, FChar ->
-        pr "  fl = (*env)->GetFieldID (env, cl, \"%s\", \"C\");\n" name;
-        pr "  (*env)->SetLongField (env, jr, fl, r->%s);\n" name;
-  ) cols;
-  pr "  free (r);\n";
-  pr "  return jr;\n"
+and generate_php_h () =
+  generate_header CStyle LGPLv2plus;
 
-and generate_java_struct_list_return typ jtyp cols =
-  pr "  cl = (*env)->FindClass (env, \"com/redhat/et/libguestfs/%s\");\n" jtyp;
-  pr "  jr = (*env)->NewObjectArray (env, r->len, cl, NULL);\n";
-  pr "  for (i = 0; i < r->len; ++i) {\n";
-  pr "    jfl = (*env)->AllocObject (env, cl);\n";
-  List.iter (
-    function
-    | name, FString ->
-        pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
-        pr "    (*env)->SetObjectField (env, jfl, fl, (*env)->NewStringUTF (env, r->val[i].%s));\n" name;
-    | name, FUUID ->
-        pr "    {\n";
-        pr "      char s[33];\n";
-        pr "      memcpy (s, r->val[i].%s, 32);\n" name;
-        pr "      s[32] = 0;\n";
-        pr "      fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
-        pr "      (*env)->SetObjectField (env, jfl, fl, (*env)->NewStringUTF (env, s));\n";
-        pr "    }\n";
-    | name, FBuffer ->
-        pr "    {\n";
-        pr "      int len = r->val[i].%s_len;\n" name;
-        pr "      char s[len+1];\n";
-        pr "      memcpy (s, r->val[i].%s, len);\n" name;
-        pr "      s[len] = 0;\n";
-        pr "      fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
-        pr "      (*env)->SetObjectField (env, jfl, fl, (*env)->NewStringUTF (env, s));\n";
-        pr "    }\n";
-    | name, (FBytes|FUInt64|FInt64) ->
-        pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"J\");\n" name;
-        pr "    (*env)->SetLongField (env, jfl, fl, r->val[i].%s);\n" name;
-    | name, (FUInt32|FInt32) ->
-        pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"I\");\n" name;
-        pr "    (*env)->SetLongField (env, jfl, fl, r->val[i].%s);\n" name;
-    | name, FOptPercent ->
-        pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"F\");\n" name;
-        pr "    (*env)->SetFloatField (env, jfl, fl, r->val[i].%s);\n" name;
-    | name, FChar ->
-        pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"C\");\n" name;
-        pr "    (*env)->SetLongField (env, jfl, fl, r->val[i].%s);\n" name;
-  ) cols;
-  pr "    (*env)->SetObjectArrayElement (env, jfl, i, jfl);\n";
-  pr "  }\n";
-  pr "  guestfs_free_%s_list (r);\n" typ;
-  pr "  return jr;\n"
+  pr "\
+#ifndef PHP_GUESTFS_PHP_H
+#define PHP_GUESTFS_PHP_H 1
 
-and generate_java_makefile_inc () =
-  generate_header HashStyle GPLv2;
+#ifdef ZTS
+#include \"TSRM.h\"
+#endif
+
+#define PHP_GUESTFS_PHP_EXTNAME \"guestfs_php\"
+#define PHP_GUESTFS_PHP_VERSION \"1.0\"
+
+PHP_MINIT_FUNCTION (guestfs_php);
+
+#define PHP_GUESTFS_HANDLE_RES_NAME \"guestfs_h\"
+
+PHP_FUNCTION (guestfs_create);
+PHP_FUNCTION (guestfs_last_error);
+";
 
-  pr "java_built_sources = \\\n";
   List.iter (
-    fun (typ, jtyp) ->
-        pr "\tcom/redhat/et/libguestfs/%s.java \\\n" jtyp;
-  ) java_structs;
-  pr "\tcom/redhat/et/libguestfs/GuestFS.java\n"
+    fun (shortname, style, _, _, _, _, _) ->
+      pr "PHP_FUNCTION (guestfs_%s);\n" shortname
+  ) all_functions_sorted;
 
-and generate_haskell_hs () =
-  generate_header HaskellStyle LGPLv2;
+  pr "\
 
-  (* XXX We only know how to generate partial FFI for Haskell
-   * at the moment.  Please help out!
-   *)
-  let can_generate style =
-    match style with
-    | RErr, _
-    | RInt _, _
-    | RInt64 _, _ -> true
-    | RBool _, _
-    | RConstString _, _
-    | RConstOptString _, _
-    | RString _, _
-    | RStringList _, _
-    | RStruct _, _
-    | RStructList _, _
-    | RHashtable _, _
-    | RBufferOut _, _ -> false in
+extern zend_module_entry guestfs_php_module_entry;
+#define phpext_guestfs_php_ptr &guestfs_php_module_entry
+
+#endif /* PHP_GUESTFS_PHP_H */
+"
+
+and generate_php_c () =
+  generate_header CStyle LGPLv2plus;
 
   pr "\
-{-# INCLUDE <guestfs.h> #-}
-{-# LANGUAGE ForeignFunctionInterface #-}
+/* NOTE: Be very careful with all macros in PHP header files.  The
+ * morons who wrote them aren't good at making them safe for inclusion
+ * in arbitrary places in C code, eg. not using 'do ... while(0)'
+ * or parenthesizing any of the arguments.
+ */
 
-module Guestfs (
-  create";
+/* NOTE (2): Some parts of the API can't be used on 32 bit platforms.
+ * Any 64 bit numbers will be truncated.  There's no easy way around
+ * this in PHP.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <php.h>
+#include <php_guestfs_php.h>
+
+#include \"guestfs.h\"
+
+static int res_guestfs_h;
+
+static void
+guestfs_php_handle_dtor (zend_rsrc_list_entry *rsrc TSRMLS_DC)
+{
+  guestfs_h *g = (guestfs_h *) rsrc->ptr;
+  if (g != NULL)
+    guestfs_close (g);
+}
+
+PHP_MINIT_FUNCTION (guestfs_php)
+{
+  res_guestfs_h =
+    zend_register_list_destructors_ex (guestfs_php_handle_dtor,
+    NULL, PHP_GUESTFS_HANDLE_RES_NAME, module_number);
+}
+
+static function_entry guestfs_php_functions[] = {
+  PHP_FE (guestfs_create, NULL)
+  PHP_FE (guestfs_last_error, NULL)
+";
 
-  (* List out the names of the actions we want to export. *)
   List.iter (
-    fun (name, style, _, _, _, _, _) ->
-      if can_generate style then pr ",\n  %s" name
-  ) all_functions;
+    fun (shortname, style, _, _, _, _, _) ->
+      pr "  PHP_FE (guestfs_%s, NULL)\n" shortname
+  ) all_functions_sorted;
 
-  pr "
-  ) where
+  pr "  { NULL, NULL, NULL }
+};
 
--- Unfortunately some symbols duplicate ones already present
--- in Prelude.  We don't know which, so we hard-code a list
--- here.
-import Prelude hiding (truncate)
+zend_module_entry guestfs_php_module_entry = {
+#if ZEND_MODULE_API_NO >= 20010901
+  STANDARD_MODULE_HEADER,
+#endif
+  PHP_GUESTFS_PHP_EXTNAME,
+  guestfs_php_functions,
+  PHP_MINIT (guestfs_php),
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+#if ZEND_MODULE_API_NO >= 20010901
+  PHP_GUESTFS_PHP_VERSION,
+#endif
+  STANDARD_MODULE_PROPERTIES
+};
 
-import Foreign
-import Foreign.C
-import Foreign.C.Types
-import IO
-import Control.Exception
-import Data.Typeable
+#ifdef COMPILE_DL_GUESTFS_PHP
+ZEND_GET_MODULE (guestfs_php)
+#endif
 
-data GuestfsS = GuestfsS            -- represents the opaque C struct
-type GuestfsP = Ptr GuestfsS        -- guestfs_h *
-type GuestfsH = ForeignPtr GuestfsS -- guestfs_h * with attached finalizer
+PHP_FUNCTION (guestfs_create)
+{
+  guestfs_h *g = guestfs_create ();
+  if (g == NULL) {
+    RETURN_FALSE;
+  }
 
--- XXX define properly later XXX
-data PV = PV
-data VG = VG
-data LV = LV
-data IntBool = IntBool
-data Stat = Stat
-data StatVFS = StatVFS
-data Hashtable = Hashtable
+  guestfs_set_error_handler (g, NULL, NULL);
 
-foreign import ccall unsafe \"guestfs_create\" c_create
-  :: IO GuestfsP
-foreign import ccall unsafe \"&guestfs_close\" c_close
-  :: FunPtr (GuestfsP -> IO ())
-foreign import ccall unsafe \"guestfs_set_error_handler\" c_set_error_handler
-  :: GuestfsP -> Ptr CInt -> Ptr CInt -> IO ()
+  ZEND_REGISTER_RESOURCE (return_value, g, res_guestfs_h);
+}
+
+PHP_FUNCTION (guestfs_last_error)
+{
+  zval *z_g;
+  guestfs_h *g;
+
+  if (zend_parse_parameters (ZEND_NUM_ARGS() TSRMLS_CC, \"r\",
+                             &z_g) == FAILURE) {
+    RETURN_FALSE;
+  }
+
+  ZEND_FETCH_RESOURCE (g, guestfs_h *, &z_g, -1, PHP_GUESTFS_HANDLE_RES_NAME,
+                       res_guestfs_h);
+  if (g == NULL) {
+    RETURN_FALSE;
+  }
+
+  const char *err = guestfs_last_error (g);
+  if (err) {
+    RETURN_STRING (err, 1);
+  } else {
+    RETURN_NULL ();
+  }
+}
+
+";
+
+  (* Now generate the PHP bindings for each action. *)
+  List.iter (
+    fun (shortname, style, _, _, _, _, _) ->
+      pr "PHP_FUNCTION (guestfs_%s)\n" shortname;
+      pr "{\n";
+      pr "  zval *z_g;\n";
+      pr "  guestfs_h *g;\n";
+
+      List.iter (
+        function
+        | String n | Device n | Pathname n | Dev_or_Path n
+        | FileIn n | FileOut n | Key n
+        | OptString n
+        | BufferIn n ->
+            pr "  char *%s;\n" n;
+            pr "  int %s_size;\n" n
+        | StringList n
+        | DeviceList n ->
+            pr "  zval *z_%s;\n" n;
+            pr "  char **%s;\n" n;
+        | Bool n ->
+            pr "  zend_bool %s;\n" n
+        | Int n | Int64 n ->
+            pr "  long %s;\n" n
+        ) (snd style);
+
+      pr "\n";
+
+      (* Parse the parameters. *)
+      let param_string = String.concat "" (
+        List.map (
+          function
+          | String n | Device n | Pathname n | Dev_or_Path n
+          | FileIn n | FileOut n | BufferIn n | Key n -> "s"
+          | OptString n -> "s!"
+          | StringList n | DeviceList n -> "a"
+          | Bool n -> "b"
+          | Int n | Int64 n -> "l"
+        ) (snd style)
+      ) in
+
+      pr "  if (zend_parse_parameters (ZEND_NUM_ARGS() TSRMLS_CC, \"r%s\",\n"
+        param_string;
+      pr "        &z_g";
+      List.iter (
+        function
+        | String n | Device n | Pathname n | Dev_or_Path n
+        | FileIn n | FileOut n | BufferIn n | Key n
+        | OptString n ->
+            pr ", &%s, &%s_size" n n
+        | StringList n | DeviceList n ->
+            pr ", &z_%s" n
+        | Bool n ->
+            pr ", &%s" n
+        | Int n | Int64 n ->
+            pr ", &%s" n
+      ) (snd style);
+      pr ") == FAILURE) {\n";
+      pr "    RETURN_FALSE;\n";
+      pr "  }\n";
+      pr "\n";
+      pr "  ZEND_FETCH_RESOURCE (g, guestfs_h *, &z_g, -1, PHP_GUESTFS_HANDLE_RES_NAME,\n";
+      pr "                       res_guestfs_h);\n";
+      pr "  if (g == NULL) {\n";
+      pr "    RETURN_FALSE;\n";
+      pr "  }\n";
+      pr "\n";
+
+      List.iter (
+        function
+        | String n | Device n | Pathname n | Dev_or_Path n
+        | FileIn n | FileOut n | Key n
+        | OptString n ->
+            (* Just need to check the string doesn't contain any ASCII
+             * NUL characters, which won't be supported by the C API.
+             *)
+            pr "  if (strlen (%s) != %s_size) {\n" n n;
+            pr "    fprintf (stderr, \"libguestfs: %s: parameter '%s' contains embedded ASCII NUL.\\n\");\n" shortname n;
+            pr "    RETURN_FALSE;\n";
+            pr "  }\n";
+            pr "\n"
+        | BufferIn n -> ()
+        | StringList n
+        | DeviceList n ->
+            (* Convert array to list of strings.
+             * http://marc.info/?l=pecl-dev&m=112205192100631&w=2
+             *)
+            pr "  {\n";
+            pr "    HashTable *a;\n";
+            pr "    int n;\n";
+            pr "    HashPosition p;\n";
+            pr "    zval **d;\n";
+            pr "    size_t c = 0;\n";
+            pr "\n";
+            pr "    a = Z_ARRVAL_P (z_%s);\n" n;
+            pr "    n = zend_hash_num_elements (a);\n";
+            pr "    %s = safe_emalloc (n + 1, sizeof (char *), 0);\n" n;
+            pr "    for (zend_hash_internal_pointer_reset_ex (a, &p);\n";
+            pr "         zend_hash_get_current_data_ex (a, (void **) &d, &p) == SUCCESS;\n";
+            pr "         zend_hash_move_forward_ex (a, &p)) {\n";
+            pr "      zval t = **d;\n";
+            pr "      zval_copy_ctor (&t);\n";
+            pr "      convert_to_string (&t);\n";
+            pr "      %s[c] = Z_STRVAL (t);\n" n;
+            pr "      c++;\n";
+            pr "    }\n";
+            pr "    %s[c] = NULL;\n" n;
+            pr "  }\n";
+            pr "\n"
+        | Bool n | Int n | Int64 n -> ()
+        ) (snd style);
 
-create :: IO GuestfsH
-create = do
-  p <- c_create
-  c_set_error_handler p nullPtr nullPtr
-  h <- newForeignPtr c_close p
-  return h
+      (* Return value. *)
+      let error_code =
+        match fst style with
+        | RErr -> pr "  int r;\n"; "-1"
+        | RBool _
+        | RInt _ -> pr "  int r;\n"; "-1"
+        | RInt64 _ -> pr "  int64_t r;\n"; "-1"
+        | RConstString _ -> pr "  const char *r;\n"; "NULL"
+        | RConstOptString _ -> pr "  const char *r;\n"; "NULL"
+        | RString _ ->
+            pr "  char *r;\n"; "NULL"
+        | RStringList _ ->
+            pr "  char **r;\n"; "NULL"
+        | RStruct (_, typ) ->
+            pr "  struct guestfs_%s *r;\n" typ; "NULL"
+        | RStructList (_, typ) ->
+            pr "  struct guestfs_%s_list *r;\n" typ; "NULL"
+        | RHashtable _ ->
+            pr "  char **r;\n"; "NULL"
+        | RBufferOut _ ->
+            pr "  char *r;\n";
+            pr "  size_t size;\n";
+            "NULL" in
 
-foreign import ccall unsafe \"guestfs_last_error\" c_last_error
-  :: GuestfsP -> IO CString
+      (* Call the function. *)
+      pr "  r = guestfs_%s " shortname;
+      generate_c_call_args ~handle:"g" style;
+      pr ";\n";
+      pr "\n";
 
--- last_error :: GuestfsH -> IO (Maybe String)
--- last_error h = do
---   str <- withForeignPtr h (\\p -> c_last_error p)
---   maybePeek peekCString str
+      (* Free up parameters. *)
+      List.iter (
+        function
+        | String n | Device n | Pathname n | Dev_or_Path n
+        | FileIn n | FileOut n | Key n
+        | OptString n -> ()
+        | BufferIn n -> ()
+        | StringList n
+        | DeviceList n ->
+            pr "  {\n";
+            pr "    size_t c = 0;\n";
+            pr "\n";
+            pr "    for (c = 0; %s[c] != NULL; ++c)\n" n;
+            pr "      efree (%s[c]);\n" n;
+            pr "    efree (%s);\n" n;
+            pr "  }\n";
+            pr "\n"
+        | Bool n | Int n | Int64 n -> ()
+        ) (snd style);
 
-last_error :: GuestfsH -> IO (String)
-last_error h = do
-  str <- withForeignPtr h (\\p -> c_last_error p)
-  if (str == nullPtr)
-    then return \"no error\"
-    else peekCString str
+      (* Check for errors. *)
+      pr "  if (r == %s) {\n" error_code;
+      pr "    RETURN_FALSE;\n";
+      pr "  }\n";
+      pr "\n";
 
-";
+      (* Convert the return value. *)
+      (match fst style with
+       | RErr ->
+           pr "  RETURN_TRUE;\n"
+       | RBool _ ->
+           pr "  RETURN_BOOL (r);\n"
+       | RInt _ ->
+           pr "  RETURN_LONG (r);\n"
+       | RInt64 _ ->
+           pr "  RETURN_LONG (r);\n"
+       | RConstString _ ->
+           pr "  RETURN_STRING (r, 1);\n"
+       | RConstOptString _ ->
+           pr "  if (r) { RETURN_STRING (r, 1); }\n";
+           pr "  else { RETURN_NULL (); }\n"
+       | RString _ ->
+           pr "  char *r_copy = estrdup (r);\n";
+           pr "  free (r);\n";
+           pr "  RETURN_STRING (r_copy, 0);\n"
+       | RBufferOut _ ->
+           pr "  char *r_copy = estrndup (r, size);\n";
+           pr "  free (r);\n";
+           pr "  RETURN_STRING (r_copy, 0);\n"
+       | RStringList _ ->
+           pr "  size_t c = 0;\n";
+           pr "  array_init (return_value);\n";
+           pr "  for (c = 0; r[c] != NULL; ++c) {\n";
+           pr "    add_next_index_string (return_value, r[c], 1);\n";
+           pr "    free (r[c]);\n";
+           pr "  }\n";
+           pr "  free (r);\n";
+       | RHashtable _ ->
+           pr "  size_t c = 0;\n";
+           pr "  array_init (return_value);\n";
+           pr "  for (c = 0; r[c] != NULL; c += 2) {\n";
+           pr "    add_assoc_string (return_value, r[c], r[c+1], 1);\n";
+           pr "    free (r[c]);\n";
+           pr "    free (r[c+1]);\n";
+           pr "  }\n";
+           pr "  free (r);\n";
+       | RStruct (_, typ) ->
+           let cols = cols_of_struct typ in
+           generate_php_struct_code typ cols
+       | RStructList (_, typ) ->
+           let cols = cols_of_struct typ in
+           generate_php_struct_list_code typ cols
+      );
 
-  (* Generate wrappers for each foreign function. *)
-  List.iter (
-    fun (name, style, _, _, _, _, _) ->
-      if can_generate style then (
-        pr "foreign import ccall unsafe \"guestfs_%s\" c_%s\n" name name;
-        pr "  :: ";
-        generate_haskell_prototype ~handle:"GuestfsP" style;
-        pr "\n";
-        pr "\n";
-        pr "%s :: " name;
-        generate_haskell_prototype ~handle:"GuestfsH" ~hs:true style;
-        pr "\n";
-        pr "%s %s = do\n" name
-          (String.concat " " ("h" :: List.map name_of_argt (snd style)));
-        pr "  r <- ";
-        (* Convert pointer arguments using with* functions. *)
-        List.iter (
-          function
-          | FileIn n
-          | FileOut n
-          | Pathname n | Device n | Dev_or_Path n | String n -> pr "withCString %s $ \\%s -> " n n
-          | OptString n -> pr "maybeWith withCString %s $ \\%s -> " n n
-          | StringList n | DeviceList n -> pr "withMany withCString %s $ \\%s -> withArray0 nullPtr %s $ \\%s -> " n n n n
-          | Bool _ | Int _ | Int64 _ -> ()
-        ) (snd style);
-        (* Convert integer arguments. *)
-        let args =
-          List.map (
-            function
-            | Bool n -> sprintf "(fromBool %s)" n
-            | Int n -> sprintf "(fromIntegral %s)" n
-            | Int64 n -> sprintf "(fromIntegral %s)" n
-            | FileIn n | FileOut n
-            | Pathname n | Device n | Dev_or_Path n | String n | OptString n | StringList n | DeviceList n -> n
-          ) (snd style) in
-        pr "withForeignPtr h (\\p -> c_%s %s)\n" name
-          (String.concat " " ("p" :: args));
-        (match fst style with
-         | RErr | RInt _ | RInt64 _ | RBool _ ->
-             pr "  if (r == -1)\n";
-             pr "    then do\n";
-             pr "      err <- last_error h\n";
-             pr "      fail err\n";
-         | RConstString _ | RConstOptString _ | RString _
-         | RStringList _ | RStruct _
-         | RStructList _ | RHashtable _ | RBufferOut _ ->
-             pr "  if (r == nullPtr)\n";
-             pr "    then do\n";
-             pr "      err <- last_error h\n";
-             pr "      fail err\n";
-        );
-        (match fst style with
-         | RErr ->
-             pr "    else return ()\n"
-         | RInt _ ->
-             pr "    else return (fromIntegral r)\n"
-         | RInt64 _ ->
-             pr "    else return (fromIntegral r)\n"
-         | RBool _ ->
-             pr "    else return (toBool r)\n"
-         | RConstString _
-         | RConstOptString _
-         | RString _
-         | RStringList _
-         | RStruct _
-         | RStructList _
-         | RHashtable _
-         | RBufferOut _ ->
-             pr "    else return ()\n" (* XXXXXXXXXXXXXXXXXXXX *)
-        );
-        pr "\n";
-      )
-  ) all_functions
+      pr "}\n";
+      pr "\n"
+  ) all_functions_sorted
 
-and generate_haskell_prototype ~handle ?(hs = false) style =
-  pr "%s -> " handle;
-  let string = if hs then "String" else "CString" in
-  let int = if hs then "Int" else "CInt" in
-  let bool = if hs then "Bool" else "CInt" in
-  let int64 = if hs then "Integer" else "Int64" in
+and generate_php_struct_code typ cols =
+  pr "  array_init (return_value);\n";
   List.iter (
-    fun arg ->
-      (match arg with
-       | Pathname _ | Device _ | Dev_or_Path _ | String _ -> pr "%s" string
-       | OptString _ -> if hs then pr "Maybe String" else pr "CString"
-       | StringList _ | DeviceList _ -> if hs then pr "[String]" else pr "Ptr CString"
-       | Bool _ -> pr "%s" bool
-       | Int _ -> pr "%s" int
-       | Int64 _ -> pr "%s" int
-       | FileIn _ -> pr "%s" string
-       | FileOut _ -> pr "%s" string
-      );
-      pr " -> ";
-  ) (snd style);
-  pr "IO (";
-  (match fst style with
-   | RErr -> if not hs then pr "CInt"
-   | RInt _ -> pr "%s" int
-   | RInt64 _ -> pr "%s" int64
-   | RBool _ -> pr "%s" bool
-   | RConstString _ -> pr "%s" string
-   | RConstOptString _ -> pr "Maybe %s" string
-   | RString _ -> pr "%s" string
-   | RStringList _ -> pr "[%s]" string
-   | RStruct (_, typ) ->
-       let name = java_name_of_struct typ in
-       pr "%s" name
-   | RStructList (_, typ) ->
-       let name = java_name_of_struct typ in
-       pr "[%s]" name
-   | RHashtable _ -> pr "Hashtable"
-   | RBufferOut _ -> pr "%s" string
-  );
-  pr ")"
+    function
+    | name, FString ->
+        pr "  add_assoc_string (return_value, \"%s\", r->%s, 1);\n" name name
+    | name, FBuffer ->
+        pr "  add_assoc_stringl (return_value, \"%s\", r->%s, r->%s_len, 1);\n"
+          name name name
+    | name, FUUID ->
+        pr "  add_assoc_stringl (return_value, \"%s\", r->%s, 32, 1);\n"
+          name name
+    | name, (FBytes|FUInt64|FInt64|FInt32|FUInt32) ->
+        pr "  add_assoc_long (return_value, \"%s\", r->%s);\n"
+          name name
+    | name, FChar ->
+        pr "  add_assoc_stringl (return_value, \"%s\", &r->%s, 1, 1);\n"
+          name name
+    | name, FOptPercent ->
+        pr "  add_assoc_double (return_value, \"%s\", r->%s);\n"
+          name name
+  ) cols;
+  pr "  guestfs_free_%s (r);\n" typ
+
+and generate_php_struct_list_code typ cols =
+  pr "  array_init (return_value);\n";
+  pr "  size_t c = 0;\n";
+  pr "  for (c = 0; c < r->len; ++c) {\n";
+  pr "    zval *z_elem;\n";
+  pr "    ALLOC_INIT_ZVAL (z_elem);\n";
+  pr "    array_init (z_elem);\n";
+  List.iter (
+    function
+    | name, FString ->
+        pr "    add_assoc_string (z_elem, \"%s\", r->val[c].%s, 1);\n"
+          name name
+    | name, FBuffer ->
+        pr "    add_assoc_stringl (z_elem, \"%s\", r->val[c].%s, r->val[c].%s_len, 1);\n"
+          name name name
+    | name, FUUID ->
+        pr "    add_assoc_stringl (z_elem, \"%s\", r->val[c].%s, 32, 1);\n"
+          name name
+    | name, (FBytes|FUInt64|FInt64|FInt32|FUInt32) ->
+        pr "    add_assoc_long (z_elem, \"%s\", r->val[c].%s);\n"
+          name name
+    | name, FChar ->
+        pr "    add_assoc_stringl (z_elem, \"%s\", &r->val[c].%s, 1, 1);\n"
+          name name
+    | name, FOptPercent ->
+        pr "    add_assoc_double (z_elem, \"%s\", r->val[c].%s);\n"
+          name name
+  ) cols;
+  pr "    add_next_index_zval (return_value, z_elem);\n";
+  pr "  }\n";
+  pr "  guestfs_free_%s_list (r);\n" typ
 
 and generate_bindtests () =
-  generate_header CStyle LGPLv2;
+  generate_header CStyle LGPLv2plus;
 
   pr "\
 #include <stdio.h>
@@ -9901,7 +12647,7 @@ and generate_bindtests () =
 static void
 print_strings (char *const *argv)
 {
-  int argc;
+  size_t argc;
 
   printf (\"[\");
   for (argc = 0; argv[argc] != NULL; ++argc) {
@@ -9930,7 +12676,15 @@ print_strings (char *const *argv)
       | Device n | Dev_or_Path n
       | String n
       | FileIn n
-      | FileOut n -> pr "  printf (\"%%s\\n\", %s);\n" n
+      | FileOut n
+      | Key n -> pr "  printf (\"%%s\\n\", %s);\n" n
+      | BufferIn n ->
+         pr "  {\n";
+         pr "    size_t i;\n";
+          pr "    for (i = 0; i < %s_size; ++i)\n" n;
+          pr "      printf (\"<%%02x>\", %s[i]);\n" n;
+          pr "    printf (\"\\n\");\n";
+         pr "  }\n";
       | OptString n -> pr "  printf (\"%%s\\n\", %s ? %s : \"null\");\n" n n
       | StringList n | DeviceList n -> pr "  print_strings (%s);\n" n
       | Bool n -> pr "  printf (\"%%s\\n\", %s ? \"true\" : \"false\");\n" n
@@ -10033,7 +12787,7 @@ print_strings (char *const *argv)
   ) tests
 
 and generate_ocaml_bindtests () =
-  generate_header OCamlStyle GPLv2;
+  generate_header OCamlStyle GPLv2plus;
 
   pr "\
 let () =
@@ -10054,6 +12808,7 @@ let () =
         | CallInt64 i when i >= 0L -> Int64.to_string i ^ "L"
         | CallInt64 i (* when i < 0L *) -> "(" ^ Int64.to_string i ^ "L)"
         | CallBool b -> string_of_bool b
+        | CallBuffer s -> sprintf "%S" s
       ) args
     )
   in
@@ -10066,7 +12821,7 @@ let () =
 
 and generate_perl_bindtests () =
   pr "#!/usr/bin/perl -w\n";
-  generate_header HashStyle GPLv2;
+  generate_header HashStyle GPLv2plus;
 
   pr "\
 use strict;
@@ -10088,6 +12843,7 @@ my $g = Sys::Guestfs->new ();
         | CallInt i -> string_of_int i
         | CallInt64 i -> Int64.to_string i
         | CallBool b -> if b then "1" else "0"
+        | CallBuffer s -> "\"" ^ c_quote s ^ "\""
       ) args
     )
   in
@@ -10099,7 +12855,7 @@ my $g = Sys::Guestfs->new ();
   pr "print \"EOF\\n\"\n"
 
 and generate_python_bindtests () =
-  generate_header HashStyle GPLv2;
+  generate_header HashStyle GPLv2plus;
 
   pr "\
 import guestfs
@@ -10119,6 +12875,7 @@ g = guestfs.GuestFS ()
         | CallInt i -> string_of_int i
         | CallInt64 i -> Int64.to_string i
         | CallBool b -> if b then "1" else "0"
+        | CallBuffer s -> "\"" ^ c_quote s ^ "\""
       ) args
     )
   in
@@ -10130,7 +12887,7 @@ g = guestfs.GuestFS ()
   pr "print \"EOF\"\n"
 
 and generate_ruby_bindtests () =
-  generate_header HashStyle GPLv2;
+  generate_header HashStyle GPLv2plus;
 
   pr "\
 require 'guestfs'
@@ -10150,6 +12907,7 @@ g = Guestfs::create()
         | CallInt i -> string_of_int i
         | CallInt64 i -> Int64.to_string i
         | CallBool b -> string_of_bool b
+        | CallBuffer s -> "\"" ^ c_quote s ^ "\""
       ) args
     )
   in
@@ -10161,7 +12919,7 @@ g = Guestfs::create()
   pr "print \"EOF\\n\"\n"
 
 and generate_java_bindtests () =
-  generate_header CStyle GPLv2;
+  generate_header CStyle GPLv2plus;
 
   pr "\
 import com.redhat.et.libguestfs.*;
@@ -10186,6 +12944,10 @@ public class Bindtests {
         | CallInt i -> string_of_int i
         | CallInt64 i -> Int64.to_string i
         | CallBool b -> string_of_bool b
+        | CallBuffer s ->
+            "new byte[] { " ^ String.concat "," (
+              map_chars (fun c -> string_of_int (Char.code c)) s
+            ) ^ " }"
       ) args
     )
   in
@@ -10206,7 +12968,7 @@ public class Bindtests {
 "
 
 and generate_haskell_bindtests () =
-  generate_header HaskellStyle GPLv2;
+  generate_header HaskellStyle GPLv2plus;
 
   pr "\
 module Bindtests where
@@ -10231,6 +12993,7 @@ main = do
         | CallInt64 i -> Int64.to_string i
         | CallBool true -> "True"
         | CallBool false -> "False"
+        | CallBuffer s -> "\"" ^ c_quote s ^ "\""
       ) args
     )
   in
@@ -10247,78 +13010,79 @@ main = do
 and generate_lang_bindtests call =
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList []; CallBool false;
-                CallInt 0; CallInt64 0L; CallString "123"; CallString "456"];
+                CallInt 0; CallInt64 0L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString None;
                 CallStringList []; CallBool false;
-                CallInt 0; CallInt64 0L; CallString "123"; CallString "456"];
+                CallInt 0; CallInt64 0L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString ""; CallOptString (Some "def");
                 CallStringList []; CallBool false;
-                CallInt 0; CallInt64 0L; CallString "123"; CallString "456"];
+                CallInt 0; CallInt64 0L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString ""; CallOptString (Some "");
                 CallStringList []; CallBool false;
-                CallInt 0; CallInt64 0L; CallString "123"; CallString "456"];
+                CallInt 0; CallInt64 0L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
-                CallInt 0; CallInt64 0L; CallString "123"; CallString "456"];
+                CallInt 0; CallInt64 0L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"; "2"]; CallBool false;
-                CallInt 0; CallInt64 0L; CallString "123"; CallString "456"];
+                CallInt 0; CallInt64 0L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool true;
-                CallInt 0; CallInt64 0L; CallString "123"; CallString "456"];
+                CallInt 0; CallInt64 0L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
-                CallInt (-1); CallInt64 (-1L); CallString "123"; CallString "456"];
+                CallInt (-1); CallInt64 (-1L); CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
-                CallInt (-2); CallInt64 (-2L); CallString "123"; CallString "456"];
+                CallInt (-2); CallInt64 (-2L); CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
-                CallInt 1; CallInt64 1L; CallString "123"; CallString "456"];
+                CallInt 1; CallInt64 1L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
-                CallInt 2; CallInt64 2L; CallString "123"; CallString "456"];
+                CallInt 2; CallInt64 2L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
-                CallInt 4095; CallInt64 4095L; CallString "123"; CallString "456"];
+                CallInt 4095; CallInt64 4095L; CallString "123"; CallString "456";
+                CallBuffer "abc\000abc"];
   call "test0" [CallString "abc"; CallOptString (Some "def");
                 CallStringList ["1"]; CallBool false;
-                CallInt 0; CallInt64 0L; CallString ""; CallString ""]
+                CallInt 0; CallInt64 0L; CallString ""; CallString "";
+                CallBuffer "abc\000abc"]
 
 (* XXX Add here tests of the return and error functions. *)
 
-(* This is used to generate the src/MAX_PROC_NR file which
- * contains the maximum procedure number, a surrogate for the
- * ABI version number.  See src/Makefile.am for the details.
- *)
 and generate_max_proc_nr () =
-  let proc_nrs = List.map (
-    fun (_, _, proc_nr, _, _, _, _) -> proc_nr
-  ) daemon_functions in
-
-  let max_proc_nr = List.fold_left max 0 proc_nrs in
-
   pr "%d\n" max_proc_nr
 
-let output_to filename =
+let output_to filename =
   let filename_new = filename ^ ".new" in
   chan := open_out filename_new;
-  let close () =
-    close_out !chan;
-    chan := Pervasives.stdout;
-
-    (* Is the new file different from the current file? *)
-    if Sys.file_exists filename && files_equal filename filename_new then
-      unlink filename_new              (* same, so skip it *)
-    else (
-      (* different, overwrite old one *)
-      (try chmod filename 0o644 with Unix_error _ -> ());
-      rename filename_new filename;
-      chmod filename 0o444;
-      printf "written %s\n%!" filename;
-    )
-  in
-  close
+  k ();
+  close_out !chan;
+  chan := Pervasives.stdout;
+
+  (* Is the new file different from the current file? *)
+  if Sys.file_exists filename && files_equal filename filename_new then
+    unlink filename_new                 (* same, so skip it *)
+  else (
+    (* different, overwrite old one *)
+    (try chmod filename 0o644 with Unix_error _ -> ());
+    rename filename_new filename;
+    chmod filename 0o444;
+    printf "written %s\n%!" filename;
+  )
 
 let perror msg = function
   | Unix_error (err, _, _) ->
@@ -10354,166 +13118,63 @@ Run it from the top source directory using the command
 
   check_functions ();
 
-  let close = output_to "src/guestfs_protocol.x" in
-  generate_xdr ();
-  close ();
-
-  let close = output_to "src/guestfs-structs.h" in
-  generate_structs_h ();
-  close ();
-
-  let close = output_to "src/guestfs-actions.h" in
-  generate_actions_h ();
-  close ();
-
-  let close = output_to "src/guestfs-internal-actions.h" in
-  generate_internal_actions_h ();
-  close ();
-
-  let close = output_to "src/guestfs-actions.c" in
-  generate_client_actions ();
-  close ();
-
-  let close = output_to "daemon/actions.h" in
-  generate_daemon_actions_h ();
-  close ();
-
-  let close = output_to "daemon/stubs.c" in
-  generate_daemon_actions ();
-  close ();
-
-  let close = output_to "daemon/names.c" in
-  generate_daemon_names ();
-  close ();
-
-  let close = output_to "daemon/optgroups.c" in
-  generate_daemon_optgroups_c ();
-  close ();
-
-  let close = output_to "daemon/optgroups.h" in
-  generate_daemon_optgroups_h ();
-  close ();
-
-  let close = output_to "capitests/tests.c" in
-  generate_tests ();
-  close ();
-
-  let close = output_to "src/guestfs-bindtests.c" in
-  generate_bindtests ();
-  close ();
-
-  let close = output_to "fish/cmds.c" in
-  generate_fish_cmds ();
-  close ();
-
-  let close = output_to "fish/completion.c" in
-  generate_fish_completion ();
-  close ();
-
-  let close = output_to "guestfs-structs.pod" in
-  generate_structs_pod ();
-  close ();
-
-  let close = output_to "guestfs-actions.pod" in
-  generate_actions_pod ();
-  close ();
-
-  let close = output_to "guestfs-availability.pod" in
-  generate_availability_pod ();
-  close ();
-
-  let close = output_to "guestfish-actions.pod" in
-  generate_fish_actions_pod ();
-  close ();
-
-  let close = output_to "ocaml/guestfs.mli" in
-  generate_ocaml_mli ();
-  close ();
-
-  let close = output_to "ocaml/guestfs.ml" in
-  generate_ocaml_ml ();
-  close ();
-
-  let close = output_to "ocaml/guestfs_c_actions.c" in
-  generate_ocaml_c ();
-  close ();
-
-  let close = output_to "ocaml/bindtests.ml" in
-  generate_ocaml_bindtests ();
-  close ();
-
-  let close = output_to "perl/Guestfs.xs" in
-  generate_perl_xs ();
-  close ();
-
-  let close = output_to "perl/lib/Sys/Guestfs.pm" in
-  generate_perl_pm ();
-  close ();
-
-  let close = output_to "perl/bindtests.pl" in
-  generate_perl_bindtests ();
-  close ();
-
-  let close = output_to "python/guestfs-py.c" in
-  generate_python_c ();
-  close ();
-
-  let close = output_to "python/guestfs.py" in
-  generate_python_py ();
-  close ();
-
-  let close = output_to "python/bindtests.py" in
-  generate_python_bindtests ();
-  close ();
-
-  let close = output_to "ruby/ext/guestfs/_guestfs.c" in
-  generate_ruby_c ();
-  close ();
-
-  let close = output_to "ruby/bindtests.rb" in
-  generate_ruby_bindtests ();
-  close ();
-
-  let close = output_to "java/com/redhat/et/libguestfs/GuestFS.java" in
-  generate_java_java ();
-  close ();
+  output_to "src/guestfs_protocol.x" generate_xdr;
+  output_to "src/guestfs-structs.h" generate_structs_h;
+  output_to "src/guestfs-actions.h" generate_actions_h;
+  output_to "src/guestfs-internal-actions.h" generate_internal_actions_h;
+  output_to "src/actions.c" generate_client_actions;
+  output_to "src/bindtests.c" generate_bindtests;
+  output_to "src/guestfs-structs.pod" generate_structs_pod;
+  output_to "src/guestfs-actions.pod" generate_actions_pod;
+  output_to "src/guestfs-availability.pod" generate_availability_pod;
+  output_to "src/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/optgroups.c" generate_daemon_optgroups_c;
+  output_to "daemon/optgroups.h" generate_daemon_optgroups_h;
+  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 "fish/guestfish-actions.pod" generate_fish_actions_pod;
+  output_to "fish/prepopts.c" generate_fish_prep_options_c;
+  output_to "fish/prepopts.h" generate_fish_prep_options_h;
+  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/bindtests.ml" generate_ocaml_bindtests;
+  output_to "perl/Guestfs.xs" generate_perl_xs;
+  output_to "perl/lib/Sys/Guestfs.pm" generate_perl_pm;
+  output_to "perl/bindtests.pl" generate_perl_bindtests;
+  output_to "python/guestfs-py.c" generate_python_c;
+  output_to "python/guestfs.py" generate_python_py;
+  output_to "python/bindtests.py" generate_python_bindtests;
+  output_to "ruby/ext/guestfs/_guestfs.c" generate_ruby_c;
+  output_to "ruby/bindtests.rb" generate_ruby_bindtests;
+  output_to "java/com/redhat/et/libguestfs/GuestFS.java" generate_java_java;
 
   List.iter (
     fun (typ, jtyp) ->
       let cols = cols_of_struct typ in
       let filename = sprintf "java/com/redhat/et/libguestfs/%s.java" jtyp in
-      let close = output_to filename in
-      generate_java_struct jtyp cols;
-      close ();
+      output_to filename (generate_java_struct jtyp cols);
   ) java_structs;
 
-  let close = output_to "java/Makefile.inc" in
-  generate_java_makefile_inc ();
-  close ();
-
-  let close = output_to "java/com_redhat_et_libguestfs_GuestFS.c" in
-  generate_java_c ();
-  close ();
-
-  let close = output_to "java/Bindtests.java" in
-  generate_java_bindtests ();
-  close ();
-
-  let close = output_to "haskell/Guestfs.hs" in
-  generate_haskell_hs ();
-  close ();
-
-  let close = output_to "haskell/Bindtests.hs" in
-  generate_haskell_bindtests ();
-  close ();
-
-  let close = output_to "src/MAX_PROC_NR" in
-  generate_max_proc_nr ();
-  close ();
+  output_to "java/Makefile.inc" generate_java_makefile_inc;
+  output_to "java/com_redhat_et_libguestfs_GuestFS.c" generate_java_c;
+  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 "csharp/Libguestfs.cs" generate_csharp;
+  output_to "php/extension/php_guestfs_php.h" generate_php_h;
+  output_to "php/extension/guestfs_php.c" generate_php_c;
 
   (* 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