From 777f3ac82c20469c9e438b9fd88a57007fd2c2bb Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Wed, 19 Feb 2025 14:16:13 +0000 Subject: [PATCH] drivers: Handle large output from 'rpm -ql' command This requires the new guestfs_sh_out API from libguestfs 1.55.6. Update common submodule to include: Richard W.M. Jones (3): mlstdutils: Reimplement String.find, add String.find_from mlstdutils: Reimplement String.nsplit tail recursively mldrivers: Handle large output from 'rpm -ql' command Fixes: https://issues.redhat.com/browse/RHEL-80214 Reported-by: Nijin Ashok (cherry picked from commit 5520f1cfae55377c2fe1db3f2974f6006822e0ea) --- common | 2 +- m4/guestfs-libraries.m4 | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) Submodule common 766384a45..ee88791e1: diff --git a/common/mldrivers/linux.ml b/common/mldrivers/linux.ml index 4e30a8e1f..0dec15495 100644 --- a/common/mldrivers/linux.ml +++ b/common/mldrivers/linux.ml @@ -58,76 +58,84 @@ and do_remove g root packages = let file_list_of_package (g : Guestfs.guestfs) root app = let package_format = g#inspect_get_package_format root in - match package_format with - | "deb" -> - let cmd = [| "dpkg"; "-L"; app.G.app2_name |] in - debug "%s" (String.concat " " (Array.to_list cmd)); - let files = g#command_lines cmd in - let files = Array.to_list files in + + let cmd = + match package_format with + | "deb" -> sprintf "dpkg -L %s" (quote app.G.app2_name) + + | "rpm" -> + (* Since RPM allows multiple packages installed with the same + * name, always check the full NEVR here (RHBZ#1161250). + * + * In RPM < 4.11 query commands that use the epoch number in the + * package name did not work. + * + * For example: + * RHEL 6 (rpm 4.8.0): + * $ rpm -q tar-2:1.23-11.el6.x86_64 + * package tar-2:1.23-11.el6.x86_64 is not installed + * Fedora 20 (rpm 4.11.2): + * $ rpm -q tar-2:1.26-30.fc20.x86_64 + * tar-1.26-30.fc20.x86_64 + *) + let is_rpm_lt_4_11 () = + let ver = + try + (* Since we're going to run 'rpm' below anyway, seems safe + * to run it here and assume the binary works. + *) + let cmd = [| "rpm"; "--version" |] in + debug "%s" (String.concat " " (Array.to_list cmd)); + let ver = g#command_lines cmd in + let ver = + if Array.length ver > 0 then ver.(0) else raise Not_found in + debug "%s" ver; + let ver = String.nsplit " " ver in + let ver = + match ver with + | [ "RPM"; "version"; ver ] -> ver + | _ -> raise Not_found in + if not (PCRE.matches re_version ver) then raise Not_found; + (int_of_string (PCRE.sub 1), int_of_string (PCRE.sub 2)) + with Not_found -> + (* 'rpm' not installed? Hmm... *) + (0, 0) in + ver < (4, 11) + in + let pkg_name = + if app.G.app2_epoch = Int32.zero || is_rpm_lt_4_11 () then + sprintf "%s-%s-%s" app.G.app2_name app.G.app2_version + app.G.app2_release + else + sprintf "%s-%ld:%s-%s" app.G.app2_name app.G.app2_epoch + app.G.app2_version app.G.app2_release in + sprintf "rpm -ql %s" (quote pkg_name) + + | format -> + error (f_"don’t know how to get list of files from package using %s") + format in + + debug "file_list_of_package: running: %s" cmd; + + (* Some packages have a lot of files, too many to list without + * breaking the maximum message size assumption in libguestfs. + * To cope with this, use guestfs_sh_out, added in 1.55.6. + * https://issues.redhat.com/browse/RHEL-80080 + *) + let tmpfile = Filename.temp_file "v2vcmd" ".out" in + On_exit.unlink tmpfile; + g#sh_out cmd tmpfile; + let files = read_whole_file tmpfile in + + (* RPM prints "(contains no files)" on stdout when a package + * has no files in it: + * https://github.com/rpm-software-management/rpm/issues/962 + *) + if String.is_prefix files "(contains no files)" then [] + else ( + let files = String.nsplit "\n" files in List.sort compare files - - | "rpm" -> - (* Since RPM allows multiple packages installed with the same - * name, always check the full NEVR here (RHBZ#1161250). - * - * In RPM < 4.11 query commands that use the epoch number in the - * package name did not work. - * - * For example: - * RHEL 6 (rpm 4.8.0): - * $ rpm -q tar-2:1.23-11.el6.x86_64 - * package tar-2:1.23-11.el6.x86_64 is not installed - * Fedora 20 (rpm 4.11.2): - * $ rpm -q tar-2:1.26-30.fc20.x86_64 - * tar-1.26-30.fc20.x86_64 - *) - let is_rpm_lt_4_11 () = - let ver = - try - (* Since we're going to run 'rpm' below anyway, seems safe - * to run it here and assume the binary works. - *) - let cmd = [| "rpm"; "--version" |] in - debug "%s" (String.concat " " (Array.to_list cmd)); - let ver = g#command_lines cmd in - let ver = if Array.length ver > 0 then ver.(0) else raise Not_found in - debug "%s" ver; - let ver = String.nsplit " " ver in - let ver = - match ver with - | [ "RPM"; "version"; ver ] -> ver - | _ -> raise Not_found in - if not (PCRE.matches re_version ver) then raise Not_found; - (int_of_string (PCRE.sub 1), int_of_string (PCRE.sub 2)) - with Not_found -> - (* 'rpm' not installed? Hmm... *) - (0, 0) in - ver < (4, 11) - in - let pkg_name = - if app.G.app2_epoch = Int32.zero || is_rpm_lt_4_11 () then - sprintf "%s-%s-%s" app.G.app2_name app.G.app2_version - app.G.app2_release - else - sprintf "%s-%ld:%s-%s" app.G.app2_name app.G.app2_epoch - app.G.app2_version app.G.app2_release in - let cmd = [| "rpm"; "-ql"; pkg_name |] in - debug "%s" (String.concat " " (Array.to_list cmd)); - let files = g#command_lines cmd in - (* RPM prints "(contains no files)" on stdout when a package - * has no files in it: - * https://github.com/rpm-software-management/rpm/issues/962 - *) - if files = [| "(contains no files)" |] then - [] - else ( - let files = Array.to_list files in - List.sort compare files - ) - - | format -> - error (f_"don’t know how to get list of files from package using %s") - format + ) let is_file_owned (g : G.guestfs) root path = let package_format = g#inspect_get_package_format root in diff --git a/common/mlstdutils/std_utils.ml b/common/mlstdutils/std_utils.ml index 86b21a7c5..1a36ab772 100644 --- a/common/mlstdutils/std_utils.ml +++ b/common/mlstdutils/std_utils.ml @@ -98,24 +98,27 @@ module String = struct and len = length str in len >= sufflen && sub str (len - sufflen) sufflen = suffix - let rec find s sub = - let len = length s in + let find_from str pos sub = let sublen = length sub in - let rec loop i = - if i <= len-sublen then ( - let rec loop2 j = - if j < sublen then ( - if s.[i+j] = sub.[j] then loop2 (j+1) - else -1 - ) else - i (* found *) - in - let r = loop2 0 in - if r = -1 then loop (i+1) else r - ) else - -1 (* not found *) - in - loop 0 + if sublen = 0 then + 0 + else ( + let found = ref 0 in + let len = length str in + try + for i = pos to len - sublen do + let j = ref 0 in + while unsafe_get str (i + !j) = unsafe_get sub !j do + incr j; + if !j = sublen then begin found := i; raise Exit; end; + done; + done; + -1 + with + Exit -> !found + ) + + let find str sub = find_from str 0 sub let rec replace s s1 s2 = let len = length s in @@ -145,7 +148,7 @@ module String = struct else if n >= len then str, "" else sub str 0 n, sub str n (len-n) - let rec split sep str = + let split sep str = let seplen = length sep in let strlen = length str in let i = find str sep in @@ -154,20 +157,36 @@ module String = struct sub str 0 i, sub str (i + seplen) (strlen - i - seplen) ) - and nsplit ?(max = 0) sep str = + let nsplit ?(max = 0) sep str = if max < 0 then invalid_arg "String.nsplit: max parameter should not be negative"; - (* If we reached the limit, OR if the pattern does not match the string - * at all, return the rest of the string as a single element list. - *) - if max = 1 || find str sep = -1 then - [str] - else ( - let s1, s2 = split sep str in - let max = if max = 0 then 0 else max - 1 in - s1 :: nsplit ~max sep s2 - ) + let len = String.length str in + let seplen = String.length sep in + + let rec loop iters posn acc = + (* If we reached the limit, OR if the pattern does not match + * the string at all, return the rest of the string. + *) + if max > 0 && iters = max then ( + let rest = + if posn = 0 then str else String.sub str posn (len-posn) in + List.rev (rest :: acc) + ) + else ( + let end_ = find_from str posn sep in + if end_ = -1 then ( + let rest = + if posn = 0 then str else String.sub str posn (len-posn) in + List.rev (rest :: acc) + ) + else ( + let acc = String.sub str posn (end_-posn) :: acc in + loop (iters+1) (end_+seplen) acc + ) + ) + in + loop 1 0 [] let rec lines_split str = let buf = Buffer.create 16 in diff --git a/common/mlstdutils/std_utils.mli b/common/mlstdutils/std_utils.mli index a39ac5f3b..7bd55fbae 100644 --- a/common/mlstdutils/std_utils.mli +++ b/common/mlstdutils/std_utils.mli @@ -82,6 +82,10 @@ module String : sig val find : string -> string -> int (** [find str sub] searches for [sub] as a substring of [str]. If found it returns the index. If not found, it returns [-1]. *) + val find_from : string -> int -> string -> int + (** [find_from str start sub] searches for [sub] as a substring of [str], + starting at index [start]. If found it returns the index. + If not found, it returns [-1]. *) val replace : string -> string -> string -> string (** [replace str s1 s2] replaces all instances of [s1] appearing in [str] with [s2]. *) diff --git a/common/mlstdutils/std_utils_tests.ml b/common/mlstdutils/std_utils_tests.ml index 3f5bb1a86..4e368152f 100644 --- a/common/mlstdutils/std_utils_tests.ml +++ b/common/mlstdutils/std_utils_tests.ml @@ -113,13 +113,19 @@ let test_string_nsplit ctx = assert_equal_stringlist [""] (String.nsplit " " ""); assert_equal_stringlist ["abc"] (String.nsplit " " "abc"); assert_equal_stringlist ["a"; "b"; "c"] (String.nsplit " " "a b c"); + assert_equal_stringlist ["abc"; "d"; "e"] (String.nsplit " " "abc d e"); assert_equal_stringlist ["a"; "b"; "c"; ""] (String.nsplit " " "a b c "); assert_equal_stringlist [""; "a"; "b"; "c"] (String.nsplit " " " a b c"); assert_equal_stringlist [""; "a"; "b"; "c"; ""] (String.nsplit " " " a b c "); assert_equal_stringlist ["a b c d"] (String.nsplit ~max:1 " " "a b c d"); assert_equal_stringlist ["a"; "b c d"] (String.nsplit ~max:2 " " "a b c d"); assert_equal_stringlist ["a"; "b"; "c d"] (String.nsplit ~max:3 " " "a b c d"); - assert_equal_stringlist ["a"; "b"; "c"; "d"] (String.nsplit ~max:10 " " "a b c d") + assert_equal_stringlist ["a"; "b"; "c"; "d"] (String.nsplit ~max:10 " " "a b c d"); + + (* Test that nsplit can handle large strings. *) + let xs = Array.to_list (Array.make 10_000_000 "xyz") in + let xs_concat = String.concat " " xs in + assert_equal_stringlist xs (String.nsplit " " xs_concat) (* Test Std_utils.String.lines_split. *) let test_string_lines_split ctx = diff --git a/m4/guestfs-libraries.m4 b/m4/guestfs-libraries.m4 index 80f9425f0..7c66853dd 100644 --- a/m4/guestfs-libraries.m4 +++ b/m4/guestfs-libraries.m4 @@ -19,9 +19,8 @@ dnl Any C libraries required by the libguestfs C library (not the daemon). dnl Of course we need libguestfs. dnl -dnl We need libguestfs 1.49.8 for guestfs_inspect_get_build_id in -dnl virt-drivers. -PKG_CHECK_MODULES([LIBGUESTFS], [libguestfs >= 1.49.8]) +dnl We need libguestfs >= 1:1.54.0-4.el9_6 for guestfs_sh_out. +PKG_CHECK_MODULES([LIBGUESTFS], [libguestfs >= 1.54.0]) dnl Test if it's GNU or XSI strerror_r. AC_FUNC_STRERROR_R