diff --git a/0012-daemon-listfs.ml-Refactor-is_partition_can_hold_file.patch b/0012-daemon-listfs.ml-Refactor-is_partition_can_hold_file.patch new file mode 100644 index 0000000..30bfdc8 --- /dev/null +++ b/0012-daemon-listfs.ml-Refactor-is_partition_can_hold_file.patch @@ -0,0 +1,87 @@ +From 14313b0191d7a1e28b2ed1d923427ed544859e08 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Thu, 16 Apr 2026 08:06:50 +0100 +Subject: [PATCH] daemon/listfs.ml: Refactor is_partition_can_hold_filesystem + +Refactor and simplify the function. + +This is just code motion, there is no functional change. + +(cherry picked from commit 88bd07f350407619d4f5fe406c5594b50cf541dd) +--- + daemon/listfs.ml | 53 ++++++++++++++++++++---------------------------- + 1 file changed, 22 insertions(+), 31 deletions(-) + +diff --git a/daemon/listfs.ml b/daemon/listfs.ml +index 4c90796ef..067314c47 100644 +--- a/daemon/listfs.ml ++++ b/daemon/listfs.ml +@@ -115,43 +115,34 @@ and is_not_partitioned_device device = + * Windows Snapshot Partition as well as MBR extended partitions. + *) + and is_partition_can_hold_filesystem partition = +- let device = Devsparts.part_to_dev partition in +- let partnum = Devsparts.part_to_partnum partition in +- let parttype = Parted.part_get_parttype device in ++ let device = Devsparts.part_to_dev partition ++ and partnum = Devsparts.part_to_partnum partition in + +- let is_gpt = parttype = "gpt" in +- let is_mbr = parttype = "msdos" in +- let is_gpt_or_mbr = is_gpt || is_mbr in ++ match Parted.part_get_parttype device with ++ | "msdos" -> ++ if Parted.part_get_mbr_part_type device partnum = "extended" then ++ false ++ else if partnum = 1 && Utils.has_bogus_mbr device then ++ true ++ else ++ true + +- if is_gpt_or_mbr then ( +- if is_mbr_extended parttype device partnum then +- false +- else if is_mbr_bogus parttype device partnum then +- true +- else if is_mbr then +- true +- else ( +- let gpt_type = Sfdisk.part_get_gpt_type device partnum in +- match gpt_type with ++ | "gpt" -> ++ let gpt_type = Sfdisk.part_get_gpt_type device partnum in ++ (match gpt_type with + (* Windows Logical Disk Manager metadata partition. *) + | "5808C8AA-7E8F-42E0-85D2-E1E90434CFB3" +- (* Windows Logical Disk Manager data partition. *) +- | "AF9B60A0-1431-4F62-BC68-3311714A69AD" +- (* Microsoft Reserved Partition. *) +- | "E3C9E316-0B5C-4DB8-817D-F92DF00215AE" +- (* Windows Snapshot Partition. *) +- | "CADDEBF1-4400-4DE8-B103-12117DCF3CCF" -> false ++ (* Windows Logical Disk Manager data partition. *) ++ | "AF9B60A0-1431-4F62-BC68-3311714A69AD" ++ (* Microsoft Reserved Partition. *) ++ | "E3C9E316-0B5C-4DB8-817D-F92DF00215AE" ++ (* Windows Snapshot Partition. *) ++ | "CADDEBF1-4400-4DE8-B103-12117DCF3CCF" -> false + | _ -> true +- ) +- ) +- else true ++ ) + +-and is_mbr_extended parttype device partnum = +- parttype = "msdos" && +- Parted.part_get_mbr_part_type device partnum = "extended" +- +-and is_mbr_bogus parttype device partnum = +- parttype = "msdos" && partnum = 1 && Utils.has_bogus_mbr device ++ | _ -> (* unknown or other *) ++ true + + (* Use vfs-type to check for a filesystem of some sort of [device]. + * Appends (device, vfs_type) to the ret parameter (there may be +-- +2.47.3 + diff --git a/0013-daemon-listfs.ml-Ignore-CHS-geometry-error-from-part.patch b/0013-daemon-listfs.ml-Ignore-CHS-geometry-error-from-part.patch new file mode 100644 index 0000000..2a6d962 --- /dev/null +++ b/0013-daemon-listfs.ml-Ignore-CHS-geometry-error-from-part.patch @@ -0,0 +1,114 @@ +From 41cfc0423316acf42fd1122c9b345b49636d9214 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Thu, 16 Apr 2026 08:34:38 +0100 +Subject: [PATCH] daemon/listfs.ml: Ignore CHS geometry error from parted +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +parted has an annoying bug where it fails to parse Sun partition +tables (still used by Veritas). It tries to check the CHS geometry +stored in the header matches the physical CHS geometry, which is a +meaningless test. + +In function is_partition_can_hold_filesystem we are only interested in +MBR and GPT partition types, so catch and ignore this specific parted +error. + +I tested this by adding a Sun disk as a second disk to a Fedora +guest and doing inspection. Previously the operation would +fail: + + $ guestfish --ro -a /var/tmp/fedora-41.img -a /var/tmp/dump.img -i + libguestfs: error: inspect_os: parted exited with status 1: Warning: + The disk CHS geometry (205603,255,2) reported by the operating + system does not match the geometry stored on the disk label + (64887,16,101). + +After this change it succeeds: + + $ guestfish --ro -a /var/tmp/fedora-41.img -a /var/tmp/dump.img -i + + Welcome to guestfish, the guest filesystem shell for + editing virtual machine filesystems and disk images. + + Type: ‘help’ for help on commands + ‘man’ to read the manual + ‘quit’ to quit the shell + + Operating system: Fedora Linux 41 (Forty One) + /dev/sda3 mounted on / + /dev/sda2 mounted on /boot + +Fixes: https://redhat.atlassian.net/browse/RHEL-165220 +(cherry picked from commit 8d83bf2bcf31c5206b6deaa5e993009a85ff174f) +--- + daemon/listfs.ml | 44 ++++++++++++++++++++++++++++---------------- + 1 file changed, 28 insertions(+), 16 deletions(-) + +diff --git a/daemon/listfs.ml b/daemon/listfs.ml +index 067314c47..8ca5ca8a8 100644 +--- a/daemon/listfs.ml ++++ b/daemon/listfs.ml +@@ -118,30 +118,42 @@ and is_partition_can_hold_filesystem partition = + let device = Devsparts.part_to_dev partition + and partnum = Devsparts.part_to_partnum partition in + +- match Parted.part_get_parttype device with +- | "msdos" -> +- if Parted.part_get_mbr_part_type device partnum = "extended" then +- false +- else if partnum = 1 && Utils.has_bogus_mbr device then +- true +- else +- true ++ try ++ match Parted.part_get_parttype device with ++ | "msdos" -> ++ if Parted.part_get_mbr_part_type device partnum = "extended" then ++ false ++ else if partnum = 1 && Utils.has_bogus_mbr device then ++ true ++ else ++ true + +- | "gpt" -> +- let gpt_type = Sfdisk.part_get_gpt_type device partnum in +- (match gpt_type with +- (* Windows Logical Disk Manager metadata partition. *) +- | "5808C8AA-7E8F-42E0-85D2-E1E90434CFB3" ++ | "gpt" -> ++ let gpt_type = Sfdisk.part_get_gpt_type device partnum in ++ (match gpt_type with ++ (* Windows Logical Disk Manager metadata partition. *) ++ | "5808C8AA-7E8F-42E0-85D2-E1E90434CFB3" + (* Windows Logical Disk Manager data partition. *) + | "AF9B60A0-1431-4F62-BC68-3311714A69AD" + (* Microsoft Reserved Partition. *) + | "E3C9E316-0B5C-4DB8-817D-F92DF00215AE" + (* Windows Snapshot Partition. *) + | "CADDEBF1-4400-4DE8-B103-12117DCF3CCF" -> false +- | _ -> true +- ) ++ | _ -> true ++ ) + +- | _ -> (* unknown or other *) ++ | _ -> (* unknown or other *) ++ true ++ ++ with ++ | Failure msg when String.find msg "CHS geometry" >= 0 -> ++ (* Parted has poor handling of "sun" partition types, always ++ * issuing a warning because the CHS doesn't match the physical ++ * geometry. Ignore this as we don't care about it in this ++ * function (RHEL-165220). ++ *) ++ eprintf "is_partition_can_hold_filesystem: \ ++ ignoring warning from parted: %s\n%!" msg; + true + + (* Use vfs-type to check for a filesystem of some sort of [device]. +-- +2.47.3 + diff --git a/0014-daemon-Move-read_whole_file-to-common-utils.patch b/0014-daemon-Move-read_whole_file-to-common-utils.patch new file mode 100644 index 0000000..9c2f6fe --- /dev/null +++ b/0014-daemon-Move-read_whole_file-to-common-utils.patch @@ -0,0 +1,684 @@ +From 1308ee6c911cdaaa9ed15e9fb45a0c59d45c2490 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 28 Apr 2026 11:51:30 +0100 +Subject: [PATCH] daemon: Move read_whole_file to common/utils + +Remove our read_whole_file function. A similar function will be added +to common/utils which we will use instead. + +Update common submodule, pulling in: + + Richard W.M. Jones (2): + mldrivers/firmware.ml: Ignore CHS geometry error from parted + utils: Add read_whole_file function + + Srihari Parimi (2): + mltools: add prefix to debug () output + mltools: for external commands include function name in debug() output + + Susant Sahani (1): + smp: respect cgroup v2 CPU limits for appliance SMP + +(cherry picked from commit 76f590de0626ccc10d8aaecc1a7d9761189b6117) +--- + common | 2 +- + daemon/daemon.h | 1 - + daemon/ntfsclone.c | 3 +-- + daemon/tar.c | 3 +-- + daemon/utils.c | 58 ---------------------------------------------- + 5 files changed, 3 insertions(+), 64 deletions(-) + +Submodule common 3ac5d1841..800510306: +diff --git a/common/edit/file-edit.c b/common/edit/file-edit.c +index ef56ed1..a10d61c 100644 +--- a/common/edit/file-edit.c ++++ b/common/edit/file-edit.c +@@ -119,7 +119,7 @@ edit_file_editor (guestfs_h *g, const char *filename, const char *editor, + fprintf (stderr, "%s\n", cmd); + + r = system (cmd); +- if (r == -1 || WEXITSTATUS (r) != 0) { ++ if (r == -1 || !WIFEXITED (r) || WEXITSTATUS (r) != 0) { + perror (cmd); + return -1; + } +@@ -194,7 +194,7 @@ edit_file_perl (guestfs_h *g, const char *filename, const char *perl_expr, + fprintf (stderr, "%s\n", cmd); + + r = system (cmd); +- if (r == -1 || WEXITSTATUS (r) != 0) ++ if (r == -1 || !WIFEXITED (r) || WEXITSTATUS (r) != 0) + return -1; + + if (rename (outfile, tmpfilename) == -1) { +diff --git a/common/mlpcre/Makefile.am b/common/mlpcre/Makefile.am +index a1d8b02..30a60f6 100644 +--- a/common/mlpcre/Makefile.am ++++ b/common/mlpcre/Makefile.am +@@ -83,7 +83,7 @@ endif + libmlpcre_a_DEPENDENCIES = $(OBJECTS) + + $(MLPCRE_CMA): $(OBJECTS) libmlpcre.a +- $(OCAMLFIND) mklib $(OCAMLPACKAGES) \ ++ $(AM_V_GEN) $(OCAMLFIND) mklib $(OCAMLPACKAGES) \ + $(OBJECTS) $(libmlpcre_a_OBJECTS) -cclib -lpcre2-8 -o mlpcre + + # Tests. +diff --git a/common/mlstdutils/Makefile.am b/common/mlstdutils/Makefile.am +index b9632b0..0f88190 100644 +--- a/common/mlstdutils/Makefile.am ++++ b/common/mlstdutils/Makefile.am +@@ -84,11 +84,11 @@ endif + libmlstdutils_a_DEPENDENCIES = $(OBJECTS) + + mlstdutils.cma: $(BOBJECTS) +- $(OCAMLFIND) ocamlc $(OCAMLPACKAGES) -a $^ -o $@ ++ $(AM_V_GEN) $(OCAMLFIND) ocamlc $(OCAMLPACKAGES) -a $^ -o $@ + + if HAVE_OCAMLOPT + mlstdutils.cmxa: $(XOBJECTS) +- $(OCAMLFIND) ocamlopt $(OCAMLPACKAGES) -a $^ -o $@ ++ $(AM_V_GEN) $(OCAMLFIND) ocamlopt $(OCAMLPACKAGES) -a $^ -o $@ + endif + + # Tests. +diff --git a/common/mlstdutils/std_utils.mli b/common/mlstdutils/std_utils.mli +index 6c1911d..77cf107 100644 +--- a/common/mlstdutils/std_utils.mli ++++ b/common/mlstdutils/std_utils.mli +@@ -51,6 +51,7 @@ module List : sig + val find_all : ('a -> bool) -> 'a list -> 'a list + val partition : ('a -> bool) -> 'a list -> 'a list * 'a list + val assoc : 'a -> ('a * 'b) list -> 'b ++ val assoc_opt : 'a -> ('a * 'b) list -> 'b option + val assq : 'a -> ('a * 'b) list -> 'b + val mem_assoc : 'a -> ('a * 'b) list -> bool + val mem_assq : 'a -> ('a * 'b) list -> bool +diff --git a/common/mlutils/Makefile.am b/common/mlutils/Makefile.am +index d52cb9c..084ce63 100644 +--- a/common/mlutils/Makefile.am ++++ b/common/mlutils/Makefile.am +@@ -86,7 +86,7 @@ endif + libmlcutils_a_DEPENDENCIES = $(OBJECTS) + + $(MLCUTILS_CMA): $(OBJECTS) libmlcutils.a +- $(OCAMLFIND) mklib $(OCAMLPACKAGES) \ ++ $(AM_V_GEN) $(OCAMLFIND) mklib $(OCAMLPACKAGES) \ + $(OBJECTS) $(libmlcutils_a_OBJECTS) \ + -cclib -lutils \ + -o mlcutils +diff --git a/common/mlutils/c_utils-c.c b/common/mlutils/c_utils-c.c +index d9c1a48..f0e2798 100644 +--- a/common/mlutils/c_utils-c.c ++++ b/common/mlutils/c_utils-c.c +@@ -68,7 +68,7 @@ guestfs_int_mlutils_shell_unquote (value strv) + + ret = guestfs_int_shell_unquote (String_val (strv)); + if (ret == NULL) +- unix_error (errno, (char *) "guestfs_int_shell_unquote", Nothing); ++ caml_unix_error (errno, (char *) "guestfs_int_shell_unquote", Nothing); + + retv = caml_copy_string (ret); + free (ret); +@@ -101,6 +101,8 @@ guestfs_int_mlutils_full_path (value dirv, value namev) + name = String_val (Field (namev, 0)); + + ret = guestfs_int_full_path (String_val (dirv), name); ++ if (ret == NULL) ++ caml_unix_error (errno, (char *) "guestfs_int_full_path", dirv); + rv = caml_copy_string (ret); + free (ret); + +diff --git a/common/mlutils/unix_utils-c.c b/common/mlutils/unix_utils-c.c +index ee5a379..919f526 100644 +--- a/common/mlutils/unix_utils-c.c ++++ b/common/mlutils/unix_utils-c.c +@@ -65,6 +65,8 @@ + #include + #include + ++#include "guestfs-utils.h" ++ + extern value guestfs_int_mllib_dev_t_makedev (value majv, value minv); + extern value guestfs_int_mllib_dev_t_major (value devv); + extern value guestfs_int_mllib_dev_t_minor (value devv); +@@ -147,7 +149,7 @@ guestfs_int_mllib_fnmatch (value patternv, value strv, value flagsv) + /* XXX The fnmatch specification doesn't mention what errors can + * be returned by fnmatch. Assume they are errnos for now. + */ +- unix_error (errno, (char *) "fnmatch", patternv); ++ caml_unix_error (errno, (char *) "fnmatch", patternv); + } + } + +@@ -180,16 +182,16 @@ guestfs_int_mllib_fsync_file (value filenamev) + /* Note to do fsync you have to open for write. */ + fd = open (filename, O_RDWR); + if (fd == -1) +- unix_error (errno, (char *) "open", filenamev); ++ caml_unix_error (errno, (char *) "open", filenamev); + + if (fsync (fd) == -1) { + err = errno; + close (fd); +- unix_error (err, (char *) "fsync", filenamev); ++ caml_unix_error (err, (char *) "fsync", filenamev); + } + + if (close (fd) == -1) +- unix_error (errno, (char *) "close", filenamev); ++ caml_unix_error (errno, (char *) "close", filenamev); + + CAMLreturn (Val_unit); + } +@@ -203,11 +205,14 @@ guestfs_int_mllib_mkdtemp (value val_pattern) + + pattern = strdup (String_val (val_pattern)); + if (pattern == NULL) +- unix_error (errno, (char *) "strdup", val_pattern); ++ caml_unix_error (errno, (char *) "strdup", val_pattern); + + ret = mkdtemp (pattern); +- if (ret == NULL) +- unix_error (errno, (char *) "mkdtemp", val_pattern); ++ if (ret == NULL) { ++ int err = errno; ++ free (pattern); ++ caml_unix_error (err, (char *) "mkdtemp", val_pattern); ++ } + + rv = caml_copy_string (ret); + free (pattern); +@@ -224,7 +229,7 @@ guestfs_int_mllib_realpath (value pathv) + + r = realpath (String_val (pathv), NULL); + if (r == NULL) +- unix_error (errno, (char *) "realpath", pathv); ++ caml_unix_error (errno, (char *) "realpath", pathv); + + rv = caml_copy_string (r); + free (r); +@@ -252,7 +257,7 @@ guestfs_int_mllib_statvfs_statvfs (value pathv) + struct statvfs buf; + + if (statvfs (String_val (pathv), &buf) == -1) +- unix_error (errno, (char *) "statvfs", pathv); ++ caml_unix_error (errno, (char *) "statvfs", pathv); + + f_bsize = buf.f_bsize; + f_frsize = buf.f_frsize; +@@ -276,7 +281,7 @@ guestfs_int_mllib_statvfs_statvfs (value pathv) + (PULARGE_INTEGER) &free_bytes_available, + (PULARGE_INTEGER) &total_number_of_bytes, + (PULARGE_INTEGER) &total_number_of_free_bytes)) +- unix_error (EIO, (char *) "statvfs: GetDiskFreeSpaceEx", pathv); ++ caml_unix_error (EIO, (char *) "statvfs: GetDiskFreeSpaceEx", pathv); + + /* XXX I couldn't determine how to get block size. MSDN has a + * unhelpful hard-coded list here: +@@ -341,15 +346,17 @@ guestfs_int_mllib_statvfs_statvfs (value pathv) + CAMLreturn (rv); + } + +-/* NB: This is a [@@noalloc] call. */ + value + guestfs_int_mllib_statvfs_is_network_filesystem (value pathv) + { ++ CAMLparam1 (pathv); ++ CAMLlocal1 (rv); ++ + #ifdef HAVE_STATFS + struct statfs buf; + + if (statfs (String_val (pathv), &buf) == -1) +- unix_error (errno, (char *) "statvfs", pathv); ++ caml_unix_error (errno, (char *) "statvfs", pathv); + + /* Some but not all of these are defined in . */ + #ifndef CIFS_MAGIC_NUMBER +@@ -362,12 +369,14 @@ guestfs_int_mllib_statvfs_is_network_filesystem (value pathv) + #define SMB_SUPER_MAGIC 0x517b + #endif + +- return Val_bool ((unsigned int) buf.f_type == CIFS_MAGIC_NUMBER || +- (unsigned int) buf.f_type == NFS_SUPER_MAGIC || +- (unsigned int) buf.f_type == SMB_SUPER_MAGIC); ++ rv = Val_bool ((unsigned int) buf.f_type == CIFS_MAGIC_NUMBER || ++ (unsigned int) buf.f_type == NFS_SUPER_MAGIC || ++ (unsigned int) buf.f_type == SMB_SUPER_MAGIC); + #else +- return Val_bool (0); ++ rv = Val_bool (0); + #endif ++ ++ CAMLreturn (rv); + } + + /* NB: This is a [@@noalloc] call. */ +diff --git a/common/mlutils/unix_utils.ml b/common/mlutils/unix_utils.ml +index b79144a..9d7e11a 100644 +--- a/common/mlutils/unix_utils.ml ++++ b/common/mlutils/unix_utils.ml +@@ -82,10 +82,35 @@ module StatVFS = struct + let free_space { f_bsize = bsize; f_bavail = bavail } = bsize *^ bavail + + external is_network_filesystem : string -> bool = +- "guestfs_int_mllib_statvfs_is_network_filesystem" [@@noalloc] ++ "guestfs_int_mllib_statvfs_is_network_filesystem" + end + + module Sysconf = struct + external nr_processors_online : unit -> int = + "guestfs_int_mllib_sysconf_nr_processors_online" [@@noalloc] + end ++ ++module Cgroup = struct ++ let v2_cpus () = ++ let file = "/sys/fs/cgroup/cpu.max" in ++ if Sys.file_exists file then ++ try ++ let line = read_first_line_from_file file in ++ if String.starts_with ~prefix:"max" line then ++ None ++ else ++ let quota, period = ++ Scanf.sscanf line "%Ld %Ld" (fun q p -> (q, p)) in ++ if period > 0L then ++ Some (max 1 (Int64.to_int (Int64.div quota period))) ++ else None ++ with ++ | Scanf.Scan_failure _ | Failure _ | End_of_file -> None ++ else ++ None ++ ++ let nr_cpus_available () = ++ match v2_cpus () with ++ | Some cpus -> cpus ++ | None -> Sysconf.nr_processors_online () ++end +diff --git a/common/mlutils/unix_utils.mli b/common/mlutils/unix_utils.mli +index aead4df..cb4f042 100644 +--- a/common/mlutils/unix_utils.mli ++++ b/common/mlutils/unix_utils.mli +@@ -130,3 +130,21 @@ module Sysconf : sig + Note this never fails. In case we cannot get the number of + cores it returns 1. *) + end ++ ++module Cgroup : sig ++ (** Functions to read CPU limits from cgroup filesystems. *) ++ ++ val v2_cpus : unit -> int option ++ (** [v2_cpus ()] reads the cgroup v2 CPU quota from ++ [/sys/fs/cgroup/cpu.max] and returns the number of CPUs ++ allocated (quota / period), or [None] if the file does not ++ exist, the quota is "max" (unlimited), or the file cannot ++ be parsed. The kernel stores quota and period as [long] ++ values in microseconds, so we parse them as [Int64]. *) ++ ++ val nr_cpus_available : unit -> int ++ (** [nr_cpus_available ()] returns the number of CPUs available, ++ taking into account cgroup v2 CPU limits. ++ Falls back to {!Sysconf.nr_processors_online} if no cgroup ++ limits are set. *) ++end +diff --git a/common/options/keys.c b/common/options/keys.c +index 7027104..b8f19ce 100644 +--- a/common/options/keys.c ++++ b/common/options/keys.c +@@ -78,6 +78,7 @@ read_key (const char *param) + len = getline (&ret, &allocsize, infp); + if (len == -1) { + perror ("getline"); ++ free (ret); + ret = NULL; + goto error; + } +diff --git a/common/parallel/parallel.c b/common/parallel/parallel.c +index 18b0607..88ad819 100644 +--- a/common/parallel/parallel.c ++++ b/common/parallel/parallel.c +@@ -221,6 +221,7 @@ worker_thread (void *thread_data_vp) + g = guestfs_create (); + if (g == NULL) { + perror ("guestfs_create"); ++ fclose (fp); + thread_data->r = -1; + return &thread_data->r; + } +diff --git a/common/qemuopts/qemuopts.c b/common/qemuopts/qemuopts.c +index 7dd9136..e351465 100644 +--- a/common/qemuopts/qemuopts.c ++++ b/common/qemuopts/qemuopts.c +@@ -756,6 +756,11 @@ qemuopts_to_channel (struct qemuopts *qopts, FILE *fp) + } + fputc ('\n', fp); + ++ if (ferror (fp)) { ++ errno = EIO; ++ return -1; ++ } ++ + return 0; + } + +@@ -1035,5 +1040,10 @@ qemuopts_to_config_channel (struct qemuopts *qopts, FILE *fp) + fprintf (fp, "\n"); + } + ++ if (ferror (fp)) { ++ errno = EIO; ++ return -1; ++ } ++ + return 0; + } +diff --git a/common/update-submodule.sh b/common/update-submodule.sh +new file mode 100755 +index 0000000..ecf22f1 +--- /dev/null ++++ b/common/update-submodule.sh +@@ -0,0 +1,52 @@ ++#!/bin/bash ++# (C) Copyright 2026 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., 675 Mass Ave, Cambridge, MA 02139, USA. ++ ++# Generate a submodule update commit, with formatted shortlog output. ++# Run this from the parent repo like: ./common/update-submodule.sh ++# Then `git commit --amend ...` as desired. ++ ++set -euo pipefail ++ ++if ! git submodule status common &>/dev/null; then ++ echo "Error: this script must be run from the top directory of a repo with a 'common/' submodule" >&2 ++ exit 1 ++fi ++ ++if ! git diff --quiet --exit-code; then ++ echo "Error: working tree has uncommitted changes" >&2 ++ exit 1 ++fi ++ ++echo "Running: git submodule update --remote common" ++git submodule update --remote common ++ ++if git diff --quiet --exit-code; then ++ echo "'common' submodule already up to date" ++ exit 0 ++fi ++ ++OLD_COMMIT=$(git ls-tree HEAD common | awk '{print $3}') ++NEW_COMMIT=$(git -C common rev-parse HEAD) ++echo "Old 'common' commit: $OLD_COMMIT" ++echo "New 'common' commit: $NEW_COMMIT" ++ ++SHORTLOG=$(git -C common shortlog --no-merges "${OLD_COMMIT}..${NEW_COMMIT}" | sed '/^$/!s/^/ /') ++git commit -F - common < ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "guestfs-utils.h" ++ ++/** ++ * Read the whole file C into a memory buffer. ++ * ++ * The memory buffer is initialized and returned in C. The ++ * size of the file in bytes is returned in C. The return ++ * buffer must be freed by the caller. ++ * ++ * On error this prints an error on C and returns -1. Unlike ++ * the similar C this does not use the ++ * libguestfs handle or call C. ++ * ++ * For the convenience of callers, the returned buffer is ++ * NUL-terminated (the NUL is not included in the size). ++ * ++ * The file must be a B, B, B file. In ++ * particular, do not use this function to read files that might be ++ * under control of an untrusted user since that will lead to a ++ * denial-of-service attack. ++ */ ++int ++read_whole_file (const char *filename, char **data_r, size_t *size_r) ++{ ++ int fd; ++ char *data; ++ off_t size; ++ off_t n; ++ ssize_t r; ++ struct stat statbuf; ++ ++ fd = open (filename, O_RDONLY|O_CLOEXEC); ++ if (fd == -1) { ++ perror (filename); ++ return -1; ++ } ++ ++ if (fstat (fd, &statbuf) == -1) { ++ perror (filename); ++ close (fd); ++ return -1; ++ } ++ ++ size = statbuf.st_size; ++ data = malloc (size + 1); ++ if (data == NULL) { ++ perror ("malloc"); ++ close (fd); ++ return -1; ++ } ++ ++ n = 0; ++ while (n < size) { ++ r = read (fd, &data[n], size - n); ++ if (r == -1) { ++ perror (filename); ++ free (data); ++ close (fd); ++ return -1; ++ } ++ if (r == 0) { ++ fprintf (stderr, "%s: unexpected end of input", filename); ++ free (data); ++ close (fd); ++ return -1; ++ } ++ n += r; ++ } ++ ++ if (close (fd) == -1) { ++ perror (filename); ++ free (data); ++ return -1; ++ } ++ ++ /* For convenience of callers, \0-terminate the data. */ ++ data[size] = '\0'; ++ ++ *data_r = data; ++ if (size_r != NULL) ++ *size_r = size; ++ ++ return 0; ++} +diff --git a/daemon/daemon.h b/daemon/daemon.h +index 72b9e97c0..d3ed90924 100644 +--- a/daemon/daemon.h ++++ b/daemon/daemon.h +@@ -87,7 +87,6 @@ extern void udev_settle (void); + extern int random_name (char *template); + extern char *get_random_uuid (void); + extern char *make_exclude_from_file (const char *function, char *const *excludes); +-extern char *read_whole_file (const char *filename, size_t *size_r); + + /* mountable functions (in utils.c) */ + extern char *mountable_to_string (const mountable_t *mountable); +diff --git a/daemon/ntfsclone.c b/daemon/ntfsclone.c +index e9a4c3ea9..3a3e9a7a7 100644 +--- a/daemon/ntfsclone.c ++++ b/daemon/ntfsclone.c +@@ -39,8 +39,7 @@ read_error_file (char *error_file) + size_t len; + char *str; + +- str = read_whole_file (error_file, &len); +- if (str == NULL) { ++ if (read_whole_file (error_file, &str, &len) == -1) { + str = strdup ("(no error)"); + if (str == NULL) + error (EXIT_FAILURE, errno, "strdup"); /* XXX */ +diff --git a/daemon/tar.c b/daemon/tar.c +index 1d6a4b7eb..faf46051b 100644 +--- a/daemon/tar.c ++++ b/daemon/tar.c +@@ -108,8 +108,7 @@ read_error_file (char *error_file) + size_t len; + char *str; + +- str = read_whole_file (error_file, &len); +- if (str == NULL) { ++ if (read_whole_file (error_file, &str, &len) == -1) { + str = strdup ("(no error)"); + if (str == NULL) + error (EXIT_FAILURE, errno, "strdup"); /* XXX */ +diff --git a/daemon/utils.c b/daemon/utils.c +index 2da33b9aa..d359d3c3b 100644 +--- a/daemon/utils.c ++++ b/daemon/utils.c +@@ -845,61 +845,3 @@ cleanup_free_mountable (mountable_t *mountable) + free (mountable->volume); + } + } +- +-/** +- * Read whole file into dynamically allocated array. If there is an +- * error, DON'T call reply_with_perror, just return NULL. Returns a +- * C<\0>-terminated string. C can be specified to get the +- * size of the returned data. +- */ +-char * +-read_whole_file (const char *filename, size_t *size_r) +-{ +- char *r = NULL; +- size_t alloc = 0, size = 0; +- int fd; +- +- fd = open (filename, O_RDONLY|O_CLOEXEC); +- if (fd == -1) { +- perror (filename); +- return NULL; +- } +- +- while (1) { +- alloc += 256; +- char *r2 = realloc (r, alloc); +- if (r2 == NULL) { +- perror ("realloc"); +- free (r); +- close (fd); +- return NULL; +- } +- r = r2; +- +- /* The '- 1' in the size calculation ensures there is space below +- * to add \0 to the end of the input. +- */ +- ssize_t n = read (fd, r + size, alloc - size - 1); +- if (n == -1) { +- fprintf (stderr, "read: %s: %m\n", filename); +- free (r); +- close (fd); +- return NULL; +- } +- if (n == 0) +- break; +- size += n; +- } +- +- if (close (fd) == -1) { +- fprintf (stderr, "close: %s: %m\n", filename); +- free (r); +- return NULL; +- } +- +- r[size] = '\0'; +- if (size_r != NULL) +- *size_r = size; +- +- return r; +-} +-- +2.47.3 + diff --git a/0015-generator-Adjust-comment-for-String-Key.patch b/0015-generator-Adjust-comment-for-String-Key.patch new file mode 100644 index 0000000..0360d9a --- /dev/null +++ b/0015-generator-Adjust-comment-for-String-Key.patch @@ -0,0 +1,40 @@ +From 84bc5dc2c9c9c82a27270e8068e224af51b7ac89 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 28 Apr 2026 09:18:27 +0100 +Subject: [PATCH] generator: Adjust comment for String(Key) + +Just tidy up the comment. + +(cherry picked from commit f2b1d9de9b2adaf9e372438f27da710d243e628d) +--- + generator/types.mli | 14 ++++++++------ + 1 file changed, 8 insertions(+), 6 deletions(-) + +diff --git a/generator/types.mli b/generator/types.mli +index 0e1b89846..01f00044e 100644 +--- a/generator/types.mli ++++ b/generator/types.mli +@@ -185,12 +185,14 @@ and stringt = + stdin or write to stdout. *) + + | Key +- (** Key material / passphrase. Eventually we should treat this +- as sensitive and mlock it into physical RAM. However this +- is highly complex because of all the places that XDR-encoded +- strings can end up. So currently the only difference from +- 'PlainString' is the way that guestfish requests these +- parameters from the user. *) ++ (** Key material / passphrase. ++ ++ Currently the only difference from 'PlainString' is the way ++ that guestfish requests these parameters from the user. ++ ++ Eventually we should treat this as sensitive and mlock it ++ into physical RAM. This is highly complex because of all ++ the places that XDR-encoded strings can end up. *) + + | GUID + (** A GUID string. +-- +2.47.3 + diff --git a/0016-daemon-Move-some-deprecated-functions-to-new-Luks-mo.patch b/0016-daemon-Move-some-deprecated-functions-to-new-Luks-mo.patch new file mode 100644 index 0000000..060c5eb --- /dev/null +++ b/0016-daemon-Move-some-deprecated-functions-to-new-Luks-mo.patch @@ -0,0 +1,151 @@ +From 9ad80afc0c79311c7d67b4dd67aa80a41e039263 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 28 Apr 2026 09:36:18 +0100 +Subject: [PATCH] daemon: Move some deprecated functions to new Luks module + +No change, just code movement. + +(cherry picked from commit 5e7aef40c2e9a81f5ad3f8c2e1ff1389e7825efb) +--- + .gitignore | 1 + + daemon/Makefile.am | 3 +++ + daemon/cryptsetup.ml | 9 +------- + daemon/luks.ml | 32 ++++++++++++++++++++++++++++ + generator/actions_core_deprecated.ml | 6 +++--- + 5 files changed, 40 insertions(+), 11 deletions(-) + create mode 100644 daemon/luks.ml + +diff --git a/.gitignore b/.gitignore +index c8a8312fa..7715d2cfc 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -95,6 +95,7 @@ Makefile.in + /daemon/ldm.mli + /daemon/link.mli + /daemon/listfs.mli ++/daemon/luks.mli + /daemon/lvm.mli + /daemon/lvm_dm.mli + /daemon/lvm_full.mli +diff --git a/daemon/Makefile.am b/daemon/Makefile.am +index d4a805046..6f3b9e7d5 100644 +--- a/daemon/Makefile.am ++++ b/daemon/Makefile.am +@@ -49,6 +49,7 @@ generator_built = \ + ldm.mli \ + link.mli \ + listfs.mli \ ++ luks.mli \ + lvm.mli \ + lvm_dm.mli \ + lvm_full.mli \ +@@ -296,6 +297,7 @@ SOURCES_MLI = \ + ldm.mli \ + link.mli \ + listfs.mli \ ++ luks.mli \ + lvm.mli \ + lvm_dm.mli \ + lvm_full.mli \ +@@ -327,6 +329,7 @@ SOURCES_ML = \ + blkid.ml \ + btrfs.ml \ + cryptsetup.ml \ ++ luks.ml \ + devsparts.ml \ + file_helper.ml \ + file.ml \ +diff --git a/daemon/cryptsetup.ml b/daemon/cryptsetup.ml +index 6f677aef0..002e0993a 100644 +--- a/daemon/cryptsetup.ml ++++ b/daemon/cryptsetup.ml +@@ -1,5 +1,5 @@ + (* guestfs-inspection +- * Copyright (C) 2009-2025 Red Hat Inc. ++ * Copyright (C) 2009-2026 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 +@@ -70,10 +70,3 @@ let cryptsetup_close device = + ignore (command "cryptsetup" ["close"; mapname]); + + udev_settle () +- +-(* Deprecated APIs for backwards compatibility. *) +-let luks_open device key mapname = +- cryptsetup_open ~crypttype:"luks" device key mapname +-let luks_open_ro device key mapname = +- cryptsetup_open ~crypttype:"luks" ~readonly:true device key mapname +-let luks_close = cryptsetup_close +diff --git a/daemon/luks.ml b/daemon/luks.ml +new file mode 100644 +index 000000000..e7e6deefe +--- /dev/null ++++ b/daemon/luks.ml +@@ -0,0 +1,32 @@ ++(* guestfs-inspection ++ * Copyright (C) 2009-2026 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 Unix ++ ++open Std_utils ++ ++open Utils ++ ++(* Deprecated APIs for backwards compatibility. *) ++let luks_open device key mapname = ++ Cryptsetup.cryptsetup_open ~crypttype:"luks" device key mapname ++let luks_open_ro device key mapname = ++ Cryptsetup.cryptsetup_open ~crypttype:"luks" ~readonly:true device key mapname ++let luks_close = ++ Cryptsetup.cryptsetup_close +diff --git a/generator/actions_core_deprecated.ml b/generator/actions_core_deprecated.ml +index 308495c45..d930b8b88 100644 +--- a/generator/actions_core_deprecated.ml ++++ b/generator/actions_core_deprecated.ml +@@ -843,7 +843,7 @@ physical volumes, volume groups and logical volumes." }; + { defaults with + name = "luks_open"; added = (1, 5, 1); + style = RErr, [String (Device, "device"); String (Key, "key"); String (PlainString, "mapname")], []; +- impl = OCaml "Cryptsetup.luks_open"; ++ impl = OCaml "Luks.luks_open"; + optional = Some "luks"; + deprecated_by = Replaced_by "cryptsetup_open"; + shortdesc = "open a LUKS-encrypted block device"; +@@ -869,7 +869,7 @@ devices.|} }; + { defaults with + name = "luks_open_ro"; added = (1, 5, 1); + style = RErr, [String (Device, "device"); String (Key, "key"); String (PlainString, "mapname")], []; +- impl = OCaml "Cryptsetup.luks_open_ro"; ++ impl = OCaml "Luks.luks_open_ro"; + optional = Some "luks"; + deprecated_by = Replaced_by "cryptsetup_open"; + shortdesc = "open a LUKS-encrypted block device read-only"; +@@ -880,7 +880,7 @@ mapping is created." }; + { defaults with + name = "luks_close"; added = (1, 5, 1); + style = RErr, [String (Device, "device")], []; +- impl = OCaml "Cryptsetup.luks_close"; ++ impl = OCaml "Luks.luks_close"; + optional = Some "luks"; + deprecated_by = Replaced_by "cryptsetup_close"; + shortdesc = "close a LUKS device"; +-- +2.47.3 + diff --git a/0017-daemon-Rewrite-some-luks_-APIs-in-OCaml.patch b/0017-daemon-Rewrite-some-luks_-APIs-in-OCaml.patch new file mode 100644 index 0000000..3a5d83e --- /dev/null +++ b/0017-daemon-Rewrite-some-luks_-APIs-in-OCaml.patch @@ -0,0 +1,323 @@ +From fcc511920866437fbe71739f0c170e377e18f73e Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 28 Apr 2026 09:57:27 +0100 +Subject: [PATCH] daemon: Rewrite some luks_* APIs in OCaml + +No change. Just rewriting these to so that all functions that handle +cryptsetup Key parameters are written in OCaml, so we can use a common +function in future for handling base64. + +Note that the previous versions of these functions ignored the case of +cryptsetup exiting with a non-zero error code, as does existing +Cryptsetup.cryptsetup_open. Unclear if this is right or not, but this +change keeps the same behaviour. + +(cherry picked from commit ec3548af1e6c75e021e49887fd27efa9262be98b) +--- + daemon/luks.c | 182 -------------------------------------- + daemon/luks.ml | 59 ++++++++++++ + generator/actions_core.ml | 4 + + 3 files changed, 63 insertions(+), 182 deletions(-) + +diff --git a/daemon/luks.c b/daemon/luks.c +index 9d120b0eb..db061fe66 100644 +--- a/daemon/luks.c ++++ b/daemon/luks.c +@@ -34,188 +34,6 @@ optgroup_luks_available (void) + return prog_exists ("cryptsetup"); + } + +-#pragma GCC diagnostic push +-#pragma GCC diagnostic ignored "-Wanalyzer-possible-null-argument" +-/* Callers must also call remove_temp (tempfile). */ +-static char * +-write_key_to_temp (const char *key) +-{ +- char *tempfile; +- int fd; +- size_t len; +- +- tempfile = strdup ("/tmp/luksXXXXXX"); +- if (!tempfile) { +- reply_with_perror ("strdup"); +- return NULL; +- } +- +- fd = mkstemp (tempfile); +- if (fd == -1) { +- reply_with_perror ("mkstemp"); +- goto error; +- } +- +- len = strlen (key); +- if (xwrite (fd, key, len) == -1) { +- reply_with_perror ("write"); +- close (fd); +- goto error; +- } +- +- if (close (fd) == -1) { +- reply_with_perror ("close"); +- goto error; +- } +- +- return tempfile; +- +- error: +- unlink (tempfile); +- free (tempfile); +- return NULL; +-} +-#pragma GCC diagnostic pop +- +-#pragma GCC diagnostic push +-#pragma GCC diagnostic ignored "-Wanalyzer-double-free" +-static void +-remove_temp (char *tempfile) +-{ +- unlink (tempfile); +- free (tempfile); +-} +-#pragma GCC diagnostic pop +- +-static int +-luks_format (const char *device, const char *key, int keyslot, +- const char *cipher) +-{ +- char *tempfile = write_key_to_temp (key); +- if (!tempfile) +- return -1; +- +- const char *argv[MAX_ARGS]; +- char keyslot_s[16]; +- size_t i = 0; +- +- ADD_ARG (argv, i, "cryptsetup"); +- ADD_ARG (argv, i, "-q"); +- if (cipher) { +- ADD_ARG (argv, i, "--cipher"); +- ADD_ARG (argv, i, cipher); +- } +- ADD_ARG (argv, i, "--key-slot"); +- snprintf (keyslot_s, sizeof keyslot_s, "%d", keyslot); +- ADD_ARG (argv, i, keyslot_s); +- ADD_ARG (argv, i, "luksFormat"); +- ADD_ARG (argv, i, device); +- ADD_ARG (argv, i, tempfile); +- ADD_ARG (argv, i, NULL); +- +- CLEANUP_FREE char *err = NULL; +- int r = commandv (NULL, &err, (const char * const *) argv); +- remove_temp (tempfile); +- +- if (r == -1) { +- reply_with_error ("%s", err); +- return -1; +- } +- +- udev_settle (); +- +- return 0; +-} +- +-int +-do_luks_format (const char *device, const char *key, int keyslot) +-{ +- return luks_format (device, key, keyslot, NULL); +-} +- +-int +-do_luks_format_cipher (const char *device, const char *key, int keyslot, +- const char *cipher) +-{ +- return luks_format (device, key, keyslot, cipher); +-} +- +-int +-do_luks_add_key (const char *device, const char *key, const char *newkey, +- int keyslot) +-{ +- char *keyfile = write_key_to_temp (key); +- if (!keyfile) +- return -1; +- +- char *newkeyfile = write_key_to_temp (newkey); +- if (!newkeyfile) { +- remove_temp (keyfile); +- return -1; +- } +- +- const char *argv[MAX_ARGS]; +- char keyslot_s[16]; +- size_t i = 0; +- +- ADD_ARG (argv, i, "cryptsetup"); +- ADD_ARG (argv, i, "-q"); +- ADD_ARG (argv, i, "-d"); +- ADD_ARG (argv, i, keyfile); +- ADD_ARG (argv, i, "--key-slot"); +- snprintf (keyslot_s, sizeof keyslot_s, "%d", keyslot); +- ADD_ARG (argv, i, keyslot_s); +- ADD_ARG (argv, i, "luksAddKey"); +- ADD_ARG (argv, i, device); +- ADD_ARG (argv, i, newkeyfile); +- ADD_ARG (argv, i, NULL); +- +- CLEANUP_FREE char *err = NULL; +- int r = commandv (NULL, &err, (const char * const *) argv); +- remove_temp (keyfile); +- remove_temp (newkeyfile); +- +- if (r == -1) { +- reply_with_error ("%s", err); +- return -1; +- } +- +- return 0; +-} +- +-int +-do_luks_kill_slot (const char *device, const char *key, int keyslot) +-{ +- char *tempfile = write_key_to_temp (key); +- if (!tempfile) +- return -1; +- +- const char *argv[MAX_ARGS]; +- char keyslot_s[16]; +- size_t i = 0; +- +- ADD_ARG (argv, i, "cryptsetup"); +- ADD_ARG (argv, i, "-q"); +- ADD_ARG (argv, i, "-d"); +- ADD_ARG (argv, i, tempfile); +- ADD_ARG (argv, i, "luksKillSlot"); +- ADD_ARG (argv, i, device); +- snprintf (keyslot_s, sizeof keyslot_s, "%d", keyslot); +- ADD_ARG (argv, i, keyslot_s); +- ADD_ARG (argv, i, NULL); +- +- CLEANUP_FREE char *err = NULL; +- int r = commandv (NULL, &err, (const char * const *) argv); +- remove_temp (tempfile); +- +- if (r == -1) { +- reply_with_error ("%s", err); +- return -1; +- } +- +- return 0; +-} +- + char * + do_luks_uuid (const char *device) + { +diff --git a/daemon/luks.ml b/daemon/luks.ml +index e7e6deefe..4e6531755 100644 +--- a/daemon/luks.ml ++++ b/daemon/luks.ml +@@ -23,6 +23,65 @@ open Std_utils + + open Utils + ++let write_key_to_tmp_file key = ++ let filename, chan = Filename.open_temp_file "luks" ".out" in ++ output_string chan key; ++ close_out chan; ++ filename ++ ++let rec luks_format device key keyslot = ++ _luks_format device key keyslot ++ ++and luks_format_cipher device key keyslot cipher = ++ _luks_format ~cipher device key keyslot ++ ++and _luks_format ?cipher device key keyslot = ++ let tmp = write_key_to_tmp_file key in ++ Fun.protect ~finally:(fun () -> unlink tmp) ( ++ fun () -> ++ let args = ref [] in ++ List.push_back args "-q"; ++ Option.iter (fun s -> List.push_back_list args ["--cipher"; s]) cipher; ++ List.push_back args "--key-slot"; ++ List.push_back args (string_of_int keyslot); ++ List.push_back args "luksFormat"; ++ List.push_back args device; ++ List.push_back args tmp; ++ ignore (command "cryptsetup" !args) ++ ); ++ udev_settle () ++ ++let luks_add_key device key newkey keyslot = ++ let keyfile = write_key_to_tmp_file key ++ and newkeyfile = write_key_to_tmp_file newkey in ++ Fun.protect ~finally:(fun () -> unlink keyfile; unlink newkeyfile) ( ++ fun () -> ++ let args = ref [] in ++ List.push_back args "-q"; ++ List.push_back args "-d"; ++ List.push_back args keyfile; ++ List.push_back args "--key-slot"; ++ List.push_back args (string_of_int keyslot); ++ List.push_back args "luksAddKey"; ++ List.push_back args device; ++ List.push_back args newkeyfile; ++ ignore (command "cryptsetup" !args) ++ ) ++ ++let luks_kill_slot device key keyslot = ++ let tmp = write_key_to_tmp_file key in ++ Fun.protect ~finally:(fun () -> unlink tmp) ( ++ fun () -> ++ let args = ref [] in ++ List.push_back args "-q"; ++ List.push_back args "-d"; ++ List.push_back args tmp; ++ List.push_back args "luksKillSlot"; ++ List.push_back args device; ++ List.push_back args (string_of_int keyslot); ++ ignore (command "cryptsetup" !args) ++ ) ++ + (* Deprecated APIs for backwards compatibility. *) + let luks_open device key mapname = + Cryptsetup.cryptsetup_open ~crypttype:"luks" device key mapname +diff --git a/generator/actions_core.ml b/generator/actions_core.ml +index 26d77c667..5876188e9 100644 +--- a/generator/actions_core.ml ++++ b/generator/actions_core.ml +@@ -5503,6 +5503,7 @@ group scan.|} }; + { defaults with + name = "luks_format"; added = (1, 5, 2); + style = RErr, [String (Device, "device"); String (Key, "key"); Int "keyslot"], []; ++ impl = OCaml "Luks.luks_format"; + optional = Some "luks"; + shortdesc = "format a block device as a LUKS encrypted device"; + longdesc = "\ +@@ -5514,6 +5515,7 @@ supports 8 key slots, numbered 0-7)." }; + { defaults with + name = "luks_format_cipher"; added = (1, 5, 2); + style = RErr, [String (Device, "device"); String (Key, "key"); Int "keyslot"; String (PlainString, "cipher")], []; ++ impl = OCaml "Luks.luks_format_cipher"; + optional = Some "luks"; + shortdesc = "format a block device as a LUKS encrypted device"; + longdesc = "\ +@@ -5523,6 +5525,7 @@ it also allows you to set the C used." }; + { defaults with + name = "luks_add_key"; added = (1, 5, 2); + style = RErr, [String (Device, "device"); String (Key, "key"); String (Key, "newkey"); Int "keyslot"], []; ++ impl = OCaml "Luks.luks_add_key"; + optional = Some "luks"; + shortdesc = "add a key on a LUKS encrypted device"; + longdesc = {|This command adds a new key on LUKS device C. +@@ -5537,6 +5540,7 @@ first to remove that key.|} }; + { defaults with + name = "luks_kill_slot"; added = (1, 5, 2); + style = RErr, [String (Device, "device"); String (Key, "key"); Int "keyslot"], []; ++ impl = OCaml "Luks.luks_kill_slot"; + optional = Some "luks"; + shortdesc = "remove a key from a LUKS encrypted device"; + longdesc = "\ +-- +2.47.3 + diff --git a/0018-daemon-cryptsetup.ml-Reformat-this-code-for-consiste.patch b/0018-daemon-cryptsetup.ml-Reformat-this-code-for-consiste.patch new file mode 100644 index 0000000..f0e1570 --- /dev/null +++ b/0018-daemon-cryptsetup.ml-Reformat-this-code-for-consiste.patch @@ -0,0 +1,50 @@ +From ac9b4454413097667158a54cbd2d4d646d25e600 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 28 Apr 2026 14:22:51 +0100 +Subject: [PATCH] daemon/cryptsetup.ml: Reformat this code for consistency + +No functional change, simply reformat the code for consistency with +the Luks module. + +(cherry picked from commit e3a79166d9a3ce07e3434e6d2504495248160061) +--- + daemon/cryptsetup.ml | 23 ++++++++++++++--------- + 1 file changed, 14 insertions(+), 9 deletions(-) + +diff --git a/daemon/cryptsetup.ml b/daemon/cryptsetup.ml +index 002e0993a..47b17856c 100644 +--- a/daemon/cryptsetup.ml ++++ b/daemon/cryptsetup.ml +@@ -49,15 +49,20 @@ let cryptsetup_open ?(readonly = false) ?crypttype ?cipher device key mapname = + output_string chan key; + close_out chan; + +- let args = ref [] in +- List.push_back_list args ["-d"; keyfile]; +- if readonly then List.push_back args "--readonly"; +- List.push_back_list args ["open"; device; mapname; "--type"; crypttype]; +- Option.iter (fun s -> List.push_back_list args ["--cipher"; s]) cipher; +- +- (* Make sure we always remove the temporary file. *) +- Fun.protect (fun () -> ignore (command "cryptsetup" !args)) +- ~finally:(fun () -> unlink keyfile); ++ Fun.protect ~finally:(fun () -> unlink keyfile) ( ++ fun () -> ++ let args = ref [] in ++ List.push_back args "-d"; ++ List.push_back args keyfile; ++ if readonly then List.push_back args "--readonly"; ++ List.push_back args "open"; ++ List.push_back args device; ++ List.push_back args mapname; ++ List.push_back args "--type"; ++ List.push_back args crypttype; ++ Option.iter (fun s -> List.push_back_list args ["--cipher"; s]) cipher; ++ ignore (command "cryptsetup" !args) ++ ); + + udev_settle () + +-- +2.47.3 + diff --git a/0019-daemon-Use-common-Utils.write_key_to_tmp_file.patch b/0019-daemon-Use-common-Utils.write_key_to_tmp_file.patch new file mode 100644 index 0000000..5ac767c --- /dev/null +++ b/0019-daemon-Use-common-Utils.write_key_to_tmp_file.patch @@ -0,0 +1,81 @@ +From fabe35d7b170eccac917de451fb955be4af0808d Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 28 Apr 2026 10:09:09 +0100 +Subject: [PATCH] daemon: Use common Utils.write_key_to_tmp_file + +Move this function to Utils module, and ensure it is called from all +places where we use Key parameters. + +(cherry picked from commit 8a36eb4afa10186bf9c62e121ca4db6ef0ec5a8a) +--- + daemon/cryptsetup.ml | 4 +--- + daemon/luks.ml | 6 ------ + daemon/utils.ml | 6 ++++++ + daemon/utils.mli | 6 ++++++ + 4 files changed, 13 insertions(+), 9 deletions(-) + +diff --git a/daemon/cryptsetup.ml b/daemon/cryptsetup.ml +index 47b17856c..29f3acc4d 100644 +--- a/daemon/cryptsetup.ml ++++ b/daemon/cryptsetup.ml +@@ -45,9 +45,7 @@ let cryptsetup_open ?(readonly = false) ?crypttype ?cipher device key mapname = + failwithf "%s: unknown encrypted device type" t in + + (* Write the key to a temporary file. *) +- let keyfile, chan = Filename.open_temp_file "crypt" ".key" in +- output_string chan key; +- close_out chan; ++ let keyfile = write_key_to_tmp_file key in + + Fun.protect ~finally:(fun () -> unlink keyfile) ( + fun () -> +diff --git a/daemon/luks.ml b/daemon/luks.ml +index 4e6531755..371c656ef 100644 +--- a/daemon/luks.ml ++++ b/daemon/luks.ml +@@ -23,12 +23,6 @@ open Std_utils + + open Utils + +-let write_key_to_tmp_file key = +- let filename, chan = Filename.open_temp_file "luks" ".out" in +- output_string chan key; +- close_out chan; +- filename +- + let rec luks_format device key keyslot = + _luks_format device key keyslot + +diff --git a/daemon/utils.ml b/daemon/utils.ml +index 3aa1d7ed2..2a03c7190 100644 +--- a/daemon/utils.ml ++++ b/daemon/utils.ml +@@ -295,3 +295,9 @@ let parse_key_value_strings ?unquote lines = + let hex_of_string s = + let bytes = String.map_chars (fun c -> sprintf "%02x" (Char.code c)) s in + String.concat " " bytes ++ ++let write_key_to_tmp_file key = ++ let filename, chan = Filename.open_temp_file "key" ".out" in ++ output_string chan key; ++ close_out chan; ++ filename +diff --git a/daemon/utils.mli b/daemon/utils.mli +index e14735038..730e4af38 100644 +--- a/daemon/utils.mli ++++ b/daemon/utils.mli +@@ -125,5 +125,11 @@ val hex_of_string : string -> string + (** Return a string as a list of hex bytes. + Use this for debugging msgs only. *) + ++val write_key_to_tmp_file : string -> string ++(** Write a Key parameter to a temporary file. Returns the name of ++ the temporary file. ++ ++ The caller must call {!Unix.unlink} on the file. *) ++ + (**/**) + val get_verbose_flag : unit -> bool +-- +2.47.3 + diff --git a/0020-daemon-Allow-base64-text-as-a-prefix-on-Key-paramete.patch b/0020-daemon-Allow-base64-text-as-a-prefix-on-Key-paramete.patch new file mode 100644 index 0000000..3f4fbe2 --- /dev/null +++ b/0020-daemon-Allow-base64-text-as-a-prefix-on-Key-paramete.patch @@ -0,0 +1,126 @@ +From 20b5a3f5d4a4ebd51354b0ab92141dd4f80e0ef9 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 28 Apr 2026 09:33:06 +0100 +Subject: [PATCH] daemon: Allow base64:/text: as a prefix on Key parameters + +To support 8 bit keys, allow Key parameters to be passed with the +prefix "base64:..." where the key that follows is base64-encoded. + +"text:..." can be used to prefix plaintext passphrases. Or, for +backwards compat, they can be passed without prefix, but this is now +slightly ambiguous. + +Base64 keys are passed through all the APIs as opaque strings, and +then decoded in the daemon just before calling the cryptsetup command. + +(cherry picked from commit 9324859875bd8845e63edd918885ef70bb47211b) +--- + daemon/utils.ml | 23 ++++++++++++++++++++++- + daemon/utils.mli | 3 +++ + generator/types.mli | 6 ++++++ + lib/guestfs.pod | 23 +++++++++++++++++++++++ + 4 files changed, 54 insertions(+), 1 deletion(-) + +diff --git a/daemon/utils.ml b/daemon/utils.ml +index 2a03c7190..2ee303a41 100644 +--- a/daemon/utils.ml ++++ b/daemon/utils.ml +@@ -296,8 +296,29 @@ let hex_of_string s = + let bytes = String.map_chars (fun c -> sprintf "%02x" (Char.code c)) s in + String.concat " " bytes + +-let write_key_to_tmp_file key = ++let rec write_key_to_tmp_file key = ++ (* If the key starts with a prefix then it may need decoding. *) ++ let len = String.length key in ++ if String.starts_with "base64:" key then ++ _decode_base64_key (String.sub key 7 (len-7)) ++ else if String.starts_with "text:" key then ++ _write_key (String.sub key 5 (len-5)) ++ else ++ _write_key key ++ ++and _write_key key = + let filename, chan = Filename.open_temp_file "key" ".out" in + output_string chan key; + close_out chan; + filename ++ ++and _decode_base64_key b64 = ++ let b64file, chan = Filename.open_temp_file "key" ".b64" in ++ output_string chan b64; ++ close_out chan; ++ let keyfile = Filename.temp_file "key" ".out" in ++ let cmd = sprintf "base64 -d < %s > %s" (quote b64file) (quote keyfile) in ++ if Sys.command cmd <> 0 then ++ failwithf "could not decode base64-encoded key"; ++ unlink b64file; ++ keyfile +diff --git a/daemon/utils.mli b/daemon/utils.mli +index 730e4af38..57f33dea8 100644 +--- a/daemon/utils.mli ++++ b/daemon/utils.mli +@@ -129,6 +129,9 @@ val write_key_to_tmp_file : string -> string + (** Write a Key parameter to a temporary file. Returns the name of + the temporary file. + ++ This handles the special "base64:..." or "text:..." prefixes ++ introduced in libguestfs 1.60. ++ + The caller must call {!Unix.unlink} on the file. *) + + (**/**) +diff --git a/generator/types.mli b/generator/types.mli +index 01f00044e..9416974db 100644 +--- a/generator/types.mli ++++ b/generator/types.mli +@@ -190,6 +190,12 @@ and stringt = + Currently the only difference from 'PlainString' is the way + that guestfish requests these parameters from the user. + ++ In libguestfs >= 1.60, these strings can be prefixed with ++ 'base64:...' to pass in a base64 encoded string (supporting ++ arbitrary 8 bit binary). 'text:...' can be used for plain ++ passphrases, or (for backwards compat) the passphrase can be ++ unprefixed. ++ + Eventually we should treat this as sensitive and mlock it + into physical RAM. This is highly complex because of all + the places that XDR-encoded strings can end up. *) +diff --git a/lib/guestfs.pod b/lib/guestfs.pod +index 07737c839..fe74f5542 100644 +--- a/lib/guestfs.pod ++++ b/lib/guestfs.pod +@@ -1218,6 +1218,29 @@ representation. Also consider how it might work in guestfish. + Certain libguestfs calls take a parameter that contains sensitive key + material, passed in as a C string. + ++In libguestfs E 1.60 you can pass in 8 bit binary key data ++(allowing C<'\n'> or C<'\0'>) by prefixing the string with: ++ ++=over 4 ++ ++=item C ++ ++Pass in base64-encoded key data. This supports 8 bit binary key ++data. ++ ++=item C ++ ++Pass in regular text passphrase. This does not support 8 bit binary ++key data. ++ ++=item Unprefixed ++ ++For backwards compatibility with libguestfs E 1.58, a text ++passphrase can be passed in unprefixed. However this does not support ++8 bit binary key data. ++ ++=back ++ + In the future we would hope to change the libguestfs implementation so + that keys are L-ed into physical RAM, and thus can never end + up in swap. However this is I done at the moment, because of the +-- +2.47.3 + diff --git a/0021-Update-common-submodule.patch b/0021-Update-common-submodule.patch new file mode 100644 index 0000000..ebc70f7 --- /dev/null +++ b/0021-Update-common-submodule.patch @@ -0,0 +1,174 @@ +From 7508991c1356155c8fa9dd65d0d5c2c90eea75aa Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 28 Apr 2026 12:34:08 +0100 +Subject: [PATCH] Update common submodule + + Richard W.M. Jones (3): + options/keys.c: When reading key from user, prefix with "text:" + options/keys.c: When using --key :key:, prefix with "text:" + options/keys.c: When reading the key from a file, encode it with base64 + +Fixes: https://redhat.atlassian.net/browse/RHEL-170864 +Fixes: https://redhat.atlassian.net/browse/RHEL-171895 +Fixes: https://redhat.atlassian.net/browse/RHEL-171896 +(cherry picked from commit 6a181ecc7abe8cd67ce0ac15a1a75fd58837091e) +--- + common | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +Submodule common 800510306..cf2e12078: +diff --git a/common/options/keys.c b/common/options/keys.c +index b8f19ce..432e26d 100644 +--- a/common/options/keys.c ++++ b/common/options/keys.c +@@ -37,17 +37,23 @@ + * Read a passphrase ('Key') from F with echo off. + * + * The caller (F) will call free on the string +- * afterwards. Based on the code in cryptsetup file F. ++ * afterwards. ++ * ++ * The entered string is prefixed with "text:..." to avoid ambiguity ++ * (with libguestfs >= 1.60). Base64 encoding cannot be used here. ++ * ++ * Based on the code in cryptsetup file F. + */ + char * + read_key (const char *param) + { + FILE *infp, *outfp; + struct termios orig, temp; ++ CLEANUP_FREE char *key = NULL; ++ size_t keysize = 0; + char *ret = NULL; + int tty; + int tcset = 0; +- size_t allocsize = 0; + ssize_t len; + + /* Read and write to /dev/tty if available. */ +@@ -75,17 +81,21 @@ read_key (const char *param) + } + } + +- len = getline (&ret, &allocsize, infp); ++ len = getline (&key, &keysize, infp); + if (len == -1) { + perror ("getline"); +- free (ret); +- ret = NULL; + goto error; + } + + /* Remove the terminating \n if there is one. */ +- if (len > 0 && ret[len-1] == '\n') +- ret[len-1] = '\0'; ++ if (len > 0 && key[len-1] == '\n') ++ key[len-1] = '\0'; ++ ++ /* Prefix with "text:". */ ++ if (asprintf (&ret, "text:%s", key) == -1) { ++ perror ("asprintf"); ++ goto error; ++ } + + error: + /* Restore echo, close file descriptor. */ +@@ -100,27 +110,60 @@ read_key (const char *param) + return ret; + } + ++/* Read a key from a file and base64 encode it, returning "base64:..." */ + static char * +-read_first_line_from_file (const char *filename) ++read_key_and_base64_encode (const char *filename) + { +- CLEANUP_FCLOSE FILE *fp = NULL; +- char *ret = NULL; +- size_t allocsize = 0; +- ssize_t len; ++ CLEANUP_FREE char *inp = NULL; ++ char *out; ++ size_t inplen, outlen, i, j; + +- fp = fopen (filename, "r"); +- if (!fp) +- error (EXIT_FAILURE, errno, "fopen: %s", filename); ++ if (read_whole_file (filename, &inp, &inplen) == -1) ++ error (EXIT_FAILURE, 0, "read_key_and_base64_encode: read_whole_file: %s", ++ filename); + +- len = getline (&ret, &allocsize, fp); +- if (len == -1) +- error (EXIT_FAILURE, errno, "getline: %s", filename); ++ /* From https://stackoverflow.com/a/6782480 */ ++ static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', ++ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', ++ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', ++ 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', ++ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', ++ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', ++ 'w', 'x', 'y', 'z', '0', '1', '2', '3', ++ '4', '5', '6', '7', '8', '9', '+', '/'}; ++ static int mod_table[] = {0, 2, 1}; + +- /* Remove the terminating \n if there is one. */ +- if (len > 0 && ret[len-1] == '\n') +- ret[len-1] = '\0'; ++ outlen = 4 * ((inplen + 2) / 3); ++ out = malloc (outlen + 7 + 1); ++ if (!out) ++ error (EXIT_FAILURE, errno, "read_key_and_base64_encode: %s: malloc", ++ filename); + +- return ret; ++ /* Add prefix and NUL-termination, then adjust 'out' to make the ++ * rest of the code simpler. ++ */ ++ memcpy (out, "base64:", 7); ++ out[7 + outlen] = '\0'; ++ out += 7; ++ ++ for (i = 0, j = 0; i < inplen;) { ++ uint32_t octet_a = i < inplen ? (unsigned char) inp[i++] : 0; ++ uint32_t octet_b = i < inplen ? (unsigned char) inp[i++] : 0; ++ uint32_t octet_c = i < inplen ? (unsigned char) inp[i++] : 0; ++ ++ uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; ++ ++ assert (j <= outlen-4); ++ out[j++] = encoding_table[(triple >> 3 * 6) & 0x3F]; ++ out[j++] = encoding_table[(triple >> 2 * 6) & 0x3F]; ++ out[j++] = encoding_table[(triple >> 1 * 6) & 0x3F]; ++ out[j++] = encoding_table[(triple >> 0 * 6) & 0x3F]; ++ } ++ ++ for (i = 0; i < mod_table[inplen % 3]; i++) ++ out[outlen - 1 - i] = '='; ++ ++ return out - 7 /* see above */; + } + + /* Return the key(s) matching this particular device from the +@@ -164,15 +207,14 @@ get_keys (struct key_store *ks, const char *device, const char *uuid, + + switch (key->type) { + case key_string: +- s = strdup (key->string.s); +- if (!s) +- error (EXIT_FAILURE, errno, "strdup"); ++ if (asprintf (&s, "text:%s", key->string.s) == -1) ++ error (EXIT_FAILURE, errno, "asprintf"); + match->clevis = false; + match->passphrase = s; + ++match; + break; + case key_file: +- s = read_first_line_from_file (key->file.name); ++ s = read_key_and_base64_encode (key->file.name); + match->clevis = false; + match->passphrase = s; + ++match; +-- +2.47.3 + diff --git a/0022-tests-luks-Test-handling-of-a-binary-key-dev-file-fi.patch b/0022-tests-luks-Test-handling-of-a-binary-key-dev-file-fi.patch new file mode 100644 index 0000000..51ad5f2 --- /dev/null +++ b/0022-tests-luks-Test-handling-of-a-binary-key-dev-file-fi.patch @@ -0,0 +1,114 @@ +From b105fe3e7c470ca8b2f51dce10666b6d320e5ba0 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 28 Apr 2026 12:47:12 +0100 +Subject: [PATCH] tests/luks: Test handling of a binary --key :file: + +(cherry picked from commit 2ddbff5c2939fe987388428c8d8aed2575bdec33) +--- + test-data/phony-guests/make-fedora-img.pl | 7 +++++-- + tests/Makefile.am | 3 ++- + tests/luks/fedora-lv3.key | Bin 0 -> 10 bytes + tests/luks/test-key-option-inspect-luks-on-lvm.sh | 10 +++++----- + 4 files changed, 12 insertions(+), 8 deletions(-) + create mode 100644 tests/luks/fedora-lv3.key + +diff --git a/test-data/phony-guests/make-fedora-img.pl b/test-data/phony-guests/make-fedora-img.pl +index e4ea07841..6025bc838 100755 +--- a/test-data/phony-guests/make-fedora-img.pl ++++ b/test-data/phony-guests/make-fedora-img.pl +@@ -234,7 +234,10 @@ EOF + $g->luks_format ('/dev/Volume-Group/Root', 'FEDORA-Root', 0); + $g->luks_format ('/dev/Volume-Group/Logical-Volume-1', 'FEDORA-LV1', 0); + $g->luks_format ('/dev/Volume-Group/Logical-Volume-2', 'FEDORA-LV2', 0); +- $g->luks_format ('/dev/Volume-Group/Logical-Volume-3', 'FEDORA-LV3', 0); ++ # This is a little different, because we use a binary passphrase. ++ # The base64 string is "FEDORA\0LV3" (containing ASCII NUL). ++ my $base64_key3 = 'base64:RkVET1JBAExWMw=='; ++ $g->luks_format ('/dev/Volume-Group/Logical-Volume-3', $base64_key3, 0); + + # Open the LUKS devices. This creates nodes like /dev/mapper/*-luks. + $g->cryptsetup_open ('/dev/Volume-Group/Root', +@@ -244,7 +247,7 @@ EOF + $g->cryptsetup_open ('/dev/Volume-Group/Logical-Volume-2', + 'FEDORA-LV2', 'LV2-luks'); + $g->cryptsetup_open ('/dev/Volume-Group/Logical-Volume-3', +- 'FEDORA-LV3', 'LV3-luks'); ++ $base64_key3, 'LV3-luks'); + + # Phony root filesystem. + $g->mkfs ('ext2', '/dev/mapper/Root-luks', blocksize => 4096, label => 'ROOT'); +diff --git a/tests/Makefile.am b/tests/Makefile.am +index 91e917b50..f4d92eaa7 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -441,7 +441,8 @@ EXTRA_DIST += \ + luks/test-luks-list.sh \ + luks/test-key-option.sh \ + luks/test-key-option-inspect-luks-on-lvm.sh \ +- luks/test-key-option-inspect-lvm-on-luks.sh ++ luks/test-key-option-inspect-lvm-on-luks.sh \ ++ luks/fedora-lv3.key + + TESTS += \ + lvm/test-lvm-filtering.sh \ +diff --git a/tests/luks/fedora-lv3.key b/tests/luks/fedora-lv3.key +new file mode 100644 +index 0000000000000000000000000000000000000000..89c372fd5df2216b6d2ca9ccb7879dc8ade8ec5c +GIT binary patch +literal 10 +RcmZ>Bb@2~!Wbg?y1^^Cl0*3$q + +literal 0 +HcmV?d00001 + +diff --git a/tests/luks/test-key-option-inspect-luks-on-lvm.sh b/tests/luks/test-key-option-inspect-luks-on-lvm.sh +index 6109c4eec..2ee83332c 100755 +--- a/tests/luks/test-key-option-inspect-luks-on-lvm.sh ++++ b/tests/luks/test-key-option-inspect-luks-on-lvm.sh +@@ -1,6 +1,6 @@ + #!/bin/bash - + # libguestfs +-# Copyright (C) 2019-2025 Red Hat Inc. ++# Copyright (C) 2019-2026 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 +@@ -34,7 +34,7 @@ guestfish=(guestfish --listen --ro --inspector + keys_by_lvname=(--key /dev/Volume-Group/Root:key:FEDORA-Root + --key /dev/Volume-Group/Logical-Volume-1:key:FEDORA-LV1 + --key /dev/Volume-Group/Logical-Volume-2:key:FEDORA-LV2 +- --key /dev/Volume-Group/Logical-Volume-3:key:FEDORA-LV3) ++ --key /dev/Volume-Group/Logical-Volume-3:file:luks/fedora-lv3.key) + + # The variable assignment below will fail, and abort the script, if guestfish + # refuses to start up. +@@ -96,7 +96,7 @@ GUESTFISH_PID= + keys_by_uuid=(--key "$uuid_root":key:FEDORA-Root + --key "$uuid_lv1":key:FEDORA-LV1 + --key "$uuid_lv2":key:FEDORA-LV2 +- --key "$uuid_lv3":key:FEDORA-LV3) ++ --key "$uuid_lv3":file:luks/fedora-lv3.key) + fish_ref=$("${guestfish[@]}" "${keys_by_uuid[@]}") + eval "$fish_ref" + +@@ -113,7 +113,7 @@ keys_by_mapper_lvname=( + --key /dev/mapper/Volume--Group-Root:key:FEDORA-Root + --key /dev/mapper/Volume--Group-Logical--Volume--1:key:FEDORA-LV1 + --key /dev/mapper/Volume--Group-Logical--Volume--2:key:FEDORA-LV2 +- --key /dev/mapper/Volume--Group-Logical--Volume--3:key:FEDORA-LV3 ++ --key /dev/mapper/Volume--Group-Logical--Volume--3:file:luks/fedora-lv3.key + ) + fish_ref=$("${guestfish[@]}" "${keys_by_mapper_lvname[@]}") + eval "$fish_ref" +@@ -130,7 +130,7 @@ keys_by_mapper_lvname=( + --key all:key:FEDORA-Root + --key all:key:FEDORA-LV1 + --key all:key:FEDORA-LV2 +- --key all:key:FEDORA-LV3 ++ --key all:file:luks/fedora-lv3.key + ) + fish_ref=$("${guestfish[@]}" "${keys_by_mapper_lvname[@]}") + eval "$fish_ref" +-- +2.47.3 + diff --git a/0023-docs-guestfs-recipes.pod-Remove-reference-to-deleted.patch b/0023-docs-guestfs-recipes.pod-Remove-reference-to-deleted.patch new file mode 100644 index 0000000..bbc4f24 --- /dev/null +++ b/0023-docs-guestfs-recipes.pod-Remove-reference-to-deleted.patch @@ -0,0 +1,31 @@ +From ae3a26d4a26fd446054ba2b53082b0dd1406d29c Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 5 May 2026 12:03:11 +0100 +Subject: [PATCH] docs/guestfs-recipes.pod: Remove reference to deleted + documentation + +guestfs-tools commit 82edee0cdc ("sysprep: Remove documentation about +copying and cloning") removed this documentation from the +virt-sysprep(1) man page, so remove the reference here. + +(cherry picked from commit c08fbe10db0b54916b6a52b2340106a0193a394d) +--- + docs/guestfs-recipes.pod | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/docs/guestfs-recipes.pod b/docs/guestfs-recipes.pod +index 305bd92f6..4a54696c6 100644 +--- a/docs/guestfs-recipes.pod ++++ b/docs/guestfs-recipes.pod +@@ -68,8 +68,6 @@ Use a combination of tools like L, L, and + virt tools like L, L + and L. + +-For more details, see: L. +- + =head1 Convert a CD-ROM / DVD / ISO to a tarball + + This converts input F to output F: +-- +2.47.3 + diff --git a/0024-generator-Refer-to-new-virt-customize-1-FIRSTBOOT-se.patch b/0024-generator-Refer-to-new-virt-customize-1-FIRSTBOOT-se.patch new file mode 100644 index 0000000..62c640c --- /dev/null +++ b/0024-generator-Refer-to-new-virt-customize-1-FIRSTBOOT-se.patch @@ -0,0 +1,55 @@ +From 596d6c6dc194e914064b53fa68d6834d2e8e78a1 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 5 May 2026 12:22:18 +0100 +Subject: [PATCH] generator: Refer to new virt-customize(1)/FIRSTBOOT section + +Documentation about firstboot scripts was consolidated and reworked +into a new section of the virt-customize(1) manual. Change the +existing references. + +Update common submodule: + + Richard W.M. Jones (1): + mlcustomize: Update generated files + + Srihari Parimi (1): + mlcustomize: firstboot.bat must not reboot VM on error code 250 + +(cherry picked from commit 1f00e533610dc9291337c81669997ebebae23719) +--- + common | 2 +- + generator/customize.ml | 10 ++-------- + 2 files changed, 3 insertions(+), 9 deletions(-) + +Submodule common cf2e12078..dd34aebb4: +diff --git a/generator/customize.ml b/generator/customize.ml +index 19511dcdc..8df20ac1d 100644 +--- a/generator/customize.ml ++++ b/generator/customize.ml +@@ -224,10 +224,7 @@ conveniently wraps the command up in a single line script for you. + You can have multiple I<--firstboot> options. They run in the same + order that they appear on the command line. + +-Please take a look at L for more +-information and caveats about the first boot scripts. +- +-See also I<--run>.|}; ++See also: L, I<--run>.|}; + }; + + { op_name = "firstboot-command"; +@@ -241,10 +238,7 @@ boots up (as root, late in the boot process). + You can have multiple I<--firstboot> options. They run in the same + order that they appear on the command line. + +-Please take a look at L for more +-information and caveats about the first boot scripts. +- +-See also I<--run>.|}; ++See also: L, I<--run>.|}; + }; + + { op_name = "firstboot-install"; +-- +2.47.3 + diff --git a/libguestfs.spec b/libguestfs.spec index 992d67e..6bf930d 100644 --- a/libguestfs.spec +++ b/libguestfs.spec @@ -35,7 +35,7 @@ Summary: Access and modify virtual machine disk images Name: libguestfs Epoch: 1 Version: 1.58.1 -Release: 2%{?dist}.alma.1 +Release: 6%{?dist}.alma.1 License: LGPL-2.1-or-later # Build only for architectures that have a kernel @@ -86,6 +86,22 @@ Patch0008: 0008-New-API-xfs_info2.patch Patch0009: 0009-daemon-Reimplement-xfs_info-using-xfs_info2.patch Patch0010: 0010-generator-Deprecate-xfs_info-replaced-by-xfs_info2.patch Patch0011: 0011-tests-disks-debug-qemu.sh-Fix-test-for-update-QMP-te.patch +Patch0012: 0012-daemon-listfs.ml-Refactor-is_partition_can_hold_file.patch +Patch0013: 0013-daemon-listfs.ml-Ignore-CHS-geometry-error-from-part.patch +Patch0014: 0014-daemon-Move-read_whole_file-to-common-utils.patch +Patch0015: 0015-generator-Adjust-comment-for-String-Key.patch +Patch0016: 0016-daemon-Move-some-deprecated-functions-to-new-Luks-mo.patch +Patch0017: 0017-daemon-Rewrite-some-luks_-APIs-in-OCaml.patch +Patch0018: 0018-daemon-cryptsetup.ml-Reformat-this-code-for-consiste.patch +Patch0019: 0019-daemon-Use-common-Utils.write_key_to_tmp_file.patch +Patch0020: 0020-daemon-Allow-base64-text-as-a-prefix-on-Key-paramete.patch +Patch0021: 0021-Update-common-submodule.patch +Patch0022: 0022-tests-luks-Test-handling-of-a-binary-key-dev-file-fi.patch +Patch0023: 0023-docs-guestfs-recipes.pod-Remove-reference-to-deleted.patch +Patch0024: 0024-generator-Refer-to-new-virt-customize-1-FIRSTBOOT-se.patch + +# For applying patches: +BuildRequires: git BuildRequires: autoconf, automake, libtool, gettext-devel @@ -676,8 +692,7 @@ for %{name}. %if 0%{verify_tarball_signature} %{gpgverify} --keyring='%{SOURCE7}' --signature='%{SOURCE1}' --data='%{SOURCE0}' %endif -%setup -q -%autopatch -p1 +%autosetup -S git -p1 autoreconf -fiv @@ -1097,6 +1112,14 @@ rm ocaml/html/.gitignore %changelog +* Mon Jun 08 2026 Andrew Lukoshko - 1:1.58.1-6.alma.1 +- Update to libguestfs-1.58.1-6 +- daemon/listfs.ml: ignore CHS geometry error from parted (Veritas/Sun) +- daemon: allow base64:/text: prefixes on Key parameters (binary LUKS keys) +- daemon: move read_whole_file to common/utils and rewrite luks_* APIs in OCaml +- Update common submodule +- Apply patches with %%autosetup -S git (a patch now adds binary test data) + * Tue Jan 27 2026 Eduard Abdullin - 1:1.58.1-2.alma.1 - Enable building for ppc64le