Add command trace functionality.
[libguestfs.git] / src / generator.ml
index 230be80..71aeeed 100755 (executable)
@@ -178,13 +178,13 @@ type flags =
  *
  * Note that the test environment has 3 block devices, of size 500MB,
  * 50MB and 10MB (respectively /dev/sda, /dev/sdb, /dev/sdc), and
- * a fourth squashfs block device with some known files on it (/dev/sdd).
+ * a fourth ISO block device with some known files on it (/dev/sdd).
  *
  * Note for partitioning purposes, the 500MB device has 1015 cylinders.
  * Number of cylinders was 63 for IDE emulated disks with precisely
  * the same size.  How exactly this is calculated is a mystery.
  *
- * The squashfs block device (/dev/sdd) comes from images/test.sqsh.
+ * The ISO block device (/dev/sdd) comes from images/test.iso.
  *
  * To be able to run the tests in a reasonable amount of time,
  * the virtual machine and block devices are reused between tests.
@@ -326,10 +326,10 @@ and test_init =
      *)
   | InitBasicFSonLVM
 
-    (* /dev/sdd (the squashfs, see images/ directory in source)
+    (* /dev/sdd (the ISO, see images/ directory in source)
      * is mounted on /
      *)
-  | InitSquashFS
+  | InitISOFS
 
 (* Sequence of commands for testing. *)
 and seq = cmd list
@@ -805,6 +805,32 @@ is passed to the appliance at boot time.  See C<guestfs_set_selinux>.
 For more information on the architecture of libguestfs,
 see L<guestfs(3)>.");
 
+  ("set_trace", (RErr, [Bool "trace"]), -1, [FishAlias "trace"],
+   [InitNone, Always, TestOutputTrue (
+      [["set_trace"; "true"];
+       ["get_trace"]])],
+   "enable or disable command traces",
+   "\
+If the command trace flag is set to 1, then commands are
+printed on stdout before they are executed in a format
+which is very similar to the one used by guestfish.  In
+other words, you can run a program with this enabled, and
+you will get out a script which you can feed to guestfish
+to perform the same set of actions.
+
+If you want to trace C API calls into libguestfs (and
+other libraries) then possibly a better way is to use
+the external ltrace(1) command.
+
+Command traces are disabled unless the environment variable
+C<LIBGUESTFS_TRACE> is defined and set to C<1>.");
+
+  ("get_trace", (RBool "trace", []), -1, [],
+   [],
+   "get command trace enabled flag",
+   "\
+Return the command trace flag.");
+
 ]
 
 (* daemon_functions are any functions which cause some action
@@ -859,7 +885,7 @@ update the timestamps on a file, or, if the file does not exist,
 to create a new zero-length file.");
 
   ("cat", (RString "content", [Pathname "path"]), 4, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutput (
+   [InitISOFS, Always, TestOutput (
       [["cat"; "/known-2"]], "abcdef\n")],
    "list the contents of a file",
    "\
@@ -1007,9 +1033,9 @@ 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", [Pathname "path"]), 15, [],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["read_lines"; "/known-4"]], ["abc"; "def"; "ghi"]);
-    InitSquashFS, Always, TestOutputList (
+    InitISOFS, Always, TestOutputList (
       [["read_lines"; "/empty"]], [])],
    "read file as lines",
    "\
@@ -1271,9 +1297,9 @@ names, you will need to locate and parse the password file
 yourself (Augeas support makes this relatively easy).");
 
   ("exists", (RBool "existsflag", [Pathname "path"]), 36, [],
-   [InitSquashFS, Always, TestOutputTrue (
+   [InitISOFS, Always, TestOutputTrue (
       [["exists"; "/empty"]]);
-    InitSquashFS, Always, TestOutputTrue (
+    InitISOFS, Always, TestOutputTrue (
       [["exists"; "/directory"]])],
    "test if file or directory exists",
    "\
@@ -1283,9 +1309,9 @@ 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", [Pathname "path"]), 37, [],
-   [InitSquashFS, Always, TestOutputTrue (
+   [InitISOFS, Always, TestOutputTrue (
       [["is_file"; "/known-1"]]);
-    InitSquashFS, Always, TestOutputFalse (
+    InitISOFS, Always, TestOutputFalse (
       [["is_file"; "/directory"]])],
    "test if file exists",
    "\
@@ -1296,9 +1322,9 @@ other objects like directories.
 See also C<guestfs_stat>.");
 
   ("is_dir", (RBool "dirflag", [Pathname "path"]), 38, [],
-   [InitSquashFS, Always, TestOutputFalse (
+   [InitISOFS, Always, TestOutputFalse (
       [["is_dir"; "/known-3"]]);
-    InitSquashFS, Always, TestOutputTrue (
+    InitISOFS, Always, TestOutputTrue (
       [["is_dir"; "/directory"]])],
    "test if file exists",
    "\
@@ -1493,11 +1519,11 @@ This command removes all LVM logical volumes, volume groups
 and physical volumes.");
 
   ("file", (RString "description", [Dev_or_Path "path"]), 49, [],
-   [InitSquashFS, Always, TestOutput (
+   [InitISOFS, Always, TestOutput (
       [["file"; "/empty"]], "empty");
-    InitSquashFS, Always, TestOutput (
+    InitISOFS, Always, TestOutput (
       [["file"; "/known-1"]], "ASCII text");
-    InitSquashFS, Always, TestLastFail (
+    InitISOFS, Always, TestLastFail (
       [["file"; "/notexists"]])],
    "determine file type",
    "\
@@ -1646,7 +1672,7 @@ result into a list of lines.
 See also: C<guestfs_sh_lines>");
 
   ("stat", (RStruct ("statbuf", "stat"), [Pathname "path"]), 52, [],
-   [InitSquashFS, Always, TestOutputStruct (
+   [InitISOFS, Always, TestOutputStruct (
       [["stat"; "/empty"]], [CompareWithInt ("size", 0)])],
    "get file information",
    "\
@@ -1655,7 +1681,7 @@ Returns file information for the given C<path>.
 This is the same as the C<stat(2)> system call.");
 
   ("lstat", (RStruct ("statbuf", "stat"), [Pathname "path"]), 53, [],
-   [InitSquashFS, Always, TestOutputStruct (
+   [InitISOFS, Always, TestOutputStruct (
       [["lstat"; "/empty"]], [CompareWithInt ("size", 0)])],
    "get file information for a symbolic link",
    "\
@@ -1668,8 +1694,8 @@ refers to.
 This is the same as the C<lstat(2)> system call.");
 
   ("statvfs", (RStruct ("statbuf", "statvfs"), [Pathname "path"]), 54, [],
-   [InitSquashFS, Always, TestOutputStruct (
-      [["statvfs"; "/"]], [CompareWithInt ("namemax", 256)])],
+   [InitISOFS, Always, TestOutputStruct (
+      [["statvfs"; "/"]], [CompareWithInt ("namemax", 255)])],
    "get file system statistics",
    "\
 Returns file system statistics for any mounted file system.
@@ -1834,21 +1860,21 @@ C<filename> can also be a named pipe.
 See also C<guestfs_upload>, C<guestfs_cat>.");
 
   ("checksum", (RString "checksum", [String "csumtype"; Pathname "path"]), 68, [],
-   [InitSquashFS, Always, TestOutput (
+   [InitISOFS, Always, TestOutput (
       [["checksum"; "crc"; "/known-3"]], "2891671662");
-    InitSquashFS, Always, TestLastFail (
+    InitISOFS, Always, TestLastFail (
       [["checksum"; "crc"; "/notexists"]]);
-    InitSquashFS, Always, TestOutput (
+    InitISOFS, Always, TestOutput (
       [["checksum"; "md5"; "/known-3"]], "46d6ca27ee07cdc6fa99c2e138cc522c");
-    InitSquashFS, Always, TestOutput (
+    InitISOFS, Always, TestOutput (
       [["checksum"; "sha1"; "/known-3"]], "b7ebccc3ee418311091c3eda0a45b83c0a770f15");
-    InitSquashFS, Always, TestOutput (
+    InitISOFS, Always, TestOutput (
       [["checksum"; "sha224"; "/known-3"]], "d2cd1774b28f3659c14116be0a6dc2bb5c4b350ce9cd5defac707741");
-    InitSquashFS, Always, TestOutput (
+    InitISOFS, Always, TestOutput (
       [["checksum"; "sha256"; "/known-3"]], "75bb71b90cd20cb13f86d2bea8dad63ac7194e7517c3b52b8d06ff52d3487d30");
-    InitSquashFS, Always, TestOutput (
+    InitISOFS, Always, TestOutput (
       [["checksum"; "sha384"; "/known-3"]], "5fa7883430f357b5d7b7271d3a1d2872b51d73cba72731de6863d3dea55f30646af2799bef44d5ea776a5ec7941ac640");
-    InitSquashFS, Always, TestOutput (
+    InitISOFS, Always, TestOutput (
       [["checksum"; "sha512"; "/known-3"]], "2794062c328c6b216dca90443b7f7134c5f40e56bd0ed7853123275a09982a6f992e6ca682f9d2fba34a4c5e870d8fe077694ff831e3032a004ee077e00603f6")],
    "compute MD5, SHAx or CRC checksum of file",
    "\
@@ -2288,9 +2314,9 @@ true if their content is exactly equal, or false otherwise.
 The external L<cmp(1)> program is used for the comparison.");
 
   ("strings", (RStringList "stringsout", [Pathname "path"]), 94, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["strings"; "/known-5"]], ["abcdefghi"; "jklmnopqr"]);
-    InitSquashFS, Always, TestOutputList (
+    InitISOFS, Always, TestOutputList (
       [["strings"; "/empty"]], [])],
    "print the printable strings in a file",
    "\
@@ -2298,7 +2324,7 @@ This runs the L<strings(1)> command on a file and returns
 the list of printable strings found.");
 
   ("strings_e", (RStringList "stringsout", [String "encoding"; Pathname "path"]), 95, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["strings_e"; "b"; "/known-5"]], []);
     InitBasicFS, Disabled, TestOutputList (
       [["write_file"; "/new"; "\000h\000e\000l\000l\000o\000\n\000w\000o\000r\000l\000d\000\n"; "24"];
@@ -2316,12 +2342,12 @@ show strings inside Windows/x86 files.
 The returned strings are transcoded to UTF-8.");
 
   ("hexdump", (RString "dump", [Pathname "path"]), 96, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutput (
+   [InitISOFS, 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.
      *)
-    InitSquashFS, Always, TestRun (
+    InitISOFS, Always, TestRun (
       [["hexdump"; "/100krandom"]])],
    "dump a file in hexadecimal",
    "\
@@ -2663,7 +2689,7 @@ directory and its contents after use.
 See also: L<mkdtemp(3)>");
 
   ("wc_l", (RInt "lines", [Pathname "path"]), 118, [],
-   [InitSquashFS, Always, TestOutputInt (
+   [InitISOFS, Always, TestOutputInt (
       [["wc_l"; "/10klines"]], 10000)],
    "count lines in a file",
    "\
@@ -2671,7 +2697,7 @@ This command counts the lines in a file, using the
 C<wc -l> external command.");
 
   ("wc_w", (RInt "words", [Pathname "path"]), 119, [],
-   [InitSquashFS, Always, TestOutputInt (
+   [InitISOFS, Always, TestOutputInt (
       [["wc_w"; "/10klines"]], 10000)],
    "count words in a file",
    "\
@@ -2679,7 +2705,7 @@ This command counts the words in a file, using the
 C<wc -w> external command.");
 
   ("wc_c", (RInt "chars", [Pathname "path"]), 120, [],
-   [InitSquashFS, Always, TestOutputInt (
+   [InitISOFS, Always, TestOutputInt (
       [["wc_c"; "/100kallspaces"]], 102400)],
    "count characters in a file",
    "\
@@ -2687,7 +2713,7 @@ This command counts the characters in a file, using the
 C<wc -c> external command.");
 
   ("head", (RStringList "lines", [Pathname "path"]), 121, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["head"; "/10klines"]], ["0abcdefghijklmnopqrstuvwxyz";"1abcdefghijklmnopqrstuvwxyz";"2abcdefghijklmnopqrstuvwxyz";"3abcdefghijklmnopqrstuvwxyz";"4abcdefghijklmnopqrstuvwxyz";"5abcdefghijklmnopqrstuvwxyz";"6abcdefghijklmnopqrstuvwxyz";"7abcdefghijklmnopqrstuvwxyz";"8abcdefghijklmnopqrstuvwxyz";"9abcdefghijklmnopqrstuvwxyz"])],
    "return first 10 lines of a file",
    "\
@@ -2695,11 +2721,11 @@ This command returns up to the first 10 lines of a file as
 a list of strings.");
 
   ("head_n", (RStringList "lines", [Int "nrlines"; Pathname "path"]), 122, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["head_n"; "3"; "/10klines"]], ["0abcdefghijklmnopqrstuvwxyz";"1abcdefghijklmnopqrstuvwxyz";"2abcdefghijklmnopqrstuvwxyz"]);
-    InitSquashFS, Always, TestOutputList (
+    InitISOFS, Always, TestOutputList (
       [["head_n"; "-9997"; "/10klines"]], ["0abcdefghijklmnopqrstuvwxyz";"1abcdefghijklmnopqrstuvwxyz";"2abcdefghijklmnopqrstuvwxyz"]);
-    InitSquashFS, Always, TestOutputList (
+    InitISOFS, Always, TestOutputList (
       [["head_n"; "0"; "/10klines"]], [])],
    "return first N lines of a file",
    "\
@@ -2712,7 +2738,7 @@ 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", [Pathname "path"]), 123, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["tail"; "/10klines"]], ["9990abcdefghijklmnopqrstuvwxyz";"9991abcdefghijklmnopqrstuvwxyz";"9992abcdefghijklmnopqrstuvwxyz";"9993abcdefghijklmnopqrstuvwxyz";"9994abcdefghijklmnopqrstuvwxyz";"9995abcdefghijklmnopqrstuvwxyz";"9996abcdefghijklmnopqrstuvwxyz";"9997abcdefghijklmnopqrstuvwxyz";"9998abcdefghijklmnopqrstuvwxyz";"9999abcdefghijklmnopqrstuvwxyz"])],
    "return last 10 lines of a file",
    "\
@@ -2720,11 +2746,11 @@ This command returns up to the last 10 lines of a file as
 a list of strings.");
 
   ("tail_n", (RStringList "lines", [Int "nrlines"; Pathname "path"]), 124, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["tail_n"; "3"; "/10klines"]], ["9997abcdefghijklmnopqrstuvwxyz";"9998abcdefghijklmnopqrstuvwxyz";"9999abcdefghijklmnopqrstuvwxyz"]);
-    InitSquashFS, Always, TestOutputList (
+    InitISOFS, Always, TestOutputList (
       [["tail_n"; "-9998"; "/10klines"]], ["9997abcdefghijklmnopqrstuvwxyz";"9998abcdefghijklmnopqrstuvwxyz";"9999abcdefghijklmnopqrstuvwxyz"]);
-    InitSquashFS, Always, TestOutputList (
+    InitISOFS, Always, TestOutputList (
       [["tail_n"; "0"; "/10klines"]], [])],
    "return last N lines of a file",
    "\
@@ -2762,8 +2788,8 @@ is I<not> intended that you try to parse the output string.
 Use C<statvfs> from programs.");
 
   ("du", (RInt64 "sizekb", [Pathname "path"]), 127, [],
-   [InitSquashFS, Always, TestOutputInt (
-      [["du"; "/directory"]], 0 (* squashfs doesn't have blocks *))],
+   [InitISOFS, Always, TestOutputInt (
+      [["du"; "/directory"]], 2 (* ISO fs blocksize is 2K *))],
    "estimate file space usage",
    "\
 This command runs the C<du -s> command to estimate file space
@@ -2777,7 +2803,7 @@ The result is the estimated size in I<kilobytes>
 (ie. units of 1024 bytes).");
 
   ("initrd_list", (RStringList "filenames", [Pathname "path"]), 128, [],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["initrd_list"; "/initrd"]], ["empty";"known-1";"known-2";"known-3";"known-4"; "known-5"])],
    "list files in an initrd",
    "\
@@ -2968,7 +2994,7 @@ were rarely if ever used anyway.
 
 See also C<guestfs_sfdisk> and the L<sfdisk(8)> manpage.");
 
-  ("zfile", (RString "description", [String "method"; Pathname "path"]), 140, [DeprecatedBy "file"],
+  ("zfile", (RString "description", [String "meth"; Pathname "path"]), 140, [DeprecatedBy "file"],
    [],
    "determine file type inside a compressed file",
    "\
@@ -3089,7 +3115,7 @@ with C<guestfs_mkmountpoint>.  See C<guestfs_mkmountpoint>
 for full details.");
 
   ("read_file", (RBufferOut "content", [Pathname "path"]), 150, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputBuffer (
+   [InitISOFS, Always, TestOutputBuffer (
       [["read_file"; "/known-4"]], "abc\ndef\nghi")],
    "read a file",
    "\
@@ -3102,9 +3128,9 @@ However unlike C<guestfs_download>, this function is limited
 in the total size of file that can be handled.");
 
   ("grep", (RStringList "lines", [String "regex"; Pathname "path"]), 151, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["grep"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"]);
-    InitSquashFS, Always, TestOutputList (
+    InitISOFS, Always, TestOutputList (
       [["grep"; "nomatch"; "/test-grep.txt"]], [])],
    "return lines matching a pattern",
    "\
@@ -3112,7 +3138,7 @@ This calls the external C<grep> program and returns the
 matching lines.");
 
   ("egrep", (RStringList "lines", [String "regex"; Pathname "path"]), 152, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["egrep"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"])],
    "return lines matching a pattern",
    "\
@@ -3120,7 +3146,7 @@ This calls the external C<egrep> program and returns the
 matching lines.");
 
   ("fgrep", (RStringList "lines", [String "pattern"; Pathname "path"]), 153, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["fgrep"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"])],
    "return lines matching a pattern",
    "\
@@ -3128,7 +3154,7 @@ This calls the external C<fgrep> program and returns the
 matching lines.");
 
   ("grepi", (RStringList "lines", [String "regex"; Pathname "path"]), 154, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["grepi"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"; "ABC"])],
    "return lines matching a pattern",
    "\
@@ -3136,7 +3162,7 @@ This calls the external C<grep -i> program and returns the
 matching lines.");
 
   ("egrepi", (RStringList "lines", [String "regex"; Pathname "path"]), 155, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["egrepi"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"; "ABC"])],
    "return lines matching a pattern",
    "\
@@ -3144,7 +3170,7 @@ This calls the external C<egrep -i> program and returns the
 matching lines.");
 
   ("fgrepi", (RStringList "lines", [String "pattern"; Pathname "path"]), 156, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["fgrepi"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"; "ABC"])],
    "return lines matching a pattern",
    "\
@@ -3152,7 +3178,7 @@ This calls the external C<fgrep -i> program and returns the
 matching lines.");
 
   ("zgrep", (RStringList "lines", [String "regex"; Pathname "path"]), 157, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["zgrep"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"])],
    "return lines matching a pattern",
    "\
@@ -3160,7 +3186,7 @@ This calls the external C<zgrep> program and returns the
 matching lines.");
 
   ("zegrep", (RStringList "lines", [String "regex"; Pathname "path"]), 158, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["zegrep"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"])],
    "return lines matching a pattern",
    "\
@@ -3168,7 +3194,7 @@ This calls the external C<zegrep> program and returns the
 matching lines.");
 
   ("zfgrep", (RStringList "lines", [String "pattern"; Pathname "path"]), 159, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["zfgrep"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"])],
    "return lines matching a pattern",
    "\
@@ -3176,7 +3202,7 @@ This calls the external C<zfgrep> program and returns the
 matching lines.");
 
   ("zgrepi", (RStringList "lines", [String "regex"; Pathname "path"]), 160, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["zgrepi"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"; "ABC"])],
    "return lines matching a pattern",
    "\
@@ -3184,7 +3210,7 @@ This calls the external C<zgrep -i> program and returns the
 matching lines.");
 
   ("zegrepi", (RStringList "lines", [String "regex"; Pathname "path"]), 161, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["zegrepi"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"; "ABC"])],
    "return lines matching a pattern",
    "\
@@ -3192,7 +3218,7 @@ This calls the external C<zegrep -i> program and returns the
 matching lines.");
 
   ("zfgrepi", (RStringList "lines", [String "pattern"; Pathname "path"]), 162, [ProtocolLimitWarning],
-   [InitSquashFS, Always, TestOutputList (
+   [InitISOFS, Always, TestOutputList (
       [["zfgrepi"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"; "ABC"])],
    "return lines matching a pattern",
    "\
@@ -3200,7 +3226,7 @@ This calls the external C<zfgrep -i> program and returns the
 matching lines.");
 
   ("realpath", (RString "rpath", [Pathname "path"]), 163, [],
-   [InitSquashFS, Always, TestOutput (
+   [InitISOFS, Always, TestOutput (
       [["realpath"; "/../directory"]], "/directory")],
    "canonicalized absolute pathname",
    "\
@@ -3361,7 +3387,7 @@ 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 (
+   [InitISOFS, Always, TestRun (
       [["inotify_init"; "0"]])],
    "create an inotify handle",
    "\
@@ -3560,8 +3586,8 @@ an external journal on the journal with UUID C<uuid>.
 
 See also C<guestfs_mke2journal_U>.");
 
-  ("modprobe", (RErr, [String "module"]), 194, [],
-   [InitNone, Always, TestRun [["modprobe"; "ext2"]]],
+  ("modprobe", (RErr, [String "modulename"]), 194, [],
+   [InitNone, Always, TestRun [["modprobe"; "fat"]]],
    "load a kernel module",
    "\
 This loads a kernel module in the appliance.
@@ -3569,6 +3595,19 @@ This loads a kernel module in the appliance.
 The kernel module must have been whitelisted when libguestfs
 was built (see C<appliance/kmod.whitelist.in> in the source).");
 
+  ("echo_daemon", (RString "output", [StringList "words"]), 195, [],
+   [InitNone, Always, TestOutput (
+     [["echo_daemon"; "This is a test"]], "This is a test"
+   )],
+   "echo arguments back to the client",
+   "\
+This command concatenate the list of C<words> passed with single spaces between
+them and returns the resulting string.
+
+You can use this command to test the connection through to the daemon.
+
+See also C<guestfs_ping_daemon>.");
+
 ]
 
 let all_functions = non_daemon_functions @ daemon_functions
@@ -4050,7 +4089,36 @@ let check_functions () =
         if n = "i" || n = "n" then
           failwithf "%s has a param/ret called 'i' or 'n', which will cause some conflicts in the generated code" name;
         if n = "argv" || n = "args" then
-          failwithf "%s has a param/ret called 'argv' or 'args', which will cause some conflicts in the generated code" name
+          failwithf "%s has a param/ret called 'argv' or 'args', which will cause some conflicts in the generated code" name;
+
+        (* List Haskell, OCaml and C keywords here.
+         * http://www.haskell.org/haskellwiki/Keywords
+         * http://caml.inria.fr/pub/docs/manual-ocaml/lex.html#operator-char
+         * http://en.wikipedia.org/wiki/C_syntax#Reserved_keywords
+         * Formatted via: cat c haskell ocaml|sort -u|grep -vE '_|^val$' \
+         *   |perl -pe 's/(.+)/"$1";/'|fmt -70
+         * Omitting _-containing words, since they're handled above.
+         * Omitting the OCaml reserved word, "val", is ok,
+         * and saves us from renaming several parameters.
+         *)
+        let reserved = [
+          "and"; "as"; "asr"; "assert"; "auto"; "begin"; "break"; "case";
+          "char"; "class"; "const"; "constraint"; "continue"; "data";
+          "default"; "deriving"; "do"; "done"; "double"; "downto"; "else";
+          "end"; "enum"; "exception"; "extern"; "external"; "false"; "float";
+          "for"; "forall"; "foreign"; "fun"; "function"; "functor"; "goto";
+          "hiding"; "if"; "import"; "in"; "include"; "infix"; "infixl";
+          "infixr"; "inherit"; "initializer"; "inline"; "instance"; "int";
+          "land"; "lazy"; "let"; "long"; "lor"; "lsl"; "lsr"; "lxor";
+          "match"; "mdo"; "method"; "mod"; "module"; "mutable"; "new";
+          "newtype"; "object"; "of"; "open"; "or"; "private"; "qualified";
+          "rec"; "register"; "restrict"; "return"; "short"; "sig"; "signed";
+          "sizeof"; "static"; "struct"; "switch"; "then"; "to"; "true"; "try";
+          "type"; "typedef"; "union"; "unsigned"; "virtual"; "void";
+          "volatile"; "when"; "where"; "while";
+          ] in
+        if List.mem n reserved then
+          failwithf "%s has param/ret using reserved word %s" name n;
       in
 
       (match fst style with
@@ -4519,6 +4587,16 @@ and generate_actions_h () =
         name style
   ) all_functions
 
+(* Generate the guestfs-internal-actions.h file. *)
+and generate_internal_actions_h () =
+  generate_header CStyle LGPLv2;
+  List.iter (
+    fun (shortname, style, _, _, _, _, _) ->
+      let name = "guestfs__" ^ shortname in
+      generate_prototype ~single_line:true ~newline:true ~handle:"handle"
+        name style
+  ) non_daemon_functions
+
 (* Generate the client-side dispatch stubs. *)
 and generate_client_actions () =
   generate_header CStyle LGPLv2;
@@ -4528,6 +4606,7 @@ and generate_client_actions () =
 #include <stdlib.h>
 
 #include \"guestfs.h\"
+#include \"guestfs-internal-actions.h\"
 #include \"guestfs_protocol.h\"
 
 #define error guestfs_error
@@ -4590,6 +4669,68 @@ check_state (guestfs_h *g, const char *caller)
 
 ";
 
+  (* Generate code to generate guestfish call traces. *)
+  let trace_call shortname style =
+    pr "  if (guestfs__get_trace (g)) {\n";
+
+    let needs_i =
+      List.exists (function
+                  | StringList _ | DeviceList _ -> true
+                  | _ -> false) (snd style) in
+    if needs_i then (
+      pr "    int i;\n";
+      pr "\n"
+    );
+
+    pr "    printf (\"%s\");\n" shortname;
+    List.iter (
+      function
+      | String n                       (* strings *)
+      | Device n
+      | Pathname n
+      | Dev_or_Path n
+      | FileIn n
+      | FileOut n ->
+         (* guestfish doesn't support string escaping, so neither do we *)
+         pr "    printf (\" \\\"%%s\\\"\", %s);\n" n
+      | OptString n ->                 (* string option *)
+         pr "    if (%s) printf (\" \\\"%%s\\\"\", %s);\n" n n;
+         pr "    else printf (\" null\");\n"
+      | StringList n
+      | DeviceList n ->                        (* string list *)
+         pr "    putchar (' ');\n";
+         pr "    putchar ('\"');\n";
+         pr "    for (i = 0; %s[i]; ++i) {\n" n;
+         pr "      if (i > 0) putchar (' ');\n";
+         pr "      fputs (%s[i], stdout);\n" n;
+         pr "    }\n";
+         pr "    putchar ('\"');\n";
+      | Bool n ->                      (* boolean *)
+         pr "    fputs (%s ? \" true\" : \" false\", stdout);\n" n
+      | Int n ->                       (* int *)
+         pr "    printf (\" %%d\", %s);\n" n
+    ) (snd style);
+    pr "    putchar ('\\n');\n";
+    pr "  }\n";
+    pr "\n";
+  in
+
+  (* For non-daemon functions, generate a wrapper around each function. *)
+  List.iter (
+    fun (shortname, style, _, _, _, _, _) ->
+      let name = "guestfs_" ^ shortname in
+
+      generate_prototype ~extern:false ~semicolon:false ~newline:true
+        ~handle:"g" name style;
+      pr "{\n";
+      trace_call shortname style;
+      pr "  return guestfs__%s " shortname;
+      generate_c_call_args ~handle:"g" style;
+      pr ";\n";
+      pr "}\n";
+      pr "\n"
+  ) non_daemon_functions;
+
   (* Client-side stubs for each function. *)
   List.iter (
     fun (shortname, style, _, _, _, _, _) ->
@@ -4690,6 +4831,7 @@ check_state (guestfs_h *g, const char *caller)
       pr "  guestfs_main_loop *ml = guestfs_get_main_loop (g);\n";
       pr "  int serial;\n";
       pr "\n";
+      trace_call shortname style;
       pr "  if (check_state (g, \"%s\") == -1) return %s;\n" name error_code;
       pr "  guestfs_set_busy (g);\n";
       pr "\n";
@@ -5350,7 +5492,7 @@ static void print_table (char const *const *argv)
 int main (int argc, char *argv[])
 {
   char c = 0;
-  int failed = 0;
+  unsigned long int n_failed = 0;
   const char *filename;
   int fd;
   int nr_tests, test_num = 0;
@@ -5453,8 +5595,8 @@ int main (int argc, char *argv[])
     exit (1);
   }
 
-  if (guestfs_add_drive_ro (g, \"../images/test.sqsh\") == -1) {
-    printf (\"guestfs_add_drive_ro ../images/test.sqsh FAILED\\n\");
+  if (guestfs_add_drive_ro (g, \"../images/test.iso\") == -1) {
+    printf (\"guestfs_add_drive_ro ../images/test.iso FAILED\\n\");
     exit (1);
   }
 
@@ -5484,7 +5626,7 @@ int main (int argc, char *argv[])
       pr "  printf (\"%%3d/%%3d %s\\n\", test_num, nr_tests);\n" test_name;
       pr "  if (%s () == -1) {\n" test_name;
       pr "    printf (\"%s FAILED\\n\");\n" test_name;
-      pr "    failed++;\n";
+      pr "    n_failed++;\n";
       pr "  }\n";
   ) test_names;
   pr "\n";
@@ -5495,8 +5637,8 @@ int main (int argc, char *argv[])
   pr "  unlink (\"test3.img\");\n";
   pr "\n";
 
-  pr "  if (failed > 0) {\n";
-  pr "    printf (\"***** %%d / %%d tests FAILED *****\\n\", failed, nr_tests);\n";
+  pr "  if (n_failed > 0) {\n";
+  pr "    printf (\"***** %%lu / %%d tests FAILED *****\\n\", n_failed, nr_tests);\n";
   pr "    exit (1);\n";
   pr "  }\n";
   pr "\n";
@@ -5612,13 +5754,13 @@ and generate_one_test_body name i test_name init test =
           ["lvcreate"; "LV"; "VG"; "8"];
           ["mkfs"; "ext2"; "/dev/VG/LV"];
           ["mount"; "/dev/VG/LV"; "/"]]
-   | InitSquashFS ->
-       pr "  /* InitSquashFS for %s */\n" test_name;
+   | InitISOFS ->
+       pr "  /* InitISOFS 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"; "/"]]
+          ["mount_ro"; "/dev/sdd"; "/"]]
   );
 
   let get_seq_last = function
@@ -6080,7 +6222,7 @@ and generate_fish_cmds () =
     pr "static void print_%s_list (struct guestfs_%s_list *%ss)\n"
       typ typ typ;
     pr "{\n";
-    pr "  int i;\n";
+    pr "  unsigned int i;\n";
     pr "\n";
     pr "  for (i = 0; i < %ss->len; ++i) {\n" typ;
     pr "    printf (\"[%%d] = {\\n\", i);\n";
@@ -6100,7 +6242,7 @@ and generate_fish_cmds () =
       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";
+        pr "  unsigned int i;\n";
         pr "\n"
       );
       List.iter (
@@ -6192,7 +6334,7 @@ and generate_fish_cmds () =
         | OptString n
         | FileIn n
         | FileOut n -> pr "  const char *%s;\n" n
-        | StringList n | DeviceList n -> pr "  char *const *%s;\n" n
+        | StringList n | DeviceList n -> pr "  char **%s;\n" n
         | Bool n -> pr "  int %s;\n" n
         | Int n -> pr "  int %s;\n" n
       ) (snd style);
@@ -6235,6 +6377,15 @@ and generate_fish_cmds () =
       generate_c_call_args ~handle:"g" style;
       pr ";\n";
 
+      List.iter (
+        function
+        | Pathname name | Device name | Dev_or_Path name | String name
+        | OptString name | FileIn name | FileOut name | Bool name
+        | Int name -> ()
+        | StringList name | DeviceList name ->
+            pr "  free_strings (%s);\n" name
+      ) (snd style);
+
       (* Check return value for errors and display command results. *)
       (match fst style with
        | RErr -> pr "  return r;\n"
@@ -6663,6 +6814,29 @@ copy_table (char * const * argv)
 ";
 
   (* Struct copy functions. *)
+
+  let emit_ocaml_copy_list_function typ =
+    pr "static CAMLprim value\n";
+    pr "copy_%s_list (const struct guestfs_%s_list *%ss)\n" typ typ typ;
+    pr "{\n";
+    pr "  CAMLparam0 ();\n";
+    pr "  CAMLlocal2 (rv, v);\n";
+    pr "  unsigned int i;\n";
+    pr "\n";
+    pr "  if (%ss->len == 0)\n" typ;
+    pr "    CAMLreturn (Atom (0));\n";
+    pr "  else {\n";
+    pr "    rv = caml_alloc (%ss->len, 0);\n" typ;
+    pr "    for (i = 0; i < %ss->len; ++i) {\n" typ;
+    pr "      v = copy_%s (&%ss->val[i]);\n" typ typ;
+    pr "      caml_modify (&Field (rv, i), v);\n";
+    pr "    }\n";
+    pr "    CAMLreturn (rv);\n";
+    pr "  }\n";
+    pr "}\n";
+    pr "\n";
+  in
+
   List.iter (
     fun (typ, cols) ->
       let has_optpercent_col =
@@ -6709,29 +6883,17 @@ copy_table (char * const * argv)
       pr "  CAMLreturn (rv);\n";
       pr "}\n";
       pr "\n";
-
-      pr "static CAMLprim value\n";
-      pr "copy_%s_list (const struct guestfs_%s_list *%ss)\n"
-        typ typ typ;
-      pr "{\n";
-      pr "  CAMLparam0 ();\n";
-      pr "  CAMLlocal2 (rv, v);\n";
-      pr "  int i;\n";
-      pr "\n";
-      pr "  if (%ss->len == 0)\n" typ;
-      pr "    CAMLreturn (Atom (0));\n";
-      pr "  else {\n";
-      pr "    rv = caml_alloc (%ss->len, 0);\n" typ;
-      pr "    for (i = 0; i < %ss->len; ++i) {\n" typ;
-      pr "      v = copy_%s (&%ss->val[i]);\n" typ typ;
-      pr "      caml_modify (&Field (rv, i), v);\n";
-      pr "    }\n";
-      pr "    CAMLreturn (rv);\n";
-      pr "  }\n";
-      pr "}\n";
-      pr "\n";
   ) structs;
 
+  (* Emit a copy_TYPE_list function definition only if that function is used. *)
+  List.iter (
+    function
+    | typ, (RStructListOnly | RStructAndList) ->
+        (* generate the function for typ *)
+        emit_ocaml_copy_list_function typ
+    | typ, _ -> () (* empty *)
+  ) rstructs_used;
+
   (* The wrappers. *)
   List.iter (
     fun (name, style, _, _, _, _, _) ->
@@ -6741,6 +6903,10 @@ copy_table (char * const * argv)
       let needs_extra_vs =
         match fst style with RConstOptString _ -> true | _ -> false in
 
+      pr "/* Emit prototype to appease gcc's -Wmissing-prototypes. */\n";
+      pr "CAMLprim value ocaml_guestfs_%s (value %s" name (List.hd params);
+      List.iter (pr ", value %s") (List.tl params); pr ");\n";
+
       pr "CAMLprim value\n";
       pr "ocaml_guestfs_%s (value %s" name (List.hd params);
       List.iter (pr ", value %s") (List.tl params);
@@ -6874,6 +7040,9 @@ copy_table (char * const * argv)
       pr "\n";
 
       if List.length params > 5 then (
+        pr "/* Emit prototype to appease gcc's -Wmissing-prototypes. */\n";
+        pr "CAMLprim value ";
+        pr "ocaml_guestfs_%s_byte (value *argv, int argn);\n" name;
         pr "CAMLprim value\n";
         pr "ocaml_guestfs_%s_byte (value *argv, int argn)\n" name;
         pr "{\n";
@@ -9042,6 +9211,7 @@ and generate_bindtests () =
 #include <string.h>
 
 #include \"guestfs.h\"
+#include \"guestfs-internal-actions.h\"
 #include \"guestfs_protocol.h\"
 
 #define error guestfs_error
@@ -9072,7 +9242,7 @@ print_strings (char *const *argv)
   let () =
     let (name, style, _, _, _, _, _) = test0 in
     generate_prototype ~extern:false ~semicolon:false ~newline:true
-      ~handle:"g" ~prefix:"guestfs_" name style;
+      ~handle:"g" ~prefix:"guestfs__" name style;
     pr "{\n";
     List.iter (
       function
@@ -9097,7 +9267,7 @@ print_strings (char *const *argv)
       if String.sub name (String.length name - 3) 3 <> "err" then (
         pr "/* Test normal return. */\n";
         generate_prototype ~extern:false ~semicolon:false ~newline:true
-          ~handle:"g" ~prefix:"guestfs_" name style;
+          ~handle:"g" ~prefix:"guestfs__" name style;
         pr "{\n";
         (match fst style with
          | RErr ->
@@ -9163,7 +9333,7 @@ print_strings (char *const *argv)
       ) else (
         pr "/* Test error return. */\n";
         generate_prototype ~extern:false ~semicolon:false ~newline:true
-          ~handle:"g" ~prefix:"guestfs_" name style;
+          ~handle:"g" ~prefix:"guestfs__" name style;
         pr "{\n";
         pr "  error (g, \"error\");\n";
         (match fst style with
@@ -9486,6 +9656,10 @@ Run it from the top source directory using the command
   generate_actions_h ();
   close ();
 
+  let close = output_to "src/guestfs-internal-actions.h" in
+  generate_internal_actions_h ();
+  close ();
+
   let close = output_to "src/guestfs-actions.c" in
   generate_client_actions ();
   close ();