*)
#load "unix.cma";;
+#load "str.cma";;
open Printf
*)
| RErr
(* "RInt" as a return value means an int which is -1 for error
- * or any value >= 0 on success.
+ * or any value >= 0 on success. Only use this for smallish
+ * positive ints (0 <= i < 2^30).
*)
| RInt of string
+ (* "RInt64" is the same as RInt, but is guaranteed to be able
+ * to return a full 64 bit value, _except_ that -1 means error
+ * (so -1 cannot be a valid, non-error return value).
+ *)
+ | RInt64 of string
(* "RBool" is a bool return value which can be true/false or
* -1 for error.
*)
| RPVList of string
| RVGList of string
| RLVList of string
+ (* Stat buffers. *)
+ | RStat of string
+ | RStatVFS of string
+ (* Key-value pairs of untyped strings. Turns into a hashtable or
+ * dictionary in languages which support it. DON'T use this as a
+ * general "bucket" for results. Prefer a stronger typed return
+ * value if one is available, or write a custom struct. Don't use
+ * this if the list could potentially be very long, since it is
+ * inefficient. Keys should be unique. NULLs are not permitted.
+ *)
+ | RHashtable of string
+
and args = argt list (* Function parameters, guestfs handle is implicit. *)
(* Note in future we should allow a "variable args" parameter as
| StringList of string(* list of strings (each string cannot be NULL) *)
| Bool of string (* boolean *)
| Int of string (* int (smallish ints, signed, <= 31 bits) *)
+ (* These are treated as filenames (simple string parameters) in
+ * the C API and bindings. But in the RPC protocol, we transfer
+ * the actual file content up to or down from the daemon.
+ * FileIn: local machine -> daemon (in request)
+ * FileOut: daemon -> local machine (in reply)
+ * In guestfish (only), the special name "-" means read from
+ * stdin or write to stdout.
+ *)
+ | FileIn of string
+ | FileOut of string
type flags =
| ProtocolLimitWarning (* display warning about protocol size limits *)
| FishAlias of string (* provide an alias for this cmd in guestfish *)
| FishAction of string (* call this function in guestfish *)
| NotInFish (* do not export via guestfish *)
+ | NotInDocs (* do not add this function to documentation *)
let protocol_limit_warning =
"Because of the message protocol, there is a transfer limit
(* You can supply zero or as many tests as you want per API call.
*
* Note that the test environment has 3 block devices, of size 500MB,
- * 50MB and 10MB (respectively /dev/sda, /dev/sdb, /dev/sdc).
+ * 50MB and 10MB (respectively /dev/sda, /dev/sdb, /dev/sdc), and
+ * a fourth squashfs block device with some known files on it (/dev/sdd).
+ *
* Note for partitioning purposes, the 500MB device has 63 cylinders.
*
+ * The squashfs block device (/dev/sdd) comes from images/test.sqsh.
+ *
* To be able to run the tests in a reasonable amount of time,
* 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.
+ * 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_<CMD>_<NUM>=1 SKIP_TEST_COMMAND_3=1 (skips test #3 of command)
+ * SKIP_TEST_<CMD>=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
* content).
*)
| TestOutputLength of seq * int
+ (* Run the command sequence and expect the output of the final
+ * command to be a structure.
+ *)
+ | TestOutputStruct of seq * test_field_compare list
(* Run the command sequence and expect the final command (only)
* to fail.
*)
| TestLastFail of seq
+and test_field_compare =
+ | CompareWithInt of string * int
+ | CompareWithString of string * string
+ | 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
* Apart from that, long descriptions are just perldoc paragraphs.
*)
-let non_daemon_functions = [
+(* These test functions are used in the language binding tests. *)
+
+let test_all_args = [
+ String "str";
+ OptString "optstr";
+ StringList "strlist";
+ Bool "b";
+ Int "integer";
+ FileIn "filein";
+ FileOut "fileout";
+]
+
+let test_all_rets = [
+ (* except for RErr, which is tested thoroughly elsewhere *)
+ "test0rint", RInt "valout";
+ "test0rint64", RInt64 "valout";
+ "test0rbool", RBool "valout";
+ "test0rconststring", RConstString "valout";
+ "test0rstring", RString "valout";
+ "test0rstringlist", RStringList "valout";
+ "test0rintbool", RIntBool ("valout", "valout");
+ "test0rpvlist", RPVList "valout";
+ "test0rvglist", RVGList "valout";
+ "test0rlvlist", RLVList "valout";
+ "test0rstat", RStat "valout";
+ "test0rstatvfs", RStatVFS "valout";
+ "test0rhashtable", RHashtable "valout";
+]
+
+let test_functions = [
+ ("test0", (RErr, test_all_args), -1, [NotInFish; NotInDocs],
+ [],
+ "internal test function - do not use",
+ "\
+This is an internal test function which is used to test whether
+the automatically generated bindings can handle every possible
+parameter type correctly.
+
+It echos the contents of each parameter to stdout.
+
+You probably don't want to call this function.");
+] @ List.flatten (
+ List.map (
+ fun (name, ret) ->
+ [(name, (ret, [String "val"]), -1, [NotInFish; NotInDocs],
+ [],
+ "internal test function - do not use",
+ "\
+This is an internal test function which is used to test whether
+the automatically generated bindings can handle every possible
+return type correctly.
+
+It converts string C<val> to the return type.
+
+You probably don't want to call this function.");
+ (name ^ "err", (ret, []), -1, [NotInFish; NotInDocs],
+ [],
+ "internal test function - do not use",
+ "\
+This is an internal test function which is used to test whether
+the automatically generated bindings can handle every possible
+return type correctly.
+
+This function always returns an error.
+
+You probably don't want to call this function.")]
+ ) test_all_rets
+)
+
+(* non_daemon_functions are any functions which don't get processed
+ * in the daemon, eg. functions for setting and getting local
+ * configuration values.
+ *)
+
+let non_daemon_functions = test_functions @ [
("launch", (RErr, []), -1, [FishAlias "run"; FishAction "launch"],
[],
"launch the qemu subprocess",
just want to read the image or write access if you want to modify the
image).
-This is equivalent to the qemu parameter C<-drive file=filename>.");
+This is equivalent to the qemu parameter C<-drive file=filename>.
+
+Note that this call checks for the existence of C<filename>. This
+stops you from specifying other types of drive which are supported
+by qemu such as C<nbd:> and C<http:> URLs. To specify those, use
+the general C<guestfs_config> call instead.");
("add_cdrom", (RErr, [String "filename"]), -1, [FishAlias "cdrom"],
[],
"\
This function adds a virtual CD-ROM disk image to the guest.
-This is equivalent to the qemu parameter C<-cdrom filename>.");
+This is equivalent to the qemu parameter C<-cdrom filename>.
+
+Note that this call checks for the existence of C<filename>. This
+stops you from specifying other types of drive which are supported
+by qemu such as C<nbd:> and C<http:> URLs. To specify those, use
+the general C<guestfs_config> call instead.");
+
+ ("add_drive_ro", (RErr, [String "filename"]), -1, [FishAlias "add-ro"],
+ [],
+ "add a drive in snapshot mode (read-only)",
+ "\
+This adds a drive in snapshot mode, making it effectively
+read-only.
+
+Note that writes to the device are allowed, and will be seen for
+the duration of the guestfs handle, but they are written
+to a temporary file which is discarded as soon as the guestfs
+handle is closed. We don't currently have any method to enable
+changes to be committed, although qemu can support this.
+
+This is equivalent to the qemu parameter
+C<-drive file=filename,snapshot=on>.
+
+Note that this call checks for the existence of C<filename>. This
+stops you from specifying other types of drive which are supported
+by qemu such as C<nbd:> and C<http:> URLs. To specify those, use
+the general C<guestfs_config> call instead.");
("config", (RErr, [String "qemuparam"; OptString "qemuvalue"]), -1, [],
[],
C<value> can be NULL.");
+ ("set_qemu", (RErr, [String "qemu"]), -1, [FishAlias "qemu"],
+ [],
+ "set the qemu binary",
+ "\
+Set the qemu binary that we will use.
+
+The default is chosen when the library was compiled by the
+configure script.
+
+You can also override this by setting the C<LIBGUESTFS_QEMU>
+environment variable.
+
+Setting C<qemu> to C<NULL> restores the default qemu binary.");
+
+ ("get_qemu", (RConstString "qemu", []), -1, [],
+ [],
+ "get the qemu binary",
+ "\
+Return the current qemu binary.
+
+This is always non-NULL. If it wasn't set already, then this will
+return the default qemu binary name.");
+
("set_path", (RErr, [String "path"]), -1, [FishAlias "path"],
[],
"set the search path",
The default is C<$libdir/guestfs> unless overridden by setting
C<LIBGUESTFS_PATH> environment variable.
-The string C<path> is stashed in the libguestfs handle, so the caller
-must make sure it remains valid for the lifetime of the handle.
-
Setting C<path> to C<NULL> restores the default path.");
("get_path", (RConstString "path", []), -1, [],
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<NULL> unless overridden by setting
+C<LIBGUESTFS_APPEND> environment variable.
+
+Setting C<append> to C<NULL> means I<no> additional options
+are passed (libguestfs always adds a few of its own).");
+
+ ("get_append", (RConstString "append", []), -1, [],
+ [],
+ "get the additional kernel options",
+ "\
+Return the additional kernel options which are added to the
+guest kernel command line.
+
+If C<NULL> then no options are added.");
+
("set_autosync", (RErr, [Bool "autosync"]), -1, [FishAlias "autosync"],
[],
"set autosync mode",
"\
If C<autosync> is true, this enables autosync. Libguestfs will make a
-best effort attempt to run C<guestfs_sync> when the handle is closed
-(also if the program exits without closing handles).");
+best effort attempt to run C<guestfs_umount_all> followed by
+C<guestfs_sync> when the handle is closed
+(also if the program exits without closing handles).
+
+This is disabled by default (except in guestfish where it is
+enabled by default).");
("get_autosync", (RBool "autosync", []), -1, [],
[],
[],
"get verbose mode",
"\
-This returns the verbose messages flag.")
+This returns the verbose messages flag.");
+
+ ("is_ready", (RBool "ready", []), -1, [],
+ [],
+ "is ready to accept commands",
+ "\
+This returns true iff this handle is ready to accept commands
+(in the C<READY> state).
+
+For more information on states, see L<guestfs(3)>.");
+
+ ("is_config", (RBool "config", []), -1, [],
+ [],
+ "is in configuration state",
+ "\
+This returns true iff this handle is being configured
+(in the C<CONFIG> state).
+
+For more information on states, see L<guestfs(3)>.");
+
+ ("is_launching", (RBool "launching", []), -1, [],
+ [],
+ "is launching subprocess",
+ "\
+This returns true iff this handle is launching the subprocess
+(in the C<LAUNCHING> state).
+
+For more information on states, see L<guestfs(3)>.");
+
+ ("is_busy", (RBool "busy", []), -1, [],
+ [],
+ "is busy processing a command",
+ "\
+This returns true iff this handle is busy processing a command
+(in the C<BUSY> state).
+
+For more information on states, see L<guestfs(3)>.");
+
+ ("get_state", (RInt "state", []), -1, [],
+ [],
+ "get the current state",
+ "\
+This returns the current state as an opaque integer. This is
+only useful for printing debug and internal error messages.
+
+For more information on states, see L<guestfs(3)>.");
+
+ ("set_busy", (RErr, []), -1, [NotInFish],
+ [],
+ "set state to busy",
+ "\
+This sets the state to C<BUSY>. This is only used when implementing
+actions using the low-level API.
+
+For more information on states, see L<guestfs(3)>.");
+
+ ("set_ready", (RErr, []), -1, [NotInFish],
+ [],
+ "set state to ready",
+ "\
+This sets the state to C<READY>. This is only used when implementing
+actions using the low-level API.
+
+For more information on states, see L<guestfs(3)>.");
+
+ ("end_busy", (RErr, []), -1, [NotInFish],
+ [],
+ "leave the busy state",
+ "\
+This sets the state to C<READY>, or if in C<CONFIG> 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<guestfs(3)>.");
+
]
+(* daemon_functions are any functions which cause some action
+ * to take place in the daemon.
+ *)
+
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"; "/"];
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
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",
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",
Note that this function cannot correctly handle binary files
(specifically, files containing C<\\0> character which is treated
-as end of string). For those you need to use the C<guestfs_read_file>
+as end of string). For those you need to use the C<guestfs_download>
function which has a more complex interface.");
("ll", (RString "listing", [String "directory"]), 5, [],
is I<not> 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"];
should probably use C<guestfs_readdir> instead.");
("list_devices", (RStringList "devices", []), 7, [],
- [InitEmpty, TestOutputList (
- [["list_devices"]], ["/dev/sda"; "/dev/sdb"; "/dev/sdc"])],
+ [InitEmpty, Always, TestOutputList (
+ [["list_devices"]], ["/dev/sda"; "/dev/sdb"; "/dev/sdc"; "/dev/sdd"])],
"list the block devices",
"\
List all the block devices.
The full block device names are returned, eg. C</dev/sda>");
("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",
call C<guestfs_lvs>.");
("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"];
See also C<guestfs_pvs_full>.");
("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"];
See also C<guestfs_vgs_full>.");
("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"];
See also C<guestfs_lvs_full>.");
("pvs_full", (RPVList "physvols", []), 12, [],
- [InitBasicFSonLVM, TestOutputLength (
- [["pvs"]], 1)],
+ [], (* XXX how to test? *)
"list the LVM physical volumes (PVs)",
"\
List all the physical volumes detected. This is the equivalent
of the L<pvs(8)> command. The \"full\" version includes all fields.");
("vgs_full", (RVGList "volgroups", []), 13, [],
- [InitBasicFSonLVM, TestOutputLength (
- [["pvs"]], 1)],
+ [], (* XXX how to test? *)
"list the LVM volume groups (VGs)",
"\
List all the volumes groups detected. This is the equivalent
of the L<vgs(8)> command. The \"full\" version includes all fields.");
("lvs_full", (RLVList "logvols", []), 14, [],
- [InitBasicFSonLVM, TestOutputLength (
- [["pvs"]], 1)],
+ [], (* XXX how to test? *)
"list the LVM logical volumes (LVs)",
"\
List all the logical volumes detected. This is the equivalent
of the L<lvs(8)> command. The \"full\" version includes all fields.");
("read_lines", (RStringList "lines", [String "path"]), 15, [],
- [InitBasicFS, 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",
C<path/*> 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",
Remove the single file C<path>.");
("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",
Remove the single directory C<path>.");
("rm_rf", (RErr, [String "path"]), 31, [],
- [InitBasicFS, TestOutputFalse
+ [InitBasicFS, Always, TestOutputFalse
[["mkdir"; "/new"];
["mkdir"; "/new/foo"];
["touch"; "/new/foo/bar"];
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<path>.");
("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"]]],
+ ["is_dir"; "/new"]];
+ (* Regression tests for RHBZ#503133: *)
+ InitBasicFS, Always, TestRun
+ [["mkdir"; "/new"];
+ ["mkdir_p"; "/new"]];
+ InitBasicFS, Always, TestLastFail
+ [["touch"; "/new"];
+ ["mkdir_p"; "/new"]]],
"create a directory and parents",
"\
Create a directory named C<path>, creating any parent directories
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",
See also C<guestfs_is_file>, C<guestfs_is_dir>, C<guestfs_stat>.");
("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",
See also C<guestfs_stat>.");
("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",
See also C<guestfs_stat>.");
("pvcreate", (RErr, [String "device"]), 39, [],
- [InitEmpty, TestOutputList (
+ [InitEmpty, Always, TestOutputList (
[["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"];
["pvcreate"; "/dev/sda1"];
["pvcreate"; "/dev/sda2"];
as C</dev/sda1>.");
("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"];
from the non-empty list of physical volumes C<physvols>.");
("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"];
on the volume group C<volgroup>, with C<size> 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"; "/"];
"make a filesystem",
"\
This creates a filesystem on C<device> (usually a partition
-of LVM logical volume). The filesystem type is C<fstype>, for
+or LVM logical volume). The filesystem type is C<fstype>, for
example C<ext3>.");
("sfdisk", (RErr, [String "device";
To create a single partition occupying the whole disk, you would
pass C<lines> as a single element list, when the single element being
-the string C<,> (comma).");
+the string C<,> (comma).
+
+See also: C<guestfs_sfdisk_l>, C<guestfs_sfdisk_N>");
("write_file", (RErr, [String "path"; String "content"; Int "size"]), 44, [ProtocolLimitWarning],
- [InitEmpty, TestOutput (
- [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","];
- ["mkfs"; "ext2"; "/dev/sda1"];
- ["mount"; "/dev/sda1"; "/"];
- ["write_file"; "/new"; "new file contents"; "0"];
- ["cat"; "/new"]], "new file contents")],
+ [InitBasicFS, Always, TestOutput (
+ [["write_file"; "/new"; "new file contents"; "0"];
+ ["cat"; "/new"]], "new file contents");
+ InitBasicFS, Always, TestOutput (
+ [["write_file"; "/new"; "\nnew file contents\n"; "0"];
+ ["cat"; "/new"]], "\nnew file contents\n");
+ InitBasicFS, Always, TestOutput (
+ [["write_file"; "/new"; "\n\n"; "0"];
+ ["cat"; "/new"]], "\n\n");
+ InitBasicFS, Always, TestOutput (
+ [["write_file"; "/new"; ""; "0"];
+ ["cat"; "/new"]], "");
+ InitBasicFS, Always, TestOutput (
+ [["write_file"; "/new"; "\n\n\n"; "0"];
+ ["cat"; "/new"]], "\n\n\n");
+ InitBasicFS, Always, TestOutput (
+ [["write_file"; "/new"; "\n"; "0"];
+ ["cat"; "/new"]], "\n")],
"create a file",
"\
This call creates a file called C<path>. The contents of the
As a special case, if C<size> is C<0>
then the length is calculated using C<strlen> (so in this case
-the content cannot contain embedded ASCII NULs).");
+the content cannot contain embedded ASCII NULs).
+
+I<NB.> Owing to a bug, writing content containing ASCII NUL
+characters does I<not> work, even if the length is specified.
+We hope to resolve this bug in a future version. In the meantime
+use C<guestfs_upload>.");
("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"; "/"];
contains the filesystem.");
("mounts", (RStringList "devices", []), 46, [],
- [InitBasicFS, TestOutputList (
+ [InitBasicFS, Always, TestOutputList (
[["mounts"]], ["/dev/sda1"])],
"show mounted filesystems",
"\
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, Always, TestOutputList (
+ [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"];
+ ["mkfs"; "ext2"; "/dev/sda1"];
+ ["mkfs"; "ext2"; "/dev/sda2"];
+ ["mkfs"; "ext2"; "/dev/sda3"];
+ ["mount"; "/dev/sda1"; "/"];
+ ["mkdir"; "/mp1"];
+ ["mount"; "/dev/sda2"; "/mp1"];
+ ["mkdir"; "/mp1/mp2"];
+ ["mount"; "/dev/sda3"; "/mp1/mp2"];
+ ["mkdir"; "/mp1/mp2/mp3"];
+ ["umount_all"];
["mounts"]], [])],
"unmount all filesystems",
"\
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",
"\
particular that the filename is not prepended to the output
(the C<-b> option).");
-]
+ ("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
+filesystem must be mounted, and must contain a compatible
+operating system (ie. something Linux, with the same
+or compatible processor architecture).
+
+The single parameter is an argv-style list of arguments.
+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<stdout> 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<stderr> from the command.
+
+The C<$PATH> environment variable will contain at least
+C</usr/bin> and C</bin>. If you require a program from
+another location, you should provide the full path in the
+first parameter.
+
+Shared libraries and data files required by the program
+must be available on filesystems which are mounted in the
+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, [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<guestfs_command>, but splits the
+result into a list of lines.");
-let all_functions = non_daemon_functions @ daemon_functions
+ ("stat", (RStat "statbuf", [String "path"]), 52, [],
+ [InitBasicFS, Always, TestOutputStruct (
+ [["touch"; "/new"];
+ ["stat"; "/new"]], [CompareWithInt ("size", 0)])],
+ "get file information",
+ "\
+Returns file information for the given C<path>.
-(* In some places we want the functions to be displayed sorted
- * alphabetically, so this is useful:
- *)
-let all_functions_sorted =
- List.sort (fun (n1,_,_,_,_,_,_) (n2,_,_,_,_,_,_) ->
- compare n1 n2) all_functions
+This is the same as the C<stat(2)> system call.");
-(* Column names and types from LVM PVs/VGs/LVs. *)
-let pv_cols = [
- "pv_name", `String;
- "pv_uuid", `UUID;
- "pv_fmt", `String;
- "pv_size", `Bytes;
- "dev_size", `Bytes;
- "pv_free", `Bytes;
- "pv_used", `Bytes;
- "pv_attr", `String (* XXX *);
- "pv_pe_count", `Int;
- "pv_pe_alloc_count", `Int;
- "pv_tags", `String;
- "pe_start", `Bytes;
- "pv_mda_count", `Int;
- "pv_mda_free", `Bytes;
-(* Not in Fedora 10:
- "pv_mda_size", `Bytes;
-*)
-]
-let vg_cols = [
- "vg_name", `String;
- "vg_uuid", `UUID;
- "vg_fmt", `String;
- "vg_attr", `String (* XXX *);
- "vg_size", `Bytes;
- "vg_free", `Bytes;
- "vg_sysid", `String;
- "vg_extent_size", `Bytes;
- "vg_extent_count", `Int;
- "vg_free_count", `Int;
- "max_lv", `Int;
- "max_pv", `Int;
- "pv_count", `Int;
- "lv_count", `Int;
- "snap_count", `Int;
- "vg_seqno", `Int;
- "vg_tags", `String;
- "vg_mda_count", `Int;
- "vg_mda_free", `Bytes;
-(* Not in Fedora 10:
- "vg_mda_size", `Bytes;
-*)
-]
-let lv_cols = [
- "lv_name", `String;
- "lv_uuid", `UUID;
- "lv_attr", `String (* XXX *);
- "lv_major", `Int;
- "lv_minor", `Int;
- "lv_kernel_major", `Int;
- "lv_kernel_minor", `Int;
- "lv_size", `Bytes;
- "seg_count", `Int;
- "origin", `String;
- "snap_percent", `OptPercent;
- "copy_percent", `OptPercent;
- "move_pv", `String;
- "lv_tags", `String;
- "mirror_log", `String;
- "modules", `String;
-]
+ ("lstat", (RStat "statbuf", [String "path"]), 53, [],
+ [InitBasicFS, Always, TestOutputStruct (
+ [["touch"; "/new"];
+ ["lstat"; "/new"]], [CompareWithInt ("size", 0)])],
+ "get file information for a symbolic link",
+ "\
+Returns file information for the given C<path>.
-(* Useful functions.
- * Note we don't want to use any external OCaml libraries which
- * makes this a bit harder than it should be.
- *)
-let failwithf fs = ksprintf failwith fs
+This is the same as C<guestfs_stat> except that if C<path>
+is a symbolic link, then the link is stat-ed, not the file it
+refers to.
-let replace_char s c1 c2 =
- let s2 = String.copy s in
- let r = ref false in
- for i = 0 to String.length s2 - 1 do
- if String.unsafe_get s2 i = c1 then (
- String.unsafe_set s2 i c2;
- r := true
- )
- done;
- if not !r then s else s2
+This is the same as the C<lstat(2)> system call.");
-let rec find s sub =
- let len = String.length s in
- let sublen = String.length sub in
- let rec loop i =
- if i <= len-sublen then (
- let rec loop2 j =
- if j < sublen then (
- if s.[i+j] = sub.[j] then loop2 (j+1)
- else -1
- ) else
- i (* found *)
- in
- let r = loop2 0 in
- if r = -1 then loop (i+1) else r
- ) else
- -1 (* not found *)
- in
- loop 0
+ ("statvfs", (RStatVFS "statbuf", [String "path"]), 54, [],
+ [InitBasicFS, Always, TestOutputStruct (
+ [["statvfs"; "/"]], [CompareWithInt ("bfree", 487702);
+ CompareWithInt ("blocks", 490020);
+ CompareWithInt ("bsize", 1024)])],
+ "get file system statistics",
+ "\
+Returns file system statistics for any mounted file system.
+C<path> should be a file or directory in the mounted file system
+(typically it is the mount point itself, but it doesn't need to be).
-let rec replace_str s s1 s2 =
- let len = String.length s in
- let sublen = String.length s1 in
- let i = find s s1 in
- if i = -1 then s
- else (
- let s' = String.sub s 0 i in
- let s'' = String.sub s (i+sublen) (len-i-sublen) in
- s' ^ s2 ^ replace_str s'' s1 s2
- )
+This is the same as the C<statvfs(2)> system call.");
-let rec string_split sep str =
- let len = String.length str in
- let seplen = String.length sep in
- let i = find str sep in
- if i = -1 then [str]
- else (
- let s' = String.sub str 0 i in
- let s'' = String.sub str (i+seplen) (len-i-seplen) in
- s' :: string_split sep s''
- )
+ ("tune2fs_l", (RHashtable "superblock", [String "device"]), 55, [],
+ [], (* XXX test *)
+ "get ext2/ext3/ext4 superblock details",
+ "\
+This returns the contents of the ext2, ext3 or ext4 filesystem
+superblock on C<device>.
+
+It is the same as running C<tune2fs -l device>. See L<tune2fs(8)>
+manpage for more details. The list of fields returned isn't
+clearly defined, and depends on both the version of C<tune2fs>
+that libguestfs was built against, and the filesystem itself.");
+
+ ("blockdev_setro", (RErr, [String "device"]), 56, [],
+ [InitEmpty, Always, TestOutputTrue (
+ [["blockdev_setro"; "/dev/sda"];
+ ["blockdev_getro"; "/dev/sda"]])],
+ "set block device to read-only",
+ "\
+Sets the block device named C<device> to read-only.
-let rec find_map f = function
- | [] -> raise Not_found
- | x :: xs ->
- match f x with
- | Some y -> y
- | None -> find_map f xs
+This uses the L<blockdev(8)> command.");
-let iteri f xs =
- let rec loop i = function
- | [] -> ()
- | x :: xs -> f i x; loop (i+1) xs
- in
- loop 0 xs
+ ("blockdev_setrw", (RErr, [String "device"]), 57, [],
+ [InitEmpty, Always, TestOutputFalse (
+ [["blockdev_setrw"; "/dev/sda"];
+ ["blockdev_getro"; "/dev/sda"]])],
+ "set block device to read-write",
+ "\
+Sets the block device named C<device> to read-write.
-let mapi f xs =
- let rec loop i = function
- | [] -> []
- | x :: xs -> let r = f i x in r :: loop (i+1) xs
- in
- loop 0 xs
+This uses the L<blockdev(8)> command.");
-let name_of_argt = function
- | String n | OptString n | StringList n | Bool n | Int n -> n
+ ("blockdev_getro", (RBool "ro", [String "device"]), 58, [],
+ [InitEmpty, Always, TestOutputTrue (
+ [["blockdev_setro"; "/dev/sda"];
+ ["blockdev_getro"; "/dev/sda"]])],
+ "is block device set to read-only",
+ "\
+Returns a boolean indicating if the block device is read-only
+(true if read-only, false if not).
-(* Check function names etc. for consistency. *)
-let check_functions () =
- let contains_uppercase str =
- let len = String.length str in
- let rec loop i =
- if i >= len then false
- else (
- let c = str.[i] in
- if c >= 'A' && c <= 'Z' then true
- else loop (i+1)
- )
- in
- loop 0
- in
+This uses the L<blockdev(8)> command.");
- (* Check function names. *)
- List.iter (
- 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 String.contains name '-' then
- failwithf "function name %s should not contain '-', use '_' instead."
- name
- ) all_functions;
+ ("blockdev_getss", (RInt "sectorsize", [String "device"]), 59, [],
+ [InitEmpty, Always, TestOutputInt (
+ [["blockdev_getss"; "/dev/sda"]], 512)],
+ "get sectorsize of block device",
+ "\
+This returns the size of sectors on a block device.
+Usually 512, but can be larger for modern devices.
- (* Check function parameter/return names. *)
- List.iter (
- fun (name, style, _, _, _, _, _) ->
- let check_arg_ret_name n =
- if contains_uppercase n then
- failwithf "%s param/ret %s should not contain uppercase chars"
- name n;
- if String.contains n '-' || String.contains n '_' then
- failwithf "%s param/ret %s should not contain '-' or '_'"
- name n;
- if n = "value" then
- failwithf "%s has a param/ret called 'value', which causes conflicts in the OCaml bindings, use something like 'val' or a more descriptive name" n
- in
+(Note, this is not the size in sectors, use C<guestfs_blockdev_getsz>
+for that).
- (match fst style with
- | RErr -> ()
- | RInt n | RBool n | RConstString n | RString n
- | RStringList n | RPVList n | RVGList n | RLVList n ->
- check_arg_ret_name n
- | RIntBool (n,m) ->
- check_arg_ret_name n;
- check_arg_ret_name m
- );
- List.iter (fun arg -> check_arg_ret_name (name_of_argt arg)) (snd style)
- ) all_functions;
+This uses the L<blockdev(8)> command.");
- (* Check short descriptions. *)
- List.iter (
- fun (name, _, _, _, _, shortdesc, _) ->
- if shortdesc.[0] <> Char.lowercase shortdesc.[0] then
- failwithf "short description of %s should begin with lowercase." name;
- let c = shortdesc.[String.length shortdesc-1] in
- if c = '\n' || c = '.' then
- failwithf "short description of %s should not end with . or \\n." name
- ) all_functions;
+ ("blockdev_getbsz", (RInt "blocksize", [String "device"]), 60, [],
+ [InitEmpty, Always, TestOutputInt (
+ [["blockdev_getbsz"; "/dev/sda"]], 4096)],
+ "get blocksize of block device",
+ "\
+This returns the block size of a device.
- (* Check long dscriptions. *)
- List.iter (
- fun (name, _, _, _, _, _, longdesc) ->
- if longdesc.[String.length longdesc-1] = '\n' then
- failwithf "long description of %s should not end with \\n." name
- ) all_functions;
+(Note this is different from both I<size in blocks> and
+I<filesystem block size>).
- (* Check proc_nrs. *)
- List.iter (
- fun (name, _, proc_nr, _, _, _, _) ->
- if proc_nr <= 0 then
- failwithf "daemon function %s should have proc_nr > 0" name
- ) daemon_functions;
+This uses the L<blockdev(8)> command.");
- List.iter (
- fun (name, _, proc_nr, _, _, _, _) ->
- if proc_nr <> -1 then
- failwithf "non-daemon function %s should have proc_nr -1" name
- ) non_daemon_functions;
+ ("blockdev_setbsz", (RErr, [String "device"; Int "blocksize"]), 61, [],
+ [], (* XXX test *)
+ "set blocksize of block device",
+ "\
+This sets the block size of a device.
- let proc_nrs =
- List.map (fun (name, _, proc_nr, _, _, _, _) -> name, proc_nr)
- daemon_functions in
- let proc_nrs =
- List.sort (fun (_,nr1) (_,nr2) -> compare nr1 nr2) proc_nrs in
- let rec loop = function
- | [] -> ()
- | [_] -> ()
- | (name1,nr1) :: ((name2,nr2) :: _ as rest) when nr1 < nr2 ->
- loop rest
- | (name1,nr1) :: (name2,nr2) :: _ ->
- failwithf "%s and %s have conflicting procedure numbers (%d, %d)"
- name1 name2 nr1 nr2
- in
- loop proc_nrs
+(Note this is different from both I<size in blocks> and
+I<filesystem block size>).
-(* 'pr' prints to the current output file. *)
-let chan = ref stdout
-let pr fs = ksprintf (output_string !chan) fs
+This uses the L<blockdev(8)> command.");
-(* Generate a header block in a number of standard styles. *)
-type comment_style = CStyle | HashStyle | OCamlStyle
-type license = GPLv2 | LGPLv2
+ ("blockdev_getsz", (RInt64 "sizeinsectors", [String "device"]), 62, [],
+ [InitEmpty, Always, TestOutputInt (
+ [["blockdev_getsz"; "/dev/sda"]], 1024000)],
+ "get total size of device in 512-byte sectors",
+ "\
+This returns the size of the device in units of 512-byte sectors
+(even if the sectorsize isn't 512 bytes ... weird).
-let generate_header comment license =
- let c = match comment with
- | CStyle -> pr "/* "; " *"
- | HashStyle -> pr "# "; "#"
- | OCamlStyle -> 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;
- pr "%s\n" c;
- pr "%s Copyright (C) 2009 Red Hat Inc.\n" c;
- pr "%s\n" c;
- (match license with
- | GPLv2 ->
- pr "%s This program is free software; you can redistribute it and/or modify\n" c;
- pr "%s it under the terms of the GNU General Public License as published by\n" c;
- pr "%s the Free Software Foundation; either version 2 of the License, or\n" c;
- pr "%s (at your option) any later version.\n" c;
- pr "%s\n" c;
- pr "%s This program is distributed in the hope that it will be useful,\n" c;
- pr "%s but WITHOUT ANY WARRANTY; without even the implied warranty of\n" c;
- pr "%s MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" c;
- pr "%s GNU General Public License for more details.\n" c;
- pr "%s\n" c;
- pr "%s You should have received a copy of the GNU General Public License along\n" c;
- pr "%s with this program; if not, write to the Free Software Foundation, Inc.,\n" c;
- pr "%s 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n" c;
+See also C<guestfs_blockdev_getss> for the real sector size of
+the device, and C<guestfs_blockdev_getsize64> for the more
+useful I<size in bytes>.
- | LGPLv2 ->
- pr "%s This library is free software; you can redistribute it and/or\n" c;
- pr "%s modify it under the terms of the GNU Lesser General Public\n" c;
- pr "%s License as published by the Free Software Foundation; either\n" c;
- pr "%s version 2 of the License, or (at your option) any later version.\n" c;
- pr "%s\n" c;
- pr "%s This library is distributed in the hope that it will be useful,\n" c;
- pr "%s but WITHOUT ANY WARRANTY; without even the implied warranty of\n" c;
- pr "%s MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" c;
- pr "%s Lesser General Public License for more details.\n" c;
- pr "%s\n" c;
- pr "%s You should have received a copy of the GNU Lesser General Public\n" c;
- pr "%s License along with this library; if not, write to the Free Software\n" c;
- pr "%s Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n" c;
- );
- (match comment with
- | CStyle -> pr " */\n"
- | HashStyle -> ()
- | OCamlStyle -> pr " *)\n"
- );
- pr "\n"
+This uses the L<blockdev(8)> command.");
-(* Start of main code generation functions below this line. *)
+ ("blockdev_getsize64", (RInt64 "sizeinbytes", [String "device"]), 63, [],
+ [InitEmpty, Always, TestOutputInt (
+ [["blockdev_getsize64"; "/dev/sda"]], 524288000)],
+ "get total size of device in bytes",
+ "\
+This returns the size of the device in bytes.
-(* Generate the pod documentation for the C API. *)
-let rec generate_actions_pod () =
- List.iter (
- fun (shortname, style, _, flags, _, _, longdesc) ->
- let name = "guestfs_" ^ shortname in
- pr "=head2 %s\n\n" name;
- pr " ";
- generate_prototype ~extern:false ~handle:"handle" name style;
- pr "\n\n";
- pr "%s\n\n" longdesc;
- (match fst style with
- | RErr ->
- pr "This function returns 0 on success or -1 on error.\n\n"
- | RInt _ ->
- pr "On error this function returns -1.\n\n"
- | RBool _ ->
- pr "This function returns a C truth value on success or -1 on error.\n\n"
- | RConstString _ ->
- pr "This function returns a string or NULL on error.
-The string is owned by the guest handle and must I<not> be freed.\n\n"
- | RString _ ->
- pr "This function returns a string or NULL on error.
-I<The caller must free the returned string after use>.\n\n"
- | RStringList _ ->
- pr "This function returns a NULL-terminated array of strings
-(like L<environ(3)>), or NULL if there was an error.
-I<The caller must free the strings and the array after use>.\n\n"
- | RIntBool _ ->
- pr "This function returns a C<struct guestfs_int_bool *>.
-I<The caller must call C<guestfs_free_int_bool> after use>.\n\n"
- | RPVList _ ->
- pr "This function returns a C<struct guestfs_lvm_pv_list *>.
-I<The caller must call C<guestfs_free_lvm_pv_list> after use>.\n\n"
- | RVGList _ ->
- pr "This function returns a C<struct guestfs_lvm_vg_list *>.
-I<The caller must call C<guestfs_free_lvm_vg_list> after use>.\n\n"
- | RLVList _ ->
- pr "This function returns a C<struct guestfs_lvm_lv_list *>.
-I<The caller must call C<guestfs_free_lvm_lv_list> after use>.\n\n"
- );
- if List.mem ProtocolLimitWarning flags then
- pr "%s\n\n" protocol_limit_warning;
- if List.mem DangerWillRobinson flags then
- pr "%s\n\n" danger_will_robinson;
- ) all_functions_sorted
+See also C<guestfs_blockdev_getsz>.
-and generate_structs_pod () =
- (* LVM structs documentation. *)
- List.iter (
- fun (typ, cols) ->
- pr "=head2 guestfs_lvm_%s\n" typ;
- pr "\n";
- pr " struct guestfs_lvm_%s {\n" typ;
- List.iter (
- function
- | name, `String -> pr " char *%s;\n" name
- | name, `UUID ->
- pr " /* The next field is NOT nul-terminated, be careful when printing it: */\n";
- pr " char %s[32];\n" name
- | name, `Bytes -> pr " uint64_t %s;\n" name
- | name, `Int -> pr " int64_t %s;\n" name
- | name, `OptPercent ->
- pr " /* The next field is [0..100] or -1 meaning 'not present': */\n";
- pr " float %s;\n" name
- ) cols;
- pr " \n";
- pr " struct guestfs_lvm_%s_list {\n" typ;
- pr " uint32_t len; /* Number of elements in list. */\n";
- pr " struct guestfs_lvm_%s *val; /* Elements. */\n" typ;
- pr " };\n";
- pr " \n";
- pr " void guestfs_free_lvm_%s_list (struct guestfs_free_lvm_%s_list *);\n"
- typ typ;
- pr "\n"
- ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols]
+This uses the L<blockdev(8)> command.");
-(* Generate the protocol (XDR) file, 'guestfs_protocol.x' and
- * indirectly 'guestfs_protocol.h' and 'guestfs_protocol.c'.
- *
- * We have to use an underscore instead of a dash because otherwise
- * rpcgen generates incorrect code.
- *
- * This header is NOT exported to clients, but see also generate_structs_h.
- *)
-and generate_xdr () =
- generate_header CStyle LGPLv2;
+ ("blockdev_flushbufs", (RErr, [String "device"]), 64, [],
+ [InitEmpty, Always, TestRun
+ [["blockdev_flushbufs"; "/dev/sda"]]],
+ "flush device buffers",
+ "\
+This tells the kernel to flush internal buffers associated
+with C<device>.
- (* This has to be defined to get around a limitation in Sun's rpcgen. *)
- pr "typedef string str<>;\n";
- pr "\n";
+This uses the L<blockdev(8)> command.");
- (* LVM internal structures. *)
- List.iter (
- function
- | typ, cols ->
- pr "struct guestfs_lvm_int_%s {\n" typ;
- List.iter (function
- | name, `String -> pr " string %s<>;\n" name
- | name, `UUID -> pr " opaque %s[32];\n" name
- | name, `Bytes -> pr " hyper %s;\n" name
- | name, `Int -> pr " hyper %s;\n" name
- | name, `OptPercent -> pr " float %s;\n" name
- ) cols;
- pr "};\n";
- pr "\n";
- pr "typedef struct guestfs_lvm_int_%s guestfs_lvm_int_%s_list<>;\n" typ typ;
- pr "\n";
- ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
+ ("blockdev_rereadpt", (RErr, [String "device"]), 65, [],
+ [InitEmpty, Always, TestRun
+ [["blockdev_rereadpt"; "/dev/sda"]]],
+ "reread partition table",
+ "\
+Reread the partition table on C<device>.
- List.iter (
- fun (shortname, style, _, _, _, _, _) ->
- let name = "guestfs_" ^ shortname in
+This uses the L<blockdev(8)> command.");
- (match snd style with
- | [] -> ()
- | args ->
- pr "struct %s_args {\n" name;
- List.iter (
- function
- | String n -> pr " string %s<>;\n" n
- | OptString n -> pr " str *%s;\n" n
- | StringList n -> pr " str %s<>;\n" n
- | Bool n -> pr " bool %s;\n" n
- | Int n -> pr " int %s;\n" n
- ) args;
- pr "};\n\n"
- );
- (match fst style with
- | RErr -> ()
- | RInt n ->
- pr "struct %s_ret {\n" name;
- pr " int %s;\n" n;
- pr "};\n\n"
- | RBool n ->
- pr "struct %s_ret {\n" name;
- pr " bool %s;\n" n;
- pr "};\n\n"
- | RConstString _ ->
- failwithf "RConstString cannot be returned from a daemon function"
- | RString n ->
- pr "struct %s_ret {\n" name;
- pr " string %s<>;\n" n;
- pr "};\n\n"
- | RStringList n ->
- pr "struct %s_ret {\n" name;
- pr " str %s<>;\n" n;
- pr "};\n\n"
- | RIntBool (n,m) ->
- pr "struct %s_ret {\n" name;
- pr " int %s;\n" n;
- pr " bool %s;\n" m;
- pr "};\n\n"
- | RPVList n ->
- pr "struct %s_ret {\n" name;
- pr " guestfs_lvm_int_pv_list %s;\n" n;
- pr "};\n\n"
- | RVGList n ->
- pr "struct %s_ret {\n" name;
- pr " guestfs_lvm_int_vg_list %s;\n" n;
- pr "};\n\n"
- | RLVList n ->
- pr "struct %s_ret {\n" name;
- pr " guestfs_lvm_int_lv_list %s;\n" n;
- pr "};\n\n"
- );
- ) daemon_functions;
-
- (* Table of procedure numbers. *)
- pr "enum guestfs_procedure {\n";
- List.iter (
- fun (shortname, _, proc_nr, _, _, _, _) ->
- pr " GUESTFS_PROC_%s = %d,\n" (String.uppercase shortname) proc_nr
- ) daemon_functions;
- pr " GUESTFS_PROC_dummy\n"; (* so we don't have a "hanging comma" *)
- pr "};\n";
- pr "\n";
+ ("upload", (RErr, [FileIn "filename"; String "remotefilename"]), 66, [],
+ [InitBasicFS, Always, TestOutput (
+ (* Pick a file from cwd which isn't likely to change. *)
+ [["upload"; "../COPYING.LIB"; "/COPYING.LIB"];
+ ["checksum"; "md5"; "/COPYING.LIB"]], "e3eda01d9815f8d24aae2dbd89b68b06")],
+ "upload a file from the local machine",
+ "\
+Upload local file C<filename> to C<remotefilename> on the
+filesystem.
- (* Having to choose a maximum message size is annoying for several
- * reasons (it limits what we can do in the API), but it (a) makes
- * the protocol a lot simpler, and (b) provides a bound on the size
- * of the daemon which operates in limited memory space. For large
- * file transfers you should use FTP.
- *)
- pr "const GUESTFS_MESSAGE_MAX = %d;\n" (4 * 1024 * 1024);
- pr "\n";
+C<filename> can also be a named pipe.
- (* Message header, etc. *)
- pr "\
-const GUESTFS_PROGRAM = 0x2000F5F5;
-const GUESTFS_PROTOCOL_VERSION = 1;
+See also C<guestfs_download>.");
-enum guestfs_message_direction {
- GUESTFS_DIRECTION_CALL = 0, /* client -> daemon */
- GUESTFS_DIRECTION_REPLY = 1 /* daemon -> client */
-};
+ ("download", (RErr, [String "remotefilename"; FileOut "filename"]), 67, [],
+ [InitBasicFS, Always, TestOutput (
+ (* Pick a file from cwd which isn't likely to change. *)
+ [["upload"; "../COPYING.LIB"; "/COPYING.LIB"];
+ ["download"; "/COPYING.LIB"; "testdownload.tmp"];
+ ["upload"; "testdownload.tmp"; "/upload"];
+ ["checksum"; "md5"; "/upload"]], "e3eda01d9815f8d24aae2dbd89b68b06")],
+ "download a file to the local machine",
+ "\
+Download file C<remotefilename> and save it as C<filename>
+on the local machine.
+
+C<filename> can also be a named pipe.
+
+See also C<guestfs_upload>, C<guestfs_cat>.");
+
+ ("checksum", (RString "checksum", [String "csumtype"; String "path"]), 68, [],
+ [InitBasicFS, Always, TestOutput (
+ [["write_file"; "/new"; "test\n"; "0"];
+ ["checksum"; "crc"; "/new"]], "935282863");
+ InitBasicFS, Always, TestLastFail (
+ [["checksum"; "crc"; "/new"]]);
+ InitBasicFS, Always, TestOutput (
+ [["write_file"; "/new"; "test\n"; "0"];
+ ["checksum"; "md5"; "/new"]], "d8e8fca2dc0f896fd7cb4cb0031ba249");
+ InitBasicFS, Always, TestOutput (
+ [["write_file"; "/new"; "test\n"; "0"];
+ ["checksum"; "sha1"; "/new"]], "4e1243bd22c66e76c2ba9eddc1f91394e57f9f83");
+ InitBasicFS, Always, TestOutput (
+ [["write_file"; "/new"; "test\n"; "0"];
+ ["checksum"; "sha224"; "/new"]], "52f1bf093f4b7588726035c176c0cdb4376cfea53819f1395ac9e6ec");
+ InitBasicFS, Always, TestOutput (
+ [["write_file"; "/new"; "test\n"; "0"];
+ ["checksum"; "sha256"; "/new"]], "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2");
+ InitBasicFS, Always, TestOutput (
+ [["write_file"; "/new"; "test\n"; "0"];
+ ["checksum"; "sha384"; "/new"]], "109bb6b5b6d5547c1ce03c7a8bd7d8f80c1cb0957f50c4f7fda04692079917e4f9cad52b878f3d8234e1a170b154b72d");
+ InitBasicFS, Always, TestOutput (
+ [["write_file"; "/new"; "test\n"; "0"];
+ ["checksum"; "sha512"; "/new"]], "0e3e75234abc68f4378a86b3f4b32a198ba301845b0cd6e50106e874345700cc6663a86c1ea125dc5e92be17c98f9a0f85ca9d5f595db2012f7cc3571945c123");
+ InitBasicFS, Always, TestOutput (
+ [["mount"; "/dev/sdd"; "/"];
+ ["checksum"; "md5"; "/known-3"]], "46d6ca27ee07cdc6fa99c2e138cc522c")],
+ "compute MD5, SHAx or CRC checksum of file",
+ "\
+This call computes the MD5, SHAx or CRC checksum of the
+file named C<path>.
-enum guestfs_message_status {
- GUESTFS_STATUS_OK = 0,
- GUESTFS_STATUS_ERROR = 1
-};
+The type of checksum to compute is given by the C<csumtype>
+parameter which must have one of the following values:
-const GUESTFS_ERROR_LEN = 256;
+=over 4
-struct guestfs_message_error {
- string error<GUESTFS_ERROR_LEN>; /* error message */
-};
+=item C<crc>
-struct guestfs_message_header {
- unsigned prog; /* GUESTFS_PROGRAM */
- unsigned vers; /* GUESTFS_PROTOCOL_VERSION */
- guestfs_procedure proc; /* GUESTFS_PROC_x */
- guestfs_message_direction direction;
- unsigned serial; /* message serial number */
- guestfs_message_status status;
-};
-"
+Compute the cyclic redundancy check (CRC) specified by POSIX
+for the C<cksum> command.
-(* Generate the guestfs-structs.h file. *)
-and generate_structs_h () =
- generate_header CStyle LGPLv2;
+=item C<md5>
- (* This is a public exported header file containing various
- * structures. The structures are carefully written to have
- * exactly the same in-memory format as the XDR structures that
- * we use on the wire to the daemon. The reason for creating
- * copies of these structures here is just so we don't have to
- * export the whole of guestfs_protocol.h (which includes much
- * unrelated and XDR-dependent stuff that we don't want to be
- * public, or required by clients).
- *
- * To reiterate, we will pass these structures to and from the
- * client with a simple assignment or memcpy, so the format
- * must be identical to what rpcgen / the RFC defines.
- *)
+Compute the MD5 hash (using the C<md5sum> program).
- (* guestfs_int_bool structure. *)
- pr "struct guestfs_int_bool {\n";
- pr " int32_t i;\n";
- pr " int32_t b;\n";
- pr "};\n";
- pr "\n";
+=item C<sha1>
- (* LVM public structures. *)
- List.iter (
- function
- | typ, cols ->
- pr "struct guestfs_lvm_%s {\n" typ;
- List.iter (
- function
- | name, `String -> pr " char *%s;\n" name
- | name, `UUID -> pr " char %s[32]; /* this is NOT nul-terminated, be careful when printing */\n" name
- | name, `Bytes -> pr " uint64_t %s;\n" name
- | name, `Int -> pr " int64_t %s;\n" name
- | name, `OptPercent -> pr " float %s; /* [0..100] or -1 */\n" name
- ) cols;
- pr "};\n";
- pr "\n";
- pr "struct guestfs_lvm_%s_list {\n" typ;
- pr " uint32_t len;\n";
- pr " struct guestfs_lvm_%s *val;\n" typ;
- pr "};\n";
- pr "\n"
- ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols]
+Compute the SHA1 hash (using the C<sha1sum> program).
-(* Generate the guestfs-actions.h file. *)
-and generate_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
- ) all_functions
+=item C<sha224>
-(* Generate the client-side dispatch stubs. *)
-and generate_client_actions () =
- generate_header CStyle LGPLv2;
+Compute the SHA224 hash (using the C<sha224sum> program).
- (* Client-side stubs for each function. *)
- List.iter (
- fun (shortname, style, _, _, _, _, _) ->
- let name = "guestfs_" ^ shortname in
+=item C<sha256>
- (* Generate the return value struct. *)
- pr "struct %s_rv {\n" shortname;
- pr " int cb_done; /* flag to indicate callback was called */\n";
- pr " struct guestfs_message_header hdr;\n";
- pr " struct guestfs_message_error err;\n";
- (match fst style with
- | RErr -> ()
- | RConstString _ ->
- failwithf "RConstString cannot be returned from a daemon function"
- | RInt _
- | RBool _ | RString _ | RStringList _
- | RIntBool _
- | RPVList _ | RVGList _ | RLVList _ ->
- pr " struct %s_ret ret;\n" name
- );
- pr "};\n\n";
+Compute the SHA256 hash (using the C<sha256sum> program).
- (* Generate the callback function. *)
- pr "static void %s_cb (guestfs_h *g, void *data, XDR *xdr)\n" shortname;
- pr "{\n";
- pr " struct %s_rv *rv = (struct %s_rv *) data;\n" shortname shortname;
- pr "\n";
- pr " if (!xdr_guestfs_message_header (xdr, &rv->hdr)) {\n";
- pr " error (g, \"%s: failed to parse reply header\");\n" name;
- pr " return;\n";
- pr " }\n";
- pr " if (rv->hdr.status == GUESTFS_STATUS_ERROR) {\n";
- pr " if (!xdr_guestfs_message_error (xdr, &rv->err)) {\n";
- pr " error (g, \"%s: failed to parse reply error\");\n" name;
- pr " return;\n";
- pr " }\n";
- pr " goto done;\n";
- pr " }\n";
+=item C<sha384>
- (match fst style with
- | RErr -> ()
- | RConstString _ ->
- failwithf "RConstString cannot be returned from a daemon function"
- | RInt _
- | RBool _ | RString _ | RStringList _
- | RIntBool _
- | RPVList _ | RVGList _ | RLVList _ ->
- pr " if (!xdr_%s_ret (xdr, &rv->ret)) {\n" name;
- pr " error (g, \"%s: failed to parse reply\");\n" name;
- pr " return;\n";
- pr " }\n";
- );
+Compute the SHA384 hash (using the C<sha384sum> program).
- pr " done:\n";
- pr " rv->cb_done = 1;\n";
- pr " main_loop.main_loop_quit (g);\n";
- pr "}\n\n";
+=item C<sha512>
- (* Generate the action stub. *)
- generate_prototype ~extern:false ~semicolon:false ~newline:true
- ~handle:"g" name style;
+Compute the SHA512 hash (using the C<sha512sum> program).
- let error_code =
- match fst style with
- | RErr | RInt _ | RBool _ -> "-1"
- | RConstString _ ->
- failwithf "RConstString cannot be returned from a daemon function"
- | RString _ | RStringList _ | RIntBool _
- | RPVList _ | RVGList _ | RLVList _ ->
- "NULL" in
+=back
- pr "{\n";
+The checksum is returned as a printable string.");
- (match snd style with
- | [] -> ()
- | _ -> pr " struct %s_args args;\n" name
- );
+ ("tar_in", (RErr, [FileIn "tarfile"; String "directory"]), 69, [],
+ [InitBasicFS, Always, TestOutput (
+ [["tar_in"; "../images/helloworld.tar"; "/"];
+ ["cat"; "/hello"]], "hello\n")],
+ "unpack tarfile to directory",
+ "\
+This command uploads and unpacks local file C<tarfile> (an
+I<uncompressed> tar file) into C<directory>.
- pr " struct %s_rv rv;\n" shortname;
- pr " int serial;\n";
- pr "\n";
- pr " if (g->state != READY) {\n";
- pr " error (g, \"%s called from the wrong state, %%d != READY\",\n"
- name;
- pr " g->state);\n";
- pr " return %s;\n" error_code;
- pr " }\n";
- pr "\n";
- pr " memset (&rv, 0, sizeof rv);\n";
- pr "\n";
+To upload a compressed tarball, use C<guestfs_tgz_in>.");
- (match snd style with
- | [] ->
- pr " serial = dispatch (g, GUESTFS_PROC_%s, NULL, NULL);\n"
- (String.uppercase shortname)
- | args ->
- List.iter (
- function
- | String n ->
- pr " args.%s = (char *) %s;\n" n n
- | OptString n ->
- pr " args.%s = %s ? (char **) &%s : NULL;\n" n n n
- | StringList n ->
- pr " args.%s.%s_val = (char **) %s;\n" n n n;
- pr " for (args.%s.%s_len = 0; %s[args.%s.%s_len]; args.%s.%s_len++) ;\n" n n n n n n n;
- | Bool n ->
- pr " args.%s = %s;\n" n n
- | Int n ->
- pr " args.%s = %s;\n" n n
- ) args;
- pr " serial = dispatch (g, GUESTFS_PROC_%s,\n"
- (String.uppercase shortname);
- pr " (xdrproc_t) xdr_%s_args, (char *) &args);\n"
- name;
- );
- pr " if (serial == -1)\n";
- pr " return %s;\n" error_code;
- pr "\n";
+ ("tar_out", (RErr, [String "directory"; FileOut "tarfile"]), 70, [],
+ [],
+ "pack directory into tarfile",
+ "\
+This command packs the contents of C<directory> and downloads
+it to local file C<tarfile>.
- pr " rv.cb_done = 0;\n";
- pr " g->reply_cb_internal = %s_cb;\n" shortname;
- pr " g->reply_cb_internal_data = &rv;\n";
- pr " main_loop.main_loop_run (g);\n";
- pr " g->reply_cb_internal = NULL;\n";
- pr " g->reply_cb_internal_data = NULL;\n";
- pr " if (!rv.cb_done) {\n";
- pr " error (g, \"%s failed, see earlier error messages\");\n" name;
- pr " return %s;\n" error_code;
- pr " }\n";
- pr "\n";
+To download a compressed tarball, use C<guestfs_tgz_out>.");
- pr " if (check_reply_header (g, &rv.hdr, GUESTFS_PROC_%s, serial) == -1)\n"
- (String.uppercase shortname);
- pr " return %s;\n" error_code;
- pr "\n";
+ ("tgz_in", (RErr, [FileIn "tarball"; String "directory"]), 71, [],
+ [InitBasicFS, Always, TestOutput (
+ [["tgz_in"; "../images/helloworld.tar.gz"; "/"];
+ ["cat"; "/hello"]], "hello\n")],
+ "unpack compressed tarball to directory",
+ "\
+This command uploads and unpacks local file C<tarball> (a
+I<gzip compressed> tar file) into C<directory>.
- pr " if (rv.hdr.status == GUESTFS_STATUS_ERROR) {\n";
- pr " error (g, \"%%s\", rv.err.error);\n";
- pr " return %s;\n" error_code;
- pr " }\n";
- pr "\n";
+To upload an uncompressed tarball, use C<guestfs_tar_in>.");
- (match fst style with
- | RErr -> pr " return 0;\n"
- | RInt n
- | RBool n -> pr " return rv.ret.%s;\n" n
- | RConstString _ ->
- failwithf "RConstString cannot be returned from a daemon function"
- | RString n ->
- pr " return rv.ret.%s; /* caller will free */\n" n
- | RStringList n ->
- pr " /* caller will free this, but we need to add a NULL entry */\n";
- pr " rv.ret.%s.%s_val =" n n;
- pr " safe_realloc (g, rv.ret.%s.%s_val,\n" n n;
- pr " sizeof (char *) * (rv.ret.%s.%s_len + 1));\n"
- n n;
- pr " rv.ret.%s.%s_val[rv.ret.%s.%s_len] = NULL;\n" n n n n;
- pr " return rv.ret.%s.%s_val;\n" n n
- | RIntBool _ ->
- pr " /* caller with free this */\n";
- pr " return safe_memdup (g, &rv.ret, sizeof (rv.ret));\n"
- | RPVList n ->
- pr " /* caller will free this */\n";
- pr " return safe_memdup (g, &rv.ret.%s, sizeof (rv.ret.%s));\n" n n
- | RVGList n ->
- pr " /* caller will free this */\n";
- pr " return safe_memdup (g, &rv.ret.%s, sizeof (rv.ret.%s));\n" n n
- | RLVList n ->
- pr " /* caller will free this */\n";
- pr " return safe_memdup (g, &rv.ret.%s, sizeof (rv.ret.%s));\n" n n
- );
+ ("tgz_out", (RErr, [String "directory"; FileOut "tarball"]), 72, [],
+ [],
+ "pack directory into compressed tarball",
+ "\
+This command packs the contents of C<directory> and downloads
+it to local file C<tarball>.
+
+To download an uncompressed tarball, use C<guestfs_tar_out>.");
+
+ ("mount_ro", (RErr, [String "device"; String "mountpoint"]), 73, [],
+ [InitBasicFS, Always, TestLastFail (
+ [["umount"; "/"];
+ ["mount_ro"; "/dev/sda1"; "/"];
+ ["touch"; "/new"]]);
+ InitBasicFS, Always, TestOutput (
+ [["write_file"; "/new"; "data"; "0"];
+ ["umount"; "/"];
+ ["mount_ro"; "/dev/sda1"; "/"];
+ ["cat"; "/new"]], "data")],
+ "mount a guest disk, read-only",
+ "\
+This is the same as the C<guestfs_mount> command, but it
+mounts the filesystem with the read-only (I<-o ro>) flag.");
- pr "}\n\n"
- ) daemon_functions
+ ("mount_options", (RErr, [String "options"; String "device"; String "mountpoint"]), 74, [],
+ [],
+ "mount a guest disk with mount options",
+ "\
+This is the same as the C<guestfs_mount> command, but it
+allows you to set the mount options as for the
+L<mount(8)> I<-o> flag.");
-(* Generate daemon/actions.h. *)
-and generate_daemon_actions_h () =
- generate_header CStyle GPLv2;
+ ("mount_vfs", (RErr, [String "options"; String "vfstype"; String "device"; String "mountpoint"]), 75, [],
+ [],
+ "mount a guest disk with mount options and vfstype",
+ "\
+This is the same as the C<guestfs_mount> command, but it
+allows you to set both the mount options and the vfstype
+as for the L<mount(8)> I<-o> and I<-t> flags.");
- pr "#include \"../src/guestfs_protocol.h\"\n";
- pr "\n";
+ ("debug", (RString "result", [String "subcmd"; StringList "extraargs"]), 76, [],
+ [],
+ "debugging and internals",
+ "\
+The C<guestfs_debug> command exposes some internals of
+C<guestfsd> (the guestfs daemon) that runs inside the
+qemu subprocess.
- List.iter (
- fun (name, style, _, _, _, _, _) ->
- generate_prototype
- ~single_line:true ~newline:true ~in_daemon:true ~prefix:"do_"
- name style;
- ) daemon_functions
+There is no comprehensive help for this command. You have
+to look at the file C<daemon/debug.c> in the libguestfs source
+to find out what you can do.");
-(* Generate the server-side stubs. *)
-and generate_daemon_actions () =
- generate_header CStyle GPLv2;
+ ("lvremove", (RErr, [String "device"]), 77, [],
+ [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, 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, 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"];
+ ["vgs"]], ["VG"])],
+ "remove an LVM logical volume",
+ "\
+Remove an LVM logical volume C<device>, where C<device> is
+the path to the LV, such as C</dev/VG/LV>.
- pr "#define _GNU_SOURCE // for strchrnul\n";
- pr "\n";
- pr "#include <stdio.h>\n";
- pr "#include <stdlib.h>\n";
- pr "#include <string.h>\n";
- pr "#include <inttypes.h>\n";
- pr "#include <ctype.h>\n";
- pr "#include <rpc/types.h>\n";
- pr "#include <rpc/xdr.h>\n";
- pr "\n";
- pr "#include \"daemon.h\"\n";
- pr "#include \"../src/guestfs_protocol.h\"\n";
- pr "#include \"actions.h\"\n";
- pr "\n";
+You can also remove all LVs in a volume group by specifying
+the VG name, C</dev/VG>.");
- List.iter (
- fun (name, style, _, _, _, _, _) ->
- (* Generate server-side stubs. *)
- pr "static void %s_stub (XDR *xdr_in)\n" name;
- pr "{\n";
- let error_code =
- match fst style with
- | RErr | RInt _ -> pr " int r;\n"; "-1"
- | RBool _ -> pr " int r;\n"; "-1"
- | RConstString _ ->
- failwithf "RConstString cannot be returned from a daemon function"
- | RString _ -> pr " char *r;\n"; "NULL"
- | RStringList _ -> pr " char **r;\n"; "NULL"
- | RIntBool _ -> pr " guestfs_%s_ret *r;\n" name; "NULL"
- | RPVList _ -> pr " guestfs_lvm_int_pv_list *r;\n"; "NULL"
- | RVGList _ -> pr " guestfs_lvm_int_vg_list *r;\n"; "NULL"
- | RLVList _ -> pr " guestfs_lvm_int_lv_list *r;\n"; "NULL" in
+ ("vgremove", (RErr, [String "vgname"]), 78, [],
+ [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, 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"];
+ ["vgs"]], [])],
+ "remove an LVM volume group",
+ "\
+Remove an LVM volume group C<vgname>, (for example C<VG>).
- (match snd style with
- | [] -> ()
- | args ->
- pr " struct guestfs_%s_args args;\n" name;
- List.iter (
- function
- | String n
- | OptString n -> pr " const char *%s;\n" n
- | StringList n -> pr " char **%s;\n" n
- | Bool n -> pr " int %s;\n" n
- | Int n -> pr " int %s;\n" n
- ) args
- );
- pr "\n";
+This also forcibly removes all logical volumes in the volume
+group (if any).");
- (match snd style with
- | [] -> ()
- | args ->
- pr " memset (&args, 0, sizeof args);\n";
- pr "\n";
- pr " if (!xdr_guestfs_%s_args (xdr_in, &args)) {\n" name;
- pr " reply_with_error (\"%%s: daemon failed to decode procedure arguments\", \"%s\");\n" name;
- pr " return;\n";
- pr " }\n";
- List.iter (
- function
- | String n -> pr " %s = args.%s;\n" n n
- | OptString n -> pr " %s = args.%s ? *args.%s : NULL;\n" n n n
- | StringList n ->
- pr " args.%s.%s_val = realloc (args.%s.%s_val, sizeof (char *) * (args.%s.%s_len+1));\n" n n n n n n;
- pr " args.%s.%s_val[args.%s.%s_len] = NULL;\n" n n n n;
- pr " %s = args.%s.%s_val;\n" n n n
- | Bool n -> pr " %s = args.%s;\n" n n
- | Int n -> pr " %s = args.%s;\n" n n
- ) args;
- pr "\n"
- );
+ ("pvremove", (RErr, [String "device"]), 79, [],
+ [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/sda1"];
+ ["lvs"]], []);
+ 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/sda1"];
+ ["vgs"]], []);
+ 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/sda1"];
+ ["pvs"]], [])],
+ "remove an LVM physical volume",
+ "\
+This wipes a physical volume C<device> so that LVM will no longer
+recognise it.
+
+The implementation uses the C<pvremove> command which refuses to
+wipe physical volumes that contain any volume groups, so you have
+to remove those first.");
+
+ ("set_e2label", (RErr, [String "device"; String "label"]), 80, [],
+ [InitBasicFS, Always, TestOutput (
+ [["set_e2label"; "/dev/sda1"; "testlabel"];
+ ["get_e2label"; "/dev/sda1"]], "testlabel")],
+ "set the ext2/3/4 filesystem label",
+ "\
+This sets the ext2/3/4 filesystem label of the filesystem on
+C<device> to C<label>. Filesystem labels are limited to
+16 characters.
- pr " r = do_%s " name;
- generate_call_args style;
- pr ";\n";
+You can use either C<guestfs_tune2fs_l> or C<guestfs_get_e2label>
+to return the existing label on a filesystem.");
- pr " if (r == %s)\n" error_code;
- pr " /* do_%s has already called reply_with_error */\n" name;
- pr " goto done;\n";
- pr "\n";
+ ("get_e2label", (RString "label", [String "device"]), 81, [],
+ [],
+ "get the ext2/3/4 filesystem label",
+ "\
+This returns the ext2/3/4 filesystem label of the filesystem on
+C<device>.");
+
+ ("set_e2uuid", (RErr, [String "device"; String "uuid"]), 82, [],
+ [InitBasicFS, Always, TestOutput (
+ [["set_e2uuid"; "/dev/sda1"; "a3a61220-882b-4f61-89f4-cf24dcc7297d"];
+ ["get_e2uuid"; "/dev/sda1"]], "a3a61220-882b-4f61-89f4-cf24dcc7297d");
+ 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, Always, TestRun (
+ [["set_e2uuid"; "/dev/sda1"; "random"]]);
+ InitBasicFS, Always, TestRun (
+ [["set_e2uuid"; "/dev/sda1"; "time"]])],
+ "set the ext2/3/4 filesystem UUID",
+ "\
+This sets the ext2/3/4 filesystem UUID of the filesystem on
+C<device> to C<uuid>. The format of the UUID and alternatives
+such as C<clear>, C<random> and C<time> are described in the
+L<tune2fs(8)> manpage.
- (match fst style with
- | RErr -> pr " reply (NULL, NULL);\n"
- | RInt n ->
- pr " struct guestfs_%s_ret ret;\n" name;
- pr " ret.%s = r;\n" n;
- pr " reply ((xdrproc_t) &xdr_guestfs_%s_ret, (char *) &ret);\n" name
- | RBool n ->
- pr " struct guestfs_%s_ret ret;\n" name;
- pr " ret.%s = r;\n" n;
- pr " reply ((xdrproc_t) &xdr_guestfs_%s_ret, (char *) &ret);\n" name
- | RConstString _ ->
- failwithf "RConstString cannot be returned from a daemon function"
- | RString n ->
- pr " struct guestfs_%s_ret ret;\n" name;
- pr " ret.%s = r;\n" n;
- pr " reply ((xdrproc_t) &xdr_guestfs_%s_ret, (char *) &ret);\n" name;
- pr " free (r);\n"
- | RStringList n ->
- pr " struct guestfs_%s_ret ret;\n" name;
- pr " ret.%s.%s_len = count_strings (r);\n" n n;
- pr " ret.%s.%s_val = r;\n" n n;
- pr " reply ((xdrproc_t) &xdr_guestfs_%s_ret, (char *) &ret);\n" name;
- pr " free_strings (r);\n"
- | RIntBool _ ->
- pr " reply ((xdrproc_t) xdr_guestfs_%s_ret, (char *) r);\n" name;
- pr " xdr_free ((xdrproc_t) xdr_guestfs_%s_ret, (char *) r);\n" name
- | RPVList n ->
- pr " struct guestfs_%s_ret ret;\n" name;
- pr " ret.%s = *r;\n" n;
- pr " reply ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n" name;
- pr " xdr_free ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n" name
- | RVGList n ->
- pr " struct guestfs_%s_ret ret;\n" name;
- pr " ret.%s = *r;\n" n;
- pr " reply ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n" name;
- pr " xdr_free ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n" name
- | RLVList n ->
- pr " struct guestfs_%s_ret ret;\n" name;
- pr " ret.%s = *r;\n" n;
- pr " reply ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n" name;
- pr " xdr_free ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n" name
- );
+You can use either C<guestfs_tune2fs_l> or C<guestfs_get_e2uuid>
+to return the existing UUID of a filesystem.");
- (* Free the args. *)
- (match snd style with
- | [] ->
- pr "done: ;\n";
- | _ ->
- pr "done:\n";
- pr " xdr_free ((xdrproc_t) xdr_guestfs_%s_args, (char *) &args);\n"
- name
- );
+ ("get_e2uuid", (RString "uuid", [String "device"]), 83, [],
+ [],
+ "get the ext2/3/4 filesystem UUID",
+ "\
+This returns the ext2/3/4 filesystem UUID of the filesystem on
+C<device>.");
+
+ ("fsck", (RInt "status", [String "fstype"; String "device"]), 84, [],
+ [InitBasicFS, Always, TestOutputInt (
+ [["umount"; "/dev/sda1"];
+ ["fsck"; "ext2"; "/dev/sda1"]], 0);
+ InitBasicFS, Always, TestOutputInt (
+ [["umount"; "/dev/sda1"];
+ ["zero"; "/dev/sda1"];
+ ["fsck"; "ext2"; "/dev/sda1"]], 8)],
+ "run the filesystem checker",
+ "\
+This runs the filesystem checker (fsck) on C<device> which
+should have filesystem type C<fstype>.
- pr "}\n\n";
- ) daemon_functions;
+The returned integer is the status. See L<fsck(8)> for the
+list of status codes from C<fsck>.
- (* Dispatch function. *)
- pr "void dispatch_incoming_message (XDR *xdr_in)\n";
- pr "{\n";
- pr " switch (proc_nr) {\n";
+Notes:
- List.iter (
- fun (name, style, _, _, _, _, _) ->
- pr " case GUESTFS_PROC_%s:\n" (String.uppercase name);
- pr " %s_stub (xdr_in);\n" name;
- pr " break;\n"
- ) daemon_functions;
+=over 4
- pr " default:\n";
- pr " reply_with_error (\"dispatch_incoming_message: unknown procedure number %%d\", proc_nr);\n";
- pr " }\n";
- pr "}\n";
- pr "\n";
+=item *
- (* LVM columns and tokenization functions. *)
- (* XXX This generates crap code. We should rethink how we
- * do this parsing.
- *)
- List.iter (
- function
- | typ, cols ->
- pr "static const char *lvm_%s_cols = \"%s\";\n"
- typ (String.concat "," (List.map fst cols));
- pr "\n";
+Multiple status codes can be summed together.
- pr "static int lvm_tokenize_%s (char *str, struct guestfs_lvm_int_%s *r)\n" typ typ;
- pr "{\n";
- pr " char *tok, *p, *next;\n";
- pr " int i, j;\n";
- pr "\n";
- (*
- pr " fprintf (stderr, \"%%s: <<%%s>>\\n\", __func__, str);\n";
- pr "\n";
- *)
- pr " if (!str) {\n";
- pr " fprintf (stderr, \"%%s: failed: passed a NULL string\\n\", __func__);\n";
- pr " return -1;\n";
- pr " }\n";
- pr " if (!*str || isspace (*str)) {\n";
- pr " fprintf (stderr, \"%%s: failed: passed a empty string or one beginning with whitespace\\n\", __func__);\n";
- pr " return -1;\n";
- pr " }\n";
- pr " tok = str;\n";
- List.iter (
- fun (name, coltype) ->
- pr " if (!tok) {\n";
- pr " fprintf (stderr, \"%%s: failed: string finished early, around token %%s\\n\", __func__, \"%s\");\n" name;
- pr " return -1;\n";
- pr " }\n";
- pr " p = strchrnul (tok, ',');\n";
- pr " if (*p) next = p+1; else next = NULL;\n";
- pr " *p = '\\0';\n";
- (match coltype with
- | `String ->
- pr " r->%s = strdup (tok);\n" name;
- pr " if (r->%s == NULL) {\n" name;
- pr " perror (\"strdup\");\n";
- pr " return -1;\n";
- pr " }\n"
- | `UUID ->
- pr " for (i = j = 0; i < 32; ++j) {\n";
- pr " if (tok[j] == '\\0') {\n";
- pr " fprintf (stderr, \"%%s: failed to parse UUID from '%%s'\\n\", __func__, tok);\n";
- pr " return -1;\n";
- pr " } else if (tok[j] != '-')\n";
- pr " r->%s[i++] = tok[j];\n" name;
- pr " }\n";
- | `Bytes ->
- pr " if (sscanf (tok, \"%%\"SCNu64, &r->%s) != 1) {\n" name;
- pr " fprintf (stderr, \"%%s: failed to parse size '%%s' from token %%s\\n\", __func__, tok, \"%s\");\n" name;
- pr " return -1;\n";
- pr " }\n";
- | `Int ->
- pr " if (sscanf (tok, \"%%\"SCNi64, &r->%s) != 1) {\n" name;
- pr " fprintf (stderr, \"%%s: failed to parse int '%%s' from token %%s\\n\", __func__, tok, \"%s\");\n" name;
- pr " return -1;\n";
- pr " }\n";
- | `OptPercent ->
- pr " if (tok[0] == '\\0')\n";
- pr " r->%s = -1;\n" name;
- pr " else if (sscanf (tok, \"%%f\", &r->%s) != 1) {\n" name;
- pr " fprintf (stderr, \"%%s: failed to parse float '%%s' from token %%s\\n\", __func__, tok, \"%s\");\n" name;
- pr " return -1;\n";
- pr " }\n";
- );
- pr " tok = next;\n";
- ) cols;
+=item *
- pr " if (tok != NULL) {\n";
- pr " fprintf (stderr, \"%%s: failed: extra tokens at end of string\\n\", __func__);\n";
- pr " return -1;\n";
- pr " }\n";
- pr " return 0;\n";
- pr "}\n";
- pr "\n";
+A non-zero return code can mean \"success\", for example if
+errors have been corrected on the filesystem.
- pr "guestfs_lvm_int_%s_list *\n" typ;
- pr "parse_command_line_%ss (void)\n" typ;
- pr "{\n";
- pr " char *out, *err;\n";
- pr " char *p, *pend;\n";
- pr " int r, i;\n";
- pr " guestfs_lvm_int_%s_list *ret;\n" typ;
- pr " void *newp;\n";
- pr "\n";
- pr " ret = malloc (sizeof *ret);\n";
- pr " if (!ret) {\n";
- pr " reply_with_perror (\"malloc\");\n";
- pr " return NULL;\n";
- pr " }\n";
- pr "\n";
- pr " ret->guestfs_lvm_int_%s_list_len = 0;\n" typ;
- pr " ret->guestfs_lvm_int_%s_list_val = NULL;\n" typ;
- pr "\n";
- pr " r = command (&out, &err,\n";
- pr " \"/sbin/lvm\", \"%ss\",\n" typ;
- pr " \"-o\", lvm_%s_cols, \"--unbuffered\", \"--noheadings\",\n" typ;
- pr " \"--nosuffix\", \"--separator\", \",\", \"--units\", \"b\", NULL);\n";
- pr " if (r == -1) {\n";
- pr " reply_with_error (\"%%s\", err);\n";
- pr " free (out);\n";
- pr " free (err);\n";
- pr " return NULL;\n";
- pr " }\n";
- pr "\n";
- pr " free (err);\n";
- pr "\n";
- pr " /* Tokenize each line of the output. */\n";
- pr " p = out;\n";
- pr " i = 0;\n";
- pr " while (p) {\n";
- pr " pend = strchr (p, '\\n'); /* Get the next line of output. */\n";
- pr " if (pend) {\n";
- pr " *pend = '\\0';\n";
- pr " pend++;\n";
- pr " }\n";
- pr "\n";
- pr " while (*p && isspace (*p)) /* Skip any leading whitespace. */\n";
- pr " p++;\n";
- pr "\n";
- pr " if (!*p) { /* Empty line? Skip it. */\n";
- pr " p = pend;\n";
- pr " continue;\n";
- pr " }\n";
- pr "\n";
- pr " /* Allocate some space to store this next entry. */\n";
- pr " newp = realloc (ret->guestfs_lvm_int_%s_list_val,\n" typ;
- pr " sizeof (guestfs_lvm_int_%s) * (i+1));\n" typ;
- pr " if (newp == NULL) {\n";
- pr " reply_with_perror (\"realloc\");\n";
- pr " free (ret->guestfs_lvm_int_%s_list_val);\n" typ;
- pr " free (ret);\n";
- pr " free (out);\n";
- pr " return NULL;\n";
- pr " }\n";
- pr " ret->guestfs_lvm_int_%s_list_val = newp;\n" typ;
- pr "\n";
- pr " /* Tokenize the next entry. */\n";
- pr " r = lvm_tokenize_%s (p, &ret->guestfs_lvm_int_%s_list_val[i]);\n" typ typ;
- pr " if (r == -1) {\n";
- pr " reply_with_error (\"failed to parse output of '%ss' command\");\n" typ;
- pr " free (ret->guestfs_lvm_int_%s_list_val);\n" typ;
- pr " free (ret);\n";
- pr " free (out);\n";
- pr " return NULL;\n";
- pr " }\n";
- pr "\n";
- pr " ++i;\n";
- pr " p = pend;\n";
- pr " }\n";
- pr "\n";
- pr " ret->guestfs_lvm_int_%s_list_len = i;\n" typ;
- pr "\n";
- pr " free (out);\n";
- pr " return ret;\n";
- pr "}\n"
+=item *
- ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols]
+Checking or repairing NTFS volumes is not supported
+(by linux-ntfs).
-(* Generate the tests. *)
-and generate_tests () =
- generate_header CStyle GPLv2;
+=back
- pr "\
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <fcntl.h>
+This command is entirely equivalent to running C<fsck -a -t fstype device>.");
-#include \"guestfs.h\"
+ ("zero", (RErr, [String "device"]), 85, [],
+ [InitBasicFS, Always, TestOutput (
+ [["umount"; "/dev/sda1"];
+ ["zero"; "/dev/sda1"];
+ ["file"; "/dev/sda1"]], "data")],
+ "write zeroes to the device",
+ "\
+This command writes zeroes over the first few blocks of C<device>.
-static guestfs_h *g;
-static int suppress_error = 0;
+How many blocks are zeroed isn't specified (but it's I<not> enough
+to securely wipe the device). It should be sufficient to remove
+any partition tables, filesystem superblocks and so on.");
-static void print_error (guestfs_h *g, void *data, const char *msg)
-{
- if (!suppress_error)
- fprintf (stderr, \"%%s\\n\", msg);
-}
+ ("grub_install", (RErr, [String "root"; String "device"]), 86, [],
+ [InitBasicFS, Always, TestOutputTrue (
+ [["grub_install"; "/"; "/dev/sda1"];
+ ["is_dir"; "/boot"]])],
+ "install GRUB",
+ "\
+This command installs GRUB (the Grand Unified Bootloader) on
+C<device>, with the root directory being C<root>.");
+
+ ("cp", (RErr, [String "src"; String "dest"]), 87, [],
+ [InitBasicFS, Always, TestOutput (
+ [["write_file"; "/old"; "file content"; "0"];
+ ["cp"; "/old"; "/new"];
+ ["cat"; "/new"]], "file content");
+ InitBasicFS, Always, TestOutputTrue (
+ [["write_file"; "/old"; "file content"; "0"];
+ ["cp"; "/old"; "/new"];
+ ["is_file"; "/old"]]);
+ InitBasicFS, Always, TestOutput (
+ [["write_file"; "/old"; "file content"; "0"];
+ ["mkdir"; "/dir"];
+ ["cp"; "/old"; "/dir/new"];
+ ["cat"; "/dir/new"]], "file content")],
+ "copy a file",
+ "\
+This copies a file from C<src> to C<dest> where C<dest> is
+either a destination filename or destination directory.");
+
+ ("cp_a", (RErr, [String "src"; String "dest"]), 88, [],
+ [InitBasicFS, Always, TestOutput (
+ [["mkdir"; "/olddir"];
+ ["mkdir"; "/newdir"];
+ ["write_file"; "/olddir/file"; "file content"; "0"];
+ ["cp_a"; "/olddir"; "/newdir"];
+ ["cat"; "/newdir/olddir/file"]], "file content")],
+ "copy a file or directory recursively",
+ "\
+This copies a file or directory from C<src> to C<dest>
+recursively using the C<cp -a> command.");
+
+ ("mv", (RErr, [String "src"; String "dest"]), 89, [],
+ [InitBasicFS, Always, TestOutput (
+ [["write_file"; "/old"; "file content"; "0"];
+ ["mv"; "/old"; "/new"];
+ ["cat"; "/new"]], "file content");
+ InitBasicFS, Always, TestOutputFalse (
+ [["write_file"; "/old"; "file content"; "0"];
+ ["mv"; "/old"; "/new"];
+ ["is_file"; "/old"]])],
+ "move a file",
+ "\
+This moves a file from C<src> to C<dest> where C<dest> is
+either a destination filename or destination directory.");
-static void print_strings (char * const * const argv)
-{
- int argc;
+ ("drop_caches", (RErr, [Int "whattodrop"]), 90, [],
+ [InitEmpty, Always, TestRun (
+ [["drop_caches"; "3"]])],
+ "drop kernel page cache, dentries and inodes",
+ "\
+This instructs the guest kernel to drop its page cache,
+and/or dentries and inode caches. The parameter C<whattodrop>
+tells the kernel what precisely to drop, see
+L<http://linux-mm.org/Drop_Caches>
- for (argc = 0; argv[argc] != NULL; ++argc)
- printf (\"\\t%%s\\n\", argv[argc]);
-}
+Setting C<whattodrop> to 3 should drop everything.
-";
+This automatically calls L<sync(2)> before the operation,
+so that the maximum guest memory is freed.");
- let test_names =
- List.map (
- fun (name, _, _, _, tests, _, _) ->
- mapi (generate_one_test name) tests
- ) all_functions in
- let test_names = List.concat test_names in
- let nr_tests = List.length test_names in
+ ("dmesg", (RString "kmsgs", []), 91, [],
+ [InitEmpty, Always, TestRun (
+ [["dmesg"]])],
+ "return kernel messages",
+ "\
+This returns the kernel messages (C<dmesg> output) from
+the guest kernel. This is sometimes useful for extended
+debugging of problems.
+
+Another way to get the same information is to enable
+verbose messages with C<guestfs_set_verbose> or by setting
+the environment variable C<LIBGUESTFS_DEBUG=1> 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<file1> and C<file2> and returns
+true if their content is exactly equal, or false otherwise.
- pr "\
-int main (int argc, char *argv[])
-{
- char c = 0;
- int failed = 0;
- const char *srcdir;
- int fd;
- char buf[256];
+The external L<cmp(1)> program is used for the comparison.");
- g = guestfs_create ();
- if (g == NULL) {
- printf (\"guestfs_create FAILED\\n\");
- exit (1);
- }
+ ("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<strings(1)> command on a file and returns
+the list of printable strings found.");
+
+ ("strings_e", (RStringList "stringsout", [String "encoding"; String "path"]), 95, [ProtocolLimitWarning],
+ [InitBasicFS, Always, TestOutputList (
+ [["write_file"; "/new"; "hello\nworld\n"; "0"];
+ ["strings_e"; "b"; "/new"]], []);
+ 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<guestfs_strings> command, but allows you to
+specify the encoding.
- guestfs_set_error_handler (g, print_error, NULL);
+See the L<strings(1)> manpage for the full list of encodings.
- srcdir = getenv (\"srcdir\");
- if (!srcdir) srcdir = \".\";
- guestfs_set_path (g, srcdir);
+Commonly useful encodings are C<l> (lower case L) which will
+show strings inside Windows/x86 files.
- snprintf (buf, sizeof buf, \"%%s/test1.img\", srcdir);
- fd = open (buf, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK|O_TRUNC, 0666);
- if (fd == -1) {
- perror (buf);
- exit (1);
- }
- if (lseek (fd, %d, SEEK_SET) == -1) {
- perror (\"lseek\");
- close (fd);
- unlink (buf);
- exit (1);
- }
- if (write (fd, &c, 1) == -1) {
- perror (\"write\");
- close (fd);
- unlink (buf);
- exit (1);
- }
- if (close (fd) == -1) {
- perror (buf);
- unlink (buf);
- exit (1);
- }
- if (guestfs_add_drive (g, buf) == -1) {
- printf (\"guestfs_add_drive %%s FAILED\\n\", buf);
- exit (1);
- }
+The returned strings are transcoded to UTF-8.");
- snprintf (buf, sizeof buf, \"%%s/test2.img\", srcdir);
- fd = open (buf, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK|O_TRUNC, 0666);
- if (fd == -1) {
- perror (buf);
- exit (1);
- }
- if (lseek (fd, %d, SEEK_SET) == -1) {
- perror (\"lseek\");
- close (fd);
- unlink (buf);
- exit (1);
- }
- if (write (fd, &c, 1) == -1) {
- perror (\"write\");
- close (fd);
- unlink (buf);
- exit (1);
- }
- if (close (fd) == -1) {
- perror (buf);
- unlink (buf);
- exit (1);
- }
- if (guestfs_add_drive (g, buf) == -1) {
- printf (\"guestfs_add_drive %%s FAILED\\n\", buf);
- exit (1);
- }
+ ("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<hexdump -C> on the given C<path>. The result is
+the human-readable, canonical hex dump of the file.");
- snprintf (buf, sizeof buf, \"%%s/test3.img\", srcdir);
- fd = open (buf, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK|O_TRUNC, 0666);
- if (fd == -1) {
- perror (buf);
- exit (1);
- }
- if (lseek (fd, %d, SEEK_SET) == -1) {
- perror (\"lseek\");
- close (fd);
- unlink (buf);
- exit (1);
- }
- if (write (fd, &c, 1) == -1) {
- perror (\"write\");
- close (fd);
- unlink (buf);
- exit (1);
- }
- if (close (fd) == -1) {
- perror (buf);
- unlink (buf);
- exit (1);
- }
- if (guestfs_add_drive (g, buf) == -1) {
- printf (\"guestfs_add_drive %%s FAILED\\n\", buf);
- exit (1);
- }
+ ("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<zerofree> program on C<device>. 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.
- if (guestfs_launch (g) == -1) {
- printf (\"guestfs_launch FAILED\\n\");
- exit (1);
- }
- if (guestfs_wait_ready (g) == -1) {
- printf (\"guestfs_wait_ready FAILED\\n\");
- exit (1);
- }
+You should B<not> run this program if the filesystem is
+mounted.
-" (500 * 1024 * 1024) (50 * 1024 * 1024) (10 * 1024 * 1024);
+It is possible that using this program can damage the filesystem
+or data on the filesystem.");
- iteri (
- fun i test_name ->
- pr " printf (\"%3d/%3d %s\\n\");\n" (i+1) nr_tests test_name;
- pr " if (%s () == -1) {\n" test_name;
- pr " printf (\"%s FAILED\\n\");\n" test_name;
- pr " failed++;\n";
- pr " }\n";
- ) test_names;
- pr "\n";
+ ("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.");
- pr " guestfs_close (g);\n";
- pr " snprintf (buf, sizeof buf, \"%%s/test1.img\", srcdir);\n";
- pr " unlink (buf);\n";
- pr " snprintf (buf, sizeof buf, \"%%s/test2.img\", srcdir);\n";
- pr " unlink (buf);\n";
- pr " snprintf (buf, sizeof buf, \"%%s/test3.img\", srcdir);\n";
- pr " unlink (buf);\n";
- pr "\n";
+ ("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<sfdisk(8)> option to modify just the single
+partition C<n> (note: C<n> counts from 1).
- pr " if (failed > 0) {\n";
- pr " printf (\"***** %%d / %d tests FAILED *****\\n\", failed);\n"
- nr_tests;
- pr " exit (1);\n";
- pr " }\n";
- pr "\n";
+For other parameters, see C<guestfs_sfdisk>. You should usually
+pass C<0> for the cyls/heads/sectors parameters.");
- pr " exit (0);\n";
- pr "}\n"
+ ("sfdisk_l", (RString "partitions", [String "device"]), 100, [],
+ [],
+ "display the partition table",
+ "\
+This displays the partition table on C<device>, in the
+human-readable output of the L<sfdisk(8)> command. It is
+not intended to be parsed.");
-and generate_one_test name i (init, test) =
- let test_name = sprintf "test_%s_%d" name i in
+ ("sfdisk_kernel_geometry", (RString "partitions", [String "device"]), 101, [],
+ [],
+ "display the kernel geometry",
+ "\
+This displays the kernel's idea of the geometry of C<device>.
- pr "static int %s (void)\n" test_name;
- pr "{\n";
+The result is in human-readable format, and not designed to
+be parsed.");
- (match init with
- | InitNone -> ()
- | InitEmpty ->
- pr " /* InitEmpty for %s (%d) */\n" name i;
- List.iter (generate_test_command_call test_name)
- [["umount_all"];
- ["lvm_remove_all"]]
- | InitBasicFS ->
- pr " /* InitBasicFS for %s (%d): create ext2 on /dev/sda1 */\n" name i;
- List.iter (generate_test_command_call test_name)
- [["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;
- List.iter (generate_test_command_call test_name)
- [["umount_all"];
- ["lvm_remove_all"];
- ["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","];
- ["pvcreate"; "/dev/sda1"];
- ["vgcreate"; "VG"; "/dev/sda1"];
- ["lvcreate"; "LV"; "VG"; "8"];
- ["mkfs"; "ext2"; "/dev/VG/LV"];
- ["mount"; "/dev/VG/LV"; "/"]]
- );
+ ("sfdisk_disk_geometry", (RString "partitions", [String "device"]), 102, [],
+ [],
+ "display the disk geometry from the partition table",
+ "\
+This displays the disk geometry of C<device> 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<guestfs_sfdisk_kernel_geometry>).
- let get_seq_last = function
- | [] ->
- failwithf "%s: you cannot use [] (empty list) when expecting a command"
- test_name
- | seq ->
- let seq = List.rev seq in
- List.rev (List.tl seq), List.hd seq
- in
+The result is in human-readable format, and not designed to
+be parsed.");
- (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\", r);\n"
- test_name 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
- | 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
- | 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
- );
+ ("vg_activate_all", (RErr, [Bool "activate"]), 103, [],
+ [],
+ "activate or deactivate all volume groups",
+ "\
+This command activates or (if C<activate> 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</dev/mapper> devices. If deactivated,
+then those devices disappear.
- pr " return 0;\n";
- pr "}\n";
- pr "\n";
- test_name
+This command is the same as running C<vgchange -a y|n>");
-(* 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.
- *)
-and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
- match cmd with
- | [] -> assert false
- | name :: args ->
- (* Look up the command to find out what args/ret it has. *)
- let style =
- try
- let _, style, _, _, _, _, _ =
- List.find (fun (n, _, _, _, _, _, _) -> n = name) all_functions in
- style
- with Not_found ->
- failwithf "%s: in test, command %s was not found" test_name name in
+ ("vg_activate", (RErr, [Bool "activate"; StringList "volgroups"]), 104, [],
+ [],
+ "activate or deactivate some volume groups",
+ "\
+This command activates or (if C<activate> is false) deactivates
+all logical volumes in the listed volume groups C<volgroups>.
+If activated, then they are made known to the
+kernel, ie. they appear as C</dev/mapper> devices. If deactivated,
+then those devices disappear.
+
+This command is the same as running C<vgchange -a y|n volgroups...>
+
+Note that if C<volgroups> is an empty list then B<all> 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<mbytes>. When reducing, data in the reduced part
+is lost.");
- if List.length (snd style) <> List.length args then
- failwithf "%s: in test, wrong number of args given to %s"
- test_name name;
+ ("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<Note:> It is sometimes required that you run C<guestfs_e2fsck_f>
+on the C<device> before calling this command. For unknown reasons
+C<resize2fs> sometimes gives an error about this and sometimes not.
+In any case, it is always safe to call C<guestfs_e2fsck_f> 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<directory>. It is essentially equivalent to
+running the shell command C<find directory -print> but some
+post-processing happens on the output, described below.
- pr " {\n";
+This returns a list of strings I<without any prefix>. Thus
+if the directory structure was:
- List.iter (
- function
- | String _, _
- | OptString _, _
- | Int _, _
- | Bool _, _ -> ()
- | StringList n, arg ->
- pr " char *%s[] = {\n" n;
- let strs = string_split " " arg in
- List.iter (
- fun str -> pr " \"%s\",\n" (c_quote str)
- ) strs;
- pr " NULL\n";
- pr " };\n";
- ) (List.combine (snd style) args);
+ /tmp/a
+ /tmp/b
+ /tmp/c/d
- let error_code =
- match fst style with
- | RErr | RInt _ | RBool _ -> pr " int r;\n"; "-1"
- | RConstString _ -> pr " const char *r;\n"; "NULL"
- | RString _ -> pr " char *r;\n"; "NULL"
- | RStringList _ ->
- pr " char **r;\n";
- pr " int i;\n";
- "NULL"
- | RIntBool _ ->
- pr " struct guestfs_int_bool *r;\n";
- "NULL"
- | RPVList _ ->
- pr " struct guestfs_lvm_pv_list *r;\n";
- "NULL"
- | RVGList _ ->
- pr " struct guestfs_lvm_vg_list *r;\n";
- "NULL"
- | RLVList _ ->
- pr " struct guestfs_lvm_lv_list *r;\n";
- "NULL" in
+then the returned list from C<guestfs_find> C</tmp> would be
+4 elements:
- pr " suppress_error = %d;\n" (if expect_error then 1 else 0);
- pr " r = guestfs_%s (g" name;
+ a
+ b
+ c
+ c/d
- (* Generate the parameters. *)
- List.iter (
- function
- | String _, 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 ->
- let i =
- try int_of_string arg
- with Failure "int_of_string" ->
- failwithf "%s: expecting an int, but got '%s'" test_name arg in
- pr ", %d" i
- | Bool _, arg ->
- let b = bool_of_string arg in pr ", %d" (if b then 1 else 0)
- ) (List.combine (snd style) args);
+If C<directory> is not a directory, then this command returns
+an error.
- pr ");\n";
- if not expect_error then
- pr " if (r == %s)\n" error_code
- else
- pr " if (r != %s)\n" error_code;
- pr " return -1;\n";
+The returned list is sorted.");
- (* Insert the test code. *)
- (match test with
- | None -> ()
- | Some f -> f ()
- );
+ ("e2fsck_f", (RErr, [String "device"]), 108, [],
+ [], (* lvresize tests this *)
+ "check an ext2/ext3 filesystem",
+ "\
+This runs C<e2fsck -p -f device>, ie. runs the ext2/ext3
+filesystem checker on C<device>, noninteractively (C<-p>),
+even if the filesystem appears to be clean (C<-f>).
- (match fst style with
- | RErr | RInt _ | RBool _ | RConstString _ -> ()
- | RString _ -> pr " free (r);\n"
- | RStringList _ ->
- pr " for (i = 0; r[i] != NULL; ++i)\n";
- pr " free (r[i]);\n";
- pr " free (r);\n"
- | RIntBool _ ->
- pr " guestfs_free_int_bool (r);\n"
- | RPVList _ ->
- pr " guestfs_free_lvm_pv_list (r);\n"
- | RVGList _ ->
- pr " guestfs_free_lvm_vg_list (r);\n"
- | RLVList _ ->
- pr " guestfs_free_lvm_lv_list (r);\n"
- );
+This command is only needed because of C<guestfs_resize2fs>
+(q.v.). Normally you should use C<guestfs_fsck>.");
- pr " }\n"
+ ("sleep", (RErr, [Int "secs"]), 109, [],
+ [InitNone, Always, TestRun (
+ [["sleep"; "1"]])],
+ "sleep for some seconds",
+ "\
+Sleep for C<secs> seconds.");
-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
- str
+ ("ntfs_3g_probe", (RInt "status", [Bool "rw"; String "device"]), 110, [],
+ [InitNone, Always, TestOutputInt (
+ [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","];
+ ["mkfs"; "ntfs"; "/dev/sda1"];
+ ["ntfs_3g_probe"; "true"; "/dev/sda1"]], 0);
+ InitNone, Always, TestOutputInt (
+ [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","];
+ ["mkfs"; "ext2"; "/dev/sda1"];
+ ["ntfs_3g_probe"; "true"; "/dev/sda1"]], 12)],
+ "probe NTFS volume",
+ "\
+This command runs the L<ntfs-3g.probe(8)> command which probes
+an NTFS C<device> for mountability. (Not all NTFS volumes can
+be mounted read-write, and some cannot be mounted at all).
-(* Generate a lot of different functions for guestfish. *)
-and generate_fish_cmds () =
- generate_header CStyle GPLv2;
+C<rw> is a boolean flag. Set it to true if you want to test
+if the volume can be mounted read-write. Set it to false if
+you want to test if the volume can be mounted read-only.
- let all_functions =
- List.filter (
- fun (_, _, _, flags, _, _, _) -> not (List.mem NotInFish flags)
- ) all_functions in
- let all_functions_sorted =
- List.filter (
- fun (_, _, _, flags, _, _, _) -> not (List.mem NotInFish flags)
- ) all_functions_sorted in
+The return value is an integer which C<0> if the operation
+would succeed, or some non-zero value documented in the
+L<ntfs-3g.probe(8)> manual page.");
- pr "#include <stdio.h>\n";
- pr "#include <stdlib.h>\n";
- pr "#include <string.h>\n";
- pr "#include <inttypes.h>\n";
- pr "\n";
- pr "#include <guestfs.h>\n";
- pr "#include \"fish.h\"\n";
- pr "\n";
+]
- (* list_commands function, which implements guestfish -h *)
- pr "void list_commands (void)\n";
- pr "{\n";
- pr " printf (\" %%-16s %%s\\n\", \"Command\", \"Description\");\n";
- pr " list_builtin_commands ();\n";
- List.iter (
- fun (name, _, _, flags, _, shortdesc, _) ->
- let name = replace_char name '_' '-' in
- pr " printf (\"%%-20s %%s\\n\", \"%s\", \"%s\");\n"
- name shortdesc
- ) all_functions_sorted;
- pr " printf (\" Use -h <cmd> / help <cmd> to show detailed help for a command.\\n\");\n";
- pr "}\n";
- pr "\n";
+let all_functions = non_daemon_functions @ daemon_functions
- (* display_command function, which implements guestfish -h cmd *)
- pr "void display_command (const char *cmd)\n";
- pr "{\n";
- List.iter (
- fun (name, style, _, flags, _, shortdesc, longdesc) ->
- let name2 = replace_char name '_' '-' in
- let alias =
- try find_map (function FishAlias n -> Some n | _ -> None) flags
- with Not_found -> name in
- let longdesc = replace_str longdesc "C<guestfs_" "C<" in
- let synopsis =
- match snd style with
- | [] -> name2
- | args ->
- sprintf "%s <%s>"
- name2 (String.concat "> <" (List.map name_of_argt args)) in
+(* In some places we want the functions to be displayed sorted
+ * alphabetically, so this is useful:
+ *)
+let all_functions_sorted =
+ List.sort (fun (n1,_,_,_,_,_,_) (n2,_,_,_,_,_,_) ->
+ compare n1 n2) all_functions
- let warnings =
- if List.mem ProtocolLimitWarning flags then
- ("\n\n" ^ protocol_limit_warning)
- else "" in
+(* Column names and types from LVM PVs/VGs/LVs. *)
+let pv_cols = [
+ "pv_name", `String;
+ "pv_uuid", `UUID;
+ "pv_fmt", `String;
+ "pv_size", `Bytes;
+ "dev_size", `Bytes;
+ "pv_free", `Bytes;
+ "pv_used", `Bytes;
+ "pv_attr", `String (* XXX *);
+ "pv_pe_count", `Int;
+ "pv_pe_alloc_count", `Int;
+ "pv_tags", `String;
+ "pe_start", `Bytes;
+ "pv_mda_count", `Int;
+ "pv_mda_free", `Bytes;
+(* Not in Fedora 10:
+ "pv_mda_size", `Bytes;
+*)
+]
+let vg_cols = [
+ "vg_name", `String;
+ "vg_uuid", `UUID;
+ "vg_fmt", `String;
+ "vg_attr", `String (* XXX *);
+ "vg_size", `Bytes;
+ "vg_free", `Bytes;
+ "vg_sysid", `String;
+ "vg_extent_size", `Bytes;
+ "vg_extent_count", `Int;
+ "vg_free_count", `Int;
+ "max_lv", `Int;
+ "max_pv", `Int;
+ "pv_count", `Int;
+ "lv_count", `Int;
+ "snap_count", `Int;
+ "vg_seqno", `Int;
+ "vg_tags", `String;
+ "vg_mda_count", `Int;
+ "vg_mda_free", `Bytes;
+(* Not in Fedora 10:
+ "vg_mda_size", `Bytes;
+*)
+]
+let lv_cols = [
+ "lv_name", `String;
+ "lv_uuid", `UUID;
+ "lv_attr", `String (* XXX *);
+ "lv_major", `Int;
+ "lv_minor", `Int;
+ "lv_kernel_major", `Int;
+ "lv_kernel_minor", `Int;
+ "lv_size", `Bytes;
+ "seg_count", `Int;
+ "origin", `String;
+ "snap_percent", `OptPercent;
+ "copy_percent", `OptPercent;
+ "move_pv", `String;
+ "lv_tags", `String;
+ "mirror_log", `String;
+ "modules", `String;
+]
- (* For DangerWillRobinson commands, we should probably have
- * guestfish prompt before allowing you to use them (especially
- * in interactive mode). XXX
- *)
- let warnings =
- warnings ^
- if List.mem DangerWillRobinson flags then
- ("\n\n" ^ danger_will_robinson)
- else "" in
+(* Column names and types from stat structures.
+ * NB. Can't use things like 'st_atime' because glibc header files
+ * define some of these as macros. Ugh.
+ *)
+let stat_cols = [
+ "dev", `Int;
+ "ino", `Int;
+ "mode", `Int;
+ "nlink", `Int;
+ "uid", `Int;
+ "gid", `Int;
+ "rdev", `Int;
+ "size", `Int;
+ "blksize", `Int;
+ "blocks", `Int;
+ "atime", `Int;
+ "mtime", `Int;
+ "ctime", `Int;
+]
+let statvfs_cols = [
+ "bsize", `Int;
+ "frsize", `Int;
+ "blocks", `Int;
+ "bfree", `Int;
+ "bavail", `Int;
+ "files", `Int;
+ "ffree", `Int;
+ "favail", `Int;
+ "fsid", `Int;
+ "flag", `Int;
+ "namemax", `Int;
+]
- let describe_alias =
- if name <> alias then
- sprintf "\n\nYou can use '%s' as an alias for this command." alias
- else "" in
+(* Used for testing language bindings. *)
+type callt =
+ | CallString of string
+ | CallOptString of string option
+ | CallStringList of string list
+ | CallInt of int
+ | CallBool of bool
- pr " if (";
- pr "strcasecmp (cmd, \"%s\") == 0" name;
- if name <> name2 then
- pr " || strcasecmp (cmd, \"%s\") == 0" name2;
- if name <> alias then
- pr " || strcasecmp (cmd, \"%s\") == 0" alias;
- pr ")\n";
- pr " pod2text (\"%s - %s\", %S);\n"
- name2 shortdesc
- (" " ^ synopsis ^ "\n\n" ^ longdesc ^ warnings ^ describe_alias);
- pr " else\n"
- ) all_functions;
- pr " display_builtin_command (cmd);\n";
- pr "}\n";
- pr "\n";
+(* Useful functions.
+ * Note we don't want to use any external OCaml libraries which
+ * makes this a bit harder than it should be.
+ *)
+let failwithf fs = ksprintf failwith fs
- (* print_{pv,vg,lv}_list functions *)
- List.iter (
- function
- | typ, cols ->
- pr "static void print_%s (struct guestfs_lvm_%s *%s)\n" typ typ typ;
- pr "{\n";
- pr " int i;\n";
- pr "\n";
- List.iter (
- function
- | name, `String ->
+let replace_char s c1 c2 =
+ let s2 = String.copy s in
+ let r = ref false in
+ for i = 0 to String.length s2 - 1 do
+ if String.unsafe_get s2 i = c1 then (
+ String.unsafe_set s2 i c2;
+ r := true
+ )
+ done;
+ if not !r then s else s2
+
+let isspace c =
+ c = ' '
+ (* || c = '\f' *) || c = '\n' || c = '\r' || c = '\t' (* || c = '\v' *)
+
+let triml ?(test = isspace) str =
+ let i = ref 0 in
+ let n = ref (String.length str) in
+ while !n > 0 && test str.[!i]; do
+ decr n;
+ incr i
+ done;
+ if !i = 0 then str
+ else String.sub str !i !n
+
+let trimr ?(test = isspace) str =
+ let n = ref (String.length str) in
+ while !n > 0 && test str.[!n-1]; do
+ decr n
+ done;
+ if !n = String.length str then str
+ else String.sub str 0 !n
+
+let trim ?(test = isspace) str =
+ trimr ~test (triml ~test str)
+
+let rec find s sub =
+ let len = String.length s in
+ let sublen = String.length sub in
+ let rec loop i =
+ if i <= len-sublen then (
+ let rec loop2 j =
+ if j < sublen then (
+ if s.[i+j] = sub.[j] then loop2 (j+1)
+ else -1
+ ) else
+ i (* found *)
+ in
+ let r = loop2 0 in
+ if r = -1 then loop (i+1) else r
+ ) else
+ -1 (* not found *)
+ in
+ loop 0
+
+let rec replace_str s s1 s2 =
+ let len = String.length s in
+ let sublen = String.length s1 in
+ let i = find s s1 in
+ if i = -1 then s
+ else (
+ let s' = String.sub s 0 i in
+ let s'' = String.sub s (i+sublen) (len-i-sublen) in
+ s' ^ s2 ^ replace_str s'' s1 s2
+ )
+
+let rec string_split sep str =
+ let len = String.length str in
+ let seplen = String.length sep in
+ let i = find str sep in
+ if i = -1 then [str]
+ else (
+ let s' = String.sub str 0 i in
+ let s'' = String.sub str (i+seplen) (len-i-seplen) in
+ s' :: string_split sep s''
+ )
+
+let files_equal n1 n2 =
+ let cmd = sprintf "cmp -s %s %s" (Filename.quote n1) (Filename.quote n2) in
+ match Sys.command cmd with
+ | 0 -> true
+ | 1 -> false
+ | i -> failwithf "%s: failed with error code %d" cmd i
+
+let rec find_map f = function
+ | [] -> raise Not_found
+ | x :: xs ->
+ match f x with
+ | Some y -> y
+ | None -> find_map f xs
+
+let iteri f xs =
+ let rec loop i = function
+ | [] -> ()
+ | x :: xs -> f i x; loop (i+1) xs
+ in
+ loop 0 xs
+
+let mapi f xs =
+ let rec loop i = function
+ | [] -> []
+ | x :: xs -> let r = f i x in r :: loop (i+1) xs
+ in
+ loop 0 xs
+
+let name_of_argt = function
+ | String n | OptString n | StringList n | Bool n | Int n
+ | FileIn n | FileOut n -> n
+
+let seq_of_test = function
+ | TestRun s | TestOutput (s, _) | TestOutputList (s, _)
+ | TestOutputInt (s, _) | TestOutputTrue s | TestOutputFalse s
+ | TestOutputLength (s, _) | TestOutputStruct (s, _)
+ | TestLastFail s -> s
+
+(* Check function names etc. for consistency. *)
+let check_functions () =
+ let contains_uppercase str =
+ let len = String.length str in
+ let rec loop i =
+ if i >= len then false
+ else (
+ let c = str.[i] in
+ if c >= 'A' && c <= 'Z' then true
+ else loop (i+1)
+ )
+ in
+ loop 0
+ in
+
+ (* Check function names. *)
+ List.iter (
+ 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 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
+ ) all_functions;
+
+ (* Check function parameter/return names. *)
+ List.iter (
+ fun (name, style, _, _, _, _, _) ->
+ let check_arg_ret_name n =
+ if contains_uppercase n then
+ failwithf "%s param/ret %s should not contain uppercase chars"
+ name n;
+ if String.contains n '-' || String.contains n '_' then
+ failwithf "%s param/ret %s should not contain '-' or '_'"
+ name n;
+ if n = "value" then
+ failwithf "%s has a param/ret called 'value', which causes conflicts in the OCaml bindings, use something like 'val' or a more descriptive name" name;
+ if n = "int" || n = "char" || n = "short" || n = "long" then
+ failwithf "%s has a param/ret which conflicts with a C type (eg. 'int', 'char' etc.)" name;
+ if n = "i" then
+ failwithf "%s has a param/ret called 'i', 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
+ in
+
+ (match fst style with
+ | RErr -> ()
+ | RInt n | RInt64 n | RBool n | RConstString n | RString n
+ | RStringList n | RPVList n | RVGList n | RLVList n
+ | RStat n | RStatVFS n
+ | RHashtable n ->
+ check_arg_ret_name n
+ | RIntBool (n,m) ->
+ check_arg_ret_name n;
+ check_arg_ret_name m
+ );
+ List.iter (fun arg -> check_arg_ret_name (name_of_argt arg)) (snd style)
+ ) all_functions;
+
+ (* Check short descriptions. *)
+ List.iter (
+ fun (name, _, _, _, _, shortdesc, _) ->
+ if shortdesc.[0] <> Char.lowercase shortdesc.[0] then
+ failwithf "short description of %s should begin with lowercase." name;
+ let c = shortdesc.[String.length shortdesc-1] in
+ if c = '\n' || c = '.' then
+ failwithf "short description of %s should not end with . or \\n." name
+ ) all_functions;
+
+ (* Check long dscriptions. *)
+ List.iter (
+ fun (name, _, _, _, _, _, longdesc) ->
+ if longdesc.[String.length longdesc-1] = '\n' then
+ failwithf "long description of %s should not end with \\n." name
+ ) all_functions;
+
+ (* Check proc_nrs. *)
+ List.iter (
+ fun (name, _, proc_nr, _, _, _, _) ->
+ if proc_nr <= 0 then
+ failwithf "daemon function %s should have proc_nr > 0" name
+ ) daemon_functions;
+
+ List.iter (
+ fun (name, _, proc_nr, _, _, _, _) ->
+ if proc_nr <> -1 then
+ failwithf "non-daemon function %s should have proc_nr -1" name
+ ) non_daemon_functions;
+
+ let proc_nrs =
+ List.map (fun (name, _, proc_nr, _, _, _, _) -> name, proc_nr)
+ daemon_functions in
+ let proc_nrs =
+ List.sort (fun (_,nr1) (_,nr2) -> compare nr1 nr2) proc_nrs in
+ let rec loop = function
+ | [] -> ()
+ | [_] -> ()
+ | (name1,nr1) :: ((name2,nr2) :: _ as rest) when nr1 < nr2 ->
+ loop rest
+ | (name1,nr1) :: (name2,nr2) :: _ ->
+ failwithf "%s and %s have conflicting procedure numbers (%d, %d)"
+ name1 name2 nr1 nr2
+ in
+ loop proc_nrs;
+
+ (* Check tests. *)
+ List.iter (
+ function
+ (* Ignore functions that have no tests. We generate a
+ * warning when the user does 'make check' instead.
+ *)
+ | name, _, _, _, [], _, _ -> ()
+ | name, _, _, _, tests, _, _ ->
+ let funcs =
+ List.map (
+ fun (_, _, test) ->
+ match seq_of_test test with
+ | [] ->
+ failwithf "%s has a test containing an empty sequence" name
+ | cmds -> List.map List.hd cmds
+ ) tests in
+ let funcs = List.flatten funcs in
+
+ let tested = List.mem name funcs in
+
+ if not tested then
+ failwithf "function %s has tests but does not test itself" name
+ ) all_functions
+
+(* 'pr' prints to the current output file. *)
+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 | HaskellStyle
+type license = GPLv2 | LGPLv2
+
+let generate_header comment license =
+ let c = match comment with
+ | CStyle -> pr "/* "; " *"
+ | HashStyle -> pr "# "; "#"
+ | 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;
+ pr "%s\n" c;
+ pr "%s Copyright (C) 2009 Red Hat Inc.\n" c;
+ pr "%s\n" c;
+ (match license with
+ | GPLv2 ->
+ pr "%s This program is free software; you can redistribute it and/or modify\n" c;
+ pr "%s it under the terms of the GNU General Public License as published by\n" c;
+ pr "%s the Free Software Foundation; either version 2 of the License, or\n" c;
+ pr "%s (at your option) any later version.\n" c;
+ pr "%s\n" c;
+ pr "%s This program is distributed in the hope that it will be useful,\n" c;
+ pr "%s but WITHOUT ANY WARRANTY; without even the implied warranty of\n" c;
+ pr "%s MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" c;
+ pr "%s GNU General Public License for more details.\n" c;
+ pr "%s\n" c;
+ pr "%s You should have received a copy of the GNU General Public License along\n" c;
+ pr "%s with this program; if not, write to the Free Software Foundation, Inc.,\n" c;
+ pr "%s 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n" c;
+
+ | LGPLv2 ->
+ pr "%s This library is free software; you can redistribute it and/or\n" c;
+ pr "%s modify it under the terms of the GNU Lesser General Public\n" c;
+ pr "%s License as published by the Free Software Foundation; either\n" c;
+ pr "%s version 2 of the License, or (at your option) any later version.\n" c;
+ pr "%s\n" c;
+ pr "%s This library is distributed in the hope that it will be useful,\n" c;
+ pr "%s but WITHOUT ANY WARRANTY; without even the implied warranty of\n" c;
+ pr "%s MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" c;
+ pr "%s Lesser General Public License for more details.\n" c;
+ pr "%s\n" c;
+ pr "%s You should have received a copy of the GNU Lesser General Public\n" c;
+ pr "%s License along with this library; if not, write to the Free Software\n" c;
+ pr "%s Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n" c;
+ );
+ (match comment with
+ | CStyle -> pr " */\n"
+ | HashStyle -> ()
+ | OCamlStyle -> pr " *)\n"
+ | HaskellStyle -> pr "-}\n"
+ );
+ pr "\n"
+
+(* Start of main code generation functions below this line. *)
+
+(* Generate the pod documentation for the C API. *)
+let rec generate_actions_pod () =
+ List.iter (
+ fun (shortname, style, _, flags, _, _, longdesc) ->
+ if not (List.mem NotInDocs flags) then (
+ let name = "guestfs_" ^ shortname in
+ pr "=head2 %s\n\n" name;
+ pr " ";
+ generate_prototype ~extern:false ~handle:"handle" name style;
+ pr "\n\n";
+ pr "%s\n\n" longdesc;
+ (match fst style with
+ | RErr ->
+ pr "This function returns 0 on success or -1 on error.\n\n"
+ | RInt _ ->
+ pr "On error this function returns -1.\n\n"
+ | RInt64 _ ->
+ pr "On error this function returns -1.\n\n"
+ | RBool _ ->
+ pr "This function returns a C truth value on success or -1 on error.\n\n"
+ | RConstString _ ->
+ pr "This function returns a string, or NULL on error.
+The string is owned by the guest handle and must I<not> be freed.\n\n"
+ | RString _ ->
+ pr "This function returns a string, or NULL on error.
+I<The caller must free the returned string after use>.\n\n"
+ | RStringList _ ->
+ pr "This function returns a NULL-terminated array of strings
+(like L<environ(3)>), or NULL if there was an error.
+I<The caller must free the strings and the array after use>.\n\n"
+ | RIntBool _ ->
+ pr "This function returns a C<struct guestfs_int_bool *>,
+or NULL if there was an error.
+I<The caller must call C<guestfs_free_int_bool> after use>.\n\n"
+ | RPVList _ ->
+ pr "This function returns a C<struct guestfs_lvm_pv_list *>
+(see E<lt>guestfs-structs.hE<gt>),
+or NULL if there was an error.
+I<The caller must call C<guestfs_free_lvm_pv_list> after use>.\n\n"
+ | RVGList _ ->
+ pr "This function returns a C<struct guestfs_lvm_vg_list *>
+(see E<lt>guestfs-structs.hE<gt>),
+or NULL if there was an error.
+I<The caller must call C<guestfs_free_lvm_vg_list> after use>.\n\n"
+ | RLVList _ ->
+ pr "This function returns a C<struct guestfs_lvm_lv_list *>
+(see E<lt>guestfs-structs.hE<gt>),
+or NULL if there was an error.
+I<The caller must call C<guestfs_free_lvm_lv_list> after use>.\n\n"
+ | RStat _ ->
+ pr "This function returns a C<struct guestfs_stat *>
+(see L<stat(2)> and E<lt>guestfs-structs.hE<gt>),
+or NULL if there was an error.
+I<The caller must call C<free> after use>.\n\n"
+ | RStatVFS _ ->
+ pr "This function returns a C<struct guestfs_statvfs *>
+(see L<statvfs(2)> and E<lt>guestfs-structs.hE<gt>),
+or NULL if there was an error.
+I<The caller must call C<free> after use>.\n\n"
+ | RHashtable _ ->
+ pr "This function returns a NULL-terminated array of
+strings, or NULL if there was an error.
+The array of strings will always have length C<2n+1>, where
+C<n> keys and values alternate, followed by the trailing NULL entry.
+I<The caller must free the strings and the array after use>.\n\n"
+ );
+ if List.mem ProtocolLimitWarning flags then
+ pr "%s\n\n" protocol_limit_warning;
+ if List.mem DangerWillRobinson flags then
+ pr "%s\n\n" danger_will_robinson
+ )
+ ) all_functions_sorted
+
+and generate_structs_pod () =
+ (* LVM structs documentation. *)
+ List.iter (
+ fun (typ, cols) ->
+ pr "=head2 guestfs_lvm_%s\n" typ;
+ pr "\n";
+ pr " struct guestfs_lvm_%s {\n" typ;
+ List.iter (
+ function
+ | name, `String -> pr " char *%s;\n" name
+ | name, `UUID ->
+ pr " /* The next field is NOT nul-terminated, be careful when printing it: */\n";
+ pr " char %s[32];\n" name
+ | name, `Bytes -> pr " uint64_t %s;\n" name
+ | name, `Int -> pr " int64_t %s;\n" name
+ | name, `OptPercent ->
+ pr " /* The next field is [0..100] or -1 meaning 'not present': */\n";
+ pr " float %s;\n" name
+ ) cols;
+ pr " \n";
+ pr " struct guestfs_lvm_%s_list {\n" typ;
+ pr " uint32_t len; /* Number of elements in list. */\n";
+ pr " struct guestfs_lvm_%s *val; /* Elements. */\n" typ;
+ pr " };\n";
+ pr " \n";
+ pr " void guestfs_free_lvm_%s_list (struct guestfs_free_lvm_%s_list *);\n"
+ typ typ;
+ pr "\n"
+ ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols]
+
+(* Generate the protocol (XDR) file, 'guestfs_protocol.x' and
+ * indirectly 'guestfs_protocol.h' and 'guestfs_protocol.c'.
+ *
+ * We have to use an underscore instead of a dash because otherwise
+ * rpcgen generates incorrect code.
+ *
+ * This header is NOT exported to clients, but see also generate_structs_h.
+ *)
+and generate_xdr () =
+ generate_header CStyle LGPLv2;
+
+ (* This has to be defined to get around a limitation in Sun's rpcgen. *)
+ pr "typedef string str<>;\n";
+ pr "\n";
+
+ (* LVM internal structures. *)
+ List.iter (
+ function
+ | typ, cols ->
+ pr "struct guestfs_lvm_int_%s {\n" typ;
+ List.iter (function
+ | name, `String -> pr " string %s<>;\n" name
+ | name, `UUID -> pr " opaque %s[32];\n" name
+ | name, `Bytes -> pr " hyper %s;\n" name
+ | name, `Int -> pr " hyper %s;\n" name
+ | name, `OptPercent -> pr " float %s;\n" name
+ ) cols;
+ pr "};\n";
+ pr "\n";
+ pr "typedef struct guestfs_lvm_int_%s guestfs_lvm_int_%s_list<>;\n" typ typ;
+ pr "\n";
+ ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
+
+ (* Stat internal structures. *)
+ List.iter (
+ function
+ | typ, cols ->
+ pr "struct guestfs_int_%s {\n" typ;
+ List.iter (function
+ | name, `Int -> pr " hyper %s;\n" name
+ ) cols;
+ pr "};\n";
+ pr "\n";
+ ) ["stat", stat_cols; "statvfs", statvfs_cols];
+
+ List.iter (
+ fun (shortname, style, _, _, _, _, _) ->
+ let name = "guestfs_" ^ shortname in
+
+ (match snd style with
+ | [] -> ()
+ | args ->
+ pr "struct %s_args {\n" name;
+ List.iter (
+ function
+ | String n -> pr " string %s<>;\n" n
+ | OptString n -> pr " str *%s;\n" n
+ | StringList n -> pr " str %s<>;\n" n
+ | Bool n -> pr " bool %s;\n" n
+ | Int n -> pr " int %s;\n" n
+ | FileIn _ | FileOut _ -> ()
+ ) args;
+ pr "};\n\n"
+ );
+ (match fst style with
+ | RErr -> ()
+ | RInt n ->
+ pr "struct %s_ret {\n" name;
+ pr " int %s;\n" n;
+ pr "};\n\n"
+ | RInt64 n ->
+ pr "struct %s_ret {\n" name;
+ pr " hyper %s;\n" n;
+ pr "};\n\n"
+ | RBool n ->
+ pr "struct %s_ret {\n" name;
+ pr " bool %s;\n" n;
+ pr "};\n\n"
+ | RConstString _ ->
+ failwithf "RConstString cannot be returned from a daemon function"
+ | RString n ->
+ pr "struct %s_ret {\n" name;
+ pr " string %s<>;\n" n;
+ pr "};\n\n"
+ | RStringList n ->
+ pr "struct %s_ret {\n" name;
+ pr " str %s<>;\n" n;
+ pr "};\n\n"
+ | RIntBool (n,m) ->
+ pr "struct %s_ret {\n" name;
+ pr " int %s;\n" n;
+ pr " bool %s;\n" m;
+ pr "};\n\n"
+ | RPVList n ->
+ pr "struct %s_ret {\n" name;
+ pr " guestfs_lvm_int_pv_list %s;\n" n;
+ pr "};\n\n"
+ | RVGList n ->
+ pr "struct %s_ret {\n" name;
+ pr " guestfs_lvm_int_vg_list %s;\n" n;
+ pr "};\n\n"
+ | RLVList n ->
+ pr "struct %s_ret {\n" name;
+ pr " guestfs_lvm_int_lv_list %s;\n" n;
+ pr "};\n\n"
+ | RStat n ->
+ pr "struct %s_ret {\n" name;
+ pr " guestfs_int_stat %s;\n" n;
+ pr "};\n\n"
+ | RStatVFS n ->
+ pr "struct %s_ret {\n" name;
+ pr " guestfs_int_statvfs %s;\n" n;
+ pr "};\n\n"
+ | RHashtable n ->
+ pr "struct %s_ret {\n" name;
+ pr " str %s<>;\n" n;
+ pr "};\n\n"
+ );
+ ) daemon_functions;
+
+ (* Table of procedure numbers. *)
+ pr "enum guestfs_procedure {\n";
+ List.iter (
+ fun (shortname, _, proc_nr, _, _, _, _) ->
+ pr " GUESTFS_PROC_%s = %d,\n" (String.uppercase shortname) proc_nr
+ ) daemon_functions;
+ pr " GUESTFS_PROC_NR_PROCS\n";
+ pr "};\n";
+ pr "\n";
+
+ (* Having to choose a maximum message size is annoying for several
+ * reasons (it limits what we can do in the API), but it (a) makes
+ * the protocol a lot simpler, and (b) provides a bound on the size
+ * of the daemon which operates in limited memory space. For large
+ * file transfers you should use FTP.
+ *)
+ pr "const GUESTFS_MESSAGE_MAX = %d;\n" (4 * 1024 * 1024);
+ pr "\n";
+
+ (* Message header, etc. *)
+ pr "\
+/* The communication protocol is now documented in the guestfs(3)
+ * manpage.
+ */
+
+const GUESTFS_PROGRAM = 0x2000F5F5;
+const GUESTFS_PROTOCOL_VERSION = 1;
+
+/* These constants must be larger than any possible message length. */
+const GUESTFS_LAUNCH_FLAG = 0xf5f55ff5;
+const GUESTFS_CANCEL_FLAG = 0xffffeeee;
+
+enum guestfs_message_direction {
+ GUESTFS_DIRECTION_CALL = 0, /* client -> daemon */
+ GUESTFS_DIRECTION_REPLY = 1 /* daemon -> client */
+};
+
+enum guestfs_message_status {
+ GUESTFS_STATUS_OK = 0,
+ GUESTFS_STATUS_ERROR = 1
+};
+
+const GUESTFS_ERROR_LEN = 256;
+
+struct guestfs_message_error {
+ string error_message<GUESTFS_ERROR_LEN>;
+};
+
+struct guestfs_message_header {
+ unsigned prog; /* GUESTFS_PROGRAM */
+ unsigned vers; /* GUESTFS_PROTOCOL_VERSION */
+ guestfs_procedure proc; /* GUESTFS_PROC_x */
+ guestfs_message_direction direction;
+ unsigned serial; /* message serial number */
+ guestfs_message_status status;
+};
+
+const GUESTFS_MAX_CHUNK_SIZE = 8192;
+
+struct guestfs_chunk {
+ int cancel; /* if non-zero, transfer is cancelled */
+ /* data size is 0 bytes if the transfer has finished successfully */
+ opaque data<GUESTFS_MAX_CHUNK_SIZE>;
+};
+"
+
+(* Generate the guestfs-structs.h file. *)
+and generate_structs_h () =
+ generate_header CStyle LGPLv2;
+
+ (* This is a public exported header file containing various
+ * structures. The structures are carefully written to have
+ * exactly the same in-memory format as the XDR structures that
+ * we use on the wire to the daemon. The reason for creating
+ * copies of these structures here is just so we don't have to
+ * export the whole of guestfs_protocol.h (which includes much
+ * unrelated and XDR-dependent stuff that we don't want to be
+ * public, or required by clients).
+ *
+ * To reiterate, we will pass these structures to and from the
+ * client with a simple assignment or memcpy, so the format
+ * must be identical to what rpcgen / the RFC defines.
+ *)
+
+ (* guestfs_int_bool structure. *)
+ pr "struct guestfs_int_bool {\n";
+ pr " int32_t i;\n";
+ pr " int32_t b;\n";
+ pr "};\n";
+ pr "\n";
+
+ (* LVM public structures. *)
+ List.iter (
+ function
+ | typ, cols ->
+ pr "struct guestfs_lvm_%s {\n" typ;
+ List.iter (
+ function
+ | name, `String -> pr " char *%s;\n" name
+ | name, `UUID -> pr " char %s[32]; /* this is NOT nul-terminated, be careful when printing */\n" name
+ | name, `Bytes -> pr " uint64_t %s;\n" name
+ | name, `Int -> pr " int64_t %s;\n" name
+ | name, `OptPercent -> pr " float %s; /* [0..100] or -1 */\n" name
+ ) cols;
+ pr "};\n";
+ pr "\n";
+ pr "struct guestfs_lvm_%s_list {\n" typ;
+ pr " uint32_t len;\n";
+ pr " struct guestfs_lvm_%s *val;\n" typ;
+ pr "};\n";
+ pr "\n"
+ ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
+
+ (* Stat structures. *)
+ List.iter (
+ function
+ | typ, cols ->
+ pr "struct guestfs_%s {\n" typ;
+ List.iter (
+ function
+ | name, `Int -> pr " int64_t %s;\n" name
+ ) cols;
+ pr "};\n";
+ pr "\n"
+ ) ["stat", stat_cols; "statvfs", statvfs_cols]
+
+(* Generate the guestfs-actions.h file. *)
+and generate_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
+ ) all_functions
+
+(* Generate the client-side dispatch stubs. *)
+and generate_client_actions () =
+ generate_header CStyle LGPLv2;
+
+ pr "\
+#include <stdio.h>
+#include <stdlib.h>
+
+#include \"guestfs.h\"
+#include \"guestfs_protocol.h\"
+
+#define error guestfs_error
+#define perrorf guestfs_perrorf
+#define safe_malloc guestfs_safe_malloc
+#define safe_realloc guestfs_safe_realloc
+#define safe_strdup guestfs_safe_strdup
+#define safe_memdup guestfs_safe_memdup
+
+/* Check the return message from a call for validity. */
+static int
+check_reply_header (guestfs_h *g,
+ const struct guestfs_message_header *hdr,
+ int proc_nr, int serial)
+{
+ if (hdr->prog != GUESTFS_PROGRAM) {
+ error (g, \"wrong program (%%d/%%d)\", hdr->prog, GUESTFS_PROGRAM);
+ return -1;
+ }
+ if (hdr->vers != GUESTFS_PROTOCOL_VERSION) {
+ error (g, \"wrong protocol version (%%d/%%d)\",
+ hdr->vers, GUESTFS_PROTOCOL_VERSION);
+ return -1;
+ }
+ if (hdr->direction != GUESTFS_DIRECTION_REPLY) {
+ error (g, \"unexpected message direction (%%d/%%d)\",
+ hdr->direction, GUESTFS_DIRECTION_REPLY);
+ return -1;
+ }
+ if (hdr->proc != proc_nr) {
+ error (g, \"unexpected procedure number (%%d/%%d)\", hdr->proc, proc_nr);
+ return -1;
+ }
+ if (hdr->serial != serial) {
+ error (g, \"unexpected serial (%%d/%%d)\", hdr->serial, serial);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Check we are in the right state to run a high-level action. */
+static int
+check_state (guestfs_h *g, const char *caller)
+{
+ if (!guestfs_is_ready (g)) {
+ if (guestfs_is_config (g))
+ error (g, \"%%s: call launch() before using this function\",
+ caller);
+ else if (guestfs_is_launching (g))
+ error (g, \"%%s: call wait_ready() before using this function\",
+ caller);
+ else
+ error (g, \"%%s called from the wrong state, %%d != READY\",
+ caller, guestfs_get_state (g));
+ return -1;
+ }
+ return 0;
+}
+
+";
+
+ (* Client-side stubs for each function. *)
+ List.iter (
+ fun (shortname, style, _, _, _, _, _) ->
+ let name = "guestfs_" ^ shortname in
+
+ (* Generate the context struct which stores the high-level
+ * state between callback functions.
+ *)
+ pr "struct %s_ctx {\n" shortname;
+ pr " /* This flag is set by the callbacks, so we know we've done\n";
+ pr " * the callbacks as expected, and in the right sequence.\n";
+ pr " * 0 = not called, 1 = reply_cb called.\n";
+ pr " */\n";
+ pr " int cb_sequence;\n";
+ pr " struct guestfs_message_header hdr;\n";
+ pr " struct guestfs_message_error err;\n";
+ (match fst style with
+ | RErr -> ()
+ | RConstString _ ->
+ failwithf "RConstString cannot be returned from a daemon function"
+ | RInt _ | RInt64 _
+ | RBool _ | RString _ | RStringList _
+ | RIntBool _
+ | RPVList _ | RVGList _ | RLVList _
+ | RStat _ | RStatVFS _
+ | RHashtable _ ->
+ pr " struct %s_ret ret;\n" name
+ );
+ pr "};\n";
+ pr "\n";
+
+ (* Generate the reply callback function. *)
+ pr "static void %s_reply_cb (guestfs_h *g, void *data, XDR *xdr)\n" shortname;
+ pr "{\n";
+ pr " guestfs_main_loop *ml = guestfs_get_main_loop (g);\n";
+ pr " struct %s_ctx *ctx = (struct %s_ctx *) data;\n" shortname shortname;
+ pr "\n";
+ pr " /* This should definitely not happen. */\n";
+ pr " if (ctx->cb_sequence != 0) {\n";
+ pr " ctx->cb_sequence = 9999;\n";
+ pr " error (g, \"%%s: internal error: reply callback called twice\", \"%s\");\n" name;
+ pr " return;\n";
+ pr " }\n";
+ pr "\n";
+ pr " ml->main_loop_quit (ml, g);\n";
+ pr "\n";
+ pr " if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {\n";
+ pr " error (g, \"%%s: failed to parse reply header\", \"%s\");\n" name;
+ pr " return;\n";
+ pr " }\n";
+ pr " if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {\n";
+ pr " if (!xdr_guestfs_message_error (xdr, &ctx->err)) {\n";
+ pr " error (g, \"%%s: failed to parse reply error\", \"%s\");\n"
+ name;
+ pr " return;\n";
+ pr " }\n";
+ pr " goto done;\n";
+ pr " }\n";
+
+ (match fst style with
+ | RErr -> ()
+ | RConstString _ ->
+ failwithf "RConstString cannot be returned from a daemon function"
+ | RInt _ | RInt64 _
+ | RBool _ | RString _ | RStringList _
+ | RIntBool _
+ | RPVList _ | RVGList _ | RLVList _
+ | RStat _ | RStatVFS _
+ | RHashtable _ ->
+ pr " if (!xdr_%s_ret (xdr, &ctx->ret)) {\n" name;
+ pr " error (g, \"%%s: failed to parse reply\", \"%s\");\n" name;
+ pr " return;\n";
+ pr " }\n";
+ );
+
+ pr " done:\n";
+ pr " ctx->cb_sequence = 1;\n";
+ pr "}\n\n";
+
+ (* Generate the action stub. *)
+ generate_prototype ~extern:false ~semicolon:false ~newline:true
+ ~handle:"g" name style;
+
+ let error_code =
+ match fst style with
+ | RErr | RInt _ | RInt64 _ | RBool _ -> "-1"
+ | RConstString _ ->
+ failwithf "RConstString cannot be returned from a daemon function"
+ | RString _ | RStringList _ | RIntBool _
+ | RPVList _ | RVGList _ | RLVList _
+ | RStat _ | RStatVFS _
+ | RHashtable _ ->
+ "NULL" in
+
+ pr "{\n";
+
+ (match snd style with
+ | [] -> ()
+ | _ -> pr " struct %s_args args;\n" name
+ );
+
+ pr " struct %s_ctx ctx;\n" shortname;
+ pr " guestfs_main_loop *ml = guestfs_get_main_loop (g);\n";
+ pr " int serial;\n";
+ pr "\n";
+ pr " if (check_state (g, \"%s\") == -1) return %s;\n" name error_code;
+ pr " guestfs_set_busy (g);\n";
+ pr "\n";
+ pr " memset (&ctx, 0, sizeof ctx);\n";
+ pr "\n";
+
+ (* Send the main header and arguments. *)
+ (match snd style with
+ | [] ->
+ pr " serial = guestfs__send_sync (g, GUESTFS_PROC_%s, NULL, NULL);\n"
+ (String.uppercase shortname)
+ | args ->
+ List.iter (
+ function
+ | String n ->
+ pr " args.%s = (char *) %s;\n" n n
+ | OptString n ->
+ pr " args.%s = %s ? (char **) &%s : NULL;\n" n n n
+ | StringList n ->
+ pr " args.%s.%s_val = (char **) %s;\n" n n n;
+ pr " for (args.%s.%s_len = 0; %s[args.%s.%s_len]; args.%s.%s_len++) ;\n" n n n n n n n;
+ | Bool n ->
+ pr " args.%s = %s;\n" n n
+ | Int n ->
+ pr " args.%s = %s;\n" n n
+ | FileIn _ | FileOut _ -> ()
+ ) args;
+ pr " serial = guestfs__send_sync (g, GUESTFS_PROC_%s,\n"
+ (String.uppercase shortname);
+ pr " (xdrproc_t) xdr_%s_args, (char *) &args);\n"
+ name;
+ );
+ pr " if (serial == -1) {\n";
+ pr " guestfs_end_busy (g);\n";
+ pr " return %s;\n" error_code;
+ pr " }\n";
+ pr "\n";
+
+ (* Send any additional files (FileIn) requested. *)
+ let need_read_reply_label = ref false in
+ List.iter (
+ function
+ | FileIn n ->
+ pr " {\n";
+ pr " int r;\n";
+ pr "\n";
+ pr " r = guestfs__send_file_sync (g, %s);\n" n;
+ pr " if (r == -1) {\n";
+ pr " guestfs_end_busy (g);\n";
+ pr " return %s;\n" error_code;
+ pr " }\n";
+ pr " if (r == -2) /* daemon cancelled */\n";
+ pr " goto read_reply;\n";
+ need_read_reply_label := true;
+ pr " }\n";
+ pr "\n";
+ | _ -> ()
+ ) (snd style);
+
+ (* Wait for the reply from the remote end. *)
+ if !need_read_reply_label then pr " read_reply:\n";
+ pr " guestfs__switch_to_receiving (g);\n";
+ pr " ctx.cb_sequence = 0;\n";
+ pr " guestfs_set_reply_callback (g, %s_reply_cb, &ctx);\n" shortname;
+ pr " (void) ml->main_loop_run (ml, g);\n";
+ 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_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_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 " free (ctx.err.error_message);\n";
+ pr " guestfs_end_busy (g);\n";
+ pr " return %s;\n" error_code;
+ pr " }\n";
+ pr "\n";
+
+ (* Expecting to receive further files (FileOut)? *)
+ List.iter (
+ function
+ | FileOut n ->
+ pr " if (guestfs__receive_file_sync (g, %s) == -1) {\n" n;
+ pr " guestfs_end_busy (g);\n";
+ pr " return %s;\n" error_code;
+ pr " }\n";
+ pr "\n";
+ | _ -> ()
+ ) (snd style);
+
+ pr " guestfs_end_busy (g);\n";
+
+ (match fst style with
+ | RErr -> pr " return 0;\n"
+ | RInt n | RInt64 n | RBool n ->
+ pr " return ctx.ret.%s;\n" n
+ | RConstString _ ->
+ failwithf "RConstString cannot be returned from a daemon function"
+ | RString n ->
+ pr " return ctx.ret.%s; /* caller will free */\n" n
+ | RStringList n | RHashtable n ->
+ pr " /* caller will free this, but we need to add a NULL entry */\n";
+ pr " ctx.ret.%s.%s_val =\n" n n;
+ pr " safe_realloc (g, ctx.ret.%s.%s_val,\n" n n;
+ pr " sizeof (char *) * (ctx.ret.%s.%s_len + 1));\n"
+ n n;
+ pr " ctx.ret.%s.%s_val[ctx.ret.%s.%s_len] = NULL;\n" n n n n;
+ pr " return ctx.ret.%s.%s_val;\n" n n
+ | RIntBool _ ->
+ pr " /* caller with free this */\n";
+ pr " return safe_memdup (g, &ctx.ret, sizeof (ctx.ret));\n"
+ | RPVList n | RVGList n | RLVList n
+ | RStat n | RStatVFS n ->
+ pr " /* caller will free this */\n";
+ pr " return safe_memdup (g, &ctx.ret.%s, sizeof (ctx.ret.%s));\n" n n
+ );
+
+ pr "}\n\n"
+ ) daemon_functions
+
+(* Generate daemon/actions.h. *)
+and generate_daemon_actions_h () =
+ generate_header CStyle GPLv2;
+
+ pr "#include \"../src/guestfs_protocol.h\"\n";
+ pr "\n";
+
+ List.iter (
+ fun (name, style, _, _, _, _, _) ->
+ generate_prototype
+ ~single_line:true ~newline:true ~in_daemon:true ~prefix:"do_"
+ name style;
+ ) daemon_functions
+
+(* Generate the server-side stubs. *)
+and generate_daemon_actions () =
+ generate_header CStyle GPLv2;
+
+ pr "#include <config.h>\n";
+ pr "\n";
+ pr "#include <stdio.h>\n";
+ pr "#include <stdlib.h>\n";
+ pr "#include <string.h>\n";
+ pr "#include <inttypes.h>\n";
+ pr "#include <ctype.h>\n";
+ pr "#include <rpc/types.h>\n";
+ pr "#include <rpc/xdr.h>\n";
+ pr "\n";
+ pr "#include \"daemon.h\"\n";
+ pr "#include \"../src/guestfs_protocol.h\"\n";
+ pr "#include \"actions.h\"\n";
+ pr "\n";
+
+ List.iter (
+ fun (name, style, _, _, _, _, _) ->
+ (* Generate server-side stubs. *)
+ pr "static void %s_stub (XDR *xdr_in)\n" name;
+ pr "{\n";
+ let error_code =
+ match fst style with
+ | RErr | RInt _ -> pr " int r;\n"; "-1"
+ | RInt64 _ -> pr " int64_t r;\n"; "-1"
+ | RBool _ -> pr " int r;\n"; "-1"
+ | RConstString _ ->
+ failwithf "RConstString cannot be returned from a daemon function"
+ | RString _ -> pr " char *r;\n"; "NULL"
+ | RStringList _ | RHashtable _ -> pr " char **r;\n"; "NULL"
+ | RIntBool _ -> pr " guestfs_%s_ret *r;\n" name; "NULL"
+ | RPVList _ -> pr " guestfs_lvm_int_pv_list *r;\n"; "NULL"
+ | RVGList _ -> pr " guestfs_lvm_int_vg_list *r;\n"; "NULL"
+ | RLVList _ -> pr " guestfs_lvm_int_lv_list *r;\n"; "NULL"
+ | RStat _ -> pr " guestfs_int_stat *r;\n"; "NULL"
+ | RStatVFS _ -> pr " guestfs_int_statvfs *r;\n"; "NULL" in
+
+ (match snd style with
+ | [] -> ()
+ | args ->
+ pr " struct guestfs_%s_args args;\n" name;
+ List.iter (
+ function
+ | String n
+ | OptString n -> pr " const char *%s;\n" n
+ | StringList n -> pr " char **%s;\n" n
+ | Bool n -> pr " int %s;\n" n
+ | Int n -> pr " int %s;\n" n
+ | FileIn _ | FileOut _ -> ()
+ ) args
+ );
+ pr "\n";
+
+ (match snd style with
+ | [] -> ()
+ | args ->
+ pr " memset (&args, 0, sizeof args);\n";
+ pr "\n";
+ pr " if (!xdr_guestfs_%s_args (xdr_in, &args)) {\n" name;
+ pr " reply_with_error (\"%%s: daemon failed to decode procedure arguments\", \"%s\");\n" name;
+ pr " return;\n";
+ pr " }\n";
+ List.iter (
+ function
+ | String n -> pr " %s = args.%s;\n" n n
+ | OptString n -> pr " %s = args.%s ? *args.%s : NULL;\n" n n n
+ | StringList n ->
+ pr " %s = realloc (args.%s.%s_val,\n" n n n;
+ pr " sizeof (char *) * (args.%s.%s_len+1));\n" n n;
+ pr " if (%s == NULL) {\n" n;
+ pr " reply_with_perror (\"realloc\");\n";
+ pr " goto done;\n";
+ pr " }\n";
+ pr " %s[args.%s.%s_len] = NULL;\n" n n n;
+ pr " args.%s.%s_val = %s;\n" n n n;
+ | Bool n -> pr " %s = args.%s;\n" n n
+ | Int n -> pr " %s = args.%s;\n" n n
+ | FileIn _ | FileOut _ -> ()
+ ) args;
+ pr "\n"
+ );
+
+ (* Don't want to call the impl with any FileIn or FileOut
+ * parameters, since these go "outside" the RPC protocol.
+ *)
+ let argsnofile =
+ List.filter (function FileIn _ | FileOut _ -> false | _ -> true)
+ (snd style) in
+ pr " r = do_%s " name;
+ generate_call_args argsnofile;
+ pr ";\n";
+
+ pr " if (r == %s)\n" error_code;
+ pr " /* do_%s has already called reply_with_error */\n" name;
+ pr " goto done;\n";
+ pr "\n";
+
+ (* If there are any FileOut parameters, then the impl must
+ * send its own reply.
+ *)
+ let no_reply =
+ List.exists (function FileOut _ -> true | _ -> false) (snd style) in
+ if no_reply then
+ pr " /* do_%s has already sent a reply */\n" name
+ else (
+ match fst style with
+ | RErr -> pr " reply (NULL, NULL);\n"
+ | RInt n | RInt64 n | RBool n ->
+ pr " struct guestfs_%s_ret ret;\n" name;
+ pr " ret.%s = r;\n" n;
+ pr " reply ((xdrproc_t) &xdr_guestfs_%s_ret, (char *) &ret);\n"
+ name
+ | RConstString _ ->
+ failwithf "RConstString cannot be returned from a daemon function"
+ | RString n ->
+ pr " struct guestfs_%s_ret ret;\n" name;
+ pr " ret.%s = r;\n" n;
+ pr " reply ((xdrproc_t) &xdr_guestfs_%s_ret, (char *) &ret);\n"
+ name;
+ pr " free (r);\n"
+ | RStringList n | RHashtable n ->
+ pr " struct guestfs_%s_ret ret;\n" name;
+ pr " ret.%s.%s_len = count_strings (r);\n" n n;
+ pr " ret.%s.%s_val = r;\n" n n;
+ pr " reply ((xdrproc_t) &xdr_guestfs_%s_ret, (char *) &ret);\n"
+ name;
+ pr " free_strings (r);\n"
+ | RIntBool _ ->
+ pr " reply ((xdrproc_t) xdr_guestfs_%s_ret, (char *) r);\n"
+ name;
+ pr " xdr_free ((xdrproc_t) xdr_guestfs_%s_ret, (char *) r);\n" name
+ | RPVList n | RVGList n | RLVList n
+ | RStat n | RStatVFS n ->
+ pr " struct guestfs_%s_ret ret;\n" name;
+ pr " ret.%s = *r;\n" n;
+ pr " reply ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n"
+ name;
+ pr " xdr_free ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n"
+ name
+ );
+
+ (* Free the args. *)
+ (match snd style with
+ | [] ->
+ pr "done: ;\n";
+ | _ ->
+ pr "done:\n";
+ pr " xdr_free ((xdrproc_t) xdr_guestfs_%s_args, (char *) &args);\n"
+ name
+ );
+
+ pr "}\n\n";
+ ) daemon_functions;
+
+ (* Dispatch function. *)
+ pr "void dispatch_incoming_message (XDR *xdr_in)\n";
+ pr "{\n";
+ pr " switch (proc_nr) {\n";
+
+ List.iter (
+ fun (name, style, _, _, _, _, _) ->
+ pr " case GUESTFS_PROC_%s:\n" (String.uppercase name);
+ pr " %s_stub (xdr_in);\n" name;
+ pr " break;\n"
+ ) daemon_functions;
+
+ pr " default:\n";
+ pr " reply_with_error (\"dispatch_incoming_message: unknown procedure number %%d\", proc_nr);\n";
+ pr " }\n";
+ pr "}\n";
+ pr "\n";
+
+ (* LVM columns and tokenization functions. *)
+ (* XXX This generates crap code. We should rethink how we
+ * do this parsing.
+ *)
+ List.iter (
+ function
+ | typ, cols ->
+ pr "static const char *lvm_%s_cols = \"%s\";\n"
+ typ (String.concat "," (List.map fst cols));
+ pr "\n";
+
+ pr "static int lvm_tokenize_%s (char *str, struct guestfs_lvm_int_%s *r)\n" typ typ;
+ pr "{\n";
+ pr " char *tok, *p, *next;\n";
+ pr " int i, j;\n";
+ pr "\n";
+ (*
+ pr " fprintf (stderr, \"%%s: <<%%s>>\\n\", __func__, str);\n";
+ pr "\n";
+ *)
+ pr " if (!str) {\n";
+ pr " fprintf (stderr, \"%%s: failed: passed a NULL string\\n\", __func__);\n";
+ pr " return -1;\n";
+ pr " }\n";
+ pr " if (!*str || isspace (*str)) {\n";
+ pr " fprintf (stderr, \"%%s: failed: passed a empty string or one beginning with whitespace\\n\", __func__);\n";
+ pr " return -1;\n";
+ pr " }\n";
+ pr " tok = str;\n";
+ List.iter (
+ fun (name, coltype) ->
+ pr " if (!tok) {\n";
+ pr " fprintf (stderr, \"%%s: failed: string finished early, around token %%s\\n\", __func__, \"%s\");\n" name;
+ pr " return -1;\n";
+ pr " }\n";
+ pr " p = strchrnul (tok, ',');\n";
+ pr " if (*p) next = p+1; else next = NULL;\n";
+ pr " *p = '\\0';\n";
+ (match coltype with
+ | `String ->
+ pr " r->%s = strdup (tok);\n" name;
+ pr " if (r->%s == NULL) {\n" name;
+ pr " perror (\"strdup\");\n";
+ pr " return -1;\n";
+ pr " }\n"
+ | `UUID ->
+ pr " for (i = j = 0; i < 32; ++j) {\n";
+ pr " if (tok[j] == '\\0') {\n";
+ pr " fprintf (stderr, \"%%s: failed to parse UUID from '%%s'\\n\", __func__, tok);\n";
+ pr " return -1;\n";
+ pr " } else if (tok[j] != '-')\n";
+ pr " r->%s[i++] = tok[j];\n" name;
+ pr " }\n";
+ | `Bytes ->
+ pr " if (sscanf (tok, \"%%\"SCNu64, &r->%s) != 1) {\n" name;
+ pr " fprintf (stderr, \"%%s: failed to parse size '%%s' from token %%s\\n\", __func__, tok, \"%s\");\n" name;
+ pr " return -1;\n";
+ pr " }\n";
+ | `Int ->
+ pr " if (sscanf (tok, \"%%\"SCNi64, &r->%s) != 1) {\n" name;
+ pr " fprintf (stderr, \"%%s: failed to parse int '%%s' from token %%s\\n\", __func__, tok, \"%s\");\n" name;
+ pr " return -1;\n";
+ pr " }\n";
+ | `OptPercent ->
+ pr " if (tok[0] == '\\0')\n";
+ pr " r->%s = -1;\n" name;
+ pr " else if (sscanf (tok, \"%%f\", &r->%s) != 1) {\n" name;
+ pr " fprintf (stderr, \"%%s: failed to parse float '%%s' from token %%s\\n\", __func__, tok, \"%s\");\n" name;
+ pr " return -1;\n";
+ pr " }\n";
+ );
+ pr " tok = next;\n";
+ ) cols;
+
+ pr " if (tok != NULL) {\n";
+ pr " fprintf (stderr, \"%%s: failed: extra tokens at end of string\\n\", __func__);\n";
+ pr " return -1;\n";
+ pr " }\n";
+ pr " return 0;\n";
+ pr "}\n";
+ pr "\n";
+
+ pr "guestfs_lvm_int_%s_list *\n" typ;
+ pr "parse_command_line_%ss (void)\n" typ;
+ pr "{\n";
+ pr " char *out, *err;\n";
+ pr " char *p, *pend;\n";
+ pr " int r, i;\n";
+ pr " guestfs_lvm_int_%s_list *ret;\n" typ;
+ pr " void *newp;\n";
+ pr "\n";
+ pr " ret = malloc (sizeof *ret);\n";
+ pr " if (!ret) {\n";
+ pr " reply_with_perror (\"malloc\");\n";
+ pr " return NULL;\n";
+ pr " }\n";
+ pr "\n";
+ pr " ret->guestfs_lvm_int_%s_list_len = 0;\n" typ;
+ pr " ret->guestfs_lvm_int_%s_list_val = NULL;\n" typ;
+ pr "\n";
+ pr " r = command (&out, &err,\n";
+ pr " \"/sbin/lvm\", \"%ss\",\n" typ;
+ pr " \"-o\", lvm_%s_cols, \"--unbuffered\", \"--noheadings\",\n" typ;
+ pr " \"--nosuffix\", \"--separator\", \",\", \"--units\", \"b\", NULL);\n";
+ pr " if (r == -1) {\n";
+ pr " reply_with_error (\"%%s\", err);\n";
+ pr " free (out);\n";
+ pr " free (err);\n";
+ pr " free (ret);\n";
+ pr " return NULL;\n";
+ pr " }\n";
+ pr "\n";
+ pr " free (err);\n";
+ pr "\n";
+ pr " /* Tokenize each line of the output. */\n";
+ pr " p = out;\n";
+ pr " i = 0;\n";
+ pr " while (p) {\n";
+ pr " pend = strchr (p, '\\n'); /* Get the next line of output. */\n";
+ pr " if (pend) {\n";
+ pr " *pend = '\\0';\n";
+ pr " pend++;\n";
+ pr " }\n";
+ pr "\n";
+ pr " while (*p && isspace (*p)) /* Skip any leading whitespace. */\n";
+ pr " p++;\n";
+ pr "\n";
+ pr " if (!*p) { /* Empty line? Skip it. */\n";
+ pr " p = pend;\n";
+ pr " continue;\n";
+ pr " }\n";
+ pr "\n";
+ pr " /* Allocate some space to store this next entry. */\n";
+ pr " newp = realloc (ret->guestfs_lvm_int_%s_list_val,\n" typ;
+ pr " sizeof (guestfs_lvm_int_%s) * (i+1));\n" typ;
+ pr " if (newp == NULL) {\n";
+ pr " reply_with_perror (\"realloc\");\n";
+ pr " free (ret->guestfs_lvm_int_%s_list_val);\n" typ;
+ pr " free (ret);\n";
+ pr " free (out);\n";
+ pr " return NULL;\n";
+ pr " }\n";
+ pr " ret->guestfs_lvm_int_%s_list_val = newp;\n" typ;
+ pr "\n";
+ pr " /* Tokenize the next entry. */\n";
+ pr " r = lvm_tokenize_%s (p, &ret->guestfs_lvm_int_%s_list_val[i]);\n" typ typ;
+ pr " if (r == -1) {\n";
+ pr " reply_with_error (\"failed to parse output of '%ss' command\");\n" typ;
+ pr " free (ret->guestfs_lvm_int_%s_list_val);\n" typ;
+ pr " free (ret);\n";
+ pr " free (out);\n";
+ pr " return NULL;\n";
+ pr " }\n";
+ pr "\n";
+ pr " ++i;\n";
+ pr " p = pend;\n";
+ pr " }\n";
+ pr "\n";
+ pr " ret->guestfs_lvm_int_%s_list_len = i;\n" typ;
+ pr "\n";
+ pr " free (out);\n";
+ pr " return ret;\n";
+ pr "}\n"
+
+ ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols]
+
+(* Generate the tests. *)
+and generate_tests () =
+ generate_header CStyle GPLv2;
+
+ pr "\
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include \"guestfs.h\"
+
+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)
+ fprintf (stderr, \"%%s\\n\", msg);
+}
+
+static void print_strings (char * const * const argv)
+{
+ int argc;
+
+ for (argc = 0; argv[argc] != NULL; ++argc)
+ printf (\"\\t%%s\\n\", argv[argc]);
+}
+
+/*
+static void print_table (char * const * const argv)
+{
+ int i;
+
+ for (i = 0; argv[i] != NULL; i += 2)
+ printf (\"%%s: %%s\\n\", argv[i], argv[i+1]);
+}
+*/
+
+static void no_test_warnings (void)
+{
+";
+
+ List.iter (
+ function
+ | name, _, _, _, [], _, _ ->
+ pr " fprintf (stderr, \"warning: \\\"guestfs_%s\\\" has no tests\\n\");\n" name
+ | name, _, _, _, tests, _, _ -> ()
+ ) all_functions;
+
+ pr "}\n";
+ pr "\n";
+
+ (* Generate the actual tests. Note that we generate the tests
+ * in reverse order, deliberately, so that (in general) the
+ * newest tests run first. This makes it quicker and easier to
+ * debug them.
+ *)
+ let test_names =
+ List.map (
+ fun (name, _, _, _, tests, _, _) ->
+ mapi (generate_one_test name) tests
+ ) (List.rev all_functions) in
+ let test_names = List.concat test_names in
+ let nr_tests = List.length test_names in
+
+ pr "\
+int main (int argc, char *argv[])
+{
+ char c = 0;
+ int failed = 0;
+ const char *filename;
+ int fd, i;
+ int nr_tests, test_num = 0;
+ char **devs;
+
+ no_test_warnings ();
+
+ g = guestfs_create ();
+ if (g == NULL) {
+ printf (\"guestfs_create FAILED\\n\");
+ exit (1);
+ }
+
+ guestfs_set_error_handler (g, print_error, NULL);
+
+ guestfs_set_path (g, \"../appliance\");
+
+ filename = \"test1.img\";
+ fd = open (filename, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK|O_TRUNC, 0666);
+ if (fd == -1) {
+ perror (filename);
+ exit (1);
+ }
+ if (lseek (fd, %d, SEEK_SET) == -1) {
+ perror (\"lseek\");
+ close (fd);
+ unlink (filename);
+ exit (1);
+ }
+ if (write (fd, &c, 1) == -1) {
+ perror (\"write\");
+ close (fd);
+ unlink (filename);
+ exit (1);
+ }
+ if (close (fd) == -1) {
+ perror (filename);
+ unlink (filename);
+ exit (1);
+ }
+ if (guestfs_add_drive (g, filename) == -1) {
+ printf (\"guestfs_add_drive %%s FAILED\\n\", filename);
+ exit (1);
+ }
+
+ filename = \"test2.img\";
+ fd = open (filename, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK|O_TRUNC, 0666);
+ if (fd == -1) {
+ perror (filename);
+ exit (1);
+ }
+ if (lseek (fd, %d, SEEK_SET) == -1) {
+ perror (\"lseek\");
+ close (fd);
+ unlink (filename);
+ exit (1);
+ }
+ if (write (fd, &c, 1) == -1) {
+ perror (\"write\");
+ close (fd);
+ unlink (filename);
+ exit (1);
+ }
+ if (close (fd) == -1) {
+ perror (filename);
+ unlink (filename);
+ exit (1);
+ }
+ if (guestfs_add_drive (g, filename) == -1) {
+ printf (\"guestfs_add_drive %%s FAILED\\n\", filename);
+ exit (1);
+ }
+
+ filename = \"test3.img\";
+ fd = open (filename, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK|O_TRUNC, 0666);
+ if (fd == -1) {
+ perror (filename);
+ exit (1);
+ }
+ if (lseek (fd, %d, SEEK_SET) == -1) {
+ perror (\"lseek\");
+ close (fd);
+ unlink (filename);
+ exit (1);
+ }
+ if (write (fd, &c, 1) == -1) {
+ perror (\"write\");
+ close (fd);
+ unlink (filename);
+ exit (1);
+ }
+ if (close (fd) == -1) {
+ perror (filename);
+ unlink (filename);
+ exit (1);
+ }
+ if (guestfs_add_drive (g, filename) == -1) {
+ printf (\"guestfs_add_drive %%s FAILED\\n\", filename);
+ exit (1);
+ }
+
+ if (guestfs_add_drive_ro (g, \"../images/test.sqsh\") == -1) {
+ printf (\"guestfs_add_drive_ro ../images/test.sqsh FAILED\\n\");
+ exit (1);
+ }
+
+ if (guestfs_launch (g) == -1) {
+ printf (\"guestfs_launch FAILED\\n\");
+ exit (1);
+ }
+ if (guestfs_wait_ready (g) == -1) {
+ printf (\"guestfs_wait_ready FAILED\\n\");
+ 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;
+
+ iteri (
+ fun i test_name ->
+ pr " test_num++;\n";
+ 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";
+ ) test_names;
+ pr "\n";
+
+ pr " guestfs_close (g);\n";
+ pr " unlink (\"test1.img\");\n";
+ pr " unlink (\"test2.img\");\n";
+ pr " unlink (\"test3.img\");\n";
+ pr "\n";
+
+ pr " if (failed > 0) {\n";
+ pr " printf (\"***** %%d / %%d tests FAILED *****\\n\", failed, nr_tests);\n";
+ pr " exit (1);\n";
+ pr " }\n";
+ pr "\n";
+
+ pr " exit (0);\n";
+ pr "}\n"
+
+and generate_one_test name i (init, prereq, test) =
+ let test_name = sprintf "test_%s_%d" name i in
+
+ 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
+ | InitEmpty ->
+ pr " /* InitNone|InitEmpty for %s */\n" test_name;
+ List.iter (generate_test_command_call test_name)
+ [["blockdev_setrw"; "/dev/sda"];
+ ["umount_all"];
+ ["lvm_remove_all"]]
+ | InitBasicFS ->
+ pr " /* InitBasicFS for %s: create ext2 on /dev/sda1 */\n" test_name;
+ List.iter (generate_test_command_call test_name)
+ [["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: create ext2 on /dev/VG/LV */\n"
+ test_name;
+ List.iter (generate_test_command_call test_name)
+ [["blockdev_setrw"; "/dev/sda"];
+ ["umount_all"];
+ ["lvm_remove_all"];
+ ["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","];
+ ["pvcreate"; "/dev/sda1"];
+ ["vgcreate"; "VG"; "/dev/sda1"];
+ ["lvcreate"; "LV"; "VG"; "8"];
+ ["mkfs"; "ext2"; "/dev/VG/LV"];
+ ["mount"; "/dev/VG/LV"; "/"]]
+ );
+
+ let get_seq_last = function
+ | [] ->
+ failwithf "%s: you cannot use [] (empty list) when expecting a command"
+ test_name
+ | seq ->
+ let seq = List.rev seq in
+ 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;
+ 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.
+ *)
+and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
+ match cmd with
+ | [] -> assert false
+ | name :: args ->
+ (* Look up the command to find out what args/ret it has. *)
+ let style =
+ try
+ let _, style, _, _, _, _, _ =
+ List.find (fun (n, _, _, _, _, _, _) -> n = name) all_functions in
+ style
+ with Not_found ->
+ failwithf "%s: in test, command %s was not found" test_name name in
+
+ if List.length (snd style) <> List.length args then
+ failwithf "%s: in test, wrong number of args given to %s"
+ test_name name;
+
+ pr " {\n";
+
+ List.iter (
+ function
+ | 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 _, _
+ | FileIn _, _ | FileOut _, _ -> ()
+ | StringList n, arg ->
+ let strs = string_split " " arg in
+ 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";
+ ) (List.combine (snd style) args);
+
+ let error_code =
+ match fst style with
+ | RErr | RInt _ | RBool _ -> pr " int r;\n"; "-1"
+ | RInt64 _ -> pr " int64_t r;\n"; "-1"
+ | RConstString _ -> pr " const char *r;\n"; "NULL"
+ | RString _ -> pr " char *r;\n"; "NULL"
+ | RStringList _ | RHashtable _ ->
+ pr " char **r;\n";
+ pr " int i;\n";
+ "NULL"
+ | RIntBool _ ->
+ pr " struct guestfs_int_bool *r;\n"; "NULL"
+ | RPVList _ ->
+ pr " struct guestfs_lvm_pv_list *r;\n"; "NULL"
+ | RVGList _ ->
+ pr " struct guestfs_lvm_vg_list *r;\n"; "NULL"
+ | RLVList _ ->
+ pr " struct guestfs_lvm_lv_list *r;\n"; "NULL"
+ | RStat _ ->
+ pr " struct guestfs_stat *r;\n"; "NULL"
+ | RStatVFS _ ->
+ pr " struct guestfs_statvfs *r;\n"; "NULL" in
+
+ pr " suppress_error = %d;\n" (if expect_error then 1 else 0);
+ pr " r = guestfs_%s (g" name;
+
+ (* Generate the parameters. *)
+ List.iter (
+ function
+ | OptString _, "NULL" -> pr ", NULL"
+ | String n, _
+ | OptString n, _ ->
+ pr ", %s" n
+ | FileIn _, arg | FileOut _, arg ->
+ pr ", \"%s\"" (c_quote arg)
+ | StringList n, _ ->
+ pr ", %s" n
+ | Int _, arg ->
+ let i =
+ try int_of_string arg
+ with Failure "int_of_string" ->
+ failwithf "%s: expecting an int, but got '%s'" test_name arg in
+ pr ", %d" i
+ | Bool _, arg ->
+ let b = bool_of_string arg in pr ", %d" (if b then 1 else 0)
+ ) (List.combine (snd style) args);
+
+ pr ");\n";
+ if not expect_error then
+ pr " if (r == %s)\n" error_code
+ else
+ pr " if (r != %s)\n" error_code;
+ pr " return -1;\n";
+
+ (* Insert the test code. *)
+ (match test with
+ | None -> ()
+ | Some f -> f ()
+ );
+
+ (match fst style with
+ | RErr | RInt _ | RInt64 _ | RBool _ | RConstString _ -> ()
+ | RString _ -> pr " free (r);\n"
+ | RStringList _ | RHashtable _ ->
+ pr " for (i = 0; r[i] != NULL; ++i)\n";
+ pr " free (r[i]);\n";
+ pr " free (r);\n"
+ | RIntBool _ ->
+ pr " guestfs_free_int_bool (r);\n"
+ | RPVList _ ->
+ pr " guestfs_free_lvm_pv_list (r);\n"
+ | RVGList _ ->
+ pr " guestfs_free_lvm_vg_list (r);\n"
+ | RLVList _ ->
+ pr " guestfs_free_lvm_lv_list (r);\n"
+ | RStat _ | RStatVFS _ ->
+ pr " free (r);\n"
+ );
+
+ pr " }\n"
+
+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. *)
+and generate_fish_cmds () =
+ generate_header CStyle GPLv2;
+
+ let all_functions =
+ List.filter (
+ fun (_, _, _, flags, _, _, _) -> not (List.mem NotInFish flags)
+ ) all_functions in
+ let all_functions_sorted =
+ List.filter (
+ fun (_, _, _, flags, _, _, _) -> not (List.mem NotInFish flags)
+ ) all_functions_sorted in
+
+ pr "#include <stdio.h>\n";
+ pr "#include <stdlib.h>\n";
+ pr "#include <string.h>\n";
+ pr "#include <inttypes.h>\n";
+ pr "\n";
+ pr "#include <guestfs.h>\n";
+ pr "#include \"fish.h\"\n";
+ pr "\n";
+
+ (* list_commands function, which implements guestfish -h *)
+ pr "void list_commands (void)\n";
+ pr "{\n";
+ pr " printf (\" %%-16s %%s\\n\", \"Command\", \"Description\");\n";
+ pr " list_builtin_commands ();\n";
+ List.iter (
+ fun (name, _, _, flags, _, shortdesc, _) ->
+ let name = replace_char name '_' '-' in
+ pr " printf (\"%%-20s %%s\\n\", \"%s\", \"%s\");\n"
+ name shortdesc
+ ) all_functions_sorted;
+ pr " printf (\" Use -h <cmd> / help <cmd> to show detailed help for a command.\\n\");\n";
+ pr "}\n";
+ pr "\n";
+
+ (* display_command function, which implements guestfish -h cmd *)
+ pr "void display_command (const char *cmd)\n";
+ pr "{\n";
+ List.iter (
+ fun (name, style, _, flags, _, shortdesc, longdesc) ->
+ let name2 = replace_char name '_' '-' in
+ let alias =
+ try find_map (function FishAlias n -> Some n | _ -> None) flags
+ with Not_found -> name in
+ let longdesc = replace_str longdesc "C<guestfs_" "C<" in
+ let synopsis =
+ match snd style with
+ | [] -> name2
+ | args ->
+ sprintf "%s <%s>"
+ name2 (String.concat "> <" (List.map name_of_argt args)) in
+
+ let warnings =
+ if List.mem ProtocolLimitWarning flags then
+ ("\n\n" ^ protocol_limit_warning)
+ else "" in
+
+ (* For DangerWillRobinson commands, we should probably have
+ * guestfish prompt before allowing you to use them (especially
+ * in interactive mode). XXX
+ *)
+ let warnings =
+ warnings ^
+ if List.mem DangerWillRobinson flags then
+ ("\n\n" ^ danger_will_robinson)
+ else "" in
+
+ let describe_alias =
+ if name <> alias then
+ sprintf "\n\nYou can use '%s' as an alias for this command." alias
+ else "" in
+
+ pr " if (";
+ pr "strcasecmp (cmd, \"%s\") == 0" name;
+ if name <> name2 then
+ pr " || strcasecmp (cmd, \"%s\") == 0" name2;
+ if name <> alias then
+ pr " || strcasecmp (cmd, \"%s\") == 0" alias;
+ pr ")\n";
+ pr " pod2text (\"%s - %s\", %S);\n"
+ name2 shortdesc
+ (" " ^ synopsis ^ "\n\n" ^ longdesc ^ warnings ^ describe_alias);
+ pr " else\n"
+ ) all_functions;
+ pr " display_builtin_command (cmd);\n";
+ pr "}\n";
+ pr "\n";
+
+ (* print_{pv,vg,lv}_list functions *)
+ List.iter (
+ function
+ | typ, cols ->
+ pr "static void print_%s (struct guestfs_lvm_%s *%s)\n" typ typ typ;
+ pr "{\n";
+ pr " int i;\n";
+ pr "\n";
+ List.iter (
+ function
+ | name, `String ->
pr " printf (\"%s: %%s\\n\", %s->%s);\n" name typ name
| name, `UUID ->
pr " printf (\"%s: \");\n" name;
pr "static void print_%s_list (struct guestfs_lvm_%s_list *%ss)\n"
typ typ typ;
pr "{\n";
- pr " int i;\n";
- pr "\n";
- pr " for (i = 0; i < %ss->len; ++i)\n" typ;
- pr " print_%s (&%ss->val[i]);\n" typ typ;
+ pr " int i;\n";
+ pr "\n";
+ pr " for (i = 0; i < %ss->len; ++i)\n" typ;
+ pr " print_%s (&%ss->val[i]);\n" typ typ;
+ pr "}\n";
+ pr "\n";
+ ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
+
+ (* print_{stat,statvfs} functions *)
+ List.iter (
+ function
+ | typ, cols ->
+ pr "static void print_%s (struct guestfs_%s *%s)\n" typ typ typ;
+ pr "{\n";
+ List.iter (
+ function
+ | name, `Int ->
+ pr " printf (\"%s: %%\" PRIi64 \"\\n\", %s->%s);\n" name typ name
+ ) cols;
+ pr "}\n";
+ pr "\n";
+ ) ["stat", stat_cols; "statvfs", statvfs_cols];
+
+ (* run_<action> actions *)
+ List.iter (
+ fun (name, style, _, flags, _, _, _) ->
+ pr "static int run_%s (const char *cmd, int argc, char *argv[])\n" name;
+ pr "{\n";
+ (match fst style with
+ | RErr
+ | RInt _
+ | RBool _ -> pr " int r;\n"
+ | RInt64 _ -> pr " int64_t r;\n"
+ | RConstString _ -> pr " const char *r;\n"
+ | RString _ -> pr " char *r;\n"
+ | RStringList _ | RHashtable _ -> pr " char **r;\n"
+ | RIntBool _ -> pr " struct guestfs_int_bool *r;\n"
+ | RPVList _ -> pr " struct guestfs_lvm_pv_list *r;\n"
+ | RVGList _ -> pr " struct guestfs_lvm_vg_list *r;\n"
+ | RLVList _ -> pr " struct guestfs_lvm_lv_list *r;\n"
+ | RStat _ -> pr " struct guestfs_stat *r;\n"
+ | RStatVFS _ -> pr " struct guestfs_statvfs *r;\n"
+ );
+ List.iter (
+ function
+ | String n
+ | OptString n
+ | FileIn n
+ | FileOut n -> pr " const char *%s;\n" n
+ | StringList n -> pr " char **%s;\n" n
+ | Bool n -> pr " int %s;\n" n
+ | Int n -> pr " int %s;\n" n
+ ) (snd style);
+
+ (* Check and convert parameters. *)
+ let argc_expected = List.length (snd style) in
+ pr " if (argc != %d) {\n" argc_expected;
+ pr " fprintf (stderr, \"%%s should have %d parameter(s)\\n\", cmd);\n"
+ argc_expected;
+ pr " fprintf (stderr, \"type 'help %%s' for help on %%s\\n\", cmd, cmd);\n";
+ pr " return -1;\n";
+ pr " }\n";
+ iteri (
+ fun i ->
+ function
+ | String name -> pr " %s = argv[%d];\n" name i
+ | OptString name ->
+ pr " %s = strcmp (argv[%d], \"\") != 0 ? argv[%d] : NULL;\n"
+ name i i
+ | FileIn name ->
+ pr " %s = strcmp (argv[%d], \"-\") != 0 ? argv[%d] : \"/dev/stdin\";\n"
+ name i i
+ | FileOut name ->
+ pr " %s = strcmp (argv[%d], \"-\") != 0 ? argv[%d] : \"/dev/stdout\";\n"
+ name i i
+ | StringList name ->
+ pr " %s = parse_string_list (argv[%d]);\n" name i
+ | Bool name ->
+ pr " %s = is_true (argv[%d]) ? 1 : 0;\n" name i
+ | Int name ->
+ pr " %s = atoi (argv[%d]);\n" name i
+ ) (snd style);
+
+ (* Call C API function. *)
+ let fn =
+ try find_map (function FishAction n -> Some n | _ -> None) flags
+ with Not_found -> sprintf "guestfs_%s" name in
+ pr " r = %s " fn;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+
+ (* Check return value for errors and display command results. *)
+ (match fst style with
+ | RErr -> pr " return r;\n"
+ | RInt _ ->
+ pr " if (r == -1) return -1;\n";
+ pr " printf (\"%%d\\n\", r);\n";
+ pr " return 0;\n"
+ | RInt64 _ ->
+ pr " if (r == -1) return -1;\n";
+ pr " printf (\"%%\" PRIi64 \"\\n\", r);\n";
+ pr " return 0;\n"
+ | RBool _ ->
+ pr " if (r == -1) return -1;\n";
+ pr " if (r) printf (\"true\\n\"); else printf (\"false\\n\");\n";
+ pr " return 0;\n"
+ | RConstString _ ->
+ pr " if (r == NULL) return -1;\n";
+ pr " printf (\"%%s\\n\", r);\n";
+ pr " return 0;\n"
+ | RString _ ->
+ pr " if (r == NULL) return -1;\n";
+ pr " printf (\"%%s\\n\", r);\n";
+ pr " free (r);\n";
+ pr " return 0;\n"
+ | RStringList _ ->
+ pr " if (r == NULL) return -1;\n";
+ pr " print_strings (r);\n";
+ pr " free_strings (r);\n";
+ pr " return 0;\n"
+ | RIntBool _ ->
+ pr " if (r == NULL) return -1;\n";
+ pr " printf (\"%%d, %%s\\n\", r->i,\n";
+ pr " r->b ? \"true\" : \"false\");\n";
+ pr " guestfs_free_int_bool (r);\n";
+ pr " return 0;\n"
+ | RPVList _ ->
+ pr " if (r == NULL) return -1;\n";
+ pr " print_pv_list (r);\n";
+ pr " guestfs_free_lvm_pv_list (r);\n";
+ pr " return 0;\n"
+ | RVGList _ ->
+ pr " if (r == NULL) return -1;\n";
+ pr " print_vg_list (r);\n";
+ pr " guestfs_free_lvm_vg_list (r);\n";
+ pr " return 0;\n"
+ | RLVList _ ->
+ pr " if (r == NULL) return -1;\n";
+ pr " print_lv_list (r);\n";
+ pr " guestfs_free_lvm_lv_list (r);\n";
+ pr " return 0;\n"
+ | RStat _ ->
+ pr " if (r == NULL) return -1;\n";
+ pr " print_stat (r);\n";
+ pr " free (r);\n";
+ pr " return 0;\n"
+ | RStatVFS _ ->
+ pr " if (r == NULL) return -1;\n";
+ pr " print_statvfs (r);\n";
+ pr " free (r);\n";
+ pr " return 0;\n"
+ | RHashtable _ ->
+ pr " if (r == NULL) return -1;\n";
+ pr " print_table (r);\n";
+ pr " free_strings (r);\n";
+ pr " return 0;\n"
+ );
+ pr "}\n";
+ pr "\n"
+ ) all_functions;
+
+ (* run_action function *)
+ pr "int run_action (const char *cmd, int argc, char *argv[])\n";
+ pr "{\n";
+ List.iter (
+ fun (name, _, _, flags, _, _, _) ->
+ let name2 = replace_char name '_' '-' in
+ let alias =
+ try find_map (function FishAlias n -> Some n | _ -> None) flags
+ with Not_found -> name in
+ pr " if (";
+ pr "strcasecmp (cmd, \"%s\") == 0" name;
+ if name <> name2 then
+ pr " || strcasecmp (cmd, \"%s\") == 0" name2;
+ if name <> alias then
+ pr " || strcasecmp (cmd, \"%s\") == 0" alias;
+ pr ")\n";
+ pr " return run_%s (cmd, argc, argv);\n" name;
+ pr " else\n";
+ ) all_functions;
+ pr " {\n";
+ pr " fprintf (stderr, \"%%s: unknown command\\n\", cmd);\n";
+ pr " return -1;\n";
+ pr " }\n";
+ pr " return 0;\n";
+ pr "}\n";
+ pr "\n"
+
+(* Readline completion for guestfish. *)
+and generate_fish_completion () =
+ generate_header CStyle GPLv2;
+
+ let all_functions =
+ List.filter (
+ fun (_, _, _, flags, _, _, _) -> not (List.mem NotInFish flags)
+ ) all_functions in
+
+ pr "\
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_LIBREADLINE
+#include <readline/readline.h>
+#endif
+
+#include \"fish.h\"
+
+#ifdef HAVE_LIBREADLINE
+
+static const char *const commands[] = {
+ BUILTIN_COMMANDS_FOR_COMPLETION,
+";
+
+ (* 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, _, _, _) ->
+ let name2 = replace_char name '_' '-' in
+ let alias =
+ try find_map (function FishAlias n -> Some n | _ -> None) flags
+ with Not_found -> name in
+
+ if name <> alias then [name2; alias] else [name2]
+ ) all_functions in
+ let commands = List.flatten commands in
+
+ List.iter (pr " \"%s\",\n") commands;
+
+ pr " NULL
+};
+
+static char *
+generator (const char *text, int state)
+{
+ static int index, len;
+ const char *name;
+
+ if (!state) {
+ index = 0;
+ len = strlen (text);
+ }
+
+ while ((name = commands[index]) != NULL) {
+ index++;
+ if (strncasecmp (name, text, len) == 0)
+ return strdup (name);
+ }
+
+ return NULL;
+}
+
+#endif /* HAVE_LIBREADLINE */
+
+char **do_completion (const char *text, int start, int end)
+{
+ char **matches = NULL;
+
+#ifdef HAVE_LIBREADLINE
+ if (start == 0)
+ matches = rl_completion_matches (text, generator);
+#endif
+
+ return matches;
+}
+";
+
+(* Generate the POD documentation for guestfish. *)
+and generate_fish_actions_pod () =
+ let all_functions_sorted =
+ List.filter (
+ fun (_, _, _, flags, _, _, _) ->
+ not (List.mem NotInFish flags || List.mem NotInDocs flags)
+ ) all_functions_sorted in
+
+ let rex = Str.regexp "C<guestfs_\\([^>]+\\)>" in
+
+ List.iter (
+ fun (name, style, _, flags, _, _, longdesc) ->
+ let longdesc =
+ Str.global_substitute rex (
+ fun s ->
+ let sub =
+ try Str.matched_group 1 s
+ with Not_found ->
+ failwithf "error substituting C<guestfs_...> in longdesc of function %s" name in
+ "C<" ^ replace_char sub '_' '-' ^ ">"
+ ) longdesc in
+ let name = replace_char name '_' '-' in
+ let alias =
+ try find_map (function FishAlias n -> Some n | _ -> None) flags
+ with Not_found -> name in
+
+ pr "=head2 %s" name;
+ if name <> alias then
+ pr " | %s" alias;
+ pr "\n";
+ pr "\n";
+ pr " %s" name;
+ List.iter (
+ function
+ | String n -> pr " %s" n
+ | OptString n -> pr " %s" n
+ | StringList n -> pr " '%s ...'" n
+ | Bool _ -> pr " true|false"
+ | Int n -> pr " %s" n
+ | FileIn n | FileOut n -> pr " (%s|-)" n
+ ) (snd style);
+ pr "\n";
+ pr "\n";
+ pr "%s\n\n" longdesc;
+
+ if List.exists (function FileIn _ | FileOut _ -> true
+ | _ -> false) (snd style) then
+ pr "Use C<-> instead of a filename to read/write from stdin/stdout.\n\n";
+
+ if List.mem ProtocolLimitWarning flags then
+ pr "%s\n\n" protocol_limit_warning;
+
+ if List.mem DangerWillRobinson flags then
+ pr "%s\n\n" danger_will_robinson
+ ) all_functions_sorted
+
+(* Generate a C function prototype. *)
+and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
+ ?(single_line = false) ?(newline = false) ?(in_daemon = false)
+ ?(prefix = "")
+ ?handle name style =
+ if extern then pr "extern ";
+ if static then pr "static ";
+ (match fst style with
+ | RErr -> pr "int "
+ | RInt _ -> pr "int "
+ | RInt64 _ -> pr "int64_t "
+ | RBool _ -> pr "int "
+ | RConstString _ -> pr "const char *"
+ | RString _ -> pr "char *"
+ | RStringList _ | RHashtable _ -> pr "char **"
+ | RIntBool _ ->
+ if not in_daemon then pr "struct guestfs_int_bool *"
+ else pr "guestfs_%s_ret *" name
+ | RPVList _ ->
+ if not in_daemon then pr "struct guestfs_lvm_pv_list *"
+ else pr "guestfs_lvm_int_pv_list *"
+ | RVGList _ ->
+ if not in_daemon then pr "struct guestfs_lvm_vg_list *"
+ else pr "guestfs_lvm_int_vg_list *"
+ | RLVList _ ->
+ if not in_daemon then pr "struct guestfs_lvm_lv_list *"
+ else pr "guestfs_lvm_int_lv_list *"
+ | RStat _ ->
+ if not in_daemon then pr "struct guestfs_stat *"
+ else pr "guestfs_int_stat *"
+ | RStatVFS _ ->
+ if not in_daemon then pr "struct guestfs_statvfs *"
+ else pr "guestfs_int_statvfs *"
+ );
+ pr "%s%s (" prefix name;
+ if handle = None && List.length (snd style) = 0 then
+ pr "void"
+ else (
+ let comma = ref false in
+ (match handle with
+ | None -> ()
+ | Some handle -> pr "guestfs_h *%s" handle; comma := true
+ );
+ let next () =
+ if !comma then (
+ if single_line then pr ", " else pr ",\n\t\t"
+ );
+ comma := true
+ in
+ List.iter (
+ function
+ | String n
+ | OptString n -> next (); pr "const char *%s" n
+ | StringList n -> next (); pr "char * const* const %s" n
+ | Bool n -> next (); pr "int %s" n
+ | Int n -> next (); pr "int %s" n
+ | FileIn n
+ | FileOut n ->
+ if not in_daemon then (next (); pr "const char *%s" n)
+ ) (snd style);
+ );
+ pr ")";
+ if semicolon then pr ";";
+ if newline then pr "\n"
+
+(* Generate C call arguments, eg "(handle, foo, bar)" *)
+and generate_call_args ?handle args =
+ pr "(";
+ let comma = ref false in
+ (match handle with
+ | None -> ()
+ | Some handle -> pr "%s" handle; comma := true
+ );
+ List.iter (
+ fun arg ->
+ if !comma then pr ", ";
+ comma := true;
+ pr "%s" (name_of_argt arg)
+ ) args;
+ pr ")"
+
+(* Generate the OCaml bindings interface. *)
+and generate_ocaml_mli () =
+ generate_header OCamlStyle LGPLv2;
+
+ pr "\
+(** For API documentation you should refer to the C API
+ in the guestfs(3) manual page. The OCaml API uses almost
+ exactly the same calls. *)
+
+type t
+(** A [guestfs_h] handle. *)
+
+exception Error of string
+(** This exception is raised when there is an error. *)
+
+val create : unit -> t
+
+val close : t -> unit
+(** Handles are closed by the garbage collector when they become
+ unreferenced, but callers can also call this in order to
+ provide predictable cleanup. *)
+
+";
+ generate_ocaml_lvm_structure_decls ();
+
+ generate_ocaml_stat_structure_decls ();
+
+ (* The actions. *)
+ List.iter (
+ fun (name, style, _, _, _, shortdesc, _) ->
+ generate_ocaml_prototype name style;
+ pr "(** %s *)\n" shortdesc;
+ pr "\n"
+ ) all_functions
+
+(* Generate the OCaml bindings implementation. *)
+and generate_ocaml_ml () =
+ generate_header OCamlStyle LGPLv2;
+
+ pr "\
+type t
+exception Error of string
+external create : unit -> t = \"ocaml_guestfs_create\"
+external close : t -> unit = \"ocaml_guestfs_close\"
+
+let () =
+ Callback.register_exception \"ocaml_guestfs_error\" (Error \"\")
+
+";
+
+ generate_ocaml_lvm_structure_decls ();
+
+ generate_ocaml_stat_structure_decls ();
+
+ (* The actions. *)
+ List.iter (
+ fun (name, style, _, _, _, shortdesc, _) ->
+ generate_ocaml_prototype ~is_external:true name style;
+ ) all_functions
+
+(* Generate the OCaml bindings C implementation. *)
+and generate_ocaml_c () =
+ generate_header CStyle LGPLv2;
+
+ pr "\
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <caml/config.h>
+#include <caml/alloc.h>
+#include <caml/callback.h>
+#include <caml/fail.h>
+#include <caml/memory.h>
+#include <caml/mlvalues.h>
+#include <caml/signals.h>
+
+#include <guestfs.h>
+
+#include \"guestfs_c.h\"
+
+/* Copy a hashtable of string pairs into an assoc-list. We return
+ * the list in reverse order, but hashtables aren't supposed to be
+ * ordered anyway.
+ */
+static CAMLprim value
+copy_table (char * const * argv)
+{
+ CAMLparam0 ();
+ CAMLlocal5 (rv, pairv, kv, vv, cons);
+ int i;
+
+ rv = Val_int (0);
+ for (i = 0; argv[i] != NULL; i += 2) {
+ kv = caml_copy_string (argv[i]);
+ vv = caml_copy_string (argv[i+1]);
+ pairv = caml_alloc (2, 0);
+ Store_field (pairv, 0, kv);
+ Store_field (pairv, 1, vv);
+ cons = caml_alloc (2, 0);
+ Store_field (cons, 1, rv);
+ rv = cons;
+ Store_field (cons, 0, pairv);
+ }
+
+ CAMLreturn (rv);
+}
+
+";
+
+ (* LVM struct copy functions. *)
+ List.iter (
+ fun (typ, cols) ->
+ let has_optpercent_col =
+ List.exists (function (_, `OptPercent) -> true | _ -> false) cols in
+
+ pr "static CAMLprim value\n";
+ pr "copy_lvm_%s (const struct guestfs_lvm_%s *%s)\n" typ typ typ;
+ pr "{\n";
+ pr " CAMLparam0 ();\n";
+ if has_optpercent_col then
+ pr " CAMLlocal3 (rv, v, v2);\n"
+ else
+ pr " CAMLlocal2 (rv, v);\n";
+ pr "\n";
+ pr " rv = caml_alloc (%d, 0);\n" (List.length cols);
+ iteri (
+ fun i col ->
+ (match col with
+ | name, `String ->
+ pr " v = caml_copy_string (%s->%s);\n" typ name
+ | name, `UUID ->
+ pr " v = caml_alloc_string (32);\n";
+ pr " memcpy (String_val (v), %s->%s, 32);\n" typ name
+ | name, `Bytes
+ | name, `Int ->
+ pr " v = caml_copy_int64 (%s->%s);\n" typ name
+ | name, `OptPercent ->
+ pr " if (%s->%s >= 0) { /* Some %s */\n" typ name name;
+ pr " v2 = caml_copy_double (%s->%s);\n" typ name;
+ pr " v = caml_alloc (1, 0);\n";
+ pr " Store_field (v, 0, v2);\n";
+ pr " } else /* None */\n";
+ pr " v = Val_int (0);\n";
+ );
+ pr " Store_field (rv, %d, v);\n" i
+ ) cols;
+ pr " CAMLreturn (rv);\n";
+ pr "}\n";
+ pr "\n";
+
+ pr "static CAMLprim value\n";
+ pr "copy_lvm_%s_list (const struct guestfs_lvm_%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_lvm_%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";
+ ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
+
+ (* Stat copy functions. *)
+ List.iter (
+ fun (typ, cols) ->
+ pr "static CAMLprim value\n";
+ pr "copy_%s (const struct guestfs_%s *%s)\n" typ typ typ;
+ pr "{\n";
+ pr " CAMLparam0 ();\n";
+ pr " CAMLlocal2 (rv, v);\n";
+ pr "\n";
+ pr " rv = caml_alloc (%d, 0);\n" (List.length cols);
+ iteri (
+ fun i col ->
+ (match col with
+ | name, `Int ->
+ pr " v = caml_copy_int64 (%s->%s);\n" typ name
+ );
+ pr " Store_field (rv, %d, v);\n" i
+ ) cols;
+ pr " CAMLreturn (rv);\n";
+ pr "}\n";
+ pr "\n";
+ ) ["stat", stat_cols; "statvfs", statvfs_cols];
+
+ (* The wrappers. *)
+ List.iter (
+ fun (name, style, _, _, _, _, _) ->
+ let params =
+ "gv" :: List.map (fun arg -> name_of_argt arg ^ "v") (snd style) in
+
+ pr "CAMLprim value\n";
+ pr "ocaml_guestfs_%s (value %s" name (List.hd params);
+ List.iter (pr ", value %s") (List.tl params);
+ pr ")\n";
+ pr "{\n";
+
+ (match params with
+ | [p1; p2; p3; p4; p5] ->
+ pr " CAMLparam5 (%s);\n" (String.concat ", " params)
+ | p1 :: p2 :: p3 :: p4 :: p5 :: rest ->
+ pr " CAMLparam5 (%s);\n" (String.concat ", " [p1; p2; p3; p4; p5]);
+ pr " CAMLxparam%d (%s);\n"
+ (List.length rest) (String.concat ", " rest)
+ | ps ->
+ pr " CAMLparam%d (%s);\n" (List.length ps) (String.concat ", " ps)
+ );
+ pr " CAMLlocal1 (rv);\n";
+ pr "\n";
+
+ pr " guestfs_h *g = Guestfs_val (gv);\n";
+ pr " if (g == NULL)\n";
+ pr " caml_failwith (\"%s: used handle after closing it\");\n" name;
+ pr "\n";
+
+ List.iter (
+ function
+ | String n
+ | FileIn n
+ | FileOut n ->
+ pr " const char *%s = String_val (%sv);\n" n n
+ | OptString n ->
+ pr " const char *%s =\n" n;
+ pr " %sv != Val_int (0) ? String_val (Field (%sv, 0)) : NULL;\n"
+ n n
+ | StringList n ->
+ pr " char **%s = ocaml_guestfs_strings_val (g, %sv);\n" n n
+ | Bool n ->
+ pr " int %s = Bool_val (%sv);\n" n n
+ | Int n ->
+ pr " int %s = Int_val (%sv);\n" n n
+ ) (snd style);
+ let error_code =
+ match fst style with
+ | RErr -> pr " int r;\n"; "-1"
+ | RInt _ -> pr " int r;\n"; "-1"
+ | RInt64 _ -> pr " int64_t r;\n"; "-1"
+ | RBool _ -> pr " int r;\n"; "-1"
+ | RConstString _ -> pr " const char *r;\n"; "NULL"
+ | RString _ -> pr " char *r;\n"; "NULL"
+ | RStringList _ ->
+ pr " int i;\n";
+ pr " char **r;\n";
+ "NULL"
+ | RIntBool _ ->
+ pr " struct guestfs_int_bool *r;\n"; "NULL"
+ | RPVList _ ->
+ pr " struct guestfs_lvm_pv_list *r;\n"; "NULL"
+ | RVGList _ ->
+ pr " struct guestfs_lvm_vg_list *r;\n"; "NULL"
+ | RLVList _ ->
+ pr " struct guestfs_lvm_lv_list *r;\n"; "NULL"
+ | RStat _ ->
+ pr " struct guestfs_stat *r;\n"; "NULL"
+ | RStatVFS _ ->
+ pr " struct guestfs_statvfs *r;\n"; "NULL"
+ | RHashtable _ ->
+ pr " int i;\n";
+ pr " char **r;\n";
+ "NULL" in
+ pr "\n";
+
+ pr " caml_enter_blocking_section ();\n";
+ pr " r = guestfs_%s " name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+ pr " caml_leave_blocking_section ();\n";
+
+ List.iter (
+ function
+ | StringList n ->
+ pr " ocaml_guestfs_free_strings (%s);\n" n;
+ | String _ | OptString _ | Bool _ | Int _ | FileIn _ | FileOut _ -> ()
+ ) (snd style);
+
+ pr " if (r == %s)\n" error_code;
+ pr " ocaml_guestfs_raise_error (g, \"%s\");\n" name;
+ pr "\n";
+
+ (match fst style with
+ | RErr -> pr " rv = Val_unit;\n"
+ | RInt _ -> pr " rv = Val_int (r);\n"
+ | RInt64 _ ->
+ pr " rv = caml_copy_int64 (r);\n"
+ | RBool _ -> pr " rv = Val_bool (r);\n"
+ | RConstString _ -> pr " rv = caml_copy_string (r);\n"
+ | RString _ ->
+ pr " rv = caml_copy_string (r);\n";
+ pr " free (r);\n"
+ | RStringList _ ->
+ pr " rv = caml_copy_string_array ((const char **) r);\n";
+ pr " for (i = 0; r[i] != NULL; ++i) free (r[i]);\n";
+ pr " free (r);\n"
+ | RIntBool _ ->
+ pr " rv = caml_alloc (2, 0);\n";
+ pr " Store_field (rv, 0, Val_int (r->i));\n";
+ pr " Store_field (rv, 1, Val_bool (r->b));\n";
+ pr " guestfs_free_int_bool (r);\n";
+ | RPVList _ ->
+ pr " rv = copy_lvm_pv_list (r);\n";
+ pr " guestfs_free_lvm_pv_list (r);\n";
+ | RVGList _ ->
+ pr " rv = copy_lvm_vg_list (r);\n";
+ pr " guestfs_free_lvm_vg_list (r);\n";
+ | RLVList _ ->
+ pr " rv = copy_lvm_lv_list (r);\n";
+ pr " guestfs_free_lvm_lv_list (r);\n";
+ | RStat _ ->
+ pr " rv = copy_stat (r);\n";
+ pr " free (r);\n";
+ | RStatVFS _ ->
+ pr " rv = copy_statvfs (r);\n";
+ pr " free (r);\n";
+ | RHashtable _ ->
+ pr " rv = copy_table (r);\n";
+ pr " for (i = 0; r[i] != NULL; ++i) free (r[i]);\n";
+ pr " free (r);\n";
+ );
+
+ pr " CAMLreturn (rv);\n";
+ pr "}\n";
+ pr "\n";
+
+ if List.length params > 5 then (
+ pr "CAMLprim value\n";
+ pr "ocaml_guestfs_%s_byte (value *argv, int argn)\n" name;
+ pr "{\n";
+ pr " return ocaml_guestfs_%s (argv[0]" name;
+ iteri (fun i _ -> pr ", argv[%d]" i) (List.tl params);
+ pr ");\n";
pr "}\n";
- pr "\n";
- ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
+ pr "\n"
+ )
+ ) all_functions
+
+and generate_ocaml_lvm_structure_decls () =
+ List.iter (
+ fun (typ, cols) ->
+ pr "type lvm_%s = {\n" typ;
+ List.iter (
+ function
+ | name, `String -> pr " %s : string;\n" name
+ | name, `UUID -> pr " %s : string;\n" name
+ | name, `Bytes -> pr " %s : int64;\n" name
+ | name, `Int -> pr " %s : int64;\n" name
+ | name, `OptPercent -> pr " %s : float option;\n" name
+ ) cols;
+ pr "}\n";
+ pr "\n"
+ ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols]
+
+and generate_ocaml_stat_structure_decls () =
+ List.iter (
+ fun (typ, cols) ->
+ pr "type %s = {\n" typ;
+ List.iter (
+ function
+ | name, `Int -> pr " %s : int64;\n" name
+ ) cols;
+ pr "}\n";
+ pr "\n"
+ ) ["stat", stat_cols; "statvfs", statvfs_cols]
+
+and generate_ocaml_prototype ?(is_external = false) name style =
+ if is_external then pr "external " else pr "val ";
+ pr "%s : t -> " name;
+ List.iter (
+ function
+ | String _ | FileIn _ | FileOut _ -> pr "string -> "
+ | OptString _ -> pr "string option -> "
+ | StringList _ -> pr "string array -> "
+ | Bool _ -> pr "bool -> "
+ | Int _ -> pr "int -> "
+ ) (snd style);
+ (match fst style with
+ | RErr -> pr "unit" (* all errors are turned into exceptions *)
+ | RInt _ -> pr "int"
+ | RInt64 _ -> pr "int64"
+ | RBool _ -> pr "bool"
+ | RConstString _ -> pr "string"
+ | RString _ -> pr "string"
+ | RStringList _ -> pr "string array"
+ | RIntBool _ -> pr "int * bool"
+ | RPVList _ -> pr "lvm_pv array"
+ | RVGList _ -> pr "lvm_vg array"
+ | RLVList _ -> pr "lvm_lv array"
+ | RStat _ -> pr "stat"
+ | RStatVFS _ -> pr "statvfs"
+ | RHashtable _ -> pr "(string * string) list"
+ );
+ if is_external then (
+ pr " = ";
+ if List.length (snd style) + 1 > 5 then
+ pr "\"ocaml_guestfs_%s_byte\" " name;
+ pr "\"ocaml_guestfs_%s\"" name
+ );
+ pr "\n"
+
+(* Generate Perl xs code, a sort of crazy variation of C with macros. *)
+and generate_perl_xs () =
+ generate_header CStyle LGPLv2;
+
+ pr "\
+#include \"EXTERN.h\"
+#include \"perl.h\"
+#include \"XSUB.h\"
+
+#include <guestfs.h>
+
+#ifndef PRId64
+#define PRId64 \"lld\"
+#endif
+
+static SV *
+my_newSVll(long long val) {
+#ifdef USE_64_BIT_ALL
+ return newSViv(val);
+#else
+ char buf[100];
+ int len;
+ len = snprintf(buf, 100, \"%%\" PRId64, val);
+ return newSVpv(buf, len);
+#endif
+}
+
+#ifndef PRIu64
+#define PRIu64 \"llu\"
+#endif
+
+static SV *
+my_newSVull(unsigned long long val) {
+#ifdef USE_64_BIT_ALL
+ return newSVuv(val);
+#else
+ char buf[100];
+ int len;
+ len = snprintf(buf, 100, \"%%\" PRIu64, val);
+ return newSVpv(buf, len);
+#endif
+}
+
+/* http://www.perlmonks.org/?node_id=680842 */
+static char **
+XS_unpack_charPtrPtr (SV *arg) {
+ char **ret;
+ AV *av;
+ I32 i;
+
+ if (!arg || !SvOK (arg) || !SvROK (arg) || SvTYPE (SvRV (arg)) != SVt_PVAV)
+ croak (\"array reference expected\");
+
+ av = (AV *)SvRV (arg);
+ ret = malloc ((av_len (av) + 1 + 1) * sizeof (char *));
+ if (!ret)
+ croak (\"malloc failed\");
+
+ for (i = 0; i <= av_len (av); i++) {
+ SV **elem = av_fetch (av, i, 0);
+
+ if (!elem || !*elem)
+ croak (\"missing element in list\");
+
+ ret[i] = SvPV_nolen (*elem);
+ }
+
+ ret[i] = NULL;
+
+ return ret;
+}
+
+MODULE = Sys::Guestfs PACKAGE = Sys::Guestfs
+
+PROTOTYPES: ENABLE
+
+guestfs_h *
+_create ()
+ CODE:
+ RETVAL = guestfs_create ();
+ if (!RETVAL)
+ croak (\"could not create guestfs handle\");
+ guestfs_set_error_handler (RETVAL, NULL, NULL);
+ OUTPUT:
+ RETVAL
+
+void
+DESTROY (g)
+ guestfs_h *g;
+ PPCODE:
+ guestfs_close (g);
+
+";
- (* run_<action> actions *)
List.iter (
- fun (name, style, _, flags, _, _, _) ->
- pr "static int run_%s (const char *cmd, int argc, char *argv[])\n" name;
- pr "{\n";
+ fun (name, style, _, _, _, _, _) ->
(match fst style with
- | RErr
- | RInt _
- | RBool _ -> pr " int r;\n"
- | RConstString _ -> pr " const char *r;\n"
- | RString _ -> pr " char *r;\n"
- | RStringList _ -> pr " char **r;\n"
- | RIntBool _ -> pr " struct guestfs_int_bool *r;\n"
- | RPVList _ -> pr " struct guestfs_lvm_pv_list *r;\n"
- | RVGList _ -> pr " struct guestfs_lvm_vg_list *r;\n"
- | RLVList _ -> pr " struct guestfs_lvm_lv_list *r;\n"
+ | RErr -> pr "void\n"
+ | RInt _ -> pr "SV *\n"
+ | RInt64 _ -> pr "SV *\n"
+ | RBool _ -> pr "SV *\n"
+ | RConstString _ -> pr "SV *\n"
+ | RString _ -> pr "SV *\n"
+ | RStringList _
+ | RIntBool _
+ | RPVList _ | RVGList _ | RLVList _
+ | RStat _ | RStatVFS _
+ | RHashtable _ ->
+ pr "void\n" (* all lists returned implictly on the stack *)
);
- List.iter (
- function
- | String n
- | OptString n -> pr " const char *%s;\n" n
- | StringList n -> pr " char **%s;\n" n
- | Bool n -> pr " int %s;\n" n
- | Int n -> pr " int %s;\n" n
- ) (snd style);
-
- (* Check and convert parameters. *)
- let argc_expected = List.length (snd style) in
- pr " if (argc != %d) {\n" argc_expected;
- pr " fprintf (stderr, \"%%s should have %d parameter(s)\\n\", cmd);\n"
- argc_expected;
- pr " fprintf (stderr, \"type 'help %%s' for help on %%s\\n\", cmd, cmd);\n";
- pr " return -1;\n";
- pr " }\n";
+ (* Call and arguments. *)
+ pr "%s " name;
+ generate_call_args ~handle:"g" (snd style);
+ pr "\n";
+ pr " guestfs_h *g;\n";
iteri (
fun i ->
function
- | String name -> pr " %s = argv[%d];\n" name i
- | OptString name ->
- pr " %s = strcmp (argv[%d], \"\") != 0 ? argv[%d] : NULL;\n"
- name i i
- | StringList name ->
- pr " %s = parse_string_list (argv[%d]);\n" name i
- | Bool name ->
- pr " %s = is_true (argv[%d]) ? 1 : 0;\n" name i
- | Int name ->
- pr " %s = atoi (argv[%d]);\n" name i
+ | String n | FileIn n | FileOut n -> pr " char *%s;\n" n
+ | OptString n ->
+ (* http://www.perlmonks.org/?node_id=554277
+ * Note that the implicit handle argument means we have
+ * to add 1 to the ST(x) operator.
+ *)
+ pr " char *%s = SvOK(ST(%d)) ? SvPV_nolen(ST(%d)) : NULL;\n" n (i+1) (i+1)
+ | StringList n -> pr " char **%s;\n" n
+ | Bool n -> pr " int %s;\n" n
+ | Int n -> pr " int %s;\n" n
) (snd style);
- (* Call C API function. *)
- let fn =
- try find_map (function FishAction n -> Some n | _ -> None) flags
- with Not_found -> sprintf "guestfs_%s" name in
- pr " r = %s " fn;
- generate_call_args ~handle:"g" style;
- pr ";\n";
+ let do_cleanups () =
+ List.iter (
+ function
+ | String _ | OptString _ | Bool _ | Int _
+ | FileIn _ | FileOut _ -> ()
+ | StringList n -> pr " free (%s);\n" n
+ ) (snd style)
+ in
- (* Check return value for errors and display command results. *)
+ (* Code. *)
(match fst style with
- | RErr -> pr " return r;\n"
- | RInt _ ->
- pr " if (r == -1) return -1;\n";
- pr " if (r) printf (\"%%d\\n\", r);\n";
- pr " return 0;\n"
- | RBool _ ->
- pr " if (r == -1) return -1;\n";
- pr " if (r) printf (\"true\\n\"); else printf (\"false\\n\");\n";
- pr " return 0;\n"
- | RConstString _ ->
- pr " if (r == NULL) return -1;\n";
- pr " printf (\"%%s\\n\", r);\n";
- pr " return 0;\n"
- | RString _ ->
- pr " if (r == NULL) return -1;\n";
- pr " printf (\"%%s\\n\", r);\n";
- pr " free (r);\n";
- pr " return 0;\n"
- | RStringList _ ->
- pr " if (r == NULL) return -1;\n";
- pr " print_strings (r);\n";
- pr " free_strings (r);\n";
- pr " return 0;\n"
- | RIntBool _ ->
- pr " if (r == NULL) return -1;\n";
- pr " printf (\"%%d, %%s\\n\", r->i,\n";
- pr " r->b ? \"true\" : \"false\");\n";
- pr " guestfs_free_int_bool (r);\n";
- pr " return 0;\n"
- | RPVList _ ->
- pr " if (r == NULL) return -1;\n";
- pr " print_pv_list (r);\n";
- pr " guestfs_free_lvm_pv_list (r);\n";
- pr " return 0;\n"
- | RVGList _ ->
- pr " if (r == NULL) return -1;\n";
- pr " print_vg_list (r);\n";
- pr " guestfs_free_lvm_vg_list (r);\n";
- pr " return 0;\n"
- | RLVList _ ->
- pr " if (r == NULL) return -1;\n";
- pr " print_lv_list (r);\n";
- pr " guestfs_free_lvm_lv_list (r);\n";
- pr " return 0;\n"
+ | RErr ->
+ pr "PREINIT:\n";
+ pr " int r;\n";
+ pr " PPCODE:\n";
+ pr " r = guestfs_%s " name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+ do_cleanups ();
+ pr " if (r == -1)\n";
+ pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+ | RInt n
+ | RBool n ->
+ pr "PREINIT:\n";
+ pr " int %s;\n" n;
+ pr " CODE:\n";
+ pr " %s = guestfs_%s " n name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+ do_cleanups ();
+ pr " if (%s == -1)\n" n;
+ pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+ pr " RETVAL = newSViv (%s);\n" n;
+ pr " OUTPUT:\n";
+ pr " RETVAL\n"
+ | RInt64 n ->
+ pr "PREINIT:\n";
+ pr " int64_t %s;\n" n;
+ pr " CODE:\n";
+ pr " %s = guestfs_%s " n name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+ do_cleanups ();
+ pr " if (%s == -1)\n" n;
+ pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+ pr " RETVAL = my_newSVll (%s);\n" n;
+ pr " OUTPUT:\n";
+ pr " RETVAL\n"
+ | RConstString n ->
+ pr "PREINIT:\n";
+ pr " const char *%s;\n" n;
+ pr " CODE:\n";
+ pr " %s = guestfs_%s " n name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+ do_cleanups ();
+ pr " if (%s == NULL)\n" n;
+ pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+ pr " RETVAL = newSVpv (%s, 0);\n" n;
+ pr " OUTPUT:\n";
+ pr " RETVAL\n"
+ | RString n ->
+ pr "PREINIT:\n";
+ pr " char *%s;\n" n;
+ pr " CODE:\n";
+ pr " %s = guestfs_%s " n name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+ do_cleanups ();
+ pr " if (%s == NULL)\n" n;
+ pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+ pr " RETVAL = newSVpv (%s, 0);\n" n;
+ pr " free (%s);\n" n;
+ pr " OUTPUT:\n";
+ pr " RETVAL\n"
+ | RStringList n | RHashtable n ->
+ pr "PREINIT:\n";
+ pr " char **%s;\n" n;
+ pr " int i, n;\n";
+ pr " PPCODE:\n";
+ pr " %s = guestfs_%s " n name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+ do_cleanups ();
+ pr " if (%s == NULL)\n" n;
+ pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+ pr " for (n = 0; %s[n] != NULL; ++n) /**/;\n" n;
+ pr " EXTEND (SP, n);\n";
+ pr " for (i = 0; i < n; ++i) {\n";
+ pr " PUSHs (sv_2mortal (newSVpv (%s[i], 0)));\n" n;
+ pr " free (%s[i]);\n" n;
+ pr " }\n";
+ pr " free (%s);\n" n;
+ | RIntBool _ ->
+ pr "PREINIT:\n";
+ pr " struct guestfs_int_bool *r;\n";
+ pr " PPCODE:\n";
+ pr " r = guestfs_%s " name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+ do_cleanups ();
+ pr " if (r == NULL)\n";
+ pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+ pr " EXTEND (SP, 2);\n";
+ pr " PUSHs (sv_2mortal (newSViv (r->i)));\n";
+ pr " PUSHs (sv_2mortal (newSViv (r->b)));\n";
+ pr " guestfs_free_int_bool (r);\n";
+ | RPVList n ->
+ generate_perl_lvm_code "pv" pv_cols name style n do_cleanups
+ | RVGList n ->
+ generate_perl_lvm_code "vg" vg_cols name style n do_cleanups
+ | RLVList n ->
+ generate_perl_lvm_code "lv" lv_cols name style n do_cleanups
+ | RStat n ->
+ generate_perl_stat_code "stat" stat_cols name style n do_cleanups
+ | RStatVFS n ->
+ generate_perl_stat_code
+ "statvfs" statvfs_cols name style n do_cleanups
);
- pr "}\n";
+
pr "\n"
- ) all_functions;
+ ) all_functions
- (* run_action function *)
- pr "int run_action (const char *cmd, int argc, char *argv[])\n";
- pr "{\n";
+and generate_perl_lvm_code typ cols name style n do_cleanups =
+ pr "PREINIT:\n";
+ pr " struct guestfs_lvm_%s_list *%s;\n" typ n;
+ pr " int i;\n";
+ pr " HV *hv;\n";
+ pr " PPCODE:\n";
+ pr " %s = guestfs_%s " n name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+ do_cleanups ();
+ pr " if (%s == NULL)\n" n;
+ pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+ pr " EXTEND (SP, %s->len);\n" n;
+ pr " for (i = 0; i < %s->len; ++i) {\n" n;
+ pr " hv = newHV ();\n";
List.iter (
- fun (name, _, _, flags, _, _, _) ->
- let name2 = replace_char name '_' '-' in
- let alias =
- try find_map (function FishAlias n -> Some n | _ -> None) flags
- with Not_found -> name in
- pr " if (";
- pr "strcasecmp (cmd, \"%s\") == 0" name;
- if name <> name2 then
- pr " || strcasecmp (cmd, \"%s\") == 0" name2;
- if name <> alias then
- pr " || strcasecmp (cmd, \"%s\") == 0" alias;
- pr ")\n";
- pr " return run_%s (cmd, argc, argv);\n" name;
- pr " else\n";
- ) all_functions;
- pr " {\n";
- pr " fprintf (stderr, \"%%s: unknown command\\n\", cmd);\n";
- pr " return -1;\n";
- pr " }\n";
- pr " return 0;\n";
- pr "}\n";
- pr "\n"
-
-(* Generate the POD documentation for guestfish. *)
-and generate_fish_actions_pod () =
- let all_functions_sorted =
- List.filter (
- fun (_, _, _, flags, _, _, _) -> not (List.mem NotInFish flags)
- ) all_functions_sorted in
+ function
+ | name, `String ->
+ pr " (void) hv_store (hv, \"%s\", %d, newSVpv (%s->val[i].%s, 0), 0);\n"
+ name (String.length name) n name
+ | name, `UUID ->
+ pr " (void) hv_store (hv, \"%s\", %d, newSVpv (%s->val[i].%s, 32), 0);\n"
+ name (String.length name) n name
+ | name, `Bytes ->
+ pr " (void) hv_store (hv, \"%s\", %d, my_newSVull (%s->val[i].%s), 0);\n"
+ name (String.length name) n name
+ | name, `Int ->
+ pr " (void) hv_store (hv, \"%s\", %d, my_newSVll (%s->val[i].%s), 0);\n"
+ name (String.length name) n name
+ | name, `OptPercent ->
+ pr " (void) hv_store (hv, \"%s\", %d, newSVnv (%s->val[i].%s), 0);\n"
+ name (String.length name) n name
+ ) cols;
+ pr " PUSHs (sv_2mortal ((SV *) hv));\n";
+ pr " }\n";
+ pr " guestfs_free_lvm_%s_list (%s);\n" typ n
+and generate_perl_stat_code typ cols name style n do_cleanups =
+ pr "PREINIT:\n";
+ pr " struct guestfs_%s *%s;\n" typ n;
+ pr " PPCODE:\n";
+ pr " %s = guestfs_%s " n name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+ do_cleanups ();
+ pr " if (%s == NULL)\n" n;
+ pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+ pr " EXTEND (SP, %d);\n" (List.length cols);
List.iter (
- fun (name, style, _, flags, _, _, longdesc) ->
- let longdesc = replace_str longdesc "C<guestfs_" "C<" in
- let name = replace_char name '_' '-' in
- let alias =
- try find_map (function FishAlias n -> Some n | _ -> None) flags
- with Not_found -> name in
+ function
+ | name, `Int ->
+ pr " PUSHs (sv_2mortal (my_newSVll (%s->%s)));\n" n name
+ ) cols;
+ pr " free (%s);\n" n
- pr "=head2 %s" name;
- if name <> alias then
- pr " | %s" alias;
- pr "\n";
- pr "\n";
- pr " %s" name;
- List.iter (
- function
- | String n -> pr " %s" n
- | OptString n -> pr " %s" n
- | StringList n -> pr " %s,..." n
- | Bool _ -> pr " true|false"
- | Int n -> pr " %s" n
- ) (snd style);
- pr "\n";
- pr "\n";
- pr "%s\n\n" longdesc;
+(* Generate Sys/Guestfs.pm. *)
+and generate_perl_pm () =
+ generate_header HashStyle LGPLv2;
- if List.mem ProtocolLimitWarning flags then
- pr "%s\n\n" protocol_limit_warning;
+ pr "\
+=pod
- if List.mem DangerWillRobinson flags then
- pr "%s\n\n" danger_will_robinson
- ) all_functions_sorted
+=head1 NAME
-(* Generate a C function prototype. *)
-and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
- ?(single_line = false) ?(newline = false) ?(in_daemon = false)
- ?(prefix = "")
- ?handle name style =
- if extern then pr "extern ";
- if static then pr "static ";
- (match fst style with
- | RErr -> pr "int "
- | RInt _ -> pr "int "
- | RBool _ -> pr "int "
- | RConstString _ -> pr "const char *"
- | RString _ -> pr "char *"
- | RStringList _ -> pr "char **"
- | RIntBool _ ->
- if not in_daemon then pr "struct guestfs_int_bool *"
- else pr "guestfs_%s_ret *" name
- | RPVList _ ->
- if not in_daemon then pr "struct guestfs_lvm_pv_list *"
- else pr "guestfs_lvm_int_pv_list *"
- | RVGList _ ->
- if not in_daemon then pr "struct guestfs_lvm_vg_list *"
- else pr "guestfs_lvm_int_vg_list *"
- | RLVList _ ->
- if not in_daemon then pr "struct guestfs_lvm_lv_list *"
- else pr "guestfs_lvm_int_lv_list *"
- );
- pr "%s%s (" prefix name;
- if handle = None && List.length (snd style) = 0 then
- pr "void"
- else (
- let comma = ref false in
- (match handle with
- | None -> ()
- | Some handle -> pr "guestfs_h *%s" handle; comma := true
- );
- let next () =
- if !comma then (
- if single_line then pr ", " else pr ",\n\t\t"
- );
- comma := true
- in
- List.iter (
- function
- | String n -> next (); pr "const char *%s" n
- | OptString n -> next (); pr "const char *%s" n
- | StringList n -> next (); pr "char * const* const %s" n
- | Bool n -> next (); pr "int %s" n
- | Int n -> next (); pr "int %s" n
- ) (snd style);
- );
- pr ")";
- if semicolon then pr ";";
- if newline then pr "\n"
+Sys::Guestfs - Perl bindings for libguestfs
-(* Generate C call arguments, eg "(handle, foo, bar)" *)
-and generate_call_args ?handle style =
- pr "(";
- let comma = ref false in
- (match handle with
- | None -> ()
- | Some handle -> pr "%s" handle; comma := true
- );
- List.iter (
- fun arg ->
- if !comma then pr ", ";
- comma := true;
- match arg with
- | String n
- | OptString n
- | StringList n
- | Bool n
- | Int n -> pr "%s" n
- ) (snd style);
- pr ")"
+=head1 SYNOPSIS
-(* Generate the OCaml bindings interface. *)
-and generate_ocaml_mli () =
- generate_header OCamlStyle LGPLv2;
+ use Sys::Guestfs;
+
+ my $h = Sys::Guestfs->new ();
+ $h->add_drive ('guest.img');
+ $h->launch ();
+ $h->wait_ready ();
+ $h->mount ('/dev/sda1', '/');
+ $h->touch ('/hello');
+ $h->sync ();
+
+=head1 DESCRIPTION
+
+The C<Sys::Guestfs> module provides a Perl XS binding to the
+libguestfs API for examining and modifying virtual machine
+disk images.
+
+Amongst the things this is good for: making batch configuration
+changes to guests, getting disk used/free statistics (see also:
+virt-df), migrating between virtualization systems (see also:
+virt-p2v), performing partial backups, performing partial guest
+clones, cloning guests and changing registry/UUID/hostname info, and
+much else besides.
+
+Libguestfs uses Linux kernel and qemu code, and can access any type of
+guest filesystem that Linux and qemu can, including but not limited
+to: ext2/3/4, btrfs, FAT and NTFS, LVM, many different disk partition
+schemes, qcow, qcow2, vmdk.
+
+Libguestfs provides ways to enumerate guest storage (eg. partitions,
+LVs, what filesystem is in each LV, etc.). It can also run commands
+in the context of the guest. Also you can access filesystems over FTP.
+
+=head1 ERRORS
+
+All errors turn into calls to C<croak> (see L<Carp(3)>).
+
+=head1 METHODS
+
+=over 4
+
+=cut
+
+package Sys::Guestfs;
+
+use strict;
+use warnings;
+
+require XSLoader;
+XSLoader::load ('Sys::Guestfs');
- pr "\
-(** For API documentation you should refer to the C API
- in the guestfs(3) manual page. The OCaml API uses almost
- exactly the same calls. *)
+=item $h = Sys::Guestfs->new ();
-type t
-(** A [guestfs_h] handle. *)
+Create a new guestfs handle.
-exception Error of string
-(** This exception is raised when there is an error. *)
+=cut
-val create : unit -> t
+sub new {
+ my $proto = shift;
+ my $class = ref ($proto) || $proto;
-val close : t -> unit
-(** Handles are closed by the garbage collector when they become
- unreferenced, but callers can also call this in order to
- provide predictable cleanup. *)
+ my $self = Sys::Guestfs::_create ();
+ bless $self, $class;
+ return $self;
+}
";
- generate_ocaml_lvm_structure_decls ();
- (* The actions. *)
+ (* Actions. We only need to print documentation for these as
+ * they are pulled in from the XS code automatically.
+ *)
List.iter (
- fun (name, style, _, _, _, shortdesc, _) ->
- generate_ocaml_prototype name style;
- pr "(** %s *)\n" shortdesc;
- pr "\n"
- ) all_functions
-
-(* Generate the OCaml bindings implementation. *)
-and generate_ocaml_ml () =
- generate_header OCamlStyle LGPLv2;
+ fun (name, style, _, flags, _, _, longdesc) ->
+ if not (List.mem NotInDocs flags) then (
+ let longdesc = replace_str longdesc "C<guestfs_" "C<$h-E<gt>" in
+ pr "=item ";
+ generate_perl_prototype name style;
+ pr "\n\n";
+ pr "%s\n\n" longdesc;
+ if List.mem ProtocolLimitWarning flags then
+ pr "%s\n\n" protocol_limit_warning;
+ if List.mem DangerWillRobinson flags then
+ pr "%s\n\n" danger_will_robinson
+ )
+ ) all_functions_sorted;
+ (* End of file. *)
pr "\
-type t
-exception Error of string
-external create : unit -> t = \"ocaml_guestfs_create\"
-external close : t -> unit = \"ocaml_guestfs_close\"
+=cut
-let () =
- Callback.register_exception \"ocaml_guestfs_error\" (Error \"\")
+1;
-";
+=back
- generate_ocaml_lvm_structure_decls ();
+=head1 COPYRIGHT
- (* The actions. *)
- List.iter (
- fun (name, style, _, _, _, shortdesc, _) ->
- generate_ocaml_prototype ~is_external:true name style;
- ) all_functions
+Copyright (C) 2009 Red Hat Inc.
-(* Generate the OCaml bindings C implementation. *)
-and generate_ocaml_c () =
- generate_header CStyle LGPLv2;
+=head1 LICENSE
- pr "#include <stdio.h>\n";
- pr "#include <stdlib.h>\n";
- pr "#include <string.h>\n";
- pr "\n";
- pr "#include <caml/config.h>\n";
- pr "#include <caml/alloc.h>\n";
- pr "#include <caml/callback.h>\n";
- pr "#include <caml/fail.h>\n";
- pr "#include <caml/memory.h>\n";
- pr "#include <caml/mlvalues.h>\n";
- pr "#include <caml/signals.h>\n";
- pr "\n";
- pr "#include <guestfs.h>\n";
- pr "\n";
- pr "#include \"guestfs_c.h\"\n";
- pr "\n";
+Please see the file COPYING.LIB for the full license.
- (* LVM struct copy functions. *)
- List.iter (
- fun (typ, cols) ->
- let has_optpercent_col =
- List.exists (function (_, `OptPercent) -> true | _ -> false) cols in
+=head1 SEE ALSO
- pr "static CAMLprim value\n";
- pr "copy_lvm_%s (const struct guestfs_lvm_%s *%s)\n" typ typ typ;
- pr "{\n";
- pr " CAMLparam0 ();\n";
- if has_optpercent_col then
- pr " CAMLlocal3 (rv, v, v2);\n"
- else
- pr " CAMLlocal2 (rv, v);\n";
- pr "\n";
- pr " rv = caml_alloc (%d, 0);\n" (List.length cols);
- iteri (
- fun i col ->
- (match col with
- | name, `String ->
- pr " v = caml_copy_string (%s->%s);\n" typ name
- | name, `UUID ->
- pr " v = caml_alloc_string (32);\n";
- pr " memcpy (String_val (v), %s->%s, 32);\n" typ name
- | name, `Bytes
- | name, `Int ->
- pr " v = caml_copy_int64 (%s->%s);\n" typ name
- | name, `OptPercent ->
- pr " if (%s->%s >= 0) { /* Some %s */\n" typ name name;
- pr " v2 = caml_copy_double (%s->%s);\n" typ name;
- pr " v = caml_alloc (1, 0);\n";
- pr " Store_field (v, 0, v2);\n";
- pr " } else /* None */\n";
- pr " v = Val_int (0);\n";
- );
- pr " Store_field (rv, %d, v);\n" i
- ) cols;
- pr " CAMLreturn (rv);\n";
- pr "}\n";
- pr "\n";
+L<guestfs(3)>, L<guestfish(1)>.
- pr "static CAMLprim value\n";
- pr "copy_lvm_%s_list (const struct guestfs_lvm_%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_lvm_%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";
- ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
+=cut
+"
+and generate_perl_prototype name style =
+ (match fst style with
+ | RErr -> ()
+ | RBool n
+ | RInt n
+ | RInt64 n
+ | RConstString n
+ | RString n -> pr "$%s = " n
+ | RIntBool (n, m) -> pr "($%s, $%s) = " n m
+ | RStringList n
+ | RPVList n
+ | RVGList n
+ | RLVList n -> pr "@%s = " n
+ | RStat n
+ | RStatVFS n
+ | RHashtable n -> pr "%%%s = " n
+ );
+ pr "$h->%s (" name;
+ let comma = ref false in
List.iter (
- fun (name, style, _, _, _, _, _) ->
- let params =
- "gv" :: List.map (fun arg -> name_of_argt arg ^ "v") (snd style) in
+ fun arg ->
+ if !comma then pr ", ";
+ comma := true;
+ match arg with
+ | String n | OptString n | Bool n | Int n | FileIn n | FileOut n ->
+ pr "$%s" n
+ | StringList n ->
+ pr "\\@%s" n
+ ) (snd style);
+ pr ");"
- pr "CAMLprim value\n";
- pr "ocaml_guestfs_%s (value %s" name (List.hd params);
- List.iter (pr ", value %s") (List.tl params);
- pr ")\n";
- pr "{\n";
+(* Generate Python C module. *)
+and generate_python_c () =
+ generate_header CStyle LGPLv2;
- (match params with
- | p1 :: p2 :: p3 :: p4 :: p5 :: rest ->
- pr " CAMLparam5 (%s);\n" (String.concat ", " [p1; p2; p3; p4; p5]);
- pr " CAMLxparam%d (%s);\n"
- (List.length rest) (String.concat ", " rest)
- | ps ->
- pr " CAMLparam%d (%s);\n" (List.length ps) (String.concat ", " ps)
- );
- pr " CAMLlocal1 (rv);\n";
- pr "\n";
+ pr "\
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
- pr " guestfs_h *g = Guestfs_val (gv);\n";
- pr " if (g == NULL)\n";
- pr " caml_failwith (\"%s: used handle after closing it\");\n" name;
- pr "\n";
+#include <Python.h>
- List.iter (
- function
- | String n ->
- pr " const char *%s = String_val (%sv);\n" n n
- | OptString n ->
- pr " const char *%s =\n" n;
- pr " %sv != Val_int (0) ? String_val (Field (%sv, 0)) : NULL;\n"
- n n
- | StringList n ->
- pr " char **%s = ocaml_guestfs_strings_val (%sv);\n" n n
- | Bool n ->
- pr " int %s = Bool_val (%sv);\n" n n
- | Int n ->
- pr " int %s = Int_val (%sv);\n" n n
- ) (snd style);
- let error_code =
- match fst style with
- | RErr -> pr " int r;\n"; "-1"
- | RInt _ -> pr " int r;\n"; "-1"
- | RBool _ -> pr " int r;\n"; "-1"
- | RConstString _ -> pr " const char *r;\n"; "NULL"
- | RString _ -> pr " char *r;\n"; "NULL"
- | RStringList _ ->
- pr " int i;\n";
- pr " char **r;\n";
- "NULL"
- | RIntBool _ ->
- pr " struct guestfs_int_bool *r;\n";
- "NULL"
- | RPVList _ ->
- pr " struct guestfs_lvm_pv_list *r;\n";
- "NULL"
- | RVGList _ ->
- pr " struct guestfs_lvm_vg_list *r;\n";
- "NULL"
- | RLVList _ ->
- pr " struct guestfs_lvm_lv_list *r;\n";
- "NULL" in
- pr "\n";
+#include \"guestfs.h\"
+
+typedef struct {
+ PyObject_HEAD
+ guestfs_h *g;
+} Pyguestfs_Object;
+
+static guestfs_h *
+get_handle (PyObject *obj)
+{
+ assert (obj);
+ assert (obj != Py_None);
+ return ((Pyguestfs_Object *) obj)->g;
+}
+
+static PyObject *
+put_handle (guestfs_h *g)
+{
+ assert (g);
+ return
+ PyCObject_FromVoidPtrAndDesc ((void *) g, (char *) \"guestfs_h\", NULL);
+}
+
+/* This list should be freed (but not the strings) after use. */
+static const char **
+get_string_list (PyObject *obj)
+{
+ int i, len;
+ const char **r;
+
+ assert (obj);
- pr " caml_enter_blocking_section ();\n";
- pr " r = guestfs_%s " name;
- generate_call_args ~handle:"g" style;
- pr ";\n";
- pr " caml_leave_blocking_section ();\n";
+ if (!PyList_Check (obj)) {
+ PyErr_SetString (PyExc_RuntimeError, \"expecting a list parameter\");
+ return NULL;
+ }
- List.iter (
- function
- | StringList n ->
- pr " ocaml_guestfs_free_strings (%s);\n" n;
- | String _ | OptString _ | Bool _ | Int _ -> ()
- ) (snd style);
+ len = PyList_Size (obj);
+ r = malloc (sizeof (char *) * (len+1));
+ if (r == NULL) {
+ PyErr_SetString (PyExc_RuntimeError, \"get_string_list: out of memory\");
+ return NULL;
+ }
- pr " if (r == %s)\n" error_code;
- pr " ocaml_guestfs_raise_error (g, \"%s\");\n" name;
- pr "\n";
+ for (i = 0; i < len; ++i)
+ r[i] = PyString_AsString (PyList_GetItem (obj, i));
+ r[len] = NULL;
- (match fst style with
- | RErr -> pr " rv = Val_unit;\n"
- | RInt _ -> pr " rv = Val_int (r);\n"
- | RBool _ -> pr " rv = Val_bool (r);\n"
- | RConstString _ -> pr " rv = caml_copy_string (r);\n"
- | RString _ ->
- pr " rv = caml_copy_string (r);\n";
- pr " free (r);\n"
- | RStringList _ ->
- pr " rv = caml_copy_string_array ((const char **) r);\n";
- pr " for (i = 0; r[i] != NULL; ++i) free (r[i]);\n";
- pr " free (r);\n"
- | RIntBool _ ->
- pr " rv = caml_alloc (2, 0);\n";
- pr " Store_field (rv, 0, Val_int (r->i));\n";
- pr " Store_field (rv, 1, Val_bool (r->b));\n";
- pr " guestfs_free_int_bool (r);\n";
- | RPVList _ ->
- pr " rv = copy_lvm_pv_list (r);\n";
- pr " guestfs_free_lvm_pv_list (r);\n";
- | RVGList _ ->
- pr " rv = copy_lvm_vg_list (r);\n";
- pr " guestfs_free_lvm_vg_list (r);\n";
- | RLVList _ ->
- pr " rv = copy_lvm_lv_list (r);\n";
- pr " guestfs_free_lvm_lv_list (r);\n";
- );
+ return r;
+}
- pr " CAMLreturn (rv);\n";
- pr "}\n";
- pr "\n";
+static PyObject *
+put_string_list (char * const * const argv)
+{
+ PyObject *list;
+ int argc, i;
- if List.length params > 5 then (
- pr "CAMLprim value\n";
- pr "ocaml_guestfs_%s_byte (value *argv, int argn)\n" name;
- pr "{\n";
- pr " return ocaml_guestfs_%s (argv[0]" name;
- iteri (fun i _ -> pr ", argv[%d]" i) (List.tl params);
- pr ");\n";
- pr "}\n";
- pr "\n"
- )
- ) all_functions
+ for (argc = 0; argv[argc] != NULL; ++argc)
+ ;
-and generate_ocaml_lvm_structure_decls () =
- List.iter (
- fun (typ, cols) ->
- pr "type lvm_%s = {\n" typ;
- List.iter (
- function
- | name, `String -> pr " %s : string;\n" name
- | name, `UUID -> pr " %s : string;\n" name
- | name, `Bytes -> pr " %s : int64;\n" name
- | name, `Int -> pr " %s : int64;\n" name
- | name, `OptPercent -> pr " %s : float option;\n" name
- ) cols;
- pr "}\n";
- pr "\n"
- ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols]
+ list = PyList_New (argc);
+ for (i = 0; i < argc; ++i)
+ PyList_SetItem (list, i, PyString_FromString (argv[i]));
-and generate_ocaml_prototype ?(is_external = false) name style =
- if is_external then pr "external " else pr "val ";
- pr "%s : t -> " name;
- List.iter (
- function
- | String _ -> pr "string -> "
- | OptString _ -> pr "string option -> "
- | StringList _ -> pr "string array -> "
- | Bool _ -> pr "bool -> "
- | Int _ -> pr "int -> "
- ) (snd style);
- (match fst style with
- | RErr -> pr "unit" (* all errors are turned into exceptions *)
- | RInt _ -> pr "int"
- | RBool _ -> pr "bool"
- | RConstString _ -> pr "string"
- | RString _ -> pr "string"
- | RStringList _ -> pr "string array"
- | RIntBool _ -> pr "int * bool"
- | RPVList _ -> pr "lvm_pv array"
- | RVGList _ -> pr "lvm_vg array"
- | RLVList _ -> pr "lvm_lv array"
- );
- if is_external then (
- pr " = ";
- if List.length (snd style) + 1 > 5 then
- pr "\"ocaml_guestfs_%s_byte\" " name;
- pr "\"ocaml_guestfs_%s\"" name
- );
- pr "\n"
+ return list;
+}
-(* Generate Perl xs code, a sort of crazy variation of C with macros. *)
-and generate_perl_xs () =
- generate_header CStyle LGPLv2;
+static PyObject *
+put_table (char * const * const argv)
+{
+ PyObject *list, *item;
+ int argc, i;
- pr "\
-#include \"EXTERN.h\"
-#include \"perl.h\"
-#include \"XSUB.h\"
+ for (argc = 0; argv[argc] != NULL; ++argc)
+ ;
-#include <guestfs.h>
+ list = PyList_New (argc >> 1);
+ for (i = 0; i < argc; i += 2) {
+ item = PyTuple_New (2);
+ PyTuple_SetItem (item, 0, PyString_FromString (argv[i]));
+ PyTuple_SetItem (item, 1, PyString_FromString (argv[i+1]));
+ PyList_SetItem (list, i >> 1, item);
+ }
-#ifndef PRId64
-#define PRId64 \"lld\"
-#endif
+ return list;
+}
-static SV *
-my_newSVll(long long val) {
-#ifdef USE_64_BIT_ALL
- return newSViv(val);
-#else
- char buf[100];
- int len;
- len = snprintf(buf, 100, \"%%\" PRId64, val);
- return newSVpv(buf, len);
-#endif
+static void
+free_strings (char **argv)
+{
+ int argc;
+
+ for (argc = 0; argv[argc] != NULL; ++argc)
+ free (argv[argc]);
+ free (argv);
}
-#ifndef PRIu64
-#define PRIu64 \"llu\"
-#endif
+static PyObject *
+py_guestfs_create (PyObject *self, PyObject *args)
+{
+ guestfs_h *g;
-static SV *
-my_newSVull(unsigned long long val) {
-#ifdef USE_64_BIT_ALL
- return newSVuv(val);
-#else
- char buf[100];
- int len;
- len = snprintf(buf, 100, \"%%\" PRIu64, val);
- return newSVpv(buf, len);
-#endif
+ g = guestfs_create ();
+ if (g == NULL) {
+ PyErr_SetString (PyExc_RuntimeError,
+ \"guestfs.create: failed to allocate handle\");
+ return NULL;
+ }
+ guestfs_set_error_handler (g, NULL, NULL);
+ return put_handle (g);
}
-/* http://www.perlmonks.org/?node_id=680842 */
-static char **
-XS_unpack_charPtrPtr (SV *arg) {
- char **ret;
- AV *av;
- I32 i;
+static PyObject *
+py_guestfs_close (PyObject *self, PyObject *args)
+{
+ PyObject *py_g;
+ guestfs_h *g;
+
+ if (!PyArg_ParseTuple (args, (char *) \"O:guestfs_close\", &py_g))
+ return NULL;
+ g = get_handle (py_g);
+
+ guestfs_close (g);
+
+ Py_INCREF (Py_None);
+ return Py_None;
+}
+
+";
+
+ (* LVM structures, turned into Python dictionaries. *)
+ List.iter (
+ fun (typ, cols) ->
+ pr "static PyObject *\n";
+ pr "put_lvm_%s (struct guestfs_lvm_%s *%s)\n" typ typ typ;
+ pr "{\n";
+ pr " PyObject *dict;\n";
+ pr "\n";
+ pr " dict = PyDict_New ();\n";
+ List.iter (
+ function
+ | name, `String ->
+ pr " PyDict_SetItemString (dict, \"%s\",\n" name;
+ pr " PyString_FromString (%s->%s));\n"
+ typ name
+ | name, `UUID ->
+ pr " PyDict_SetItemString (dict, \"%s\",\n" name;
+ pr " PyString_FromStringAndSize (%s->%s, 32));\n"
+ typ name
+ | name, `Bytes ->
+ pr " PyDict_SetItemString (dict, \"%s\",\n" name;
+ pr " PyLong_FromUnsignedLongLong (%s->%s));\n"
+ typ name
+ | name, `Int ->
+ pr " PyDict_SetItemString (dict, \"%s\",\n" name;
+ pr " PyLong_FromLongLong (%s->%s));\n"
+ typ name
+ | name, `OptPercent ->
+ pr " if (%s->%s >= 0)\n" typ name;
+ pr " PyDict_SetItemString (dict, \"%s\",\n" name;
+ pr " PyFloat_FromDouble ((double) %s->%s));\n"
+ typ name;
+ pr " else {\n";
+ pr " Py_INCREF (Py_None);\n";
+ pr " PyDict_SetItemString (dict, \"%s\", Py_None);" name;
+ pr " }\n"
+ ) cols;
+ pr " return dict;\n";
+ pr "};\n";
+ pr "\n";
+
+ pr "static PyObject *\n";
+ pr "put_lvm_%s_list (struct guestfs_lvm_%s_list *%ss)\n" typ typ typ;
+ pr "{\n";
+ pr " PyObject *list;\n";
+ pr " int i;\n";
+ pr "\n";
+ pr " list = PyList_New (%ss->len);\n" typ;
+ pr " for (i = 0; i < %ss->len; ++i)\n" typ;
+ pr " PyList_SetItem (list, i, put_lvm_%s (&%ss->val[i]));\n" typ typ;
+ pr " return list;\n";
+ pr "};\n";
+ pr "\n"
+ ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
- if (!arg || !SvOK (arg) || !SvROK (arg) || SvTYPE (SvRV (arg)) != SVt_PVAV) {
- croak (\"array reference expected\");
- }
+ (* Stat structures, turned into Python dictionaries. *)
+ List.iter (
+ fun (typ, cols) ->
+ pr "static PyObject *\n";
+ pr "put_%s (struct guestfs_%s *%s)\n" typ typ typ;
+ pr "{\n";
+ pr " PyObject *dict;\n";
+ pr "\n";
+ pr " dict = PyDict_New ();\n";
+ List.iter (
+ function
+ | name, `Int ->
+ pr " PyDict_SetItemString (dict, \"%s\",\n" name;
+ pr " PyLong_FromLongLong (%s->%s));\n"
+ typ name
+ ) cols;
+ pr " return dict;\n";
+ pr "};\n";
+ pr "\n";
+ ) ["stat", stat_cols; "statvfs", statvfs_cols];
- av = (AV *)SvRV (arg);
- ret = (char **)malloc (av_len (av) + 1 + 1);
+ (* Python wrapper functions. *)
+ List.iter (
+ fun (name, style, _, _, _, _, _) ->
+ pr "static PyObject *\n";
+ pr "py_guestfs_%s (PyObject *self, PyObject *args)\n" name;
+ pr "{\n";
- for (i = 0; i <= av_len (av); i++) {
- SV **elem = av_fetch (av, i, 0);
+ pr " PyObject *py_g;\n";
+ pr " guestfs_h *g;\n";
+ pr " PyObject *py_r;\n";
- if (!elem || !*elem)
- croak (\"missing element in list\");
+ let error_code =
+ match fst style with
+ | RErr | RInt _ | RBool _ -> pr " int r;\n"; "-1"
+ | RInt64 _ -> pr " int64_t r;\n"; "-1"
+ | RConstString _ -> pr " const char *r;\n"; "NULL"
+ | RString _ -> pr " char *r;\n"; "NULL"
+ | RStringList _ | RHashtable _ -> pr " char **r;\n"; "NULL"
+ | RIntBool _ -> pr " struct guestfs_int_bool *r;\n"; "NULL"
+ | RPVList n -> pr " struct guestfs_lvm_pv_list *r;\n"; "NULL"
+ | RVGList n -> pr " struct guestfs_lvm_vg_list *r;\n"; "NULL"
+ | RLVList n -> pr " struct guestfs_lvm_lv_list *r;\n"; "NULL"
+ | RStat n -> pr " struct guestfs_stat *r;\n"; "NULL"
+ | RStatVFS n -> pr " struct guestfs_statvfs *r;\n"; "NULL" in
- ret[i] = SvPV_nolen (*elem);
- }
+ List.iter (
+ function
+ | String n | FileIn n | FileOut n -> pr " const char *%s;\n" n
+ | OptString n -> pr " const char *%s;\n" n
+ | StringList n ->
+ pr " PyObject *py_%s;\n" n;
+ pr " const char **%s;\n" n
+ | Bool n -> pr " int %s;\n" n
+ | Int n -> pr " int %s;\n" n
+ ) (snd style);
- ret[i] = NULL;
+ pr "\n";
- return ret;
-}
+ (* Convert the parameters. *)
+ pr " if (!PyArg_ParseTuple (args, (char *) \"O";
+ List.iter (
+ function
+ | String _ | FileIn _ | FileOut _ -> pr "s"
+ | OptString _ -> pr "z"
+ | StringList _ -> pr "O"
+ | Bool _ -> pr "i" (* XXX Python has booleans? *)
+ | Int _ -> pr "i"
+ ) (snd style);
+ pr ":guestfs_%s\",\n" name;
+ pr " &py_g";
+ List.iter (
+ function
+ | String n | FileIn n | FileOut n -> pr ", &%s" n
+ | OptString n -> pr ", &%s" n
+ | StringList n -> pr ", &py_%s" n
+ | Bool n -> pr ", &%s" n
+ | Int n -> pr ", &%s" n
+ ) (snd style);
-MODULE = Sys::Guestfs PACKAGE = Sys::Guestfs
+ pr "))\n";
+ pr " return NULL;\n";
-guestfs_h *
-_create ()
- CODE:
- RETVAL = guestfs_create ();
- if (!RETVAL)
- croak (\"could not create guestfs handle\");
- guestfs_set_error_handler (RETVAL, NULL, NULL);
- OUTPUT:
- RETVAL
+ pr " g = get_handle (py_g);\n";
+ List.iter (
+ function
+ | String _ | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ -> ()
+ | StringList n ->
+ pr " %s = get_string_list (py_%s);\n" n n;
+ pr " if (!%s) return NULL;\n" n
+ ) (snd style);
-void
-DESTROY (g)
- guestfs_h *g;
- PPCODE:
- guestfs_close (g);
+ pr "\n";
-";
+ pr " r = guestfs_%s " name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
- List.iter (
- fun (name, style, _, _, _, _, _) ->
- (match fst style with
- | RErr -> pr "void\n"
- | RInt _ -> pr "SV *\n"
- | RBool _ -> pr "SV *\n"
- | RConstString _ -> pr "SV *\n"
- | RString _ -> pr "SV *\n"
- | RStringList _
- | RIntBool _
- | RPVList _ | RVGList _ | RLVList _ ->
- pr "void\n" (* all lists returned implictly on the stack *)
- );
- (* Call and arguments. *)
- pr "%s " name;
- generate_call_args ~handle:"g" style;
- pr "\n";
- pr " guestfs_h *g;\n";
List.iter (
function
- | String n -> pr " char *%s;\n" n
- | OptString n -> pr " char *%s;\n" n
- | StringList n -> pr " char **%s;\n" n
- | Bool n -> pr " int %s;\n" n
- | Int n -> pr " int %s;\n" n
+ | String _ | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ -> ()
+ | StringList n ->
+ pr " free (%s);\n" n
) (snd style);
- let do_cleanups () =
- List.iter (
- function
- | String _
- | OptString _
- | Bool _
- | Int _ -> ()
- | StringList n -> pr " free (%s);\n" n
- ) (snd style)
- in
+ pr " if (r == %s) {\n" error_code;
+ pr " PyErr_SetString (PyExc_RuntimeError, guestfs_last_error (g));\n";
+ pr " return NULL;\n";
+ pr " }\n";
+ pr "\n";
- (* Code. *)
(match fst style with
| RErr ->
- pr " PPCODE:\n";
- pr " if (guestfs_%s " name;
- generate_call_args ~handle:"g" style;
- pr " == -1) {\n";
- do_cleanups ();
- pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
- pr " }\n"
- | RInt n
- | RBool n ->
- pr "PREINIT:\n";
- pr " int %s;\n" n;
- pr " CODE:\n";
- pr " %s = guestfs_%s " n name;
- generate_call_args ~handle:"g" style;
- pr ";\n";
- pr " if (%s == -1) {\n" n;
- do_cleanups ();
- pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
- pr " }\n";
- pr " RETVAL = newSViv (%s);\n" n;
- pr " OUTPUT:\n";
- pr " RETVAL\n"
- | RConstString n ->
- pr "PREINIT:\n";
- pr " const char *%s;\n" n;
- pr " CODE:\n";
- pr " %s = guestfs_%s " n name;
- generate_call_args ~handle:"g" style;
- pr ";\n";
- pr " if (%s == NULL) {\n" n;
- do_cleanups ();
- pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
- pr " }\n";
- pr " RETVAL = newSVpv (%s, 0);\n" n;
- pr " OUTPUT:\n";
- pr " RETVAL\n"
- | RString n ->
- pr "PREINIT:\n";
- pr " char *%s;\n" n;
- pr " CODE:\n";
- pr " %s = guestfs_%s " n name;
- generate_call_args ~handle:"g" style;
- pr ";\n";
- pr " if (%s == NULL) {\n" n;
- do_cleanups ();
- pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
- pr " }\n";
- pr " RETVAL = newSVpv (%s, 0);\n" n;
- pr " free (%s);\n" n;
- pr " OUTPUT:\n";
- pr " RETVAL\n"
- | RStringList n ->
- pr "PREINIT:\n";
- pr " char **%s;\n" n;
- pr " int i, n;\n";
- pr " PPCODE:\n";
- pr " %s = guestfs_%s " n name;
- generate_call_args ~handle:"g" style;
- pr ";\n";
- pr " if (%s == NULL) {\n" n;
- do_cleanups ();
- pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
- pr " }\n";
- pr " for (n = 0; %s[n] != NULL; ++n) /**/;\n" n;
- pr " EXTEND (SP, n);\n";
- pr " for (i = 0; i < n; ++i) {\n";
- pr " PUSHs (sv_2mortal (newSVpv (%s[i], 0)));\n" n;
- pr " free (%s[i]);\n" n;
- pr " }\n";
- pr " free (%s);\n" n;
- | RIntBool _ ->
- pr "PREINIT:\n";
- pr " struct guestfs_int_bool *r;\n";
- pr " PPCODE:\n";
- pr " r = guestfs_%s " name;
- generate_call_args ~handle:"g" style;
- pr ";\n";
- pr " if (r == NULL) {\n";
- do_cleanups ();
- pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
- pr " }\n";
- pr " EXTEND (SP, 2);\n";
- pr " PUSHs (sv_2mortal (newSViv (r->i)));\n";
- pr " PUSHs (sv_2mortal (newSViv (r->b)));\n";
- pr " guestfs_free_int_bool (r);\n";
- | RPVList n ->
- generate_perl_lvm_code "pv" pv_cols name style n;
- | RVGList n ->
- generate_perl_lvm_code "vg" vg_cols name style n;
- | RLVList n ->
- generate_perl_lvm_code "lv" lv_cols name style n;
- );
-
- do_cleanups ();
-
- pr "\n"
- ) all_functions
-
-and generate_perl_lvm_code typ cols name style n =
- pr "PREINIT:\n";
- pr " struct guestfs_lvm_%s_list *%s;\n" typ n;
- pr " int i;\n";
- pr " HV *hv;\n";
- pr " PPCODE:\n";
- pr " %s = guestfs_%s " n name;
- generate_call_args ~handle:"g" style;
- pr ";\n";
- pr " if (%s == NULL)\n" n;
- pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
- pr " EXTEND (SP, %s->len);\n" n;
- pr " for (i = 0; i < %s->len; ++i) {\n" n;
- pr " hv = newHV ();\n";
- List.iter (
- function
- | name, `String ->
- pr " (void) hv_store (hv, \"%s\", %d, newSVpv (%s->val[i].%s, 0), 0);\n"
- name (String.length name) n name
- | name, `UUID ->
- pr " (void) hv_store (hv, \"%s\", %d, newSVpv (%s->val[i].%s, 32), 0);\n"
- name (String.length name) n name
- | name, `Bytes ->
- pr " (void) hv_store (hv, \"%s\", %d, my_newSVull (%s->val[i].%s), 0);\n"
- name (String.length name) n name
- | name, `Int ->
- pr " (void) hv_store (hv, \"%s\", %d, my_newSVll (%s->val[i].%s), 0);\n"
- name (String.length name) n name
- | name, `OptPercent ->
- pr " (void) hv_store (hv, \"%s\", %d, newSVnv (%s->val[i].%s), 0);\n"
- name (String.length name) n name
- ) cols;
- pr " PUSHs (sv_2mortal ((SV *) hv));\n";
- pr " }\n";
- pr " guestfs_free_lvm_%s_list (%s);\n" typ n
+ pr " Py_INCREF (Py_None);\n";
+ pr " py_r = Py_None;\n"
+ | RInt _
+ | RBool _ -> pr " py_r = PyInt_FromLong ((long) r);\n"
+ | RInt64 _ -> pr " py_r = PyLong_FromLongLong (r);\n"
+ | RConstString _ -> pr " py_r = PyString_FromString (r);\n"
+ | RString _ ->
+ pr " py_r = PyString_FromString (r);\n";
+ pr " free (r);\n"
+ | RStringList _ ->
+ pr " py_r = put_string_list (r);\n";
+ pr " free_strings (r);\n"
+ | RIntBool _ ->
+ pr " py_r = PyTuple_New (2);\n";
+ pr " PyTuple_SetItem (py_r, 0, PyInt_FromLong ((long) r->i));\n";
+ pr " PyTuple_SetItem (py_r, 1, PyInt_FromLong ((long) r->b));\n";
+ pr " guestfs_free_int_bool (r);\n"
+ | RPVList n ->
+ pr " py_r = put_lvm_pv_list (r);\n";
+ pr " guestfs_free_lvm_pv_list (r);\n"
+ | RVGList n ->
+ pr " py_r = put_lvm_vg_list (r);\n";
+ pr " guestfs_free_lvm_vg_list (r);\n"
+ | RLVList n ->
+ pr " py_r = put_lvm_lv_list (r);\n";
+ pr " guestfs_free_lvm_lv_list (r);\n"
+ | RStat n ->
+ pr " py_r = put_stat (r);\n";
+ pr " free (r);\n"
+ | RStatVFS n ->
+ pr " py_r = put_statvfs (r);\n";
+ pr " free (r);\n"
+ | RHashtable n ->
+ pr " py_r = put_table (r);\n";
+ pr " free_strings (r);\n"
+ );
-(* Generate Sys/Guestfs.pm. *)
-and generate_perl_pm () =
- generate_header HashStyle LGPLv2;
+ pr " return py_r;\n";
+ pr "}\n";
+ pr "\n"
+ ) all_functions;
- pr "\
-=pod
+ (* Table of functions. *)
+ pr "static PyMethodDef methods[] = {\n";
+ pr " { (char *) \"create\", py_guestfs_create, METH_VARARGS, NULL },\n";
+ pr " { (char *) \"close\", py_guestfs_close, METH_VARARGS, NULL },\n";
+ List.iter (
+ fun (name, _, _, _, _, _, _) ->
+ pr " { (char *) \"%s\", py_guestfs_%s, METH_VARARGS, NULL },\n"
+ name name
+ ) all_functions;
+ pr " { NULL, NULL, 0, NULL }\n";
+ pr "};\n";
+ pr "\n";
-=head1 NAME
+ (* Init function. *)
+ pr "\
+void
+initlibguestfsmod (void)
+{
+ static int initialized = 0;
-Sys::Guestfs - Perl bindings for libguestfs
+ if (initialized) return;
+ Py_InitModule ((char *) \"libguestfsmod\", methods);
+ initialized = 1;
+}
+"
-=head1 SYNOPSIS
+(* Generate Python module. *)
+and generate_python_py () =
+ generate_header HashStyle LGPLv2;
- use Sys::Guestfs;
-
- my $h = Sys::Guestfs->new ();
- $h->add_drive ('guest.img');
- $h->launch ();
- $h->wait_ready ();
- $h->mount ('/dev/sda1', '/');
- $h->touch ('/hello');
- $h->sync ();
+ pr "\
+u\"\"\"Python bindings for libguestfs
-=head1 DESCRIPTION
+import guestfs
+g = guestfs.GuestFS ()
+g.add_drive (\"guest.img\")
+g.launch ()
+g.wait_ready ()
+parts = g.list_partitions ()
-The C<Sys::Guestfs> module provides a Perl XS binding to the
-libguestfs API for examining and modifying virtual machine
-disk images.
+The guestfs module provides a Python binding to the libguestfs API
+for examining and modifying virtual machine disk images.
Amongst the things this is good for: making batch configuration
changes to guests, getting disk used/free statistics (see also:
LVs, what filesystem is in each LV, etc.). It can also run commands
in the context of the guest. Also you can access filesystems over FTP.
-=head1 ERRORS
+Errors which happen while using the API are turned into Python
+RuntimeError exceptions.
-All errors turn into calls to C<croak> (see L<Carp(3)>).
+To create a guestfs handle you usually have to perform the following
+sequence of calls:
-=head1 METHODS
+# Create the handle, call add_drive at least once, and possibly
+# several times if the guest has multiple block devices:
+g = guestfs.GuestFS ()
+g.add_drive (\"guest.img\")
-=over 4
+# Launch the qemu subprocess and wait for it to become ready:
+g.launch ()
+g.wait_ready ()
-=cut
+# Now you can issue commands, for example:
+logvols = g.lvs ()
-package Sys::Guestfs;
+\"\"\"
-use strict;
-use warnings;
+import libguestfsmod
-require XSLoader;
-XSLoader::load ('Sys::Guestfs');
+class GuestFS:
+ \"\"\"Instances of this class are libguestfs API handles.\"\"\"
-=item $h = Sys::Guestfs->new ();
+ def __init__ (self):
+ \"\"\"Create a new libguestfs handle.\"\"\"
+ self._o = libguestfsmod.create ()
-Create a new guestfs handle.
+ def __del__ (self):
+ libguestfsmod.close (self._o)
-=cut
+";
-sub new {
- my $proto = shift;
- my $class = ref ($proto) || $proto;
+ List.iter (
+ fun (name, style, _, flags, _, _, longdesc) ->
+ pr " def %s " name;
+ generate_call_args ~handle:"self" (snd style);
+ pr ":\n";
- my $self = Sys::Guestfs::_create ();
- bless $self, $class;
- return $self;
+ if not (List.mem NotInDocs flags) then (
+ let doc = replace_str longdesc "C<guestfs_" "C<g." in
+ let doc =
+ match fst style with
+ | RErr | RInt _ | RInt64 _ | RBool _ | RConstString _
+ | RString _ -> doc
+ | RStringList _ ->
+ doc ^ "\n\nThis function returns a list of strings."
+ | RIntBool _ ->
+ doc ^ "\n\nThis function returns a tuple (int, bool).\n"
+ | RPVList _ ->
+ doc ^ "\n\nThis function returns a list of PVs. Each PV is represented as a dictionary."
+ | RVGList _ ->
+ doc ^ "\n\nThis function returns a list of VGs. Each VG is represented as a dictionary."
+ | RLVList _ ->
+ doc ^ "\n\nThis function returns a list of LVs. Each LV is represented as a dictionary."
+ | RStat _ ->
+ doc ^ "\n\nThis function returns a dictionary, with keys matching the various fields in the stat structure."
+ | RStatVFS _ ->
+ doc ^ "\n\nThis function returns a dictionary, with keys matching the various fields in the statvfs structure."
+ | RHashtable _ ->
+ doc ^ "\n\nThis function returns a dictionary." in
+ let doc =
+ if List.mem ProtocolLimitWarning flags then
+ doc ^ "\n\n" ^ protocol_limit_warning
+ else doc in
+ let doc =
+ if List.mem DangerWillRobinson flags then
+ doc ^ "\n\n" ^ danger_will_robinson
+ else doc in
+ let doc = pod2text ~width:60 name doc in
+ let doc = List.map (fun line -> replace_str line "\\" "\\\\") doc in
+ let doc = String.concat "\n " doc in
+ pr " u\"\"\"%s\"\"\"\n" doc;
+ );
+ pr " return libguestfsmod.%s " name;
+ generate_call_args ~handle:"self._o" (snd style);
+ pr "\n";
+ pr "\n";
+ ) all_functions
+
+(* Useful if you need the longdesc POD text as plain text. Returns a
+ * list of lines.
+ *
+ * This is the slowest thing about autogeneration.
+ *)
+and pod2text ~width name longdesc =
+ let filename, chan = Filename.open_temp_file "gen" ".tmp" in
+ fprintf chan "=head1 %s\n\n%s\n" name longdesc;
+ close_out chan;
+ let cmd = sprintf "pod2text -w %d %s" width (Filename.quote filename) in
+ let chan = Unix.open_process_in cmd in
+ let lines = ref [] in
+ let rec loop i =
+ let line = input_line chan in
+ if i = 1 then (* discard the first line of output *)
+ loop (i+1)
+ else (
+ let line = triml line in
+ lines := line :: !lines;
+ loop (i+1)
+ ) in
+ let lines = try loop 1 with End_of_file -> List.rev !lines in
+ Unix.unlink filename;
+ match Unix.close_process_in chan with
+ | Unix.WEXITED 0 -> lines
+ | Unix.WEXITED i ->
+ failwithf "pod2text: process exited with non-zero status (%d)" i
+ | Unix.WSIGNALED i | Unix.WSTOPPED i ->
+ failwithf "pod2text: process signalled or stopped by signal %d" i
+
+(* Generate ruby bindings. *)
+and generate_ruby_c () =
+ generate_header CStyle LGPLv2;
+
+ pr "\
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <ruby.h>
+
+#include \"guestfs.h\"
+
+#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 */
+
+static void ruby_guestfs_free (void *p)
+{
+ if (!p) return;
+ guestfs_close ((guestfs_h *) p);
+}
+
+static VALUE ruby_guestfs_create (VALUE m)
+{
+ guestfs_h *g;
+
+ g = guestfs_create ();
+ if (!g)
+ rb_raise (e_Error, \"failed to create guestfs handle\");
+
+ /* Don't print error messages to stderr by default. */
+ guestfs_set_error_handler (g, NULL, NULL);
+
+ /* Wrap it, and make sure the close function is called when the
+ * handle goes away.
+ */
+ return Data_Wrap_Struct (c_guestfs, NULL, ruby_guestfs_free, g);
+}
+
+static VALUE ruby_guestfs_close (VALUE gv)
+{
+ guestfs_h *g;
+ Data_Get_Struct (gv, guestfs_h, g);
+
+ ruby_guestfs_free (g);
+ DATA_PTR (gv) = NULL;
+
+ return Qnil;
}
";
- (* Actions. We only need to print documentation for these as
- * they are pulled in from the XS code automatically.
- *)
List.iter (
- fun (name, style, _, flags, _, _, longdesc) ->
- let longdesc = replace_str longdesc "C<guestfs_" "C<$h-E<gt>" in
- pr "=item ";
- generate_perl_prototype name style;
- pr "\n\n";
- pr "%s\n\n" longdesc;
- if List.mem ProtocolLimitWarning flags then
- pr "%s\n\n" protocol_limit_warning;
- if List.mem DangerWillRobinson flags then
- pr "%s\n\n" danger_will_robinson
- ) all_functions_sorted;
+ fun (name, style, _, _, _, _, _) ->
+ pr "static VALUE ruby_guestfs_%s (VALUE gv" name;
+ List.iter (fun arg -> pr ", VALUE %sv" (name_of_argt arg)) (snd style);
+ pr ")\n";
+ pr "{\n";
+ pr " guestfs_h *g;\n";
+ pr " Data_Get_Struct (gv, guestfs_h, g);\n";
+ pr " if (!g)\n";
+ pr " rb_raise (rb_eArgError, \"%%s: used handle after closing it\", \"%s\");\n"
+ name;
+ pr "\n";
+
+ List.iter (
+ function
+ | String n | FileIn n | FileOut n ->
+ pr " const char *%s = StringValueCStr (%sv);\n" n n;
+ pr " if (!%s)\n" n;
+ pr " rb_raise (rb_eTypeError, \"expected string for parameter %%s of %%s\",\n";
+ pr " \"%s\", \"%s\");\n" n name
+ | OptString n ->
+ pr " const char *%s = !NIL_P (%sv) ? StringValueCStr (%sv) : NULL;\n" n n n
+ | StringList n ->
+ pr " char **%s;" n;
+ pr " {\n";
+ pr " int i, len;\n";
+ pr " len = RARRAY_LEN (%sv);\n" n;
+ pr " %s = guestfs_safe_malloc (g, sizeof (char *) * (len+1));\n"
+ n;
+ pr " for (i = 0; i < len; ++i) {\n";
+ pr " VALUE v = rb_ary_entry (%sv, i);\n" n;
+ pr " %s[i] = StringValueCStr (v);\n" n;
+ pr " }\n";
+ pr " %s[len] = NULL;\n" n;
+ pr " }\n";
+ | Bool n ->
+ pr " int %s = RTEST (%sv);\n" n n
+ | Int n ->
+ pr " int %s = NUM2INT (%sv);\n" n n
+ ) (snd style);
+ pr "\n";
+
+ let error_code =
+ match fst style with
+ | RErr | RInt _ | RBool _ -> pr " int r;\n"; "-1"
+ | RInt64 _ -> pr " int64_t r;\n"; "-1"
+ | RConstString _ -> pr " const char *r;\n"; "NULL"
+ | RString _ -> pr " char *r;\n"; "NULL"
+ | RStringList _ | RHashtable _ -> pr " char **r;\n"; "NULL"
+ | RIntBool _ -> pr " struct guestfs_int_bool *r;\n"; "NULL"
+ | RPVList n -> pr " struct guestfs_lvm_pv_list *r;\n"; "NULL"
+ | RVGList n -> pr " struct guestfs_lvm_vg_list *r;\n"; "NULL"
+ | RLVList n -> pr " struct guestfs_lvm_lv_list *r;\n"; "NULL"
+ | RStat n -> pr " struct guestfs_stat *r;\n"; "NULL"
+ | RStatVFS n -> pr " struct guestfs_statvfs *r;\n"; "NULL" in
+ pr "\n";
+
+ pr " r = guestfs_%s " name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+
+ List.iter (
+ function
+ | String _ | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ -> ()
+ | StringList n ->
+ pr " free (%s);\n" n
+ ) (snd style);
+
+ pr " if (r == %s)\n" error_code;
+ pr " rb_raise (e_Error, \"%%s\", guestfs_last_error (g));\n";
+ pr "\n";
+
+ (match fst style with
+ | RErr ->
+ pr " return Qnil;\n"
+ | RInt _ | RBool _ ->
+ pr " return INT2NUM (r);\n"
+ | RInt64 _ ->
+ pr " return ULL2NUM (r);\n"
+ | RConstString _ ->
+ pr " return rb_str_new2 (r);\n";
+ | RString _ ->
+ pr " VALUE rv = rb_str_new2 (r);\n";
+ pr " free (r);\n";
+ pr " return rv;\n";
+ | RStringList _ ->
+ pr " int i, len = 0;\n";
+ pr " for (i = 0; r[i] != NULL; ++i) len++;\n";
+ pr " VALUE rv = rb_ary_new2 (len);\n";
+ pr " for (i = 0; r[i] != NULL; ++i) {\n";
+ pr " rb_ary_push (rv, rb_str_new2 (r[i]));\n";
+ pr " free (r[i]);\n";
+ pr " }\n";
+ pr " free (r);\n";
+ pr " return rv;\n"
+ | RIntBool _ ->
+ pr " VALUE rv = rb_ary_new2 (2);\n";
+ pr " rb_ary_push (rv, INT2NUM (r->i));\n";
+ pr " rb_ary_push (rv, INT2NUM (r->b));\n";
+ pr " guestfs_free_int_bool (r);\n";
+ pr " return rv;\n"
+ | RPVList n ->
+ generate_ruby_lvm_code "pv" pv_cols
+ | RVGList n ->
+ generate_ruby_lvm_code "vg" vg_cols
+ | RLVList n ->
+ generate_ruby_lvm_code "lv" lv_cols
+ | RStat n ->
+ pr " VALUE rv = rb_hash_new ();\n";
+ List.iter (
+ function
+ | name, `Int ->
+ pr " rb_hash_aset (rv, rb_str_new2 (\"%s\"), ULL2NUM (r->%s));\n" name name
+ ) stat_cols;
+ pr " free (r);\n";
+ pr " return rv;\n"
+ | RStatVFS n ->
+ pr " VALUE rv = rb_hash_new ();\n";
+ List.iter (
+ function
+ | name, `Int ->
+ pr " rb_hash_aset (rv, rb_str_new2 (\"%s\"), ULL2NUM (r->%s));\n" name name
+ ) statvfs_cols;
+ pr " free (r);\n";
+ pr " return rv;\n"
+ | RHashtable _ ->
+ pr " VALUE rv = rb_hash_new ();\n";
+ pr " int i;\n";
+ pr " for (i = 0; r[i] != NULL; i+=2) {\n";
+ pr " rb_hash_aset (rv, rb_str_new2 (r[i]), rb_str_new2 (r[i+1]));\n";
+ pr " free (r[i]);\n";
+ pr " free (r[i+1]);\n";
+ pr " }\n";
+ pr " free (r);\n";
+ pr " return rv;\n"
+ );
+
+ pr "}\n";
+ pr "\n"
+ ) all_functions;
- (* End of file. *)
pr "\
-=cut
+/* Initialize the module. */
+void Init__guestfs ()
+{
+ m_guestfs = rb_define_module (\"Guestfs\");
+ c_guestfs = rb_define_class_under (m_guestfs, \"Guestfs\", rb_cObject);
+ e_Error = rb_define_class_under (m_guestfs, \"Error\", rb_eStandardError);
-1;
+ rb_define_module_function (m_guestfs, \"create\", ruby_guestfs_create, 0);
+ rb_define_method (c_guestfs, \"close\", ruby_guestfs_close, 0);
-=back
+";
+ (* Define the rest of the methods. *)
+ List.iter (
+ fun (name, style, _, _, _, _, _) ->
+ pr " rb_define_method (c_guestfs, \"%s\",\n" name;
+ pr " ruby_guestfs_%s, %d);\n" name (List.length (snd style))
+ ) all_functions;
-=head1 COPYRIGHT
+ pr "}\n"
-Copyright (C) 2009 Red Hat Inc.
+(* Ruby code to return an LVM struct list. *)
+and generate_ruby_lvm_code typ cols =
+ pr " VALUE rv = rb_ary_new2 (r->len);\n";
+ pr " int i;\n";
+ pr " for (i = 0; i < r->len; ++i) {\n";
+ pr " VALUE hv = rb_hash_new ();\n";
+ List.iter (
+ function
+ | name, `String ->
+ pr " rb_hash_aset (rv, rb_str_new2 (\"%s\"), rb_str_new2 (r->val[i].%s));\n" name name
+ | name, `UUID ->
+ pr " rb_hash_aset (rv, rb_str_new2 (\"%s\"), rb_str_new (r->val[i].%s, 32));\n" name name
+ | name, `Bytes
+ | name, `Int ->
+ pr " rb_hash_aset (rv, rb_str_new2 (\"%s\"), ULL2NUM (r->val[i].%s));\n" name name
+ | name, `OptPercent ->
+ pr " rb_hash_aset (rv, rb_str_new2 (\"%s\"), rb_dbl2big (r->val[i].%s));\n" name name
+ ) cols;
+ pr " rb_ary_push (rv, hv);\n";
+ pr " }\n";
+ pr " guestfs_free_lvm_%s_list (r);\n" typ;
+ pr " return rv;\n"
-=head1 LICENSE
+(* Generate Java bindings GuestFS.java file. *)
+and generate_java_java () =
+ generate_header CStyle LGPLv2;
-Please see the file COPYING.LIB for the full license.
+ pr "\
+package com.redhat.et.libguestfs;
+
+import java.util.HashMap;
+import com.redhat.et.libguestfs.LibGuestFSException;
+import com.redhat.et.libguestfs.PV;
+import com.redhat.et.libguestfs.VG;
+import com.redhat.et.libguestfs.LV;
+import com.redhat.et.libguestfs.Stat;
+import com.redhat.et.libguestfs.StatVFS;
+import com.redhat.et.libguestfs.IntBool;
+
+/**
+ * The GuestFS object is a libguestfs handle.
+ *
+ * @author rjones
+ */
+public class GuestFS {
+ // Load the native code.
+ static {
+ System.loadLibrary (\"guestfs_jni\");
+ }
-=head1 SEE ALSO
+ /**
+ * The native guestfs_h pointer.
+ */
+ long g;
+
+ /**
+ * Create a libguestfs handle.
+ *
+ * @throws LibGuestFSException
+ */
+ public GuestFS () throws LibGuestFSException
+ {
+ g = _create ();
+ }
+ private native long _create () throws LibGuestFSException;
+
+ /**
+ * Close a libguestfs handle.
+ *
+ * You can also leave handles to be collected by the garbage
+ * collector, but this method ensures that the resources used
+ * by the handle are freed up immediately. If you call any
+ * other methods after closing the handle, you will get an
+ * exception.
+ *
+ * @throws LibGuestFSException
+ */
+ public void close () throws LibGuestFSException
+ {
+ if (g != 0)
+ _close (g);
+ g = 0;
+ }
+ private native void _close (long g) throws LibGuestFSException;
+
+ public void finalize () throws LibGuestFSException
+ {
+ close ();
+ }
+
+";
+
+ List.iter (
+ fun (name, style, _, flags, _, shortdesc, longdesc) ->
+ if not (List.mem NotInDocs flags); then (
+ let doc = replace_str longdesc "C<guestfs_" "C<g." in
+ let doc =
+ if List.mem ProtocolLimitWarning flags then
+ doc ^ "\n\n" ^ protocol_limit_warning
+ else doc in
+ let doc =
+ if List.mem DangerWillRobinson flags then
+ doc ^ "\n\n" ^ danger_will_robinson
+ else doc in
+ let doc = pod2text ~width:60 name doc in
+ let doc = List.map ( (* RHBZ#501883 *)
+ function
+ | "" -> "<p>"
+ | nonempty -> nonempty
+ ) doc in
+ let doc = String.concat "\n * " doc in
+
+ pr " /**\n";
+ pr " * %s\n" shortdesc;
+ pr " * <p>\n";
+ pr " * %s\n" doc;
+ pr " * @throws LibGuestFSException\n";
+ pr " */\n";
+ pr " ";
+ );
+ generate_java_prototype ~public:true ~semicolon:false name style;
+ pr "\n";
+ pr " {\n";
+ pr " if (g == 0)\n";
+ pr " throw new LibGuestFSException (\"%s: handle is closed\");\n"
+ name;
+ pr " ";
+ if fst style <> RErr then pr "return ";
+ pr "_%s " name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+ pr " }\n";
+ pr " ";
+ generate_java_prototype ~privat:true ~native:true name style;
+ pr "\n";
+ pr "\n";
+ ) all_functions;
-L<guestfs(3)>, L<guestfish(1)>.
+ pr "}\n"
-=cut
-"
+and generate_java_prototype ?(public=false) ?(privat=false) ?(native=false)
+ ?(semicolon=true) name style =
+ if privat then pr "private ";
+ if public then pr "public ";
+ if native then pr "native ";
-and generate_perl_prototype name style =
+ (* return type *)
(match fst style with
- | RErr -> ()
- | RBool n
- | RInt n
- | RConstString n
- | RString n -> pr "$%s = " n
- | RIntBool (n, m) -> pr "($%s, $%s) = " n m
- | RStringList n
- | RPVList n
- | RVGList n
- | RLVList n -> pr "@%s = " n
+ | RErr -> pr "void ";
+ | RInt _ -> pr "int ";
+ | RInt64 _ -> pr "long ";
+ | RBool _ -> pr "boolean ";
+ | RConstString _ | RString _ -> pr "String ";
+ | RStringList _ -> pr "String[] ";
+ | RIntBool _ -> pr "IntBool ";
+ | RPVList _ -> pr "PV[] ";
+ | RVGList _ -> pr "VG[] ";
+ | RLVList _ -> pr "LV[] ";
+ | RStat _ -> pr "Stat ";
+ | RStatVFS _ -> pr "StatVFS ";
+ | RHashtable _ -> pr "HashMap<String,String> ";
);
- pr "$h->%s (" name;
- let comma = ref false in
+
+ if native then pr "_%s " name else pr "%s " name;
+ pr "(";
+ let needs_comma = ref false in
+ if native then (
+ pr "long g";
+ needs_comma := true
+ );
+
+ (* args *)
List.iter (
fun arg ->
- if !comma then pr ", ";
- comma := true;
+ if !needs_comma then pr ", ";
+ needs_comma := true;
+
match arg with
- | String n | OptString n | Bool n | Int n ->
- pr "$%s" n
+ | String n
+ | OptString n
+ | FileIn n
+ | FileOut n ->
+ pr "String %s" n
| StringList n ->
- pr "\\@%s" n
+ pr "String[] %s" n
+ | Bool n ->
+ pr "boolean %s" n
+ | Int n ->
+ pr "int %s" n
) (snd style);
- pr ");"
-(* Generate Python C module. *)
-and generate_python_c () =
+ pr ")\n";
+ pr " throws LibGuestFSException";
+ if semicolon then pr ";"
+
+and generate_java_struct typ cols =
generate_header CStyle LGPLv2;
pr "\
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#include <Python.h>
-
-#include \"guestfs.h\"
-
-typedef struct {
- PyObject_HEAD
- guestfs_h *g;
-} Pyguestfs_Object;
-
-static guestfs_h *
-get_handle (PyObject *obj)
-{
- assert (obj);
- assert (obj != Py_None);
- return ((Pyguestfs_Object *) obj)->g;
-}
+package com.redhat.et.libguestfs;
-static PyObject *
-put_handle (guestfs_h *g)
-{
- assert (g);
- return
- PyCObject_FromVoidPtrAndDesc ((void *) g, (char *) \"guestfs_h\", NULL);
-}
-
-/* This list should be freed (but not the strings) after use. */
-static const char **
-get_string_list (PyObject *obj)
-{
- int i, len;
- const char **r;
-
- assert (obj);
-
- if (!PyList_Check (obj)) {
- PyErr_SetString (PyExc_RuntimeError, \"expecting a list parameter\");
- return NULL;
- }
-
- len = PyList_Size (obj);
- r = malloc (sizeof (char *) * (len+1));
- if (r == NULL) {
- PyErr_SetString (PyExc_RuntimeError, \"get_string_list: out of memory\");
- return NULL;
- }
-
- for (i = 0; i < len; ++i)
- r[i] = PyString_AsString (PyList_GetItem (obj, i));
- r[len] = NULL;
+/**
+ * Libguestfs %s structure.
+ *
+ * @author rjones
+ * @see GuestFS
+ */
+public class %s {
+" typ typ;
- return r;
-}
+ List.iter (
+ function
+ | name, `String
+ | name, `UUID -> pr " public String %s;\n" name
+ | name, `Bytes
+ | name, `Int -> pr " public long %s;\n" name
+ | name, `OptPercent ->
+ pr " /* The next field is [0..100] or -1 meaning 'not present': */\n";
+ pr " public float %s;\n" name
+ ) cols;
-static PyObject *
-put_string_list (char * const * const argv)
-{
- PyObject *list;
- int argc, i;
+ pr "}\n"
- for (argc = 0; argv[argc] != NULL; ++argc)
- ;
+and generate_java_c () =
+ generate_header CStyle LGPLv2;
- list = PyList_New (argc);
- for (i = 0; i < argc; ++i)
- PyList_SetItem (list, i, PyString_FromString (argv[i]));
+ pr "\
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
- return list;
-}
+#include \"com_redhat_et_libguestfs_GuestFS.h\"
+#include \"guestfs.h\"
+/* Note that this function returns. The exception is not thrown
+ * until after the wrapper function returns.
+ */
static void
-free_strings (char **argv)
+throw_exception (JNIEnv *env, const char *msg)
{
- int argc;
-
- for (argc = 0; argv[argc] != NULL; ++argc)
- free (argv[argc]);
- free (argv);
+ jclass cl;
+ cl = (*env)->FindClass (env,
+ \"com/redhat/et/libguestfs/LibGuestFSException\");
+ (*env)->ThrowNew (env, cl, msg);
}
-static PyObject *
-py_guestfs_create (PyObject *self, PyObject *args)
+JNIEXPORT jlong JNICALL
+Java_com_redhat_et_libguestfs_GuestFS__1create
+ (JNIEnv *env, jobject obj)
{
guestfs_h *g;
g = guestfs_create ();
if (g == NULL) {
- PyErr_SetString (PyExc_RuntimeError,
- \"guestfs.create: failed to allocate handle\");
- return NULL;
+ throw_exception (env, \"GuestFS.create: failed to allocate handle\");
+ return 0;
}
guestfs_set_error_handler (g, NULL, NULL);
- return put_handle (g);
+ return (jlong) (long) g;
}
-static PyObject *
-py_guestfs_close (PyObject *self, PyObject *args)
+JNIEXPORT void JNICALL
+Java_com_redhat_et_libguestfs_GuestFS__1close
+ (JNIEnv *env, jobject obj, jlong jg)
{
- PyObject *py_g;
- guestfs_h *g;
-
- if (!PyArg_ParseTuple (args, (char *) \"O:guestfs_close\", &py_g))
- return NULL;
- g = get_handle (py_g);
-
+ guestfs_h *g = (guestfs_h *) (long) jg;
guestfs_close (g);
-
- Py_INCREF (Py_None);
- return Py_None;
}
";
- (* LVM structures, turned into Python dictionaries. *)
List.iter (
- fun (typ, cols) ->
- pr "static PyObject *\n";
- pr "put_lvm_%s (struct guestfs_lvm_%s *%s)\n" typ typ typ;
- pr "{\n";
- pr " PyObject *dict;\n";
+ fun (name, style, _, _, _, _, _) ->
+ pr "JNIEXPORT ";
+ (match fst style with
+ | RErr -> pr "void ";
+ | RInt _ -> pr "jint ";
+ | RInt64 _ -> pr "jlong ";
+ | RBool _ -> pr "jboolean ";
+ | RConstString _ | RString _ -> pr "jstring ";
+ | RIntBool _ | RStat _ | RStatVFS _ | RHashtable _ ->
+ pr "jobject ";
+ | RStringList _ | RPVList _ | RVGList _ | RLVList _ ->
+ pr "jobjectArray ";
+ );
+ pr "JNICALL\n";
+ pr "Java_com_redhat_et_libguestfs_GuestFS_";
+ pr "%s" (replace_str ("_" ^ name) "_" "_1");
pr "\n";
- pr " dict = PyDict_New ();\n";
+ pr " (JNIEnv *env, jobject obj, jlong jg";
List.iter (
function
- | name, `String ->
- pr " PyDict_SetItemString (dict, \"%s\",\n" name;
- pr " PyString_FromString (%s->%s));\n"
- typ name
- | name, `UUID ->
- pr " PyDict_SetItemString (dict, \"%s\",\n" name;
- pr " PyString_FromStringAndSize (%s->%s, 32));\n"
- typ name
- | name, `Bytes ->
- pr " PyDict_SetItemString (dict, \"%s\",\n" name;
- pr " PyLong_FromUnsignedLongLong (%s->%s));\n"
- typ name
- | name, `Int ->
- pr " PyDict_SetItemString (dict, \"%s\",\n" name;
- pr " PyLong_FromLongLong (%s->%s));\n"
- typ name
- | name, `OptPercent ->
- pr " if (%s->%s >= 0)\n" typ name;
- pr " PyDict_SetItemString (dict, \"%s\",\n" name;
- pr " PyFloat_FromDouble ((double) %s->%s));\n"
- typ name;
- pr " else {\n";
- pr " Py_INCREF (Py_None);\n";
- pr " PyDict_SetItemString (dict, \"%s\", Py_None);" name;
- pr " }\n"
- ) cols;
- pr " return dict;\n";
- pr "};\n";
- pr "\n";
-
- pr "static PyObject *\n";
- pr "put_lvm_%s_list (struct guestfs_lvm_%s_list *%ss)\n" typ typ typ;
- pr "{\n";
- pr " PyObject *list;\n";
- pr " int i;\n";
- pr "\n";
- pr " list = PyList_New (%ss->len);\n" typ;
- pr " for (i = 0; i < %ss->len; ++i)\n" typ;
- pr " PyList_SetItem (list, i, put_lvm_%s (&%ss->val[i]));\n" typ typ;
- pr " return list;\n";
- pr "};\n";
- pr "\n"
- ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
-
- (* Python wrapper functions. *)
- List.iter (
- fun (name, style, _, _, _, _, _) ->
- pr "static PyObject *\n";
- pr "py_guestfs_%s (PyObject *self, PyObject *args)\n" name;
+ | String n
+ | OptString n
+ | FileIn n
+ | FileOut n ->
+ pr ", jstring j%s" n
+ | StringList n ->
+ pr ", jobjectArray j%s" n
+ | Bool n ->
+ pr ", jboolean j%s" n
+ | Int n ->
+ pr ", jint j%s" n
+ ) (snd style);
+ pr ")\n";
pr "{\n";
-
- pr " PyObject *py_g;\n";
- pr " guestfs_h *g;\n";
- pr " PyObject *py_r;\n";
-
- let error_code =
+ pr " guestfs_h *g = (guestfs_h *) (long) jg;\n";
+ let error_code, no_ret =
match fst style with
- | RErr | RInt _ | RBool _ -> pr " int r;\n"; "-1"
- | RConstString _ -> pr " const char *r;\n"; "NULL"
- | RString _ -> pr " char *r;\n"; "NULL"
- | RStringList _ -> pr " char **r;\n"; "NULL"
- | RIntBool _ -> pr " struct guestfs_int_bool *r;\n"; "NULL"
- | RPVList n -> pr " struct guestfs_lvm_pv_list *r;\n"; "NULL"
- | RVGList n -> pr " struct guestfs_lvm_vg_list *r;\n"; "NULL"
- | RLVList n -> pr " struct guestfs_lvm_lv_list *r;\n"; "NULL" in
-
+ | RErr -> pr " int r;\n"; "-1", ""
+ | RBool _
+ | RInt _ -> pr " int r;\n"; "-1", "0"
+ | RInt64 _ -> pr " int64_t r;\n"; "-1", "0"
+ | RConstString _ -> pr " const char *r;\n"; "NULL", "NULL"
+ | RString _ ->
+ pr " jstring jr;\n";
+ pr " char *r;\n"; "NULL", "NULL"
+ | RStringList _ ->
+ pr " jobjectArray jr;\n";
+ pr " int r_len;\n";
+ pr " jclass cl;\n";
+ pr " jstring jstr;\n";
+ pr " char **r;\n"; "NULL", "NULL"
+ | RIntBool _ ->
+ pr " jobject jr;\n";
+ pr " jclass cl;\n";
+ pr " jfieldID fl;\n";
+ pr " struct guestfs_int_bool *r;\n"; "NULL", "NULL"
+ | RStat _ ->
+ pr " jobject jr;\n";
+ pr " jclass cl;\n";
+ pr " jfieldID fl;\n";
+ pr " struct guestfs_stat *r;\n"; "NULL", "NULL"
+ | RStatVFS _ ->
+ pr " jobject jr;\n";
+ pr " jclass cl;\n";
+ pr " jfieldID fl;\n";
+ pr " struct guestfs_statvfs *r;\n"; "NULL", "NULL"
+ | RPVList _ ->
+ pr " jobjectArray jr;\n";
+ pr " jclass cl;\n";
+ pr " jfieldID fl;\n";
+ pr " jobject jfl;\n";
+ pr " struct guestfs_lvm_pv_list *r;\n"; "NULL", "NULL"
+ | RVGList _ ->
+ pr " jobjectArray jr;\n";
+ pr " jclass cl;\n";
+ pr " jfieldID fl;\n";
+ pr " jobject jfl;\n";
+ pr " struct guestfs_lvm_vg_list *r;\n"; "NULL", "NULL"
+ | RLVList _ ->
+ pr " jobjectArray jr;\n";
+ pr " jclass cl;\n";
+ pr " jfieldID fl;\n";
+ pr " jobject jfl;\n";
+ pr " struct guestfs_lvm_lv_list *r;\n"; "NULL", "NULL"
+ | RHashtable _ -> pr " char **r;\n"; "NULL", "NULL" in
List.iter (
function
- | String n -> pr " const char *%s;\n" n
- | OptString n -> pr " const char *%s;\n" n
+ | String n
+ | OptString n
+ | FileIn n
+ | FileOut n ->
+ pr " const char *%s;\n" n
| StringList n ->
- pr " PyObject *py_%s;\n" n;
+ pr " int %s_len;\n" n;
pr " const char **%s;\n" n
- | Bool n -> pr " int %s;\n" n
- | Int n -> pr " int %s;\n" n
+ | Bool n
+ | Int n ->
+ pr " int %s;\n" n
) (snd style);
- pr "\n";
-
- (* Convert the parameters. *)
- pr " if (!PyArg_ParseTuple (args, (char *) \"O";
- List.iter (
- function
- | String _ -> pr "s"
- | OptString _ -> pr "z"
- | StringList _ -> pr "O"
- | Bool _ -> pr "i" (* XXX Python has booleans? *)
- | Int _ -> pr "i"
- ) (snd style);
- pr ":guestfs_%s\",\n" name;
- pr " &py_g";
- List.iter (
- function
- | String n -> pr ", &%s" n
- | OptString n -> pr ", &%s" n
- | StringList n -> pr ", &py_%s" n
- | Bool n -> pr ", &%s" n
- | Int n -> pr ", &%s" n
- ) (snd style);
+ let needs_i =
+ (match fst style with
+ | RStringList _ | RPVList _ | RVGList _ | RLVList _ -> true
+ | RErr | RBool _ | RInt _ | RInt64 _ | RConstString _
+ | RString _ | RIntBool _ | RStat _ | RStatVFS _
+ | RHashtable _ -> false) ||
+ List.exists (function StringList _ -> true | _ -> false) (snd style) in
+ if needs_i then
+ pr " int i;\n";
- pr "))\n";
- pr " return NULL;\n";
+ pr "\n";
- pr " g = get_handle (py_g);\n";
+ (* Get the parameters. *)
List.iter (
function
- | String _ | OptString _ | Bool _ | Int _ -> ()
+ | String n
+ | FileIn n
+ | FileOut n ->
+ pr " %s = (*env)->GetStringUTFChars (env, j%s, NULL);\n" n n
+ | OptString n ->
+ (* This is completely undocumented, but Java null becomes
+ * a NULL parameter.
+ *)
+ pr " %s = j%s ? (*env)->GetStringUTFChars (env, j%s, NULL) : NULL;\n" n n n
| StringList n ->
- pr " %s = get_string_list (py_%s);\n" n n;
- pr " if (!%s) return NULL;\n" n
+ pr " %s_len = (*env)->GetArrayLength (env, j%s);\n" n n;
+ pr " %s = guestfs_safe_malloc (g, sizeof (char *) * (%s_len+1));\n" n n;
+ pr " for (i = 0; i < %s_len; ++i) {\n" n;
+ pr " jobject o = (*env)->GetObjectArrayElement (env, j%s, i);\n"
+ n;
+ pr " %s[i] = (*env)->GetStringUTFChars (env, o, NULL);\n" n;
+ pr " }\n";
+ pr " %s[%s_len] = NULL;\n" n n;
+ | Bool n
+ | Int n ->
+ pr " %s = j%s;\n" n n
) (snd style);
- pr "\n";
-
+ (* Make the call. *)
pr " r = guestfs_%s " name;
- generate_call_args ~handle:"g" style;
+ generate_call_args ~handle:"g" (snd style);
pr ";\n";
+ (* Release the parameters. *)
List.iter (
function
- | String _ | OptString _ | Bool _ | Int _ -> ()
+ | String n
+ | FileIn n
+ | FileOut n ->
+ pr " (*env)->ReleaseStringUTFChars (env, j%s, %s);\n" n n
+ | OptString n ->
+ pr " if (j%s)\n" n;
+ pr " (*env)->ReleaseStringUTFChars (env, j%s, %s);\n" n n
| StringList n ->
+ pr " for (i = 0; i < %s_len; ++i) {\n" n;
+ pr " jobject o = (*env)->GetObjectArrayElement (env, j%s, i);\n"
+ n;
+ pr " (*env)->ReleaseStringUTFChars (env, o, %s[i]);\n" n;
+ pr " }\n";
pr " free (%s);\n" n
+ | Bool n
+ | Int n -> ()
) (snd style);
+ (* Check for errors. *)
pr " if (r == %s) {\n" error_code;
- pr " PyErr_SetString (PyExc_RuntimeError, guestfs_last_error (g));\n";
- pr " return NULL;\n";
+ pr " throw_exception (env, guestfs_last_error (g));\n";
+ pr " return %s;\n" no_ret;
pr " }\n";
- pr "\n";
+ (* Return value. *)
(match fst style with
- | RErr ->
- pr " Py_INCREF (Py_None);\n";
- pr " py_r = Py_None;\n"
- | RInt _
- | RBool _ -> pr " py_r = PyInt_FromLong ((long) r);\n"
- | RConstString _ -> pr " py_r = PyString_FromString (r);\n"
+ | RErr -> ()
+ | RInt _ -> pr " return (jint) r;\n"
+ | RBool _ -> pr " return (jboolean) r;\n"
+ | RInt64 _ -> pr " return (jlong) r;\n"
+ | RConstString _ -> pr " return (*env)->NewStringUTF (env, r);\n"
| RString _ ->
- pr " py_r = PyString_FromString (r);\n";
- pr " free (r);\n"
+ pr " jr = (*env)->NewStringUTF (env, r);\n";
+ pr " free (r);\n";
+ pr " return jr;\n"
| RStringList _ ->
- pr " py_r = put_string_list (r);\n";
- pr " free_strings (r);\n"
+ pr " for (r_len = 0; r[r_len] != NULL; ++r_len) ;\n";
+ pr " cl = (*env)->FindClass (env, \"java/lang/String\");\n";
+ pr " jstr = (*env)->NewStringUTF (env, \"\");\n";
+ pr " jr = (*env)->NewObjectArray (env, r_len, cl, jstr);\n";
+ pr " for (i = 0; i < r_len; ++i) {\n";
+ pr " jstr = (*env)->NewStringUTF (env, r[i]);\n";
+ pr " (*env)->SetObjectArrayElement (env, jr, i, jstr);\n";
+ pr " free (r[i]);\n";
+ pr " }\n";
+ pr " free (r);\n";
+ pr " return jr;\n"
| RIntBool _ ->
- pr " py_r = PyTuple_New (2);\n";
- pr " PyTuple_SetItem (py_r, 0, PyInt_FromLong ((long) r->i));\n";
- pr " PyTuple_SetItem (py_r, 1, PyInt_FromLong ((long) r->b));\n";
- pr " guestfs_free_int_bool (r);\n"
- | RPVList n ->
- pr " py_r = put_lvm_pv_list (r);\n";
- pr " guestfs_free_lvm_pv_list (r);\n"
- | RVGList n ->
- pr " py_r = put_lvm_vg_list (r);\n";
- pr " guestfs_free_lvm_vg_list (r);\n"
- | RLVList n ->
- pr " py_r = put_lvm_lv_list (r);\n";
- pr " guestfs_free_lvm_lv_list (r);\n"
+ pr " cl = (*env)->FindClass (env, \"com/redhat/et/libguestfs/IntBool\");\n";
+ pr " jr = (*env)->AllocObject (env, cl);\n";
+ pr " fl = (*env)->GetFieldID (env, cl, \"i\", \"I\");\n";
+ pr " (*env)->SetIntField (env, jr, fl, r->i);\n";
+ pr " fl = (*env)->GetFieldID (env, cl, \"i\", \"Z\");\n";
+ pr " (*env)->SetBooleanField (env, jr, fl, r->b);\n";
+ pr " guestfs_free_int_bool (r);\n";
+ pr " return jr;\n"
+ | RStat _ ->
+ pr " cl = (*env)->FindClass (env, \"com/redhat/et/libguestfs/Stat\");\n";
+ pr " jr = (*env)->AllocObject (env, cl);\n";
+ List.iter (
+ function
+ | name, `Int ->
+ pr " fl = (*env)->GetFieldID (env, cl, \"%s\", \"J\");\n"
+ name;
+ pr " (*env)->SetLongField (env, jr, fl, r->%s);\n" name;
+ ) stat_cols;
+ pr " free (r);\n";
+ pr " return jr;\n"
+ | RStatVFS _ ->
+ pr " cl = (*env)->FindClass (env, \"com/redhat/et/libguestfs/StatVFS\");\n";
+ pr " jr = (*env)->AllocObject (env, cl);\n";
+ List.iter (
+ function
+ | name, `Int ->
+ pr " fl = (*env)->GetFieldID (env, cl, \"%s\", \"J\");\n"
+ name;
+ pr " (*env)->SetLongField (env, jr, fl, r->%s);\n" name;
+ ) statvfs_cols;
+ pr " free (r);\n";
+ pr " return jr;\n"
+ | RPVList _ ->
+ generate_java_lvm_return "pv" "PV" pv_cols
+ | RVGList _ ->
+ generate_java_lvm_return "vg" "VG" vg_cols
+ | RLVList _ ->
+ generate_java_lvm_return "lv" "LV" lv_cols
+ | RHashtable _ ->
+ (* XXX *)
+ pr " throw_exception (env, \"%s: internal error: please let us know how to make a Java HashMap from JNI bindings!\");\n" name;
+ pr " return NULL;\n"
);
- pr " return py_r;\n";
pr "}\n";
pr "\n"
- ) all_functions;
+ ) all_functions
- (* Table of functions. *)
- pr "static PyMethodDef methods[] = {\n";
- pr " { (char *) \"create\", py_guestfs_create, METH_VARARGS, NULL },\n";
- pr " { (char *) \"close\", py_guestfs_close, METH_VARARGS, NULL },\n";
+and generate_java_lvm_return typ jtyp cols =
+ pr " cl = (*env)->FindClass (env, \"com/redhat/et/libguestfs/%s\");\n" jtyp;
+ pr " jr = (*env)->NewObjectArray (env, r->len, cl, NULL);\n";
+ pr " for (i = 0; i < r->len; ++i) {\n";
+ pr " jfl = (*env)->AllocObject (env, cl);\n";
List.iter (
- fun (name, _, _, _, _, _, _) ->
- pr " { (char *) \"%s\", py_guestfs_%s, METH_VARARGS, NULL },\n"
- name name
+ function
+ | name, `String ->
+ pr " fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
+ pr " (*env)->SetObjectField (env, jfl, fl, (*env)->NewStringUTF (env, r->val[i].%s));\n" name;
+ | name, `UUID ->
+ pr " {\n";
+ pr " char s[33];\n";
+ pr " memcpy (s, r->val[i].%s, 32);\n" name;
+ pr " s[32] = 0;\n";
+ pr " fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
+ pr " (*env)->SetObjectField (env, jfl, fl, (*env)->NewStringUTF (env, s));\n";
+ pr " }\n";
+ | name, (`Bytes|`Int) ->
+ pr " fl = (*env)->GetFieldID (env, cl, \"%s\", \"J\");\n" name;
+ pr " (*env)->SetLongField (env, jfl, fl, r->val[i].%s);\n" name;
+ | name, `OptPercent ->
+ pr " fl = (*env)->GetFieldID (env, cl, \"%s\", \"F\");\n" name;
+ pr " (*env)->SetFloatField (env, jfl, fl, r->val[i].%s);\n" name;
+ ) cols;
+ pr " (*env)->SetObjectArrayElement (env, jfl, i, jfl);\n";
+ pr " }\n";
+ 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 <guestfs.h> #-}
+{-# 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 " { NULL, NULL, 0, NULL }\n";
- pr "};\n";
- pr "\n";
- (* Init function. *)
+ 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 ")"
+
+and generate_bindtests () =
+ generate_header CStyle LGPLv2;
+
pr "\
-void
-initlibguestfsmod (void)
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include \"guestfs.h\"
+#include \"guestfs_protocol.h\"
+
+#define error guestfs_error
+
+static void
+print_strings (char * const* const argv)
{
- static int initialized = 0;
+ int argc;
- if (initialized) return;
- Py_InitModule ((char *) \"libguestfsmod\", methods);
- initialized = 1;
+ printf (\"[\");
+ for (argc = 0; argv[argc] != NULL; ++argc) {
+ if (argc > 0) printf (\", \");
+ printf (\"\\\"%%s\\\"\", argv[argc]);
+ }
+ printf (\"]\\n\");
}
-"
-(* Generate Python module. *)
-and generate_python_py () =
- generate_header HashStyle LGPLv2;
+/* The test0 function prints its parameters to stdout. */
+";
- pr "import libguestfsmod\n";
- pr "\n";
- pr "class GuestFS:\n";
- pr " def __init__ (self):\n";
- pr " self._o = libguestfsmod.create ()\n";
- pr "\n";
- pr " def __del__ (self):\n";
- pr " libguestfsmod.close (self._o)\n";
- pr "\n";
+ let test0, tests =
+ match test_functions with
+ | [] -> assert false
+ | test0 :: tests -> test0, tests in
+
+ let () =
+ let (name, style, _, _, _, _, _) = test0 in
+ generate_prototype ~extern:false ~semicolon:false ~newline:true
+ ~handle:"g" ~prefix:"guestfs_" name style;
+ pr "{\n";
+ List.iter (
+ function
+ | String n
+ | FileIn n
+ | FileOut n -> pr " printf (\"%%s\\n\", %s);\n" n
+ | OptString n -> pr " printf (\"%%s\\n\", %s ? %s : \"null\");\n" n n
+ | StringList n -> pr " print_strings (%s);\n" n
+ | Bool n -> pr " printf (\"%%s\\n\", %s ? \"true\" : \"false\");\n" n
+ | Int n -> pr " printf (\"%%d\\n\", %s);\n" n
+ ) (snd style);
+ pr " /* Java changes stdout line buffering so we need this: */\n";
+ pr " fflush (stdout);\n";
+ pr " return 0;\n";
+ pr "}\n";
+ pr "\n" in
List.iter (
fun (name, style, _, _, _, _, _) ->
- pr " def %s " name;
- generate_call_args ~handle:"self" style;
- pr ":\n";
- pr " return libguestfsmod.%s " name;
- generate_call_args ~handle:"self._o" style;
- pr "\n";
- pr "\n";
- ) all_functions
+ 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;
+ pr "{\n";
+ (match fst style with
+ | RErr ->
+ pr " return 0;\n"
+ | RInt _ ->
+ pr " int r;\n";
+ pr " sscanf (val, \"%%d\", &r);\n";
+ pr " return r;\n"
+ | RInt64 _ ->
+ pr " int64_t r;\n";
+ pr " sscanf (val, \"%%\" SCNi64, &r);\n";
+ pr " return r;\n"
+ | RBool _ ->
+ pr " return strcmp (val, \"true\") == 0;\n"
+ | RConstString _ ->
+ (* Can't return the input string here. Return a static
+ * string so we ensure we get a segfault if the caller
+ * tries to free it.
+ *)
+ pr " return \"static string\";\n"
+ | RString _ ->
+ pr " return strdup (val);\n"
+ | RStringList _ ->
+ pr " char **strs;\n";
+ pr " int n, i;\n";
+ pr " sscanf (val, \"%%d\", &n);\n";
+ pr " strs = malloc ((n+1) * sizeof (char *));\n";
+ pr " for (i = 0; i < n; ++i) {\n";
+ pr " strs[i] = malloc (16);\n";
+ pr " snprintf (strs[i], 16, \"%%d\", i);\n";
+ pr " }\n";
+ pr " strs[n] = NULL;\n";
+ pr " return strs;\n"
+ | RIntBool _ ->
+ pr " struct guestfs_int_bool *r;\n";
+ pr " r = malloc (sizeof (struct guestfs_int_bool));\n";
+ pr " sscanf (val, \"%%\" SCNi32, &r->i);\n";
+ pr " r->b = 0;\n";
+ pr " return r;\n"
+ | RPVList _ ->
+ pr " struct guestfs_lvm_pv_list *r;\n";
+ pr " int i;\n";
+ pr " r = malloc (sizeof (struct guestfs_lvm_pv_list));\n";
+ pr " sscanf (val, \"%%d\", &r->len);\n";
+ pr " r->val = calloc (r->len, sizeof (struct guestfs_lvm_pv));\n";
+ pr " for (i = 0; i < r->len; ++i) {\n";
+ pr " r->val[i].pv_name = malloc (16);\n";
+ pr " snprintf (r->val[i].pv_name, 16, \"%%d\", i);\n";
+ pr " }\n";
+ pr " return r;\n"
+ | RVGList _ ->
+ pr " struct guestfs_lvm_vg_list *r;\n";
+ pr " int i;\n";
+ pr " r = malloc (sizeof (struct guestfs_lvm_vg_list));\n";
+ pr " sscanf (val, \"%%d\", &r->len);\n";
+ pr " r->val = calloc (r->len, sizeof (struct guestfs_lvm_vg));\n";
+ pr " for (i = 0; i < r->len; ++i) {\n";
+ pr " r->val[i].vg_name = malloc (16);\n";
+ pr " snprintf (r->val[i].vg_name, 16, \"%%d\", i);\n";
+ pr " }\n";
+ pr " return r;\n"
+ | RLVList _ ->
+ pr " struct guestfs_lvm_lv_list *r;\n";
+ pr " int i;\n";
+ pr " r = malloc (sizeof (struct guestfs_lvm_lv_list));\n";
+ pr " sscanf (val, \"%%d\", &r->len);\n";
+ pr " r->val = calloc (r->len, sizeof (struct guestfs_lvm_lv));\n";
+ pr " for (i = 0; i < r->len; ++i) {\n";
+ pr " r->val[i].lv_name = malloc (16);\n";
+ pr " snprintf (r->val[i].lv_name, 16, \"%%d\", i);\n";
+ pr " }\n";
+ pr " return r;\n"
+ | RStat _ ->
+ pr " struct guestfs_stat *r;\n";
+ pr " r = calloc (1, sizeof (*r));\n";
+ pr " sscanf (val, \"%%\" SCNi64, &r->dev);\n";
+ pr " return r;\n"
+ | RStatVFS _ ->
+ pr " struct guestfs_statvfs *r;\n";
+ pr " r = calloc (1, sizeof (*r));\n";
+ pr " sscanf (val, \"%%\" SCNi64, &r->bsize);\n";
+ pr " return r;\n"
+ | RHashtable _ ->
+ pr " char **strs;\n";
+ pr " int n, i;\n";
+ pr " sscanf (val, \"%%d\", &n);\n";
+ pr " strs = malloc ((n*2+1) * sizeof (char *));\n";
+ pr " for (i = 0; i < n; ++i) {\n";
+ pr " strs[i*2] = malloc (16);\n";
+ pr " strs[i*2+1] = malloc (16);\n";
+ pr " snprintf (strs[i*2], 16, \"%%d\", i);\n";
+ pr " snprintf (strs[i*2+1], 16, \"%%d\", i);\n";
+ pr " }\n";
+ pr " strs[n*2] = NULL;\n";
+ pr " return strs;\n"
+ );
+ pr "}\n";
+ pr "\n"
+ ) else (
+ pr "/* Test error return. */\n";
+ generate_prototype ~extern:false ~semicolon:false ~newline:true
+ ~handle:"g" ~prefix:"guestfs_" name style;
+ pr "{\n";
+ pr " error (g, \"error\");\n";
+ (match fst style with
+ | RErr | RInt _ | RInt64 _ | RBool _ ->
+ pr " return -1;\n"
+ | RConstString _
+ | RString _ | RStringList _ | RIntBool _
+ | RPVList _ | RVGList _ | RLVList _ | RStat _ | RStatVFS _
+ | RHashtable _ ->
+ pr " return NULL;\n"
+ );
+ pr "}\n";
+ pr "\n"
+ )
+ ) tests
+
+and generate_ocaml_bindtests () =
+ generate_header OCamlStyle GPLv2;
+
+ pr "\
+let () =
+ let g = Guestfs.create () in
+";
+
+ let mkargs args =
+ String.concat " " (
+ List.map (
+ function
+ | CallString s -> "\"" ^ s ^ "\""
+ | CallOptString None -> "None"
+ | CallOptString (Some s) -> sprintf "(Some \"%s\")" s
+ | CallStringList xs ->
+ "[|" ^ String.concat ";" (List.map (sprintf "\"%s\"") xs) ^ "|]"
+ | CallInt i when i >= 0 -> string_of_int i
+ | CallInt i (* when i < 0 *) -> "(" ^ string_of_int i ^ ")"
+ | CallBool b -> string_of_bool b
+ ) args
+ )
+ in
+
+ generate_lang_bindtests (
+ fun f args -> pr " Guestfs.%s g %s;\n" f (mkargs args)
+ );
+
+ pr "print_endline \"EOF\"\n"
+
+and generate_perl_bindtests () =
+ pr "#!/usr/bin/perl -w\n";
+ generate_header HashStyle GPLv2;
+
+ pr "\
+use strict;
+
+use Sys::Guestfs;
+
+my $g = Sys::Guestfs->new ();
+";
+
+ let mkargs args =
+ String.concat ", " (
+ List.map (
+ function
+ | CallString s -> "\"" ^ s ^ "\""
+ | CallOptString None -> "undef"
+ | CallOptString (Some s) -> sprintf "\"%s\"" s
+ | CallStringList xs ->
+ "[" ^ String.concat "," (List.map (sprintf "\"%s\"") xs) ^ "]"
+ | CallInt i -> string_of_int i
+ | CallBool b -> if b then "1" else "0"
+ ) args
+ )
+ in
+
+ generate_lang_bindtests (
+ fun f args -> pr "$g->%s (%s);\n" f (mkargs args)
+ );
+
+ pr "print \"EOF\\n\"\n"
+
+and generate_python_bindtests () =
+ generate_header HashStyle GPLv2;
+
+ pr "\
+import guestfs
+
+g = guestfs.GuestFS ()
+";
+
+ let mkargs args =
+ String.concat ", " (
+ List.map (
+ function
+ | CallString s -> "\"" ^ s ^ "\""
+ | CallOptString None -> "None"
+ | CallOptString (Some s) -> sprintf "\"%s\"" s
+ | CallStringList xs ->
+ "[" ^ String.concat "," (List.map (sprintf "\"%s\"") xs) ^ "]"
+ | CallInt i -> string_of_int i
+ | CallBool b -> if b then "1" else "0"
+ ) args
+ )
+ in
+
+ generate_lang_bindtests (
+ fun f args -> pr "g.%s (%s)\n" f (mkargs args)
+ );
+
+ pr "print \"EOF\"\n"
+
+and generate_ruby_bindtests () =
+ generate_header HashStyle GPLv2;
+
+ pr "\
+require 'guestfs'
+
+g = Guestfs::create()
+";
+
+ let mkargs args =
+ String.concat ", " (
+ List.map (
+ function
+ | CallString s -> "\"" ^ s ^ "\""
+ | CallOptString None -> "nil"
+ | CallOptString (Some s) -> sprintf "\"%s\"" s
+ | CallStringList xs ->
+ "[" ^ String.concat "," (List.map (sprintf "\"%s\"") xs) ^ "]"
+ | CallInt i -> string_of_int i
+ | CallBool b -> string_of_bool b
+ ) args
+ )
+ in
+
+ generate_lang_bindtests (
+ fun f args -> pr "g.%s(%s)\n" f (mkargs args)
+ );
+
+ pr "print \"EOF\\n\"\n"
+
+and generate_java_bindtests () =
+ generate_header CStyle GPLv2;
+
+ pr "\
+import com.redhat.et.libguestfs.*;
+
+public class Bindtests {
+ public static void main (String[] argv)
+ {
+ try {
+ GuestFS g = new GuestFS ();
+";
+
+ let mkargs args =
+ String.concat ", " (
+ List.map (
+ function
+ | CallString s -> "\"" ^ s ^ "\""
+ | CallOptString None -> "null"
+ | CallOptString (Some s) -> sprintf "\"%s\"" s
+ | CallStringList xs ->
+ "new String[]{" ^
+ String.concat "," (List.map (sprintf "\"%s\"") xs) ^ "}"
+ | CallInt i -> string_of_int i
+ | CallBool b -> string_of_bool b
+ ) args
+ )
+ in
+
+ generate_lang_bindtests (
+ fun f args -> pr " g.%s (%s);\n" f (mkargs args)
+ );
+
+ pr "
+ System.out.println (\"EOF\");
+ }
+ catch (Exception exn) {
+ System.err.println (exn);
+ System.exit (1);
+ }
+ }
+}
+"
+
+and generate_haskell_bindtests () =
+ () (* XXX Haskell bindings need to be fleshed out. *)
+
+(* Language-independent bindings tests - we do it this way to
+ * ensure there is parity in testing bindings across all languages.
+ *)
+and generate_lang_bindtests call =
+ call "test0" [CallString "abc"; CallOptString (Some "def");
+ CallStringList []; CallBool false;
+ CallInt 0; CallString "123"; CallString "456"];
+ call "test0" [CallString "abc"; CallOptString None;
+ CallStringList []; CallBool false;
+ CallInt 0; CallString "123"; CallString "456"];
+ call "test0" [CallString ""; CallOptString (Some "def");
+ CallStringList []; CallBool false;
+ CallInt 0; CallString "123"; CallString "456"];
+ call "test0" [CallString ""; CallOptString (Some "");
+ CallStringList []; CallBool false;
+ CallInt 0; CallString "123"; CallString "456"];
+ call "test0" [CallString "abc"; CallOptString (Some "def");
+ CallStringList ["1"]; CallBool false;
+ CallInt 0; CallString "123"; CallString "456"];
+ call "test0" [CallString "abc"; CallOptString (Some "def");
+ CallStringList ["1"; "2"]; CallBool false;
+ CallInt 0; CallString "123"; CallString "456"];
+ call "test0" [CallString "abc"; CallOptString (Some "def");
+ CallStringList ["1"]; CallBool true;
+ CallInt 0; CallString "123"; CallString "456"];
+ call "test0" [CallString "abc"; CallOptString (Some "def");
+ CallStringList ["1"]; CallBool false;
+ CallInt (-1); CallString "123"; CallString "456"];
+ call "test0" [CallString "abc"; CallOptString (Some "def");
+ CallStringList ["1"]; CallBool false;
+ CallInt (-2); CallString "123"; CallString "456"];
+ call "test0" [CallString "abc"; CallOptString (Some "def");
+ CallStringList ["1"]; CallBool false;
+ CallInt 1; CallString "123"; CallString "456"];
+ call "test0" [CallString "abc"; CallOptString (Some "def");
+ CallStringList ["1"]; CallBool false;
+ CallInt 2; CallString "123"; CallString "456"];
+ call "test0" [CallString "abc"; CallOptString (Some "def");
+ CallStringList ["1"]; CallBool false;
+ CallInt 4095; CallString "123"; CallString "456"];
+ call "test0" [CallString "abc"; CallOptString (Some "def");
+ CallStringList ["1"]; CallBool false;
+ CallInt 0; CallString ""; CallString ""]
+
+ (* XXX Add here tests of the return and error functions. *)
let output_to filename =
let filename_new = filename ^ ".new" in
let close () =
close_out !chan;
chan := stdout;
- Unix.rename filename_new filename;
- printf "written %s\n%!" filename;
+
+ (* Is the new file different from the current file? *)
+ if Sys.file_exists filename && files_equal filename filename_new then
+ Unix.unlink filename_new (* same, so skip it *)
+ else (
+ (* different, overwrite old one *)
+ (try Unix.chmod filename 0o644 with Unix.Unix_error _ -> ());
+ Unix.rename filename_new filename;
+ Unix.chmod filename 0o444;
+ printf "written %s\n%!" filename;
+ )
in
close
generate_daemon_actions ();
close ();
- let close = output_to "tests.c" in
+ let close = output_to "capitests/tests.c" in
generate_tests ();
close ();
+ let close = output_to "src/guestfs-bindtests.c" in
+ generate_bindtests ();
+ close ();
+
let close = output_to "fish/cmds.c" in
generate_fish_cmds ();
close ();
+ let close = output_to "fish/completion.c" in
+ generate_fish_completion ();
+ close ();
+
let close = output_to "guestfs-structs.pod" in
generate_structs_pod ();
close ();
generate_ocaml_c ();
close ();
+ let close = output_to "ocaml/bindtests.ml" in
+ generate_ocaml_bindtests ();
+ close ();
+
let close = output_to "perl/Guestfs.xs" in
generate_perl_xs ();
close ();
generate_perl_pm ();
close ();
+ let close = output_to "perl/bindtests.pl" in
+ generate_perl_bindtests ();
+ close ();
+
let close = output_to "python/guestfs-py.c" in
generate_python_c ();
close ();
let close = output_to "python/guestfs.py" in
generate_python_py ();
close ();
+
+ let close = output_to "python/bindtests.py" in
+ generate_python_bindtests ();
+ close ();
+
+ let close = output_to "ruby/ext/guestfs/_guestfs.c" in
+ generate_ruby_c ();
+ close ();
+
+ let close = output_to "ruby/bindtests.rb" in
+ generate_ruby_bindtests ();
+ close ();
+
+ let close = output_to "java/com/redhat/et/libguestfs/GuestFS.java" in
+ generate_java_java ();
+ close ();
+
+ let close = output_to "java/com/redhat/et/libguestfs/PV.java" in
+ generate_java_struct "PV" pv_cols;
+ close ();
+
+ let close = output_to "java/com/redhat/et/libguestfs/VG.java" in
+ generate_java_struct "VG" vg_cols;
+ close ();
+
+ let close = output_to "java/com/redhat/et/libguestfs/LV.java" in
+ generate_java_struct "LV" lv_cols;
+ close ();
+
+ let close = output_to "java/com/redhat/et/libguestfs/Stat.java" in
+ generate_java_struct "Stat" stat_cols;
+ close ();
+
+ let close = output_to "java/com/redhat/et/libguestfs/StatVFS.java" in
+ generate_java_struct "StatVFS" statvfs_cols;
+ close ();
+
+ let close = output_to "java/com_redhat_et_libguestfs_GuestFS.c" in
+ generate_java_c ();
+ close ();
+
+ let close = output_to "java/Bindtests.java" in
+ generate_java_bindtests ();
+ close ();
+
+ let close = output_to "haskell/Guestfs.hs" in
+ generate_haskell_hs ();
+ close ();
+
+ let close = output_to "haskell/bindtests.hs" in
+ generate_haskell_bindtests ();
+ close ();