Files
libguestfs/daemon/xfs.ml
Richard W.M. Jones dfd2700616 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
2026-01-26 14:40:39 +00:00

129 lines
4.4 KiB
OCaml

(* 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