| 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 *)
Note that this function cannot correctly handle binary files
(specifically, files containing C<\\0> character which is treated
-as end of string). For those you need to use the C<guestfs_read_file>
+as end of string). For those you need to use the C<guestfs_download>
function which has a more complex interface.");
("ll", (RString "listing", [String "directory"]), 5, [],
This uses the L<blockdev(8)> command.");
+(*
+ ("upload", (RErr, [FileIn "filename"; String "remotefilename"]), 66, [],
+ [],
+ "upload a file from the local machine",
+ "\
+Upload local file C<filename> to C<remotefilename> on the
+filesystem.
+
+C<filename> can also be a named pipe.
+
+See also C<guestfs_upload>.");
+
+ ("download", (RErr, [String "remotefilename"; FileOut "filename"]), 67, [],
+ [],
+ "download a file to the local machine",
+ "\
+Download file C<remotefilename> and save it as C<filename>
+on the local machine.
+
+C<filename> can also be a named pipe.
+
+See also C<guestfs_download>, C<guestfs_cat>.");
+*)
+
]
let all_functions = non_daemon_functions @ daemon_functions
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, _)
| 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"
);
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";
string error<GUESTFS_ERROR_LEN>; /* error message */
};
+/* For normal requests and replies (not involving any FileIn or
+ * FileOut parameters), the protocol is:
+ *
+ * For requests:
+ * total length (header + args, but not including length word itself)
+ * header
+ * guestfs_foo_args struct
+ * For replies:
+ * total length (as above)
+ * header
+ * guestfs_foo_ret struct
+ */
+
struct guestfs_message_header {
unsigned prog; /* GUESTFS_PROGRAM */
unsigned vers; /* GUESTFS_PROTOCOL_VERSION */
unsigned serial; /* message serial number */
guestfs_message_status status;
};
+
+/* Chunked encoding used to transfer files, for FileIn and FileOut
+ * parameters.
+ *
+ * For requests which have >= 1 FileIn parameter:
+ * length of header + args (but not length word itself, and not chunks)
+ * header
+ * guestfs_foo_args struct
+ * sequence of chunks for FileIn param #0
+ * sequence of chunks for FileIn param #1 etc
+ *
+ * For replies which have >= 1 FileOut parameter:
+ * length of header + ret (but not length word itself, and not chunks)
+ * header
+ * guestfs_foo_ret struct
+ * sequence of chunks for FileOut param #0
+ * sequence of chunks for FileOut param #1 etc
+ */
+const GUESTFS_MAX_CHUNK_SIZE = 8192;
+
+struct guestfs_chunk {
+ int cancel; /* if non-zero, transfer is cancelled */
+ /* data size is 0 bytes if the transfer has finished successfully */
+ opaque data<GUESTFS_MAX_CHUNK_SIZE>;
+};
"
(* Generate the guestfs-structs.h file. *)
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 state struct which stores the high-level
+ * state between callback functions. The callback(s) are:
+ * <name>_cb_header_sent header was sent
+ * <name>_cb_file_sent FileIn file was sent
+ * <name>_cb_reply_received reply received
+ *)
+ pr "struct %s_state {\n" shortname;
+ pr " int cb_done;\n";
pr " struct guestfs_message_header hdr;\n";
pr " struct guestfs_message_error err;\n";
(match fst style with
| 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;
pr "{\n";
- pr " struct %s_rv *rv = (struct %s_rv *) data;\n" shortname shortname;
+ pr " struct %s_state *state = (struct %s_state *) data;\n" shortname shortname;
pr "\n";
- pr " if (!xdr_guestfs_message_header (xdr, &rv->hdr)) {\n";
+ pr " if (!xdr_guestfs_message_header (xdr, &state->hdr)) {\n";
pr " error (g, \"%s: failed to parse reply header\");\n" name;
pr " return;\n";
pr " }\n";
- pr " if (rv->hdr.status == GUESTFS_STATUS_ERROR) {\n";
- pr " if (!xdr_guestfs_message_error (xdr, &rv->err)) {\n";
+ pr " if (state->hdr.status == GUESTFS_STATUS_ERROR) {\n";
+ pr " if (!xdr_guestfs_message_error (xdr, &state->err)) {\n";
pr " error (g, \"%s: failed to parse reply error\");\n" name;
pr " return;\n";
pr " }\n";
| RPVList _ | RVGList _ | RLVList _
| RStat _ | RStatVFS _
| RHashtable _ ->
- pr " if (!xdr_%s_ret (xdr, &rv->ret)) {\n" name;
+ pr " if (!xdr_%s_ret (xdr, &state->ret)) {\n" name;
pr " error (g, \"%s: failed to parse reply\");\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 " state->cb_done = 1;\n";
+ pr " g->main_loop->main_loop_quit (g->main_loop, g);\n";
pr "}\n\n";
(* Generate the action stub. *)
| _ -> pr " struct %s_args args;\n" name
);
- pr " struct %s_rv rv;\n" shortname;
+ pr " struct %s_state state;\n" shortname;
pr " int serial;\n";
pr "\n";
pr " if (g->state != READY) {\n";
- pr " error (g, \"%s called from the wrong state, %%d != READY\",\n"
- name;
- pr " g->state);\n";
+ pr " if (g->state == CONFIG)\n";
+ pr " error (g, \"%%s: call launch() before using this function\",\n";
+ pr " \"%s\");\n" name;
+ pr " else if (g->state == LAUNCHING)\n";
+ pr " error (g, \"%%s: call wait_ready() before using this function\",\n";
+ pr " \"%s\");\n" name;
+ pr " else\n";
+ pr " error (g, \"%%s called from the wrong state, %%d != READY\",\n";
+ pr " \"%s\", g->state);\n" name;
pr " return %s;\n" error_code;
pr " }\n";
pr "\n";
- pr " memset (&rv, 0, sizeof rv);\n";
+ pr " memset (&state, 0, sizeof state);\n";
pr "\n";
+ (* Dispatch the main header and arguments. *)
(match snd style with
| [] ->
pr " serial = dispatch (g, GUESTFS_PROC_%s, NULL, NULL);\n"
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"
(String.uppercase shortname);
pr " return %s;\n" error_code;
pr "\n";
- pr " rv.cb_done = 0;\n";
+ (* Send any additional files requested. *)
+ List.iter (
+ function
+ | FileIn n ->
+ pr " if (send_file (g, %s) == -1)\n" n;
+ pr " return %s;\n" error_code;
+ pr "\n";
+ | _ -> ()
+ ) (snd style);
+
+ (* Wait for the reply from the remote end. *)
+ pr " state.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_data = &state;\n";
+ pr " (void) g->main_loop->main_loop_run (g->main_loop, g);\n";
pr " g->reply_cb_internal = NULL;\n";
pr " g->reply_cb_internal_data = NULL;\n";
- pr " if (!rv.cb_done) {\n";
+ pr " if (!state.cb_done) {\n";
pr " error (g, \"%s failed, see earlier error messages\");\n" name;
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, &state.hdr, GUESTFS_PROC_%s, serial) == -1)\n"
(String.uppercase shortname);
pr " return %s;\n" error_code;
pr "\n";
- pr " if (rv.hdr.status == GUESTFS_STATUS_ERROR) {\n";
- pr " error (g, \"%%s\", rv.err.error);\n";
+ pr " if (state.hdr.status == GUESTFS_STATUS_ERROR) {\n";
+ pr " error (g, \"%%s\", state.err.error);\n";
pr " return %s;\n" error_code;
pr " }\n";
pr "\n";
+ (* Expecting to receive further files (FileOut)? *)
+ List.iter (
+ function
+ | FileOut n ->
+ pr " if (receive_file (g, %s) == -1)\n" n;
+ pr " return %s;\n" error_code;
+ pr "\n";
+ | _ -> ()
+ ) (snd style);
+
(match fst style with
| RErr -> pr " return 0;\n"
| RInt n | RInt64 n | RBool n ->
- pr " return rv.ret.%s;\n" n
+ pr " return state.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 state.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 " state.ret.%s.%s_val =" n n;
+ pr " safe_realloc (g, state.ret.%s.%s_val,\n" n n;
+ pr " sizeof (char *) * (state.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 " state.ret.%s.%s_val[state.ret.%s.%s_len] = NULL;\n" n n n n;
+ pr " return state.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, &state.ret, sizeof (state.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, &state.ret.%s, sizeof (state.ret.%s));\n" n n
);
pr "}\n\n"
| StringList n -> pr " char **%s;\n" n
| Bool n -> pr " int %s;\n" n
| Int n -> pr " int %s;\n" n
+ | FileIn _ | FileOut _ -> ()
) args
);
pr "\n";
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;
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. *)
| OptString _, _
| Int _, _
| Bool _, _ -> ()
+ | FileIn _, _ | FileOut _, _ -> ()
| StringList n, arg ->
pr " char *%s[] = {\n" n;
let strs = string_split " " arg in
(* 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, _ ->
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
| 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 ->
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. *)
| 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;
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 ")";
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
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. *)
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;
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";
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;
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 -> "
);
(* 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
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
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";
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;
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;
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;
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;
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;
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";
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;
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;
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
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]));
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;
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? *)
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
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
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);
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
| Unix.WSIGNALED i | Unix.WSTOPPED i ->
failwithf "pod2text: process signalled or stopped by signal %d" i
+(* Generate ruby bindings. *)
+and generate_ruby_c () =
+ generate_header CStyle LGPLv2;
+
+ pr "\
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <ruby.h>
+
+#include \"guestfs.h\"
+
+#include \"extconf.h\"
+
+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;
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 ();