guestfish: Display RStructList results more pleasantly.
[libguestfs.git] / src / generator.ml
index c098ec8..b6f6f42 100755 (executable)
@@ -30,7 +30,6 @@
  *
  * 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";;
@@ -207,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
+
     (* 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 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 <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 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 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 final command (only)
      * to fail.
      *)
@@ -262,14 +276,17 @@ and test_field_compare =
 and test_prereq =
     (* Test always runs. *)
   | Always
+
     (* 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
+
     (* As for 'If' but the test runs _unless_ the code returns true. *)
   | Unless of string
 
@@ -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],
-   [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>.
@@ -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, [],
-   [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>.
@@ -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, [],
-   [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
@@ -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, [],
-   [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
@@ -1236,12 +1246,10 @@ other objects like directories.
 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
@@ -1435,14 +1443,12 @@ This command removes all LVM logical volumes, volume groups
 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
@@ -1590,9 +1596,8 @@ result into a list of lines.
 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>.
@@ -1600,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, [],
-   [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>.
@@ -1614,9 +1618,9 @@ refers to.
 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.
@@ -1779,31 +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, [],
-   [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");
+   [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"; "md5"; "/known-3"]], "46d6ca27ee07cdc6fa99c2e138cc522c")],
+      [["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
@@ -2241,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],
-   [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],
-   [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"])],
@@ -2272,9 +2264,8 @@ show strings inside Windows/x86 files.
 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.
      *)
@@ -2715,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, [],
-   [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
@@ -2732,7 +2722,7 @@ The result is the estimated size in I<kilobytes>
 
   ("initrd_list", (RStringList "filenames", [String "path"]), 128, [],
    [InitSquashFS, Always, TestOutputList (
-      [["initrd_list"; "/initrd"]], ["empty";"known-1";"known-2";"known-3"])],
+      [["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.
@@ -2767,7 +2757,11 @@ Create a swap partition on C<device>.");
        ["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 (
@@ -3033,9 +3027,8 @@ with C<guestfs_mkmountpoint>.  See C<guestfs_mkmountpoint>
 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
@@ -3144,6 +3137,262 @@ matching lines.");
 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>.");
+
+  ("inotify_init", (RErr, [Int "maxevents"]), 179, [],
+   [InitSquashFS, Always, TestRun (
+      [["inotify_init"; "0"]])],
+   "create an inotify handle",
+   "\
+This command creates a new inotify handle.
+The inotify subsystem can be used to notify events which happen to
+objects in the guest filesystem.
+
+C<maxevents> is the maximum number of events which will be
+queued up between calls to C<guestfs_inotify_read> or
+C<guestfs_inotify_files>.
+If this is passed as C<0>, then the kernel (or previously set)
+default is used.  For Linux 2.6.29 the default was 16384 events.
+Beyond this limit, the kernel throws away events, but records
+the fact that it threw them away by setting a flag
+C<IN_Q_OVERFLOW> in the returned structure list (see
+C<guestfs_inotify_read>).
+
+Before any events are generated, you have to add some
+watches to the internal watch list.  See:
+C<guestfs_inotify_add_watch>,
+C<guestfs_inotify_rm_watch> and
+C<guestfs_inotify_watch_all>.
+
+Queued up events should be read periodically by calling
+C<guestfs_inotify_read>
+(or C<guestfs_inotify_files> which is just a helpful
+wrapper around C<guestfs_inotify_read>).  If you don't
+read the events out often enough then you risk the internal
+queue overflowing.
+
+The handle should be closed after use by calling
+C<guestfs_inotify_close>.  This also removes any
+watches automatically.
+
+See also L<inotify(7)> for an overview of the inotify interface
+as exposed by the Linux kernel, which is roughly what we expose
+via libguestfs.  Note that there is one global inotify handle
+per libguestfs instance.");
+
+  ("inotify_add_watch", (RInt64 "wd", [String "path"; Int "mask"]), 180, [],
+   [InitBasicFS, Always, TestOutputList (
+      [["inotify_init"; "0"];
+       ["inotify_add_watch"; "/"; "1073741823"];
+       ["touch"; "/a"];
+       ["touch"; "/b"];
+       ["inotify_files"]], ["a"; "b"])],
+   "add an inotify watch",
+   "\
+Watch C<path> for the events listed in C<mask>.
+
+Note that if C<path> is a directory then events within that
+directory are watched, but this does I<not> happen recursively
+(in subdirectories).
+
+Note for non-C or non-Linux callers: the inotify events are
+defined by the Linux kernel ABI and are listed in
+C</usr/include/sys/inotify.h>.");
+
+  ("inotify_rm_watch", (RErr, [Int(*XXX64*) "wd"]), 181, [],
+   [],
+   "remove an inotify watch",
+   "\
+Remove a previously defined inotify watch.
+See C<guestfs_inotify_add_watch>.");
+
+  ("inotify_read", (RStructList ("events", "inotify_event"), []), 182, [],
+   [],
+   "return list of inotify events",
+   "\
+Return the complete queue of events that have happened
+since the previous read call.
+
+If no events have happened, this returns an empty list.
+
+I<Note>: In order to make sure that all events have been
+read, you must call this function repeatedly until it
+returns an empty list.  The reason is that the call will
+read events up to the maximum appliance-to-host message
+size and leave remaining events in the queue.");
+
+  ("inotify_files", (RStringList "paths", []), 183, [],
+   [],
+   "return list of watched files that had events",
+   "\
+This function is a helpful wrapper around C<guestfs_inotify_read>
+which just returns a list of pathnames of objects that were
+touched.  The returned pathnames are sorted and deduplicated.");
+
+  ("inotify_close", (RErr, []), 184, [],
+   [],
+   "close the inotify handle",
+   "\
+This closes the inotify handle which was previously
+opened by inotify_init.  It removes all watches, throws
+away any pending events, and deallocates all resources.");
+
 ]
 
 let all_functions = non_daemon_functions @ daemon_functions
@@ -3158,7 +3407,7 @@ let all_functions_sorted =
 (* Field types for structures. *)
 type field =
   | FChar                      (* C 'char' (really, a 7 bit byte). *)
-  | FString                    (* nul-terminated ASCII string. *)
+  | FString                    (* nul-terminated ASCII string, NOT NULL. *)
   | FBuffer                    (* opaque buffer of bytes, (char *, int) pair *)
   | FUInt32
   | FInt32
@@ -3304,6 +3553,14 @@ let structs = [
     "attrname", FString;
     "attrval", FBuffer;
   ];
+
+  (* Inotify events. *)
+  "inotify_event", [
+    "in_wd", FInt64;
+    "in_mask", FUInt32;
+    "in_cookie", FUInt32;
+    "in_name", FString;
+  ];
 ] (* end of structs *)
 
 (* Ugh, Java has to be different ..
@@ -3319,6 +3576,7 @@ let java_structs = [
   "dirent", "Dirent";
   "version", "Version";
   "xattr", "XAttr";
+  "inotify_event", "INotifyEvent";
 ]
 
 (* Used for testing language bindings. *)
@@ -3430,6 +3688,13 @@ let files_equal n1 n2 =
   | 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 ->
@@ -3471,7 +3736,8 @@ let seq_of_test = function
   | TestOutputListOfDevices (s, _)
   | TestOutputInt (s, _) | TestOutputIntOp (s, _, _)
   | TestOutputTrue s | TestOutputFalse s
-  | TestOutputLength (s, _) | TestOutputStruct (s, _)
+  | TestOutputLength (s, _) | TestOutputBuffer (s, _)
+  | TestOutputStruct (s, _)
   | TestLastFail s -> s
 
 (* Handling for function flags. *)
@@ -4771,15 +5037,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 (
-    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
-    | name, _, _, _, tests, _, _ -> ()
   ) all_functions;
 
   pr "}\n";
@@ -5219,6 +5499,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
@@ -5427,15 +5724,16 @@ and generate_fish_cmds () =
   (* 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 "  printf (\"%%-20s %%s\\n\", \"%s\", \"%s\");\n"
+      pr "  printf (\"%%-20s %%s\\n\", \"%s\", _(\"%s\"));\n"
        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";
 
@@ -5489,7 +5787,7 @@ and generate_fish_cmds () =
       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"
@@ -5504,7 +5802,7 @@ and generate_fish_cmds () =
       let needs_i =
         List.exists (function (_, (FUUID|FBuffer)) -> true | _ -> false) cols in
 
-      pr "static void print_%s (struct guestfs_%s *%s)\n" typ typ typ;
+      pr "static void print_%s_indent (struct guestfs_%s *%s, const char *indent)\n" typ typ typ;
       pr "{\n";
       if needs_i then (
         pr "  int i;\n";
@@ -5513,44 +5811,57 @@ and generate_fish_cmds () =
       List.iter (
        function
        | name, FString ->
-           pr "  printf (\"%s: %%s\\n\", %s->%s);\n" name typ name
+           pr "  printf (\"%%s%s: %%s\\n\", indent, %s->%s);\n" name typ name
        | name, FUUID ->
            pr "  printf (\"%s: \");\n" name;
            pr "  for (i = 0; i < 32; ++i)\n";
-           pr "    printf (\"%%c\", %s->%s[i]);\n" typ name;
+           pr "    printf (\"%%s%%c\", indent, %s->%s[i]);\n" typ name;
            pr "  printf (\"\\n\");\n"
        | name, FBuffer ->
-           pr "  printf (\"%s: \");\n" name;
+           pr "  printf (\"%%s%s: \", indent);\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 "      printf (\"%%s%%c\", indent, %s->%s[i]);\n" typ name;
            pr "    else\n";
-           pr "      printf (\"\\\\x%%02x\", %s->%s[i]);\n" typ name;
+           pr "      printf (\"%%s\\\\x%%02x\", indent, %s->%s[i]);\n" typ name;
            pr "  printf (\"\\n\");\n"
        | name, (FUInt64|FBytes) ->
-           pr "  printf (\"%s: %%\" PRIu64 \"\\n\", %s->%s);\n" name typ name
+           pr "  printf (\"%%s%s: %%\" PRIu64 \"\\n\", indent, %s->%s);\n"
+             name typ name
        | name, FInt64 ->
-           pr "  printf (\"%s: %%\" PRIi64 \"\\n\", %s->%s);\n" name typ name
+           pr "  printf (\"%%s%s: %%\" PRIi64 \"\\n\", indent, %s->%s);\n"
+             name typ name
        | name, FUInt32 ->
-           pr "  printf (\"%s: %%\" PRIu32 \"\\n\", %s->%s);\n" name typ name
+           pr "  printf (\"%%s%s: %%\" PRIu32 \"\\n\", indent, %s->%s);\n"
+             name typ name
        | name, FInt32 ->
-           pr "  printf (\"%s: %%\" PRIi32 \"\\n\", %s->%s);\n" name typ name
+           pr "  printf (\"%%s%s: %%\" PRIi32 \"\\n\", indent, %s->%s);\n"
+             name typ name
        | name, FChar ->
-           pr "  printf (\"%s: %%c\\n\", %s->%s);\n" name typ name
+           pr "  printf (\"%%s%s: %%c\\n\", indent, %s->%s);\n"
+             name typ name
        | name, FOptPercent ->
-           pr "  if (%s->%s >= 0) printf (\"%s: %%g %%%%\\n\", %s->%s);\n"
+           pr "  if (%s->%s >= 0) printf (\"%%s%s: %%g %%%%\\n\", indent, %s->%s);\n"
              typ name name typ name;
-           pr "  else printf (\"%s: \\n\");\n" name
+           pr "  else printf (\"%%s%s: \\n\", indent);\n" name
       ) cols;
       pr "}\n";
       pr "\n";
+      pr "static void print_%s (struct guestfs_%s *%s)\n" typ typ typ;
+      pr "{\n";
+      pr "  print_%s_indent (%s, \"\");\n" typ typ;
+      pr "}\n";
+      pr "\n";
       pr "static void print_%s_list (struct guestfs_%s_list *%ss)\n"
        typ typ typ;
       pr "{\n";
       pr "  int i;\n";
       pr "\n";
-      pr "  for (i = 0; i < %ss->len; ++i)\n" typ;
-      pr "    print_%s (&%ss->val[i]);\n" typ typ;
+      pr "  for (i = 0; i < %ss->len; ++i) {\n" typ;
+      pr "    printf (\"[%%d] = {\\n\", i);\n";
+      pr "    print_%s_indent (&%ss->val[i], \"  \");\n" typ typ;
+      pr "    printf (\"}\\n\");\n";
+      pr "  }\n";
       pr "}\n";
       pr "\n";
   ) structs;
@@ -5588,9 +5899,9 @@ and generate_fish_cmds () =
       (* 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;
-      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 (
@@ -5699,7 +6010,7 @@ and generate_fish_cmds () =
       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";