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
This commit is contained in:
Richard W.M. Jones
2026-01-23 08:58:07 +00:00
committed by rwmjones
parent 1c9c03bcd4
commit dfd2700616
7 changed files with 172 additions and 4 deletions

1
.gitignore vendored
View File

@@ -119,6 +119,7 @@ Makefile.in
/daemon/stubs-?.c /daemon/stubs-?.c
/daemon/stubs.h /daemon/stubs.h
/daemon/types.ml /daemon/types.ml
/daemon/xfs.mli
/depcomp /depcomp
/docs/guestfs-building.1 /docs/guestfs-building.1
/docs/guestfs-faq.1 /docs/guestfs-faq.1

2
common

Submodule common updated: b54ba2031a...3ac5d18419

View File

@@ -63,7 +63,8 @@ generator_built = \
sfdisk.mli \ sfdisk.mli \
statvfs.mli \ statvfs.mli \
structs.ml \ structs.ml \
structs.mli structs.mli \
xfs.mli
CONFIGURE_GENERATED_ML = \ CONFIGURE_GENERATED_ML = \
daemon_config.ml daemon_config.ml
@@ -312,7 +313,8 @@ SOURCES_MLI = \
statvfs.mli \ statvfs.mli \
structs.mli \ structs.mli \
sysroot.mli \ sysroot.mli \
utils.mli utils.mli \
xfs.mli
SOURCES_ML = \ SOURCES_ML = \
$(CONFIGURE_GENERATED_ML) \ $(CONFIGURE_GENERATED_ML) \
@@ -347,6 +349,7 @@ SOURCES_ML = \
realpath.ml \ realpath.ml \
statvfs.ml \ statvfs.ml \
selinux.ml \ selinux.ml \
xfs.ml \
inspect_types.ml \ inspect_types.ml \
inspect_utils.ml \ inspect_utils.ml \
inspect_fs_unix_fstab.ml \ inspect_fs_unix_fstab.ml \

128
daemon/xfs.ml Normal file
View File

@@ -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

View File

@@ -9609,4 +9609,39 @@ The optional C<force> boolean controls whether the context
is reset for customizable files, and also whether the is reset for customizable files, and also whether the
user, role and range parts of the file context is changed.|} }; 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)>.|} };
] ]

View File

@@ -524,6 +524,7 @@ let proc_nr = [
519, "setfiles"; 519, "setfiles";
520, "ntfs_chmod"; 520, "ntfs_chmod";
521, "inspect_get_windows_group_policy"; 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 (* End of list. If adding a new entry, add it at the end of the list

View File

@@ -1 +1 @@
521 522