352 lines
12 KiB
Diff
352 lines
12 KiB
Diff
From aac2ec68202f2913ae6000aa117a8ed5dffbd6bb Mon Sep 17 00:00:00 2001
|
||
From: "Richard W.M. Jones" <rjones@redhat.com>
|
||
Date: Fri, 23 Jan 2026 08:58:07 +0000
|
||
Subject: [PATCH] New API: xfs_info2
|
||
|
||
Reimplement xfs_info by returning a hash table of values (rather than
|
||
a limited struct), and by writing it in OCaml with PCRE which makes
|
||
string parsing a lot simpler. This will now flexibly return all the
|
||
fields from the underlying xfs_info command, even (hopefully) future
|
||
fields.
|
||
|
||
Note the field values are returned as strings, because the actual
|
||
fields in xfs_info output are fairly random and free-form. There is a
|
||
trade off here between returning as much information as we can, and
|
||
requiring the user to do a bit of (simple) field parsing.
|
||
|
||
Fixes: https://issues.redhat.com/browse/RHEL-143673
|
||
(cherry picked from commit dfd2700616dee92d75e91936fb56b5de0450f992)
|
||
---
|
||
.gitignore | 1 +
|
||
common | 2 +-
|
||
daemon/Makefile.am | 7 ++-
|
||
daemon/xfs.ml | 128 ++++++++++++++++++++++++++++++++++++++
|
||
generator/actions_core.ml | 35 +++++++++++
|
||
generator/proc_nr.ml | 1 +
|
||
lib/MAX_PROC_NR | 2 +-
|
||
7 files changed, 172 insertions(+), 4 deletions(-)
|
||
create mode 100644 daemon/xfs.ml
|
||
|
||
diff --git a/.gitignore b/.gitignore
|
||
index 02160caff..c8a8312fa 100644
|
||
--- a/.gitignore
|
||
+++ b/.gitignore
|
||
@@ -119,6 +119,7 @@ Makefile.in
|
||
/daemon/stubs-?.c
|
||
/daemon/stubs.h
|
||
/daemon/types.ml
|
||
+/daemon/xfs.mli
|
||
/depcomp
|
||
/docs/guestfs-building.1
|
||
/docs/guestfs-faq.1
|
||
Submodule common b54ba2031..3ac5d1841:
|
||
diff --git a/common/mlpcre/PCRE.ml b/common/mlpcre/PCRE.ml
|
||
index 077290e..33074af 100644
|
||
--- a/common/mlpcre/PCRE.ml
|
||
+++ b/common/mlpcre/PCRE.ml
|
||
@@ -22,7 +22,7 @@ exception Error of string * int
|
||
|
||
type regexp
|
||
|
||
-external compile : ?caseless:bool -> ?dotall:bool -> ?extended:bool -> ?multiline:bool -> string -> regexp = "guestfs_int_pcre_compile"
|
||
+external compile : ?anchored:bool -> ?caseless:bool -> ?dotall:bool -> ?extended:bool -> ?multiline:bool -> string -> regexp = "guestfs_int_pcre_compile_byte" "guestfs_int_pcre_compile"
|
||
external matches : ?offset:int -> regexp -> string -> bool = "guestfs_int_pcre_matches"
|
||
external sub : int -> string = "guestfs_int_pcre_sub"
|
||
external subi : int -> int * int = "guestfs_int_pcre_subi"
|
||
diff --git a/common/mlpcre/PCRE.mli b/common/mlpcre/PCRE.mli
|
||
index b69a56b..0fdc2bd 100644
|
||
--- a/common/mlpcre/PCRE.mli
|
||
+++ b/common/mlpcre/PCRE.mli
|
||
@@ -52,11 +52,12 @@ exception Error of string * int
|
||
type regexp
|
||
(** The type of a compiled regular expression. *)
|
||
|
||
-val compile : ?caseless:bool -> ?dotall:bool -> ?extended:bool -> ?multiline:bool -> string -> regexp
|
||
+val compile : ?anchored:bool -> ?caseless:bool -> ?dotall:bool ->
|
||
+ ?extended:bool -> ?multiline:bool -> string -> regexp
|
||
(** Compile a regular expression. This can raise {!Error}.
|
||
|
||
- The flags [?caseless], [?dotall], [?extended], [?multiline]
|
||
- correspond to the [pcre_compile] flags [PCRE_CASELESS] etc.
|
||
+ The flags [?anchored], [?caseless], [?dotall], [?extended], [?multiline]
|
||
+ correspond to the [pcre_compile] flags [PCRE_ANCHORED] etc.
|
||
See pcre2api(3) for details of what they do.
|
||
All flags default to false. *)
|
||
|
||
diff --git a/common/mlpcre/pcre-c.c b/common/mlpcre/pcre-c.c
|
||
index 3959fd5..11be157 100644
|
||
--- a/common/mlpcre/pcre-c.c
|
||
+++ b/common/mlpcre/pcre-c.c
|
||
@@ -154,11 +154,12 @@ Optint_val (value intv, int defval)
|
||
}
|
||
|
||
value
|
||
-guestfs_int_pcre_compile (value caselessv, value dotallv,
|
||
- value extendedv, value multilinev,
|
||
+guestfs_int_pcre_compile (value anchoredv, value caselessv,
|
||
+ value dotallv, value extendedv,
|
||
+ value multilinev,
|
||
value pattv)
|
||
{
|
||
- CAMLparam4 (caselessv, dotallv, extendedv, multilinev);
|
||
+ CAMLparam5 (anchoredv, caselessv, dotallv, extendedv, multilinev);
|
||
CAMLxparam1 (pattv);
|
||
const char *patt;
|
||
int options = 0;
|
||
@@ -167,6 +168,8 @@ guestfs_int_pcre_compile (value caselessv, value dotallv,
|
||
PCRE2_SIZE errnum;
|
||
|
||
/* Flag parameters are all ‘bool option’, defaulting to false. */
|
||
+ if (is_Some_true (anchoredv))
|
||
+ options |= PCRE2_ANCHORED;
|
||
if (is_Some_true (caselessv))
|
||
options |= PCRE2_CASELESS;
|
||
if (is_Some_true (dotallv))
|
||
@@ -186,6 +189,14 @@ guestfs_int_pcre_compile (value caselessv, value dotallv,
|
||
CAMLreturn (Val_regexp (re));
|
||
}
|
||
|
||
+value
|
||
+guestfs_int_pcre_compile_byte (value *argv, int argn)
|
||
+{
|
||
+ assert (argn == 6);
|
||
+ return guestfs_int_pcre_compile (argv[0], argv[1], argv[2],
|
||
+ argv[3], argv[4], argv[5]);
|
||
+}
|
||
+
|
||
value
|
||
guestfs_int_pcre_matches (value offsetv, value rev, value strv)
|
||
{
|
||
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
|
||
index c644d9881..d4a805046 100644
|
||
--- a/daemon/Makefile.am
|
||
+++ b/daemon/Makefile.am
|
||
@@ -63,7 +63,8 @@ generator_built = \
|
||
sfdisk.mli \
|
||
statvfs.mli \
|
||
structs.ml \
|
||
- structs.mli
|
||
+ structs.mli \
|
||
+ xfs.mli
|
||
|
||
CONFIGURE_GENERATED_ML = \
|
||
daemon_config.ml
|
||
@@ -312,7 +313,8 @@ SOURCES_MLI = \
|
||
statvfs.mli \
|
||
structs.mli \
|
||
sysroot.mli \
|
||
- utils.mli
|
||
+ utils.mli \
|
||
+ xfs.mli
|
||
|
||
SOURCES_ML = \
|
||
$(CONFIGURE_GENERATED_ML) \
|
||
@@ -347,6 +349,7 @@ SOURCES_ML = \
|
||
realpath.ml \
|
||
statvfs.ml \
|
||
selinux.ml \
|
||
+ xfs.ml \
|
||
inspect_types.ml \
|
||
inspect_utils.ml \
|
||
inspect_fs_unix_fstab.ml \
|
||
diff --git a/daemon/xfs.ml b/daemon/xfs.ml
|
||
new file mode 100644
|
||
index 000000000..142b26775
|
||
--- /dev/null
|
||
+++ b/daemon/xfs.ml
|
||
@@ -0,0 +1,128 @@
|
||
+(* 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.
|
||
+ *)
|
||
+
|
||
+open Printf
|
||
+
|
||
+open Std_utils
|
||
+
|
||
+open Utils
|
||
+
|
||
+(* The output is horrific ...
|
||
+
|
||
+meta-data=/dev/sda1 isize=512 agcount=4, agsize=122094659 blks
|
||
+ = sectsz=4096 attr=2, projid32bit=1
|
||
+ = crc=1 finobt=1, sparse=1, rmapbt=0
|
||
+ = reflink=1 bigtime=1 inobtcount=1 nrext64=0
|
||
+ = exchange=0 metadir=0
|
||
+data = bsize=4096 blocks=488378636, imaxpct=5
|
||
+ = sunit=0 swidth=0 blks
|
||
+naming =version 2 bsize=4096 ascii-ci=0, ftype=1, parent=0
|
||
+log =internal log bsize=4096 blocks=238466, version=2
|
||
+ = sectsz=4096 sunit=1 blks, lazy-count=1
|
||
+realtime =none extsz=4096 blocks=0, rtextents=0
|
||
+ = rgcount=0 rgsize=0 extents
|
||
+ = zoned=0 start=0 reserved=0
|
||
+
|
||
+^heading ^"stuff" ^ data fields vaguely related to heading
|
||
+
|
||
+Note also the inconsistent use of commas.
|
||
+*)
|
||
+
|
||
+(* Split into groups using a positive lookahead assertion. *)
|
||
+let re1 = PCRE.compile ~extended:true {| \n (?=[a-z]) |}
|
||
+
|
||
+(* Separate group heading and the rest. *)
|
||
+let re2 = PCRE.compile ~extended:true {| = |}
|
||
+
|
||
+(* Match the first field in a group (if present). *)
|
||
+let re3 = PCRE.compile ~anchored:true ~extended:true {|
|
||
+ (version\s\d+|\S+\slog|\S+).*
|
||
+|}
|
||
+
|
||
+(* Match next field=value in group. *)
|
||
+let re4 = PCRE.compile ~extended:true {|
|
||
+ ([-\w]+)=(\d+(\s(blks|extents))?)
|
||
+|}
|
||
+
|
||
+let xfs_info2 dev =
|
||
+ (* Uncomment the first line to enable extra debugging. *)
|
||
+ (*let extra_debug = verbose () in*)
|
||
+ let extra_debug = false in
|
||
+
|
||
+ let is_dev = is_device_parameter dev in
|
||
+ let arg = if is_dev then dev else Sysroot.sysroot_path dev in
|
||
+ let out = command "xfs_info" [arg] in
|
||
+
|
||
+ (* Split the output by heading. *)
|
||
+ let groups = PCRE.nsplit re1 out in
|
||
+ let groups = List.map (PCRE.split re2) groups in
|
||
+ let groups = List.map (fun (name, rest) -> String.trim name, rest) groups in
|
||
+
|
||
+ if extra_debug then (
|
||
+ List.iteri (
|
||
+ fun i (name, rest) ->
|
||
+ eprintf "xfs_info2: group %d: %S: %S\n%!" i name rest
|
||
+ ) groups
|
||
+ );
|
||
+
|
||
+ (* Parse each group into the final list of values. *)
|
||
+ let values = ref [] in
|
||
+ List.iter (
|
||
+ fun (group_name, rest) ->
|
||
+ let len = String.length rest in
|
||
+
|
||
+ (* If there is some string at the beginning of the
|
||
+ * group then we create a (group_name, string) value,
|
||
+ * eg. ("meta-data", "/dev/sda1")
|
||
+ *)
|
||
+ let start =
|
||
+ if PCRE.matches re3 rest then (
|
||
+ let value = PCRE.sub 1 in
|
||
+ List.push_front (group_name, value) values;
|
||
+ (* Start parsing after this. *)
|
||
+ String.length value
|
||
+ )
|
||
+ else 0 in
|
||
+
|
||
+ let rec loop i =
|
||
+ if extra_debug then
|
||
+ eprintf "xfs_info2: parsing group %S from %d\n%!" group_name i;
|
||
+ if i <= len && PCRE.matches ~offset:i re4 rest then (
|
||
+ let field_name = PCRE.sub 1 in
|
||
+ if extra_debug then eprintf "xfs_info2: sub1=%S\n%!" field_name;
|
||
+ let value = PCRE.sub 2 in
|
||
+ if extra_debug then eprintf "xfs_info2: sub2=%S\n%!" value;
|
||
+ let name = sprintf "%s.%s" group_name field_name in
|
||
+ List.push_front (name, value) values;
|
||
+
|
||
+ (* Next time round the loop, start parsing after the
|
||
+ * current matched subexpression.
|
||
+ *)
|
||
+ loop (snd (PCRE.subi 2) + 1)
|
||
+ )
|
||
+ in
|
||
+ (try
|
||
+ loop start
|
||
+ with
|
||
+ Not_found ->
|
||
+ failwithf "xfs_info2: internal error: unexpected Not_found exception. Enable debug and send the full output in a bug report."
|
||
+ );
|
||
+
|
||
+ ) groups;
|
||
+
|
||
+ List.rev !values
|
||
diff --git a/generator/actions_core.ml b/generator/actions_core.ml
|
||
index b57cf56b0..b7e4dae45 100644
|
||
--- a/generator/actions_core.ml
|
||
+++ b/generator/actions_core.ml
|
||
@@ -9575,4 +9575,39 @@ The optional C<force> boolean controls whether the context
|
||
is reset for customizable files, and also whether the
|
||
user, role and range parts of the file context is changed.|} };
|
||
|
||
+ { defaults with
|
||
+ name = "xfs_info2"; added = (1, 59, 2);
|
||
+ style = RHashtable (RPlainString, RPlainString, "info"), [String (Dev_or_Path, "pathordevice")], [];
|
||
+ impl = OCaml "Xfs.xfs_info2";
|
||
+ optional = Some "xfs";
|
||
+ tests = [
|
||
+ InitEmpty, Always, TestResult (
|
||
+ [["part_disk"; "/dev/sda"; "mbr"];
|
||
+ ["mkfs"; "xfs"; "/dev/sda1"; ""; "NOARG"; ""; ""; "NOARG"];
|
||
+ ["mount"; "/dev/sda1"; "/"];
|
||
+ ["xfs_info2"; "/"]],
|
||
+ "check_hash (ret, \"data.bsize\", \"4096\") == 0"), []
|
||
+ ];
|
||
+ shortdesc = "get information about the XFS filesystem";
|
||
+ longdesc = {|C<pathordevice> is a mounted XFS filesystem or
|
||
+a device containing an XFS filesystem. This command returns
|
||
+miscellaneous metadata about the XFS filesystem.
|
||
+
|
||
+The output is a hash derived from the output of L<xfs_info(8)>,
|
||
+and generally looks like:
|
||
+
|
||
+ meta-data: /dev/sda1
|
||
+ meta-data.isize: 512
|
||
+ meta-data.agcount: 4
|
||
+ meta-data.agsize: 65528 blks
|
||
+ meta-data.sectsz: 512
|
||
+ meta-data.attr: 2
|
||
+ meta-data.projid32bit: 1
|
||
+ meta-data.crc: 1
|
||
+ [...]
|
||
+ data.bsize: 4096
|
||
+ data.blocks: 262112
|
||
+ [...]
|
||
+
|
||
+More information can be found by reading L<xfs_info(8)>.|} };
|
||
]
|
||
diff --git a/generator/proc_nr.ml b/generator/proc_nr.ml
|
||
index 11e7b9d1b..b22d88b58 100644
|
||
--- a/generator/proc_nr.ml
|
||
+++ b/generator/proc_nr.ml
|
||
@@ -524,6 +524,7 @@ let proc_nr = [
|
||
519, "setfiles";
|
||
520, "ntfs_chmod";
|
||
521, "inspect_get_windows_group_policy";
|
||
+522, "xfs_info2";
|
||
]
|
||
|
||
(* End of list. If adding a new entry, add it at the end of the list
|
||
diff --git a/lib/MAX_PROC_NR b/lib/MAX_PROC_NR
|
||
index 5a232f264..ec0e415d0 100644
|
||
--- a/lib/MAX_PROC_NR
|
||
+++ b/lib/MAX_PROC_NR
|
||
@@ -1 +1 @@
|
||
-521
|
||
+522
|
||
--
|
||
2.47.3
|
||
|