1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
|
(* virt-v2v
* Copyright (C) 2009-2023 Red Hat Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*)
open Printf
open Std_utils
open Tools_utils
open Common_gettext.Gettext
module G = Guestfs
class virtual bootloader = object
method virtual name : string
method virtual augeas_device_patterns : string list
method virtual list_kernels : string list
method virtual set_default_kernel : string -> unit
method set_augeas_configuration () = false
method virtual configure_console : unit -> unit
method virtual remove_console : unit -> unit
method update () = ()
method virtual get_config_file : unit -> string
end
(* Helper function for SUSE: remove (hdX,X) prefix from a path. *)
let remove_hd_prefix =
let rex = PCRE.compile "^\\(hd.*\\)" in
PCRE.replace rex ""
(* Grub1 (AKA grub-legacy) representation. *)
class bootloader_grub1 (g : G.guestfs) root grub_config =
let () =
(* Apply the "grub" lens if it is not handling the file
* already -- Augeas < 1.7.0 will error out otherwise.
*)
if g#aug_ls ("/files" ^ grub_config) = [||] then
g#aug_transform "grub" grub_config in
(* Grub prefix? Usually "/boot". *)
let grub_prefix =
let mounts = g#inspect_get_mountpoints root in
try
List.find (
fun path -> List.mem_assoc path mounts
) [ "/boot/grub"; "/boot" ]
with Not_found -> "" in
object
inherit bootloader
method name = "grub1"
method augeas_device_patterns = [
"/files" ^ grub_config ^ "/*/kernel/root";
"/files" ^ grub_config ^ "/*/kernel/resume";
"/files/boot/grub/device.map/*[label() != \"#comment\"]";
"/files/etc/sysconfig/grub/boot";
]
method list_kernels =
let paths =
let expr = sprintf "/files%s/title/kernel" grub_config in
let paths = g#aug_match expr in
let paths = Array.to_list paths in
(* Remove duplicates. *)
let paths = List.remove_duplicates paths in
(* Get the default kernel from grub if it's set. *)
let default =
let expr = sprintf "/files%s/default" grub_config in
try
let idx = g#aug_get expr in
let idx = int_of_string idx in
(* Grub indices are zero-based, augeas is 1-based. *)
let expr =
sprintf "/files%s/title[%d]/kernel" grub_config (idx+1) in
Some expr
with G.Error msg
when String.find msg "aug_get: no matching node" >= 0 ->
None in
(* If a default kernel was set, put it at the beginning of the paths
* list. If not set, assume the first kernel always boots (?)
*)
match default with
| None -> paths
| Some p -> p :: List.filter ((<>) p) paths in
(* Resolve the Augeas paths to kernel filenames. *)
let vmlinuzes = List.map g#aug_get paths in
(* Make sure kernel does not begin with (hdX,X). *)
let vmlinuzes = List.map remove_hd_prefix vmlinuzes in
(* Prepend grub filesystem. *)
List.map ((^) grub_prefix) vmlinuzes
method set_default_kernel vmlinuz =
if not (String.is_prefix vmlinuz grub_prefix) then
error (f_"kernel %s is not under grub tree %s")
vmlinuz grub_prefix;
let kernel_under_grub_prefix =
let prefix_len = String.length grub_prefix in
let kernel_len = String.length vmlinuz in
String.sub vmlinuz prefix_len (kernel_len - prefix_len) in
(* Find the grub entry for the given kernel. *)
let paths = g#aug_match (sprintf "/files%s/title/kernel[. = '%s']"
grub_config kernel_under_grub_prefix) in
let paths = Array.to_list paths in
if paths = [] then
error (f_"didn't find grub entry for kernel %s") vmlinuz;
let path = List.hd paths in
let rex = PCRE.compile "/title(?:\\[(\\d+)\\])?/kernel" in
if not (PCRE.matches rex path) then
error (f_"internal error: regular expression did not match ā%sā")
path;
let index = try int_of_string (PCRE.sub 1) - 1 with Not_found -> 0 in
g#aug_set (sprintf "/files%s/default" grub_config) (string_of_int index);
g#aug_save ()
method set_augeas_configuration () =
let incls = g#aug_match "/augeas/load/Grub/incl" in
let incls = Array.to_list incls in
let incls_contains_conf =
List.exists (fun incl -> g#aug_get incl = grub_config) incls in
if not incls_contains_conf then (
g#aug_set "/augeas/load/Grub/incl[last()+1]" grub_config;
true
) else
false
method configure_console () =
let rex = PCRE.compile "\\b([xh]vc0)\\b" in
let expr = sprintf "/files%s/title/kernel/console" grub_config in
let paths = g#aug_match expr in
let paths = Array.to_list paths in
List.iter (
fun path ->
let console = g#aug_get path in
let console' = PCRE.replace ~global:true rex "ttyS0" console in
if console <> console' then g#aug_set path console'
) paths;
g#aug_save ()
method remove_console () =
let rex = PCRE.compile "\\b([xh]vc0)\\b" in
let expr = sprintf "/files%s/title/kernel/console" grub_config in
let rec loop = function
| [] -> ()
| path :: paths ->
let console = g#aug_get path in
if PCRE.matches rex console then (
ignore (g#aug_rm path);
(* All the paths are invalid, restart the loop. *)
let paths = g#aug_match expr in
let paths = Array.to_list paths in
loop paths
)
else
loop paths
in
let paths = g#aug_match expr in
let paths = Array.to_list paths in
loop paths;
g#aug_save ()
method get_config_file () =
grub_config
end
(** The method used to get and set the default kernel in Grub2. *)
type default_kernel_method =
| MethodGrubby (** Use the 'grubby' tool. *)
| MethodPerlBootloader (** Use the 'Bootloader::Tools' Perl module. *)
| MethodNone (** No known way. *)
(* Grub2 representation. *)
class bootloader_grub2 (g : G.guestfs) grub_config =
let grub2_mkconfig_cmd =
let elems = [
"/sbin/grub2-mkconfig";
"/usr/sbin/grub2-mkconfig";
"/sbin/grub-mkconfig";
"/usr/sbin/grub-mkconfig"
] in
try List.find (g#is_file ~followsymlinks:true) elems
with Not_found ->
error (f_"failed to find grub2-mkconfig binary (but Grub2 was detected on guest)")
in
let get_default_method =
let has_perl_bootloader () =
try
ignore (g#command [| "/usr/bin/perl"; "-MBootloader::Tools"; "-e1" |]);
true
with G.Error _ -> false
in
if g#exists "/sbin/grubby" then MethodGrubby
else if has_perl_bootloader () then MethodPerlBootloader
else (
warning (f_"could not determine a way to update the configuration of Grub2");
MethodNone
) in
object (self)
inherit bootloader
method name = "grub2"
method augeas_device_patterns = [
"/files/etc/sysconfig/grub/GRUB_CMDLINE_LINUX";
"/files/etc/default/grub/GRUB_CMDLINE_LINUX";
"/files/etc/default/grub/GRUB_CMDLINE_LINUX_DEFAULT";
"/files/boot/grub2/device.map/*[label() != \"#comment\"]";
"/files/boot/grub/device.map/*[label() != \"#comment\"]";
]
method list_kernels =
let get_default_image () =
let res =
match get_default_method with
| MethodGrubby ->
let res = g#command [| "grubby"; "--default-kernel" |] in
(match res with
| "" -> None
| _ -> Some res)
| MethodPerlBootloader ->
let cmd =
[| "/usr/bin/perl"; "-MBootloader::Tools"; "-e"; "
InitLibrary();
my $default = Bootloader::Tools::GetDefaultSection ();
if (!defined $default) {
print 'NODEFAULTSECTION'
}
elsif (exists $default->{image}) {
print $default->{image}
}
else {
die 'no $default->{image}' # should never happen
}
" |] in
let res = g#command cmd in
(match res with
| "NODEFAULTSECTION" -> None
| _ -> Some res)
| MethodNone ->
None in
match res with
| None -> None
| Some k ->
let k = String.chomp k in
Some (remove_hd_prefix k)
in
let vmlinuzes =
(match get_default_image () with
| None -> []
| Some k -> [k]) @
(* This is how the grub2 config generator enumerates kernels. *)
Array.to_list (g#glob_expand "/boot/kernel-*") @
Array.to_list (g#glob_expand "/boot/vmlinuz-*") @
Array.to_list (g#glob_expand "/vmlinuz-*") in
let vmlinuzes = List.filter (
fun filename -> not (Linux.is_package_manager_save_file filename)
) vmlinuzes in
vmlinuzes
method set_default_kernel vmlinuz =
match get_default_method with
| MethodGrubby ->
ignore (g#command [| "grubby"; "--set-default"; vmlinuz |])
| MethodPerlBootloader ->
let cmd =
[| "/usr/bin/perl"; "-MBootloader::Tools"; "-e"; sprintf "
InitLibrary();
my @sections = GetSectionList(type=>image, image=>\"%s\");
my $section = GetSection(@sections);
my $newdefault = $section->{name};
SetGlobals(default, \"$newdefault\");
" vmlinuz |] in
ignore (g#command cmd)
| MethodNone -> ()
method private grub2_update_console ~remove () =
let rex = PCRE.compile "\\bconsole=[xh]vc0\\b" in
let paths = [
"/files/etc/sysconfig/grub/GRUB_CMDLINE_LINUX";
"/files/etc/default/grub/GRUB_CMDLINE_LINUX";
"/files/etc/default/grub/GRUB_CMDLINE_LINUX_DEFAULT"
] in
let paths = List.map g#aug_match paths in
let paths = List.map Array.to_list paths in
let paths = List.flatten paths in
match paths with
| [] ->
if not remove then
warning (f_"could not add grub2 serial console (ignored)")
else
warning (f_"could not remove grub2 serial console (ignored)")
| path :: _ ->
let grub_cmdline = g#aug_get path in
if PCRE.matches rex grub_cmdline then (
let new_grub_cmdline =
if not remove then
PCRE.replace ~global:true rex "console=ttyS0" grub_cmdline
else
PCRE.replace ~global:true rex "" grub_cmdline in
g#aug_set path new_grub_cmdline;
g#aug_save ();
try
self#update ()
with
G.Error msg ->
warning (f_"could not rebuild grub2 configuration file (%s). This may mean that grub output will not be sent to the serial port, but otherwise should be harmless. Original error message: %s")
grub_config msg
)
method configure_console = self#grub2_update_console ~remove:false
method remove_console = self#grub2_update_console ~remove:true
method update () =
ignore (g#command [| grub2_mkconfig_cmd; "-o"; grub_config |]);
(* Grub2 runs osprober which sometimes leaves around read-only
* device-mapper maps covering existing filesystems. These
* confuse later steps (especially fstrim). So just delete
* any if found. (RHBZ#2003503).
*)
ignore (g#command [| "sh"; "-c"; "rm -f /dev/mapper/osprober-linux-*" |])
method get_config_file () =
grub_config
end
(* Helper type used in detect_bootloader. *)
type bootloader_type =
| Grub1
| Grub2
let detect_bootloader (g : G.guestfs) root i_firmware =
(* Where to start searching for bootloaders. *)
let mp =
match i_firmware with
| Firmware.I_BIOS -> "/boot"
| I_UEFI _ -> "/boot/efi/EFI" in
(* Find all paths below the mountpoint, then filter them to find
* the grub config file.
*)
let paths =
try List.map ((^) mp) (Array.to_list (g#find mp))
with G.Error msg ->
error (f_"could not find bootloader mount point (%s): %s") mp msg in
(*
* Workaround for older UEFI-based Debian which may not have
* /boot/efi/EFI/debian/grub.cfg.
*)
let paths =
if g#exists "/boot/grub/grub.cfg" then
match i_firmware with
| Firmware.I_BIOS -> paths
| I_UEFI _ -> paths @ ["/boot/grub/grub.cfg"]
else paths
in
(* We can determine if the bootloader config file is grub 1 or
* grub 2 just by looking at the filename.
*)
let bootloader_type_of_filename path =
match last_part_of path '/' with
| Some "grub.cfg" -> Some Grub2
| Some ("grub.conf" | "menu.lst") -> Some Grub1
| Some _
| None -> None
in
let grub_config, typ =
let rec loop = function
| [] -> error (f_"no bootloader detected")
| path :: paths ->
match bootloader_type_of_filename path with
| None -> loop paths
| Some typ ->
if not (g#is_file ~followsymlinks:true path) then loop paths
else path, typ
in
loop paths in
let bl =
match typ with
| Grub1 -> new bootloader_grub1 g root grub_config
| Grub2 -> new bootloader_grub2 g grub_config in
debug "detected bootloader %s at %s" bl#name grub_config;
bl
|