X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=src%2Fgenerator.ml;h=674154126f7081eb8f80f551b33cff5483dcf37a;hp=92da3817d3ea7436be90ff91621e67d9cbeb08e7;hb=e2206733d1287f5809dbde954f3eb64420471b0d;hpb=8c60f5c681b5d7cf90bc798fcaf94cd4e242e27f diff --git a/src/generator.ml b/src/generator.ml index 92da381..6741541 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -136,12 +136,30 @@ can easily destroy all your data>." * the virtual machine and block devices are reused between tests. * So don't try testing kill_subprocess :-x * - * Between each test we umount-all and lvm-remove-all (except InitNone). + * Between each test we blockdev-setrw, umount-all, lvm-remove-all. + * + * If the appliance is running an older Linux kernel (eg. RHEL 5) then + * devices are named /dev/hda etc. To cope with this, the test suite + * adds some hairly logic to detect this case, and then automagically + * replaces all strings which match "/dev/sd.*" with "/dev/hd.*". + * When writing test cases you shouldn't have to worry about this + * difference. * * Don't assume anything about the previous contents of the block * devices. Use 'Init*' to create some initial scenarios. + * + * You can add a prerequisite clause to any individual test. This + * is a run-time check, which, if it fails, causes the test to be + * skipped. Useful if testing a command which might not work on + * all variations of libguestfs builds. A test that has prerequisite + * of 'Always' is run unconditionally. + * + * In addition, packagers can skip individual tests by setting the + * environment variables: eg: + * SKIP_TEST__=1 SKIP_TEST_COMMAND_3=1 (skips test #3 of command) + * SKIP_TEST_=1 SKIP_TEST_ZEROFREE=1 (skips all zerofree tests) *) -type tests = (test_init * test) list +type tests = (test_init * test_prereq * test) list and test = (* Run the command sequence and just expect nothing to fail. *) | TestRun of seq @@ -185,6 +203,21 @@ and test_field_compare = | CompareFieldsIntEq of string * string | CompareFieldsStrEq of string * string +(* Test prerequisites. *) +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 + (* Some initial scenarios for testing. *) and test_init = (* Do nothing, block devices could contain random stuff including @@ -297,9 +330,6 @@ configure script. You can also override this by setting the C environment variable. -The string C is stashed in the libguestfs handle, so the caller -must make sure it remains valid for the lifetime of the handle. - Setting C to C restores the default qemu binary."); ("get_qemu", (RConstString "qemu", []), -1, [], @@ -320,9 +350,6 @@ Set the path that libguestfs searches for kernel and initrd.img. The default is C<$libdir/guestfs> unless overridden by setting C environment variable. -The string C is stashed in the libguestfs handle, so the caller -must make sure it remains valid for the lifetime of the handle. - Setting C to C restores the default path."); ("get_path", (RConstString "path", []), -1, [], @@ -334,6 +361,28 @@ Return the current search path. This is always non-NULL. If it wasn't set already, then this will return the default path."); + ("set_append", (RErr, [String "append"]), -1, [FishAlias "append"], + [], + "add options to kernel command line", + "\ +This function is used to add additional options to the +guest kernel command line. + +The default is C unless overridden by setting +C environment variable. + +Setting C to C means I additional options +are passed (libguestfs always adds a few of its own)."); + + ("get_append", (RConstString "append", []), -1, [], + [], + "get the additional kernel options", + "\ +Return the additional kernel options which are added to the +guest kernel command line. + +If C then no options are added."); + ("set_autosync", (RErr, [Bool "autosync"]), -1, [FishAlias "autosync"], [], "set autosync mode", @@ -430,11 +479,21 @@ actions using the low-level API. For more information on states, see L."); + ("end_busy", (RErr, []), -1, [NotInFish], + [], + "leave the busy state", + "\ +This sets the state to C, or if in C then it leaves the +state as is. This is only used when implementing +actions using the low-level API. + +For more information on states, see L."); + ] let daemon_functions = [ ("mount", (RErr, [String "device"; String "mountpoint"]), 1, [], - [InitEmpty, TestOutput ( + [InitEmpty, Always, TestOutput ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; ["mkfs"; "ext2"; "/dev/sda1"]; ["mount"; "/dev/sda1"; "/"]; @@ -460,7 +519,7 @@ The filesystem options C and C are set with this call, in order to improve reliability."); ("sync", (RErr, []), 2, [], - [ InitEmpty, TestRun [["sync"]]], + [ InitEmpty, Always, TestRun [["sync"]]], "sync disks, writes are flushed through to the disk image", "\ This syncs the disk, so that any writes are flushed through to the @@ -470,7 +529,7 @@ You should always call this if you have modified a disk image, before closing the handle."); ("touch", (RErr, [String "path"]), 3, [], - [InitBasicFS, TestOutputTrue ( + [InitBasicFS, Always, TestOutputTrue ( [["touch"; "/new"]; ["exists"; "/new"]])], "update file timestamps or create a new file", @@ -480,7 +539,7 @@ 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, TestOutput ( + [InitBasicFS, Always, TestOutput ( [["write_file"; "/new"; "new file contents"; "0"]; ["cat"; "/new"]], "new file contents")], "list the contents of a file", @@ -505,7 +564,7 @@ This command is mostly useful for interactive sessions. It is I intended that you try to parse the output string."); ("ls", (RStringList "listing", [String "directory"]), 6, [], - [InitBasicFS, TestOutputList ( + [InitBasicFS, Always, TestOutputList ( [["touch"; "/new"]; ["touch"; "/newer"]; ["touch"; "/newest"]; @@ -520,7 +579,7 @@ This command is mostly useful for interactive sessions. Programs should probably use C instead."); ("list_devices", (RStringList "devices", []), 7, [], - [InitEmpty, TestOutputList ( + [InitEmpty, Always, TestOutputList ( [["list_devices"]], ["/dev/sda"; "/dev/sdb"; "/dev/sdc"])], "list the block devices", "\ @@ -529,9 +588,9 @@ List all the block devices. The full block device names are returned, eg. C"); ("list_partitions", (RStringList "partitions", []), 8, [], - [InitBasicFS, TestOutputList ( + [InitBasicFS, Always, TestOutputList ( [["list_partitions"]], ["/dev/sda1"]); - InitEmpty, TestOutputList ( + InitEmpty, Always, TestOutputList ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"]; ["list_partitions"]], ["/dev/sda1"; "/dev/sda2"; "/dev/sda3"])], "list the partitions", @@ -544,9 +603,9 @@ This does not return logical volumes. For that you will need to call C."); ("pvs", (RStringList "physvols", []), 9, [], - [InitBasicFSonLVM, TestOutputList ( + [InitBasicFSonLVM, Always, TestOutputList ( [["pvs"]], ["/dev/sda1"]); - InitEmpty, TestOutputList ( + InitEmpty, Always, TestOutputList ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"]; ["pvcreate"; "/dev/sda1"]; ["pvcreate"; "/dev/sda2"]; @@ -563,9 +622,9 @@ PVs (eg. C). See also C."); ("vgs", (RStringList "volgroups", []), 10, [], - [InitBasicFSonLVM, TestOutputList ( + [InitBasicFSonLVM, Always, TestOutputList ( [["vgs"]], ["VG"]); - InitEmpty, TestOutputList ( + InitEmpty, Always, TestOutputList ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"]; ["pvcreate"; "/dev/sda1"]; ["pvcreate"; "/dev/sda2"]; @@ -584,9 +643,9 @@ detected (eg. C). See also C."); ("lvs", (RStringList "logvols", []), 11, [], - [InitBasicFSonLVM, TestOutputList ( + [InitBasicFSonLVM, Always, TestOutputList ( [["lvs"]], ["/dev/VG/LV"]); - InitEmpty, TestOutputList ( + InitEmpty, Always, TestOutputList ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"]; ["pvcreate"; "/dev/sda1"]; ["pvcreate"; "/dev/sda2"]; @@ -629,10 +688,10 @@ List all the logical volumes detected. This is the equivalent of the L command. The \"full\" version includes all fields."); ("read_lines", (RStringList "lines", [String "path"]), 15, [], - [InitBasicFS, TestOutputList ( + [InitBasicFS, Always, TestOutputList ( [["write_file"; "/new"; "line1\r\nline2\nline3"; "0"]; ["read_lines"; "/new"]], ["line1"; "line2"; "line3"]); - InitBasicFS, TestOutputList ( + InitBasicFS, Always, TestOutputList ( [["write_file"; "/new"; ""; "0"]; ["read_lines"; "/new"]], [])], "read file as lines", @@ -807,12 +866,12 @@ This is just a shortcut for listing C C and sorting the resulting nodes into alphabetical order."); ("rm", (RErr, [String "path"]), 29, [], - [InitBasicFS, TestRun + [InitBasicFS, Always, TestRun [["touch"; "/new"]; ["rm"; "/new"]]; - InitBasicFS, TestLastFail + InitBasicFS, Always, TestLastFail [["rm"; "/new"]]; - InitBasicFS, TestLastFail + InitBasicFS, Always, TestLastFail [["mkdir"; "/new"]; ["rm"; "/new"]]], "remove a file", @@ -820,12 +879,12 @@ C and sorting the resulting nodes into alphabetical order."); Remove the single file C."); ("rmdir", (RErr, [String "path"]), 30, [], - [InitBasicFS, TestRun + [InitBasicFS, Always, TestRun [["mkdir"; "/new"]; ["rmdir"; "/new"]]; - InitBasicFS, TestLastFail + InitBasicFS, Always, TestLastFail [["rmdir"; "/new"]]; - InitBasicFS, TestLastFail + InitBasicFS, Always, TestLastFail [["touch"; "/new"]; ["rmdir"; "/new"]]], "remove a directory", @@ -833,7 +892,7 @@ Remove the single file C."); Remove the single directory C."); ("rm_rf", (RErr, [String "path"]), 31, [], - [InitBasicFS, TestOutputFalse + [InitBasicFS, Always, TestOutputFalse [["mkdir"; "/new"]; ["mkdir"; "/new/foo"]; ["touch"; "/new/foo/bar"]; @@ -846,23 +905,23 @@ contents if its a directory. This is like the C shell command."); ("mkdir", (RErr, [String "path"]), 32, [], - [InitBasicFS, TestOutputTrue + [InitBasicFS, Always, TestOutputTrue [["mkdir"; "/new"]; ["is_dir"; "/new"]]; - InitBasicFS, TestLastFail + InitBasicFS, Always, TestLastFail [["mkdir"; "/new/foo/bar"]]], "create a directory", "\ Create a directory named C."); ("mkdir_p", (RErr, [String "path"]), 33, [], - [InitBasicFS, TestOutputTrue + [InitBasicFS, Always, TestOutputTrue [["mkdir_p"; "/new/foo/bar"]; ["is_dir"; "/new/foo/bar"]]; - InitBasicFS, TestOutputTrue + InitBasicFS, Always, TestOutputTrue [["mkdir_p"; "/new/foo/bar"]; ["is_dir"; "/new/foo"]]; - InitBasicFS, TestOutputTrue + InitBasicFS, Always, TestOutputTrue [["mkdir_p"; "/new/foo/bar"]; ["is_dir"; "/new"]]], "create a directory and parents", @@ -888,10 +947,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, TestOutputTrue ( + [InitBasicFS, Always, TestOutputTrue ( [["touch"; "/new"]; ["exists"; "/new"]]); - InitBasicFS, TestOutputTrue ( + InitBasicFS, Always, TestOutputTrue ( [["mkdir"; "/new"]; ["exists"; "/new"]])], "test if file or directory exists", @@ -902,10 +961,10 @@ This returns C if and only if there is a file, directory See also C, C, C."); ("is_file", (RBool "fileflag", [String "path"]), 37, [], - [InitBasicFS, TestOutputTrue ( + [InitBasicFS, Always, TestOutputTrue ( [["touch"; "/new"]; ["is_file"; "/new"]]); - InitBasicFS, TestOutputFalse ( + InitBasicFS, Always, TestOutputFalse ( [["mkdir"; "/new"]; ["is_file"; "/new"]])], "test if file exists", @@ -917,10 +976,10 @@ other objects like directories. See also C."); ("is_dir", (RBool "dirflag", [String "path"]), 38, [], - [InitBasicFS, TestOutputFalse ( + [InitBasicFS, Always, TestOutputFalse ( [["touch"; "/new"]; ["is_dir"; "/new"]]); - InitBasicFS, TestOutputTrue ( + InitBasicFS, Always, TestOutputTrue ( [["mkdir"; "/new"]; ["is_dir"; "/new"]])], "test if file exists", @@ -932,7 +991,7 @@ other objects like files. See also C."); ("pvcreate", (RErr, [String "device"]), 39, [], - [InitEmpty, TestOutputList ( + [InitEmpty, Always, TestOutputList ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"]; ["pvcreate"; "/dev/sda1"]; ["pvcreate"; "/dev/sda2"]; @@ -945,7 +1004,7 @@ where C should usually be a partition name such as C."); ("vgcreate", (RErr, [String "volgroup"; StringList "physvols"]), 40, [], - [InitEmpty, TestOutputList ( + [InitEmpty, Always, TestOutputList ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"]; ["pvcreate"; "/dev/sda1"]; ["pvcreate"; "/dev/sda2"]; @@ -959,7 +1018,7 @@ This creates an LVM volume group called C from the non-empty list of physical volumes C."); ("lvcreate", (RErr, [String "logvol"; String "volgroup"; Int "mbytes"]), 41, [], - [InitEmpty, TestOutputList ( + [InitEmpty, Always, TestOutputList ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"]; ["pvcreate"; "/dev/sda1"]; ["pvcreate"; "/dev/sda2"]; @@ -980,7 +1039,7 @@ This creates an LVM volume group called C on the volume group C, with C megabytes."); ("mkfs", (RErr, [String "fstype"; String "device"]), 42, [], - [InitEmpty, TestOutput ( + [InitEmpty, Always, TestOutput ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; ["mkfs"; "ext2"; "/dev/sda1"]; ["mount"; "/dev/sda1"; "/"]; @@ -989,7 +1048,7 @@ on the volume group C, with C megabytes."); "make a filesystem", "\ This creates a filesystem on C (usually a partition -of LVM logical volume). The filesystem type is C, for +or LVM logical volume). The filesystem type is C, for example C."); ("sfdisk", (RErr, [String "device"; @@ -1016,25 +1075,27 @@ information refer to the L manpage. To create a single partition occupying the whole disk, you would pass C as a single element list, when the single element being -the string C<,> (comma)."); +the string C<,> (comma). + +See also: C, C"); ("write_file", (RErr, [String "path"; String "content"; Int "size"]), 44, [ProtocolLimitWarning], - [InitBasicFS, TestOutput ( + [InitBasicFS, Always, TestOutput ( [["write_file"; "/new"; "new file contents"; "0"]; ["cat"; "/new"]], "new file contents"); - InitBasicFS, TestOutput ( + InitBasicFS, Always, TestOutput ( [["write_file"; "/new"; "\nnew file contents\n"; "0"]; ["cat"; "/new"]], "\nnew file contents\n"); - InitBasicFS, TestOutput ( + InitBasicFS, Always, TestOutput ( [["write_file"; "/new"; "\n\n"; "0"]; ["cat"; "/new"]], "\n\n"); - InitBasicFS, TestOutput ( + InitBasicFS, Always, TestOutput ( [["write_file"; "/new"; ""; "0"]; ["cat"; "/new"]], ""); - InitBasicFS, TestOutput ( + InitBasicFS, Always, TestOutput ( [["write_file"; "/new"; "\n\n\n"; "0"]; ["cat"; "/new"]], "\n\n\n"); - InitBasicFS, TestOutput ( + InitBasicFS, Always, TestOutput ( [["write_file"; "/new"; "\n"; "0"]; ["cat"; "/new"]], "\n")], "create a file", @@ -1045,15 +1106,20 @@ with length C. As a special case, if C is C<0> then the length is calculated using C (so in this case -the content cannot contain embedded ASCII NULs)."); +the content cannot contain embedded ASCII NULs). + +I Owing to a bug, writing content containing ASCII NUL +characters does I work, even if the length is specified. +We hope to resolve this bug in a future version. In the meantime +use C."); ("umount", (RErr, [String "pathordevice"]), 45, [FishAlias "unmount"], - [InitEmpty, TestOutputList ( + [InitEmpty, Always, TestOutputList ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; ["mkfs"; "ext2"; "/dev/sda1"]; ["mount"; "/dev/sda1"; "/"]; ["mounts"]], ["/dev/sda1"]); - InitEmpty, TestOutputList ( + InitEmpty, Always, TestOutputList ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; ["mkfs"; "ext2"; "/dev/sda1"]; ["mount"; "/dev/sda1"; "/"]; @@ -1066,7 +1132,7 @@ specified either by its mountpoint (path) or the device which contains the filesystem."); ("mounts", (RStringList "devices", []), 46, [], - [InitBasicFS, TestOutputList ( + [InitBasicFS, Always, TestOutputList ( [["mounts"]], ["/dev/sda1"])], "show mounted filesystems", "\ @@ -1076,11 +1142,11 @@ the list of devices (eg. C, C). Some internal mounts are not shown."); ("umount_all", (RErr, []), 47, [FishAlias "unmount-all"], - [InitBasicFS, TestOutputList ( + [InitBasicFS, Always, TestOutputList ( [["umount_all"]; ["mounts"]], []); (* check that umount_all can unmount nested mounts correctly: *) - InitEmpty, TestOutputList ( + InitEmpty, Always, TestOutputList ( [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"]; ["mkfs"; "ext2"; "/dev/sda1"]; ["mkfs"; "ext2"; "/dev/sda2"]; @@ -1107,13 +1173,13 @@ This command removes all LVM logical volumes, volume groups and physical volumes."); ("file", (RString "description", [String "path"]), 49, [], - [InitBasicFS, TestOutput ( + [InitBasicFS, Always, TestOutput ( [["touch"; "/new"]; ["file"; "/new"]], "empty"); - InitBasicFS, TestOutput ( + InitBasicFS, Always, TestOutput ( [["write_file"; "/new"; "some content\n"; "0"]; ["file"; "/new"]], "ASCII text"); - InitBasicFS, TestLastFail ( + InitBasicFS, Always, TestLastFail ( [["file"; "/nofile"]])], "determine file type", "\ @@ -1125,8 +1191,55 @@ The exact command which runs is C. Note in particular that the filename is not prepended to the output (the C<-b> option)."); - ("command", (RString "output", [StringList "arguments"]), 50, [], - [], (* XXX how to test? *) + ("command", (RString "output", [StringList "arguments"]), 50, [ProtocolLimitWarning], + [InitBasicFS, Always, TestOutput ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command"; "/test-command 1"]], "Result1"); + InitBasicFS, Always, TestOutput ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command"; "/test-command 2"]], "Result2\n"); + InitBasicFS, Always, TestOutput ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command"; "/test-command 3"]], "\nResult3"); + InitBasicFS, Always, TestOutput ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command"; "/test-command 4"]], "\nResult4\n"); + InitBasicFS, Always, TestOutput ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command"; "/test-command 5"]], "\nResult5\n\n"); + InitBasicFS, Always, TestOutput ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command"; "/test-command 6"]], "\n\nResult6\n\n"); + InitBasicFS, Always, TestOutput ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command"; "/test-command 7"]], ""); + InitBasicFS, Always, TestOutput ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command"; "/test-command 8"]], "\n"); + InitBasicFS, Always, TestOutput ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command"; "/test-command 9"]], "\n\n"); + InitBasicFS, Always, TestOutput ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command"; "/test-command 10"]], "Result10-1\nResult10-2\n"); + InitBasicFS, Always, TestOutput ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command"; "/test-command 11"]], "Result11-1\nResult11-2"); + InitBasicFS, Always, TestLastFail ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command"; "/test-command"]])], "run a command from the guest filesystem", "\ This call runs a command from the guest filesystem. The @@ -1139,6 +1252,13 @@ The first element is the name of the program to run. Subsequent elements are parameters. The list must be non-empty (ie. must contain a program name). +The return value is anything printed to I by +the command. + +If the command returns a non-zero exit status, then +this function returns an error message. The error message +string is the content of I from the command. + The C<$PATH> environment variable will contain at least C and C. If you require a program from another location, you should provide the full path in the @@ -1150,15 +1270,58 @@ correct places. It is the caller's responsibility to ensure all filesystems that are needed are mounted at the right locations."); - ("command_lines", (RStringList "lines", [StringList "arguments"]), 51, [], - [], (* XXX how to test? *) + ("command_lines", (RStringList "lines", [StringList "arguments"]), 51, [ProtocolLimitWarning], + [InitBasicFS, Always, TestOutputList ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command_lines"; "/test-command 1"]], ["Result1"]); + InitBasicFS, Always, TestOutputList ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command_lines"; "/test-command 2"]], ["Result2"]); + InitBasicFS, Always, TestOutputList ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command_lines"; "/test-command 3"]], ["";"Result3"]); + InitBasicFS, Always, TestOutputList ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command_lines"; "/test-command 4"]], ["";"Result4"]); + InitBasicFS, Always, TestOutputList ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command_lines"; "/test-command 5"]], ["";"Result5";""]); + InitBasicFS, Always, TestOutputList ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command_lines"; "/test-command 6"]], ["";"";"Result6";""]); + InitBasicFS, Always, TestOutputList ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command_lines"; "/test-command 7"]], []); + InitBasicFS, Always, TestOutputList ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command_lines"; "/test-command 8"]], [""]); + InitBasicFS, Always, TestOutputList ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command_lines"; "/test-command 9"]], ["";""]); + InitBasicFS, Always, TestOutputList ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command_lines"; "/test-command 10"]], ["Result10-1";"Result10-2"]); + InitBasicFS, Always, TestOutputList ( + [["upload"; "test-command"; "/test-command"]; + ["chmod"; "493"; "/test-command"]; + ["command_lines"; "/test-command 11"]], ["Result11-1";"Result11-2"])], "run a command, returning lines", "\ This is the same as C, but splits the result into a list of lines."); ("stat", (RStat "statbuf", [String "path"]), 52, [], - [InitBasicFS, TestOutputStruct ( + [InitBasicFS, Always, TestOutputStruct ( [["touch"; "/new"]; ["stat"; "/new"]], [CompareWithInt ("size", 0)])], "get file information", @@ -1168,7 +1331,7 @@ Returns file information for the given C. This is the same as the C system call."); ("lstat", (RStat "statbuf", [String "path"]), 53, [], - [InitBasicFS, TestOutputStruct ( + [InitBasicFS, Always, TestOutputStruct ( [["touch"; "/new"]; ["lstat"; "/new"]], [CompareWithInt ("size", 0)])], "get file information for a symbolic link", @@ -1182,7 +1345,7 @@ refers to. This is the same as the C system call."); ("statvfs", (RStatVFS "statbuf", [String "path"]), 54, [], - [InitBasicFS, TestOutputStruct ( + [InitBasicFS, Always, TestOutputStruct ( [["statvfs"; "/"]], [CompareWithInt ("bfree", 487702); CompareWithInt ("blocks", 490020); CompareWithInt ("bsize", 1024)])], @@ -1207,7 +1370,7 @@ clearly defined, and depends on both the version of C that libguestfs was built against, and the filesystem itself."); ("blockdev_setro", (RErr, [String "device"]), 56, [], - [InitEmpty, TestOutputTrue ( + [InitEmpty, Always, TestOutputTrue ( [["blockdev_setro"; "/dev/sda"]; ["blockdev_getro"; "/dev/sda"]])], "set block device to read-only", @@ -1217,7 +1380,7 @@ Sets the block device named C to read-only. This uses the L command."); ("blockdev_setrw", (RErr, [String "device"]), 57, [], - [InitEmpty, TestOutputFalse ( + [InitEmpty, Always, TestOutputFalse ( [["blockdev_setrw"; "/dev/sda"]; ["blockdev_getro"; "/dev/sda"]])], "set block device to read-write", @@ -1227,7 +1390,7 @@ Sets the block device named C to read-write. This uses the L command."); ("blockdev_getro", (RBool "ro", [String "device"]), 58, [], - [InitEmpty, TestOutputTrue ( + [InitEmpty, Always, TestOutputTrue ( [["blockdev_setro"; "/dev/sda"]; ["blockdev_getro"; "/dev/sda"]])], "is block device set to read-only", @@ -1238,7 +1401,7 @@ Returns a boolean indicating if the block device is read-only This uses the L command."); ("blockdev_getss", (RInt "sectorsize", [String "device"]), 59, [], - [InitEmpty, TestOutputInt ( + [InitEmpty, Always, TestOutputInt ( [["blockdev_getss"; "/dev/sda"]], 512)], "get sectorsize of block device", "\ @@ -1251,7 +1414,7 @@ for that). This uses the L command."); ("blockdev_getbsz", (RInt "blocksize", [String "device"]), 60, [], - [InitEmpty, TestOutputInt ( + [InitEmpty, Always, TestOutputInt ( [["blockdev_getbsz"; "/dev/sda"]], 4096)], "get blocksize of block device", "\ @@ -1274,7 +1437,7 @@ I). This uses the L command."); ("blockdev_getsz", (RInt64 "sizeinsectors", [String "device"]), 62, [], - [InitEmpty, TestOutputInt ( + [InitEmpty, Always, TestOutputInt ( [["blockdev_getsz"; "/dev/sda"]], 1024000)], "get total size of device in 512-byte sectors", "\ @@ -1288,7 +1451,7 @@ useful I. This uses the L command."); ("blockdev_getsize64", (RInt64 "sizeinbytes", [String "device"]), 63, [], - [InitEmpty, TestOutputInt ( + [InitEmpty, Always, TestOutputInt ( [["blockdev_getsize64"; "/dev/sda"]], 524288000)], "get total size of device in bytes", "\ @@ -1299,7 +1462,7 @@ See also C. This uses the L command."); ("blockdev_flushbufs", (RErr, [String "device"]), 64, [], - [InitEmpty, TestRun + [InitEmpty, Always, TestRun [["blockdev_flushbufs"; "/dev/sda"]]], "flush device buffers", "\ @@ -1309,7 +1472,7 @@ with C. This uses the L command."); ("blockdev_rereadpt", (RErr, [String "device"]), 65, [], - [InitEmpty, TestRun + [InitEmpty, Always, TestRun [["blockdev_rereadpt"; "/dev/sda"]]], "reread partition table", "\ @@ -1318,7 +1481,7 @@ Reread the partition table on C. This uses the L command."); ("upload", (RErr, [FileIn "filename"; String "remotefilename"]), 66, [], - [InitBasicFS, TestOutput ( + [InitBasicFS, Always, TestOutput ( (* Pick a file from cwd which isn't likely to change. *) [["upload"; "COPYING.LIB"; "/COPYING.LIB"]; ["checksum"; "md5"; "/COPYING.LIB"]], "e3eda01d9815f8d24aae2dbd89b68b06")], @@ -1332,7 +1495,7 @@ C can also be a named pipe. See also C."); ("download", (RErr, [String "remotefilename"; FileOut "filename"]), 67, [], - [InitBasicFS, TestOutput ( + [InitBasicFS, Always, TestOutput ( (* Pick a file from cwd which isn't likely to change. *) [["upload"; "COPYING.LIB"; "/COPYING.LIB"]; ["download"; "/COPYING.LIB"; "testdownload.tmp"]; @@ -1348,27 +1511,27 @@ C can also be a named pipe. See also C, C."); ("checksum", (RString "checksum", [String "csumtype"; String "path"]), 68, [], - [InitBasicFS, TestOutput ( + [InitBasicFS, Always, TestOutput ( [["write_file"; "/new"; "test\n"; "0"]; ["checksum"; "crc"; "/new"]], "935282863"); - InitBasicFS, TestLastFail ( + InitBasicFS, Always, TestLastFail ( [["checksum"; "crc"; "/new"]]); - InitBasicFS, TestOutput ( + InitBasicFS, Always, TestOutput ( [["write_file"; "/new"; "test\n"; "0"]; ["checksum"; "md5"; "/new"]], "d8e8fca2dc0f896fd7cb4cb0031ba249"); - InitBasicFS, TestOutput ( + InitBasicFS, Always, TestOutput ( [["write_file"; "/new"; "test\n"; "0"]; ["checksum"; "sha1"; "/new"]], "4e1243bd22c66e76c2ba9eddc1f91394e57f9f83"); - InitBasicFS, TestOutput ( + InitBasicFS, Always, TestOutput ( [["write_file"; "/new"; "test\n"; "0"]; ["checksum"; "sha224"; "/new"]], "52f1bf093f4b7588726035c176c0cdb4376cfea53819f1395ac9e6ec"); - InitBasicFS, TestOutput ( + InitBasicFS, Always, TestOutput ( [["write_file"; "/new"; "test\n"; "0"]; ["checksum"; "sha256"; "/new"]], "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2"); - InitBasicFS, TestOutput ( + InitBasicFS, Always, TestOutput ( [["write_file"; "/new"; "test\n"; "0"]; ["checksum"; "sha384"; "/new"]], "109bb6b5b6d5547c1ce03c7a8bd7d8f80c1cb0957f50c4f7fda04692079917e4f9cad52b878f3d8234e1a170b154b72d"); - InitBasicFS, TestOutput ( + InitBasicFS, Always, TestOutput ( [["write_file"; "/new"; "test\n"; "0"]; ["checksum"; "sha512"; "/new"]], "0e3e75234abc68f4378a86b3f4b32a198ba301845b0cd6e50106e874345700cc6663a86c1ea125dc5e92be17c98f9a0f85ca9d5f595db2012f7cc3571945c123")], "compute MD5, SHAx or CRC checksum of file", @@ -1415,7 +1578,7 @@ Compute the SHA512 hash (using the C program). The checksum is returned as a printable string."); ("tar_in", (RErr, [FileIn "tarfile"; String "directory"]), 69, [], - [InitBasicFS, TestOutput ( + [InitBasicFS, Always, TestOutput ( [["tar_in"; "images/helloworld.tar"; "/"]; ["cat"; "/hello"]], "hello\n")], "unpack tarfile to directory", @@ -1435,7 +1598,7 @@ it to local file C. To download a compressed tarball, use C."); ("tgz_in", (RErr, [FileIn "tarball"; String "directory"]), 71, [], - [InitBasicFS, TestOutput ( + [InitBasicFS, Always, TestOutput ( [["tgz_in"; "images/helloworld.tar.gz"; "/"]; ["cat"; "/hello"]], "hello\n")], "unpack compressed tarball to directory", @@ -1455,11 +1618,11 @@ it to local file C. To download an uncompressed tarball, use C."); ("mount_ro", (RErr, [String "device"; String "mountpoint"]), 73, [], - [InitBasicFS, TestLastFail ( + [InitBasicFS, Always, TestLastFail ( [["umount"; "/"]; ["mount_ro"; "/dev/sda1"; "/"]; ["touch"; "/new"]]); - InitBasicFS, TestOutput ( + InitBasicFS, Always, TestOutput ( [["write_file"; "/new"; "data"; "0"]; ["umount"; "/"]; ["mount_ro"; "/dev/sda1"; "/"]; @@ -1498,23 +1661,26 @@ to look at the file C in the libguestfs source to find out what you can do."); ("lvremove", (RErr, [String "device"]), 77, [], - [InitEmpty, TestOutputList ( - [["pvcreate"; "/dev/sda"]; - ["vgcreate"; "VG"; "/dev/sda"]; + [InitEmpty, Always, TestOutputList ( + [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; + ["pvcreate"; "/dev/sda1"]; + ["vgcreate"; "VG"; "/dev/sda1"]; ["lvcreate"; "LV1"; "VG"; "50"]; ["lvcreate"; "LV2"; "VG"; "50"]; ["lvremove"; "/dev/VG/LV1"]; ["lvs"]], ["/dev/VG/LV2"]); - InitEmpty, TestOutputList ( - [["pvcreate"; "/dev/sda"]; - ["vgcreate"; "VG"; "/dev/sda"]; + InitEmpty, Always, TestOutputList ( + [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; + ["pvcreate"; "/dev/sda1"]; + ["vgcreate"; "VG"; "/dev/sda1"]; ["lvcreate"; "LV1"; "VG"; "50"]; ["lvcreate"; "LV2"; "VG"; "50"]; ["lvremove"; "/dev/VG"]; ["lvs"]], []); - InitEmpty, TestOutputList ( - [["pvcreate"; "/dev/sda"]; - ["vgcreate"; "VG"; "/dev/sda"]; + InitEmpty, Always, TestOutputList ( + [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; + ["pvcreate"; "/dev/sda1"]; + ["vgcreate"; "VG"; "/dev/sda1"]; ["lvcreate"; "LV1"; "VG"; "50"]; ["lvcreate"; "LV2"; "VG"; "50"]; ["lvremove"; "/dev/VG"]; @@ -1528,16 +1694,18 @@ You can also remove all LVs in a volume group by specifying the VG name, C."); ("vgremove", (RErr, [String "vgname"]), 78, [], - [InitEmpty, TestOutputList ( - [["pvcreate"; "/dev/sda"]; - ["vgcreate"; "VG"; "/dev/sda"]; + [InitEmpty, Always, TestOutputList ( + [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; + ["pvcreate"; "/dev/sda1"]; + ["vgcreate"; "VG"; "/dev/sda1"]; ["lvcreate"; "LV1"; "VG"; "50"]; ["lvcreate"; "LV2"; "VG"; "50"]; ["vgremove"; "VG"]; ["lvs"]], []); - InitEmpty, TestOutputList ( - [["pvcreate"; "/dev/sda"]; - ["vgcreate"; "VG"; "/dev/sda"]; + InitEmpty, Always, TestOutputList ( + [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; + ["pvcreate"; "/dev/sda1"]; + ["vgcreate"; "VG"; "/dev/sda1"]; ["lvcreate"; "LV1"; "VG"; "50"]; ["lvcreate"; "LV2"; "VG"; "50"]; ["vgremove"; "VG"]; @@ -1550,29 +1718,32 @@ This also forcibly removes all logical volumes in the volume group (if any)."); ("pvremove", (RErr, [String "device"]), 79, [], - [InitEmpty, TestOutputList ( - [["pvcreate"; "/dev/sda"]; - ["vgcreate"; "VG"; "/dev/sda"]; + [InitEmpty, Always, TestOutputList ( + [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; + ["pvcreate"; "/dev/sda1"]; + ["vgcreate"; "VG"; "/dev/sda1"]; ["lvcreate"; "LV1"; "VG"; "50"]; ["lvcreate"; "LV2"; "VG"; "50"]; ["vgremove"; "VG"]; - ["pvremove"; "/dev/sda"]; + ["pvremove"; "/dev/sda1"]; ["lvs"]], []); - InitEmpty, TestOutputList ( - [["pvcreate"; "/dev/sda"]; - ["vgcreate"; "VG"; "/dev/sda"]; + InitEmpty, Always, TestOutputList ( + [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; + ["pvcreate"; "/dev/sda1"]; + ["vgcreate"; "VG"; "/dev/sda1"]; ["lvcreate"; "LV1"; "VG"; "50"]; ["lvcreate"; "LV2"; "VG"; "50"]; ["vgremove"; "VG"]; - ["pvremove"; "/dev/sda"]; + ["pvremove"; "/dev/sda1"]; ["vgs"]], []); - InitEmpty, TestOutputList ( - [["pvcreate"; "/dev/sda"]; - ["vgcreate"; "VG"; "/dev/sda"]; + InitEmpty, Always, TestOutputList ( + [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; + ["pvcreate"; "/dev/sda1"]; + ["vgcreate"; "VG"; "/dev/sda1"]; ["lvcreate"; "LV1"; "VG"; "50"]; ["lvcreate"; "LV2"; "VG"; "50"]; ["vgremove"; "VG"]; - ["pvremove"; "/dev/sda"]; + ["pvremove"; "/dev/sda1"]; ["pvs"]], [])], "remove an LVM physical volume", "\ @@ -1584,7 +1755,7 @@ wipe physical volumes that contain any volume groups, so you have to remove those first."); ("set_e2label", (RErr, [String "device"; String "label"]), 80, [], - [InitBasicFS, TestOutput ( + [InitBasicFS, Always, TestOutput ( [["set_e2label"; "/dev/sda1"; "testlabel"]; ["get_e2label"; "/dev/sda1"]], "testlabel")], "set the ext2/3/4 filesystem label", @@ -1604,16 +1775,16 @@ This returns the ext2/3/4 filesystem label of the filesystem on C."); ("set_e2uuid", (RErr, [String "device"; String "uuid"]), 82, [], - [InitBasicFS, TestOutput ( + [InitBasicFS, Always, TestOutput ( [["set_e2uuid"; "/dev/sda1"; "a3a61220-882b-4f61-89f4-cf24dcc7297d"]; ["get_e2uuid"; "/dev/sda1"]], "a3a61220-882b-4f61-89f4-cf24dcc7297d"); - InitBasicFS, TestOutput ( + InitBasicFS, Always, TestOutput ( [["set_e2uuid"; "/dev/sda1"; "clear"]; ["get_e2uuid"; "/dev/sda1"]], ""); (* We can't predict what UUIDs will be, so just check the commands run. *) - InitBasicFS, TestRun ( + InitBasicFS, Always, TestRun ( [["set_e2uuid"; "/dev/sda1"; "random"]]); - InitBasicFS, TestRun ( + InitBasicFS, Always, TestRun ( [["set_e2uuid"; "/dev/sda1"; "time"]])], "set the ext2/3/4 filesystem UUID", "\ @@ -1633,10 +1804,10 @@ This returns the ext2/3/4 filesystem UUID of the filesystem on C."); ("fsck", (RInt "status", [String "fstype"; String "device"]), 84, [], - [InitBasicFS, TestOutputInt ( + [InitBasicFS, Always, TestOutputInt ( [["umount"; "/dev/sda1"]; ["fsck"; "ext2"; "/dev/sda1"]], 0); - InitBasicFS, TestOutputInt ( + InitBasicFS, Always, TestOutputInt ( [["umount"; "/dev/sda1"]; ["zero"; "/dev/sda1"]; ["fsck"; "ext2"; "/dev/sda1"]], 8)], @@ -1671,7 +1842,7 @@ Checking or repairing NTFS volumes is not supported This command is entirely equivalent to running C."); ("zero", (RErr, [String "device"]), 85, [], - [InitBasicFS, TestOutput ( + [InitBasicFS, Always, TestOutput ( [["umount"; "/dev/sda1"]; ["zero"; "/dev/sda1"]; ["file"; "/dev/sda1"]], "data")], @@ -1684,7 +1855,7 @@ to securely wipe the device). It should be sufficient to remove any partition tables, filesystem superblocks and so on."); ("grub_install", (RErr, [String "root"; String "device"]), 86, [], - [InitBasicFS, TestOutputTrue ( + [InitBasicFS, Always, TestOutputTrue ( [["grub_install"; "/"; "/dev/sda1"]; ["is_dir"; "/boot"]])], "install GRUB", @@ -1693,15 +1864,15 @@ This command installs GRUB (the Grand Unified Bootloader) on C, with the root directory being C."); ("cp", (RErr, [String "src"; String "dest"]), 87, [], - [InitBasicFS, TestOutput ( + [InitBasicFS, Always, TestOutput ( [["write_file"; "/old"; "file content"; "0"]; ["cp"; "/old"; "/new"]; ["cat"; "/new"]], "file content"); - InitBasicFS, TestOutputTrue ( + InitBasicFS, Always, TestOutputTrue ( [["write_file"; "/old"; "file content"; "0"]; ["cp"; "/old"; "/new"]; ["is_file"; "/old"]]); - InitBasicFS, TestOutput ( + InitBasicFS, Always, TestOutput ( [["write_file"; "/old"; "file content"; "0"]; ["mkdir"; "/dir"]; ["cp"; "/old"; "/dir/new"]; @@ -1712,7 +1883,7 @@ This copies a file from C to C where C is either a destination filename or destination directory."); ("cp_a", (RErr, [String "src"; String "dest"]), 88, [], - [InitBasicFS, TestOutput ( + [InitBasicFS, Always, TestOutput ( [["mkdir"; "/olddir"]; ["mkdir"; "/newdir"]; ["write_file"; "/olddir/file"; "file content"; "0"]; @@ -1724,11 +1895,11 @@ This copies a file or directory from C to C recursively using the C command."); ("mv", (RErr, [String "src"; String "dest"]), 89, [], - [InitBasicFS, TestOutput ( + [InitBasicFS, Always, TestOutput ( [["write_file"; "/old"; "file content"; "0"]; ["mv"; "/old"; "/new"]; ["cat"; "/new"]], "file content"); - InitBasicFS, TestOutputFalse ( + InitBasicFS, Always, TestOutputFalse ( [["write_file"; "/old"; "file content"; "0"]; ["mv"; "/old"; "/new"]; ["is_file"; "/old"]])], @@ -1738,7 +1909,7 @@ This moves a file from C to C where C is either a destination filename or destination directory."); ("drop_caches", (RErr, [Int "whattodrop"]), 90, [], - [InitEmpty, TestRun ( + [InitEmpty, Always, TestRun ( [["drop_caches"; "3"]])], "drop kernel page cache, dentries and inodes", "\ @@ -1753,7 +1924,7 @@ This automatically calls L before the operation, so that the maximum guest memory is freed."); ("dmesg", (RString "kmsgs", []), 91, [], - [InitEmpty, TestRun ( + [InitEmpty, Always, TestRun ( [["dmesg"]])], "return kernel messages", "\ @@ -1766,6 +1937,256 @@ verbose messages with C or by setting the environment variable C before running the program."); + ("ping_daemon", (RErr, []), 92, [], + [InitEmpty, Always, TestRun ( + [["ping_daemon"]])], + "ping the guest daemon", + "\ +This is a test probe into the guestfs daemon running inside +the qemu subprocess. Calling this function checks that the +daemon responds to the ping message, without affecting the daemon +or attached block device(s) in any other way."); + + ("equal", (RBool "equality", [String "file1"; String "file2"]), 93, [], + [InitBasicFS, Always, TestOutputTrue ( + [["write_file"; "/file1"; "contents of a file"; "0"]; + ["cp"; "/file1"; "/file2"]; + ["equal"; "/file1"; "/file2"]]); + InitBasicFS, Always, TestOutputFalse ( + [["write_file"; "/file1"; "contents of a file"; "0"]; + ["write_file"; "/file2"; "contents of another file"; "0"]; + ["equal"; "/file1"; "/file2"]]); + InitBasicFS, Always, TestLastFail ( + [["equal"; "/file1"; "/file2"]])], + "test if two files have equal contents", + "\ +This compares the two files C and C and returns +true if their content is exactly equal, or false otherwise. + +The external L 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"]], [])], + "print the printable strings in a file", + "\ +This runs the L 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"]], []); + 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"])], + "print the printable strings in a file", + "\ +This is like the C command, but allows you to +specify the encoding. + +See the L manpage for the full list of encodings. + +Commonly useful encodings are C (lower case L) which will +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")], + "dump a file in hexadecimal", + "\ +This runs C on the given C. The result is +the human-readable, canonical hex dump of the file."); + + ("zerofree", (RErr, [String "device"]), 97, [], + [InitNone, Always, TestOutput ( + [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; + ["mkfs"; "ext3"; "/dev/sda1"]; + ["mount"; "/dev/sda1"; "/"]; + ["write_file"; "/new"; "test file"; "0"]; + ["umount"; "/dev/sda1"]; + ["zerofree"; "/dev/sda1"]; + ["mount"; "/dev/sda1"; "/"]; + ["cat"; "/new"]], "test file")], + "zero unused inodes and disk blocks on ext2/3 filesystem", + "\ +This runs the I program on C. This program +claims to zero unused inodes and disk blocks on an ext2/3 +filesystem, thus making it possible to compress the filesystem +more effectively. + +You should B run this program if the filesystem is +mounted. + +It is possible that using this program can damage the filesystem +or data on the filesystem."); + + ("pvresize", (RErr, [String "device"]), 98, [], + [], + "resize an LVM physical volume", + "\ +This resizes (expands or shrinks) an existing LVM physical +volume to match the new size of the underlying device."); + + ("sfdisk_N", (RErr, [String "device"; Int "n"; + Int "cyls"; Int "heads"; Int "sectors"; + String "line"]), 99, [DangerWillRobinson], + [], + "modify a single partition on a block device", + "\ +This runs L option to modify just the single +partition C (note: C counts from 1). + +For other parameters, see C. You should usually +pass C<0> for the cyls/heads/sectors parameters."); + + ("sfdisk_l", (RString "partitions", [String "device"]), 100, [], + [], + "display the partition table", + "\ +This displays the partition table on C, in the +human-readable output of the L command. It is +not intended to be parsed."); + + ("sfdisk_kernel_geometry", (RString "partitions", [String "device"]), 101, [], + [], + "display the kernel geometry", + "\ +This displays the kernel's idea of the geometry of C. + +The result is in human-readable format, and not designed to +be parsed."); + + ("sfdisk_disk_geometry", (RString "partitions", [String "device"]), 102, [], + [], + "display the disk geometry from the partition table", + "\ +This displays the disk geometry of C read from the +partition table. Especially in the case where the underlying +block device has been resized, this can be different from the +kernel's idea of the geometry (see C). + +The result is in human-readable format, and not designed to +be parsed."); + + ("vg_activate_all", (RErr, [Bool "activate"]), 103, [], + [], + "activate or deactivate all volume groups", + "\ +This command activates or (if C is false) deactivates +all logical volumes in all volume groups. +If activated, then they are made known to the +kernel, ie. they appear as C devices. If deactivated, +then those devices disappear. + +This command is the same as running C"); + + ("vg_activate", (RErr, [Bool "activate"; StringList "volgroups"]), 104, [], + [], + "activate or deactivate some volume groups", + "\ +This command activates or (if C is false) deactivates +all logical volumes in the listed volume groups C. +If activated, then they are made known to the +kernel, ie. they appear as C devices. If deactivated, +then those devices disappear. + +This command is the same as running C + +Note that if C is an empty list then B volume groups +are activated or deactivated."); + + ("lvresize", (RErr, [String "device"; Int "mbytes"]), 105, [], + [InitNone, Always, TestOutput ( + [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; + ["pvcreate"; "/dev/sda1"]; + ["vgcreate"; "VG"; "/dev/sda1"]; + ["lvcreate"; "LV"; "VG"; "10"]; + ["mkfs"; "ext2"; "/dev/VG/LV"]; + ["mount"; "/dev/VG/LV"; "/"]; + ["write_file"; "/new"; "test content"; "0"]; + ["umount"; "/"]; + ["lvresize"; "/dev/VG/LV"; "20"]; + ["e2fsck_f"; "/dev/VG/LV"]; + ["resize2fs"; "/dev/VG/LV"]; + ["mount"; "/dev/VG/LV"; "/"]; + ["cat"; "/new"]], "test content")], + "resize an LVM logical volume", + "\ +This resizes (expands or shrinks) an existing LVM logical +volume to C. When reducing, data in the reduced part +is lost."); + + ("resize2fs", (RErr, [String "device"]), 106, [], + [], (* lvresize tests this *) + "resize an ext2/ext3 filesystem", + "\ +This resizes an ext2 or ext3 filesystem to match the size of +the underlying device. + +I It is sometimes required that you run C +on the C before calling this command. For unknown reasons +C sometimes gives an error about this and sometimes not. +In any case, it is always safe to call C before +calling this function."); + + ("find", (RStringList "names", [String "directory"]), 107, [], + [InitBasicFS, Always, TestOutputList ( + [["find"; "/"]], ["lost+found"]); + InitBasicFS, Always, TestOutputList ( + [["touch"; "/a"]; + ["mkdir"; "/b"]; + ["touch"; "/b/c"]; + ["find"; "/"]], ["a"; "b"; "b/c"; "lost+found"]); + InitBasicFS, Always, TestOutputList ( + [["mkdir_p"; "/a/b/c"]; + ["touch"; "/a/b/c/d"]; + ["find"; "/a/b/"]], ["c"; "c/d"])], + "find all files and directories", + "\ +This command lists out all files and directories, recursively, +starting at C. It is essentially equivalent to +running the shell command C but some +post-processing happens on the output, described below. + +This returns a list of strings I. Thus +if the directory structure was: + + /tmp/a + /tmp/b + /tmp/c/d + +then the returned list from C C would be +4 elements: + + a + b + c + c/d + +If C is not a directory, then this command returns +an error. + +The returned list is sorted."); + + ("e2fsck_f", (RErr, [String "device"]), 108, [], + [], (* lvresize tests this *) + "check an ext2/ext3 filesystem", + "\ +This runs C, ie. runs the ext2/ext3 +filesystem checker on C, noninteractively (C<-p>), +even if the filesystem appears to be clean (C<-f>). + +This command is only needed because of C +(q.v.). Normally you should use C."); + ] let all_functions = non_daemon_functions @ daemon_functions @@ -2014,8 +2435,10 @@ let check_functions () = fun (name, _, _, _, _, _, _) -> if String.length name >= 7 && String.sub name 0 7 = "guestfs" then failwithf "function name %s does not need 'guestfs' prefix" name; - if contains_uppercase name then - failwithf "function name %s should not contain uppercase chars" name; + if name = "" then + failwithf "function name is empty"; + if name.[0] < 'a' || name.[0] > 'z' then + failwithf "function name %s must start with lowercase a-z" name; if String.contains name '-' then failwithf "function name %s should not contain '-', use '_' instead." name @@ -2107,7 +2530,7 @@ let check_functions () = | name, _, _, _, tests, _, _ -> let funcs = List.map ( - fun (_, test) -> + fun (_, _, test) -> match seq_of_test test with | [] -> failwithf "%s has a test containing an empty sequence" name @@ -2126,14 +2549,15 @@ let chan = ref stdout let pr fs = ksprintf (output_string !chan) fs (* Generate a header block in a number of standard styles. *) -type comment_style = CStyle | HashStyle | OCamlStyle +type comment_style = CStyle | HashStyle | OCamlStyle | HaskellStyle type license = GPLv2 | LGPLv2 let generate_header comment license = let c = match comment with | CStyle -> pr "/* "; " *" | HashStyle -> pr "# "; "#" - | OCamlStyle -> pr "(* "; " *" in + | OCamlStyle -> pr "(* "; " *" + | HaskellStyle -> pr "{- "; " " in pr "libguestfs generated file\n"; pr "%s WARNING: THIS FILE IS GENERATED BY 'src/generator.ml'.\n" c; pr "%s ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST.\n" c; @@ -2175,6 +2599,7 @@ let generate_header comment license = | CStyle -> pr " */\n" | HashStyle -> () | OCamlStyle -> pr " *)\n" + | HaskellStyle -> pr "-}\n" ); pr "\n" @@ -2746,7 +3171,7 @@ check_state (guestfs_h *g, const char *caller) name; ); pr " if (serial == -1) {\n"; - pr " guestfs_set_ready (g);\n"; + pr " guestfs_end_busy (g);\n"; pr " return %s;\n" error_code; pr " }\n"; pr "\n"; @@ -2761,7 +3186,7 @@ check_state (guestfs_h *g, const char *caller) pr "\n"; pr " r = guestfs__send_file_sync (g, %s);\n" n; pr " if (r == -1) {\n"; - pr " guestfs_set_ready (g);\n"; + pr " guestfs_end_busy (g);\n"; pr " return %s;\n" error_code; pr " }\n"; pr " if (r == -2) /* daemon cancelled */\n"; @@ -2781,21 +3206,22 @@ check_state (guestfs_h *g, const char *caller) pr " guestfs_set_reply_callback (g, NULL, NULL);\n"; pr " if (ctx.cb_sequence != 1) {\n"; pr " error (g, \"%%s reply failed, see earlier error messages\", \"%s\");\n" name; - pr " guestfs_set_ready (g);\n"; + pr " guestfs_end_busy (g);\n"; pr " return %s;\n" error_code; pr " }\n"; pr "\n"; pr " if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_%s, serial) == -1) {\n" (String.uppercase shortname); - pr " guestfs_set_ready (g);\n"; + pr " guestfs_end_busy (g);\n"; pr " return %s;\n" error_code; pr " }\n"; pr "\n"; pr " if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {\n"; pr " error (g, \"%%s\", ctx.err.error_message);\n"; - pr " guestfs_set_ready (g);\n"; + pr " free (ctx.err.error_message);\n"; + pr " guestfs_end_busy (g);\n"; pr " return %s;\n" error_code; pr " }\n"; pr "\n"; @@ -2805,14 +3231,14 @@ check_state (guestfs_h *g, const char *caller) function | FileOut n -> pr " if (guestfs__receive_file_sync (g, %s) == -1) {\n" n; - pr " guestfs_set_ready (g);\n"; + pr " guestfs_end_busy (g);\n"; pr " return %s;\n" error_code; pr " }\n"; pr "\n"; | _ -> () ) (snd style); - pr " guestfs_set_ready (g);\n"; + pr " guestfs_end_busy (g);\n"; (match fst style with | RErr -> pr " return 0;\n" @@ -3214,6 +3640,11 @@ and generate_tests () = static guestfs_h *g; static int suppress_error = 0; +/* This will be 's' or 'h' depending on whether the guest kernel + * names IDE devices /dev/sd* or /dev/hd*. + */ +static char devchar = 's'; + static void print_error (guestfs_h *g, void *data, const char *msg) { if (!suppress_error) @@ -3272,8 +3703,9 @@ int main (int argc, char *argv[]) int failed = 0; const char *srcdir; const char *filename; - int fd; + int fd, i; int nr_tests, test_num = 0; + char **devs; no_test_warnings (); @@ -3285,10 +3717,7 @@ int main (int argc, char *argv[]) guestfs_set_error_handler (g, print_error, NULL); - srcdir = getenv (\"srcdir\"); - if (!srcdir) srcdir = \".\"; - chdir (srcdir); - guestfs_set_path (g, \".\"); + guestfs_set_path (g, \"../appliance\"); filename = \"test1.img\"; fd = open (filename, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK|O_TRUNC, 0666); @@ -3383,6 +3812,28 @@ int main (int argc, char *argv[]) exit (1); } + /* Detect if the appliance uses /dev/sd* or /dev/hd* in device + * names. This changed between RHEL 5 and RHEL 6 so we have to + * support both. + */ + devs = guestfs_list_devices (g); + if (devs == NULL || devs[0] == NULL) { + printf (\"guestfs_list_devices FAILED\\n\"); + exit (1); + } + if (strncmp (devs[0], \"/dev/sd\", 7) == 0) + devchar = 's'; + else if (strncmp (devs[0], \"/dev/hd\", 7) == 0) + devchar = 'h'; + else { + printf (\"guestfs_list_devices returned unexpected string '%%s'\\n\", + devs[0]); + exit (1); + } + for (i = 0; devs[i] != NULL; ++i) + free (devs[i]); + free (devs); + nr_tests = %d; " (500 * 1024 * 1024) (50 * 1024 * 1024) (10 * 1024 * 1024) nr_tests; @@ -3413,32 +3864,93 @@ int main (int argc, char *argv[]) pr " exit (0);\n"; pr "}\n" -and generate_one_test name i (init, test) = +and generate_one_test name i (init, prereq, test) = let test_name = sprintf "test_%s_%d" name i in - pr "static int %s (void)\n" test_name; - pr "{\n"; + pr "\ +static int %s_skip (void) +{ + const char *str; + + str = getenv (\"SKIP_%s\"); + if (str && strcmp (str, \"1\") == 0) return 1; + str = getenv (\"SKIP_TEST_%s\"); + if (str && strcmp (str, \"1\") == 0) return 1; + return 0; +} + +" test_name (String.uppercase test_name) (String.uppercase name); + + (match prereq with + | Disabled | Always -> () + | If code | Unless code -> + pr "static int %s_prereq (void)\n" test_name; + pr "{\n"; + pr " %s\n" code; + pr "}\n"; + pr "\n"; + ); + + pr "\ +static int %s (void) +{ + if (%s_skip ()) { + printf (\"%%s skipped (reason: SKIP_TEST_* variable set)\\n\", \"%s\"); + return 0; + } + +" test_name test_name test_name; + + (match prereq with + | Disabled -> + pr " printf (\"%%s skipped (reason: test disabled in generator)\\n\", \"%s\");\n" test_name + | If _ -> + pr " if (! %s_prereq ()) {\n" test_name; + pr " printf (\"%%s skipped (reason: test prerequisite)\\n\", \"%s\");\n" test_name; + pr " return 0;\n"; + pr " }\n"; + pr "\n"; + generate_one_test_body name i test_name init test; + | Unless _ -> + pr " if (%s_prereq ()) {\n" test_name; + pr " printf (\"%%s skipped (reason: test prerequisite)\\n\", \"%s\");\n" test_name; + pr " return 0;\n"; + pr " }\n"; + pr "\n"; + generate_one_test_body name i test_name init test; + | Always -> + generate_one_test_body name i test_name init test + ); + pr " return 0;\n"; + pr "}\n"; + pr "\n"; + test_name + +and generate_one_test_body name i test_name init test = (match init with - | InitNone -> () + | InitNone | InitEmpty -> - pr " /* InitEmpty for %s (%d) */\n" name i; + pr " /* InitNone|InitEmpty for %s */\n" test_name; List.iter (generate_test_command_call test_name) - [["umount_all"]; + [["blockdev_setrw"; "/dev/sda"]; + ["umount_all"]; ["lvm_remove_all"]] | InitBasicFS -> - pr " /* InitBasicFS for %s (%d): create ext2 on /dev/sda1 */\n" name i; + pr " /* InitBasicFS for %s: create ext2 on /dev/sda1 */\n" test_name; List.iter (generate_test_command_call test_name) - [["umount_all"]; + [["blockdev_setrw"; "/dev/sda"]; + ["umount_all"]; ["lvm_remove_all"]; ["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; ["mkfs"; "ext2"; "/dev/sda1"]; ["mount"; "/dev/sda1"; "/"]] | InitBasicFSonLVM -> - pr " /* InitBasicFSonLVM for %s (%d): create ext2 on /dev/VG/LV */\n" - name i; + pr " /* InitBasicFSonLVM for %s: create ext2 on /dev/VG/LV */\n" + test_name; List.iter (generate_test_command_call test_name) - [["umount_all"]; + [["blockdev_setrw"; "/dev/sda"]; + ["umount_all"]; ["lvm_remove_all"]; ["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; ["pvcreate"; "/dev/sda1"]; @@ -3457,153 +3969,156 @@ and generate_one_test name i (init, test) = List.rev (List.tl seq), List.hd seq in - (match test with - | TestRun seq -> - pr " /* TestRun for %s (%d) */\n" name i; - List.iter (generate_test_command_call test_name) seq - | TestOutput (seq, expected) -> - pr " /* TestOutput for %s (%d) */\n" name i; - let seq, last = get_seq_last seq in - let test () = - pr " if (strcmp (r, \"%s\") != 0) {\n" (c_quote expected); - pr " fprintf (stderr, \"%s: expected \\\"%s\\\" but got \\\"%%s\\\"\\n\", r);\n" test_name (c_quote expected); - pr " return -1;\n"; - pr " }\n" - in - List.iter (generate_test_command_call test_name) seq; - generate_test_command_call ~test test_name last - | TestOutputList (seq, expected) -> - pr " /* TestOutputList for %s (%d) */\n" name i; - let seq, last = get_seq_last seq in - let test () = - iteri ( - fun i str -> - pr " if (!r[%d]) {\n" i; - pr " fprintf (stderr, \"%s: short list returned from command\\n\");\n" test_name; - pr " print_strings (r);\n"; - pr " return -1;\n"; - pr " }\n"; - pr " if (strcmp (r[%d], \"%s\") != 0) {\n" i (c_quote str); - pr " fprintf (stderr, \"%s: expected \\\"%s\\\" but got \\\"%%s\\\"\\n\", r[%d]);\n" test_name (c_quote str) i; - pr " return -1;\n"; - pr " }\n" - ) expected; - pr " if (r[%d] != NULL) {\n" (List.length expected); - pr " fprintf (stderr, \"%s: extra elements returned from command\\n\");\n" - test_name; - pr " print_strings (r);\n"; - pr " return -1;\n"; - pr " }\n" - in - List.iter (generate_test_command_call test_name) seq; - generate_test_command_call ~test test_name last - | TestOutputInt (seq, expected) -> - pr " /* TestOutputInt for %s (%d) */\n" name i; - let seq, last = get_seq_last seq in - let test () = - pr " if (r != %d) {\n" expected; - pr " fprintf (stderr, \"%s: expected %d but got %%d\\n\"," - test_name expected; - pr " (int) r);\n"; - pr " return -1;\n"; - pr " }\n" - in - List.iter (generate_test_command_call test_name) seq; - generate_test_command_call ~test test_name last - | TestOutputTrue seq -> - pr " /* TestOutputTrue for %s (%d) */\n" name i; - let seq, last = get_seq_last seq in - let test () = - pr " if (!r) {\n"; - pr " fprintf (stderr, \"%s: expected true, got false\\n\");\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 - | TestOutputFalse seq -> - pr " /* TestOutputFalse for %s (%d) */\n" name i; - let seq, last = get_seq_last seq in - let test () = - pr " if (r) {\n"; - pr " fprintf (stderr, \"%s: expected false, got true\\n\");\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 - | TestOutputLength (seq, expected) -> - pr " /* TestOutputLength for %s (%d) */\n" name i; - let seq, last = get_seq_last seq in - let test () = - pr " int j;\n"; - pr " for (j = 0; j < %d; ++j)\n" expected; - pr " if (r[j] == NULL) {\n"; - pr " fprintf (stderr, \"%s: short list returned\\n\");\n" - test_name; - pr " print_strings (r);\n"; - pr " return -1;\n"; - pr " }\n"; - pr " if (r[j] != NULL) {\n"; - pr " fprintf (stderr, \"%s: long list returned\\n\");\n" - test_name; - pr " print_strings (r);\n"; - pr " return -1;\n"; - pr " }\n" - in - List.iter (generate_test_command_call test_name) seq; - generate_test_command_call ~test test_name last - | TestOutputStruct (seq, checks) -> - pr " /* TestOutputStruct for %s (%d) */\n" name i; - let seq, last = get_seq_last seq in - let test () = - List.iter ( - function - | CompareWithInt (field, expected) -> - pr " if (r->%s != %d) {\n" field expected; - pr " fprintf (stderr, \"%s: %s was %%d, expected %d\\n\",\n" - test_name field expected; - pr " (int) r->%s);\n" field; - pr " return -1;\n"; - pr " }\n" - | CompareWithString (field, expected) -> - pr " if (strcmp (r->%s, \"%s\") != 0) {\n" field expected; - pr " fprintf (stderr, \"%s: %s was \"%%s\", expected \"%s\"\\n\",\n" - test_name field expected; - pr " r->%s);\n" field; - pr " return -1;\n"; - pr " }\n" - | CompareFieldsIntEq (field1, field2) -> - pr " if (r->%s != r->%s) {\n" field1 field2; - pr " fprintf (stderr, \"%s: %s (%%d) <> %s (%%d)\\n\",\n" - test_name field1 field2; - pr " (int) r->%s, (int) r->%s);\n" field1 field2; - pr " return -1;\n"; - pr " }\n" - | CompareFieldsStrEq (field1, field2) -> - pr " if (strcmp (r->%s, r->%s) != 0) {\n" field1 field2; - pr " fprintf (stderr, \"%s: %s (\"%%s\") <> %s (\"%%s\")\\n\",\n" - test_name field1 field2; - pr " r->%s, r->%s);\n" field1 field2; - pr " return -1;\n"; - pr " }\n" - ) checks - in - List.iter (generate_test_command_call test_name) seq; - generate_test_command_call ~test test_name last - | TestLastFail seq -> - pr " /* TestLastFail for %s (%d) */\n" name i; - let seq, last = get_seq_last seq in - List.iter (generate_test_command_call test_name) seq; - generate_test_command_call test_name ~expect_error:true last - ); - - pr " return 0;\n"; - pr "}\n"; - pr "\n"; - test_name + match test with + | TestRun seq -> + pr " /* TestRun for %s (%d) */\n" name i; + List.iter (generate_test_command_call test_name) seq + | TestOutput (seq, expected) -> + pr " /* TestOutput for %s (%d) */\n" name i; + pr " char expected[] = \"%s\";\n" (c_quote expected); + if String.length expected > 7 && + String.sub expected 0 7 = "/dev/sd" then + pr " expected[5] = devchar;\n"; + let seq, last = get_seq_last seq in + let test () = + pr " if (strcmp (r, expected) != 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 + | TestOutputList (seq, expected) -> + pr " /* TestOutputList for %s (%d) */\n" name i; + let seq, last = get_seq_last seq in + let test () = + iteri ( + fun i str -> + pr " if (!r[%d]) {\n" i; + pr " fprintf (stderr, \"%s: short list returned from command\\n\");\n" test_name; + pr " print_strings (r);\n"; + pr " return -1;\n"; + pr " }\n"; + pr " {\n"; + pr " char expected[] = \"%s\";\n" (c_quote str); + if String.length str > 7 && String.sub str 0 7 = "/dev/sd" then + pr " expected[5] = devchar;\n"; + pr " if (strcmp (r[%d], expected) != 0) {\n" i; + pr " fprintf (stderr, \"%s: expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", expected, r[%d]);\n" test_name i; + pr " return -1;\n"; + pr " }\n"; + pr " }\n" + ) expected; + pr " if (r[%d] != NULL) {\n" (List.length expected); + pr " fprintf (stderr, \"%s: extra elements returned from command\\n\");\n" + test_name; + pr " print_strings (r);\n"; + pr " return -1;\n"; + pr " }\n" + in + List.iter (generate_test_command_call test_name) seq; + generate_test_command_call ~test test_name last + | TestOutputInt (seq, expected) -> + pr " /* TestOutputInt for %s (%d) */\n" name i; + let seq, last = get_seq_last seq in + let test () = + pr " if (r != %d) {\n" expected; + pr " fprintf (stderr, \"%s: expected %d but got %%d\\n\"," + test_name expected; + pr " (int) r);\n"; + pr " return -1;\n"; + pr " }\n" + in + List.iter (generate_test_command_call test_name) seq; + generate_test_command_call ~test test_name last + | TestOutputTrue seq -> + pr " /* TestOutputTrue for %s (%d) */\n" name i; + let seq, last = get_seq_last seq in + let test () = + pr " if (!r) {\n"; + pr " fprintf (stderr, \"%s: expected true, got false\\n\");\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 + | TestOutputFalse seq -> + pr " /* TestOutputFalse for %s (%d) */\n" name i; + let seq, last = get_seq_last seq in + let test () = + pr " if (r) {\n"; + pr " fprintf (stderr, \"%s: expected false, got true\\n\");\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 + | TestOutputLength (seq, expected) -> + pr " /* TestOutputLength for %s (%d) */\n" name i; + let seq, last = get_seq_last seq in + let test () = + pr " int j;\n"; + pr " for (j = 0; j < %d; ++j)\n" expected; + pr " if (r[j] == NULL) {\n"; + pr " fprintf (stderr, \"%s: short list returned\\n\");\n" + test_name; + pr " print_strings (r);\n"; + pr " return -1;\n"; + pr " }\n"; + pr " if (r[j] != NULL) {\n"; + pr " fprintf (stderr, \"%s: long list returned\\n\");\n" + test_name; + pr " print_strings (r);\n"; + pr " return -1;\n"; + pr " }\n" + in + List.iter (generate_test_command_call test_name) seq; + generate_test_command_call ~test test_name last + | TestOutputStruct (seq, checks) -> + pr " /* TestOutputStruct for %s (%d) */\n" name i; + let seq, last = get_seq_last seq in + let test () = + List.iter ( + function + | CompareWithInt (field, expected) -> + pr " if (r->%s != %d) {\n" field expected; + pr " fprintf (stderr, \"%s: %s was %%d, expected %d\\n\",\n" + test_name field expected; + pr " (int) r->%s);\n" field; + pr " return -1;\n"; + pr " }\n" + | CompareWithString (field, expected) -> + pr " if (strcmp (r->%s, \"%s\") != 0) {\n" field expected; + pr " fprintf (stderr, \"%s: %s was \"%%s\", expected \"%s\"\\n\",\n" + test_name field expected; + pr " r->%s);\n" field; + pr " return -1;\n"; + pr " }\n" + | CompareFieldsIntEq (field1, field2) -> + pr " if (r->%s != r->%s) {\n" field1 field2; + pr " fprintf (stderr, \"%s: %s (%%d) <> %s (%%d)\\n\",\n" + test_name field1 field2; + pr " (int) r->%s, (int) r->%s);\n" field1 field2; + pr " return -1;\n"; + pr " }\n" + | CompareFieldsStrEq (field1, field2) -> + pr " if (strcmp (r->%s, r->%s) != 0) {\n" field1 field2; + pr " fprintf (stderr, \"%s: %s (\"%%s\") <> %s (\"%%s\")\\n\",\n" + test_name field1 field2; + pr " r->%s, r->%s);\n" field1 field2; + pr " return -1;\n"; + pr " }\n" + ) checks + in + List.iter (generate_test_command_call test_name) seq; + generate_test_command_call ~test test_name last + | TestLastFail seq -> + pr " /* TestLastFail for %s (%d) */\n" name i; + let seq, last = get_seq_last seq in + List.iter (generate_test_command_call test_name) seq; + generate_test_command_call test_name ~expect_error:true last (* Generate the code to run a command, leaving the result in 'r'. * If you expect to get an error then you should set expect_error:true. @@ -3629,16 +4144,26 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd = List.iter ( function - | String _, _ - | OptString _, _ + | OptString n, "NULL" -> () + | String n, arg + | OptString n, arg -> + pr " char %s[] = \"%s\";\n" n (c_quote arg); + if String.length arg > 7 && String.sub arg 0 7 = "/dev/sd" then + pr " %s[5] = devchar;\n" n | Int _, _ - | Bool _, _ -> () + | Bool _, _ | FileIn _, _ | FileOut _, _ -> () | StringList n, arg -> - pr " char *%s[] = {\n" n; let strs = string_split " " arg in - List.iter ( - fun str -> pr " \"%s\",\n" (c_quote str) + iteri ( + fun i str -> + pr " char %s_%d[] = \"%s\";\n" n i (c_quote str); + if String.length str > 7 && String.sub str 0 7 = "/dev/sd" then + pr " %s_%d[5] = devchar;\n" n i + ) strs; + pr " char *%s[] = {\n" n; + iteri ( + fun i _ -> pr " %s_%d,\n" n i ) strs; pr " NULL\n"; pr " };\n"; @@ -3673,11 +4198,12 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd = (* Generate the parameters. *) List.iter ( function - | String _, arg + | OptString _, "NULL" -> pr ", NULL" + | String n, _ + | OptString n, _ -> + pr ", %s" n | FileIn _, arg | FileOut _, arg -> pr ", \"%s\"" (c_quote arg) - | OptString _, arg -> - if arg = "NULL" then pr ", NULL" else pr ", \"%s\"" (c_quote arg) | StringList n, _ -> pr ", %s" n | Int _, arg -> @@ -3728,6 +4254,7 @@ and c_quote str = let str = replace_str str "\r" "\\r" in let str = replace_str str "\n" "\\n" in let str = replace_str str "\t" "\\t" in + let str = replace_str str "\000" "\\0" in str (* Generate a lot of different functions for guestfish. *) @@ -4064,9 +4591,12 @@ and generate_fish_completion () = #ifdef HAVE_LIBREADLINE static const char *const commands[] = { + BUILTIN_COMMANDS_FOR_COMPLETION, "; - (* Get the commands and sort them, including the aliases. *) + (* Get the commands, including the aliases. They don't need to be + * sorted - the generator() function just does a dumb linear search. + *) let commands = List.map ( fun (name, _, _, flags, _, _, _) -> @@ -4078,7 +4608,6 @@ static const char *const commands[] = { if name <> alias then [name2; alias] else [name2] ) all_functions in let commands = List.flatten commands in - let commands = List.sort compare commands in List.iter (pr " \"%s\",\n") commands; @@ -4719,7 +5248,7 @@ XS_unpack_charPtrPtr (SV *arg) { croak (\"array reference expected\"); av = (AV *)SvRV (arg); - ret = malloc (av_len (av) + 1 + 1); + ret = malloc ((av_len (av) + 1 + 1) * sizeof (char *)); if (!ret) croak (\"malloc failed\"); @@ -4739,6 +5268,8 @@ XS_unpack_charPtrPtr (SV *arg) { MODULE = Sys::Guestfs PACKAGE = Sys::Guestfs +PROTOTYPES: ENABLE + guestfs_h * _create () CODE: @@ -5648,6 +6179,11 @@ and generate_ruby_c () = #include \"extconf.h\" +/* For Ruby < 1.9 */ +#ifndef RARRAY_LEN +#define RARRAY_LEN(r) (RARRAY((r))->len) +#endif + static VALUE m_guestfs; /* guestfs module */ static VALUE c_guestfs; /* guestfs_h handle */ static VALUE e_Error; /* used for all errors */ @@ -5953,11 +6489,16 @@ public class GuestFS { doc ^ "\n\n" ^ danger_will_robinson else doc in let doc = pod2text ~width:60 name doc in + let doc = List.map ( (* RHBZ#501883 *) + function + | "" -> "

" + | nonempty -> nonempty + ) doc in let doc = String.concat "\n * " doc in pr " /**\n"; pr " * %s\n" shortdesc; - pr " *\n"; + pr " *

\n"; pr " * %s\n" doc; pr " * @throws LibGuestFSException\n"; pr " */\n"; @@ -6217,7 +6758,7 @@ Java_com_redhat_et_libguestfs_GuestFS__1close let needs_i = (match fst style with | RStringList _ | RPVList _ | RVGList _ | RLVList _ -> true - | RErr _ | RBool _ | RInt _ | RInt64 _ | RConstString _ + | RErr | RBool _ | RInt _ | RInt64 _ | RConstString _ | RString _ | RIntBool _ | RStat _ | RStatVFS _ | RHashtable _ -> false) || List.exists (function StringList _ -> true | _ -> false) (snd style) in @@ -6380,6 +6921,207 @@ and generate_java_lvm_return typ jtyp cols = pr " guestfs_free_lvm_%s_list (r);\n" typ; pr " return jr;\n" +and generate_haskell_hs () = + generate_header HaskellStyle LGPLv2; + + (* XXX We only know how to generate partial FFI for Haskell + * at the moment. Please help out! + *) + let can_generate style = + let check_no_bad_args = + List.for_all (function Bool _ | Int _ -> false | _ -> true) + in + match style with + | RErr, args -> check_no_bad_args args + | RBool _, _ + | RInt _, _ + | RInt64 _, _ + | RConstString _, _ + | RString _, _ + | RStringList _, _ + | RIntBool _, _ + | RPVList _, _ + | RVGList _, _ + | RLVList _, _ + | RStat _, _ + | RStatVFS _, _ + | RHashtable _, _ -> false in + + pr "\ +{-# INCLUDE #-} +{-# LANGUAGE ForeignFunctionInterface #-} + +module Guestfs ( + create"; + + (* List out the names of the actions we want to export. *) + List.iter ( + fun (name, style, _, _, _, _, _) -> + if can_generate style then pr ",\n %s" name + ) all_functions; + + pr " + ) where +import Foreign +import Foreign.C +import IO +import Control.Exception +import Data.Typeable + +data GuestfsS = GuestfsS -- represents the opaque C struct +type GuestfsP = Ptr GuestfsS -- guestfs_h * +type GuestfsH = ForeignPtr GuestfsS -- guestfs_h * with attached finalizer + +-- XXX define properly later XXX +data PV = PV +data VG = VG +data LV = LV +data IntBool = IntBool +data Stat = Stat +data StatVFS = StatVFS +data Hashtable = Hashtable + +foreign import ccall unsafe \"guestfs_create\" c_create + :: IO GuestfsP +foreign import ccall unsafe \"&guestfs_close\" c_close + :: FunPtr (GuestfsP -> IO ()) +foreign import ccall unsafe \"guestfs_set_error_handler\" c_set_error_handler + :: GuestfsP -> Ptr CInt -> Ptr CInt -> IO () + +create :: IO GuestfsH +create = do + p <- c_create + c_set_error_handler p nullPtr nullPtr + h <- newForeignPtr c_close p + return h + +foreign import ccall unsafe \"guestfs_last_error\" c_last_error + :: GuestfsP -> IO CString + +-- last_error :: GuestfsH -> IO (Maybe String) +-- last_error h = do +-- str <- withForeignPtr h (\\p -> c_last_error p) +-- maybePeek peekCString str + +last_error :: GuestfsH -> IO (String) +last_error h = do + str <- withForeignPtr h (\\p -> c_last_error p) + if (str == nullPtr) + then return \"no error\" + else peekCString str + +"; + + (* Generate wrappers for each foreign function. *) + List.iter ( + fun (name, style, _, _, _, _, _) -> + if can_generate style then ( + pr "foreign import ccall unsafe \"guestfs_%s\" c_%s\n" name name; + pr " :: "; + generate_haskell_prototype ~handle:"GuestfsP" style; + pr "\n"; + pr "\n"; + pr "%s :: " name; + generate_haskell_prototype ~handle:"GuestfsH" ~hs:true style; + pr "\n"; + pr "%s %s = do\n" name + (String.concat " " ("h" :: List.map name_of_argt (snd style))); + pr " r <- "; + List.iter ( + function + | FileIn n + | FileOut n + | String n -> pr "withCString %s $ \\%s -> " n n + | OptString n -> pr "maybeWith withCString %s $ \\%s -> " n n + | StringList n -> pr "withMany withCString %s $ \\%s -> withArray0 nullPtr %s $ \\%s -> " n n n n + | Bool n -> + (* XXX this doesn't work *) + pr " let\n"; + pr " %s = case %s of\n" n n; + pr " False -> 0\n"; + pr " True -> 1\n"; + pr " in fromIntegral %s $ \\%s ->\n" n n + | Int n -> pr "fromIntegral %s $ \\%s -> " n n + ) (snd style); + pr "withForeignPtr h (\\p -> c_%s %s)\n" name + (String.concat " " ("p" :: List.map name_of_argt (snd style))); + (match fst style with + | RErr | RInt _ | RInt64 _ | RBool _ -> + pr " if (r == -1)\n"; + pr " then do\n"; + pr " err <- last_error h\n"; + pr " fail err\n"; + | RConstString _ | RString _ | RStringList _ | RIntBool _ + | RPVList _ | RVGList _ | RLVList _ | RStat _ | RStatVFS _ + | RHashtable _ -> + pr " if (r == nullPtr)\n"; + pr " then do\n"; + pr " err <- last_error h\n"; + pr " fail err\n"; + ); + (match fst style with + | RErr -> + pr " else return ()\n" + | RInt _ -> + pr " else return (fromIntegral r)\n" + | RInt64 _ -> + pr " else return (fromIntegral r)\n" + | RBool _ -> + pr " else return (toBool r)\n" + | RConstString _ + | RString _ + | RStringList _ + | RIntBool _ + | RPVList _ + | RVGList _ + | RLVList _ + | RStat _ + | RStatVFS _ + | RHashtable _ -> + pr " else return ()\n" (* XXXXXXXXXXXXXXXXXXXX *) + ); + pr "\n"; + ) + ) all_functions + +and generate_haskell_prototype ~handle ?(hs = false) style = + pr "%s -> " handle; + let string = if hs then "String" else "CString" in + let int = if hs then "Int" else "CInt" in + let bool = if hs then "Bool" else "CInt" in + let int64 = if hs then "Integer" else "Int64" in + List.iter ( + fun arg -> + (match arg with + | String _ -> pr "%s" string + | OptString _ -> if hs then pr "Maybe String" else pr "CString" + | StringList _ -> if hs then pr "[String]" else pr "Ptr CString" + | Bool _ -> pr "%s" bool + | Int _ -> pr "%s" int + | FileIn _ -> pr "%s" string + | FileOut _ -> pr "%s" string + ); + pr " -> "; + ) (snd style); + pr "IO ("; + (match fst style with + | RErr -> if not hs then pr "CInt" + | RInt _ -> pr "%s" int + | RInt64 _ -> pr "%s" int64 + | RBool _ -> pr "%s" bool + | RConstString _ -> pr "%s" string + | RString _ -> pr "%s" string + | RStringList _ -> pr "[%s]" string + | RIntBool _ -> pr "IntBool" + | RPVList _ -> pr "[PV]" + | RVGList _ -> pr "[VG]" + | RLVList _ -> pr "[LV]" + | RStat _ -> pr "Stat" + | RStatVFS _ -> pr "StatVFS" + | RHashtable _ -> pr "Hashtable" + ); + pr ")" + let output_to filename = let filename_new = filename ^ ".new" in chan := open_out filename_new; @@ -6437,7 +7179,7 @@ Run it from the top source directory using the command generate_daemon_actions (); close (); - let close = output_to "tests.c" in + let close = output_to "capitests/tests.c" in generate_tests (); close (); @@ -6520,3 +7262,7 @@ Run it from the top source directory using the command let close = output_to "java/com_redhat_et_libguestfs_GuestFS.c" in generate_java_c (); close (); + + let close = output_to "haskell/Guestfs.hs" in + generate_haskell_hs (); + close ();