From 31b4b33ec87560182f338a088bd242d571bc79e7 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Tue, 27 Aug 2024 13:46:46 +0100 Subject: [PATCH] Update common submodule Pick up the commits below. In particular this makes several refinements to the Windows firstboot code to make it more reliable. Fixes: https://issues.redhat.com/browse/RHEL-55824 Ben Brown (1): Initialise bar->fp as NULL Richard W.M. Jones (16): mlcustomize: Update virt-customize generated files options: Allow nbd+unix:// URIs mlcustomize: Add virt-customize --inject-blnsvr generated files mlcustomize: Add Inject_virtio_win.inject_blnsvr implementation mlcustomize: firstboot: Use Linux path for Powershell script path mlcustomize: firstboot: Use powershell.exe instead of path mlcustomize: firstboot: Use Powershell -NoProfile flag mlcustomize: Revert delay installation of qemu-ga MSI mldrivers/linux_kernels.ml: Prefix general information with ^info: mlcustomize: Use Start-Process -Wait to run qemu-ga installer mlcustomize: Add Firstboot.firstboot_dir function mlcustomize: Place powershell scripts into \Temp mlcustomize: Inject qemu-ga & blnsvr into /Temp mlcustomize: Write qemu-ga log file name to log.txt mlcustomize: Add some comments to firstboot batch file mlcustomize: Reboot Windows between each firstboot script --- common | 2 +- customize/customize_run.ml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) Submodule common 54869c987..a78839676: diff --git a/common/mlcustomize/customize-synopsis.pod b/common/mlcustomize/customize-synopsis.pod index bb0ce1255..957de8cf2 100644 --- a/common/mlcustomize/customize-synopsis.pod +++ b/common/mlcustomize/customize-synopsis.pod @@ -3,16 +3,16 @@ [--copy SOURCE:DEST] [--copy-in LOCALPATH:REMOTEDIR] [--delete PATH] [--edit FILE:EXPR] [--firstboot SCRIPT] [--firstboot-command 'CMD+ARGS'] [--firstboot-install PKG,PKG..] - [--hostname HOSTNAME] [--inject-qemu-ga METHOD] - [--inject-virtio-win METHOD] [--install PKG,PKG..] - [--link TARGET:LINK[:LINK..]] [--mkdir DIR] [--move SOURCE:DEST] - [--password USER:SELECTOR] [--root-password SELECTOR] - [--run SCRIPT] [--run-command 'CMD+ARGS'] [--scrub FILE] - [--sm-attach SELECTOR] [--sm-register] [--sm-remove] - [--sm-unregister] [--ssh-inject USER[:SELECTOR]] - [--tar-in TARFILE:REMOTEDIR] [--timezone TIMEZONE] [--touch FILE] - [--truncate FILE] [--truncate-recursive PATH] - [--uninstall PKG,PKG..] [--update] [--upload FILE:DEST] - [--write FILE:CONTENT] [--no-logfile] + [--hostname HOSTNAME] + [--inject-qemu-ga METHOD] [--inject-virtio-win METHOD] + [--install PKG,PKG..] [--link TARGET:LINK[:LINK..]] [--mkdir DIR] + [--move SOURCE:DEST] [--password USER:SELECTOR] + [--root-password SELECTOR] [--run SCRIPT] + [--run-command 'CMD+ARGS'] [--scrub FILE] [--sm-attach SELECTOR] + [--sm-register] [--sm-remove] [--sm-unregister] + [--ssh-inject USER[:SELECTOR]] [--tar-in TARFILE:REMOTEDIR] + [--timezone TIMEZONE] [--touch FILE] [--truncate FILE] + [--truncate-recursive PATH] [--uninstall PKG,PKG..] [--update] + [--upload FILE:DEST] [--write FILE:CONTENT] [--no-logfile] [--password-crypto md5|sha256|sha512] [--no-selinux-relabel] [--selinux-relabel] [--sm-credentials SELECTOR] diff --git a/common/mlcustomize/customize_cmdline.ml b/common/mlcustomize/customize_cmdline.ml index 48ee33445..c4d6a77d5 100644 --- a/common/mlcustomize/customize_cmdline.ml +++ b/common/mlcustomize/customize_cmdline.ml @@ -61,6 +61,8 @@ and op = [ (* --firstboot-install PKG,PKG.. *) | `Hostname of string (* --hostname HOSTNAME *) + | `InjectBalloonServer of string + (* --inject-blnsvr METHOD *) | `InjectQemuGA of string (* --inject-qemu-ga METHOD *) | `InjectVirtioWin of string diff --git a/common/mlcustomize/customize_cmdline.mli b/common/mlcustomize/customize_cmdline.mli index 51a156eae..ee62961a1 100644 --- a/common/mlcustomize/customize_cmdline.mli +++ b/common/mlcustomize/customize_cmdline.mli @@ -53,6 +53,8 @@ and op = [ (* --firstboot-install PKG,PKG.. *) | `Hostname of string (* --hostname HOSTNAME *) + | `InjectBalloonServer of string + (* --inject-blnsvr METHOD *) | `InjectQemuGA of string (* --inject-qemu-ga METHOD *) | `InjectVirtioWin of string diff --git a/common/mlcustomize/firstboot.ml b/common/mlcustomize/firstboot.ml index 5dc012340..52e76401e 100644 --- a/common/mlcustomize/firstboot.ml +++ b/common/mlcustomize/firstboot.ml @@ -239,7 +239,22 @@ WantedBy=%s end module Windows = struct - let rec install_service (g : Guestfs.guestfs) root = + (* Create and return the firstboot directory. *) + let create_firstboot_dir (g : Guestfs.guestfs) = + let rec loop firstboot_dir firstboot_dir_win = function + | [] -> firstboot_dir, firstboot_dir_win + | dir :: path -> + let firstboot_dir = + if firstboot_dir = "" then "/" ^ dir else firstboot_dir // dir in + let firstboot_dir_win = firstboot_dir_win ^ "\\" ^ dir in + let firstboot_dir = g#case_sensitive_path firstboot_dir in + g#mkdir_p firstboot_dir; + loop firstboot_dir firstboot_dir_win path + in + loop "" "C:" ["Program Files"; "Guestfs"; "Firstboot"] + + let rec install_service (g : Guestfs.guestfs) root + firstboot_dir firstboot_dir_win = (* Either rhsrvany.exe or pvvxsvc.exe must exist. * * (Check also that it's not a dangling symlink but a real file). @@ -254,20 +269,7 @@ module Windows = struct error (f_"One of rhsrvany.exe or pvvxsvc.exe is missing in %s. One of them is required in order to install Windows firstboot scripts. You can get one by building rhsrvany (https://github.com/rwmjones/rhsrvany)") (virt_tools_data_dir ()) in - (* Create a directory for firstboot files in the guest. *) - let firstboot_dir, firstboot_dir_win = - let rec loop firstboot_dir firstboot_dir_win = function - | [] -> firstboot_dir, firstboot_dir_win - | dir :: path -> - let firstboot_dir = - if firstboot_dir = "" then "/" ^ dir else firstboot_dir // dir in - let firstboot_dir_win = firstboot_dir_win ^ "\\" ^ dir in - let firstboot_dir = g#case_sensitive_path firstboot_dir in - g#mkdir_p firstboot_dir; - loop firstboot_dir firstboot_dir_win path - in - loop "" "C:" ["Program Files"; "Guestfs"; "Firstboot"] in - + (* Create a directory for firstboot scripts in the guest. *) g#mkdir_p (firstboot_dir // "scripts"); (* Copy pvvxsvc or rhsrvany to the guest. *) @@ -276,6 +278,9 @@ module Windows = struct (* Write a firstboot.bat control script which just runs the other * scripts in the directory. Note we need to use CRLF line endings * in this script. + * + * XXX It would be better to use powershell here. For some ideas see + * https://github.com/HCK-CI/HLK-Setup-Scripts/ *) let firstboot_script = sprintf "\ @echo off @@ -297,6 +302,7 @@ if not exist \"%%scripts_done%%\" ( mkdir \"%%scripts_done%%\" ) +:: Pick the next script to run. for %%%%f in (\"%%scripts%%\"\\*.bat) do ( echo running \"%%%%f\" move \"%%%%f\" \"%%scripts_done%%\" @@ -305,8 +311,17 @@ for %%%%f in (\"%%scripts%%\"\\*.bat) do ( set elvl=!errorlevel! echo .... exit code !elvl! popd + + :: Reboot the computer. This is necessary to free any locked + :: files which may prevent later scripts from running. + shutdown /r /t 0 /y + + :: Exit the script (in case shutdown returns before rebooting). + :: On next boot, the whole firstboot service will be called again. + exit /b ) +:: Fallthrough here if there are no scripts. echo uninstalling firstboot service \"%%firstboot%%\\%s\" -s firstboot uninstall " firstboot_dir_win srvany in @@ -339,11 +354,25 @@ echo uninstalling firstboot service "PWD", REG_SZ firstboot_dir_win ]; ] in reg_import reg regedits - ); - - firstboot_dir + ) end +let firstboot_dir (g : Guestfs.guestfs) root = + let typ = g#inspect_get_type root in + + match typ with + | "linux" -> + let dir = Linux.firstboot_dir in + g#mkdir_p dir; + dir, None + + | "windows" -> + let dir, dir_win = Windows.create_firstboot_dir g in + dir, Some dir_win + + | _ -> + error (f_"guest type %s is not supported") typ + let script_count = ref 0 let add_firstboot_script (g : Guestfs.guestfs) root ?(prio = 5000) name @@ -363,7 +392,8 @@ let add_firstboot_script (g : Guestfs.guestfs) root ?(prio = 5000) name g#chmod 0o755 filename | "windows", _ -> - let firstboot_dir = Windows.install_service g root in + let firstboot_dir, firstboot_dir_win = Windows.create_firstboot_dir g in + Windows.install_service g root firstboot_dir firstboot_dir_win; let filename = firstboot_dir // "scripts" // filename ^ ".bat" in g#write filename (String.unix2dos content) @@ -382,21 +412,18 @@ let add_firstboot_powershell g root ?prio name code = *) assert (g#inspect_get_type root = "windows"); - let windows_systemroot = g#inspect_get_windows_systemroot root in - - (* Create the temporary directory to put the Powershell file. *) - let tempdir = sprintf "%s/Temp" windows_systemroot in + (* Place the Powershell script into firstboot_dir/Temp *) + let firstboot_dir, firstboot_dir_win = Windows.create_firstboot_dir g in + let tempdir = sprintf "%s/Temp" firstboot_dir in g#mkdir_p tempdir; + + let ps_path = sprintf "%s/%s.ps1" tempdir name in + let ps_path_win = sprintf "%s\\Temp\\%s.ps1" firstboot_dir_win name in let code = String.concat "\r\n" code ^ "\r\n" in - g#write (sprintf "%s/%s" tempdir name) code; + g#write ps_path code; - (* Powershell interpreter. Should we check this exists? XXX *) - let ps_exe = - windows_systemroot ^ - "\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" in - - (* Windows path to the Powershell script. *) - let ps_path = windows_systemroot ^ "\\Temp\\" ^ name in - - let fb = sprintf "%s -ExecutionPolicy ByPass -file %s" ps_exe ps_path in + (* Create a regular firstboot bat that just invokes powershell *) + let fb = + sprintf "powershell.exe -ExecutionPolicy ByPass -NoProfile -file \"%s\"" + ps_path_win in add_firstboot_script g root ?prio name fb diff --git a/common/mlcustomize/firstboot.mli b/common/mlcustomize/firstboot.mli index 8231af658..34ff06901 100644 --- a/common/mlcustomize/firstboot.mli +++ b/common/mlcustomize/firstboot.mli @@ -16,6 +16,23 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *) +val firstboot_dir : Guestfs.guestfs -> string -> string * string option +(** [firstboot_dir g root] + returns the path of the firstboot directory, creating it in + the guest if necessary. + + This returns the name of the directory as a guestfs path, and + optionally the name as a Windows path (only for Windows guests). + + For Linux this could be [/usr/lib/virt-sysprep, None] + + For Windows this could be ["/Program Files/Guestfs/Firstboot", + Some "C:\Program Files\Guestfs\Firstboot"] + + Additional files that are used during firstboot can be placed + in this directory, but be careful not to conflict with files + and scripts added by the firstboot process itself. *) + val add_firstboot_script : Guestfs.guestfs -> string -> ?prio:int -> string -> string -> unit (** [add_firstboot_script g root prio name content] adds a firstboot diff --git a/common/mlcustomize/inject_virtio_win.ml b/common/mlcustomize/inject_virtio_win.ml index 2a30b2008..afec1e456 100644 --- a/common/mlcustomize/inject_virtio_win.ml +++ b/common/mlcustomize/inject_virtio_win.ml @@ -24,6 +24,8 @@ open Common_gettext.Gettext open Regedit +let re_blnsvr = PCRE.compile ~caseless:true "\\bblnsvr\\.exe$" + type t = { g : Guestfs.guestfs; (** guestfs handle *) @@ -261,12 +263,38 @@ let rec inject_virtio_win_drivers ({ g } as t) reg = } ) -and inject_qemu_ga t = - let msi_files = copy_qemu_ga t in +and inject_qemu_ga ({ g; root } as t) = + (* Copy the qemu-ga MSI(s) to the guest. *) + let dir, dir_win = Firstboot.firstboot_dir g root in + let dir_win = Option.value dir_win ~default:dir in + let tempdir = sprintf "%s/Temp" dir in + let tempdir_win = sprintf "%s\\Temp" dir_win in + g#mkdir_p tempdir; + + let msi_files = copy_qemu_ga t tempdir in if msi_files <> [] then - configure_qemu_ga t msi_files; + configure_qemu_ga t tempdir_win msi_files; msi_files <> [] (* return true if we found some qemu-ga MSI files *) +and inject_blnsvr ({ g; root } as t) = + (* Copy the files to the guest. *) + let dir, dir_win = Firstboot.firstboot_dir g root in + let dir_win = Option.value dir_win ~default:dir in + let tempdir = sprintf "%s/Temp" dir in + let tempdir_win = sprintf "%s\\Temp" dir_win in + g#mkdir_p tempdir; + + let files = copy_blnsvr t tempdir in + match files with + | [] -> false (* Didn't find or install anything. *) + + (* We usually find blnsvr.exe in two locations (drivers/by-os and + * drivers/by-driver). Pick the first. + *) + | blnsvr :: _ -> + configure_blnsvr t tempdir_win blnsvr; + true + and add_guestor_to_registry t ((g, root) as reg) drv_name drv_pciid = let ddb_node = g#hivex_node_get_child root "DriverDatabase" in @@ -346,8 +374,13 @@ and copy_drivers t driverdir = (fun () -> error (f_"root directory ‘/’ is missing from the virtio-win directory or ISO.\n\nThis should not happen and may indicate that virtio-win or virt-v2v is broken in some way. Please report this as a bug with a full debug log.")) -and copy_qemu_ga t = - copy_from_virtio_win t "/" "/" (virtio_iso_path_matches_qemu_ga t) +and copy_qemu_ga t tempdir = + copy_from_virtio_win t "/" tempdir (virtio_iso_path_matches_qemu_ga t) + (fun () -> + error (f_"root directory ‘/’ is missing from the virtio-win directory or ISO.\n\nThis should not happen and may indicate that virtio-win or virt-v2v is broken in some way. Please report this as a bug with a full debug log.")) + +and copy_blnsvr t tempdir = + copy_from_virtio_win t "/" tempdir (virtio_iso_path_matches_blnsvr t) (fun () -> error (f_"root directory ‘/’ is missing from the virtio-win directory or ISO.\n\nThis should not happen and may indicate that virtio-win or virt-v2v is broken in some way. Please report this as a bug with a full debug log.")) @@ -513,6 +546,10 @@ and virtio_iso_path_matches_qemu_ga t path = | ("x86_64", "rhev-qga64.msi") -> true | _ -> false +(* Find blnsvr for the current Windows version. *) +and virtio_iso_path_matches_blnsvr t path = + virtio_iso_path_matches_guest_os t path && PCRE.matches re_blnsvr path + (* Look up in libosinfo for the OS, and copy all the locally * available files specified as drivers for that OS to the [destdir]. * @@ -552,37 +589,35 @@ and copy_from_libosinfo { g; i_osinfo; i_arch } destdir = ) driver.Libosinfo.files with Not_found -> [] -and configure_qemu_ga t files = +(* Install qemu-ga. [files] is the non-empty list of possible qemu-ga + * installers we detected. + *) +and configure_qemu_ga t tempdir_win files = + let script = ref [] in + let add = List.push_back script in + + add "# Virt-v2v script which installs QEMU Guest Agent"; + add ""; + add "# Uncomment this line for lots of debug output."; + add "# Set-PSDebug -Trace 2"; + add ""; + add "Write-Host Installing QEMU Guest Agent"; + add ""; + add "# Run qemu-ga installers"; List.iter ( - fun msi_path -> - (* Windows is a trashfire. - * https://stackoverflow.com/a/18730884 - * https://bugzilla.redhat.com/show_bug.cgi?id=1895323 - *) - let psh_script = ref [] in - let add = List.push_back psh_script in + fun msi -> + add (sprintf "Write-Host \"Writing log to %s\\%s.log\"" + tempdir_win msi); + (* [`] is an escape char for quotes *) + add (sprintf "Start-Process -Wait -FilePath \"%s\\%s\" -ArgumentList \"/norestart\",\"/qn\",\"/l+*vx\",\"`\"%s\\%s.log`\"\"" + tempdir_win msi tempdir_win msi) + ) files; - add "# Uncomment this line for lots of debug output."; - add "# Set-PSDebug -Trace 2"; - add ""; - add "Write-Host Removing any previously scheduled qemu-ga installation"; - add "schtasks.exe /Delete /TN Firstboot-qemu-ga /F"; - add ""; - add (sprintf - "Write-Host Scheduling delayed installation of qemu-ga from %s" - msi_path); - add "$d = (get-date).AddSeconds(120)"; - add "$dtfinfo = [System.Globalization.DateTimeFormatInfo]::CurrentInfo"; - add "$sdp = $dtfinfo.ShortDatePattern"; - add "$sdp = $sdp -replace 'y+', 'yyyy'"; - add "$sdp = $sdp -replace 'M+', 'MM'"; - add "$sdp = $sdp -replace 'd+', 'dd'"; - add "schtasks.exe /Create /SC ONCE `"; - add " /ST $d.ToString('HH:mm') /SD $d.ToString($sdp) `"; - add " /RU SYSTEM /TN Firstboot-qemu-ga `"; - add (sprintf " /TR \"C:\\%s /forcerestart /qn /l+*vx C:\\%s.log\"" - msi_path msi_path); + Firstboot.add_firstboot_powershell t.g t.root "install-qemu-ga" !script - Firstboot.add_firstboot_powershell t.g t.root - (sprintf "install-%s.ps1" msi_path) !psh_script; - ) files +and configure_blnsvr t tempdir_win blnsvr = + let cmd = sprintf "\ + @echo off\n\ + echo Installing %s\n\ + \"%s\\%s\" -i\n" blnsvr tempdir_win blnsvr in + Firstboot.add_firstboot_script t.g t.root "install-blnsvr" cmd diff --git a/common/mlcustomize/inject_virtio_win.mli b/common/mlcustomize/inject_virtio_win.mli index d14f04973..d273c4dd3 100644 --- a/common/mlcustomize/inject_virtio_win.mli +++ b/common/mlcustomize/inject_virtio_win.mli @@ -93,3 +93,11 @@ val inject_qemu_ga : t -> bool the MSI(s). Returns [true] iff we were able to inject qemu-ga. *) + +val inject_blnsvr : t -> bool +(** Inject the Balloon Server ([blnsvr.exe]) into a Windows guest. + + A firstboot script is also injected which should install + the server by running [blnsvr -i]. + + Returns [true] iff we were able to inject the Balloon Server. *) diff --git a/common/mldrivers/linux_kernels.ml b/common/mldrivers/linux_kernels.ml index 23ff76a55..e0b6b8a00 100644 --- a/common/mldrivers/linux_kernels.ml +++ b/common/mldrivers/linux_kernels.ml @@ -102,7 +102,7 @@ let detect_kernels (g : G.guestfs) root bootloader apps = ) apps in if verbose () then ( let names = List.map (fun { G.app2_name = name } -> name) kernel_pkgs in - eprintf "candidate kernel packages in this guest: %s%!\n" + eprintf "info: candidate kernel packages in this guest: %s%!\n" (String.concat " " names) ); List.filter_map ( @@ -306,7 +306,7 @@ let detect_kernels (g : G.guestfs) root bootloader apps = ) kernel_pkgs in if verbose () then ( - eprintf "installed kernel packages in this guest:\n"; + eprintf "info: installed kernel packages in this guest:\n"; List.iter (print_kernel_info stderr "\t") installed_kernels; flush stderr ); @@ -343,7 +343,7 @@ let detect_kernels (g : G.guestfs) root bootloader apps = ) vmlinuzes in if verbose () then ( - eprintf "kernels offered by the bootloader in this guest (first in list is default):\n"; + eprintf "info: kernels offered by the bootloader in this guest (first in list is default):\n"; List.iter (print_kernel_info stderr "\t") bootloader_kernels; flush stderr ); diff --git a/common/options/uri.c b/common/options/uri.c index 84d393c1e..9180d6a27 100644 --- a/common/options/uri.c +++ b/common/options/uri.c @@ -99,7 +99,7 @@ is_uri (const char *arg) return 0; for (p--; p >= arg; p--) { - if (!c_islower (*p)) + if (! (c_islower (*p) || *p == '+')) return 0; } @@ -148,7 +148,10 @@ parse (const char *arg, char **path_ret, char **protocol_ret, } */ - *protocol_ret = strdup (uri->scheme); + if (STREQ (uri->scheme, "nbd+unix")) + *protocol_ret = strdup ("nbd"); + else + *protocol_ret = strdup (uri->scheme); if (*protocol_ret == NULL) { perror ("strdup: protocol"); return -1; @@ -194,7 +197,7 @@ parse (const char *arg, char **path_ret, char **protocol_ret, if (path && path[0] == '/' && (STREQ (uri->scheme, "gluster") || STREQ (uri->scheme, "iscsi") || - STREQ (uri->scheme, "nbd") || + STRPREFIX (uri->scheme, "nbd") || STREQ (uri->scheme, "rbd") || STREQ (uri->scheme, "sheepdog"))) path++; diff --git a/common/progress/progress.c b/common/progress/progress.c index e4b30663f..5848abd70 100644 --- a/common/progress/progress.c +++ b/common/progress/progress.c @@ -123,6 +123,7 @@ progress_bar_init (unsigned flags) bar->machine_readable = 1; bar->utf8_mode = 0; bar->have_terminfo = 0; + bar->fp = NULL; } else { bar->machine_readable = 0; diff --git a/customize/customize_run.ml b/customize/customize_run.ml index 1314d6e30..afced8c84 100644 --- a/customize/customize_run.ml +++ b/customize/customize_run.ml @@ -216,6 +216,9 @@ let run (g : G.guestfs) root (ops : ops) = if not (Hostname.set_hostname g root hostname) then warning (f_"hostname could not be set for this type of guest") + | `InjectBalloonServer _ -> + error "injecting the balloon server is not supported in RHEL 9.4, use RHEL 9.5 or above" + | `InjectQemuGA meth -> (match get_virtio_win_handle "--inject-qemu-ga" meth with | None -> ()