X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=src%2Fgenerator.ml;h=191b94dffdd51f3c5be3328e5ca5568b210767c1;hp=4653d65cfdc219bf3434bc1fbad4d20ad99ce9fd;hb=42341cc00362a9983142a95f36622ea5baeb280a;hpb=05712b2457a44ee0f0020eced77db03c2aa419a1 diff --git a/src/generator.ml b/src/generator.ml index 4653d65..191b94d 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -98,6 +98,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 *) @@ -324,7 +334,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 +458,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, [], @@ -1198,6 +1272,95 @@ Reread the partition table on C. This uses the L command."); + ("upload", (RErr, [FileIn "filename"; String "remotefilename"]), 66, [], + [], + "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, [], + [], + "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."); + ] let all_functions = non_daemon_functions @ daemon_functions @@ -1322,6 +1485,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 +1573,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 +1929,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 +1995,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 +2010,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 +2034,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 +2045,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 +2129,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 +2225,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 +2259,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 +2291,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 +2320,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" @@ -2164,6 +2489,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 +2513,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 +2533,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 +2716,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"; @@ -2864,6 +3212,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 +3252,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 +3476,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 +3499,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 +3518,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. *) @@ -3368,11 +3727,16 @@ and generate_fish_actions_pod () = | 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 +3795,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 +3810,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 +3821,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. *) @@ -3689,7 +4051,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 +4098,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 +4106,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 +4202,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 +4341,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 +4356,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 +4369,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 +4380,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 +4393,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 +4406,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 +4419,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 +4434,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 +4451,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 +4483,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 +4518,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 +4674,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 +4770,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 +4925,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 +4940,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 +4950,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 +4963,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 +4972,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,27 +5062,378 @@ initlibguestfsmod (void) and generate_python_py () = generate_header HashStyle LGPLv2; - pr "import libguestfsmod\n"; - pr "\n"; - pr "class GuestFS:\n"; - pr " def __init__ (self):\n"; - pr " self._o = libguestfsmod.create ()\n"; - pr "\n"; - pr " def __del__ (self):\n"; - pr " libguestfsmod.close (self._o)\n"; - pr "\n"; + pr "\ +u\"\"\"Python bindings for libguestfs + +import guestfs +g = guestfs.GuestFS () +g.add_drive (\"guest.img\") +g.launch () +g.wait_ready () +parts = g.list_partitions () + +The guestfs module provides a Python binding to the libguestfs API +for examining and modifying virtual machine disk images. + +Amongst the things this is good for: making batch configuration +changes to guests, getting disk used/free statistics (see also: +virt-df), migrating between virtualization systems (see also: +virt-p2v), performing partial backups, performing partial guest +clones, cloning guests and changing registry/UUID/hostname info, and +much else besides. + +Libguestfs uses Linux kernel and qemu code, and can access any type of +guest filesystem that Linux and qemu can, including but not limited +to: ext2/3/4, btrfs, FAT and NTFS, LVM, many different disk partition +schemes, qcow, qcow2, vmdk. + +Libguestfs provides ways to enumerate guest storage (eg. partitions, +LVs, what filesystem is in each LV, etc.). It can also run commands +in the context of the guest. Also you can access filesystems over FTP. + +Errors which happen while using the API are turned into Python +RuntimeError exceptions. + +To create a guestfs handle you usually have to perform the following +sequence of calls: + +# Create the handle, call add_drive at least once, and possibly +# several times if the guest has multiple block devices: +g = guestfs.GuestFS () +g.add_drive (\"guest.img\") + +# Launch the qemu subprocess and wait for it to become ready: +g.launch () +g.wait_ready () + +# Now you can issue commands, for example: +logvols = g.lvs () + +\"\"\" + +import libguestfsmod + +class GuestFS: + \"\"\"Instances of this class are libguestfs API handles.\"\"\" + + def __init__ (self): + \"\"\"Create a new libguestfs handle.\"\"\" + self._o = libguestfsmod.create () + + def __del__ (self): + libguestfsmod.close (self._o) + +"; List.iter ( - fun (name, style, _, _, _, _, _) -> + fun (name, style, _, flags, _, _, longdesc) -> + let doc = replace_str longdesc "C doc + | RStringList _ -> + doc ^ "\n\nThis function returns a list of strings." + | RIntBool _ -> + doc ^ "\n\nThis function returns a tuple (int, bool).\n" + | RPVList _ -> + doc ^ "\n\nThis function returns a list of PVs. Each PV is represented as a dictionary." + | RVGList _ -> + doc ^ "\n\nThis function returns a list of VGs. Each VG is represented as a dictionary." + | RLVList _ -> + doc ^ "\n\nThis function returns a list of LVs. Each LV is represented as a dictionary." + | RStat _ -> + doc ^ "\n\nThis function returns a dictionary, with keys matching the various fields in the stat structure." + | RStatVFS _ -> + doc ^ "\n\nThis function returns a dictionary, with keys matching the various fields in the statvfs structure." + | RHashtable _ -> + doc ^ "\n\nThis function returns a dictionary." in + let doc = + if List.mem ProtocolLimitWarning flags then + doc ^ "\n\n" ^ protocol_limit_warning + else doc in + let doc = + if List.mem DangerWillRobinson flags then + doc ^ "\n\n" ^ danger_will_robinson + else doc in + let doc = pod2text ~width:60 name doc in + let doc = List.map (fun line -> replace_str line "\\" "\\\\") doc in + let doc = String.concat "\n " doc in + pr " def %s " name; - generate_call_args ~handle:"self" style; + generate_call_args ~handle:"self" (snd style); pr ":\n"; + pr " u\"\"\"%s\"\"\"\n" doc; pr " return libguestfsmod.%s " name; - generate_call_args ~handle:"self._o" style; + generate_call_args ~handle:"self._o" (snd style); pr "\n"; pr "\n"; ) all_functions +(* Useful if you need the longdesc POD text as plain text. Returns a + * list of lines. + *) +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 " }\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" + let output_to filename = let filename_new = filename ^ ".new" in chan := open_out filename_new; @@ -4821,3 +5533,7 @@ 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 ();