Improve warnings about missing tests.
[libguestfs.git] / src / generator.ml
index f851606..7437926 100755 (executable)
@@ -30,7 +30,6 @@
  *
  * IMPORTANT: This script should NOT print any warnings.  If it prints
  * warnings, you should treat them as errors.
  *
  * IMPORTANT: This script should NOT print any warnings.  If it prints
  * warnings, you should treat them as errors.
- * [Need to add -warn-error to ocaml command line]
  *)
 
 #load "unix.cma";;
  *)
 
 #load "unix.cma";;
@@ -44,39 +43,62 @@ and ret =
      * indication, ie. 0 or -1.
      *)
   | RErr
      * 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
     (* "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
     (* "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
     (* "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.
     (* "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
      * 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
   | 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
   | 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 *)
     (* "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 *)
     (* "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
     (* 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
@@ -85,6 +107,7 @@ and ret =
      * inefficient.  Keys should be unique.  NULLs are not permitted.
      *)
   | RHashtable of string
      * inefficient.  Keys should be unique.  NULLs are not permitted.
      *)
   | RHashtable of string
+
     (* "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
     (* "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
@@ -145,15 +168,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 *)
   | 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.
  *
 
 (* You can supply zero or as many tests as you want per API call.
  *
@@ -191,45 +206,60 @@ type tests = (test_init * test_prereq * test) list
 and test =
     (* Run the command sequence and just expect nothing to fail. *)
   | TestRun of seq
 and test =
     (* Run the command sequence and just expect nothing to fail. *)
   | TestRun of seq
+
     (* Run the command sequence and expect the output of the final
      * command to be the string.
      *)
   | TestOutput of seq * string
     (* Run the command sequence and expect the output of the final
      * command to be the string.
      *)
   | TestOutput of seq * string
+
     (* Run the command sequence and expect the output of the final
      * command to be the list of strings.
      *)
   | TestOutputList of seq * string list
     (* Run the command sequence and expect the output of the final
      * command to be the list of strings.
      *)
   | TestOutputList of seq * string list
+
     (* Run the command sequence and expect the output of the final
      * command to be the list of block devices (could be either
      * "/dev/sd.." or "/dev/hd.." form - we don't check the 5th
      * character of each string).
      *)
   | TestOutputListOfDevices of seq * string list
     (* Run the command sequence and expect the output of the final
      * command to be the list of block devices (could be either
      * "/dev/sd.." or "/dev/hd.." form - we don't check the 5th
      * character of each string).
      *)
   | TestOutputListOfDevices of seq * string list
+
     (* Run the command sequence and expect the output of the final
      * command to be the integer.
      *)
   | TestOutputInt of seq * int
     (* Run the command sequence and expect the output of the final
      * command to be the integer.
      *)
   | 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 <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
     (* Run the command sequence and expect the output of the final
      * command to be a true value (!= 0 or != NULL).
      *)
   | TestOutputTrue of seq
+
     (* Run the command sequence and expect the output of the final
      * command to be a false value (== 0 or == NULL, but not an error).
      *)
   | TestOutputFalse of seq
     (* Run the command sequence and expect the output of the final
      * command to be a false value (== 0 or == NULL, but not an error).
      *)
   | TestOutputFalse of seq
+
     (* Run the command sequence and expect the output of the final
      * command to be a list of the given length (but don't care about
      * content).
      *)
   | TestOutputLength of seq * int
     (* Run the command sequence and expect the output of the final
      * command to be a list of the given length (but don't care about
      * content).
      *)
   | 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
     (* Run the command sequence and expect the output of the final
      * command to be a structure.
      *)
   | TestOutputStruct of seq * test_field_compare list
+
     (* Run the command sequence and expect the final command (only)
      * to fail.
      *)
     (* Run the command sequence and expect the final command (only)
      * to fail.
      *)
@@ -246,14 +276,17 @@ and test_field_compare =
 and test_prereq =
     (* Test always runs. *)
   | Always
 and test_prereq =
     (* Test always runs. *)
   | Always
+
     (* Test is currently disabled - eg. it fails, or it tests some
      * unimplemented feature.
      *)
   | Disabled
     (* Test is currently disabled - eg. it fails, or it tests some
      * unimplemented feature.
      *)
   | Disabled
+
     (* 'string' is some C code (a function body) that should return
      * true or false.  The test will run if the code returns true.
      *)
   | If of string
     (* 'string' is some C code (a function body) that should return
      * true or false.  The test will run if the code returns true.
      *)
   | If of string
+
     (* As for 'If' but the test runs _unless_ the code returns true. *)
   | Unless of string
 
     (* As for 'If' but the test runs _unless_ the code returns true. *)
   | Unless of string
 
@@ -264,14 +297,17 @@ and test_init =
      * a bad idea.
      *)
   | InitNone
      * a bad idea.
      *)
   | InitNone
+
     (* Block devices are empty and no filesystems are mounted. *)
   | InitEmpty
     (* 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 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):
     (* /dev/sda:
      *   /dev/sda1 (is a PV):
      *     /dev/VG/LV (size 8MB):
@@ -280,6 +316,11 @@ and test_init =
      *)
   | InitBasicFSonLVM
 
      *)
   | 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
 (* Sequence of commands for testing. *)
 and seq = cmd list
 and cmd = string list
@@ -310,6 +351,7 @@ let test_all_rets = [
   "test0rint64",       RInt64 "valout";
   "test0rbool",        RBool "valout";
   "test0rconststring", RConstString "valout";
   "test0rint64",       RInt64 "valout";
   "test0rbool",        RBool "valout";
   "test0rconststring", RConstString "valout";
+  "test0rconstoptstring", RConstOptString "valout";
   "test0rstring",      RString "valout";
   "test0rstringlist",  RStringList "valout";
   "test0rstruct",      RStruct ("valout", "lvm_pv");
   "test0rstring",      RString "valout";
   "test0rstringlist",  RStringList "valout";
   "test0rstruct",      RStruct ("valout", "lvm_pv");
@@ -517,7 +559,7 @@ 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).");
 
 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.
    (* This cannot be tested with the current framework.  The
     * function can return NULL in normal operations, which the
     * test framework interprets as an error.
@@ -530,31 +572,6 @@ guest kernel command line.
 
 If C<NULL> then no options are added.");
 
 
 If C<NULL> then no options are added.");
 
-  ("set_kernel", (RErr, [OptString "kernel"]), -1, [FishAlias "kernel"],
-   [],
-   "override the normal appliance kernel",
-   "\
-This function lets you override the ordinary selection
-of kernel used in the appliance.
-
-The default is C<NULL> unless overridden by setting
-C<LIBGUESTFS_KERNEL> environment variable.
-
-Setting C<kernel> to C<NULL> means the ordinary appliance
-kernel is selected by the usual means.");
-
-  ("get_kernel", (RConstString "kernel", []), -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 override appliance kernel",
-   "\
-Return the override appliance kernel (see C<guestfs_set_kernel>).
-
-If C<NULL> then the ordinary appliance kernel is used.");
-
   ("set_autosync", (RErr, [Bool "autosync"]), -1, [FishAlias "autosync"],
    [],
    "set autosync mode",
   ("set_autosync", (RErr, [Bool "autosync"]), -1, [FishAlias "autosync"],
    [],
    "set autosync mode",
@@ -792,9 +809,8 @@ update the timestamps on a file, or, if the file does not exist,
 to create a new zero-length file.");
 
   ("cat", (RString "content", [String "path"]), 4, [ProtocolLimitWarning],
 to create a new zero-length file.");
 
   ("cat", (RString "content", [String "path"]), 4, [ProtocolLimitWarning],
-   [InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "new file contents"; "0"];
-       ["cat"; "/new"]], "new file contents")],
+   [InitSquashFS, Always, TestOutput (
+      [["cat"; "/known-2"]], "abcdef\n")],
    "list the contents of a file",
    "\
 Return the contents of the file named C<path>.
    "list the contents of a file",
    "\
 Return the contents of the file named C<path>.
@@ -941,12 +957,10 @@ List all the logical volumes detected.  This is the equivalent
 of the L<lvs(8)> command.  The \"full\" version includes all fields.");
 
   ("read_lines", (RStringList "lines", [String "path"]), 15, [],
 of the L<lvs(8)> command.  The \"full\" version includes all fields.");
 
   ("read_lines", (RStringList "lines", [String "path"]), 15, [],
-   [InitBasicFS, Always, TestOutputList (
-      [["write_file"; "/new"; "line1\r\nline2\nline3"; "0"];
-       ["read_lines"; "/new"]], ["line1"; "line2"; "line3"]);
-    InitBasicFS, Always, TestOutputList (
-      [["write_file"; "/new"; ""; "0"];
-       ["read_lines"; "/new"]], [])],
+   [InitSquashFS, Always, TestOutputList (
+      [["read_lines"; "/known-4"]], ["abc"; "def"; "ghi"]);
+    InitSquashFS, Always, TestOutputList (
+      [["read_lines"; "/empty"]], [])],
    "read file as lines",
    "\
 Return the contents of the file named C<path>.
    "read file as lines",
    "\
 Return the contents of the file named C<path>.
@@ -1207,12 +1221,10 @@ names, you will need to locate and parse the password file
 yourself (Augeas support makes this relatively easy).");
 
   ("exists", (RBool "existsflag", [String "path"]), 36, [],
 yourself (Augeas support makes this relatively easy).");
 
   ("exists", (RBool "existsflag", [String "path"]), 36, [],
-   [InitBasicFS, Always, TestOutputTrue (
-      [["touch"; "/new"];
-       ["exists"; "/new"]]);
-    InitBasicFS, Always, TestOutputTrue (
-      [["mkdir"; "/new"];
-       ["exists"; "/new"]])],
+   [InitSquashFS, Always, TestOutputTrue (
+      [["exists"; "/empty"]]);
+    InitSquashFS, Always, TestOutputTrue (
+      [["exists"; "/directory"]])],
    "test if file or directory exists",
    "\
 This returns C<true> if and only if there is a file, directory
    "test if file or directory exists",
    "\
 This returns C<true> if and only if there is a file, directory
@@ -1221,12 +1233,10 @@ This returns C<true> if and only if there is a file, directory
 See also C<guestfs_is_file>, C<guestfs_is_dir>, C<guestfs_stat>.");
 
   ("is_file", (RBool "fileflag", [String "path"]), 37, [],
 See also C<guestfs_is_file>, C<guestfs_is_dir>, C<guestfs_stat>.");
 
   ("is_file", (RBool "fileflag", [String "path"]), 37, [],
-   [InitBasicFS, Always, TestOutputTrue (
-      [["touch"; "/new"];
-       ["is_file"; "/new"]]);
-    InitBasicFS, Always, TestOutputFalse (
-      [["mkdir"; "/new"];
-       ["is_file"; "/new"]])],
+   [InitSquashFS, Always, TestOutputTrue (
+      [["is_file"; "/known-1"]]);
+    InitSquashFS, Always, TestOutputFalse (
+      [["is_file"; "/directory"]])],
    "test if file exists",
    "\
 This returns C<true> if and only if there is a file
    "test if file exists",
    "\
 This returns C<true> if and only if there is a file
@@ -1236,12 +1246,10 @@ other objects like directories.
 See also C<guestfs_stat>.");
 
   ("is_dir", (RBool "dirflag", [String "path"]), 38, [],
 See also C<guestfs_stat>.");
 
   ("is_dir", (RBool "dirflag", [String "path"]), 38, [],
-   [InitBasicFS, Always, TestOutputFalse (
-      [["touch"; "/new"];
-       ["is_dir"; "/new"]]);
-    InitBasicFS, Always, TestOutputTrue (
-      [["mkdir"; "/new"];
-       ["is_dir"; "/new"]])],
+   [InitSquashFS, Always, TestOutputFalse (
+      [["is_dir"; "/known-3"]]);
+    InitSquashFS, Always, TestOutputTrue (
+      [["is_dir"; "/directory"]])],
    "test if file exists",
    "\
 This returns C<true> if and only if there is a directory
    "test if file exists",
    "\
 This returns C<true> if and only if there is a directory
@@ -1435,21 +1443,22 @@ This command removes all LVM logical volumes, volume groups
 and physical volumes.");
 
   ("file", (RString "description", [String "path"]), 49, [],
 and physical volumes.");
 
   ("file", (RString "description", [String "path"]), 49, [],
-   [InitBasicFS, Always, TestOutput (
-      [["touch"; "/new"];
-       ["file"; "/new"]], "empty");
-    InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "some content\n"; "0"];
-       ["file"; "/new"]], "ASCII text");
-    InitBasicFS, Always, TestLastFail (
-      [["file"; "/nofile"]])],
+   [InitSquashFS, Always, TestOutput (
+      [["file"; "/empty"]], "empty");
+    InitSquashFS, Always, TestOutput (
+      [["file"; "/known-1"]], "ASCII text");
+    InitSquashFS, Always, TestLastFail (
+      [["file"; "/notexists"]])],
    "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.
 
    "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 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).");
 
 particular that the filename is not prepended to the output
 (the C<-b> option).");
 
@@ -1587,9 +1596,8 @@ result into a list of lines.
 See also: C<guestfs_sh_lines>");
 
   ("stat", (RStruct ("statbuf", "stat"), [String "path"]), 52, [],
 See also: C<guestfs_sh_lines>");
 
   ("stat", (RStruct ("statbuf", "stat"), [String "path"]), 52, [],
-   [InitBasicFS, Always, TestOutputStruct (
-      [["touch"; "/new"];
-       ["stat"; "/new"]], [CompareWithInt ("size", 0)])],
+   [InitSquashFS, Always, TestOutputStruct (
+      [["stat"; "/empty"]], [CompareWithInt ("size", 0)])],
    "get file information",
    "\
 Returns file information for the given C<path>.
    "get file information",
    "\
 Returns file information for the given C<path>.
@@ -1597,9 +1605,8 @@ Returns file information for the given C<path>.
 This is the same as the C<stat(2)> system call.");
 
   ("lstat", (RStruct ("statbuf", "stat"), [String "path"]), 53, [],
 This is the same as the C<stat(2)> system call.");
 
   ("lstat", (RStruct ("statbuf", "stat"), [String "path"]), 53, [],
-   [InitBasicFS, Always, TestOutputStruct (
-      [["touch"; "/new"];
-       ["lstat"; "/new"]], [CompareWithInt ("size", 0)])],
+   [InitSquashFS, Always, TestOutputStruct (
+      [["lstat"; "/empty"]], [CompareWithInt ("size", 0)])],
    "get file information for a symbolic link",
    "\
 Returns file information for the given C<path>.
    "get file information for a symbolic link",
    "\
 Returns file information for the given C<path>.
@@ -1611,9 +1618,9 @@ refers to.
 This is the same as the C<lstat(2)> system call.");
 
   ("statvfs", (RStruct ("statbuf", "statvfs"), [String "path"]), 54, [],
 This is the same as the C<lstat(2)> system call.");
 
   ("statvfs", (RStruct ("statbuf", "statvfs"), [String "path"]), 54, [],
-   [InitBasicFS, Always, TestOutputStruct (
-      [["statvfs"; "/"]], [CompareWithInt ("namemax", 255);
-                          CompareWithInt ("bsize", 1024)])],
+   [InitSquashFS, Always, TestOutputStruct (
+      [["statvfs"; "/"]], [CompareWithInt ("namemax", 256);
+                          CompareWithInt ("bsize", 131072)])],
    "get file system statistics",
    "\
 Returns file system statistics for any mounted file system.
    "get file system statistics",
    "\
 Returns file system statistics for any mounted file system.
@@ -1776,35 +1783,22 @@ C<filename> can also be a named pipe.
 See also C<guestfs_upload>, C<guestfs_cat>.");
 
   ("checksum", (RString "checksum", [String "csumtype"; String "path"]), 68, [],
 See also C<guestfs_upload>, C<guestfs_cat>.");
 
   ("checksum", (RString "checksum", [String "csumtype"; String "path"]), 68, [],
-   [InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "test\n"; "0"];
-       ["checksum"; "crc"; "/new"]], "935282863");
-    InitBasicFS, Always, TestLastFail (
-      [["checksum"; "crc"; "/new"]]);
-    InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "test\n"; "0"];
-       ["checksum"; "md5"; "/new"]], "d8e8fca2dc0f896fd7cb4cb0031ba249");
-    InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "test\n"; "0"];
-       ["checksum"; "sha1"; "/new"]], "4e1243bd22c66e76c2ba9eddc1f91394e57f9f83");
-    InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "test\n"; "0"];
-       ["checksum"; "sha224"; "/new"]], "52f1bf093f4b7588726035c176c0cdb4376cfea53819f1395ac9e6ec");
-    InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "test\n"; "0"];
-       ["checksum"; "sha256"; "/new"]], "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2");
-    InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "test\n"; "0"];
-       ["checksum"; "sha384"; "/new"]], "109bb6b5b6d5547c1ce03c7a8bd7d8f80c1cb0957f50c4f7fda04692079917e4f9cad52b878f3d8234e1a170b154b72d");
-    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"; "crc"; "/known-3"]], "2891671662");
+    InitSquashFS, Always, TestLastFail (
+      [["checksum"; "crc"; "/notexists"]]);
+    InitSquashFS, Always, TestOutput (
+      [["checksum"; "md5"; "/known-3"]], "46d6ca27ee07cdc6fa99c2e138cc522c");
+    InitSquashFS, Always, TestOutput (
+      [["checksum"; "sha1"; "/known-3"]], "b7ebccc3ee418311091c3eda0a45b83c0a770f15");
+    InitSquashFS, Always, TestOutput (
+      [["checksum"; "sha224"; "/known-3"]], "d2cd1774b28f3659c14116be0a6dc2bb5c4b350ce9cd5defac707741");
+    InitSquashFS, Always, TestOutput (
+      [["checksum"; "sha256"; "/known-3"]], "75bb71b90cd20cb13f86d2bea8dad63ac7194e7517c3b52b8d06ff52d3487d30");
+    InitSquashFS, Always, TestOutput (
+      [["checksum"; "sha384"; "/known-3"]], "5fa7883430f357b5d7b7271d3a1d2872b51d73cba72731de6863d3dea55f30646af2799bef44d5ea776a5ec7941ac640");
+    InitSquashFS, Always, TestOutput (
+      [["checksum"; "sha512"; "/known-3"]], "2794062c328c6b216dca90443b7f7134c5f40e56bd0ed7853123275a09982a6f992e6ca682f9d2fba34a4c5e870d8fe077694ff831e3032a004ee077e00603f6")],
    "compute MD5, SHAx or CRC checksum of file",
    "\
 This call computes the MD5, SHAx or CRC checksum of the
    "compute MD5, SHAx or CRC checksum of file",
    "\
 This call computes the MD5, SHAx or CRC checksum of the
@@ -2242,21 +2236,18 @@ true if their content is exactly equal, or false otherwise.
 The external L<cmp(1)> program is used for the comparison.");
 
   ("strings", (RStringList "stringsout", [String "path"]), 94, [ProtocolLimitWarning],
 The external L<cmp(1)> program is used for the comparison.");
 
   ("strings", (RStringList "stringsout", [String "path"]), 94, [ProtocolLimitWarning],
-   [InitBasicFS, Always, TestOutputList (
-      [["write_file"; "/new"; "hello\nworld\n"; "0"];
-       ["strings"; "/new"]], ["hello"; "world"]);
-    InitBasicFS, Always, TestOutputList (
-      [["touch"; "/new"];
-       ["strings"; "/new"]], [])],
+   [InitSquashFS, Always, TestOutputList (
+      [["strings"; "/known-5"]], ["abcdefghi"; "jklmnopqr"]);
+    InitSquashFS, Always, TestOutputList (
+      [["strings"; "/empty"]], [])],
    "print the printable strings in a file",
    "\
 This runs the L<strings(1)> command on a file and returns
 the list of printable strings found.");
 
   ("strings_e", (RStringList "stringsout", [String "encoding"; String "path"]), 95, [ProtocolLimitWarning],
    "print the printable strings in a file",
    "\
 This runs the L<strings(1)> command on a file and returns
 the list of printable strings found.");
 
   ("strings_e", (RStringList "stringsout", [String "encoding"; String "path"]), 95, [ProtocolLimitWarning],
-   [InitBasicFS, Always, TestOutputList (
-      [["write_file"; "/new"; "hello\nworld\n"; "0"];
-       ["strings_e"; "b"; "/new"]], []);
+   [InitSquashFS, 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"];
        ["strings_e"; "b"; "/new"]], ["hello"; "world"])],
     InitBasicFS, Disabled, TestOutputList (
       [["write_file"; "/new"; "\000h\000e\000l\000l\000o\000\n\000w\000o\000r\000l\000d\000\n"; "24"];
        ["strings_e"; "b"; "/new"]], ["hello"; "world"])],
@@ -2273,15 +2264,13 @@ show strings inside Windows/x86 files.
 The returned strings are transcoded to UTF-8.");
 
   ("hexdump", (RString "dump", [String "path"]), 96, [ProtocolLimitWarning],
 The returned strings are transcoded to UTF-8.");
 
   ("hexdump", (RString "dump", [String "path"]), 96, [ProtocolLimitWarning],
-   [InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "hello\nworld\n"; "12"];
-       ["hexdump"; "/new"]], "00000000  68 65 6c 6c 6f 0a 77 6f  72 6c 64 0a              |hello.world.|\n0000000c\n");
+   [InitSquashFS, Always, TestOutput (
+      [["hexdump"; "/known-4"]], "00000000  61 62 63 0a 64 65 66 0a  67 68 69                 |abc.def.ghi|\n0000000b\n");
     (* Test for RHBZ#501888c2 regression which caused large hexdump
      * commands to segfault.
      *)
     (* 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
    "dump a file in hexadecimal",
    "\
 This runs C<hexdump -C> on the given C<path>.  The result is
@@ -2618,51 +2607,44 @@ directory and its contents after use.
 See also: L<mkdtemp(3)>");
 
   ("wc_l", (RInt "lines", [String "path"]), 118, [],
 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, [],
    "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, [],
    "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],
    "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],
    "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
    "return first N lines of a file",
    "\
 If the parameter C<nrlines> is a positive number, this returns the first
@@ -2674,24 +2656,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],
 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],
    "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
    "return last N lines of a file",
    "\
 If the parameter C<nrlines> is a positive number, this returns the last
@@ -2728,9 +2706,8 @@ is I<not> intended that you try to parse the output string.
 Use C<statvfs> from programs.");
 
   ("du", (RInt64 "sizekb", [String "path"]), 127, [],
 Use C<statvfs> from programs.");
 
   ("du", (RInt64 "sizekb", [String "path"]), 127, [],
-   [InitBasicFS, Always, TestOutputInt (
-      [["mkdir"; "/p"];
-       ["du"; "/p"]], 1 (* ie. 1 block, so depends on ext3 blocksize *))],
+   [InitSquashFS, Always, TestOutputInt (
+      [["du"; "/directory"]], 0 (* squashfs doesn't have blocks *))],
    "estimate file space usage",
    "\
 This command runs the C<du -s> command to estimate file space
    "estimate file space usage",
    "\
 This command runs the C<du -s> command to estimate file space
@@ -2744,9 +2721,8 @@ The result is the estimated size in I<kilobytes>
 (ie. units of 1024 bytes).");
 
   ("initrd_list", (RStringList "filenames", [String "path"]), 128, [],
 (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";"known-4"; "known-5"])],
    "list files in an initrd",
    "\
 This command lists out files contained in an initrd.
    "list files in an initrd",
    "\
 This command lists out files contained in an initrd.
@@ -2781,7 +2757,11 @@ Create a swap partition on C<device>.");
        ["mkswap_L"; "hello"; "/dev/sda1"]])],
    "create a swap partition with a label",
    "\
        ["mkswap_L"; "hello"; "/dev/sda1"]])],
    "create a swap partition with a label",
    "\
-Create a swap partition on C<device> with label C<label>.");
+Create a swap partition on C<device> with label C<label>.
+
+Note that you cannot attach a swap label to a block device
+(eg. C</dev/sda>), just to a partition.  This appears to be
+a limitation of the kernel or swap tools.");
 
   ("mkswap_U", (RErr, [String "uuid"; String "device"]), 132, [],
    [InitEmpty, Always, TestRun (
 
   ("mkswap_U", (RErr, [String "uuid"; String "device"]), 132, [],
    [InitEmpty, Always, TestRun (
@@ -2931,7 +2911,7 @@ were rarely if ever used anyway.
 
 See also C<guestfs_sfdisk> and the L<sfdisk(8)> manpage.");
 
 
 See also C<guestfs_sfdisk> and the L<sfdisk(8)> manpage.");
 
-  ("zfile", (RString "description", [String "method"; String "path"]), 140, [],
+  ("zfile", (RString "description", [String "method"; String "path"]), 140, [DeprecatedBy "file"],
    [],
    "determine file type inside a compressed file",
    "\
    [],
    "determine file type inside a compressed file",
    "\
@@ -2940,7 +2920,8 @@ using C<method>.
 
 C<method> must be one of C<gzip>, C<compress> or C<bzip2>.
 
 
 C<method> must be one of C<gzip>, C<compress> or C<bzip2>.
 
-See also: C<guestfs_file>");
+Since 1.0.63, use C<guestfs_file> instead which can now
+process compressed files.");
 
   ("getxattrs", (RStructList ("xattrs", "xattr"), [String "path"]), 141, [],
    [],
 
   ("getxattrs", (RStructList ("xattrs", "xattr"), [String "path"]), 141, [],
    [],
@@ -3046,9 +3027,8 @@ with C<guestfs_mkmountpoint>.  See C<guestfs_mkmountpoint>
 for full details.");
 
   ("read_file", (RBufferOut "content", [String "path"]), 150, [ProtocolLimitWarning],
 for full details.");
 
   ("read_file", (RBufferOut "content", [String "path"]), 150, [ProtocolLimitWarning],
-   [InitBasicFS, Always, TestOutput (
-      [["write_file"; "/new"; "new file contents"; "0"];
-       ["read_file"; "/new"]], "new file contents")],
+   [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
    "read a file",
    "\
 This calls returns the contents of the file C<path> as a
@@ -3059,6 +3039,262 @@ 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.");
 
 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.");
+
+  ("realpath", (RString "rpath", [String "path"]), 163, [],
+   [InitSquashFS, Always, TestOutput (
+      [["realpath"; "/../directory"]], "/directory")],
+   "canonicalized absolute pathname",
+   "\
+Return the canonicalized absolute pathname of C<path>.  The
+returned path has no C<.>, C<..> or symbolic link path elements.");
+
+  ("ln", (RErr, [String "target"; String "linkname"]), 164, [],
+   [InitBasicFS, Always, TestOutputStruct (
+      [["touch"; "/a"];
+       ["ln"; "/a"; "/b"];
+       ["stat"; "/b"]], [CompareWithInt ("nlink", 2)])],
+   "create a hard link",
+   "\
+This command creates a hard link using the C<ln> command.");
+
+  ("ln_f", (RErr, [String "target"; String "linkname"]), 165, [],
+   [InitBasicFS, Always, TestOutputStruct (
+      [["touch"; "/a"];
+       ["touch"; "/b"];
+       ["ln_f"; "/a"; "/b"];
+       ["stat"; "/b"]], [CompareWithInt ("nlink", 2)])],
+   "create a hard link",
+   "\
+This command creates a hard link using the C<ln -f> command.
+The C<-f> option removes the link (C<linkname>) if it exists already.");
+
+  ("ln_s", (RErr, [String "target"; String "linkname"]), 166, [],
+   [InitBasicFS, Always, TestOutputStruct (
+      [["touch"; "/a"];
+       ["ln_s"; "a"; "/b"];
+       ["lstat"; "/b"]], [CompareWithInt ("mode", 0o120777)])],
+   "create a symbolic link",
+   "\
+This command creates a symbolic link using the C<ln -s> command.");
+
+  ("ln_sf", (RErr, [String "target"; String "linkname"]), 167, [],
+   [InitBasicFS, Always, TestOutput (
+      [["mkdir_p"; "/a/b"];
+       ["touch"; "/a/b/c"];
+       ["ln_sf"; "../d"; "/a/b/c"];
+       ["readlink"; "/a/b/c"]], "../d")],
+   "create a symbolic link",
+   "\
+This command creates a symbolic link using the C<ln -sf> command,
+The C<-f> option removes the link (C<linkname>) if it exists already.");
+
+  ("readlink", (RString "link", [String "path"]), 168, [],
+   [] (* XXX tested above *),
+   "read the target of a symbolic link",
+   "\
+This command reads the target of a symbolic link.");
+
+  ("fallocate", (RErr, [String "path"; Int "len"]), 169, [],
+   [InitBasicFS, Always, TestOutputStruct (
+      [["fallocate"; "/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.
+
+Do not confuse this with the guestfish-specific
+C<alloc> command which allocates a file in the host and
+attaches it as a device.");
+
+  ("swapon_device", (RErr, [String "device"]), 170, [],
+   [InitNone, Always, TestRun (
+      [["mkswap"; "/dev/sdb"];
+       ["swapon_device"; "/dev/sdb"];
+       ["swapoff_device"; "/dev/sdb"]])],
+   "enable swap on device",
+   "\
+This command enables the libguestfs appliance to use the
+swap device or partition named C<device>.  The increased
+memory is made available for all commands, for example
+those run using C<guestfs_command> or C<guestfs_sh>.
+
+Note that you should not swap to existing guest swap
+partitions unless you know what you are doing.  They may
+contain hibernation information, or other information that
+the guest doesn't want you to trash.  You also risk leaking
+information about the host to the guest this way.  Instead,
+attach a new host device to the guest and swap on that.");
+
+  ("swapoff_device", (RErr, [String "device"]), 171, [],
+   [], (* XXX tested by swapon_device *)
+   "disable swap on device",
+   "\
+This command disables the libguestfs appliance swap
+device or partition named C<device>.
+See C<guestfs_swapon_device>.");
+
+  ("swapon_file", (RErr, [String "file"]), 172, [],
+   [InitBasicFS, Always, TestRun (
+      [["fallocate"; "/swap"; "8388608"];
+       ["mkswap_file"; "/swap"];
+       ["swapon_file"; "/swap"];
+       ["swapoff_file"; "/swap"]])],
+   "enable swap on file",
+   "\
+This command enables swap to a file.
+See C<guestfs_swapon_device> for other notes.");
+
+  ("swapoff_file", (RErr, [String "file"]), 173, [],
+   [], (* XXX tested by swapon_file *)
+   "disable swap on file",
+   "\
+This command disables the libguestfs appliance swap on file.");
+
+  ("swapon_label", (RErr, [String "label"]), 174, [],
+   [InitEmpty, Always, TestRun (
+      [["sfdiskM"; "/dev/sdb"; ","];
+       ["mkswap_L"; "swapit"; "/dev/sdb1"];
+       ["swapon_label"; "swapit"];
+       ["swapoff_label"; "swapit"]])],
+   "enable swap on labelled swap partition",
+   "\
+This command enables swap to a labelled swap partition.
+See C<guestfs_swapon_device> for other notes.");
+
+  ("swapoff_label", (RErr, [String "label"]), 175, [],
+   [], (* XXX tested by swapon_label *)
+   "disable swap on labelled swap partition",
+   "\
+This command disables the libguestfs appliance swap on
+labelled swap partition.");
+
+  ("swapon_uuid", (RErr, [String "uuid"]), 176, [],
+   [InitEmpty, Always, TestRun (
+      [["mkswap_U"; "a3a61220-882b-4f61-89f4-cf24dcc7297d"; "/dev/sdb"];
+       ["swapon_uuid"; "a3a61220-882b-4f61-89f4-cf24dcc7297d"];
+       ["swapoff_uuid"; "a3a61220-882b-4f61-89f4-cf24dcc7297d"]])],
+   "enable swap on swap partition by UUID",
+   "\
+This command enables swap to a swap partition with the given UUID.
+See C<guestfs_swapon_device> for other notes.");
+
+  ("swapoff_uuid", (RErr, [String "uuid"]), 177, [],
+   [], (* XXX tested by swapon_uuid *)
+   "disable swap on swap partition by UUID",
+   "\
+This command disables the libguestfs appliance swap partition
+with the given UUID.");
+
+  ("mkswap_file", (RErr, [String "path"]), 178, [],
+   [InitBasicFS, Always, TestRun (
+      [["fallocate"; "/swap"; "8388608"];
+       ["mkswap_file"; "/swap"]])],
+   "create a swap file",
+   "\
+Create a swap file.
+
+This command just writes a swap file signature to an existing
+file.  To create the file itself, use something like C<guestfs_fallocate>.");
+
 ]
 
 let all_functions = non_daemon_functions @ daemon_functions
 ]
 
 let all_functions = non_daemon_functions @ daemon_functions
@@ -3345,6 +3581,13 @@ let files_equal n1 n2 =
   | 1 -> false
   | i -> failwithf "%s: failed with error code %d" cmd i
 
   | 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 ->
 let rec find_map f = function
   | [] -> raise Not_found
   | x :: xs ->
@@ -3386,9 +3629,35 @@ let seq_of_test = function
   | TestOutputListOfDevices (s, _)
   | TestOutputInt (s, _) | TestOutputIntOp (s, _, _)
   | TestOutputTrue s | TestOutputFalse s
   | TestOutputListOfDevices (s, _)
   | TestOutputInt (s, _) | TestOutputIntOp (s, _, _)
   | TestOutputTrue s | TestOutputFalse s
-  | TestOutputLength (s, _) | TestOutputStruct (s, _)
+  | TestOutputLength (s, _) | TestOutputBuffer (s, _)
+  | TestOutputStruct (s, _)
   | TestLastFail s -> 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 =
 (* Check function names etc. for consistency. *)
 let check_functions () =
   let contains_uppercase str =
@@ -3440,7 +3709,8 @@ let check_functions () =
 
       (match fst style with
        | RErr -> ()
 
       (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 | RBufferOut n ->
           check_arg_ret_name n
        | RStringList n | RStruct (n, _) | RStructList (n, _)
        | RHashtable n | RBufferOut n ->
           check_arg_ret_name n
@@ -3602,6 +3872,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"
         | 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"
         | RString _ ->
             pr "This function returns a string, or NULL on error.
 I<The caller must free the returned string after use>.\n\n"
@@ -3632,7 +3906,10 @@ 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
        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
 
       )
   ) all_functions_sorted
 
@@ -3743,8 +4020,8 @@ and generate_xdr () =
           pr "struct %s_ret {\n" name;
           pr "  bool %s;\n" n;
           pr "};\n\n"
           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;
        | RString n ->
           pr "struct %s_ret {\n" name;
           pr "  string %s<>;\n" n;
@@ -3985,8 +4262,8 @@ check_state (guestfs_h *g, const char *caller)
       pr "  struct guestfs_message_error err;\n";
       (match fst style with
        | RErr -> ()
       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 _
        | RInt _ | RInt64 _
        | RBool _ | RString _ | RStringList _
        | RStruct _ | RStructList _
@@ -4026,8 +4303,8 @@ check_state (guestfs_h *g, const char *caller)
 
       (match fst style with
        | RErr -> ()
 
       (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 _
        | RInt _ | RInt64 _
        | RBool _ | RString _ | RStringList _
        | RStruct _ | RStructList _
@@ -4049,8 +4326,8 @@ check_state (guestfs_h *g, const char *caller)
       let error_code =
        match fst style with
        | RErr | RInt _ | RInt64 _ | RBool _ -> "-1"
       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 _ | RBufferOut _ ->
        | RString _ | RStringList _
        | RStruct _ | RStructList _
        | RHashtable _ | RBufferOut _ ->
@@ -4173,8 +4450,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
        | 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 ->
        | RString n ->
           pr "  return ctx.ret.%s; /* caller will free */\n" n
        | RStringList n | RHashtable n ->
@@ -4269,8 +4546,8 @@ and generate_daemon_actions () =
        | RErr | RInt _ -> pr "  int r;\n"; "-1"
        | RInt64 _ -> pr "  int64_t r;\n"; "-1"
        | RBool _ -> pr "  int r;\n"; "-1"
        | 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"
        | RString _ -> pr "  char *r;\n"; "NULL"
        | RStringList _ | RHashtable _ -> pr "  char **r;\n"; "NULL"
        | RStruct (_, typ) -> pr "  guestfs_int_%s *r;\n" typ; "NULL"
@@ -4359,8 +4636,8 @@ and generate_daemon_actions () =
            pr "  ret.%s = r;\n" n;
            pr "  reply ((xdrproc_t) &xdr_guestfs_%s_ret, (char *) &ret);\n"
              name
            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;
        | RString n ->
            pr "  struct guestfs_%s_ret ret;\n" name;
            pr "  ret.%s = r;\n" n;
@@ -4653,15 +4930,29 @@ static void print_table (char * const * const argv)
 }
 */
 
 }
 */
 
-static void no_test_warnings (void)
-{
 ";
 
 ";
 
+  (* Generate a list of commands which are not tested anywhere. *)
+  pr "static void no_test_warnings (void)\n";
+  pr "{\n";
+
+  let hash : (string, bool) Hashtbl.t = Hashtbl.create 13 in
   List.iter (
   List.iter (
-    function
-    | name, _, _, _, [], _, _ ->
+    fun (_, _, _, _, tests, _, _) ->
+      let tests = filter_map (
+       function
+       | (_, (Always|If _|Unless _), test) -> Some test
+       | (_, Disabled, _) -> None
+      ) tests in
+      let seq = List.concat (List.map seq_of_test tests) in
+      let cmds_tested = List.map List.hd seq in
+      List.iter (fun cmd -> Hashtbl.replace hash cmd true) cmds_tested
+  ) all_functions;
+
+  List.iter (
+    fun (name, _, _, _, _, _, _) ->
+      if not (Hashtbl.mem hash name) then
        pr "  fprintf (stderr, \"warning: \\\"guestfs_%s\\\" has no tests\\n\");\n" name
        pr "  fprintf (stderr, \"warning: \\\"guestfs_%s\\\" has no tests\\n\");\n" name
-    | name, _, _, _, tests, _, _ -> ()
   ) all_functions;
 
   pr "}\n";
   ) all_functions;
 
   pr "}\n";
@@ -4939,6 +5230,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"; "/"]]
          ["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
   );
 
   let get_seq_last = function
@@ -5094,6 +5392,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
       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
   | TestOutputStruct (seq, checks) ->
       pr "  /* TestOutputStruct for %s (%d) */\n" name i;
       let seq, last = get_seq_last seq in
@@ -5194,7 +5509,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"
        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";
        | RString _ -> pr "    char *r;\n"; "NULL"
        | RStringList _ | RHashtable _ ->
            pr "    char **r;\n";
@@ -5253,7 +5569,8 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
       );
 
       (match fst style with
       );
 
       (match fst style with
-       | RErr | RInt _ | RInt64 _ | RBool _ | RConstString _ -> ()
+       | RErr | RInt _ | RInt64 _ | RBool _
+       | RConstString _ | RConstOptString _ -> ()
        | RString _ | RBufferOut _ -> pr "    free (r);\n"
        | RStringList _ | RHashtable _ ->
           pr "    for (i = 0; r[i] != NULL; ++i)\n";
        | RString _ | RBufferOut _ -> pr "    free (r);\n"
        | RStringList _ | RHashtable _ ->
           pr "    for (i = 0; r[i] != NULL; ++i)\n";
@@ -5300,15 +5617,16 @@ and generate_fish_cmds () =
   (* list_commands function, which implements guestfish -h *)
   pr "void list_commands (void)\n";
   pr "{\n";
   (* list_commands function, which implements guestfish -h *)
   pr "void list_commands (void)\n";
   pr "{\n";
-  pr "  printf (\"    %%-16s     %%s\\n\", \"Command\", \"Description\");\n";
+  pr "  printf (\"    %%-16s     %%s\\n\", _(\"Command\"), _(\"Description\"));\n";
   pr "  list_builtin_commands ();\n";
   List.iter (
     fun (name, _, _, flags, _, shortdesc, _) ->
       let name = replace_char name '_' '-' in
   pr "  list_builtin_commands ();\n";
   List.iter (
     fun (name, _, _, flags, _, shortdesc, _) ->
       let name = replace_char name '_' '-' in
-      pr "  printf (\"%%-20s %%s\\n\", \"%s\", \"%s\");\n"
+      pr "  printf (\"%%-20s %%s\\n\", \"%s\", _(\"%s\"));\n"
        name shortdesc
   ) all_functions_sorted;
        name shortdesc
   ) all_functions_sorted;
-  pr "  printf (\"    Use -h <cmd> / help <cmd> to show detailed help for a command.\\n\");\n";
+  pr "  printf (\"    %%s\\n\",";
+  pr "          _(\"Use -h <cmd> / help <cmd> to show detailed help for a command.\"));\n";
   pr "}\n";
   pr "\n";
 
   pr "}\n";
   pr "\n";
 
@@ -5344,6 +5662,12 @@ and generate_fish_cmds () =
            ("\n\n" ^ danger_will_robinson)
          else "" in
 
            ("\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
       let describe_alias =
        if name <> alias then
          sprintf "\n\nYou can use '%s' as an alias for this command." alias
@@ -5356,7 +5680,7 @@ and generate_fish_cmds () =
       if name <> alias then
        pr " || strcasecmp (cmd, \"%s\") == 0" alias;
       pr ")\n";
       if name <> alias then
        pr " || strcasecmp (cmd, \"%s\") == 0" alias;
       pr ")\n";
-      pr "    pod2text (\"%s - %s\", %S);\n"
+      pr "    pod2text (\"%s\", _(\"%s\"), %S);\n"
        name2 shortdesc
        (" " ^ synopsis ^ "\n\n" ^ longdesc ^ warnings ^ describe_alias);
       pr "  else\n"
        name2 shortdesc
        (" " ^ synopsis ^ "\n\n" ^ longdesc ^ warnings ^ describe_alias);
       pr "  else\n"
@@ -5432,7 +5756,7 @@ and generate_fish_cmds () =
        | RInt _
        | RBool _ -> pr "  int r;\n"
        | RInt64 _ -> pr "  int64_t r;\n"
        | 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
        | RString _ -> pr "  char *r;\n"
        | RStringList _ | RHashtable _ -> pr "  char **r;\n"
        | RStruct (_, typ) -> pr "  struct guestfs_%s *r;\n" typ
@@ -5455,9 +5779,9 @@ and generate_fish_cmds () =
       (* Check and convert parameters. *)
       let argc_expected = List.length (snd style) in
       pr "  if (argc != %d) {\n" argc_expected;
       (* Check and convert parameters. *)
       let argc_expected = List.length (snd style) in
       pr "  if (argc != %d) {\n" argc_expected;
-      pr "    fprintf (stderr, \"%%s should have %d parameter(s)\\n\", cmd);\n"
+      pr "    fprintf (stderr, _(\"%%s should have %%d parameter(s)\\n\"), cmd, %d);\n"
        argc_expected;
        argc_expected;
-      pr "    fprintf (stderr, \"type 'help %%s' for help on %%s\\n\", cmd, cmd);\n";
+      pr "    fprintf (stderr, _(\"type 'help %%s' for help on %%s\\n\"), cmd, cmd);\n";
       pr "    return -1;\n";
       pr "  }\n";
       iteri (
       pr "    return -1;\n";
       pr "  }\n";
       iteri (
@@ -5508,6 +5832,9 @@ and generate_fish_cmds () =
           pr "  if (r == NULL) return -1;\n";
           pr "  printf (\"%%s\\n\", r);\n";
           pr "  return 0;\n"
           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";
        | RString _ ->
           pr "  if (r == NULL) return -1;\n";
           pr "  printf (\"%%s\\n\", r);\n";
@@ -5563,7 +5890,7 @@ and generate_fish_cmds () =
       pr "  else\n";
   ) all_functions;
   pr "    {\n";
       pr "  else\n";
   ) all_functions;
   pr "    {\n";
-  pr "      fprintf (stderr, \"%%s: unknown command\\n\", cmd);\n";
+  pr "      fprintf (stderr, _(\"%%s: unknown command\\n\"), cmd);\n";
   pr "      return -1;\n";
   pr "    }\n";
   pr "  return 0;\n";
   pr "      return -1;\n";
   pr "    }\n";
   pr "  return 0;\n";
@@ -5712,7 +6039,11 @@ and generate_fish_actions_pod () =
        pr "%s\n\n" protocol_limit_warning;
 
       if List.mem DangerWillRobinson 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
 
 (* Generate a C function prototype. *)
   ) all_functions_sorted
 
 (* Generate a C function prototype. *)
@@ -5727,7 +6058,7 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
    | RInt _ -> pr "int "
    | RInt64 _ -> pr "int64_t "
    | RBool _ -> pr "int "
    | RInt _ -> pr "int "
    | RInt64 _ -> pr "int64_t "
    | RBool _ -> pr "int "
-   | RConstString _ -> pr "const char *"
+   | RConstString _ | RConstOptString _ -> pr "const char *"
    | RString _ | RBufferOut _ -> pr "char *"
    | RStringList _ | RHashtable _ -> pr "char **"
    | RStruct (_, typ) ->
    | RString _ | RBufferOut _ -> pr "char *"
    | RStringList _ | RHashtable _ -> pr "char **"
    | RStruct (_, typ) ->
@@ -5985,6 +6316,9 @@ copy_table (char * const * argv)
       let params =
        "gv" :: List.map (fun arg -> name_of_argt arg ^ "v") (snd style) in
 
       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);
       pr "CAMLprim value\n";
       pr "ocaml_guestfs_%s (value %s" name (List.hd params);
       List.iter (pr ", value %s") (List.tl params);
@@ -6001,7 +6335,10 @@ copy_table (char * const * argv)
        | ps ->
           pr "  CAMLparam%d (%s);\n" (List.length ps) (String.concat ", " ps)
       );
        | 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";
       pr "\n";
 
       pr "  guestfs_h *g = Guestfs_val (gv);\n";
@@ -6032,7 +6369,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"
        | 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";
        | RString _ -> pr "  char *r;\n"; "NULL"
        | RStringList _ ->
            pr "  int i;\n";
@@ -6075,7 +6413,15 @@ copy_table (char * const * argv)
        | RInt64 _ ->
           pr "  rv = caml_copy_int64 (r);\n"
        | RBool _ -> pr "  rv = Val_bool (r);\n"
        | 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"
        | RString _ ->
           pr "  rv = caml_copy_string (r);\n";
           pr "  free (r);\n"
@@ -6149,6 +6495,7 @@ and generate_ocaml_prototype ?(is_external = false) name style =
    | RInt64 _ -> pr "int64"
    | RBool _ -> pr "bool"
    | RConstString _ -> pr "string"
    | RInt64 _ -> pr "int64"
    | RBool _ -> pr "bool"
    | RConstString _ -> pr "string"
+   | RConstOptString _ -> pr "string option"
    | RString _ | RBufferOut _ -> pr "string"
    | RStringList _ -> pr "string array"
    | RStruct (_, typ) -> pr "%s" typ
    | RString _ | RBufferOut _ -> pr "string"
    | RStringList _ -> pr "string array"
    | RStruct (_, typ) -> pr "%s" typ
@@ -6265,6 +6612,7 @@ DESTROY (g)
        | RInt64 _ -> pr "SV *\n"
        | RBool _ -> pr "SV *\n"
        | RConstString _ -> pr "SV *\n"
        | 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 _
        | RString _ -> pr "SV *\n"
        | RBufferOut _ -> pr "SV *\n"
        | RStringList _
@@ -6353,6 +6701,20 @@ DESTROY (g)
           pr "      RETVAL = newSVpv (%s, 0);\n" n;
           pr " OUTPUT:\n";
           pr "      RETVAL\n"
           pr "      RETVAL = newSVpv (%s, 0);\n" n;
           pr " OUTPUT:\n";
           pr "      RETVAL\n"
+       | 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;
        | RString n ->
           pr "PREINIT:\n";
           pr "      char *%s;\n" n;
@@ -6598,7 +6960,10 @@ sub new {
        if List.mem ProtocolLimitWarning flags then
          pr "%s\n\n" protocol_limit_warning;
        if List.mem DangerWillRobinson flags then
        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;
 
       )
   ) all_functions_sorted;
 
@@ -6635,6 +7000,7 @@ and generate_perl_prototype name style =
    | RInt n
    | RInt64 n
    | RConstString n
    | RInt n
    | RInt64 n
    | RConstString n
+   | RConstOptString n
    | RString n
    | RBufferOut n -> pr "$%s = " n
    | RStruct (n,_)
    | RString n
    | RBufferOut n -> pr "$%s = " n
    | RStruct (n,_)
@@ -6882,7 +7248,8 @@ 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"
        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"
        | RString _ -> pr "  char *r;\n"; "NULL"
        | RStringList _ | RHashtable _ -> pr "  char **r;\n"; "NULL"
        | RStruct (_, typ) -> pr "  struct guestfs_%s *r;\n" typ; "NULL"
@@ -6966,6 +7333,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"
        | 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"
        | RString _ ->
           pr "  py_r = PyString_FromString (r);\n";
           pr "  free (r);\n"
@@ -7094,7 +7468,8 @@ class GuestFS:
        let doc = replace_str longdesc "C<guestfs_" "C<g." in
        let doc =
           match fst style with
        let doc = replace_str longdesc "C<guestfs_" "C<g." in
        let doc =
           match fst style with
-         | RErr | RInt _ | RInt64 _ | RBool _ | RConstString _
+         | RErr | RInt _ | RInt64 _ | RBool _
+         | RConstOptString _ | RConstString _
          | RString _ | RBufferOut _ -> doc
          | RStringList _ ->
              doc ^ "\n\nThis function returns a list of strings."
          | RString _ | RBufferOut _ -> doc
          | RStringList _ ->
              doc ^ "\n\nThis function returns a list of strings."
@@ -7112,6 +7487,10 @@ class GuestFS:
          if List.mem DangerWillRobinson flags then
            doc ^ "\n\n" ^ danger_will_robinson
          else doc in
          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
        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
@@ -7276,7 +7655,8 @@ 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"
        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"
        | RString _ -> pr "  char *r;\n"; "NULL"
        | RStringList _ | RHashtable _ -> pr "  char **r;\n"; "NULL"
        | RStruct (_, typ) -> pr "  struct guestfs_%s *r;\n" typ; "NULL"
@@ -7312,6 +7692,11 @@ static VALUE ruby_guestfs_close (VALUE gv)
           pr "  return ULL2NUM (r);\n"
        | RConstString _ ->
           pr "  return rb_str_new2 (r);\n";
           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";
        | RString _ ->
           pr "  VALUE rv = rb_str_new2 (r);\n";
           pr "  free (r);\n";
@@ -7514,6 +7899,10 @@ public class GuestFS {
          if List.mem DangerWillRobinson flags then
            doc ^ "\n\n" ^ danger_will_robinson
          else doc in
          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
        let doc = pod2text ~width:60 name doc in
        let doc = List.map (            (* RHBZ#501883 *)
          function
@@ -7568,7 +7957,8 @@ and generate_java_prototype ?(public=false) ?(privat=false) ?(native=false)
    | RInt _ -> pr "int ";
    | RInt64 _ -> pr "long ";
    | RBool _ -> pr "boolean ";
    | RInt _ -> pr "int ";
    | RInt64 _ -> pr "long ";
    | RBool _ -> pr "boolean ";
-   | RConstString _ | RString _ | RBufferOut _ -> pr "String ";
+   | RConstString _ | RConstOptString _ | RString _
+   | RBufferOut _ -> pr "String ";
    | RStringList _ -> pr "String[] ";
    | RStruct (_, typ) ->
        let name = java_name_of_struct typ in
    | RStringList _ -> pr "String[] ";
    | RStruct (_, typ) ->
        let name = java_name_of_struct typ in
@@ -7697,7 +8087,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
        | RInt _ -> pr "jint ";
        | RInt64 _ -> pr "jlong ";
        | RBool _ -> pr "jboolean ";
        | RInt _ -> pr "jint ";
        | RInt64 _ -> pr "jlong ";
        | RBool _ -> pr "jboolean ";
-       | RConstString _ | RString _ | RBufferOut _ -> pr "jstring ";
+       | RConstString _ | RConstOptString _ | RString _
+       | RBufferOut _ -> pr "jstring ";
        | RStruct _ | RHashtable _ ->
           pr "jobject ";
        | RStringList _ | RStructList _ ->
        | RStruct _ | RHashtable _ ->
           pr "jobject ";
        | RStringList _ | RStructList _ ->
@@ -7732,6 +8123,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"
        | 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"
        | RString _ ->
            pr "  jstring jr;\n";
            pr "  char *r;\n"; "NULL", "NULL"
@@ -7777,6 +8169,7 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
        (match fst style with
         | RStringList _ | RStructList _ -> true
         | RErr | RBool _ | RInt _ | RInt64 _ | RConstString _
        (match fst style with
         | RStringList _ | RStructList _ -> true
         | RErr | RBool _ | RInt _ | RInt64 _ | RConstString _
+        | RConstOptString _
         | RString _ | RBufferOut _ | RStruct _ | RHashtable _ -> false) ||
          List.exists (function StringList _ -> true | _ -> false) (snd style) in
       if needs_i then
         | RString _ | RBufferOut _ | RStruct _ | RHashtable _ -> false) ||
          List.exists (function StringList _ -> true | _ -> false) (snd style) in
       if needs_i then
@@ -7849,6 +8242,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"
        | 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";
        | RString _ ->
           pr "  jr = (*env)->NewStringUTF (env, r);\n";
           pr "  free (r);\n";
@@ -7986,6 +8381,7 @@ and generate_haskell_hs () =
     | RInt64 _, _ -> true
     | RBool _, _
     | RConstString _, _
     | RInt64 _, _ -> true
     | RBool _, _
     | RConstString _, _
+    | RConstOptString _, _
     | RString _, _
     | RStringList _, _
     | RStruct _, _
     | RString _, _
     | RStringList _, _
     | RStruct _, _
@@ -8100,7 +8496,8 @@ last_error h = do
             pr "    then do\n";
             pr "      err <- last_error h\n";
             pr "      fail err\n";
             pr "    then do\n";
             pr "      err <- last_error h\n";
             pr "      fail err\n";
-        | RConstString _ | RString _ | RStringList _ | RStruct _
+        | RConstString _ | RConstOptString _ | RString _
+        | RStringList _ | RStruct _
         | RStructList _ | RHashtable _ | RBufferOut _ ->
             pr "  if (r == nullPtr)\n";
             pr "    then do\n";
         | RStructList _ | RHashtable _ | RBufferOut _ ->
             pr "  if (r == nullPtr)\n";
             pr "    then do\n";
@@ -8117,6 +8514,7 @@ last_error h = do
         | RBool _ ->
             pr "    else return (toBool r)\n"
         | RConstString _
         | RBool _ ->
             pr "    else return (toBool r)\n"
         | RConstString _
+        | RConstOptString _
         | RString _
         | RStringList _
         | RStruct _
         | RString _
         | RStringList _
         | RStruct _
@@ -8155,6 +8553,7 @@ and generate_haskell_prototype ~handle ?(hs = false) style =
    | RInt64 _ -> pr "%s" int64
    | RBool _ -> pr "%s" bool
    | RConstString _ -> pr "%s" string
    | 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) ->
    | RString _ -> pr "%s" string
    | RStringList _ -> pr "[%s]" string
    | RStruct (_, typ) ->
@@ -8246,7 +8645,8 @@ print_strings (char * const* const argv)
             pr "  return r;\n"
         | RBool _ ->
             pr "  return strcmp (val, \"true\") == 0;\n"
             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.
             (* 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.
@@ -8302,7 +8702,7 @@ print_strings (char * const* const argv)
        (match fst style with
         | RErr | RInt _ | RInt64 _ | RBool _ ->
             pr "  return -1;\n"
        (match fst style with
         | RErr | RInt _ | RInt64 _ | RBool _ ->
             pr "  return -1;\n"
-        | RConstString _
+        | RConstString _ | RConstOptString _
         | RString _ | RStringList _ | RStruct _
         | RStructList _
         | RHashtable _
         | RString _ | RStringList _ | RStruct _
         | RStructList _
         | RHashtable _