tests: Fix read_file test.
[libguestfs.git] / src / generator.ml
index b7fe470..547acb7 100755 (executable)
@@ -24,8 +24,9 @@
  * this one to describe the interface (see the big table below), and
  * daemon/<somefile>.c to write the implementation.
  *
- * After editing this file, run it (./src/generator.ml) to regenerate
- * all the output files.
+ * 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.
  *
  * IMPORTANT: This script should NOT print any warnings.  If it prints
  * warnings, you should treat them as errors.
@@ -43,39 +44,62 @@ and ret =
      * indication, ie. 0 or -1.
      *)
   | RErr
+
     (* "RInt" as a return value means an int which is -1 for error
      * or any value >= 0 on success.  Only use this for smallish
      * positive ints (0 <= i < 2^30).
      *)
   | RInt of string
+
     (* "RInt64" is the same as RInt, but is guaranteed to be able
      * to return a full 64 bit value, _except_ that -1 means error
      * (so -1 cannot be a valid, non-error return value).
      *)
   | RInt64 of string
+
     (* "RBool" is a bool return value which can be true/false or
      * -1 for error.
      *)
   | RBool of string
+
     (* "RConstString" is a string that refers to a constant value.
+     * The return value must NOT be NULL (since NULL indicates
+     * an error).
+     *
      * Try to avoid using this.  In particular you cannot use this
      * for values returned from the daemon, because there is no
      * thread-safe way to return them in the C API.
      *)
   | RConstString of string
-    (* "RString" and "RStringList" are caller-frees. *)
+
+    (* "RConstOptString" is an even more broken version of
+     * "RConstString".  The returned string may be NULL and there
+     * is no way to return an error indication.  Avoid using this!
+     *)
+  | RConstOptString of string
+
+    (* "RString" is a returned string.  It must NOT be NULL, since
+     * a NULL return indicates an error.  The caller frees this.
+     *)
   | RString of string
+
+    (* "RStringList" is a list of strings.  No string in the list
+     * can be NULL.  The caller frees the strings and the array.
+     *)
   | RStringList of string
+
     (* "RStruct" is a function which returns a single named structure
      * or an error indication (in C, a struct, and in other languages
      * with varying representations, but usually very efficient).  See
      * after the function list below for the structures. 
      *)
   | RStruct of string * string         (* name of retval, name of struct *)
+
     (* "RStructList" is a function which returns either a list/array
      * of structures (could be zero-length), or an error indication.
      *)
   | RStructList of string * string     (* name of retval, name of struct *)
+
     (* Key-value pairs of untyped strings.  Turns into a hashtable or
      * dictionary in languages which support it.  DON'T use this as a
      * general "bucket" for results.  Prefer a stronger typed return
@@ -84,16 +108,21 @@ and ret =
      * inefficient.  Keys should be unique.  NULLs are not permitted.
      *)
   | RHashtable of string
-(* Not implemented:
+
     (* "RBufferOut" is handled almost exactly like RString, but
      * it allows the string to contain arbitrary 8 bit data including
      * ASCII NUL.  In the C API this causes an implicit extra parameter
-     * to be added of type <size_t *size_r>.  Other programming languages
-     * support strings with arbitrary 8 bit data.  At the RPC layer
-     * we have to use the opaque<> type instead of string<>.
+     * to be added of type <size_t *size_r>.  The extra parameter
+     * returns the actual size of the return buffer in bytes.
+     *
+     * Other programming languages support strings with arbitrary 8 bit
+     * data.
+     *
+     * At the RPC layer we have to use the opaque<> type instead of
+     * string<>.  Returned data is still limited to the max message
+     * size (ie. ~ 2 MB).
      *)
   | RBufferOut of string
-*)
 
 and args = argt list   (* Function parameters, guestfs handle is implicit. *)
 
@@ -140,15 +169,7 @@ type flags =
   | FishAction of string  (* call this function in guestfish *)
   | NotInFish            (* do not export via guestfish *)
   | NotInDocs            (* do not add this function to documentation *)
-
-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."
-
-let danger_will_robinson =
-  "B<This command is dangerous.  Without careful use you
-can easily destroy all your data>."
+  | DeprecatedBy of string (* function is deprecated, use .. instead *)
 
 (* You can supply zero or as many tests as you want per API call.
  *
@@ -205,6 +226,10 @@ and test =
      *)
   | TestOutputInt of seq * int
     (* Run the command sequence and expect the output of the final
+     * command to be <op> <int>, eg. ">=", "1".
+     *)
+  | TestOutputIntOp of seq * string * int
+    (* Run the command sequence and expect the output of the final
      * command to be a true value (!= 0 or != NULL).
      *)
   | TestOutputTrue of seq
@@ -218,6 +243,10 @@ and test =
      *)
   | TestOutputLength of seq * int
     (* Run the command sequence and expect the output of the final
+     * command to be a buffer (RBufferOut), ie. string + size.
+     *)
+  | TestOutputBuffer of seq * string
+    (* Run the command sequence and expect the output of the final
      * command to be a structure.
      *)
   | TestOutputStruct of seq * test_field_compare list
@@ -228,6 +257,7 @@ and test =
 
 and test_field_compare =
   | CompareWithInt of string * int
+  | CompareWithIntOp of string * string * int
   | CompareWithString of string * string
   | CompareFieldsIntEq of string * string
   | CompareFieldsStrEq of string * string
@@ -254,14 +284,17 @@ and test_init =
      * a bad idea.
      *)
   | InitNone
+
     (* Block devices are empty and no filesystems are mounted. *)
   | InitEmpty
+
     (* /dev/sda contains a single partition /dev/sda1, which is formatted
      * as ext2, empty [except for lost+found] and mounted on /.
      * /dev/sdb and /dev/sdc may have random content.
      * No LVM.
      *)
   | InitBasicFS
+
     (* /dev/sda:
      *   /dev/sda1 (is a PV):
      *     /dev/VG/LV (size 8MB):
@@ -270,6 +303,11 @@ and test_init =
      *)
   | InitBasicFSonLVM
 
+    (* /dev/sdd (the squashfs, see images/ directory in source)
+     * is mounted on /
+     *)
+  | InitSquashFS
+
 (* Sequence of commands for testing. *)
 and seq = cmd list
 and cmd = string list
@@ -300,6 +338,7 @@ let test_all_rets = [
   "test0rint64",       RInt64 "valout";
   "test0rbool",        RBool "valout";
   "test0rconststring", RConstString "valout";
+  "test0rconstoptstring", RConstOptString "valout";
   "test0rstring",      RString "valout";
   "test0rstringlist",  RStringList "valout";
   "test0rstruct",      RStruct ("valout", "lvm_pv");
@@ -464,7 +503,8 @@ environment variable.
 Setting C<qemu> to C<NULL> restores the default qemu binary.");
 
   ("get_qemu", (RConstString "qemu", []), -1, [],
-   [],
+   [InitNone, Always, TestRun (
+      [["get_qemu"]])],
    "get the qemu binary",
    "\
 Return the current qemu binary.
@@ -484,7 +524,8 @@ C<LIBGUESTFS_PATH> environment variable.
 Setting C<path> to C<NULL> restores the default path.");
 
   ("get_path", (RConstString "path", []), -1, [],
-   [],
+   [InitNone, Always, TestRun (
+      [["get_path"]])],
    "get the search path",
    "\
 Return the current search path.
@@ -492,7 +533,7 @@ Return the current search path.
 This is always non-NULL.  If it wasn't set already, then this will
 return the default path.");
 
-  ("set_append", (RErr, [String "append"]), -1, [FishAlias "append"],
+  ("set_append", (RErr, [OptString "append"]), -1, [FishAlias "append"],
    [],
    "add options to kernel command line",
    "\
@@ -505,7 +546,11 @@ C<LIBGUESTFS_APPEND> environment variable.
 Setting C<append> to C<NULL> means I<no> additional options
 are passed (libguestfs always adds a few of its own).");
 
-  ("get_append", (RConstString "append", []), -1, [],
+  ("get_append", (RConstOptString "append", []), -1, [],
+   (* This cannot be tested with the current framework.  The
+    * function can return NULL in normal operations, which the
+    * test framework interprets as an error.
+    *)
    [],
    "get the additional kernel options",
    "\
@@ -527,7 +572,8 @@ This is disabled by default (except in guestfish where it is
 enabled by default).");
 
   ("get_autosync", (RBool "autosync", []), -1, [],
-   [],
+   [InitNone, Always, TestRun (
+      [["get_autosync"]])],
    "get autosync mode",
    "\
 Get the autosync flag.");
@@ -548,7 +594,8 @@ C<LIBGUESTFS_DEBUG> is defined and set to C<1>.");
 This returns the verbose messages flag.");
 
   ("is_ready", (RBool "ready", []), -1, [],
-   [],
+   [InitNone, Always, TestOutputTrue (
+      [["is_ready"]])],
    "is ready to accept commands",
    "\
 This returns true iff this handle is ready to accept commands
@@ -557,7 +604,8 @@ This returns true iff this handle is ready to accept commands
 For more information on states, see L<guestfs(3)>.");
 
   ("is_config", (RBool "config", []), -1, [],
-   [],
+   [InitNone, Always, TestOutputFalse (
+      [["is_config"]])],
    "is in configuration state",
    "\
 This returns true iff this handle is being configured
@@ -566,7 +614,8 @@ This returns true iff this handle is being configured
 For more information on states, see L<guestfs(3)>.");
 
   ("is_launching", (RBool "launching", []), -1, [],
-   [],
+   [InitNone, Always, TestOutputFalse (
+      [["is_launching"]])],
    "is launching subprocess",
    "\
 This returns true iff this handle is launching the subprocess
@@ -575,7 +624,8 @@ This returns true iff this handle is launching the subprocess
 For more information on states, see L<guestfs(3)>.");
 
   ("is_busy", (RBool "busy", []), -1, [],
-   [],
+   [InitNone, Always, TestOutputFalse (
+      [["is_busy"]])],
    "is busy processing a command",
    "\
 This returns true iff this handle is busy processing a command
@@ -621,7 +671,9 @@ actions using the low-level API.
 For more information on states, see L<guestfs(3)>.");
 
   ("set_memsize", (RErr, [Int "memsize"]), -1, [FishAlias "memsize"],
-   [],
+   [InitNone, Always, TestOutputInt (
+      [["set_memsize"; "500"];
+       ["get_memsize"]], 500)],
    "set memory allocated to the qemu subprocess",
    "\
 This sets the memory size in megabytes allocated to the
@@ -636,7 +688,8 @@ For more information on the architecture of libguestfs,
 see L<guestfs(3)>.");
 
   ("get_memsize", (RInt "memsize", []), -1, [],
-   [],
+   [InitNone, Always, TestOutputIntOp (
+      [["get_memsize"]], ">=", 256)],
    "get memory allocated to the qemu subprocess",
    "\
 This gets the memory size in megabytes allocated to the
@@ -650,7 +703,8 @@ For more information on the architecture of libguestfs,
 see L<guestfs(3)>.");
 
   ("get_pid", (RInt "pid", []), -1, [FishAlias "pid"],
-   [],
+   [InitNone, Always, TestOutputIntOp (
+      [["get_pid"]], ">=", 1)],
    "get PID of qemu subprocess",
    "\
 Return the process ID of the qemu subprocess.  If there is no
@@ -659,7 +713,7 @@ qemu subprocess, then this will return an error.
 This is an internal call used for debugging and testing.");
 
   ("version", (RStruct ("version", "version"), []), -1, [],
-   [InitBasicFS, Always, TestOutputStruct (
+   [InitNone, Always, TestOutputStruct (
       [["version"]], [CompareWithInt ("major", 1)])],
    "get the library version number",
    "\
@@ -751,8 +805,8 @@ Return the contents of the file named C<path>.
 
 Note that this function cannot correctly handle binary files
 (specifically, files containing C<\\0> character which is treated
-as end of string).  For those you need to use the C<guestfs_download>
-function which has a more complex interface.");
+as end of string).  For those you need to use the C<guestfs_read_file>
+or C<guestfs_download> functions which have a more complex interface.");
 
   ("ll", (RString "listing", [String "directory"]), 5, [],
    [], (* XXX Tricky to test because it depends on the exact format
@@ -1349,7 +1403,9 @@ contains the filesystem.");
 This returns the list of currently mounted filesystems.  It returns
 the list of devices (eg. C</dev/sda1>, C</dev/VG/LV>).
 
-Some internal mounts are not shown.");
+Some internal mounts are not shown.
+
+See also: C<guestfs_mountpoints>");
 
   ("umount_all", (RErr, []), 47, [FishAlias "unmount-all"],
    [InitBasicFS, Always, TestOutputList (
@@ -1397,7 +1453,10 @@ 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 exact command which runs is C<file -bsL path>.  Note in
+This call will also transparently look inside various types
+of compressed file.
+
+The exact command which runs is C<file -zbsL path>.  Note in
 particular that the filename is not prepended to the output
 (the C<-b> option).");
 
@@ -1696,8 +1755,8 @@ This uses the L<blockdev(8)> command.");
   ("upload", (RErr, [FileIn "filename"; String "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"]], "e3eda01d9815f8d24aae2dbd89b68b06")],
+      [["upload"; "../COPYING.LIB"; "/COPYING.LIB"];
+       ["checksum"; "md5"; "/COPYING.LIB"]], "e3eda01d9815f8d24aae2dbd89b68b06")],
    "upload a file from the local machine",
    "\
 Upload local file C<filename> to C<remotefilename> on the
@@ -1710,10 +1769,10 @@ See also C<guestfs_download>.");
   ("download", (RErr, [String "remotefilename"; FileOut "filename"]), 67, [],
    [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"]], "e3eda01d9815f8d24aae2dbd89b68b06")],
+      [["upload"; "../COPYING.LIB"; "/COPYING.LIB"];
+       ["download"; "/COPYING.LIB"; "testdownload.tmp"];
+       ["upload"; "testdownload.tmp"; "/upload"];
+       ["checksum"; "md5"; "/upload"]], "e3eda01d9815f8d24aae2dbd89b68b06")],
    "download a file to the local machine",
    "\
 Download file C<remotefilename> and save it as C<filename>
@@ -1747,12 +1806,8 @@ See also C<guestfs_upload>, C<guestfs_cat>.");
     InitBasicFS, Always, TestOutput (
       [["write_file"; "/new"; "test\n"; "0"];
        ["checksum"; "sha512"; "/new"]], "0e3e75234abc68f4378a86b3f4b32a198ba301845b0cd6e50106e874345700cc6663a86c1ea125dc5e92be17c98f9a0f85ca9d5f595db2012f7cc3571945c123");
-    InitBasicFS, Always, TestOutput (
-      (* RHEL 5 thinks this is an HFS+ filesystem unless we give
-       * the type explicitly.
-       *)
-      [["mount_vfs"; "ro"; "squashfs"; "/dev/sdd"; "/"];
-       ["checksum"; "md5"; "/known-3"]], "46d6ca27ee07cdc6fa99c2e138cc522c")],
+    InitSquashFS, Always, TestOutput (
+      [["checksum"; "md5"; "/known-3"]], "46d6ca27ee07cdc6fa99c2e138cc522c")],
    "compute MD5, SHAx or CRC checksum of file",
    "\
 This call computes the MD5, SHAx or CRC checksum of the
@@ -2227,9 +2282,8 @@ The returned strings are transcoded to UTF-8.");
     (* Test for RHBZ#501888c2 regression which caused large hexdump
      * commands to segfault.
      *)
-    InitBasicFS, Always, TestRun (
-      [["mount_vfs"; "ro"; "squashfs"; "/dev/sdd"; "/"];
-       ["hexdump"; "/100krandom"]])],
+    InitSquashFS, Always, TestRun (
+      [["hexdump"; "/100krandom"]])],
    "dump a file in hexadecimal",
    "\
 This runs C<hexdump -C> on the given C<path>.  The result is
@@ -2335,19 +2389,19 @@ are activated or deactivated.");
 
   ("lvresize", (RErr, [String "device"; Int "mbytes"]), 105, [],
    [InitNone, Always, TestOutput (
-    [["sfdiskM"; "/dev/sda"; ","];
-     ["pvcreate"; "/dev/sda1"];
-     ["vgcreate"; "VG"; "/dev/sda1"];
-     ["lvcreate"; "LV"; "VG"; "10"];
-     ["mkfs"; "ext2"; "/dev/VG/LV"];
-     ["mount"; "/dev/VG/LV"; "/"];
-     ["write_file"; "/new"; "test content"; "0"];
-     ["umount"; "/"];
-     ["lvresize"; "/dev/VG/LV"; "20"];
-     ["e2fsck_f"; "/dev/VG/LV"];
-     ["resize2fs"; "/dev/VG/LV"];
-     ["mount"; "/dev/VG/LV"; "/"];
-     ["cat"; "/new"]], "test content")],
+      [["sfdiskM"; "/dev/sda"; ","];
+       ["pvcreate"; "/dev/sda1"];
+       ["vgcreate"; "VG"; "/dev/sda1"];
+       ["lvcreate"; "LV"; "VG"; "10"];
+       ["mkfs"; "ext2"; "/dev/VG/LV"];
+       ["mount"; "/dev/VG/LV"; "/"];
+       ["write_file"; "/new"; "test content"; "0"];
+       ["umount"; "/"];
+       ["lvresize"; "/dev/VG/LV"; "20"];
+       ["e2fsck_f"; "/dev/VG/LV"];
+       ["resize2fs"; "/dev/VG/LV"];
+       ["mount"; "/dev/VG/LV"; "/"];
+       ["cat"; "/new"]], "test content")],
    "resize an LVM logical volume",
    "\
 This resizes (expands or shrinks) an existing LVM logical
@@ -2419,7 +2473,7 @@ This command is only needed because of C<guestfs_resize2fs>
 
   ("sleep", (RErr, [Int "secs"]), 109, [],
    [InitNone, Always, TestRun (
-    [["sleep"; "1"]])],
+      [["sleep"; "1"]])],
    "sleep for some seconds",
    "\
 Sleep for C<secs> seconds.");
@@ -2566,51 +2620,44 @@ directory and its contents after use.
 See also: L<mkdtemp(3)>");
 
   ("wc_l", (RInt "lines", [String "path"]), 118, [],
-   [InitBasicFS, Always, TestOutputInt (
-      [["mount_vfs"; "ro"; "squashfs"; "/dev/sdd"; "/"];
-       ["wc_l"; "/10klines"]], 10000)],
+   [InitSquashFS, Always, TestOutputInt (
+      [["wc_l"; "/10klines"]], 10000)],
    "count lines in a file",
    "\
 This command counts the lines in a file, using the
 C<wc -l> external command.");
 
   ("wc_w", (RInt "words", [String "path"]), 119, [],
-   [InitBasicFS, Always, TestOutputInt (
-      [["mount_vfs"; "ro"; "squashfs"; "/dev/sdd"; "/"];
-       ["wc_w"; "/10klines"]], 10000)],
+   [InitSquashFS, Always, TestOutputInt (
+      [["wc_w"; "/10klines"]], 10000)],
    "count words in a file",
    "\
 This command counts the words in a file, using the
 C<wc -w> external command.");
 
   ("wc_c", (RInt "chars", [String "path"]), 120, [],
-   [InitBasicFS, Always, TestOutputInt (
-      [["mount_vfs"; "ro"; "squashfs"; "/dev/sdd"; "/"];
-       ["wc_c"; "/100kallspaces"]], 102400)],
+   [InitSquashFS, Always, TestOutputInt (
+      [["wc_c"; "/100kallspaces"]], 102400)],
    "count characters in a file",
    "\
 This command counts the characters in a file, using the
 C<wc -c> external command.");
 
   ("head", (RStringList "lines", [String "path"]), 121, [ProtocolLimitWarning],
-   [InitBasicFS, Always, TestOutputList (
-      [["mount_vfs"; "ro"; "squashfs"; "/dev/sdd"; "/"];
-       ["head"; "/10klines"]], ["0abcdefghijklmnopqrstuvwxyz";"1abcdefghijklmnopqrstuvwxyz";"2abcdefghijklmnopqrstuvwxyz";"3abcdefghijklmnopqrstuvwxyz";"4abcdefghijklmnopqrstuvwxyz";"5abcdefghijklmnopqrstuvwxyz";"6abcdefghijklmnopqrstuvwxyz";"7abcdefghijklmnopqrstuvwxyz";"8abcdefghijklmnopqrstuvwxyz";"9abcdefghijklmnopqrstuvwxyz"])],
+   [InitSquashFS, Always, TestOutputList (
+      [["head"; "/10klines"]], ["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
 a list of strings.");
 
   ("head_n", (RStringList "lines", [Int "nrlines"; String "path"]), 122, [ProtocolLimitWarning],
-   [InitBasicFS, Always, TestOutputList (
-      [["mount_vfs"; "ro"; "squashfs"; "/dev/sdd"; "/"];
-       ["head_n"; "3"; "/10klines"]], ["0abcdefghijklmnopqrstuvwxyz";"1abcdefghijklmnopqrstuvwxyz";"2abcdefghijklmnopqrstuvwxyz"]);
-    InitBasicFS, Always, TestOutputList (
-      [["mount_vfs"; "ro"; "squashfs"; "/dev/sdd"; "/"];
-       ["head_n"; "-9997"; "/10klines"]], ["0abcdefghijklmnopqrstuvwxyz";"1abcdefghijklmnopqrstuvwxyz";"2abcdefghijklmnopqrstuvwxyz"]);
-    InitBasicFS, Always, TestOutputList (
-      [["mount_vfs"; "ro"; "squashfs"; "/dev/sdd"; "/"];
-       ["head_n"; "0"; "/10klines"]], [])],
+   [InitSquashFS, Always, TestOutputList (
+      [["head_n"; "3"; "/10klines"]], ["0abcdefghijklmnopqrstuvwxyz";"1abcdefghijklmnopqrstuvwxyz";"2abcdefghijklmnopqrstuvwxyz"]);
+    InitSquashFS, Always, TestOutputList (
+      [["head_n"; "-9997"; "/10klines"]], ["0abcdefghijklmnopqrstuvwxyz";"1abcdefghijklmnopqrstuvwxyz";"2abcdefghijklmnopqrstuvwxyz"]);
+    InitSquashFS, Always, TestOutputList (
+      [["head_n"; "0"; "/10klines"]], [])],
    "return first N lines of a file",
    "\
 If the parameter C<nrlines> is a positive number, this returns the first
@@ -2622,24 +2669,20 @@ from the file C<path>, excluding the last C<nrlines> lines.
 If the parameter C<nrlines> is zero, this returns an empty list.");
 
   ("tail", (RStringList "lines", [String "path"]), 123, [ProtocolLimitWarning],
-   [InitBasicFS, Always, TestOutputList (
-      [["mount_vfs"; "ro"; "squashfs"; "/dev/sdd"; "/"];
-       ["tail"; "/10klines"]], ["9990abcdefghijklmnopqrstuvwxyz";"9991abcdefghijklmnopqrstuvwxyz";"9992abcdefghijklmnopqrstuvwxyz";"9993abcdefghijklmnopqrstuvwxyz";"9994abcdefghijklmnopqrstuvwxyz";"9995abcdefghijklmnopqrstuvwxyz";"9996abcdefghijklmnopqrstuvwxyz";"9997abcdefghijklmnopqrstuvwxyz";"9998abcdefghijklmnopqrstuvwxyz";"9999abcdefghijklmnopqrstuvwxyz"])],
+   [InitSquashFS, Always, TestOutputList (
+      [["tail"; "/10klines"]], ["9990abcdefghijklmnopqrstuvwxyz";"9991abcdefghijklmnopqrstuvwxyz";"9992abcdefghijklmnopqrstuvwxyz";"9993abcdefghijklmnopqrstuvwxyz";"9994abcdefghijklmnopqrstuvwxyz";"9995abcdefghijklmnopqrstuvwxyz";"9996abcdefghijklmnopqrstuvwxyz";"9997abcdefghijklmnopqrstuvwxyz";"9998abcdefghijklmnopqrstuvwxyz";"9999abcdefghijklmnopqrstuvwxyz"])],
    "return last 10 lines of a file",
    "\
 This command returns up to the last 10 lines of a file as
 a list of strings.");
 
   ("tail_n", (RStringList "lines", [Int "nrlines"; String "path"]), 124, [ProtocolLimitWarning],
-   [InitBasicFS, Always, TestOutputList (
-      [["mount_vfs"; "ro"; "squashfs"; "/dev/sdd"; "/"];
-       ["tail_n"; "3"; "/10klines"]], ["9997abcdefghijklmnopqrstuvwxyz";"9998abcdefghijklmnopqrstuvwxyz";"9999abcdefghijklmnopqrstuvwxyz"]);
-    InitBasicFS, Always, TestOutputList (
-      [["mount_vfs"; "ro"; "squashfs"; "/dev/sdd"; "/"];
-       ["tail_n"; "-9998"; "/10klines"]], ["9997abcdefghijklmnopqrstuvwxyz";"9998abcdefghijklmnopqrstuvwxyz";"9999abcdefghijklmnopqrstuvwxyz"]);
-    InitBasicFS, Always, TestOutputList (
-      [["mount_vfs"; "ro"; "squashfs"; "/dev/sdd"; "/"];
-       ["tail_n"; "0"; "/10klines"]], [])],
+   [InitSquashFS, Always, TestOutputList (
+      [["tail_n"; "3"; "/10klines"]], ["9997abcdefghijklmnopqrstuvwxyz";"9998abcdefghijklmnopqrstuvwxyz";"9999abcdefghijklmnopqrstuvwxyz"]);
+    InitSquashFS, Always, TestOutputList (
+      [["tail_n"; "-9998"; "/10klines"]], ["9997abcdefghijklmnopqrstuvwxyz";"9998abcdefghijklmnopqrstuvwxyz";"9999abcdefghijklmnopqrstuvwxyz"]);
+    InitSquashFS, Always, TestOutputList (
+      [["tail_n"; "0"; "/10klines"]], [])],
    "return last N lines of a file",
    "\
 If the parameter C<nrlines> is a positive number, this returns the last
@@ -2692,9 +2735,8 @@ The result is the estimated size in I<kilobytes>
 (ie. units of 1024 bytes).");
 
   ("initrd_list", (RStringList "filenames", [String "path"]), 128, [],
-   [InitBasicFS, Always, TestOutputList (
-      [["mount_vfs"; "ro"; "squashfs"; "/dev/sdd"; "/"];
-       ["initrd_list"; "/initrd"]], ["empty";"known-1";"known-2";"known-3"])],
+   [InitSquashFS, Always, TestOutputList (
+      [["initrd_list"; "/initrd"]], ["empty";"known-1";"known-2";"known-3"])],
    "list files in an initrd",
    "\
 This command lists out files contained in an initrd.
@@ -2819,6 +2861,50 @@ All entries in the directory are returned, including C<.> and
 C<..>.  The entries are I<not> sorted, but returned in the same
 order as the underlying filesystem.
 
+Also this call returns basic file type information about each
+file.  The C<ftyp> field will contain one of the following characters:
+
+=over 4
+
+=item 'b'
+
+Block special
+
+=item 'c'
+
+Char special
+
+=item 'd'
+
+Directory
+
+=item 'f'
+
+FIFO (named pipe)
+
+=item 'l'
+
+Symbolic link
+
+=item 'r'
+
+Regular file
+
+=item 's'
+
+Socket
+
+=item 'u'
+
+Unknown file type
+
+=item '?'
+
+The L<readdir(3)> returned a C<d_type> field with an
+unexpected value
+
+=back
+
 This function is primarily intended for use by programs.  To
 get a simple list of names, use C<guestfs_ls>.  To get a printable
 directory for human consumption, use C<guestfs_ll>.");
@@ -2835,6 +2921,232 @@ were rarely if ever used anyway.
 
 See also C<guestfs_sfdisk> and the L<sfdisk(8)> manpage.");
 
+  ("zfile", (RString "description", [String "method"; String "path"]), 140, [DeprecatedBy "file"],
+   [],
+   "determine file type inside a compressed file",
+   "\
+This command runs C<file> after first decompressing C<path>
+using C<method>.
+
+C<method> must be one of C<gzip>, C<compress> or C<bzip2>.
+
+Since 1.0.63, use C<guestfs_file> instead which can now
+process compressed files.");
+
+  ("getxattrs", (RStructList ("xattrs", "xattr"), [String "path"]), 141, [],
+   [],
+   "list extended attributes of a file or directory",
+   "\
+This call lists the extended attributes of the file or directory
+C<path>.
+
+At the system call level, this is a combination of the
+L<listxattr(2)> and L<getxattr(2)> calls.
+
+See also: C<guestfs_lgetxattrs>, L<attr(5)>.");
+
+  ("lgetxattrs", (RStructList ("xattrs", "xattr"), [String "path"]), 142, [],
+   [],
+   "list extended attributes of a file or directory",
+   "\
+This is the same as C<guestfs_getxattrs>, but if C<path>
+is a symbolic link, then it returns the extended attributes
+of the link itself.");
+
+  ("setxattr", (RErr, [String "xattr";
+                      String "val"; Int "vallen"; (* will be BufferIn *)
+                      String "path"]), 143, [],
+   [],
+   "set extended attribute of a file or directory",
+   "\
+This call sets the extended attribute named C<xattr>
+of the file C<path> to the value C<val> (of length C<vallen>).
+The value is arbitrary 8 bit data.
+
+See also: C<guestfs_lsetxattr>, L<attr(5)>.");
+
+  ("lsetxattr", (RErr, [String "xattr";
+                       String "val"; Int "vallen"; (* will be BufferIn *)
+                       String "path"]), 144, [],
+   [],
+   "set extended attribute of a file or directory",
+   "\
+This is the same as C<guestfs_setxattr>, but if C<path>
+is a symbolic link, then it sets an extended attribute
+of the link itself.");
+
+  ("removexattr", (RErr, [String "xattr"; String "path"]), 145, [],
+   [],
+   "remove extended attribute of a file or directory",
+   "\
+This call removes the extended attribute named C<xattr>
+of the file C<path>.
+
+See also: C<guestfs_lremovexattr>, L<attr(5)>.");
+
+  ("lremovexattr", (RErr, [String "xattr"; String "path"]), 146, [],
+   [],
+   "remove extended attribute of a file or directory",
+   "\
+This is the same as C<guestfs_removexattr>, but if C<path>
+is a symbolic link, then it removes an extended attribute
+of the link itself.");
+
+  ("mountpoints", (RHashtable "mps", []), 147, [],
+   [],
+   "show mountpoints",
+   "\
+This call is similar to C<guestfs_mounts>.  That call returns
+a list of devices.  This one returns a hash table (map) of
+device name to directory where the device is mounted.");
+
+  ("mkmountpoint", (RErr, [String "path"]), 148, [],
+   [],
+   "create a mountpoint",
+   "\
+C<guestfs_mkmountpoint> and C<guestfs_rmmountpoint> are
+specialized calls that can be used to create extra mountpoints
+before mounting the first filesystem.
+
+These calls are I<only> necessary in some very limited circumstances,
+mainly the case where you want to mount a mix of unrelated and/or
+read-only filesystems together.
+
+For example, live CDs often contain a \"Russian doll\" nest of
+filesystems, an ISO outer layer, with a squashfs image inside, with
+an ext2/3 image inside that.  You can unpack this as follows
+in guestfish:
+
+ add-ro Fedora-11-i686-Live.iso
+ run
+ mkmountpoint /cd
+ mkmountpoint /squash
+ mkmountpoint /ext3
+ mount /dev/sda /cd
+ mount-loop /cd/LiveOS/squashfs.img /squash
+ mount-loop /squash/LiveOS/ext3fs.img /ext3
+
+The inner filesystem is now unpacked under the /ext3 mountpoint.");
+
+  ("rmmountpoint", (RErr, [String "path"]), 149, [],
+   [],
+   "remove a mountpoint",
+   "\
+This calls removes a mountpoint that was previously created
+with C<guestfs_mkmountpoint>.  See C<guestfs_mkmountpoint>
+for full details.");
+
+  ("read_file", (RBufferOut "content", [String "path"]), 150, [ProtocolLimitWarning],
+   [InitSquashFS, Always, TestOutputBuffer (
+      [["read_file"; "/known-4"]], "abc\ndef\nghi")],
+   "read a file",
+   "\
+This calls returns the contents of the file C<path> as a
+buffer.
+
+Unlike C<guestfs_cat>, this function can correctly
+handle files that contain embedded ASCII NUL characters.
+However unlike C<guestfs_download>, this function is limited
+in the total size of file that can be handled.");
+
+  ("grep", (RStringList "lines", [String "regex"; String "path"]), 151, [ProtocolLimitWarning],
+   [InitSquashFS, Always, TestOutputList (
+      [["grep"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"]);
+    InitSquashFS, Always, TestOutputList (
+      [["grep"; "nomatch"; "/test-grep.txt"]], [])],
+   "return lines matching a pattern",
+   "\
+This calls the external C<grep> program and returns the
+matching lines.");
+
+  ("egrep", (RStringList "lines", [String "regex"; String "path"]), 152, [ProtocolLimitWarning],
+   [InitSquashFS, Always, TestOutputList (
+      [["egrep"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"])],
+   "return lines matching a pattern",
+   "\
+This calls the external C<egrep> program and returns the
+matching lines.");
+
+  ("fgrep", (RStringList "lines", [String "pattern"; String "path"]), 153, [ProtocolLimitWarning],
+   [InitSquashFS, Always, TestOutputList (
+      [["fgrep"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"])],
+   "return lines matching a pattern",
+   "\
+This calls the external C<fgrep> program and returns the
+matching lines.");
+
+  ("grepi", (RStringList "lines", [String "regex"; String "path"]), 154, [ProtocolLimitWarning],
+   [InitSquashFS, Always, TestOutputList (
+      [["grepi"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"; "ABC"])],
+   "return lines matching a pattern",
+   "\
+This calls the external C<grep -i> program and returns the
+matching lines.");
+
+  ("egrepi", (RStringList "lines", [String "regex"; String "path"]), 155, [ProtocolLimitWarning],
+   [InitSquashFS, Always, TestOutputList (
+      [["egrepi"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"; "ABC"])],
+   "return lines matching a pattern",
+   "\
+This calls the external C<egrep -i> program and returns the
+matching lines.");
+
+  ("fgrepi", (RStringList "lines", [String "pattern"; String "path"]), 156, [ProtocolLimitWarning],
+   [InitSquashFS, Always, TestOutputList (
+      [["fgrepi"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"; "ABC"])],
+   "return lines matching a pattern",
+   "\
+This calls the external C<fgrep -i> program and returns the
+matching lines.");
+
+  ("zgrep", (RStringList "lines", [String "regex"; String "path"]), 157, [ProtocolLimitWarning],
+   [InitSquashFS, Always, TestOutputList (
+      [["zgrep"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"])],
+   "return lines matching a pattern",
+   "\
+This calls the external C<zgrep> program and returns the
+matching lines.");
+
+  ("zegrep", (RStringList "lines", [String "regex"; String "path"]), 158, [ProtocolLimitWarning],
+   [InitSquashFS, Always, TestOutputList (
+      [["zegrep"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"])],
+   "return lines matching a pattern",
+   "\
+This calls the external C<zegrep> program and returns the
+matching lines.");
+
+  ("zfgrep", (RStringList "lines", [String "pattern"; String "path"]), 159, [ProtocolLimitWarning],
+   [InitSquashFS, Always, TestOutputList (
+      [["zfgrep"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"])],
+   "return lines matching a pattern",
+   "\
+This calls the external C<zfgrep> program and returns the
+matching lines.");
+
+  ("zgrepi", (RStringList "lines", [String "regex"; String "path"]), 160, [ProtocolLimitWarning],
+   [InitSquashFS, Always, TestOutputList (
+      [["zgrepi"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"; "ABC"])],
+   "return lines matching a pattern",
+   "\
+This calls the external C<zgrep -i> program and returns the
+matching lines.");
+
+  ("zegrepi", (RStringList "lines", [String "regex"; String "path"]), 161, [ProtocolLimitWarning],
+   [InitSquashFS, Always, TestOutputList (
+      [["zegrepi"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"; "ABC"])],
+   "return lines matching a pattern",
+   "\
+This calls the external C<zegrep -i> program and returns the
+matching lines.");
+
+  ("zfgrepi", (RStringList "lines", [String "pattern"; String "path"]), 162, [ProtocolLimitWarning],
+   [InitSquashFS, Always, TestOutputList (
+      [["zfgrepi"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"; "ABC"])],
+   "return lines matching a pattern",
+   "\
+This calls the external C<zfgrep -i> program and returns the
+matching lines.");
+
 ]
 
 let all_functions = non_daemon_functions @ daemon_functions
@@ -2850,6 +3162,7 @@ let all_functions_sorted =
 type field =
   | FChar                      (* C 'char' (really, a 7 bit byte). *)
   | FString                    (* nul-terminated ASCII string. *)
+  | FBuffer                    (* opaque buffer of bytes, (char *, int) pair *)
   | FUInt32
   | FInt32
   | FUInt64
@@ -2988,6 +3301,12 @@ let structs = [
     "release", FInt64;
     "extra", FString;
   ];
+
+  (* Extended attribute. *)
+  "xattr", [
+    "attrname", FString;
+    "attrval", FBuffer;
+  ];
 ] (* end of structs *)
 
 (* Ugh, Java has to be different ..
@@ -3002,6 +3321,7 @@ let java_structs = [
   "statvfs", "StatVFS";
   "dirent", "Dirent";
   "version", "Version";
+  "xattr", "XAttr";
 ]
 
 (* Used for testing language bindings. *)
@@ -3152,10 +3472,37 @@ let cols_of_struct typ =
 let seq_of_test = function
   | TestRun s | TestOutput (s, _) | TestOutputList (s, _)
   | TestOutputListOfDevices (s, _)
-  | TestOutputInt (s, _) | TestOutputTrue s | TestOutputFalse s
-  | TestOutputLength (s, _) | TestOutputStruct (s, _)
+  | TestOutputInt (s, _) | TestOutputIntOp (s, _, _)
+  | TestOutputTrue s | TestOutputFalse s
+  | TestOutputLength (s, _) | TestOutputBuffer (s, _)
+  | TestOutputStruct (s, _)
   | TestLastFail s -> s
 
+(* 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."
+
+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
+
 (* Check function names etc. for consistency. *)
 let check_functions () =
   let contains_uppercase str =
@@ -3207,9 +3554,10 @@ let check_functions () =
 
       (match fst style with
        | RErr -> ()
-       | RInt n | RInt64 n | RBool n | RConstString n | RString n
+       | RInt n | RInt64 n | RBool n
+       | RConstString n | RConstOptString n | RString n
        | RStringList n | RStruct (n, _) | RStructList (n, _)
-       | RHashtable n ->
+       | RHashtable n | RBufferOut n ->
           check_arg_ret_name n
       );
       List.iter (fun arg -> check_arg_ret_name (name_of_argt arg)) (snd style)
@@ -3369,6 +3717,10 @@ let rec generate_actions_pod () =
         | 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"
@@ -3391,11 +3743,18 @@ 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
+         pr "%s\n\n" danger_will_robinson;
+       match deprecation_notice flags with
+       | None -> ()
+       | Some txt -> pr "%s\n\n" txt
       )
   ) all_functions_sorted
 
@@ -3414,6 +3773,10 @@ and generate_structs_pod () =
        | 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
@@ -3457,6 +3820,7 @@ and generate_xdr () =
        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
@@ -3501,8 +3865,8 @@ and generate_xdr () =
           pr "struct %s_ret {\n" name;
           pr "  bool %s;\n" n;
           pr "};\n\n"
-       | RConstString _ ->
-          failwithf "RConstString cannot be returned from a daemon function"
+       | RConstString _ | RConstOptString _ ->
+          failwithf "RConstString|RConstOptString cannot be used by daemon functions"
        | RString n ->
           pr "struct %s_ret {\n" name;
           pr "  string %s<>;\n" n;
@@ -3523,6 +3887,10 @@ and generate_xdr () =
           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;
 
@@ -3618,6 +3986,9 @@ and generate_structs_h () =
        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
@@ -3703,7 +4074,7 @@ check_state (guestfs_h *g, const char *caller)
 {
   if (!guestfs_is_ready (g)) {
     if (guestfs_is_config (g))
-      error (g, \"%%s: call launch() before using this function\",
+      error (g, \"%%s: call launch before using this function\\n(in guestfish, don't forget to use the 'run' command)\",
         caller);
     else if (guestfs_is_launching (g))
       error (g, \"%%s: call wait_ready() before using this function\",
@@ -3736,12 +4107,12 @@ check_state (guestfs_h *g, const char *caller)
       pr "  struct guestfs_message_error err;\n";
       (match fst style with
        | RErr -> ()
-       | RConstString _ ->
-          failwithf "RConstString cannot be returned from a daemon function"
+       | RConstString _ | RConstOptString _ ->
+          failwithf "RConstString|RConstOptString cannot be used by daemon functions"
        | RInt _ | RInt64 _
        | RBool _ | RString _ | RStringList _
        | RStruct _ | RStructList _
-       | RHashtable _ ->
+       | RHashtable _ | RBufferOut _ ->
           pr "  struct %s_ret ret;\n" name
       );
       pr "};\n";
@@ -3777,12 +4148,12 @@ check_state (guestfs_h *g, const char *caller)
 
       (match fst style with
        | RErr -> ()
-       | RConstString _ ->
-          failwithf "RConstString cannot be returned from a daemon function"
+       | RConstString _ | RConstOptString _ ->
+          failwithf "RConstString|RConstOptString cannot be used by daemon functions"
        | RInt _ | RInt64 _
        | RBool _ | RString _ | RStringList _
        | RStruct _ | RStructList _
-       | RHashtable _ ->
+       | RHashtable _ | RBufferOut _ ->
           pr "  if (!xdr_%s_ret (xdr, &ctx->ret)) {\n" name;
           pr "    error (g, \"%%s: failed to parse reply\", \"%s\");\n" name;
           pr "    return;\n";
@@ -3800,11 +4171,11 @@ check_state (guestfs_h *g, const char *caller)
       let error_code =
        match fst style with
        | RErr | RInt _ | RInt64 _ | RBool _ -> "-1"
-       | RConstString _ ->
-           failwithf "RConstString cannot be returned from a daemon function"
+       | RConstString _ | RConstOptString _ ->
+           failwithf "RConstString|RConstOptString cannot be used by daemon functions"
        | RString _ | RStringList _
        | RStruct _ | RStructList _
-       | RHashtable _ ->
+       | RHashtable _ | RBufferOut _ ->
            "NULL" in
 
       pr "{\n";
@@ -3924,8 +4295,8 @@ check_state (guestfs_h *g, const char *caller)
        | RErr -> pr "  return 0;\n"
        | RInt n | RInt64 n | RBool n ->
           pr "  return ctx.ret.%s;\n" n
-       | RConstString _ ->
-          failwithf "RConstString cannot be returned from a daemon function"
+       | RConstString _ | RConstOptString _ ->
+          failwithf "RConstString|RConstOptString cannot be used by daemon functions"
        | RString n ->
           pr "  return ctx.ret.%s; /* caller will free */\n" n
        | RStringList n | RHashtable n ->
@@ -3942,6 +4313,9 @@ check_state (guestfs_h *g, const char *caller)
        | RStructList (n, _) ->
           pr "  /* caller will free this */\n";
           pr "  return safe_memdup (g, &ctx.ret.%s, sizeof (ctx.ret.%s));\n" n n
+       | RBufferOut n ->
+          pr "  *size_r = ctx.ret.%s.%s_len;\n" n n;
+          pr "  return ctx.ret.%s.%s_val; /* caller will free */\n" n n
       );
 
       pr "}\n\n"
@@ -4017,12 +4391,16 @@ and generate_daemon_actions () =
        | RErr | RInt _ -> pr "  int r;\n"; "-1"
        | RInt64 _ -> pr "  int64_t r;\n"; "-1"
        | RBool _ -> pr "  int r;\n"; "-1"
-       | RConstString _ ->
-           failwithf "RConstString cannot be returned from a daemon function"
+       | RConstString _ | RConstOptString _ ->
+           failwithf "RConstString|RConstOptString cannot be used by daemon functions"
        | RString _ -> pr "  char *r;\n"; "NULL"
        | RStringList _ | RHashtable _ -> pr "  char **r;\n"; "NULL"
        | RStruct (_, typ) -> pr "  guestfs_int_%s *r;\n" typ; "NULL"
-       | RStructList (_, typ) -> pr "  guestfs_int_%s_list *r;\n" typ; "NULL" in
+       | RStructList (_, typ) -> pr "  guestfs_int_%s_list *r;\n" typ; "NULL"
+       | RBufferOut _ ->
+           pr "  size_t size;\n";
+           pr "  char *r;\n";
+           "NULL" in
 
       (match snd style with
        | [] -> ()
@@ -4076,11 +4454,11 @@ and generate_daemon_actions () =
       (* Don't want to call the impl with any FileIn or FileOut
        * parameters, since these go "outside" the RPC protocol.
        *)
-      let argsnofile =
+      let args' =
        List.filter (function FileIn _ | FileOut _ -> false | _ -> true)
          (snd style) in
       pr "  r = do_%s " name;
-      generate_call_args argsnofile;
+      generate_c_call_args (fst style, args');
       pr ";\n";
 
       pr "  if (r == %s)\n" error_code;
@@ -4103,8 +4481,8 @@ and generate_daemon_actions () =
            pr "  ret.%s = r;\n" n;
            pr "  reply ((xdrproc_t) &xdr_guestfs_%s_ret, (char *) &ret);\n"
              name
-       | RConstString _ ->
-           failwithf "RConstString cannot be returned from a daemon function"
+       | RConstString _ | RConstOptString _ ->
+           failwithf "RConstString|RConstOptString cannot be used by daemon functions"
        | RString n ->
            pr "  struct guestfs_%s_ret ret;\n" name;
            pr "  ret.%s = r;\n" n;
@@ -4132,6 +4510,13 @@ and generate_daemon_actions () =
              name;
            pr "  xdr_free ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n"
              name
+       | RBufferOut n ->
+           pr "  struct guestfs_%s_ret ret;\n" name;
+           pr "  ret.%s.%s_val = r;\n" n n;
+           pr "  ret.%s.%s_len = size;\n" n n;
+           pr "  reply ((xdrproc_t) &xdr_guestfs_%s_ret, (char *) &ret);\n"
+             name;
+           pr "  free (r);\n"
       );
 
       (* Free the args. *)
@@ -4235,7 +4620,7 @@ and generate_daemon_actions () =
                 pr "    fprintf (stderr, \"%%s: failed to parse float '%%s' from token %%s\\n\", __func__, tok, \"%s\");\n" name;
                 pr "    return -1;\n";
                 pr "  }\n";
-            | FInt32 | FUInt32 | FUInt64 | FChar ->
+            | FBuffer | FInt32 | FUInt32 | FUInt64 | FChar ->
                 assert false (* can never be an LVM column *)
            );
            pr "  tok = next;\n";
@@ -4676,6 +5061,13 @@ and generate_one_test_body name i test_name init test =
          ["lvcreate"; "LV"; "VG"; "8"];
          ["mkfs"; "ext2"; "/dev/VG/LV"];
          ["mount"; "/dev/VG/LV"; "/"]]
+   | InitSquashFS ->
+       pr "  /* InitSquashFS for %s */\n" test_name;
+       List.iter (generate_test_command_call test_name)
+        [["blockdev_setrw"; "/dev/sda"];
+         ["umount_all"];
+         ["lvm_remove_all"];
+         ["mount_vfs"; "ro"; "squashfs"; "/dev/sdd"; "/"]]
   );
 
   let get_seq_last = function
@@ -4773,6 +5165,19 @@ and generate_one_test_body name i test_name init test =
       in
       List.iter (generate_test_command_call test_name) seq;
       generate_test_command_call ~test test_name last
+  | TestOutputIntOp (seq, op, expected) ->
+      pr "  /* TestOutputIntOp for %s (%d) */\n" name i;
+      let seq, last = get_seq_last seq in
+      let test () =
+       pr "    if (! (r %s %d)) {\n" op expected;
+       pr "      fprintf (stderr, \"%s: expected %s %d but got %%d\\n\","
+         test_name op expected;
+       pr "               (int) r);\n";
+       pr "      return -1;\n";
+       pr "    }\n"
+      in
+      List.iter (generate_test_command_call test_name) seq;
+      generate_test_command_call ~test test_name last
   | TestOutputTrue seq ->
       pr "  /* TestOutputTrue for %s (%d) */\n" name i;
       let seq, last = get_seq_last seq in
@@ -4818,6 +5223,23 @@ and generate_one_test_body name i test_name init test =
       in
       List.iter (generate_test_command_call test_name) seq;
       generate_test_command_call ~test test_name last
+  | TestOutputBuffer (seq, expected) ->
+      pr "  /* TestOutputBuffer for %s (%d) */\n" name i;
+      pr "  const char *expected = \"%s\";\n" (c_quote expected);
+      let seq, last = get_seq_last seq in
+      let len = String.length expected in
+      let test () =
+       pr "    if (size != %d) {\n" len;
+       pr "      fprintf (stderr, \"%s: returned size of buffer wrong, expected %d but got %%zu\\n\", size);\n" test_name len;
+       pr "      return -1;\n";
+       pr "    }\n";
+       pr "    if (strncmp (r, expected, size) != 0) {\n";
+       pr "      fprintf (stderr, \"%s: expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", expected, r);\n" test_name;
+       pr "      return -1;\n";
+       pr "    }\n"
+      in
+      List.iter (generate_test_command_call test_name) seq;
+      generate_test_command_call ~test test_name last
   | TestOutputStruct (seq, checks) ->
       pr "  /* TestOutputStruct for %s (%d) */\n" name i;
       let seq, last = get_seq_last seq in
@@ -4831,6 +5253,13 @@ and generate_one_test_body name i test_name init test =
              pr "               (int) r->%s);\n" field;
              pr "      return -1;\n";
              pr "    }\n"
+         | CompareWithIntOp (field, op, expected) ->
+             pr "    if (!(r->%s %s %d)) {\n" field op expected;
+             pr "      fprintf (stderr, \"%s: %s was %%d, expected %s %d\\n\",\n"
+               test_name field op expected;
+             pr "               (int) r->%s);\n" field;
+             pr "      return -1;\n";
+             pr "    }\n"
          | CompareWithString (field, expected) ->
              pr "    if (strcmp (r->%s, \"%s\") != 0) {\n" field expected;
              pr "      fprintf (stderr, \"%s: %s was \"%%s\", expected \"%s\"\\n\",\n"
@@ -4911,7 +5340,8 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
        match fst style with
        | RErr | RInt _ | RBool _ -> pr "    int r;\n"; "-1"
        | RInt64 _ -> pr "    int64_t r;\n"; "-1"
-       | RConstString _ -> pr "    const char *r;\n"; "NULL"
+       | RConstString _ | RConstOptString _ ->
+           pr "    const char *r;\n"; "NULL"
        | RString _ -> pr "    char *r;\n"; "NULL"
        | RStringList _ | RHashtable _ ->
            pr "    char **r;\n";
@@ -4920,7 +5350,11 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
        | RStruct (_, typ) ->
            pr "    struct guestfs_%s *r;\n" typ; "NULL"
        | RStructList (_, typ) ->
-           pr "    struct guestfs_%s_list *r;\n" typ; "NULL" in
+           pr "    struct guestfs_%s_list *r;\n" typ; "NULL"
+       | RBufferOut _ ->
+           pr "    char *r;\n";
+           pr "    size_t size;\n";
+           "NULL" in
 
       pr "    suppress_error = %d;\n" (if expect_error then 1 else 0);
       pr "    r = guestfs_%s (g" name;
@@ -4946,7 +5380,13 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
            let b = bool_of_string arg in pr ", %d" (if b then 1 else 0)
       ) (List.combine (snd style) args);
 
+      (match fst style with
+       | RBufferOut _ -> pr ", &size"
+       | _ -> ()
+      );
+
       pr ");\n";
+
       if not expect_error then
        pr "    if (r == %s)\n" error_code
       else
@@ -4960,8 +5400,9 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
       );
 
       (match fst style with
-       | RErr | RInt _ | RInt64 _ | RBool _ | RConstString _ -> ()
-       | RString _ -> pr "    free (r);\n"
+       | RErr | RInt _ | RInt64 _ | RBool _
+       | RConstString _ | RConstOptString _ -> ()
+       | RString _ | RBufferOut _ -> pr "    free (r);\n"
        | RStringList _ | RHashtable _ ->
           pr "    for (i = 0; r[i] != NULL; ++i)\n";
           pr "      free (r[i]);\n";
@@ -4998,6 +5439,7 @@ and generate_fish_cmds () =
   pr "#include <stdlib.h>\n";
   pr "#include <string.h>\n";
   pr "#include <inttypes.h>\n";
+  pr "#include <ctype.h>\n";
   pr "\n";
   pr "#include <guestfs.h>\n";
   pr "#include \"fish.h\"\n";
@@ -5050,6 +5492,12 @@ and generate_fish_cmds () =
            ("\n\n" ^ danger_will_robinson)
          else "" in
 
+      let warnings =
+       warnings ^
+         match deprecation_notice flags with
+         | None -> ""
+         | Some txt -> "\n\n" ^ txt in
+
       let describe_alias =
        if name <> alias then
          sprintf "\n\nYou can use '%s' as an alias for this command." alias
@@ -5075,7 +5523,7 @@ and generate_fish_cmds () =
   List.iter (
     fun (typ, cols) ->
       let needs_i =
-        List.exists (function (_, FUUID) -> true | _ -> false) cols in
+        List.exists (function (_, (FUUID|FBuffer)) -> true | _ -> false) cols in
 
       pr "static void print_%s (struct guestfs_%s *%s)\n" typ typ typ;
       pr "{\n";
@@ -5092,6 +5540,14 @@ and generate_fish_cmds () =
            pr "  for (i = 0; i < 32; ++i)\n";
            pr "    printf (\"%%c\", %s->%s[i]);\n" typ name;
            pr "  printf (\"\\n\");\n"
+       | name, FBuffer ->
+           pr "  printf (\"%s: \");\n" name;
+           pr "  for (i = 0; i < %s->%s_len; ++i)\n" typ name;
+           pr "    if (isprint (%s->%s[i]))\n" typ name;
+           pr "      printf (\"%%c\", %s->%s[i]);\n" typ name;
+           pr "    else\n";
+           pr "      printf (\"\\\\x%%02x\", %s->%s[i]);\n" typ name;
+           pr "  printf (\"\\n\");\n"
        | name, (FUInt64|FBytes) ->
            pr "  printf (\"%s: %%\" PRIu64 \"\\n\", %s->%s);\n" name typ name
        | name, FInt64 ->
@@ -5130,11 +5586,14 @@ and generate_fish_cmds () =
        | RInt _
        | RBool _ -> pr "  int r;\n"
        | RInt64 _ -> pr "  int64_t r;\n"
-       | RConstString _ -> pr "  const char *r;\n"
+       | RConstString _ | RConstOptString _ -> pr "  const char *r;\n"
        | RString _ -> pr "  char *r;\n"
        | RStringList _ | RHashtable _ -> pr "  char **r;\n"
        | RStruct (_, typ) -> pr "  struct guestfs_%s *r;\n" typ
        | RStructList (_, typ) -> pr "  struct guestfs_%s_list *r;\n" typ
+       | RBufferOut _ ->
+          pr "  char *r;\n";
+          pr "  size_t size;\n";
       );
       List.iter (
        function
@@ -5181,7 +5640,7 @@ and generate_fish_cmds () =
        try find_map (function FishAction n -> Some n | _ -> None) flags
        with Not_found -> sprintf "guestfs_%s" name in
       pr "  r = %s " fn;
-      generate_call_args ~handle:"g" (snd style);
+      generate_c_call_args ~handle:"g" style;
       pr ";\n";
 
       (* Check return value for errors and display command results. *)
@@ -5203,6 +5662,9 @@ and generate_fish_cmds () =
           pr "  if (r == NULL) return -1;\n";
           pr "  printf (\"%%s\\n\", r);\n";
           pr "  return 0;\n"
+       | RConstOptString _ ->
+          pr "  printf (\"%%s\\n\", r ? : \"(null)\");\n";
+          pr "  return 0;\n"
        | RString _ ->
           pr "  if (r == NULL) return -1;\n";
           pr "  printf (\"%%s\\n\", r);\n";
@@ -5228,6 +5690,11 @@ and generate_fish_cmds () =
           pr "  print_table (r);\n";
           pr "  free_strings (r);\n";
           pr "  return 0;\n"
+       | RBufferOut _ ->
+          pr "  if (r == NULL) return -1;\n";
+          pr "  fwrite (r, size, 1, stdout);\n";
+          pr "  free (r);\n";
+          pr "  return 0;\n"
       );
       pr "}\n";
       pr "\n"
@@ -5402,7 +5869,11 @@ and generate_fish_actions_pod () =
        pr "%s\n\n" protocol_limit_warning;
 
       if List.mem DangerWillRobinson flags then
-       pr "%s\n\n" danger_will_robinson
+       pr "%s\n\n" danger_will_robinson;
+
+      match deprecation_notice flags with
+      | None -> ()
+      | Some txt -> pr "%s\n\n" txt
   ) all_functions_sorted
 
 (* Generate a C function prototype. *)
@@ -5417,8 +5888,8 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
    | RInt _ -> pr "int "
    | RInt64 _ -> pr "int64_t "
    | RBool _ -> pr "int "
-   | RConstString _ -> pr "const char *"
-   | RString _ -> pr "char *"
+   | RConstString _ | RConstOptString _ -> pr "const char *"
+   | RString _ | RBufferOut _ -> pr "char *"
    | RStringList _ | RHashtable _ -> pr "char **"
    | RStruct (_, typ) ->
        if not in_daemon then pr "struct guestfs_%s *" typ
@@ -5427,8 +5898,9 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
        if not in_daemon then pr "struct guestfs_%s_list *" typ
        else pr "guestfs_int_%s_list *" typ
   );
+  let is_RBufferOut = match fst style with RBufferOut _ -> true | _ -> false in
   pr "%s%s (" prefix name;
-  if handle = None && List.length (snd style) = 0 then
+  if handle = None && List.length (snd style) = 0 && not is_RBufferOut then
     pr "void"
   else (
     let comma = ref false in
@@ -5459,25 +5931,37 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
       | FileOut n ->
          if not in_daemon then (next (); pr "const char *%s" n)
     ) (snd style);
+    if is_RBufferOut then (next (); pr "size_t *size_r");
   );
   pr ")";
   if semicolon then pr ";";
   if newline then pr "\n"
 
 (* Generate C call arguments, eg "(handle, foo, bar)" *)
-and generate_call_args ?handle args =
+and generate_c_call_args ?handle ?(decl = false) style =
   pr "(";
   let comma = ref false in
+  let next () =
+    if !comma then pr ", ";
+    comma := true
+  in
   (match handle with
    | None -> ()
    | Some handle -> pr "%s" handle; comma := true
   );
   List.iter (
     fun arg ->
-      if !comma then pr ", ";
-      comma := true;
+      next ();
       pr "%s" (name_of_argt arg)
-  ) args;
+  ) (snd style);
+  (* For RBufferOut calls, add implicit &size parameter. *)
+  if not decl then (
+    match fst style with
+    | RBufferOut _ ->
+       next ();
+       pr "&size"
+    | _ -> ()
+  );
   pr ")"
 
 (* Generate the OCaml bindings interface. *)
@@ -5607,6 +6091,10 @@ copy_table (char * const * argv)
          (match col with
           | name, FString ->
               pr "  v = caml_copy_string (%s->%s);\n" typ name
+          | name, FBuffer ->
+              pr "  v = caml_alloc_string (%s->%s_len);\n" typ name;
+              pr "  memcpy (String_val (v), %s->%s, %s->%s_len);\n"
+                typ name typ name
           | name, FUUID ->
               pr "  v = caml_alloc_string (32);\n";
               pr "  memcpy (String_val (v), %s->%s, 32);\n" typ name
@@ -5658,6 +6146,9 @@ copy_table (char * const * argv)
       let params =
        "gv" :: List.map (fun arg -> name_of_argt arg ^ "v") (snd style) in
 
+      let needs_extra_vs =
+       match fst style with RConstOptString _ -> true | _ -> false in
+
       pr "CAMLprim value\n";
       pr "ocaml_guestfs_%s (value %s" name (List.hd params);
       List.iter (pr ", value %s") (List.tl params);
@@ -5674,7 +6165,10 @@ copy_table (char * const * argv)
        | ps ->
           pr "  CAMLparam%d (%s);\n" (List.length ps) (String.concat ", " ps)
       );
-      pr "  CAMLlocal1 (rv);\n";
+      if not needs_extra_vs then
+       pr "  CAMLlocal1 (rv);\n"
+      else
+       pr "  CAMLlocal3 (rv, v, v2);\n";
       pr "\n";
 
       pr "  guestfs_h *g = Guestfs_val (gv);\n";
@@ -5705,7 +6199,8 @@ copy_table (char * const * argv)
        | RInt _ -> pr "  int r;\n"; "-1"
        | RInt64 _ -> pr "  int64_t r;\n"; "-1"
        | RBool _ -> pr "  int r;\n"; "-1"
-       | RConstString _ -> pr "  const char *r;\n"; "NULL"
+       | RConstString _ | RConstOptString _ ->
+           pr "  const char *r;\n"; "NULL"
        | RString _ -> pr "  char *r;\n"; "NULL"
        | RStringList _ ->
            pr "  int i;\n";
@@ -5718,12 +6213,16 @@ copy_table (char * const * argv)
        | RHashtable _ ->
            pr "  int i;\n";
            pr "  char **r;\n";
+           "NULL"
+       | RBufferOut _ ->
+           pr "  char *r;\n";
+           pr "  size_t size;\n";
            "NULL" in
       pr "\n";
 
       pr "  caml_enter_blocking_section ();\n";
       pr "  r = guestfs_%s " name;
-      generate_call_args ~handle:"g" (snd style);
+      generate_c_call_args ~handle:"g" style;
       pr ";\n";
       pr "  caml_leave_blocking_section ();\n";
 
@@ -5744,7 +6243,15 @@ copy_table (char * const * argv)
        | RInt64 _ ->
           pr "  rv = caml_copy_int64 (r);\n"
        | RBool _ -> pr "  rv = Val_bool (r);\n"
-       | RConstString _ -> pr "  rv = caml_copy_string (r);\n"
+       | RConstString _ ->
+          pr "  rv = caml_copy_string (r);\n"
+       | RConstOptString _ ->
+          pr "  if (r) { /* Some string */\n";
+          pr "    v = caml_alloc (1, 0);\n";
+          pr "    v2 = caml_copy_string (r);\n";
+          pr "    Store_field (v, 0, v2);\n";
+          pr "  } else /* None */\n";
+          pr "    v = Val_int (0);\n";
        | RString _ ->
           pr "  rv = caml_copy_string (r);\n";
           pr "  free (r);\n"
@@ -5762,6 +6269,9 @@ copy_table (char * const * argv)
           pr "  rv = copy_table (r);\n";
           pr "  for (i = 0; r[i] != NULL; ++i) free (r[i]);\n";
           pr "  free (r);\n";
+       | RBufferOut _ ->
+          pr "  rv = caml_alloc_string (size);\n";
+          pr "  memcpy (String_val (rv), r, size);\n";
       );
 
       pr "  CAMLreturn (rv);\n";
@@ -5787,6 +6297,7 @@ and generate_ocaml_structure_decls () =
       List.iter (
        function
        | name, FString -> pr "  %s : string;\n" name
+       | name, FBuffer -> pr "  %s : string;\n" name
        | name, FUUID -> pr "  %s : string;\n" name
        | name, (FBytes|FInt64|FUInt64) -> pr "  %s : int64;\n" name
        | name, (FInt32|FUInt32) -> pr "  %s : int32;\n" name
@@ -5814,7 +6325,8 @@ and generate_ocaml_prototype ?(is_external = false) name style =
    | RInt64 _ -> pr "int64"
    | RBool _ -> pr "bool"
    | RConstString _ -> pr "string"
-   | RString _ -> pr "string"
+   | RConstOptString _ -> pr "string option"
+   | RString _ | RBufferOut _ -> pr "string"
    | RStringList _ -> pr "string array"
    | RStruct (_, typ) -> pr "%s" typ
    | RStructList (_, typ) -> pr "%s array" typ
@@ -5930,7 +6442,9 @@ DESTROY (g)
        | RInt64 _ -> pr "SV *\n"
        | RBool _ -> pr "SV *\n"
        | RConstString _ -> pr "SV *\n"
+       | RConstOptString _ -> pr "SV *\n"
        | RString _ -> pr "SV *\n"
+       | RBufferOut _ -> pr "SV *\n"
        | RStringList _
        | RStruct _ | RStructList _
        | RHashtable _ ->
@@ -5938,7 +6452,7 @@ DESTROY (g)
       );
       (* Call and arguments. *)
       pr "%s " name;
-      generate_call_args ~handle:"g" (snd style);
+      generate_c_call_args ~handle:"g" ~decl:true style;
       pr "\n";
       pr "      guestfs_h *g;\n";
       iteri (
@@ -5972,7 +6486,7 @@ DESTROY (g)
           pr "      int r;\n";
           pr " PPCODE:\n";
           pr "      r = guestfs_%s " name;
-          generate_call_args ~handle:"g" (snd style);
+          generate_c_call_args ~handle:"g" style;
           pr ";\n";
           do_cleanups ();
           pr "      if (r == -1)\n";
@@ -5983,7 +6497,7 @@ DESTROY (g)
           pr "      int %s;\n" n;
           pr "   CODE:\n";
           pr "      %s = guestfs_%s " n name;
-          generate_call_args ~handle:"g" (snd style);
+          generate_c_call_args ~handle:"g" style;
           pr ";\n";
           do_cleanups ();
           pr "      if (%s == -1)\n" n;
@@ -5996,7 +6510,7 @@ DESTROY (g)
           pr "      int64_t %s;\n" n;
           pr "   CODE:\n";
           pr "      %s = guestfs_%s " n name;
-          generate_call_args ~handle:"g" (snd style);
+          generate_c_call_args ~handle:"g" style;
           pr ";\n";
           do_cleanups ();
           pr "      if (%s == -1)\n" n;
@@ -6009,7 +6523,7 @@ DESTROY (g)
           pr "      const char *%s;\n" n;
           pr "   CODE:\n";
           pr "      %s = guestfs_%s " n name;
-          generate_call_args ~handle:"g" (snd style);
+          generate_c_call_args ~handle:"g" style;
           pr ";\n";
           do_cleanups ();
           pr "      if (%s == NULL)\n" n;
@@ -6017,12 +6531,26 @@ DESTROY (g)
           pr "      RETVAL = newSVpv (%s, 0);\n" n;
           pr " OUTPUT:\n";
           pr "      RETVAL\n"
+       | RConstOptString n ->
+          pr "PREINIT:\n";
+          pr "      const char *%s;\n" n;
+          pr "   CODE:\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 "        RETVAL = &PL_sv_undef;\n";
+          pr "      else\n";
+          pr "        RETVAL = newSVpv (%s, 0);\n" n;
+          pr " OUTPUT:\n";
+          pr "      RETVAL\n"
        | RString n ->
           pr "PREINIT:\n";
           pr "      char *%s;\n" n;
           pr "   CODE:\n";
           pr "      %s = guestfs_%s " n name;
-          generate_call_args ~handle:"g" (snd style);
+          generate_c_call_args ~handle:"g" style;
           pr ";\n";
           do_cleanups ();
           pr "      if (%s == NULL)\n" n;
@@ -6037,7 +6565,7 @@ DESTROY (g)
           pr "      int i, n;\n";
           pr " PPCODE:\n";
           pr "      %s = guestfs_%s " n name;
-          generate_call_args ~handle:"g" (snd style);
+          generate_c_call_args ~handle:"g" style;
           pr ";\n";
           do_cleanups ();
           pr "      if (%s == NULL)\n" n;
@@ -6055,6 +6583,21 @@ DESTROY (g)
        | RStructList (n, typ) ->
           let cols = cols_of_struct typ in
           generate_perl_struct_list_code typ cols name style n do_cleanups
+       | RBufferOut n ->
+          pr "PREINIT:\n";
+          pr "      char *%s;\n" n;
+          pr "      size_t size;\n";
+          pr "   CODE:\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 "      RETVAL = newSVpv (%s, size);\n" n;
+          pr "      free (%s);\n" n;
+          pr " OUTPUT:\n";
+          pr "      RETVAL\n"
       );
 
       pr "\n"
@@ -6067,7 +6610,7 @@ and generate_perl_struct_list_code typ cols name style n do_cleanups =
   pr "      HV *hv;\n";
   pr " PPCODE:\n";
   pr "      %s = guestfs_%s " n name;
-  generate_call_args ~handle:"g" (snd style);
+  generate_c_call_args ~handle:"g" style;
   pr ";\n";
   do_cleanups ();
   pr "      if (%s == NULL)\n" n;
@@ -6083,6 +6626,9 @@ and generate_perl_struct_list_code typ cols name style n do_cleanups =
     | 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
@@ -6108,7 +6654,7 @@ and generate_perl_struct_code typ cols name style n do_cleanups =
   pr "      struct guestfs_%s *%s;\n" typ n;
   pr " PPCODE:\n";
   pr "      %s = guestfs_%s " n name;
-  generate_call_args ~handle:"g" (snd style);
+  generate_c_call_args ~handle:"g" style;
   pr ";\n";
   do_cleanups ();
   pr "      if (%s == NULL)\n" n;
@@ -6122,6 +6668,9 @@ and generate_perl_struct_code typ cols name style n do_cleanups =
       | 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
@@ -6241,7 +6790,10 @@ sub new {
        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
+         pr "%s\n\n" danger_will_robinson;
+       match deprecation_notice flags with
+       | None -> ()
+       | Some txt -> pr "%s\n\n" txt
       )
   ) all_functions_sorted;
 
@@ -6278,7 +6830,9 @@ and generate_perl_prototype name style =
    | RInt n
    | RInt64 n
    | RConstString n
-   | RString n -> pr "$%s = " n
+   | RConstOptString n
+   | RString n
+   | RBufferOut n -> pr "$%s = " n
    | RStruct (n,_)
    | RHashtable n -> pr "%%%s = " n
    | RStringList n
@@ -6454,6 +7008,10 @@ py_guestfs_close (PyObject *self, PyObject *args)
            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"
@@ -6520,12 +7078,17 @@ py_guestfs_close (PyObject *self, PyObject *args)
        match fst style with
        | RErr | RInt _ | RBool _ -> pr "  int r;\n"; "-1"
        | RInt64 _ -> pr "  int64_t r;\n"; "-1"
-       | RConstString _ -> pr "  const char *r;\n"; "NULL"
+       | 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" in
+           pr "  struct guestfs_%s_list *r;\n" typ; "NULL"
+       | RBufferOut _ ->
+           pr "  char *r;\n";
+           pr "  size_t size;\n";
+           "NULL" in
 
       List.iter (
        function
@@ -6576,7 +7139,7 @@ py_guestfs_close (PyObject *self, PyObject *args)
       pr "\n";
 
       pr "  r = guestfs_%s " name;
-      generate_call_args ~handle:"g" (snd style);
+      generate_c_call_args ~handle:"g" style;
       pr ";\n";
 
       List.iter (
@@ -6600,6 +7163,13 @@ py_guestfs_close (PyObject *self, PyObject *args)
        | 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"
@@ -6615,6 +7185,9 @@ py_guestfs_close (PyObject *self, PyObject *args)
        | 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"
       );
 
       pr "  return py_r;\n";
@@ -6718,15 +7291,16 @@ class GuestFS:
   List.iter (
     fun (name, style, _, flags, _, _, longdesc) ->
       pr "    def %s " name;
-      generate_call_args ~handle:"self" (snd style);
+      generate_py_call_args ~handle:"self" (snd style);
       pr ":\n";
 
       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 _ | RConstString _
-         | RString _ -> doc
+         | RErr | RInt _ | RInt64 _ | RBool _
+         | RConstOptString _ | RConstString _
+         | RString _ | RBufferOut _ -> doc
          | RStringList _ ->
              doc ^ "\n\nThis function returns a list of strings."
          | RStruct (_, typ) ->
@@ -6743,17 +7317,27 @@ class GuestFS:
          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_call_args ~handle:"self._o" (snd style);
+      generate_py_call_args ~handle:"self._o" (snd style);
       pr "\n";
       pr "\n";
   ) all_functions
 
+(* 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 ")"
+
 (* Useful if you need the longdesc POD text as plain text.  Returns a
  * list of lines.
  *
@@ -6901,16 +7485,21 @@ static VALUE ruby_guestfs_close (VALUE gv)
        match fst style with
        | RErr | RInt _ | RBool _ -> pr "  int r;\n"; "-1"
        | RInt64 _ -> pr "  int64_t r;\n"; "-1"
-       | RConstString _ -> pr "  const char *r;\n"; "NULL"
+       | 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" in
+           pr "  struct guestfs_%s_list *r;\n" typ; "NULL"
+       | RBufferOut _ ->
+           pr "  char *r;\n";
+           pr "  size_t size;\n";
+           "NULL" in
       pr "\n";
 
       pr "  r = guestfs_%s " name;
-      generate_call_args ~handle:"g" (snd style);
+      generate_c_call_args ~handle:"g" style;
       pr ";\n";
 
       List.iter (
@@ -6933,6 +7522,11 @@ static VALUE ruby_guestfs_close (VALUE gv)
           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 "  VALUE rv = rb_str_new2 (r);\n";
           pr "  free (r);\n";
@@ -6963,6 +7557,10 @@ static VALUE ruby_guestfs_close (VALUE gv)
           pr "  }\n";
           pr "  free (r);\n";
           pr "  return rv;\n"
+       | RBufferOut _ ->
+          pr "  VALUE rv = rb_str_new (r, size);\n";
+          pr "  free (r);\n";
+          pr "  return rv;\n";
       );
 
       pr "}\n";
@@ -6997,6 +7595,8 @@ and generate_ruby_struct_code typ cols =
     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) ->
@@ -7025,6 +7625,8 @@ and generate_ruby_struct_list_code typ cols =
     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) ->
@@ -7127,6 +7729,10 @@ public class GuestFS {
          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
@@ -7152,7 +7758,7 @@ public class GuestFS {
       pr "    ";
       if fst style <> RErr then pr "return ";
       pr "_%s " name;
-      generate_call_args ~handle:"g" (snd style);
+      generate_java_call_args ~handle:"g" (snd style);
       pr ";\n";
       pr "  }\n";
       pr "  ";
@@ -7163,6 +7769,12 @@ public class GuestFS {
 
   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 ")"
+
 and generate_java_prototype ?(public=false) ?(privat=false) ?(native=false)
     ?(semicolon=true) name style =
   if privat then pr "private ";
@@ -7175,7 +7787,8 @@ and generate_java_prototype ?(public=false) ?(privat=false) ?(native=false)
    | RInt _ -> pr "int ";
    | RInt64 _ -> pr "long ";
    | RBool _ -> pr "boolean ";
-   | RConstString _ | RString _ -> pr "String ";
+   | RConstString _ | RConstOptString _ | RString _
+   | RBufferOut _ -> pr "String ";
    | RStringList _ -> pr "String[] ";
    | RStruct (_, typ) ->
        let name = java_name_of_struct typ in
@@ -7236,7 +7849,8 @@ public class %s {
   List.iter (
     function
     | name, FString
-    | name, FUUID -> pr "  public String %s;\n" name
+    | 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
@@ -7303,7 +7917,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
        | RInt _ -> pr "jint ";
        | RInt64 _ -> pr "jlong ";
        | RBool _ -> pr "jboolean ";
-       | RConstString _ | RString _ -> pr "jstring ";
+       | RConstString _ | RConstOptString _ | RString _
+       | RBufferOut _ -> pr "jstring ";
        | RStruct _ | RHashtable _ ->
           pr "jobject ";
        | RStringList _ | RStructList _ ->
@@ -7338,6 +7953,7 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
        | 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"
@@ -7358,7 +7974,12 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
            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" in
+       | 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
        | String n
@@ -7378,7 +7999,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
        (match fst style with
         | RStringList _ | RStructList _ -> true
         | RErr | RBool _ | RInt _ | RInt64 _ | RConstString _
-        | RString _ | RStruct _ | RHashtable _ -> false) ||
+        | RConstOptString _
+        | RString _ | RBufferOut _ | RStruct _ | RHashtable _ -> false) ||
          List.exists (function StringList _ -> true | _ -> false) (snd style) in
       if needs_i then
        pr "  int i;\n";
@@ -7413,7 +8035,7 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
 
       (* Make the call. *)
       pr "  r = guestfs_%s " name;
-      generate_call_args ~handle:"g" (snd style);
+      generate_c_call_args ~handle:"g" style;
       pr ";\n";
 
       (* Release the parameters. *)
@@ -7450,6 +8072,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
        | 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";
@@ -7478,6 +8102,10 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
           (* 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 "}\n";
@@ -7500,6 +8128,15 @@ and generate_java_struct_return typ jtyp cols =
        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;
@@ -7534,6 +8171,15 @@ and generate_java_struct_list_return typ jtyp cols =
        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;
@@ -7565,11 +8211,13 @@ and generate_haskell_hs () =
     | RInt64 _, _ -> true
     | RBool _, _
     | RConstString _, _
+    | RConstOptString _, _
     | RString _, _
     | RStringList _, _
     | RStruct _, _
     | RStructList _, _
-    | RHashtable _, _ -> false in
+    | RHashtable _, _
+    | RBufferOut _, _ -> false in
 
   pr "\
 {-# INCLUDE <guestfs.h> #-}
@@ -7678,8 +8326,9 @@ last_error h = do
             pr "    then do\n";
             pr "      err <- last_error h\n";
             pr "      fail err\n";
-        | RConstString _ | RString _ | RStringList _ | RStruct _
-        | RStructList _ | RHashtable _ ->
+        | RConstString _ | RConstOptString _ | RString _
+        | RStringList _ | RStruct _
+        | RStructList _ | RHashtable _ | RBufferOut _ ->
             pr "  if (r == nullPtr)\n";
             pr "    then do\n";
             pr "      err <- last_error h\n";
@@ -7695,11 +8344,13 @@ last_error h = do
         | RBool _ ->
             pr "    else return (toBool r)\n"
         | RConstString _
+        | RConstOptString _
         | RString _
         | RStringList _
         | RStruct _
         | RStructList _
-        | RHashtable _ ->
+        | RHashtable _
+        | RBufferOut _ ->
             pr "    else return ()\n" (* XXXXXXXXXXXXXXXXXXXX *)
        );
        pr "\n";
@@ -7732,6 +8383,7 @@ and generate_haskell_prototype ~handle ?(hs = false) style =
    | 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) ->
@@ -7741,6 +8393,7 @@ and generate_haskell_prototype ~handle ?(hs = false) style =
        let name = java_name_of_struct typ in
        pr "[%s]" name
    | RHashtable _ -> pr "Hashtable"
+   | RBufferOut _ -> pr "%s" string
   );
   pr ")"
 
@@ -7822,7 +8475,8 @@ print_strings (char * const* const argv)
             pr "  return r;\n"
         | RBool _ ->
             pr "  return strcmp (val, \"true\") == 0;\n"
-        | RConstString _ ->
+        | RConstString _
+        | RConstOptString _ ->
             (* Can't return the input string here.  Return a static
              * string so we ensure we get a segfault if the caller
              * tries to free it.
@@ -7864,6 +8518,8 @@ print_strings (char * const* const argv)
             pr "  }\n";
             pr "  strs[n*2] = NULL;\n";
             pr "  return strs;\n"
+        | RBufferOut _ ->
+            pr "  return strdup (val);\n"
        );
        pr "}\n";
        pr "\n"
@@ -7876,10 +8532,11 @@ print_strings (char * const* const argv)
        (match fst style with
         | RErr | RInt _ | RInt64 _ | RBool _ ->
             pr "  return -1;\n"
-        | RConstString _
+        | RConstString _ | RConstOptString _
         | RString _ | RStringList _ | RStruct _
         | RStructList _
-        | RHashtable _ ->
+        | RHashtable _
+        | RBufferOut _ ->
             pr "  return NULL;\n"
        );
        pr "}\n";
@@ -8297,6 +8954,15 @@ Run it from the top source directory using the command
       close ();
   ) java_structs;
 
+  let close = output_to "java/Makefile.inc" in
+  pr "java_built_sources =";
+  List.iter (
+    fun (typ, jtyp) ->
+        pr " com/redhat/et/libguestfs/%s.java" jtyp;
+  ) java_structs;
+  pr " com/redhat/et/libguestfs/GuestFS.java\n";
+  close ();
+
   let close = output_to "java/com_redhat_et_libguestfs_GuestFS.c" in
   generate_java_c ();
   close ();