Files
libguestfs/generator/generator_daemon.ml
Richard Jones 04d8209077 Split generator into separate source files.
'src/generator.ml' is no more.  Instead the generator is logically
split up over many different source files.

Read generator/README for help and tips.

We compile the generator down to bytecode, not native code.  This
means it will run more slowly, but is done for maximum portability.
2010-09-11 12:04:44 +01:00

509 lines
18 KiB
OCaml

(* libguestfs
* Copyright (C) 2009-2010 Red Hat Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*)
(* Please read generator/README first. *)
open Printf
open Generator_types
open Generator_utils
open Generator_pr
open Generator_docstrings
open Generator_optgroups
open Generator_actions
open Generator_structs
open Generator_c
(* Generate daemon/actions.h. *)
let generate_daemon_actions_h () =
generate_header CStyle GPLv2plus;
pr "#include \"../src/guestfs_protocol.h\"\n";
pr "\n";
List.iter (
fun (name, style, _, _, _, _, _) ->
generate_prototype
~single_line:true ~newline:true ~in_daemon:true ~prefix:"do_"
name style;
) daemon_functions
(* Generate the server-side stubs. *)
and generate_daemon_actions () =
generate_header CStyle GPLv2plus;
pr "#include <config.h>\n";
pr "\n";
pr "#include <stdio.h>\n";
pr "#include <stdlib.h>\n";
pr "#include <string.h>\n";
pr "#include <inttypes.h>\n";
pr "#include <rpc/types.h>\n";
pr "#include <rpc/xdr.h>\n";
pr "\n";
pr "#include \"daemon.h\"\n";
pr "#include \"c-ctype.h\"\n";
pr "#include \"../src/guestfs_protocol.h\"\n";
pr "#include \"actions.h\"\n";
pr "\n";
List.iter (
fun (name, style, _, _, _, _, _) ->
(* Generate server-side stubs. *)
pr "static void %s_stub (XDR *xdr_in)\n" name;
pr "{\n";
let error_code =
match fst style with
| RErr | RInt _ -> pr " int r;\n"; "-1"
| RInt64 _ -> pr " int64_t r;\n"; "-1"
| RBool _ -> pr " int r;\n"; "-1"
| RConstString _ | RConstOptString _ ->
failwithf "RConstString|RConstOptString cannot be used by daemon functions"
| RString _ -> pr " char *r;\n"; "NULL"
| RStringList _ | RHashtable _ -> pr " char **r;\n"; "NULL"
| RStruct (_, typ) -> pr " guestfs_int_%s *r;\n" typ; "NULL"
| RStructList (_, typ) -> pr " guestfs_int_%s_list *r;\n" typ; "NULL"
| RBufferOut _ ->
pr " size_t size = 1;\n";
pr " char *r;\n";
"NULL" in
(match snd style with
| [] -> ()
| args ->
pr " struct guestfs_%s_args args;\n" name;
List.iter (
function
| Device n | Dev_or_Path n
| Pathname n
| String n
| Key n -> ()
| OptString n -> pr " char *%s;\n" n
| StringList n | DeviceList n -> pr " char **%s;\n" n
| Bool n -> pr " int %s;\n" n
| Int n -> pr " int %s;\n" n
| Int64 n -> pr " int64_t %s;\n" n
| FileIn _ | FileOut _ -> ()
| BufferIn n ->
pr " const char *%s;\n" n;
pr " size_t %s_size;\n" n
) args
);
pr "\n";
let is_filein =
List.exists (function FileIn _ -> true | _ -> false) (snd style) in
(match snd style with
| [] -> ()
| args ->
pr " memset (&args, 0, sizeof args);\n";
pr "\n";
pr " if (!xdr_guestfs_%s_args (xdr_in, &args)) {\n" name;
if is_filein then
pr " if (cancel_receive () != -2)\n";
pr " reply_with_error (\"daemon failed to decode procedure arguments\");\n";
pr " goto done;\n";
pr " }\n";
let pr_args n =
pr " char *%s = args.%s;\n" n n
in
let pr_list_handling_code n =
pr " %s = realloc (args.%s.%s_val,\n" n n n;
pr " sizeof (char *) * (args.%s.%s_len+1));\n" n n;
pr " if (%s == NULL) {\n" n;
if is_filein then
pr " if (cancel_receive () != -2)\n";
pr " reply_with_perror (\"realloc\");\n";
pr " goto done;\n";
pr " }\n";
pr " %s[args.%s.%s_len] = NULL;\n" n n n;
pr " args.%s.%s_val = %s;\n" n n n;
in
List.iter (
function
| Pathname n ->
pr_args n;
pr " ABS_PATH (%s, %s, goto done);\n"
n (if is_filein then "cancel_receive ()" else "0");
| Device n ->
pr_args n;
pr " RESOLVE_DEVICE (%s, %s, goto done);\n"
n (if is_filein then "cancel_receive ()" else "0");
| Dev_or_Path n ->
pr_args n;
pr " REQUIRE_ROOT_OR_RESOLVE_DEVICE (%s, %s, goto done);\n"
n (if is_filein then "cancel_receive ()" else "0");
| String n | Key n -> pr_args n
| OptString n -> pr " %s = args.%s ? *args.%s : NULL;\n" n n n
| StringList n ->
pr_list_handling_code n;
| DeviceList n ->
pr_list_handling_code n;
pr " /* Ensure that each is a device,\n";
pr " * and perform device name translation.\n";
pr " */\n";
pr " {\n";
pr " size_t i;\n";
pr " for (i = 0; %s[i] != NULL; ++i)\n" n;
pr " RESOLVE_DEVICE (%s[i], %s, goto done);\n" n
(if is_filein then "cancel_receive ()" else "0");
pr " }\n";
| Bool n -> pr " %s = args.%s;\n" n n
| Int n -> pr " %s = args.%s;\n" n n
| Int64 n -> pr " %s = args.%s;\n" n n
| FileIn _ | FileOut _ -> ()
| BufferIn n ->
pr " %s = args.%s.%s_val;\n" n n n;
pr " %s_size = args.%s.%s_len;\n" n n n
) args;
pr "\n"
);
(* this is used at least for do_equal *)
if List.exists (function Pathname _ -> true | _ -> false) (snd style) then (
(* Emit NEED_ROOT just once, even when there are two or
more Pathname args *)
pr " NEED_ROOT (%s, goto done);\n"
(if is_filein then "cancel_receive ()" else "0");
);
(* Don't want to call the impl with any FileIn or FileOut
* parameters, since these go "outside" the RPC protocol.
*)
let args' =
List.filter (function FileIn _ | FileOut _ -> false | _ -> true)
(snd style) in
pr " r = do_%s " name;
generate_c_call_args (fst style, args');
pr ";\n";
(match fst style with
| RErr | RInt _ | RInt64 _ | RBool _
| RConstString _ | RConstOptString _
| RString _ | RStringList _ | RHashtable _
| RStruct (_, _) | RStructList (_, _) ->
pr " if (r == %s)\n" error_code;
pr " /* do_%s has already called reply_with_error */\n" name;
pr " goto done;\n";
pr "\n"
| RBufferOut _ ->
pr " /* size == 0 && r == NULL could be a non-error case (just\n";
pr " * an ordinary zero-length buffer), so be careful ...\n";
pr " */\n";
pr " if (size == 1 && r == %s)\n" error_code;
pr " /* do_%s has already called reply_with_error */\n" name;
pr " goto done;\n";
pr "\n"
);
(* 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 _ | RConstOptString _ ->
failwithf "RConstString|RConstOptString cannot be used by daemon functions"
| 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"
| RStruct (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
| RStructList (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
| RBufferOut n ->
pr " struct guestfs_%s_ret ret;\n" name;
pr " ret.%s.%s_val = r;\n" n n;
pr " ret.%s.%s_len = size;\n" n n;
pr " reply ((xdrproc_t) &xdr_guestfs_%s_ret, (char *) &ret);\n"
name;
pr " free (r);\n"
);
(* Free the args. *)
pr "done:\n";
(match snd style with
| [] -> ()
| _ ->
pr " xdr_free ((xdrproc_t) xdr_guestfs_%s_args, (char *) &args);\n"
name
);
pr " return;\n";
pr "}\n\n";
) daemon_functions;
(* Dispatch function. *)
pr "void dispatch_incoming_message (XDR *xdr_in)\n";
pr "{\n";
pr " switch (proc_nr) {\n";
List.iter (
fun (name, style, _, _, _, _, _) ->
pr " case GUESTFS_PROC_%s:\n" (String.uppercase name);
pr " %s_stub (xdr_in);\n" name;
pr " break;\n"
) daemon_functions;
pr " default:\n";
pr " reply_with_error (\"dispatch_incoming_message: unknown procedure number %%d, set LIBGUESTFS_PATH to point to the matching libguestfs appliance directory\", proc_nr);\n";
pr " }\n";
pr "}\n";
pr "\n";
(* LVM columns and tokenization functions. *)
(* XXX This generates crap code. We should rethink how we
* do this parsing.
*)
List.iter (
function
| typ, cols ->
pr "static const char *lvm_%s_cols = \"%s\";\n"
typ (String.concat "," (List.map fst cols));
pr "\n";
pr "static int lvm_tokenize_%s (char *str, guestfs_int_lvm_%s *r)\n" typ typ;
pr "{\n";
pr " char *tok, *p, *next;\n";
pr " size_t i, j;\n";
pr "\n";
(*
pr " fprintf (stderr, \"%%s: <<%%s>>\\n\", __func__, str);\n";
pr "\n";
*)
pr " if (!str) {\n";
pr " fprintf (stderr, \"%%s: failed: passed a NULL string\\n\", __func__);\n";
pr " return -1;\n";
pr " }\n";
pr " if (!*str || c_isspace (*str)) {\n";
pr " fprintf (stderr, \"%%s: failed: passed a empty string or one beginning with whitespace\\n\", __func__);\n";
pr " return -1;\n";
pr " }\n";
pr " tok = str;\n";
List.iter (
fun (name, coltype) ->
pr " if (!tok) {\n";
pr " fprintf (stderr, \"%%s: failed: string finished early, around token %%s\\n\", __func__, \"%s\");\n" name;
pr " return -1;\n";
pr " }\n";
pr " p = strchrnul (tok, ',');\n";
pr " if (*p) next = p+1; else next = NULL;\n";
pr " *p = '\\0';\n";
(match coltype with
| FString ->
pr " r->%s = strdup (tok);\n" name;
pr " if (r->%s == NULL) {\n" name;
pr " perror (\"strdup\");\n";
pr " return -1;\n";
pr " }\n"
| FUUID ->
pr " for (i = j = 0; i < 32; ++j) {\n";
pr " if (tok[j] == '\\0') {\n";
pr " fprintf (stderr, \"%%s: failed to parse UUID from '%%s'\\n\", __func__, tok);\n";
pr " return -1;\n";
pr " } else if (tok[j] != '-')\n";
pr " r->%s[i++] = tok[j];\n" name;
pr " }\n";
| FBytes ->
pr " if (sscanf (tok, \"%%\"SCNu64, &r->%s) != 1) {\n" name;
pr " fprintf (stderr, \"%%s: failed to parse size '%%s' from token %%s\\n\", __func__, tok, \"%s\");\n" name;
pr " return -1;\n";
pr " }\n";
| FInt64 ->
pr " if (sscanf (tok, \"%%\"SCNi64, &r->%s) != 1) {\n" name;
pr " fprintf (stderr, \"%%s: failed to parse int '%%s' from token %%s\\n\", __func__, tok, \"%s\");\n" name;
pr " return -1;\n";
pr " }\n";
| FOptPercent ->
pr " if (tok[0] == '\\0')\n";
pr " r->%s = -1;\n" name;
pr " else if (sscanf (tok, \"%%f\", &r->%s) != 1) {\n" name;
pr " fprintf (stderr, \"%%s: failed to parse float '%%s' from token %%s\\n\", __func__, tok, \"%s\");\n" name;
pr " return -1;\n";
pr " }\n";
| FBuffer | FInt32 | FUInt32 | FUInt64 | FChar ->
assert false (* can never be an LVM column *)
);
pr " tok = next;\n";
) cols;
pr " if (tok != NULL) {\n";
pr " fprintf (stderr, \"%%s: failed: extra tokens at end of string\\n\", __func__);\n";
pr " return -1;\n";
pr " }\n";
pr " return 0;\n";
pr "}\n";
pr "\n";
pr "guestfs_int_lvm_%s_list *\n" typ;
pr "parse_command_line_%ss (void)\n" typ;
pr "{\n";
pr " char *out, *err;\n";
pr " char *p, *pend;\n";
pr " int r, i;\n";
pr " guestfs_int_lvm_%s_list *ret;\n" typ;
pr " void *newp;\n";
pr "\n";
pr " ret = malloc (sizeof *ret);\n";
pr " if (!ret) {\n";
pr " reply_with_perror (\"malloc\");\n";
pr " return NULL;\n";
pr " }\n";
pr "\n";
pr " ret->guestfs_int_lvm_%s_list_len = 0;\n" typ;
pr " ret->guestfs_int_lvm_%s_list_val = NULL;\n" typ;
pr "\n";
pr " r = command (&out, &err,\n";
pr " \"lvm\", \"%ss\",\n" typ;
pr " \"-o\", lvm_%s_cols, \"--unbuffered\", \"--noheadings\",\n" typ;
pr " \"--nosuffix\", \"--separator\", \",\", \"--units\", \"b\", NULL);\n";
pr " if (r == -1) {\n";
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";
pr " free (err);\n";
pr "\n";
pr " /* Tokenize each line of the output. */\n";
pr " p = out;\n";
pr " i = 0;\n";
pr " while (p) {\n";
pr " pend = strchr (p, '\\n'); /* Get the next line of output. */\n";
pr " if (pend) {\n";
pr " *pend = '\\0';\n";
pr " pend++;\n";
pr " }\n";
pr "\n";
pr " while (*p && c_isspace (*p)) /* Skip any leading whitespace. */\n";
pr " p++;\n";
pr "\n";
pr " if (!*p) { /* Empty line? Skip it. */\n";
pr " p = pend;\n";
pr " continue;\n";
pr " }\n";
pr "\n";
pr " /* Allocate some space to store this next entry. */\n";
pr " newp = realloc (ret->guestfs_int_lvm_%s_list_val,\n" typ;
pr " sizeof (guestfs_int_lvm_%s) * (i+1));\n" typ;
pr " if (newp == NULL) {\n";
pr " reply_with_perror (\"realloc\");\n";
pr " free (ret->guestfs_int_lvm_%s_list_val);\n" typ;
pr " free (ret);\n";
pr " free (out);\n";
pr " return NULL;\n";
pr " }\n";
pr " ret->guestfs_int_lvm_%s_list_val = newp;\n" typ;
pr "\n";
pr " /* Tokenize the next entry. */\n";
pr " r = lvm_tokenize_%s (p, &ret->guestfs_int_lvm_%s_list_val[i]);\n" typ typ;
pr " if (r == -1) {\n";
pr " reply_with_error (\"failed to parse output of '%ss' command\");\n" typ;
pr " free (ret->guestfs_int_lvm_%s_list_val);\n" typ;
pr " free (ret);\n";
pr " free (out);\n";
pr " return NULL;\n";
pr " }\n";
pr "\n";
pr " ++i;\n";
pr " p = pend;\n";
pr " }\n";
pr "\n";
pr " ret->guestfs_int_lvm_%s_list_len = i;\n" typ;
pr "\n";
pr " free (out);\n";
pr " return ret;\n";
pr "}\n"
) ["pv", lvm_pv_cols; "vg", lvm_vg_cols; "lv", lvm_lv_cols]
(* Generate a list of function names, for debugging in the daemon.. *)
and generate_daemon_names () =
generate_header CStyle GPLv2plus;
pr "#include <config.h>\n";
pr "\n";
pr "#include \"daemon.h\"\n";
pr "\n";
pr "/* This array is indexed by proc_nr. See guestfs_protocol.x. */\n";
pr "const char *function_names[] = {\n";
List.iter (
fun (name, _, proc_nr, _, _, _, _) -> pr " [%d] = \"%s\",\n" proc_nr name
) daemon_functions;
pr "};\n";
(* Generate the optional groups for the daemon to implement
* guestfs_available.
*)
and generate_daemon_optgroups_c () =
generate_header CStyle GPLv2plus;
pr "#include <config.h>\n";
pr "\n";
pr "#include \"daemon.h\"\n";
pr "#include \"optgroups.h\"\n";
pr "\n";
pr "struct optgroup optgroups[] = {\n";
List.iter (
fun (group, _) ->
pr " { \"%s\", optgroup_%s_available },\n" group group
) optgroups;
pr " { NULL, NULL }\n";
pr "};\n"
and generate_daemon_optgroups_h () =
generate_header CStyle GPLv2plus;
List.iter (
fun (group, _) ->
pr "extern int optgroup_%s_available (void);\n" group
) optgroups