X-Git-Url: http://git.annexia.org/?a=blobdiff_plain;ds=sidebyside;f=src%2Fgenerator.ml;h=5e2b4d4b7a2ec5d96348a971124de48f8688b1b6;hb=edd99a3f7903f6f80d3f73643cd6ee114dbdd553;hp=6ef8e1b3435cd28d1f83abe4001a31d14ca5a507;hpb=d268e64fe76944dc042e7ec68a65e59a6cff16ce;p=libguestfs.git
diff --git a/src/generator.ml b/src/generator.ml
index 6ef8e1b..5e2b4d4 100755
--- a/src/generator.ml
+++ b/src/generator.ml
@@ -33,6 +33,7 @@
*)
#load "unix.cma";;
+#load "str.cma";;
open Printf
@@ -43,9 +44,15 @@ and ret =
*)
| 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.
*)
@@ -65,6 +72,18 @@ and ret =
| 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
@@ -80,6 +99,16 @@ and argt =
| 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 *)
@@ -107,7 +136,7 @@ can easily destroy all your data>."
* the virtual machine and block devices are reused between tests.
* So don't try testing kill_subprocess :-x
*
- * Between each test we umount-all and lvm-remove-all.
+ * Between each test we umount-all and lvm-remove-all (except InitNone).
*
* Don't assume anything about the previous contents of the block
* devices. Use 'Init*' to create some initial scenarios.
@@ -141,11 +170,21 @@ and test =
* 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
+
(* Some initial scenarios for testing. *)
and test_init =
(* Do nothing, block devices could contain random stuff including
@@ -246,6 +285,32 @@ The first character of C string must be a C<-> (dash).
C 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
+environment variable.
+
+The string C is stashed in the libguestfs handle, so the caller
+must make sure it remains valid for the lifetime of the handle.
+
+Setting C to C restores the default qemu binary.");
+
+ ("get_qemu", (RConstString "qemu", []), -1, [],
+ [],
+ "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",
@@ -296,7 +361,71 @@ C is defined and set to C<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 state).
+
+For more information on states, see L.");
+
+ ("is_config", (RBool "config", []), -1, [],
+ [],
+ "is in configuration state",
+ "\
+This returns true iff this handle is being configured
+(in the C state).
+
+For more information on states, see L.");
+
+ ("is_launching", (RBool "launching", []), -1, [],
+ [],
+ "is launching subprocess",
+ "\
+This returns true iff this handle is launching the subprocess
+(in the C state).
+
+For more information on states, see L.");
+
+ ("is_busy", (RBool "busy", []), -1, [],
+ [],
+ "is busy processing a command",
+ "\
+This returns true iff this handle is busy processing a command
+(in the C state).
+
+For more information on states, see L.");
+
+ ("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.");
+
+ ("set_busy", (RErr, []), -1, [NotInFish],
+ [],
+ "set state to busy",
+ "\
+This sets the state to C. This is only used when implementing
+actions using the low-level API.
+
+For more information on states, see L.");
+
+ ("set_ready", (RErr, []), -1, [NotInFish],
+ [],
+ "set state to ready",
+ "\
+This sets the state to C. This is only used when implementing
+actions using the low-level API.
+
+For more information on states, see L.");
+
]
let daemon_functions = [
@@ -356,7 +485,7 @@ Return the contents of the file named C.
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
+as end of string). For those you need to use the C
function which has a more complex interface.");
("ll", (RString "listing", [String "directory"]), 5, [],
@@ -475,24 +604,21 @@ This returns a list of the logical volume device names
See also C.");
("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 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 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
@@ -889,12 +1015,24 @@ pass C as a single element list, when the single element being
the string C<,> (comma).");
("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, TestOutput (
+ [["write_file"; "/new"; "new file contents"; "0"];
+ ["cat"; "/new"]], "new file contents");
+ InitBasicFS, TestOutput (
+ [["write_file"; "/new"; "\nnew file contents\n"; "0"];
+ ["cat"; "/new"]], "\nnew file contents\n");
+ InitBasicFS, TestOutput (
+ [["write_file"; "/new"; "\n\n"; "0"];
+ ["cat"; "/new"]], "\n\n");
+ InitBasicFS, TestOutput (
+ [["write_file"; "/new"; ""; "0"];
+ ["cat"; "/new"]], "");
+ InitBasicFS, TestOutput (
+ [["write_file"; "/new"; "\n\n\n"; "0"];
+ ["cat"; "/new"]], "\n\n\n");
+ InitBasicFS, TestOutput (
+ [["write_file"; "/new"; "\n"; "0"];
+ ["cat"; "/new"]], "\n")],
"create a file",
"\
This call creates a file called C. The contents of the
@@ -1001,6 +1139,432 @@ locations.");
This is the same as C, but splits the
result into a list of lines.");
+ ("stat", (RStat "statbuf", [String "path"]), 52, [],
+ [InitBasicFS, TestOutputStruct (
+ [["touch"; "/new"];
+ ["stat"; "/new"]], [CompareWithInt ("size", 0)])],
+ "get file information",
+ "\
+Returns file information for the given C.
+
+This is the same as the C system call.");
+
+ ("lstat", (RStat "statbuf", [String "path"]), 53, [],
+ [InitBasicFS, TestOutputStruct (
+ [["touch"; "/new"];
+ ["lstat"; "/new"]], [CompareWithInt ("size", 0)])],
+ "get file information for a symbolic link",
+ "\
+Returns file information for the given C.
+
+This is the same as C except that if C
+is a symbolic link, then the link is stat-ed, not the file it
+refers to.
+
+This is the same as the C system call.");
+
+ ("statvfs", (RStatVFS "statbuf", [String "path"]), 54, [],
+ [InitBasicFS, 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 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).
+
+This is the same as the C system call.");
+
+ ("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.
+
+It is the same as running C. See L
+manpage for more details. The list of fields returned isn't
+clearly defined, and depends on both the version of C
+that libguestfs was built against, and the filesystem itself.");
+
+ ("blockdev_setro", (RErr, [String "device"]), 56, [],
+ [InitEmpty, TestOutputTrue (
+ [["blockdev_setro"; "/dev/sda"];
+ ["blockdev_getro"; "/dev/sda"]])],
+ "set block device to read-only",
+ "\
+Sets the block device named C to read-only.
+
+This uses the L command.");
+
+ ("blockdev_setrw", (RErr, [String "device"]), 57, [],
+ [InitEmpty, TestOutputFalse (
+ [["blockdev_setrw"; "/dev/sda"];
+ ["blockdev_getro"; "/dev/sda"]])],
+ "set block device to read-write",
+ "\
+Sets the block device named C to read-write.
+
+This uses the L command.");
+
+ ("blockdev_getro", (RBool "ro", [String "device"]), 58, [],
+ [InitEmpty, 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).
+
+This uses the L command.");
+
+ ("blockdev_getss", (RInt "sectorsize", [String "device"]), 59, [],
+ [InitEmpty, 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.
+
+(Note, this is not the size in sectors, use C
+for that).
+
+This uses the L command.");
+
+ ("blockdev_getbsz", (RInt "blocksize", [String "device"]), 60, [],
+ [InitEmpty, TestOutputInt (
+ [["blockdev_getbsz"; "/dev/sda"]], 4096)],
+ "get blocksize of block device",
+ "\
+This returns the block size of a device.
+
+(Note this is different from both I and
+I).
+
+This uses the L command.");
+
+ ("blockdev_setbsz", (RErr, [String "device"; Int "blocksize"]), 61, [],
+ [], (* XXX test *)
+ "set blocksize of block device",
+ "\
+This sets the block size of a device.
+
+(Note this is different from both I and
+I).
+
+This uses the L command.");
+
+ ("blockdev_getsz", (RInt64 "sizeinsectors", [String "device"]), 62, [],
+ [InitEmpty, 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).
+
+See also C for the real sector size of
+the device, and C for the more
+useful I.
+
+This uses the L command.");
+
+ ("blockdev_getsize64", (RInt64 "sizeinbytes", [String "device"]), 63, [],
+ [InitEmpty, TestOutputInt (
+ [["blockdev_getsize64"; "/dev/sda"]], 524288000)],
+ "get total size of device in bytes",
+ "\
+This returns the size of the device in bytes.
+
+See also C.
+
+This uses the L command.");
+
+ ("blockdev_flushbufs", (RErr, [String "device"]), 64, [],
+ [InitEmpty, TestRun
+ [["blockdev_flushbufs"; "/dev/sda"]]],
+ "flush device buffers",
+ "\
+This tells the kernel to flush internal buffers associated
+with C.
+
+This uses the L command.");
+
+ ("blockdev_rereadpt", (RErr, [String "device"]), 65, [],
+ [InitEmpty, TestRun
+ [["blockdev_rereadpt"; "/dev/sda"]]],
+ "reread partition table",
+ "\
+Reread the partition table on C.
+
+This uses the L command.");
+
+ ("upload", (RErr, [FileIn "filename"; String "remotefilename"]), 66, [],
+ [InitBasicFS, 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 to C on the
+filesystem.
+
+C can also be a named pipe.
+
+See also C.");
+
+ ("download", (RErr, [String "remotefilename"; FileOut "filename"]), 67, [],
+ [InitBasicFS, 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 and save it as C
+on the local machine.
+
+C can also be a named pipe.
+
+See also C, C.");
+
+ ("checksum", (RString "checksum", [String "csumtype"; String "path"]), 68, [],
+ [InitBasicFS, TestOutput (
+ [["write_file"; "/new"; "test\n"; "0"];
+ ["checksum"; "crc"; "/new"]], "935282863");
+ InitBasicFS, TestLastFail (
+ [["checksum"; "crc"; "/new"]]);
+ InitBasicFS, TestOutput (
+ [["write_file"; "/new"; "test\n"; "0"];
+ ["checksum"; "md5"; "/new"]], "d8e8fca2dc0f896fd7cb4cb0031ba249");
+ InitBasicFS, TestOutput (
+ [["write_file"; "/new"; "test\n"; "0"];
+ ["checksum"; "sha1"; "/new"]], "4e1243bd22c66e76c2ba9eddc1f91394e57f9f83");
+ InitBasicFS, TestOutput (
+ [["write_file"; "/new"; "test\n"; "0"];
+ ["checksum"; "sha224"; "/new"]], "52f1bf093f4b7588726035c176c0cdb4376cfea53819f1395ac9e6ec");
+ InitBasicFS, TestOutput (
+ [["write_file"; "/new"; "test\n"; "0"];
+ ["checksum"; "sha256"; "/new"]], "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2");
+ InitBasicFS, TestOutput (
+ [["write_file"; "/new"; "test\n"; "0"];
+ ["checksum"; "sha384"; "/new"]], "109bb6b5b6d5547c1ce03c7a8bd7d8f80c1cb0957f50c4f7fda04692079917e4f9cad52b878f3d8234e1a170b154b72d");
+ InitBasicFS, TestOutput (
+ [["write_file"; "/new"; "test\n"; "0"];
+ ["checksum"; "sha512"; "/new"]], "0e3e75234abc68f4378a86b3f4b32a198ba301845b0cd6e50106e874345700cc6663a86c1ea125dc5e92be17c98f9a0f85ca9d5f595db2012f7cc3571945c123")],
+ "compute MD5, SHAx or CRC checksum of file",
+ "\
+This call computes the MD5, SHAx or CRC checksum of the
+file named C.
+
+The type of checksum to compute is given by the C
+parameter which must have one of the following values:
+
+=over 4
+
+=item C
+
+Compute the cyclic redundancy check (CRC) specified by POSIX
+for the C command.
+
+=item C
+
+Compute the MD5 hash (using the C program).
+
+=item C
+
+Compute the SHA1 hash (using the C program).
+
+=item C
+
+Compute the SHA224 hash (using the C program).
+
+=item C
+
+Compute the SHA256 hash (using the C program).
+
+=item C
+
+Compute the SHA384 hash (using the C program).
+
+=item C
+
+Compute the SHA512 hash (using the C program).
+
+=back
+
+The checksum is returned as a printable string.");
+
+ ("tar_in", (RErr, [FileIn "tarfile"; String "directory"]), 69, [],
+ [InitBasicFS, TestOutput (
+ [["tar_in"; "images/helloworld.tar"; "/"];
+ ["cat"; "/hello"]], "hello\n")],
+ "unpack tarfile to directory",
+ "\
+This command uploads and unpacks local file C (an
+I tar file) into C.
+
+To upload a compressed tarball, use C.");
+
+ ("tar_out", (RErr, [String "directory"; FileOut "tarfile"]), 70, [],
+ [],
+ "pack directory into tarfile",
+ "\
+This command packs the contents of C and downloads
+it to local file C.
+
+To download a compressed tarball, use C.");
+
+ ("tgz_in", (RErr, [FileIn "tarball"; String "directory"]), 71, [],
+ [InitBasicFS, TestOutput (
+ [["tgz_in"; "images/helloworld.tar.gz"; "/"];
+ ["cat"; "/hello"]], "hello\n")],
+ "unpack compressed tarball to directory",
+ "\
+This command uploads and unpacks local file C (a
+I tar file) into C.
+
+To upload an uncompressed tarball, use C.");
+
+ ("tgz_out", (RErr, [String "directory"; FileOut "tarball"]), 72, [],
+ [],
+ "pack directory into compressed tarball",
+ "\
+This command packs the contents of C and downloads
+it to local file C.
+
+To download an uncompressed tarball, use C.");
+
+ ("mount_ro", (RErr, [String "device"; String "mountpoint"]), 73, [],
+ [InitBasicFS, TestLastFail (
+ [["umount"; "/"];
+ ["mount_ro"; "/dev/sda1"; "/"];
+ ["touch"; "/new"]]);
+ InitBasicFS, 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 command, but it
+mounts the filesystem with the read-only (I<-o ro>) flag.");
+
+ ("mount_options", (RErr, [String "options"; String "device"; String "mountpoint"]), 74, [],
+ [],
+ "mount a guest disk with mount options",
+ "\
+This is the same as the C command, but it
+allows you to set the mount options as for the
+L I<-o> flag.");
+
+ ("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 command, but it
+allows you to set both the mount options and the vfstype
+as for the L I<-o> and I<-t> flags.");
+
+ ("debug", (RString "result", [String "subcmd"; StringList "extraargs"]), 76, [],
+ [],
+ "debugging and internals",
+ "\
+The C command exposes some internals of
+C (the guestfs daemon) that runs inside the
+qemu subprocess.
+
+There is no comprehensive help for this command. You have
+to look at the file C in the libguestfs source
+to find out what you can do.");
+
+ ("lvremove", (RErr, [String "device"]), 77, [],
+ [InitEmpty, TestOutputList (
+ [["pvcreate"; "/dev/sda"];
+ ["vgcreate"; "VG"; "/dev/sda"];
+ ["lvcreate"; "LV1"; "VG"; "50"];
+ ["lvcreate"; "LV2"; "VG"; "50"];
+ ["lvremove"; "/dev/VG/LV1"];
+ ["lvs"]], ["/dev/VG/LV2"]);
+ InitEmpty, TestOutputList (
+ [["pvcreate"; "/dev/sda"];
+ ["vgcreate"; "VG"; "/dev/sda"];
+ ["lvcreate"; "LV1"; "VG"; "50"];
+ ["lvcreate"; "LV2"; "VG"; "50"];
+ ["lvremove"; "/dev/VG"];
+ ["lvs"]], []);
+ InitEmpty, TestOutputList (
+ [["pvcreate"; "/dev/sda"];
+ ["vgcreate"; "VG"; "/dev/sda"];
+ ["lvcreate"; "LV1"; "VG"; "50"];
+ ["lvcreate"; "LV2"; "VG"; "50"];
+ ["lvremove"; "/dev/VG"];
+ ["vgs"]], ["VG"])],
+ "remove an LVM logical volume",
+ "\
+Remove an LVM logical volume C, where C is
+the path to the LV, such as C.
+
+You can also remove all LVs in a volume group by specifying
+the VG name, C.");
+
+ ("vgremove", (RErr, [String "vgname"]), 78, [],
+ [InitEmpty, TestOutputList (
+ [["pvcreate"; "/dev/sda"];
+ ["vgcreate"; "VG"; "/dev/sda"];
+ ["lvcreate"; "LV1"; "VG"; "50"];
+ ["lvcreate"; "LV2"; "VG"; "50"];
+ ["vgremove"; "VG"];
+ ["lvs"]], []);
+ InitEmpty, TestOutputList (
+ [["pvcreate"; "/dev/sda"];
+ ["vgcreate"; "VG"; "/dev/sda"];
+ ["lvcreate"; "LV1"; "VG"; "50"];
+ ["lvcreate"; "LV2"; "VG"; "50"];
+ ["vgremove"; "VG"];
+ ["vgs"]], [])],
+ "remove an LVM volume group",
+ "\
+Remove an LVM volume group C, (for example C).
+
+This also forcibly removes all logical volumes in the volume
+group (if any).");
+
+ ("pvremove", (RErr, [String "device"]), 79, [],
+ [InitEmpty, TestOutputList (
+ [["pvcreate"; "/dev/sda"];
+ ["vgcreate"; "VG"; "/dev/sda"];
+ ["lvcreate"; "LV1"; "VG"; "50"];
+ ["lvcreate"; "LV2"; "VG"; "50"];
+ ["vgremove"; "VG"];
+ ["pvremove"; "/dev/sda"];
+ ["lvs"]], []);
+ InitEmpty, TestOutputList (
+ [["pvcreate"; "/dev/sda"];
+ ["vgcreate"; "VG"; "/dev/sda"];
+ ["lvcreate"; "LV1"; "VG"; "50"];
+ ["lvcreate"; "LV2"; "VG"; "50"];
+ ["vgremove"; "VG"];
+ ["pvremove"; "/dev/sda"];
+ ["vgs"]], []);
+ InitEmpty, TestOutputList (
+ [["pvcreate"; "/dev/sda"];
+ ["vgcreate"; "VG"; "/dev/sda"];
+ ["lvcreate"; "LV1"; "VG"; "50"];
+ ["lvcreate"; "LV2"; "VG"; "50"];
+ ["vgremove"; "VG"];
+ ["pvremove"; "/dev/sda"];
+ ["pvs"]], [])],
+ "remove an LVM physical volume",
+ "\
+This wipes a physical volume C so that LVM will no longer
+recognise it.
+
+The implementation uses the C command which refuses to
+wipe physical volumes that contain any volume groups, so you have
+to remove those first.");
+
]
let all_functions = non_daemon_functions @ daemon_functions
@@ -1075,6 +1639,39 @@ let lv_cols = [
"modules", `String;
]
+(* 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;
+]
+
(* Useful functions.
* Note we don't want to use any external OCaml libraries which
* makes this a bit harder than it should be.
@@ -1092,6 +1689,31 @@ let replace_char s c1 c2 =
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
@@ -1155,7 +1777,14 @@ let mapi f xs =
loop 0 xs
let name_of_argt = function
- | String n | OptString n | StringList n | Bool n | Int n -> n
+ | 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 () =
@@ -1202,8 +1831,10 @@ let check_functions () =
(match fst style with
| RErr -> ()
- | RInt n | RBool n | RConstString n | RString n
- | RStringList n | RPVList n | RVGList n | RLVList n ->
+ | 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;
@@ -1256,7 +1887,31 @@ let check_functions () =
failwithf "%s and %s have conflicting procedure numbers (%d, %d)"
name1 name2 nr1 nr2
in
- loop proc_nrs
+ 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
@@ -1332,6 +1987,8 @@ let rec generate_actions_pod () =
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 _ ->
@@ -1345,17 +2002,40 @@ I.\n\n"
(like L), or NULL if there was an error.
I.\n\n"
| RIntBool _ ->
- pr "This function returns a C.
+ pr "This function returns a C,
+or NULL if there was an error.
I after use>.\n\n"
| RPVList _ ->
- pr "This function returns a C.
+ pr "This function returns a C
+(see Eguestfs-structs.hE),
+or NULL if there was an error.
I after use>.\n\n"
| RVGList _ ->
- pr "This function returns a C.
+ pr "This function returns a C
+(see Eguestfs-structs.hE),
+or NULL if there was an error.
I after use>.\n\n"
| RLVList _ ->
- pr "This function returns a C.
+ pr "This function returns a C
+(see Eguestfs-structs.hE),
+or NULL if there was an error.
I after use>.\n\n"
+ | RStat _ ->
+ pr "This function returns a C
+(see L and Eguestfs-structs.hE),
+or NULL if there was an error.
+I after use>.\n\n"
+ | RStatVFS _ ->
+ pr "This function returns a C
+(see L and Eguestfs-structs.hE),
+or NULL if there was an error.
+I 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 keys and values alternate, followed by the trailing NULL entry.
+I.\n\n"
);
if List.mem ProtocolLimitWarning flags then
pr "%s\n\n" protocol_limit_warning;
@@ -1426,6 +2106,18 @@ and generate_xdr () =
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
@@ -1441,6 +2133,7 @@ and generate_xdr () =
| 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"
);
@@ -1450,6 +2143,10 @@ and generate_xdr () =
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;
@@ -1481,6 +2178,18 @@ and generate_xdr () =
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;
@@ -1490,7 +2199,7 @@ and generate_xdr () =
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 " GUESTFS_PROC_NR_PROCS\n";
pr "};\n";
pr "\n";
@@ -1505,9 +2214,17 @@ and generate_xdr () =
(* 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 */
@@ -1521,7 +2238,7 @@ enum guestfs_message_status {
const GUESTFS_ERROR_LEN = 256;
struct guestfs_message_error {
- string error; /* error message */
+ string error_message;
};
struct guestfs_message_header {
@@ -1532,6 +2249,14 @@ struct guestfs_message_header {
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;
+};
"
(* Generate the guestfs-structs.h file. *)
@@ -1579,7 +2304,20 @@ and generate_structs_h () =
pr " struct guestfs_lvm_%s *val;\n" typ;
pr "};\n";
pr "\n"
- ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols]
+ ) ["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 () =
@@ -1595,40 +2333,127 @@ and generate_actions_h () =
and generate_client_actions () =
generate_header CStyle LGPLv2;
- (* Client-side stubs for each function. *)
- List.iter (
+ pr "\
+#include
+#include
+
+#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 return value struct. *)
- pr "struct %s_rv {\n" shortname;
- pr " int cb_done; /* flag to indicate callback was called */\n";
+ (* 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 _
+ | RInt _ | RInt64 _
| RBool _ | RString _ | RStringList _
| RIntBool _
- | RPVList _ | RVGList _ | RLVList _ ->
+ | RPVList _ | RVGList _ | RLVList _
+ | RStat _ | RStatVFS _
+ | RHashtable _ ->
pr " struct %s_ret ret;\n" name
);
- pr "};\n\n";
+ pr "};\n";
+ pr "\n";
- (* Generate the callback function. *)
- pr "static void %s_cb (guestfs_h *g, void *data, XDR *xdr)\n" shortname;
+ (* Generate the reply callback function. *)
+ pr "static void %s_reply_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 " 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, &rv->hdr)) {\n";
- pr " error (g, \"%s: failed to parse reply header\");\n" name;
+ 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 (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 " 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";
@@ -1638,19 +2463,20 @@ and generate_client_actions () =
| RErr -> ()
| RConstString _ ->
failwithf "RConstString cannot be returned from a daemon function"
- | RInt _
+ | RInt _ | RInt64 _
| 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;
+ | 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 " rv->cb_done = 1;\n";
- pr " main_loop.main_loop_quit (g);\n";
+ pr " ctx->cb_sequence = 1;\n";
pr "}\n\n";
(* Generate the action stub. *)
@@ -1659,11 +2485,13 @@ and generate_client_actions () =
let error_code =
match fst style with
- | RErr | RInt _ | RBool _ -> "-1"
+ | RErr | RInt _ | RInt64 _ | RBool _ -> "-1"
| RConstString _ ->
failwithf "RConstString cannot be returned from a daemon function"
| RString _ | RStringList _ | RIntBool _
- | RPVList _ | RVGList _ | RLVList _ ->
+ | RPVList _ | RVGList _ | RLVList _
+ | RStat _ | RStatVFS _
+ | RHashtable _ ->
"NULL" in
pr "{\n";
@@ -1673,22 +2501,20 @@ and generate_client_actions () =
| _ -> pr " struct %s_args args;\n" name
);
- pr " struct %s_rv rv;\n" shortname;
+ 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 (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 " if (check_state (g, \"%s\") == -1) return %s;\n" name error_code;
+ pr " guestfs_set_busy (g);\n";
pr "\n";
- pr " memset (&rv, 0, sizeof rv);\n";
+ pr " memset (&ctx, 0, sizeof ctx);\n";
pr "\n";
+ (* Send the main header and arguments. *)
(match snd style with
| [] ->
- pr " serial = dispatch (g, GUESTFS_PROC_%s, NULL, NULL);\n"
+ pr " serial = guestfs__send_sync (g, GUESTFS_PROC_%s, NULL, NULL);\n"
(String.uppercase shortname)
| args ->
List.iter (
@@ -1704,67 +2530,105 @@ and generate_client_actions () =
pr " args.%s = %s;\n" n n
| Int n ->
pr " args.%s = %s;\n" n n
+ | FileIn _ | FileOut _ -> ()
) args;
- pr " serial = dispatch (g, GUESTFS_PROC_%s,\n"
+ pr " serial = guestfs__send_sync (g, GUESTFS_PROC_%s,\n"
(String.uppercase shortname);
- pr " (xdrproc_t) xdr_%s_args, (char *) &args);\n"
+ pr " (xdrproc_t) xdr_%s_args, (char *) &args);\n"
name;
);
- pr " if (serial == -1)\n";
+ pr " if (serial == -1) {\n";
+ pr " guestfs_set_ready (g);\n";
pr " return %s;\n" error_code;
+ pr " }\n";
pr "\n";
- 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;
+ (* 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_set_ready (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_set_ready (g);\n";
pr " return %s;\n" error_code;
pr " }\n";
pr "\n";
- pr " if (check_reply_header (g, &rv.hdr, GUESTFS_PROC_%s, serial) == -1)\n"
+ pr " if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_%s, serial) == -1) {\n"
(String.uppercase shortname);
+ pr " guestfs_set_ready (g);\n";
pr " return %s;\n" error_code;
+ pr " }\n";
pr "\n";
- pr " if (rv.hdr.status == GUESTFS_STATUS_ERROR) {\n";
- pr " error (g, \"%%s\", rv.err.error);\n";
+ pr " if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {\n";
+ pr " error (g, \"%%s\", ctx.err.error_message);\n";
+ pr " guestfs_set_ready (g);\n";
pr " 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_set_ready (g);\n";
+ pr " return %s;\n" error_code;
+ pr " }\n";
+ pr "\n";
+ | _ -> ()
+ ) (snd style);
+
+ pr " guestfs_set_ready (g);\n";
+
(match fst style with
| RErr -> pr " return 0;\n"
- | RInt n
- | RBool n -> pr " return rv.ret.%s;\n" 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 rv.ret.%s; /* caller will free */\n" n
- | RStringList 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 " 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"
+ 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 " 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
+ 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, &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 " 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, &rv.ret.%s, sizeof (rv.ret.%s));\n" n n
+ pr " return safe_memdup (g, &ctx.ret.%s, sizeof (ctx.ret.%s));\n" n n
);
pr "}\n\n"
@@ -1788,7 +2652,7 @@ and generate_daemon_actions_h () =
and generate_daemon_actions () =
generate_header CStyle GPLv2;
- pr "#define _GNU_SOURCE // for strchrnul\n";
+ pr "#include \n";
pr "\n";
pr "#include \n";
pr "#include \n";
@@ -1811,15 +2675,18 @@ and generate_daemon_actions () =
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 _ -> 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" in
+ | 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
| [] -> ()
@@ -1832,6 +2699,7 @@ and generate_daemon_actions () =
| 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";
@@ -1855,12 +2723,19 @@ and generate_daemon_actions () =
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
+ | 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 style;
+ generate_call_args argsnofile;
pr ";\n";
pr " if (r == %s)\n" error_code;
@@ -1868,47 +2743,48 @@ and generate_daemon_actions () =
pr " goto done;\n";
pr "\n";
- (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
+ (* 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. *)
@@ -2050,6 +2926,7 @@ and generate_daemon_actions () =
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";
@@ -2138,13 +3015,40 @@ static void print_strings (char * const * const argv)
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
- ) all_functions in
+ ) (List.rev all_functions) in
let test_names = List.concat test_names in
let nr_tests = List.length test_names in
@@ -2154,8 +3058,11 @@ int main (int argc, char *argv[])
char c = 0;
int failed = 0;
const char *srcdir;
+ const char *filename;
int fd;
- char buf[256];
+ int nr_tests, test_num = 0;
+
+ no_test_warnings ();
g = guestfs_create ();
if (g == NULL) {
@@ -2167,89 +3074,90 @@ int main (int argc, char *argv[])
srcdir = getenv (\"srcdir\");
if (!srcdir) srcdir = \".\";
- guestfs_set_path (g, srcdir);
+ chdir (srcdir);
+ guestfs_set_path (g, \".\");
- snprintf (buf, sizeof buf, \"%%s/test1.img\", srcdir);
- fd = open (buf, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK|O_TRUNC, 0666);
+ filename = \"test1.img\";
+ fd = open (filename, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK|O_TRUNC, 0666);
if (fd == -1) {
- perror (buf);
+ perror (filename);
exit (1);
}
if (lseek (fd, %d, SEEK_SET) == -1) {
perror (\"lseek\");
close (fd);
- unlink (buf);
+ unlink (filename);
exit (1);
}
if (write (fd, &c, 1) == -1) {
perror (\"write\");
close (fd);
- unlink (buf);
+ unlink (filename);
exit (1);
}
if (close (fd) == -1) {
- perror (buf);
- unlink (buf);
+ perror (filename);
+ unlink (filename);
exit (1);
}
- if (guestfs_add_drive (g, buf) == -1) {
- printf (\"guestfs_add_drive %%s FAILED\\n\", buf);
+ if (guestfs_add_drive (g, filename) == -1) {
+ printf (\"guestfs_add_drive %%s FAILED\\n\", filename);
exit (1);
}
- snprintf (buf, sizeof buf, \"%%s/test2.img\", srcdir);
- fd = open (buf, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK|O_TRUNC, 0666);
+ filename = \"test2.img\";
+ fd = open (filename, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK|O_TRUNC, 0666);
if (fd == -1) {
- perror (buf);
+ perror (filename);
exit (1);
}
if (lseek (fd, %d, SEEK_SET) == -1) {
perror (\"lseek\");
close (fd);
- unlink (buf);
+ unlink (filename);
exit (1);
}
if (write (fd, &c, 1) == -1) {
perror (\"write\");
close (fd);
- unlink (buf);
+ unlink (filename);
exit (1);
}
if (close (fd) == -1) {
- perror (buf);
- unlink (buf);
+ perror (filename);
+ unlink (filename);
exit (1);
}
- if (guestfs_add_drive (g, buf) == -1) {
- printf (\"guestfs_add_drive %%s FAILED\\n\", buf);
+ if (guestfs_add_drive (g, filename) == -1) {
+ printf (\"guestfs_add_drive %%s FAILED\\n\", filename);
exit (1);
}
- snprintf (buf, sizeof buf, \"%%s/test3.img\", srcdir);
- fd = open (buf, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK|O_TRUNC, 0666);
+ filename = \"test3.img\";
+ fd = open (filename, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK|O_TRUNC, 0666);
if (fd == -1) {
- perror (buf);
+ perror (filename);
exit (1);
}
if (lseek (fd, %d, SEEK_SET) == -1) {
perror (\"lseek\");
close (fd);
- unlink (buf);
+ unlink (filename);
exit (1);
}
if (write (fd, &c, 1) == -1) {
perror (\"write\");
close (fd);
- unlink (buf);
+ unlink (filename);
exit (1);
}
if (close (fd) == -1) {
- perror (buf);
- unlink (buf);
+ perror (filename);
+ unlink (filename);
exit (1);
}
- if (guestfs_add_drive (g, buf) == -1) {
- printf (\"guestfs_add_drive %%s FAILED\\n\", buf);
+ if (guestfs_add_drive (g, filename) == -1) {
+ printf (\"guestfs_add_drive %%s FAILED\\n\", filename);
exit (1);
}
@@ -2262,11 +3170,14 @@ int main (int argc, char *argv[])
exit (1);
}
-" (500 * 1024 * 1024) (50 * 1024 * 1024) (10 * 1024 * 1024);
+ nr_tests = %d;
+
+" (500 * 1024 * 1024) (50 * 1024 * 1024) (10 * 1024 * 1024) nr_tests;
iteri (
fun i test_name ->
- pr " printf (\"%3d/%3d %s\\n\");\n" (i+1) nr_tests 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";
@@ -2275,17 +3186,13 @@ int main (int argc, char *argv[])
pr "\n";
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 " 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);\n"
- nr_tests;
+ pr " printf (\"***** %%d / %%d tests FAILED *****\\n\", failed, nr_tests);\n";
pr " exit (1);\n";
pr " }\n";
pr "\n";
@@ -2382,8 +3289,9 @@ and generate_one_test name i (init, test) =
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"
+ pr " fprintf (stderr, \"%s: expected %d but got %%d\\n\","
test_name expected;
+ pr " (int) r);\n";
pr " return -1;\n";
pr " }\n"
in
@@ -2434,6 +3342,44 @@ and generate_one_test name i (init, test) =
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
@@ -2474,6 +3420,7 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
| OptString _, _
| Int _, _
| Bool _, _ -> ()
+ | FileIn _, _ | FileOut _, _ -> ()
| StringList n, arg ->
pr " char *%s[] = {\n" n;
let strs = string_split " " arg in
@@ -2487,24 +3434,25 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
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 _ ->
+ | RStringList _ | RHashtable _ ->
pr " char **r;\n";
pr " int i;\n";
"NULL"
| RIntBool _ ->
- pr " struct guestfs_int_bool *r;\n";
- "NULL"
+ pr " struct guestfs_int_bool *r;\n"; "NULL"
| RPVList _ ->
- pr " struct guestfs_lvm_pv_list *r;\n";
- "NULL"
+ pr " struct guestfs_lvm_pv_list *r;\n"; "NULL"
| RVGList _ ->
- pr " struct guestfs_lvm_vg_list *r;\n";
- "NULL"
+ pr " struct guestfs_lvm_vg_list *r;\n"; "NULL"
| RLVList _ ->
- pr " struct guestfs_lvm_lv_list *r;\n";
- "NULL" in
+ 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;
@@ -2512,7 +3460,9 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
(* Generate the parameters. *)
List.iter (
function
- | String _, arg -> pr ", \"%s\"" (c_quote arg)
+ | String _, arg
+ | FileIn _, arg | FileOut _, arg ->
+ pr ", \"%s\"" (c_quote arg)
| OptString _, arg ->
if arg = "NULL" then pr ", NULL" else pr ", \"%s\"" (c_quote arg)
| StringList n, _ ->
@@ -2541,9 +3491,9 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
);
(match fst style with
- | RErr | RInt _ | RBool _ | RConstString _ -> ()
+ | RErr | RInt _ | RInt64 _ | RBool _ | RConstString _ -> ()
| RString _ -> pr " free (r);\n"
- | RStringList _ ->
+ | RStringList _ | RHashtable _ ->
pr " for (i = 0; r[i] != NULL; ++i)\n";
pr " free (r[i]);\n";
pr " free (r);\n"
@@ -2555,6 +3505,8 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
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"
@@ -2694,6 +3646,21 @@ and generate_fish_cmds () =
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_ actions *)
List.iter (
fun (name, style, _, flags, _, _, _) ->
@@ -2703,18 +3670,23 @@ and generate_fish_cmds () =
| 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 _ -> 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 -> pr " const char *%s;\n" 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
@@ -2735,6 +3707,12 @@ and generate_fish_cmds () =
| 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 ->
@@ -2748,7 +3726,7 @@ and generate_fish_cmds () =
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;
+ generate_call_args ~handle:"g" (snd style);
pr ";\n";
(* Check return value for errors and display command results. *)
@@ -2756,7 +3734,11 @@ and generate_fish_cmds () =
| RErr -> pr " return r;\n"
| RInt _ ->
pr " if (r == -1) return -1;\n";
- pr " if (r) printf (\"%%d\\n\", r);\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";
@@ -2797,6 +3779,21 @@ and generate_fish_cmds () =
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"
@@ -2917,9 +3914,19 @@ and generate_fish_actions_pod () =
fun (_, _, _, flags, _, _, _) -> not (List.mem NotInFish flags)
) all_functions_sorted in
+ let rex = Str.regexp "C]+\\)>" in
+
List.iter (
fun (name, style, _, flags, _, _, longdesc) ->
- let longdesc = replace_str longdesc "C
+ let sub =
+ try Str.matched_group 1 s
+ with Not_found ->
+ failwithf "error substituting C 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
@@ -2935,14 +3942,19 @@ and generate_fish_actions_pod () =
function
| String n -> pr " %s" n
| OptString n -> pr " %s" n
- | StringList 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;
@@ -2960,10 +3972,11 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
(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 _ -> pr "char **"
+ | RStringList _ | RHashtable _ -> pr "char **"
| RIntBool _ ->
if not in_daemon then pr "struct guestfs_int_bool *"
else pr "guestfs_%s_ret *" name
@@ -2976,6 +3989,12 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
| 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
@@ -2994,11 +4013,14 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
in
List.iter (
function
- | String n -> next (); pr "const char *%s" n
+ | 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 ")";
@@ -3006,7 +4028,7 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
if newline then pr "\n"
(* Generate C call arguments, eg "(handle, foo, bar)" *)
-and generate_call_args ?handle style =
+and generate_call_args ?handle args =
pr "(";
let comma = ref false in
(match handle with
@@ -3017,13 +4039,8 @@ and generate_call_args ?handle style =
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 "%s" (name_of_argt arg)
+ ) args;
pr ")"
(* Generate the OCaml bindings interface. *)
@@ -3051,6 +4068,8 @@ val close : t -> unit
";
generate_ocaml_lvm_structure_decls ();
+ generate_ocaml_stat_structure_decls ();
+
(* The actions. *)
List.iter (
fun (name, style, _, _, _, shortdesc, _) ->
@@ -3076,6 +4095,8 @@ let () =
generate_ocaml_lvm_structure_decls ();
+ generate_ocaml_stat_structure_decls ();
+
(* The actions. *)
List.iter (
fun (name, style, _, _, _, shortdesc, _) ->
@@ -3086,22 +4107,51 @@ let () =
and generate_ocaml_c () =
generate_header CStyle LGPLv2;
- pr "#include \n";
- pr "#include \n";
- pr "#include \n";
- pr "\n";
- pr "#include \n";
- pr "#include \n";
- pr "#include \n";
- pr "#include \n";
- pr "#include \n";
- pr "#include \n";
- pr "#include \n";
- pr "\n";
- pr "#include \n";
- pr "\n";
- pr "#include \"guestfs_c.h\"\n";
- pr "\n";
+ pr "\
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#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 (
@@ -3166,6 +4216,30 @@ and generate_ocaml_c () =
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 =
@@ -3178,6 +4252,8 @@ and generate_ocaml_c () =
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"
@@ -3195,7 +4271,9 @@ and generate_ocaml_c () =
List.iter (
function
- | String n ->
+ | String n
+ | FileIn n
+ | FileOut n ->
pr " const char *%s = String_val (%sv);\n" n n
| OptString n ->
pr " const char *%s =\n" n;
@@ -3212,6 +4290,7 @@ and generate_ocaml_c () =
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"
@@ -3220,22 +4299,26 @@ and generate_ocaml_c () =
pr " char **r;\n";
"NULL"
| RIntBool _ ->
- pr " struct guestfs_int_bool *r;\n";
- "NULL"
+ pr " struct guestfs_int_bool *r;\n"; "NULL"
| RPVList _ ->
- pr " struct guestfs_lvm_pv_list *r;\n";
- "NULL"
+ pr " struct guestfs_lvm_pv_list *r;\n"; "NULL"
| RVGList _ ->
- pr " struct guestfs_lvm_vg_list *r;\n";
- "NULL"
+ pr " struct guestfs_lvm_vg_list *r;\n"; "NULL"
| RLVList _ ->
- pr " struct guestfs_lvm_lv_list *r;\n";
+ 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" style;
+ generate_call_args ~handle:"g" (snd style);
pr ";\n";
pr " caml_leave_blocking_section ();\n";
@@ -3243,7 +4326,7 @@ and generate_ocaml_c () =
function
| StringList n ->
pr " ocaml_guestfs_free_strings (%s);\n" n;
- | String _ | OptString _ | Bool _ | Int _ -> ()
+ | String _ | OptString _ | Bool _ | Int _ | FileIn _ | FileOut _ -> ()
) (snd style);
pr " if (r == %s)\n" error_code;
@@ -3253,6 +4336,8 @@ and generate_ocaml_c () =
(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 _ ->
@@ -3276,6 +4361,16 @@ and generate_ocaml_c () =
| 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";
@@ -3310,12 +4405,24 @@ and generate_ocaml_lvm_structure_decls () =
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 _ -> pr "string -> "
+ | String _ | FileIn _ | FileOut _ -> pr "string -> "
| OptString _ -> pr "string option -> "
| StringList _ -> pr "string array -> "
| Bool _ -> pr "bool -> "
@@ -3324,6 +4431,7 @@ and generate_ocaml_prototype ?(is_external = false) name 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"
@@ -3332,6 +4440,9 @@ and generate_ocaml_prototype ?(is_external = false) name style =
| 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 " = ";
@@ -3437,22 +4548,25 @@ DESTROY (g)
(match fst style with
| 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 _ ->
+ | RPVList _ | RVGList _ | RLVList _
+ | RStat _ | RStatVFS _
+ | RHashtable _ ->
pr "void\n" (* all lists returned implictly on the stack *)
);
(* Call and arguments. *)
pr "%s " name;
- generate_call_args ~handle:"g" style;
+ generate_call_args ~handle:"g" (snd style);
pr "\n";
pr " guestfs_h *g;\n";
List.iter (
function
- | String n -> pr " char *%s;\n" n
+ | String n | FileIn n | FileOut 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
@@ -3462,10 +4576,8 @@ DESTROY (g)
let do_cleanups () =
List.iter (
function
- | String _
- | OptString _
- | Bool _
- | Int _ -> ()
+ | String _ | OptString _ | Bool _ | Int _
+ | FileIn _ | FileOut _ -> ()
| StringList n -> pr " free (%s);\n" n
) (snd style)
in
@@ -3477,7 +4589,7 @@ DESTROY (g)
pr " int r;\n";
pr " PPCODE:\n";
pr " r = guestfs_%s " name;
- generate_call_args ~handle:"g" style;
+ generate_call_args ~handle:"g" (snd style);
pr ";\n";
do_cleanups ();
pr " if (r == -1)\n";
@@ -3488,7 +4600,7 @@ DESTROY (g)
pr " int %s;\n" n;
pr " CODE:\n";
pr " %s = guestfs_%s " n name;
- generate_call_args ~handle:"g" style;
+ generate_call_args ~handle:"g" (snd style);
pr ";\n";
do_cleanups ();
pr " if (%s == -1)\n" n;
@@ -3496,12 +4608,25 @@ DESTROY (g)
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" style;
+ generate_call_args ~handle:"g" (snd style);
pr ";\n";
do_cleanups ();
pr " if (%s == NULL)\n" n;
@@ -3514,7 +4639,7 @@ DESTROY (g)
pr " char *%s;\n" n;
pr " CODE:\n";
pr " %s = guestfs_%s " n name;
- generate_call_args ~handle:"g" style;
+ generate_call_args ~handle:"g" (snd style);
pr ";\n";
do_cleanups ();
pr " if (%s == NULL)\n" n;
@@ -3523,13 +4648,13 @@ DESTROY (g)
pr " free (%s);\n" n;
pr " OUTPUT:\n";
pr " RETVAL\n"
- | RStringList 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" style;
+ generate_call_args ~handle:"g" (snd style);
pr ";\n";
do_cleanups ();
pr " if (%s == NULL)\n" n;
@@ -3546,7 +4671,7 @@ DESTROY (g)
pr " struct guestfs_int_bool *r;\n";
pr " PPCODE:\n";
pr " r = guestfs_%s " name;
- generate_call_args ~handle:"g" style;
+ generate_call_args ~handle:"g" (snd style);
pr ";\n";
do_cleanups ();
pr " if (r == NULL)\n";
@@ -3556,11 +4681,16 @@ DESTROY (g)
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;
+ 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;
+ 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;
+ 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"
@@ -3573,7 +4703,7 @@ and generate_perl_lvm_code typ cols name style n do_cleanups =
pr " HV *hv;\n";
pr " PPCODE:\n";
pr " %s = guestfs_%s " n name;
- generate_call_args ~handle:"g" style;
+ generate_call_args ~handle:"g" (snd style);
pr ";\n";
do_cleanups ();
pr " if (%s == NULL)\n" n;
@@ -3603,6 +4733,24 @@ and generate_perl_lvm_code typ cols name style n do_cleanups =
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 (
+ function
+ | name, `Int ->
+ pr " PUSHs (sv_2mortal (my_newSVll (%s->%s)));\n" n name
+ ) cols;
+ pr " free (%s);\n" n
+
(* Generate Sys/Guestfs.pm. *)
and generate_perl_pm () =
generate_header HashStyle LGPLv2;
@@ -3727,6 +4875,7 @@ and generate_perl_prototype name style =
| RErr -> ()
| RBool n
| RInt n
+ | RInt64 n
| RConstString n
| RString n -> pr "$%s = " n
| RIntBool (n, m) -> pr "($%s, $%s) = " n m
@@ -3734,6 +4883,9 @@ and generate_perl_prototype name style =
| 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
@@ -3742,7 +4894,7 @@ and generate_perl_prototype name style =
if !comma then pr ", ";
comma := true;
match arg with
- | String n | OptString n | Bool n | Int n ->
+ | String n | OptString n | Bool n | Int n | FileIn n | FileOut n ->
pr "$%s" n
| StringList n ->
pr "\\@%s" n
@@ -3827,6 +4979,26 @@ put_string_list (char * const * const argv)
return list;
}
+static PyObject *
+put_table (char * const * const argv)
+{
+ PyObject *list, *item;
+ int argc, i;
+
+ for (argc = 0; argv[argc] != NULL; ++argc)
+ ;
+
+ 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);
+ }
+
+ return list;
+}
+
static void
free_strings (char **argv)
{
@@ -3925,6 +5097,27 @@ py_guestfs_close (PyObject *self, PyObject *args)
pr "\n"
) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
+ (* 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];
+
(* Python wrapper functions. *)
List.iter (
fun (name, style, _, _, _, _, _) ->
@@ -3939,17 +5132,20 @@ py_guestfs_close (PyObject *self, PyObject *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 _ -> 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" in
+ | 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
List.iter (
function
- | String n -> pr " const char *%s;\n" n
+ | 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;
@@ -3964,7 +5160,7 @@ py_guestfs_close (PyObject *self, PyObject *args)
pr " if (!PyArg_ParseTuple (args, (char *) \"O";
List.iter (
function
- | String _ -> pr "s"
+ | String _ | FileIn _ | FileOut _ -> pr "s"
| OptString _ -> pr "z"
| StringList _ -> pr "O"
| Bool _ -> pr "i" (* XXX Python has booleans? *)
@@ -3974,7 +5170,7 @@ py_guestfs_close (PyObject *self, PyObject *args)
pr " &py_g";
List.iter (
function
- | String n -> pr ", &%s" n
+ | 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
@@ -3987,7 +5183,7 @@ py_guestfs_close (PyObject *self, PyObject *args)
pr " g = get_handle (py_g);\n";
List.iter (
function
- | String _ | OptString _ | Bool _ | Int _ -> ()
+ | 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
@@ -3996,12 +5192,12 @@ py_guestfs_close (PyObject *self, PyObject *args)
pr "\n";
pr " r = guestfs_%s " name;
- generate_call_args ~handle:"g" style;
+ generate_call_args ~handle:"g" (snd style);
pr ";\n";
List.iter (
function
- | String _ | OptString _ | Bool _ | Int _ -> ()
+ | String _ | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ -> ()
| StringList n ->
pr " free (%s);\n" n
) (snd style);
@@ -4018,6 +5214,7 @@ py_guestfs_close (PyObject *self, PyObject *args)
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";
@@ -4039,6 +5236,15 @@ py_guestfs_close (PyObject *self, PyObject *args)
| 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"
);
pr " return py_r;\n";
@@ -4076,27 +5282,889 @@ initlibguestfsmod (void)
and generate_python_py () =
generate_header HashStyle LGPLv2;
- 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";
+ pr "\
+u\"\"\"Python bindings for libguestfs
+
+import guestfs
+g = guestfs.GuestFS ()
+g.add_drive (\"guest.img\")
+g.launch ()
+g.wait_ready ()
+parts = g.list_partitions ()
+
+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:
+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.
+
+Errors which happen while using the API are turned into Python
+RuntimeError exceptions.
+
+To create a guestfs handle you usually have to perform the following
+sequence of calls:
+
+# 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\")
+
+# Launch the qemu subprocess and wait for it to become ready:
+g.launch ()
+g.wait_ready ()
+
+# Now you can issue commands, for example:
+logvols = g.lvs ()
+
+\"\"\"
+
+import libguestfsmod
+
+class GuestFS:
+ \"\"\"Instances of this class are libguestfs API handles.\"\"\"
+
+ def __init__ (self):
+ \"\"\"Create a new libguestfs handle.\"\"\"
+ self._o = libguestfsmod.create ()
+
+ def __del__ (self):
+ libguestfsmod.close (self._o)
+
+";
List.iter (
- fun (name, style, _, _, _, _, _) ->
+ fun (name, style, _, flags, _, _, longdesc) ->
+ let doc = replace_str longdesc "C 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 " def %s " name;
- generate_call_args ~handle:"self" style;
+ generate_call_args ~handle:"self" (snd style);
pr ":\n";
+ pr " u\"\"\"%s\"\"\"\n" doc;
pr " return libguestfsmod.%s " name;
- generate_call_args ~handle:"self._o" style;
+ 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
+#include
+
+#include
+
+#include \"guestfs.h\"
+
+#include \"extconf.h\"
+
+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;
+}
+
+";
+
+ List.iter (
+ 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 = StringValueCStr (%sv);\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 = malloc (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
+ | 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;
+
+ pr "\
+/* 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);
+
+ rb_define_module_function (m_guestfs, \"create\", ruby_guestfs_create, 0);
+ rb_define_method (c_guestfs, \"close\", ruby_guestfs_close, 0);
+
+";
+ (* 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;
+
+ pr "}\n"
+
+(* 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"
+
+(* Generate Java bindings GuestFS.java file. *)
+and generate_java_java () =
+ generate_header CStyle LGPLv2;
+
+ 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\");
+ }
+
+ /**
+ * 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) ->
+ let doc = replace_str longdesc "C 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;
+
+ pr "}\n"
+
+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 ";
+
+ (* return type *)
+ (match fst style with
+ | 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 ";
+ );
+
+ 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 !needs_comma then pr ", ";
+ needs_comma := true;
+
+ match arg with
+ | String n
+ | OptString n
+ | FileIn n
+ | FileOut n ->
+ pr "String %s" n
+ | StringList n ->
+ pr "String[] %s" n
+ | Bool n ->
+ pr "boolean %s" n
+ | Int n ->
+ pr "int %s" n
+ ) (snd style);
+
+ pr ")\n";
+ pr " throws LibGuestFSException";
+ if semicolon then pr ";"
+
+and generate_java_struct typ cols =
+ generate_header CStyle LGPLv2;
+
+ pr "\
+package com.redhat.et.libguestfs;
+
+/**
+ * Libguestfs %s structure.
+ *
+ * @author rjones
+ * @see GuestFS
+ */
+public class %s {
+" typ typ;
+
+ 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;
+
+ pr "}\n"
+
+and generate_java_c () =
+ generate_header CStyle LGPLv2;
+
+ pr "\
+#include
+#include
+#include
+
+#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
+throw_exception (JNIEnv *env, const char *msg)
+{
+ jclass cl;
+ cl = (*env)->FindClass (env,
+ \"com/redhat/et/libguestfs/LibGuestFSException\");
+ (*env)->ThrowNew (env, cl, msg);
+}
+
+JNIEXPORT jlong JNICALL
+Java_com_redhat_et_libguestfs_GuestFS__1create
+ (JNIEnv *env, jobject obj)
+{
+ guestfs_h *g;
+
+ g = guestfs_create ();
+ if (g == NULL) {
+ throw_exception (env, \"GuestFS.create: failed to allocate handle\");
+ return 0;
+ }
+ guestfs_set_error_handler (g, NULL, NULL);
+ return (jlong) (long) g;
+}
+
+JNIEXPORT void JNICALL
+Java_com_redhat_et_libguestfs_GuestFS__1close
+ (JNIEnv *env, jobject obj, jlong jg)
+{
+ guestfs_h *g = (guestfs_h *) (long) jg;
+ guestfs_close (g);
+}
+
+";
+
+ List.iter (
+ 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 " (JNIEnv *env, jobject obj, jlong jg";
+ List.iter (
+ function
+ | 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 " guestfs_h *g = (guestfs_h *) (long) jg;\n";
+ let error_code, no_ret =
+ match fst style with
+ | 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
+ | OptString n
+ | FileIn n
+ | FileOut n ->
+ pr " const char *%s;\n" n
+ | StringList n ->
+ pr " int %s_len;\n" n;
+ pr " const char **%s;\n" n
+ | Bool n
+ | Int n ->
+ pr " int %s;\n" 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";
+
+ (* Get the parameters. *)
+ List.iter (
+ function
+ | String n
+ | OptString n
+ | FileIn n
+ | FileOut n ->
+ pr " %s = (*env)->GetStringUTFChars (env, j%s, NULL);\n" n n
+ | StringList n ->
+ pr " %s_len = (*env)->GetArrayLength (env, j%s);\n" n n;
+ pr " %s = malloc (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);
+
+ (* Make the call. *)
+ pr " r = guestfs_%s " name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+
+ (* Release the parameters. *)
+ List.iter (
+ function
+ | String n
+ | OptString n
+ | FileIn n
+ | FileOut 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 " throw_exception (env, guestfs_last_error (g));\n";
+ pr " return %s;\n" no_ret;
+ pr " }\n";
+
+ (* Return value. *)
+ (match fst style with
+ | 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 " jr = (*env)->NewStringUTF (env, r);\n";
+ pr " free (r);\n";
+ pr " return jr;\n"
+ | RStringList _ ->
+ 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 " 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 "}\n";
+ pr "\n"
) all_functions
+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 (
+ 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"
+
let output_to filename =
let filename_new = filename ^ ".new" in
chan := open_out filename_new;
@@ -4196,3 +6264,35 @@ Run it from the top source directory using the command
let close = output_to "python/guestfs.py" in
generate_python_py ();
close ();
+
+ let close = output_to "ruby/ext/guestfs/_guestfs.c" in
+ generate_ruby_c ();
+ 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 ();