diff --git a/.gitignore b/.gitignore index 9d59a80f1..81cd278cc 100644 --- a/.gitignore +++ b/.gitignore @@ -97,7 +97,7 @@ Makefile.in /daemon/listfs.mli /daemon/lvm.mli /daemon/lvm_dm.mli -/daemon/lvm-tokenization.c +/daemon/lvm_full.mli /daemon/md.mli /daemon/mount.mli /daemon/names.c diff --git a/daemon/Makefile.am b/daemon/Makefile.am index bb72c0244..90ece8461 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -22,7 +22,6 @@ BUILT_SOURCES = \ caml-stubs.c \ dispatch.c \ names.c \ - lvm-tokenization.c \ structs-cleanups.c \ structs-cleanups.h \ stubs-0.c \ @@ -52,6 +51,7 @@ generator_built = \ listfs.mli \ lvm.mli \ lvm_dm.mli \ + lvm_full.mli \ md.mli \ mount.mli \ optgroups.ml \ @@ -152,7 +152,6 @@ guestfsd_SOURCES = \ luks.c \ lvm.c \ lvm-filter.c \ - lvm-tokenization.c \ md.c \ mkfs.c \ mknod.c \ @@ -298,6 +297,7 @@ SOURCES_MLI = \ listfs.mli \ lvm.mli \ lvm_dm.mli \ + lvm_full.mli \ lvm_utils.mli \ md.mli \ mount.mli \ @@ -333,6 +333,7 @@ SOURCES_ML = \ ldm.ml \ link.ml \ lvm.ml \ + lvm_full.ml \ lvm_utils.ml \ lvm_dm.ml \ findfs.ml \ diff --git a/daemon/lvm.c b/daemon/lvm.c index 261573882..924c1ddb3 100644 --- a/daemon/lvm.c +++ b/daemon/lvm.c @@ -139,28 +139,6 @@ do_vgs (void) return convert_lvm_output (out, NULL); } -/* These were so complex to implement that I ended up auto-generating - * the code. That code is in stubs.c, and it is generated as usual - * by generator.ml. - */ -guestfs_int_lvm_pv_list * -do_pvs_full (void) -{ - return parse_command_line_pvs (); -} - -guestfs_int_lvm_vg_list * -do_vgs_full (void) -{ - return parse_command_line_vgs (); -} - -guestfs_int_lvm_lv_list * -do_lvs_full (void) -{ - return parse_command_line_lvs (); -} - int do_pvcreate (const char *device) { diff --git a/daemon/lvm_full.ml b/daemon/lvm_full.ml new file mode 100644 index 000000000..d5653d2f0 --- /dev/null +++ b/daemon/lvm_full.ml @@ -0,0 +1,221 @@ +(* guestfs-inspection + * Copyright (C) 2009-2025 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. + *) + +(* This file implements the complicated lvs-full, vgs-full and pvs-full APIs + * + * XXX Deprecate these APIs are replace with APIs for getting single + * named fields from LVM. That will be slower but far more flexible + * and extensible. + *) + +open Unix +open Printf + +open Std_utils + +open Utils + +(* LVM UUIDs are basically 32 byte strings with '-' inserted. + * Remove the '-' characters and check it's the right length. + *) +let parse_uuid uuid = + let uuid' = + uuid |> String.explode |> List.filter ((<>) '-') |> String.implode in + if String.length uuid' <> 32 then + failwithf "lvm-full: parse_uuid: unexpected UUID format: %S" uuid; + uuid' + +(* Parse the percent fields. These can be empty. *) +let parse_percent pc = if pc = "" then None else Some (float_of_string pc) + +(* XXX These must match generator/structs.ml *) +let lvm_pv_cols = [ + "pv_name"; (* FString *) + "pv_uuid"; (* FUUID *) + "pv_fmt"; (* FString *) + "pv_size"; (* FBytes *) + "dev_size"; (* FBytes *) + "pv_free"; (* FBytes *) + "pv_used"; (* FBytes *) + "pv_attr"; (* FString (* XXX *) *) + "pv_pe_count"; (* FInt64 *) + "pv_pe_alloc_count"; (* FInt64 *) + "pv_tags"; (* FString *) + "pe_start"; (* FBytes *) + "pv_mda_count"; (* FInt64 *) + "pv_mda_free"; (* FBytes *) +] + +let tokenize_pvs = function + | [ pv_name; pv_uuid; pv_fmt; pv_size; dev_size; pv_free; + pv_used; pv_attr; pv_pe_count; pv_pe_alloc_count; pv_tags; + pe_start; pv_mda_count; pv_mda_free ] -> + { Structs.pv_name = pv_name; + pv_uuid = parse_uuid pv_uuid; + pv_fmt = pv_fmt; + pv_size = Int64.of_string pv_size; + dev_size = Int64.of_string dev_size; + pv_free = Int64.of_string pv_free; + pv_used = Int64.of_string pv_used; + pv_attr = pv_attr; + pv_pe_count = Int64.of_string pv_pe_count; + pv_pe_alloc_count = Int64.of_string pv_pe_alloc_count; + pv_tags = pv_tags; + pe_start = Int64.of_string pe_start; + pv_mda_count = Int64.of_string pv_mda_count; + pv_mda_free = Int64.of_string pv_mda_free } + + | fields -> + failwithf "pvs-full: tokenize_pvs: unexpected number of fields: %d" + (List.length fields) + +(* XXX These must match generator/structs.ml *) +let lvm_vg_cols = [ + "vg_name"; (* FString *) + "vg_uuid"; (* FUUID *) + "vg_fmt"; (* FString *) + "vg_attr"; (* FString (* XXX *) *) + "vg_size"; (* FBytes *) + "vg_free"; (* FBytes *) + "vg_sysid"; (* FString *) + "vg_extent_size"; (* FBytes *) + "vg_extent_count"; (* FInt64 *) + "vg_free_count"; (* FInt64 *) + "max_lv"; (* FInt64 *) + "max_pv"; (* FInt64 *) + "pv_count"; (* FInt64 *) + "lv_count"; (* FInt64 *) + "snap_count"; (* FInt64 *) + "vg_seqno"; (* FInt64 *) + "vg_tags"; (* FString *) + "vg_mda_count"; (* FInt64 *) + "vg_mda_free"; (* FBytes *) +] + +let tokenize_vgs = function + | [ vg_name; vg_uuid; vg_fmt; vg_attr; vg_size; vg_free; vg_sysid; + vg_extent_size; vg_extent_count; vg_free_count; max_lv; + max_pv; pv_count; lv_count; snap_count; vg_seqno; vg_tags; + vg_mda_count; vg_mda_free ] -> + { Structs.vg_name = vg_name; + vg_uuid = parse_uuid vg_uuid; + vg_fmt = vg_fmt; + vg_attr = vg_attr; + vg_size = Int64.of_string vg_size; + vg_free = Int64.of_string vg_free; + vg_sysid = vg_sysid; + vg_extent_size = Int64.of_string vg_extent_size; + vg_extent_count = Int64.of_string vg_extent_count; + vg_free_count = Int64.of_string vg_free_count; + max_lv = Int64.of_string max_lv; + max_pv = Int64.of_string max_pv; + pv_count = Int64.of_string pv_count; + lv_count = Int64.of_string lv_count; + snap_count = Int64.of_string snap_count; + vg_seqno = Int64.of_string vg_seqno; + vg_tags = vg_tags; + vg_mda_count = Int64.of_string vg_mda_count; + vg_mda_free = Int64.of_string vg_mda_free } + + | fields -> + failwithf "pvs-full: tokenize_vgs: unexpected number of fields: %d" + (List.length fields) + +(* XXX These must match generator/structs.ml *) +let lvm_lv_cols = [ + "lv_name"; (* FString *) + "lv_uuid"; (* FUUID *) + "lv_attr"; (* FString (* XXX *) *) + "lv_major"; (* FInt64 *) + "lv_minor"; (* FInt64 *) + "lv_kernel_major"; (* FInt64 *) + "lv_kernel_minor"; (* FInt64 *) + "lv_size"; (* FBytes *) + "seg_count"; (* FInt64 *) + "origin"; (* FString *) + "snap_percent"; (* FOptPercent *) + "copy_percent"; (* FOptPercent *) + "move_pv"; (* FString *) + "lv_tags"; (* FString *) + "mirror_log"; (* FString *) + "modules"; (* FString *) +] + +let tokenize_lvs = function + | [ lv_name; lv_uuid; lv_attr; lv_major; lv_minor; lv_kernel_major; + lv_kernel_minor; lv_size; seg_count; origin; snap_percent; + copy_percent; move_pv; lv_tags; mirror_log; modules ] -> + { Structs.lv_name = lv_name; + lv_uuid = parse_uuid lv_uuid; + lv_attr = lv_attr; + lv_major = Int64.of_string lv_major; + lv_minor = Int64.of_string lv_minor; + lv_kernel_major = Int64.of_string lv_kernel_major; + lv_kernel_minor = Int64.of_string lv_kernel_minor; + lv_size = Int64.of_string lv_size; + seg_count = Int64.of_string seg_count; + origin = origin; + snap_percent = parse_percent snap_percent; + copy_percent = parse_percent copy_percent; + move_pv = move_pv; + lv_tags = lv_tags; + mirror_log = mirror_log; + modules = modules } + + | fields -> + failwithf "pvs-full: tokenize_vgs: unexpected number of fields: %d" + (List.length fields) + +let rec pvs_full () = + let out = run_lvm_command "pvs" lvm_pv_cols in + let lines = trim_and_split out in + let pvs = List.map tokenize_pvs lines in + pvs + +and vgs_full () = + let out = run_lvm_command "vgs" lvm_vg_cols in + let lines = trim_and_split out in + let vgs = List.map tokenize_vgs lines in + vgs + +and lvs_full () = + let out = run_lvm_command "lvs" lvm_lv_cols in + let lines = trim_and_split out in + let lvs = List.map tokenize_lvs lines in + lvs + +and run_lvm_command typ cols = + let cols = String.concat "," cols in + let cmd = [ typ; "-o"; cols; + "--unbuffered"; "--noheadings"; "--nosuffix"; + "--separator"; "\r"; "--units"; "b" ] in + command "lvm" cmd + +and trim_and_split out = + (* Split the output into lines. *) + let lines = String.nsplit "\n" out in + + (* LVM puts leading whitespace on each line so remove that. *) + let lines = List.map String.triml lines in + + (* Ignore any blank lines. *) + let lines = List.filter ((<>) "") lines in + + (* Split each line into fields. *) + let lines = List.map (String.nsplit "\r") lines in + lines diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES index 0038f95e4..cdfb1d615 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -111,7 +111,6 @@ daemon/link.c daemon/ls.c daemon/luks.c daemon/lvm-filter.c -daemon/lvm-tokenization.c daemon/lvm.c daemon/md.c daemon/mkfs.c diff --git a/generator/actions_core.ml b/generator/actions_core.ml index 5e8de563a..34bd15ae6 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -1795,6 +1795,7 @@ See also C, C." }; { defaults with name = "pvs_full"; added = (0, 0, 4); style = RStructList ("physvols", "lvm_pv"), [], []; + impl = OCaml "Lvm_full.pvs_full"; optional = Some "lvm2"; shortdesc = "list the LVM physical volumes (PVs)"; longdesc = "\ @@ -1804,6 +1805,7 @@ of the L command. The \"full\" version includes all fields." }; { defaults with name = "vgs_full"; added = (0, 0, 4); style = RStructList ("volgroups", "lvm_vg"), [], []; + impl = OCaml "Lvm_full.vgs_full"; optional = Some "lvm2"; shortdesc = "list the LVM volume groups (VGs)"; longdesc = "\ @@ -1813,6 +1815,7 @@ of the L command. The \"full\" version includes all fields." }; { defaults with name = "lvs_full"; added = (0, 0, 4); style = RStructList ("logvols", "lvm_lv"), [], []; + impl = OCaml "Lvm_full.lvs_full"; optional = Some "lvm2"; shortdesc = "list the LVM logical volumes (LVs)"; longdesc = "\ diff --git a/generator/daemon.ml b/generator/daemon.ml index b23034fd7..da5593ce1 100644 --- a/generator/daemon.ml +++ b/generator/daemon.ml @@ -951,196 +951,6 @@ let generate_daemon_dispatch () = pr "}\n"; pr "\n" -let generate_daemon_lvm_tokenization () = - generate_header CStyle GPLv2plus; - - pr "\ -#include - -#include -#include -#include -#include -#include -#include -#include - -#include \"daemon.h\" -#include \"c-ctype.h\" -#include \"guestfs_protocol.h\" -#include \"actions.h\" -#include \"optgroups.h\" - -"; - - (* 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, '\\r');\n"; - pr " if (*p) next = p+1; else next = NULL;\n"; - pr " *p = '\\0';\n"; - (match coltype with - | FString | FDevice -> - 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, \"%%\" SCNi64, &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\", \"\\r\", \"--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.. *) let generate_daemon_names () = generate_header CStyle GPLv2plus; diff --git a/generator/daemon.mli b/generator/daemon.mli index f65545302..849979451 100644 --- a/generator/daemon.mli +++ b/generator/daemon.mli @@ -23,7 +23,6 @@ val generate_daemon_caml_stubs : unit -> unit val generate_daemon_caml_callbacks_ml : unit -> unit val generate_daemon_caml_interface : string -> unit -> unit val generate_daemon_dispatch : unit -> unit -val generate_daemon_lvm_tokenization : unit -> unit val generate_daemon_names : unit -> unit val generate_daemon_optgroups_c : unit -> unit val generate_daemon_optgroups_h : unit -> unit diff --git a/generator/main.ml b/generator/main.ml index 17bcef8b5..57285003b 100644 --- a/generator/main.ml +++ b/generator/main.ml @@ -147,8 +147,6 @@ Run it from the top source directory using the command Daemon.generate_daemon_optgroups_ml; output_to "daemon/optgroups.mli" Daemon.generate_daemon_optgroups_mli; - output_to "daemon/lvm-tokenization.c" - Daemon.generate_daemon_lvm_tokenization; output_to "daemon/structs-cleanups.c" Daemon.generate_daemon_structs_cleanups_c; output_to "daemon/structs-cleanups.h" diff --git a/generator/structs.ml b/generator/structs.ml index fcacade65..84db7fe84 100644 --- a/generator/structs.ml +++ b/generator/structs.ml @@ -31,9 +31,7 @@ type struc = { s_unused : unit; (* Silences warning 23 when using 'defaults with ...' *) } -(* Because we generate extra parsing code for LVM command line tools, - * we have to pull out the LVM columns separately here. - *) +(* XXX These must match daemon/lvm_full.ml *) let lvm_pv_cols = [ "pv_name", FDevice; "pv_uuid", FUUID; diff --git a/generator/structs.mli b/generator/structs.mli index 13bb0d5d3..c99086214 100644 --- a/generator/structs.mli +++ b/generator/structs.mli @@ -34,13 +34,6 @@ type struc = { val structs : struc list (** List of structures. *) -val lvm_pv_cols : cols -val lvm_vg_cols : cols -val lvm_lv_cols : cols -(** These are exported to the daemon code generator where they are - used to generate code for parsing the output of commands like - [lvs]. One day replace this with liblvm API calls. *) - val lookup_struct : string -> struc (** Lookup a struct by name. *) diff --git a/po/POTFILES b/po/POTFILES index 75b040c06..d10f97106 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -90,7 +90,6 @@ daemon/link.c daemon/ls.c daemon/luks.c daemon/lvm-filter.c -daemon/lvm-tokenization.c daemon/lvm.c daemon/md.c daemon/mkfs.c