X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=src%2Fgenerator.ml;h=427c9df27b44daf54eba2f36941552771ab58201;hp=14b5155434ef0d7dca8a64f75ae003f544e0db1d;hb=21ba59ce3cbc594ce9c7aeecd4dadb8430e4042d;hpb=843514eef9dc6d04d71e031ba9ddb16e2beb9a04 diff --git a/src/generator.ml b/src/generator.ml index 14b5155..427c9df 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -1,4 +1,4 @@ -#!/usr/bin/ocamlrun ocaml +#!/usr/bin/env ocaml (* libguestfs * Copyright (C) 2009 Red Hat Inc. * @@ -37,6 +37,10 @@ and ret = *) | RString of string | RStringList of string + (* LVM PVs, VGs and LVs. *) + | RPVList of string + | RVGList of string + | RLVList of string and args = (* 0 arguments, 1 argument, etc. The guestfs_h param is implicit. *) | P0 @@ -48,29 +52,6 @@ and argt = type flags = ProtocolLimitWarning let functions = [ - ("cat", (RString "content", P1 (String "path")), 4, [ProtocolLimitWarning], - "list the files in a directory (long format)", - "\ -Return the contents of the file named C."); - - ("ll", (RString "listing", P1 (String "directory")), 5, [], - "list the files in a directory (long format)", - "\ -List the files in C (relative to the root directory, -there is no cwd) in the format of 'ls -la'. - -This command is mostly useful for interactive sessions. It -is I intended that you try to parse the output string."); - - ("ls", (RStringList "listing", P1 (String "directory")), 6, [], - "list the files in a directory", - "\ -List the files in C (relative to the root directory, -there is no cwd). The '.' and '..' entries are not returned, but -hidden files are shown. - -This command is mostly useful for interactive sessions."); - ("mount", (Err, P2 (String "device", String "mountpoint")), 1, [], "mount a guest disk at a position in the filesystem", "\ @@ -106,8 +87,188 @@ calling C."); Touch acts like the L command. It can be used to update the timestamps on a file, or, if the file does not exist, to create a new zero-length file."); + + ("cat", (RString "content", P1 (String "path")), 4, [ProtocolLimitWarning], + "list the contents of a file", + "\ +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 +function which has a more complex interface."); + + ("ll", (RString "listing", P1 (String "directory")), 5, [], + "list the files in a directory (long format)", + "\ +List the files in C (relative to the root directory, +there is no cwd) in the format of 'ls -la'. + +This command is mostly useful for interactive sessions. It +is I intended that you try to parse the output string."); + + ("ls", (RStringList "listing", P1 (String "directory")), 6, [], + "list the files in a directory", + "\ +List the files in C (relative to the root directory, +there is no cwd). The '.' and '..' entries are not returned, but +hidden files are shown. + +This command is mostly useful for interactive sessions. Programs +should probably use C instead."); + + ("list_devices", (RStringList "devices", P0), 7, [], + "list the block devices", + "\ +List all the block devices. + +The full block device names are returned, eg. C +"); + + ("list_partitions", (RStringList "partitions", P0), 8, [], + "list the partitions", + "\ +List all the partitions detected on all block devices. + +The full partition device names are returned, eg. C + +This does not return logical volumes. For that you will need to +call C."); + + ("pvs", (RStringList "physvols", P0), 9, [], + "list the LVM physical volumes (PVs)", + "\ +List all the physical volumes detected. This is the equivalent +of the L command. + +This returns a list of just the device names that contain +PVs (eg. C). + +See also C."); + + ("vgs", (RStringList "volgroups", P0), 10, [], + "list the LVM volume groups (VGs)", + "\ +List all the volumes groups detected. This is the equivalent +of the L command. + +This returns a list of just the volume group names that were +detected (eg. C). + +See also C."); + + ("lvs", (RStringList "logvols", P0), 11, [], + "list the LVM logical volumes (LVs)", + "\ +List all the logical volumes detected. This is the equivalent +of the L command. + +This returns a list of the logical volume device names +(eg. C). + +See also C."); + + ("pvs_full", (RPVList "physvols", P0), 12, [], + "list the LVM physical volumes (PVs)", + "\ +List all the physical volumes detected. This is the equivalent +of the L command. The \"full\" version includes all fields."); + + ("vgs_full", (RVGList "volgroups", P0), 13, [], + "list the LVM volume groups (VGs)", + "\ +List all the volumes groups detected. This is the equivalent +of the L command. The \"full\" version includes all fields."); + + ("lvs_full", (RLVList "logvols", P0), 14, [], + "list the LVM logical volumes (LVs)", + "\ +List all the logical volumes detected. This is the equivalent +of the L command. The \"full\" version includes all fields."); ] +(* Column names and types from LVM PVs/VGs/LVs. *) +let pv_cols = [ + "pv_name", `String; + "pv_uuid", `UUID; + "pv_fmt", `String; + "pv_size", `Bytes; + "dev_size", `Bytes; + "pv_free", `Bytes; + "pv_used", `Bytes; + "pv_attr", `String (* XXX *); + "pv_pe_count", `Int; + "pv_pe_alloc_count", `Int; + "pv_tags", `String; + "pe_start", `Bytes; + "pv_mda_count", `Int; + "pv_mda_free", `Bytes; +(* Not in Fedora 10: + "pv_mda_size", `Bytes; +*) +] +let vg_cols = [ + "vg_name", `String; + "vg_uuid", `UUID; + "vg_fmt", `String; + "vg_attr", `String (* XXX *); + "vg_size", `Bytes; + "vg_free", `Bytes; + "vg_sysid", `String; + "vg_extent_size", `Bytes; + "vg_extent_count", `Int; + "vg_free_count", `Int; + "max_lv", `Int; + "max_pv", `Int; + "pv_count", `Int; + "lv_count", `Int; + "snap_count", `Int; + "vg_seqno", `Int; + "vg_tags", `String; + "vg_mda_count", `Int; + "vg_mda_free", `Bytes; +(* Not in Fedora 10: + "vg_mda_size", `Bytes; +*) +] +let lv_cols = [ + "lv_name", `String; + "lv_uuid", `UUID; + "lv_attr", `String (* XXX *); + "lv_major", `Int; + "lv_minor", `Int; + "lv_kernel_major", `Int; + "lv_kernel_minor", `Int; + "lv_size", `Bytes; + "seg_count", `Int; + "origin", `String; + "snap_percent", `OptPercent; + "copy_percent", `OptPercent; + "move_pv", `String; + "lv_tags", `String; + "mirror_log", `String; + "modules", `String; +] + +(* In some places we want the functions to be displayed sorted + * alphabetically, so this is useful: + *) +let sorted_functions = + List.sort (fun (n1,_,_,_,_,_) (n2,_,_,_,_,_) -> compare n1 n2) functions + +(* Useful functions. *) +let failwithf fs = ksprintf failwith fs +let replace s c1 c2 = + let s2 = String.copy s in + let r = ref false in + for i = 0 to String.length s2 - 1 do + if String.unsafe_get s2 i = c1 then ( + String.unsafe_set s2 i c2; + r := true + ) + done; + if not !r then s else s2 + (* 'pr' prints to the current output file. *) let chan = ref stdout let pr fs = ksprintf (output_string !chan) fs @@ -129,6 +290,30 @@ let map_args f = function let nr_args = function | P0 -> 0 | P1 _ -> 1 | P2 _ -> 2 +(* Check function names etc. for consistency. *) +let check_functions () = + List.iter ( + fun (name, _, _, _, _, _) -> + if String.contains name '-' then + failwithf "Function name '%s' should not contain '-', use '_' instead." + name + ) functions; + + let proc_nrs = + List.map (fun (name, _, proc_nr, _, _, _) -> name, proc_nr) functions in + let proc_nrs = + List.sort (fun (_,nr1) (_,nr2) -> compare nr1 nr2) proc_nrs in + let rec loop = function + | [] -> () + | [_] -> () + | (name1,nr1) :: ((name2,nr2) :: _ as rest) when nr1 < nr2 -> + loop rest + | (name1,nr1) :: (name2,nr2) :: _ -> + failwithf "'%s' and '%s' have conflicting procedure numbers (%d, %d)" + name1 name2 nr1 nr2 + in + loop proc_nrs + type comment_style = CStyle | HashStyle | OCamlStyle type license = GPLv2 | LGPLv2 @@ -183,7 +368,7 @@ let rec generate_header comment license = pr "\n" (* Generate the pod documentation for the C API. *) -and generate_pod () = +and generate_actions_pod () = List.iter ( fun (shortname, style, _, flags, _, longdesc) -> let name = "guestfs_" ^ shortname in @@ -196,21 +381,65 @@ and generate_pod () = | Err -> pr "This function returns 0 on success or -1 on error.\n\n" | RString _ -> - pr "This function returns a string or NULL on error. The caller -must free the returned string after use.\n\n" + pr "This function returns a string or NULL on error. +I.\n\n" | RStringList _ -> pr "This function returns a NULL-terminated array of strings (like L), or NULL if there was an error. - -The caller must free the strings I the array after use.\n\n" +I.\n\n" + | RPVList _ -> + pr "This function returns a C. +I after use.>.\n\n" + | RVGList _ -> + pr "This function returns a C. +I after use.>.\n\n" + | RLVList _ -> + pr "This function returns a C. +I after use.>.\n\n" ); if List.mem ProtocolLimitWarning flags then pr "Because of the message protocol, there is a transfer limit of somewhere between 2MB and 4MB. To transfer large files you should use FTP.\n\n"; - ) functions + ) sorted_functions + +and generate_structs_pod () = + (* LVM structs documentation. *) + List.iter ( + fun (typ, cols) -> + pr "=head2 guestfs_lvm_%s\n" typ; + pr "\n"; + pr " struct guestfs_lvm_%s {\n" typ; + List.iter ( + function + | name, `String -> pr " char *%s;\n" name + | name, `UUID -> + pr " /* The next field is NOT nul-terminated, be careful when printing it: */\n"; + pr " char %s[32];\n" name + | name, `Bytes -> pr " uint64_t %s;\n" name + | name, `Int -> pr " int64_t %s;\n" name + | name, `OptPercent -> + pr " /* The next field is [0..100] or -1 meaning 'not present': */\n"; + pr " float %s;\n" name + ) cols; + pr " \n"; + pr " struct guestfs_lvm_%s_list {\n" typ; + pr " uint32_t len; /* Number of elements in list. */\n"; + pr " struct guestfs_lvm_%s *val; /* Elements. */\n" typ; + pr " };\n"; + pr " \n"; + pr " void guestfs_free_lvm_%s_list (struct guestfs_free_lvm_%s_list *);\n" + typ typ; + pr "\n" + ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols] -(* Generate the protocol (XDR) file. *) +(* Generate the protocol (XDR) file, 'guestfs_protocol.x' and + * indirectly 'guestfs_protocol.h' and 'guestfs_protocol.c'. We + * have to use an underscore instead of a dash because otherwise + * rpcgen generates incorrect code. + * + * This header is NOT exported to clients, but see also generate_structs_h. + *) and generate_xdr () = generate_header CStyle LGPLv2; @@ -218,6 +447,24 @@ and generate_xdr () = pr "typedef string str<>;\n"; pr "\n"; + (* LVM internal structures. *) + List.iter ( + function + | typ, cols -> + pr "struct guestfs_lvm_int_%s {\n" typ; + List.iter (function + | name, `String -> pr " string %s<>;\n" name + | name, `UUID -> pr " opaque %s[32];\n" name + | name, `Bytes -> pr " hyper %s;\n" name + | name, `Int -> pr " hyper %s;\n" name + | name, `OptPercent -> pr " float %s;\n" name + ) cols; + pr "};\n"; + pr "\n"; + pr "typedef struct guestfs_lvm_int_%s guestfs_lvm_int_%s_list<>;\n" typ typ; + pr "\n"; + ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols]; + List.iter ( fun (shortname, style, _, _, _, _) -> let name = "guestfs_" ^ shortname in @@ -242,6 +489,18 @@ and generate_xdr () = pr "struct %s_ret {\n" name; pr " str %s<>;\n" n; pr "};\n\n" + | RPVList n -> + pr "struct %s_ret {\n" name; + pr " guestfs_lvm_int_pv_list %s;\n" n; + pr "};\n\n" + | RVGList n -> + pr "struct %s_ret {\n" name; + pr " guestfs_lvm_int_vg_list %s;\n" n; + pr "};\n\n" + | RLVList n -> + pr "struct %s_ret {\n" name; + pr " guestfs_lvm_int_lv_list %s;\n" n; + pr "};\n\n" ); ) functions; @@ -295,6 +554,46 @@ struct guestfs_message_header { }; " +(* Generate the guestfs-structs.h file. *) +and generate_structs_h () = + generate_header CStyle LGPLv2; + + (* This is a public exported header file containing various + * structures. The structures are carefully written to have + * exactly the same in-memory format as the XDR structures that + * we use on the wire to the daemon. The reason for creating + * copies of these structures here is just so we don't have to + * export the whole of guestfs_protocol.h (which includes much + * unrelated and XDR-dependent stuff that we don't want to be + * public, or required by clients). + * + * To reiterate, we will pass these structures to and from the + * client with a simple assignment or memcpy, so the format + * must be identical to what rpcgen / the RFC defines. + *) + + (* LVM public structures. *) + List.iter ( + function + | typ, cols -> + pr "struct guestfs_lvm_%s {\n" typ; + List.iter ( + function + | name, `String -> pr " char *%s;\n" name + | name, `UUID -> pr " char %s[32]; /* this is NOT nul-terminated, be careful when printing */\n" name + | name, `Bytes -> pr " uint64_t %s;\n" name + | name, `Int -> pr " int64_t %s;\n" name + | name, `OptPercent -> pr " float %s; /* [0..100] or -1 */\n" name + ) cols; + pr "};\n"; + pr "\n"; + pr "struct guestfs_lvm_%s_list {\n" typ; + pr " uint32_t len;\n"; + pr " struct guestfs_lvm_%s *val;\n" typ; + pr "};\n"; + pr "\n" + ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols] + (* Generate the guestfs-actions.h file. *) and generate_actions_h () = generate_header CStyle LGPLv2; @@ -308,6 +607,8 @@ and generate_actions_h () = (* Generate the client-side dispatch stubs. *) and generate_client_actions () = generate_header CStyle LGPLv2; + + (* Client-side stubs for each function. *) List.iter ( fun (shortname, style, _, _, _, _) -> let name = "guestfs_" ^ shortname in @@ -319,7 +620,8 @@ and generate_client_actions () = pr " struct guestfs_message_error err;\n"; (match fst style with | Err -> () - | RString _ | RStringList _ -> pr " struct %s_ret ret;\n" name; + | RString _ | RStringList _ | RPVList _ | RVGList _ | RLVList _ -> + pr " struct %s_ret ret;\n" name ); pr "};\n\n"; @@ -342,7 +644,7 @@ and generate_client_actions () = (match fst style with | Err -> () - | RString _ | RStringList _ -> + | RString _ | RStringList _ | RPVList _ | RVGList _ | RLVList _ -> pr " if (!xdr_%s_ret (xdr, &rv->ret)) {\n" name; pr " error (g, \"%s: failed to parse reply\");\n" name; pr " return;\n"; @@ -361,7 +663,8 @@ and generate_client_actions () = let error_code = match fst style with | Err -> "-1" - | RString _ | RStringList _ -> "NULL" in + | RString _ | RStringList _ | RPVList _ | RVGList _ | RLVList _ -> + "NULL" in pr "{\n"; @@ -430,9 +733,21 @@ and generate_client_actions () = pr " return rv.ret.%s; /* caller will free */\n" n | RStringList n -> pr " /* caller will free this, but we need to add a NULL entry */\n"; - pr " rv.ret.%s.%s_val = safe_realloc (g, rv.ret.%s.%s_val, rv.ret.%s.%s_len + 1);\n" n n n n n 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" + 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 + | RPVList n -> + pr " /* caller will free this */\n"; + pr " return safe_memdup (g, &rv.ret.%s, sizeof (rv.ret.%s));\n" n n + | RVGList n -> + pr " /* caller will free this */\n"; + pr " return safe_memdup (g, &rv.ret.%s, sizeof (rv.ret.%s));\n" n n + | RLVList n -> + pr " /* caller will free this */\n"; + pr " return safe_memdup (g, &rv.ret.%s, sizeof (rv.ret.%s));\n" n n ); pr "}\n\n" @@ -441,17 +756,30 @@ and generate_client_actions () = (* Generate daemon/actions.h. *) and generate_daemon_actions_h () = generate_header CStyle GPLv2; + + pr "#include \"../src/guestfs_protocol.h\"\n"; + pr "\n"; + List.iter ( fun (name, style, _, _, _, _) -> - generate_prototype ~single_line:true ~newline:true ("do_" ^ name) style; + generate_prototype + ~single_line:true ~newline:true ~in_daemon:true ("do_" ^ name) style; ) functions (* Generate the server-side stubs. *) and generate_daemon_actions () = generate_header CStyle GPLv2; + pr "#define _GNU_SOURCE // for strchrnul\n"; + pr "\n"; + pr "#include \n"; + pr "#include \n"; + pr "#include \n"; + pr "#include \n"; + pr "#include \n"; pr "#include \n"; pr "#include \n"; + pr "\n"; pr "#include \"daemon.h\"\n"; pr "#include \"../src/guestfs_protocol.h\"\n"; pr "#include \"actions.h\"\n"; @@ -466,7 +794,11 @@ and generate_daemon_actions () = match fst style with | Err -> pr " int r;\n"; "-1" | RString _ -> pr " char *r;\n"; "NULL" - | RStringList _ -> pr " char **r;\n"; "NULL" in + | RStringList _ -> pr " char **r;\n"; "NULL" + | RPVList _ -> pr " guestfs_lvm_int_pv_list *r;\n"; "NULL" + | RVGList _ -> pr " guestfs_lvm_int_vg_list *r;\n"; "NULL" + | RLVList _ -> pr " guestfs_lvm_int_lv_list *r;\n"; "NULL" in + (match snd style with | P0 -> () | args -> @@ -516,6 +848,21 @@ and generate_daemon_actions () = 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" + | RPVList 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 + | RVGList 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 + | RLVList 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 ); pr "}\n\n"; @@ -536,7 +883,173 @@ and generate_daemon_actions () = pr " default:\n"; pr " reply_with_error (\"dispatch_incoming_message: unknown procedure number %%d\", proc_nr);\n"; pr " }\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, struct guestfs_lvm_int_%s *r)\n" typ typ; + pr "{\n"; + pr " char *tok, *p, *next;\n"; + pr " int 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 || 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 + | `String -> + pr " r->%s = strdup (tok);\n" name; + pr " if (r->%s == NULL) {\n" name; + pr " perror (\"strdup\");\n"; + pr " return -1;\n"; + pr " }\n" + | `UUID -> + 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"; + | `Bytes -> + 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"; + | `Int -> + 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"; + | `OptPercent -> + 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"; + ); + 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_lvm_int_%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_lvm_int_%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_lvm_int_%s_list_len = 0;\n" typ; + pr " ret->guestfs_lvm_int_%s_list_val = NULL;\n" typ; + pr "\n"; + pr " r = command (&out, &err,\n"; + pr " \"/sbin/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 " 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 && 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_lvm_int_%s_list_val,\n" typ; + pr " sizeof (guestfs_lvm_int_%s) * (i+1));\n" typ; + pr " if (newp == NULL) {\n"; + pr " reply_with_perror (\"realloc\");\n"; + pr " free (ret->guestfs_lvm_int_%s_list_val);\n" typ; + pr " free (ret);\n"; + pr " free (out);\n"; + pr " return NULL;\n"; + pr " }\n"; + pr " ret->guestfs_lvm_int_%s_list_val = newp;\n" typ; + pr "\n"; + pr " /* Tokenize the next entry. */\n"; + pr " r = lvm_tokenize_%s (p, &ret->guestfs_lvm_int_%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_lvm_int_%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_lvm_int_%s_list_len = i;\n" typ; + pr "\n"; + pr " free (out);\n"; + pr " return ret;\n"; + pr "}\n" + + ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols] (* Generate a lot of different functions for guestfish. *) and generate_fish_cmds () = @@ -545,7 +1058,9 @@ and generate_fish_cmds () = pr "#include \n"; pr "#include \n"; pr "#include \n"; + pr "#include \n"; pr "\n"; + pr "#include \n"; pr "#include \"fish.h\"\n"; pr "\n"; @@ -556,9 +1071,10 @@ and generate_fish_cmds () = pr " list_builtin_commands ();\n"; List.iter ( fun (name, _, _, _, shortdesc, _) -> + let name = replace name '_' '-' in pr " printf (\"%%-20s %%s\\n\", \"%s\", \"%s\");\n" name shortdesc - ) functions; + ) sorted_functions; pr " printf (\" Use -h / help to show detailed help for a command.\\n\");\n"; pr "}\n"; pr "\n"; @@ -568,12 +1084,13 @@ and generate_fish_cmds () = pr "{\n"; List.iter ( fun (name, style, _, flags, shortdesc, longdesc) -> + let name2 = replace name '_' '-' in let synopsis = match snd style with - | P0 -> name + | P0 -> name2 | args -> sprintf "%s <%s>" - name ( + name2 ( String.concat "> <" ( map_args (function | String n -> n) args @@ -587,9 +1104,13 @@ of somewhere between 2MB and 4MB. To transfer large files you should use FTP." else "" in - pr " if (strcasecmp (cmd, \"%s\") == 0)\n" name; + pr " if ("; + pr "strcasecmp (cmd, \"%s\") == 0" name; + if name <> name2 then + pr " || strcasecmp (cmd, \"%s\") == 0" name2; + pr ")\n"; pr " pod2text (\"%s - %s\", %S);\n" - name shortdesc + name2 shortdesc (" " ^ synopsis ^ "\n\n" ^ longdesc ^ warnings); pr " else\n" ) functions; @@ -597,6 +1118,45 @@ FTP." pr "}\n"; pr "\n"; + (* print_{pv,vg,lv}_list functions *) + List.iter ( + function + | typ, cols -> + pr "static void print_%s (struct guestfs_lvm_%s *%s)\n" typ typ typ; + pr "{\n"; + pr " int i;\n"; + pr "\n"; + List.iter ( + function + | name, `String -> + pr " printf (\"%s: %%s\\n\", %s->%s);\n" name typ name + | name, `UUID -> + pr " printf (\"%s: \");\n" name; + pr " for (i = 0; i < 32; ++i)\n"; + pr " printf (\"%%c\", %s->%s[i]);\n" typ name; + pr " printf (\"\\n\");\n" + | name, `Bytes -> + pr " printf (\"%s: %%\" PRIu64 \"\\n\", %s->%s);\n" name typ name + | name, `Int -> + pr " printf (\"%s: %%\" PRIi64 \"\\n\", %s->%s);\n" name typ name + | name, `OptPercent -> + pr " if (%s->%s >= 0) printf (\"%s: %%g %%%%\\n\", %s->%s);\n" + typ name name typ name; + pr " else printf (\"%s: \\n\");\n" name + ) cols; + pr "}\n"; + pr "\n"; + pr "static void print_%s_list (struct guestfs_lvm_%s_list *%ss)\n" + typ typ typ; + pr "{\n"; + pr " int i;\n"; + pr "\n"; + pr " for (i = 0; i < %ss->len; ++i)\n" typ; + pr " print_%s (&%ss->val[i]);\n" typ typ; + pr "}\n"; + pr "\n"; + ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols]; + (* run_ actions *) List.iter ( fun (name, style, _, _, _, _) -> @@ -606,6 +1166,9 @@ FTP." | Err -> pr " int r;\n" | RString _ -> pr " char *r;\n" | RStringList _ -> pr " char **r;\n" + | RPVList _ -> pr " struct guestfs_lvm_pv_list *r;\n" + | RVGList _ -> pr " struct guestfs_lvm_vg_list *r;\n" + | RLVList _ -> pr " struct guestfs_lvm_lv_list *r;\n" ); iter_args ( function @@ -644,6 +1207,21 @@ FTP." pr " print_strings (r);\n"; pr " free_strings (r);\n"; pr " return 0;\n" + | RPVList _ -> + pr " if (r == NULL) return -1;\n"; + pr " print_pv_list (r);\n"; + pr " guestfs_free_lvm_pv_list (r);\n"; + pr " return 0;\n" + | RVGList _ -> + pr " if (r == NULL) return -1;\n"; + pr " print_vg_list (r);\n"; + pr " guestfs_free_lvm_vg_list (r);\n"; + pr " return 0;\n" + | RLVList _ -> + pr " if (r == NULL) return -1;\n"; + pr " print_lv_list (r);\n"; + pr " guestfs_free_lvm_lv_list (r);\n"; + pr " return 0;\n" ); pr "}\n"; pr "\n" @@ -654,7 +1232,12 @@ FTP." pr "{\n"; List.iter ( fun (name, _, _, _, _, _) -> - pr " if (strcasecmp (cmd, \"%s\") == 0)\n" name; + let name2 = replace name '_' '-' in + pr " if ("; + pr "strcasecmp (cmd, \"%s\") == 0" name; + if name <> name2 then + pr " || strcasecmp (cmd, \"%s\") == 0" name2; + pr ")\n"; pr " return run_%s (cmd, argc, argv);\n" name; pr " else\n"; ) functions; @@ -666,9 +1249,25 @@ FTP." pr "}\n"; pr "\n" +(* Generate the POD documentation for guestfish. *) +and generate_fish_actions_pod () = + List.iter ( + fun (name, style, _, _, _, longdesc) -> + let name = replace name '_' '-' in + pr "=head2 %s\n\n" name; + pr " %s" name; + iter_args ( + function + | String n -> pr " %s" n + ) (snd style); + pr "\n"; + pr "\n"; + pr "%s\n\n" longdesc + ) sorted_functions + (* Generate a C function prototype. *) and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true) - ?(single_line = false) ?(newline = false) + ?(single_line = false) ?(newline = false) ?(in_daemon = false) ?handle name style = if extern then pr "extern "; if static then pr "static "; @@ -676,6 +1275,15 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true) | Err -> pr "int " | RString _ -> pr "char *" | RStringList _ -> pr "char **" + | RPVList _ -> + if not in_daemon then pr "struct guestfs_lvm_pv_list *" + else pr "guestfs_lvm_int_pv_list *" + | RVGList _ -> + if not in_daemon then pr "struct guestfs_lvm_vg_list *" + else pr "guestfs_lvm_int_vg_list *" + | RLVList _ -> + if not in_daemon then pr "struct guestfs_lvm_lv_list *" + else pr "guestfs_lvm_int_lv_list *" ); pr "%s (" name; let comma = ref false in @@ -727,10 +1335,16 @@ let output_to filename = (* Main program. *) let () = + check_functions (); + let close = output_to "src/guestfs_protocol.x" in generate_xdr (); close (); + let close = output_to "src/guestfs-structs.h" in + generate_structs_h (); + close (); + let close = output_to "src/guestfs-actions.h" in generate_actions_h (); close (); @@ -751,6 +1365,14 @@ let () = generate_fish_cmds (); close (); + let close = output_to "guestfs-structs.pod" in + generate_structs_pod (); + close (); + let close = output_to "guestfs-actions.pod" in - generate_pod (); + generate_actions_pod (); + close (); + + let close = output_to "guestfish-actions.pod" in + generate_fish_actions_pod (); close ()