*)
#load "unix.cma";;
+#load "str.cma";;
open Printf
* the virtual machine and block devices are reused between tests.
* So don't try testing kill_subprocess :-x
*
- * Between each test we umount-all and lvm-remove-all (except InitNone).
+ * Between each test we blockdev-setrw, umount-all, lvm-remove-all
+ * (except InitNone).
+ *
+ * If the appliance is running an older Linux kernel (eg. RHEL 5) then
+ * devices are named /dev/hda etc. To cope with this, the test suite
+ * adds some hairly logic to detect this case, and then automagically
+ * replaces all strings which match "/dev/sd.*" with "/dev/hd.*".
+ * When writing test cases you shouldn't have to worry about this
+ * difference.
*
* Don't assume anything about the previous contents of the block
* devices. Use 'Init*' to create some initial scenarios.
C<value> can be NULL.");
+ ("set_qemu", (RErr, [String "qemu"]), -1, [FishAlias "qemu"],
+ [],
+ "set the qemu binary",
+ "\
+Set the qemu binary that we will use.
+
+The default is chosen when the library was compiled by the
+configure script.
+
+You can also override this by setting the C<LIBGUESTFS_QEMU>
+environment variable.
+
+The string C<qemu> is stashed in the libguestfs handle, so the caller
+must make sure it remains valid for the lifetime of the handle.
+
+Setting C<qemu> to C<NULL> restores the default qemu binary.");
+
+ ("get_qemu", (RConstString "qemu", []), -1, [],
+ [],
+ "get the qemu binary",
+ "\
+Return the current qemu binary.
+
+This is always non-NULL. If it wasn't set already, then this will
+return the default qemu binary name.");
+
("set_path", (RErr, [String "path"]), -1, [FishAlias "path"],
[],
"set the search path",
"set autosync mode",
"\
If C<autosync> is true, this enables autosync. Libguestfs will make a
-best effort attempt to run C<guestfs_sync> when the handle is closed
-(also if the program exits without closing handles).");
+best effort attempt to run C<guestfs_umount_all> followed by
+C<guestfs_sync> when the handle is closed
+(also if the program exits without closing handles).
+
+This is disabled by default (except in guestfish where it is
+enabled by default).");
("get_autosync", (RBool "autosync", []), -1, [],
[],
"make a filesystem",
"\
This creates a filesystem on C<device> (usually a partition
-of LVM logical volume). The filesystem type is C<fstype>, for
+or LVM logical volume). The filesystem type is C<fstype>, for
example C<ext3>.");
("sfdisk", (RErr, [String "device";
As a special case, if C<size> is C<0>
then the length is calculated using C<strlen> (so in this case
-the content cannot contain embedded ASCII NULs).");
+the content cannot contain embedded ASCII NULs).
+
+I<NB.> Owing to a bug, writing content containing ASCII NUL
+characters does I<not> work, even if the length is specified.
+We hope to resolve this bug in a future version. In the meantime
+use C<guestfs_upload>.");
("umount", (RErr, [String "pathordevice"]), 45, [FishAlias "unmount"],
[InitEmpty, TestOutputList (
("umount_all", (RErr, []), 47, [FishAlias "unmount-all"],
[InitBasicFS, TestOutputList (
[["umount_all"];
+ ["mounts"]], []);
+ (* check that umount_all can unmount nested mounts correctly: *)
+ InitEmpty, TestOutputList (
+ [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"];
+ ["mkfs"; "ext2"; "/dev/sda1"];
+ ["mkfs"; "ext2"; "/dev/sda2"];
+ ["mkfs"; "ext2"; "/dev/sda3"];
+ ["mount"; "/dev/sda1"; "/"];
+ ["mkdir"; "/mp1"];
+ ["mount"; "/dev/sda2"; "/mp1"];
+ ["mkdir"; "/mp1/mp2"];
+ ["mount"; "/dev/sda3"; "/mp1/mp2"];
+ ["mkdir"; "/mp1/mp2/mp3"];
+ ["umount_all"];
["mounts"]], [])],
"unmount all filesystems",
"\
("tune2fs_l", (RHashtable "superblock", [String "device"]), 55, [],
[], (* XXX test *)
- "get ext2/ext3 superblock details",
+ "get ext2/ext3/ext4 superblock details",
"\
-This returns the contents of the ext2 or ext3 filesystem superblock
-on C<device>.
+This returns the contents of the ext2, ext3 or ext4 filesystem
+superblock on C<device>.
It is the same as running C<tune2fs -l device>. See L<tune2fs(8)>
manpage for more details. The list of fields returned isn't
This uses the L<blockdev(8)> 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<filename> to C<remotefilename> on the
See also C<guestfs_download>.");
("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<remotefilename> and save it as C<filename>
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<tarfile> (an
+I<uncompressed> tar file) into C<directory>.
+
+To upload a compressed tarball, use C<guestfs_tgz_in>.");
+
+ ("tar_out", (RErr, [String "directory"; FileOut "tarfile"]), 70, [],
+ [],
+ "pack directory into tarfile",
+ "\
+This command packs the contents of C<directory> and downloads
+it to local file C<tarfile>.
+
+To download a compressed tarball, use C<guestfs_tgz_out>.");
+
+ ("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<tarball> (a
+I<gzip compressed> tar file) into C<directory>.
+
+To upload an uncompressed tarball, use C<guestfs_tar_in>.");
+
+ ("tgz_out", (RErr, [String "directory"; FileOut "tarball"]), 72, [],
+ [],
+ "pack directory into compressed tarball",
+ "\
+This command packs the contents of C<directory> and downloads
+it to local file C<tarball>.
+
+To download an uncompressed tarball, use C<guestfs_tar_out>.");
+
+ ("mount_ro", (RErr, [String "device"; String "mountpoint"]), 73, [],
+ [InitBasicFS, 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<guestfs_mount> 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<guestfs_mount> command, but it
+allows you to set the mount options as for the
+L<mount(8)> 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<guestfs_mount> command, but it
+allows you to set both the mount options and the vfstype
+as for the L<mount(8)> I<-o> and I<-t> flags.");
+
+ ("debug", (RString "result", [String "subcmd"; StringList "extraargs"]), 76, [],
+ [],
+ "debugging and internals",
+ "\
+The C<guestfs_debug> command exposes some internals of
+C<guestfsd> (the guestfs daemon) that runs inside the
+qemu subprocess.
+
+There is no comprehensive help for this command. You have
+to look at the file C<daemon/debug.c> in the libguestfs source
+to find out what you can do.");
+
+ ("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<device>, where C<device> is
+the path to the LV, such as C</dev/VG/LV>.
+
+You can also remove all LVs in a volume group by specifying
+the VG name, C</dev/VG>.");
+
+ ("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<vgname>, (for example C<VG>).
+
+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<device> so that LVM will no longer
+recognise it.
+
+The implementation uses the C<pvremove> command which refuses to
+wipe physical volumes that contain any volume groups, so you have
+to remove those first.");
+
+ ("set_e2label", (RErr, [String "device"; String "label"]), 80, [],
+ [InitBasicFS, TestOutput (
+ [["set_e2label"; "/dev/sda1"; "testlabel"];
+ ["get_e2label"; "/dev/sda1"]], "testlabel")],
+ "set the ext2/3/4 filesystem label",
+ "\
+This sets the ext2/3/4 filesystem label of the filesystem on
+C<device> to C<label>. Filesystem labels are limited to
+16 characters.
+
+You can use either C<guestfs_tune2fs_l> or C<guestfs_get_e2label>
+to return the existing label on a filesystem.");
+
+ ("get_e2label", (RString "label", [String "device"]), 81, [],
+ [],
+ "get the ext2/3/4 filesystem label",
+ "\
+This returns the ext2/3/4 filesystem label of the filesystem on
+C<device>.");
+
+ ("set_e2uuid", (RErr, [String "device"; String "uuid"]), 82, [],
+ [InitBasicFS, TestOutput (
+ [["set_e2uuid"; "/dev/sda1"; "a3a61220-882b-4f61-89f4-cf24dcc7297d"];
+ ["get_e2uuid"; "/dev/sda1"]], "a3a61220-882b-4f61-89f4-cf24dcc7297d");
+ InitBasicFS, TestOutput (
+ [["set_e2uuid"; "/dev/sda1"; "clear"];
+ ["get_e2uuid"; "/dev/sda1"]], "");
+ (* We can't predict what UUIDs will be, so just check the commands run. *)
+ InitBasicFS, TestRun (
+ [["set_e2uuid"; "/dev/sda1"; "random"]]);
+ InitBasicFS, TestRun (
+ [["set_e2uuid"; "/dev/sda1"; "time"]])],
+ "set the ext2/3/4 filesystem UUID",
+ "\
+This sets the ext2/3/4 filesystem UUID of the filesystem on
+C<device> to C<uuid>. The format of the UUID and alternatives
+such as C<clear>, C<random> and C<time> are described in the
+L<tune2fs(8)> manpage.
+
+You can use either C<guestfs_tune2fs_l> or C<guestfs_get_e2uuid>
+to return the existing UUID of a filesystem.");
+
+ ("get_e2uuid", (RString "uuid", [String "device"]), 83, [],
+ [],
+ "get the ext2/3/4 filesystem UUID",
+ "\
+This returns the ext2/3/4 filesystem UUID of the filesystem on
+C<device>.");
+
+ ("fsck", (RInt "status", [String "fstype"; String "device"]), 84, [],
+ [InitBasicFS, TestOutputInt (
+ [["umount"; "/dev/sda1"];
+ ["fsck"; "ext2"; "/dev/sda1"]], 0);
+ InitBasicFS, TestOutputInt (
+ [["umount"; "/dev/sda1"];
+ ["zero"; "/dev/sda1"];
+ ["fsck"; "ext2"; "/dev/sda1"]], 8)],
+ "run the filesystem checker",
+ "\
+This runs the filesystem checker (fsck) on C<device> which
+should have filesystem type C<fstype>.
+
+The returned integer is the status. See L<fsck(8)> for the
+list of status codes from C<fsck>.
+
+Notes:
+
+=over 4
+
+=item *
+
+Multiple status codes can be summed together.
+
+=item *
+
+A non-zero return code can mean \"success\", for example if
+errors have been corrected on the filesystem.
+
+=item *
+
+Checking or repairing NTFS volumes is not supported
+(by linux-ntfs).
+
+=back
+
+This command is entirely equivalent to running C<fsck -a -t fstype device>.");
+
+ ("zero", (RErr, [String "device"]), 85, [],
+ [InitBasicFS, TestOutput (
+ [["umount"; "/dev/sda1"];
+ ["zero"; "/dev/sda1"];
+ ["file"; "/dev/sda1"]], "data")],
+ "write zeroes to the device",
+ "\
+This command writes zeroes over the first few blocks of C<device>.
+
+How many blocks are zeroed isn't specified (but it's I<not> enough
+to securely wipe the device). It should be sufficient to remove
+any partition tables, filesystem superblocks and so on.");
+
+ ("grub_install", (RErr, [String "root"; String "device"]), 86, [],
+ [InitBasicFS, TestOutputTrue (
+ [["grub_install"; "/"; "/dev/sda1"];
+ ["is_dir"; "/boot"]])],
+ "install GRUB",
+ "\
+This command installs GRUB (the Grand Unified Bootloader) on
+C<device>, with the root directory being C<root>.");
+
+ ("cp", (RErr, [String "src"; String "dest"]), 87, [],
+ [InitBasicFS, TestOutput (
+ [["write_file"; "/old"; "file content"; "0"];
+ ["cp"; "/old"; "/new"];
+ ["cat"; "/new"]], "file content");
+ InitBasicFS, TestOutputTrue (
+ [["write_file"; "/old"; "file content"; "0"];
+ ["cp"; "/old"; "/new"];
+ ["is_file"; "/old"]]);
+ InitBasicFS, TestOutput (
+ [["write_file"; "/old"; "file content"; "0"];
+ ["mkdir"; "/dir"];
+ ["cp"; "/old"; "/dir/new"];
+ ["cat"; "/dir/new"]], "file content")],
+ "copy a file",
+ "\
+This copies a file from C<src> to C<dest> where C<dest> is
+either a destination filename or destination directory.");
+
+ ("cp_a", (RErr, [String "src"; String "dest"]), 88, [],
+ [InitBasicFS, TestOutput (
+ [["mkdir"; "/olddir"];
+ ["mkdir"; "/newdir"];
+ ["write_file"; "/olddir/file"; "file content"; "0"];
+ ["cp_a"; "/olddir"; "/newdir"];
+ ["cat"; "/newdir/olddir/file"]], "file content")],
+ "copy a file or directory recursively",
+ "\
+This copies a file or directory from C<src> to C<dest>
+recursively using the C<cp -a> command.");
+
+ ("mv", (RErr, [String "src"; String "dest"]), 89, [],
+ [InitBasicFS, TestOutput (
+ [["write_file"; "/old"; "file content"; "0"];
+ ["mv"; "/old"; "/new"];
+ ["cat"; "/new"]], "file content");
+ InitBasicFS, TestOutputFalse (
+ [["write_file"; "/old"; "file content"; "0"];
+ ["mv"; "/old"; "/new"];
+ ["is_file"; "/old"]])],
+ "move a file",
+ "\
+This moves a file from C<src> to C<dest> where C<dest> is
+either a destination filename or destination directory.");
+
+ ("drop_caches", (RErr, [Int "whattodrop"]), 90, [],
+ [InitEmpty, TestRun (
+ [["drop_caches"; "3"]])],
+ "drop kernel page cache, dentries and inodes",
+ "\
+This instructs the guest kernel to drop its page cache,
+and/or dentries and inode caches. The parameter C<whattodrop>
+tells the kernel what precisely to drop, see
+L<http://linux-mm.org/Drop_Caches>
+
+Setting C<whattodrop> to 3 should drop everything.
+
+This automatically calls L<sync(2)> before the operation,
+so that the maximum guest memory is freed.");
+
+ ("dmesg", (RString "kmsgs", []), 91, [],
+ [InitEmpty, TestRun (
+ [["dmesg"]])],
+ "return kernel messages",
+ "\
+This returns the kernel messages (C<dmesg> output) from
+the guest kernel. This is sometimes useful for extended
+debugging of problems.
+
+Another way to get the same information is to enable
+verbose messages with C<guestfs_set_verbose> or by setting
+the environment variable C<LIBGUESTFS_DEBUG=1> before
+running the program.");
+
+ ("ping_daemon", (RErr, []), 92, [],
+ [InitEmpty, TestRun (
+ [["ping_daemon"]])],
+ "ping the guest daemon",
+ "\
+This is a test probe into the guestfs daemon running inside
+the qemu subprocess. Calling this function checks that the
+daemon responds to the ping message, without affecting the daemon
+or attached block device(s) in any other way.");
+
+ ("equal", (RBool "equality", [String "file1"; String "file2"]), 93, [],
+ [InitBasicFS, TestOutputTrue (
+ [["write_file"; "/file1"; "contents of a file"; "0"];
+ ["cp"; "/file1"; "/file2"];
+ ["equal"; "/file1"; "/file2"]]);
+ InitBasicFS, TestOutputFalse (
+ [["write_file"; "/file1"; "contents of a file"; "0"];
+ ["write_file"; "/file2"; "contents of another file"; "0"];
+ ["equal"; "/file1"; "/file2"]]);
+ InitBasicFS, TestLastFail (
+ [["equal"; "/file1"; "/file2"]])],
+ "test if two files have equal contents",
+ "\
+This compares the two files C<file1> and C<file2> and returns
+true if their content is exactly equal, or false otherwise.
+
+The external L<cmp(1)> program is used for the comparison.");
+
]
let all_functions = non_daemon_functions @ daemon_functions
s' :: string_split sep s''
)
+let files_equal n1 n2 =
+ let cmd = sprintf "cmp -s %s %s" (Filename.quote n1) (Filename.quote n2) in
+ match Sys.command cmd with
+ | 0 -> true
+ | 1 -> false
+ | i -> failwithf "%s: failed with error code %d" cmd i
+
let rec find_map f = function
| [] -> raise Not_found
| x :: xs ->
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 = send called,\n";
- pr " * 1001 = reply called.\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 " guestfs_main_loop *ml = guestfs_get_main_loop (g);\n";
pr " struct %s_ctx *ctx = (struct %s_ctx *) data;\n" shortname shortname;
pr "\n";
+ pr " /* This should definitely not happen. */\n";
+ pr " if (ctx->cb_sequence != 0) {\n";
+ pr " ctx->cb_sequence = 9999;\n";
+ pr " error (g, \"%%s: internal error: reply callback called twice\", \"%s\");\n" name;
+ pr " return;\n";
+ pr " }\n";
+ pr "\n";
pr " ml->main_loop_quit (ml, g);\n";
pr "\n";
pr " if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {\n";
);
pr " done:\n";
- pr " ctx->cb_sequence = 1001;\n";
+ pr " ctx->cb_sequence = 1;\n";
pr "}\n\n";
(* Generate the action stub. *)
pr "\n";
(* Send any additional files (FileIn) requested. *)
+ let need_read_reply_label = ref false in
List.iter (
function
| FileIn n ->
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. *)
- pr " read_reply:\n";
+ 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 != 1001) {\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;
and generate_daemon_actions () =
generate_header CStyle GPLv2;
- pr "#define _GNU_SOURCE // for strchrnul\n";
+ pr "#include <config.h>\n";
pr "\n";
pr "#include <stdio.h>\n";
pr "#include <stdlib.h>\n";
| String n -> pr " %s = args.%s;\n" n n
| OptString n -> pr " %s = args.%s ? *args.%s : NULL;\n" n n n
| StringList n ->
- pr " args.%s.%s_val = realloc (args.%s.%s_val, sizeof (char *) * (args.%s.%s_len+1));\n" n n n n n n;
- pr " args.%s.%s_val[args.%s.%s_len] = NULL;\n" n n n n;
- pr " %s = args.%s.%s_val;\n" n n n
+ pr " %s = realloc (args.%s.%s_val,\n" n n n;
+ pr " sizeof (char *) * (args.%s.%s_len+1));\n" n n;
+ pr " if (%s == NULL) {\n" n;
+ pr " reply_with_perror (\"realloc\");\n";
+ pr " goto done;\n";
+ pr " }\n";
+ pr " %s[args.%s.%s_len] = NULL;\n" n n n;
+ pr " args.%s.%s_val = %s;\n" n n n;
| Bool n -> pr " %s = args.%s;\n" n n
| Int n -> pr " %s = args.%s;\n" n n
| FileIn _ | FileOut _ -> ()
static guestfs_h *g;
static int suppress_error = 0;
+/* This will be 's' or 'h' depending on whether the guest kernel
+ * names IDE devices /dev/sd* or /dev/hd*.
+ */
+static char devchar = 's';
+
static void print_error (guestfs_h *g, void *data, const char *msg)
{
if (!suppress_error)
char c = 0;
int failed = 0;
const char *srcdir;
- int fd;
- char buf[256];
+ const char *filename;
+ int fd, i;
int nr_tests, test_num = 0;
+ char **devs;
no_test_warnings ();
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);
}
exit (1);
}
+ /* Detect if the appliance uses /dev/sd* or /dev/hd* in device
+ * names. This changed between RHEL 5 and RHEL 6 so we have to
+ * support both.
+ */
+ devs = guestfs_list_devices (g);
+ if (devs == NULL || devs[0] == NULL) {
+ printf (\"guestfs_list_devices FAILED\\n\");
+ exit (1);
+ }
+ if (strncmp (devs[0], \"/dev/sd\", 7) == 0)
+ devchar = 's';
+ else if (strncmp (devs[0], \"/dev/hd\", 7) == 0)
+ devchar = 'h';
+ else {
+ printf (\"guestfs_list_devices returned unexpected string '%%s'\\n\",
+ devs[0]);
+ exit (1);
+ }
+ for (i = 0; devs[i] != NULL; ++i)
+ free (devs[i]);
+ free (devs);
+
nr_tests = %d;
" (500 * 1024 * 1024) (50 * 1024 * 1024) (10 * 1024 * 1024) nr_tests;
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";
| InitEmpty ->
pr " /* InitEmpty for %s (%d) */\n" name i;
List.iter (generate_test_command_call test_name)
- [["umount_all"];
+ [["blockdev_setrw"; "/dev/sda"];
+ ["umount_all"];
["lvm_remove_all"]]
| InitBasicFS ->
pr " /* InitBasicFS for %s (%d): create ext2 on /dev/sda1 */\n" name i;
List.iter (generate_test_command_call test_name)
- [["umount_all"];
+ [["blockdev_setrw"; "/dev/sda"];
+ ["umount_all"];
["lvm_remove_all"];
["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","];
["mkfs"; "ext2"; "/dev/sda1"];
pr " /* InitBasicFSonLVM for %s (%d): create ext2 on /dev/VG/LV */\n"
name i;
List.iter (generate_test_command_call test_name)
- [["umount_all"];
+ [["blockdev_setrw"; "/dev/sda"];
+ ["umount_all"];
["lvm_remove_all"];
["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","];
["pvcreate"; "/dev/sda1"];
List.iter (generate_test_command_call test_name) seq
| TestOutput (seq, expected) ->
pr " /* TestOutput for %s (%d) */\n" name i;
+ pr " char expected[] = \"%s\";\n" (c_quote expected);
+ if String.length expected > 7 &&
+ String.sub expected 0 7 = "/dev/sd" then
+ pr " expected[5] = devchar;\n";
let seq, last = get_seq_last seq in
let test () =
- pr " if (strcmp (r, \"%s\") != 0) {\n" (c_quote expected);
- pr " fprintf (stderr, \"%s: expected \\\"%s\\\" but got \\\"%%s\\\"\\n\", r);\n" test_name (c_quote expected);
+ pr " if (strcmp (r, expected) != 0) {\n";
+ pr " fprintf (stderr, \"%s: expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", expected, r);\n" test_name;
pr " return -1;\n";
pr " }\n"
in
pr " print_strings (r);\n";
pr " return -1;\n";
pr " }\n";
- pr " if (strcmp (r[%d], \"%s\") != 0) {\n" i (c_quote str);
- pr " fprintf (stderr, \"%s: expected \\\"%s\\\" but got \\\"%%s\\\"\\n\", r[%d]);\n" test_name (c_quote str) i;
- pr " return -1;\n";
+ pr " {\n";
+ pr " char expected[] = \"%s\";\n" (c_quote str);
+ if String.length str > 7 && String.sub str 0 7 = "/dev/sd" then
+ pr " expected[5] = devchar;\n";
+ pr " if (strcmp (r[%d], expected) != 0) {\n" i;
+ pr " fprintf (stderr, \"%s: expected \\\"%%s\\\" but got \\\"%%s\\\"\\n\", expected, r[%d]);\n" test_name i;
+ pr " return -1;\n";
+ pr " }\n";
pr " }\n"
) expected;
pr " if (r[%d] != NULL) {\n" (List.length expected);
List.iter (
function
- | String _, _
- | OptString _, _
+ | OptString n, "NULL" -> ()
+ | String n, arg
+ | OptString n, arg ->
+ pr " char %s[] = \"%s\";\n" n (c_quote arg);
+ if String.length arg > 7 && String.sub arg 0 7 = "/dev/sd" then
+ pr " %s[5] = devchar;\n" n
| Int _, _
- | Bool _, _ -> ()
+ | Bool _, _
| FileIn _, _ | FileOut _, _ -> ()
| StringList n, arg ->
- pr " char *%s[] = {\n" n;
let strs = string_split " " arg in
- List.iter (
- fun str -> pr " \"%s\",\n" (c_quote str)
+ iteri (
+ fun i str ->
+ pr " char %s_%d[] = \"%s\";\n" n i (c_quote str);
+ if String.length str > 7 && String.sub str 0 7 = "/dev/sd" then
+ pr " %s_%d[5] = devchar;\n" n i
+ ) strs;
+ pr " char *%s[] = {\n" n;
+ iteri (
+ fun i _ -> pr " %s_%d,\n" n i
) strs;
pr " NULL\n";
pr " };\n";
(* Generate the parameters. *)
List.iter (
function
- | String _, arg
+ | OptString _, "NULL" -> pr ", NULL"
+ | String n, _
+ | OptString n, _ ->
+ pr ", %s" n
| FileIn _, arg | FileOut _, arg ->
pr ", \"%s\"" (c_quote arg)
- | OptString _, arg ->
- if arg = "NULL" then pr ", NULL" else pr ", \"%s\"" (c_quote arg)
| StringList n, _ ->
pr ", %s" n
| Int _, arg ->
let str = replace_str str "\r" "\\r" in
let str = replace_str str "\n" "\\n" in
let str = replace_str str "\t" "\\t" in
+ let str = replace_str str "\000" "\\0" in
str
(* Generate a lot of different functions for guestfish. *)
#ifdef HAVE_LIBREADLINE
-static const char *commands[] = {
+static const char *const commands[] = {
";
(* Get the commands and sort them, including the aliases. *)
fun (_, _, _, flags, _, _, _) -> not (List.mem NotInFish flags)
) all_functions_sorted in
+ let rex = Str.regexp "C<guestfs_\\([^>]+\\)>" in
+
List.iter (
fun (name, style, _, flags, _, _, longdesc) ->
- let longdesc = replace_str longdesc "C<guestfs_" "C<" in
+ let longdesc =
+ Str.global_substitute rex (
+ fun s ->
+ let sub =
+ try Str.matched_group 1 s
+ with Not_found ->
+ failwithf "error substituting C<guestfs_...> in longdesc of function %s" name in
+ "C<" ^ replace_char sub '_' '-' ^ ">"
+ ) longdesc in
let name = replace_char name '_' '-' in
let alias =
try find_map (function FishAlias n -> Some n | _ -> None) flags
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
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"
pr " %sv != Val_int (0) ? String_val (Field (%sv, 0)) : NULL;\n"
n n
| StringList n ->
- pr " char **%s = ocaml_guestfs_strings_val (%sv);\n" n n
+ pr " char **%s = ocaml_guestfs_strings_val (g, %sv);\n" n n
| Bool n ->
pr " int %s = Bool_val (%sv);\n" n n
| Int n ->
AV *av;
I32 i;
- if (!arg || !SvOK (arg) || !SvROK (arg) || SvTYPE (SvRV (arg)) != SVt_PVAV) {
+ if (!arg || !SvOK (arg) || !SvROK (arg) || SvTYPE (SvRV (arg)) != SVt_PVAV)
croak (\"array reference expected\");
- }
av = (AV *)SvRV (arg);
- ret = (char **)malloc (av_len (av) + 1 + 1);
+ ret = malloc (av_len (av) + 1 + 1);
+ if (!ret)
+ croak (\"malloc failed\");
for (i = 0; i <= av_len (av); i++) {
SV **elem = av_fetch (av, i, 0);
MODULE = Sys::Guestfs PACKAGE = Sys::Guestfs
+PROTOTYPES: ENABLE
+
guestfs_h *
_create ()
CODE:
(* 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
#include \"extconf.h\"
+/* For Ruby < 1.9 */
+#ifndef RARRAY_LEN
+#define RARRAY_LEN(r) (RARRAY((r))->len)
+#endif
+
static VALUE m_guestfs; /* guestfs module */
static VALUE c_guestfs; /* guestfs_h handle */
static VALUE e_Error; /* used for all errors */
pr " {\n";
pr " int i, len;\n";
pr " len = RARRAY_LEN (%sv);\n" n;
- pr " %s = malloc (sizeof (char *) * (len+1));\n" n;
+ pr " %s = guestfs_safe_malloc (g, sizeof (char *) * (len+1));\n"
+ n;
pr " for (i = 0; i < len; ++i) {\n";
pr " VALUE v = rb_ary_entry (%sv, i);\n" n;
pr " %s[i] = StringValueCStr (v);\n" n;
pr " }\n";
+ pr " %s[len] = NULL;\n" n;
pr " }\n";
| Bool n
| Int 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<guestfs_" "C<g." in
+ let doc =
+ if List.mem ProtocolLimitWarning flags then
+ doc ^ "\n\n" ^ protocol_limit_warning
+ else doc in
+ let doc =
+ if List.mem DangerWillRobinson flags then
+ doc ^ "\n\n" ^ danger_will_robinson
+ else doc in
+ let doc = pod2text ~width:60 name doc in
+ let doc = String.concat "\n * " doc in
+
+ pr " /**\n";
+ pr " * %s\n" shortdesc;
+ pr " *\n";
+ pr " * %s\n" doc;
+ pr " * @throws LibGuestFSException\n";
+ pr " */\n";
+ pr " ";
+ generate_java_prototype ~public:true ~semicolon:false name style;
+ pr "\n";
+ pr " {\n";
+ pr " if (g == 0)\n";
+ pr " throw new LibGuestFSException (\"%s: handle is closed\");\n"
+ name;
+ pr " ";
+ if fst style <> RErr then pr "return ";
+ pr "_%s " name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+ pr " }\n";
+ pr " ";
+ generate_java_prototype ~privat:true ~native:true name style;
+ pr "\n";
+ pr "\n";
+ ) all_functions;
+
+ 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<String,String> ";
+ );
+
+ 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 = guestfs_safe_malloc (g, sizeof (char *) * (%s_len+1));\n" n n;
+ pr " for (i = 0; i < %s_len; ++i) {\n" n;
+ pr " jobject o = (*env)->GetObjectArrayElement (env, j%s, i);\n"
+ n;
+ pr " %s[i] = (*env)->GetStringUTFChars (env, o, NULL);\n" n;
+ pr " }\n";
+ pr " %s[%s_len] = NULL;\n" n n;
+ | Bool n
+ | Int n ->
+ pr " %s = j%s;\n" n n
+ ) (snd style);
+
+ (* 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;
let close () =
close_out !chan;
chan := stdout;
- Unix.rename filename_new filename;
- printf "written %s\n%!" filename;
+
+ (* Is the new file different from the current file? *)
+ if Sys.file_exists filename && files_equal filename filename_new then
+ Unix.unlink filename_new (* same, so skip it *)
+ else (
+ (* different, overwrite old one *)
+ (try Unix.chmod filename 0o644 with Unix.Unix_error _ -> ());
+ Unix.rename filename_new filename;
+ Unix.chmod filename 0o444;
+ printf "written %s\n%!" filename;
+ )
in
close
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 ();