X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=src%2Fgenerator.ml;h=a58441a96ae9ad8389949772ad8fff1442089b99;hp=4653d65cfdc219bf3434bc1fbad4d20ad99ce9fd;hb=58caa9e5f1dca3916178894876b938a6a45771b0;hpb=05712b2457a44ee0f0020eced77db03c2aa419a1 diff --git a/src/generator.ml b/src/generator.ml index 4653d65..a58441a 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -33,6 +33,7 @@ *) #load "unix.cma";; +#load "str.cma";; open Printf @@ -98,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 *) @@ -274,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", @@ -324,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 = [ @@ -384,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, [], @@ -1077,10 +1178,10 @@ This is the same as the C system call."); ("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. +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 @@ -1198,6 +1299,272 @@ 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 @@ -1322,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 @@ -1385,7 +1777,8 @@ 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, _) @@ -1740,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" ); @@ -1805,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"; @@ -1820,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 */ @@ -1836,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 { @@ -1847,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. *) @@ -1923,14 +2333,88 @@ and generate_actions_h () = and generate_client_actions () = generate_header CStyle LGPLv2; + 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 = send called,\n"; + pr " * 1001 = reply 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 @@ -1945,20 +2429,25 @@ and generate_client_actions () = | 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 " 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"; @@ -1974,15 +2463,14 @@ and generate_client_actions () = | RPVList _ | RVGList _ | RLVList _ | RStat _ | RStatVFS _ | RHashtable _ -> - pr " if (!xdr_%s_ret (xdr, &rv->ret)) {\n" name; - pr " error (g, \"%s: failed to parse reply\");\n" name; + 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 = 1001;\n"; pr "}\n\n"; (* Generate the action stub. *) @@ -2007,22 +2495,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 ( @@ -2038,62 +2524,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 != 1001) {\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 | RInt64 n | RBool n -> - pr " return rv.ret.%s;\n" 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 + 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" + 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" @@ -2117,7 +2646,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"; @@ -2164,6 +2693,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"; @@ -2187,12 +2717,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; @@ -2200,34 +2737,48 @@ and generate_daemon_actions () = pr " goto done;\n"; pr "\n"; - (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 + (* 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. *) @@ -2369,6 +2920,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"; @@ -2500,8 +3052,8 @@ 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 (); @@ -2516,89 +3068,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); } @@ -2627,12 +3180,9 @@ 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"; @@ -2864,6 +3414,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 @@ -2903,7 +3454,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, _ -> @@ -3125,7 +3678,9 @@ and generate_fish_cmds () = 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 @@ -3146,6 +3701,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 -> @@ -3159,7 +3720,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. *) @@ -3347,9 +3908,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 @@ -3365,14 +3936,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; @@ -3431,11 +4007,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 ")"; @@ -3443,7 +4022,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 @@ -3454,13 +4033,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. *) @@ -3672,6 +4246,8 @@ copy_table (char * const * argv) 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" @@ -3689,7 +4265,9 @@ copy_table (char * const * argv) 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; @@ -3734,7 +4312,7 @@ copy_table (char * const * argv) 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"; @@ -3742,7 +4320,7 @@ copy_table (char * const * argv) 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; @@ -3838,7 +4416,7 @@ and generate_ocaml_prototype ?(is_external = false) name style = 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 -> " @@ -3977,12 +4555,12 @@ DESTROY (g) ); (* 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 @@ -3992,10 +4570,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 @@ -4007,7 +4583,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"; @@ -4018,7 +4594,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; @@ -4031,7 +4607,7 @@ DESTROY (g) pr " int64_t %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; @@ -4044,7 +4620,7 @@ DESTROY (g) 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; @@ -4057,7 +4633,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; @@ -4072,7 +4648,7 @@ DESTROY (g) 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; @@ -4089,7 +4665,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"; @@ -4121,7 +4697,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; @@ -4156,7 +4732,7 @@ and generate_perl_stat_code typ cols name style n do_cleanups = pr " struct guestfs_%s *%s;\n" typ 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; @@ -4312,7 +4888,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 @@ -4408,7 +4984,6 @@ put_table (char * const * const argv) list = PyList_New (argc >> 1); for (i = 0; i < argc; i += 2) { - PyObject *item; item = PyTuple_New (2); PyTuple_SetItem (item, 0, PyString_FromString (argv[i])); PyTuple_SetItem (item, 1, PyString_FromString (argv[i+1])); @@ -4564,7 +5139,7 @@ py_guestfs_close (PyObject *self, PyObject *args) 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; @@ -4579,7 +5154,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? *) @@ -4589,7 +5164,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 @@ -4602,7 +5177,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 @@ -4611,12 +5186,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); @@ -4701,50 +5276,912 @@ 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 - List.iter ( - fun (name, style, _, _, _, _, _) -> - pr " def %s " name; - generate_call_args ~handle:"self" style; - pr ":\n"; - pr " return libguestfsmod.%s " name; - generate_call_args ~handle:"self._o" style; - pr "\n"; - pr "\n"; - ) all_functions +import guestfs +g = guestfs.GuestFS () +g.add_drive (\"guest.img\") +g.launch () +g.wait_ready () +parts = g.list_partitions () -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; - in - close +The guestfs module provides a Python binding to the libguestfs API +for examining and modifying virtual machine disk images. -(* Main program. *) -let () = - check_functions (); +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. - if not (Sys.file_exists "configure.ac") then ( - eprintf "\ -You are probably running this from the wrong directory. -Run it from the top source directory using the command - src/generator.ml -"; - exit 1 - ); +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, _, 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" (snd style); + pr ":\n"; + pr " u\"\"\"%s\"\"\"\n" doc; + pr " return libguestfsmod.%s " name; + generate_call_args ~handle:"self._o" (snd style); + pr "\n"; + pr "\n"; + ) all_functions + +(* Useful if you need the longdesc POD text as plain text. Returns a + * list of lines. + * + * This is the slowest thing about autogeneration. + *) +and pod2text ~width name longdesc = + let filename, chan = Filename.open_temp_file "gen" ".tmp" in + fprintf chan "=head1 %s\n\n%s\n" name longdesc; + close_out chan; + let cmd = sprintf "pod2text -w %d %s" width (Filename.quote filename) in + let chan = Unix.open_process_in cmd in + let lines = ref [] in + let rec loop i = + let line = input_line chan in + if i = 1 then (* discard the first line of output *) + loop (i+1) + else ( + let line = triml line in + lines := line :: !lines; + loop (i+1) + ) in + let lines = try loop 1 with End_of_file -> List.rev !lines in + Unix.unlink filename; + match Unix.close_process_in chan with + | Unix.WEXITED 0 -> lines + | Unix.WEXITED i -> + failwithf "pod2text: process exited with non-zero status (%d)" i + | Unix.WSIGNALED i | Unix.WSTOPPED i -> + failwithf "pod2text: process signalled or stopped by signal %d" i + +(* Generate ruby bindings. *) +and generate_ruby_c () = + generate_header CStyle LGPLv2; + + pr "\ +#include +#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; + let close () = + close_out !chan; + chan := stdout; + Unix.rename filename_new filename; + printf "written %s\n%!" filename; + in + close + +(* Main program. *) +let () = + check_functions (); + + if not (Sys.file_exists "configure.ac") then ( + eprintf "\ +You are probably running this from the wrong directory. +Run it from the top source directory using the command + src/generator.ml +"; + exit 1 + ); let close = output_to "src/guestfs_protocol.x" in generate_xdr (); @@ -4821,3 +6258,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 ();