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
|
(* guestfs-inspection
* Copyright (C) 2009-2020 Red Hat Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*)
open Printf
open Std_utils
open Utils
open Mountable
open Inspect_types
let re_primary_partition = PCRE.compile "^/dev/(?:h|s|v)d.[1234]$"
let rec inspect_os () =
Mount_utils.umount_all ();
(* Iterate over all detected filesystems. Inspect each one in turn. *)
let fses = Listfs.list_filesystems () in
let fses =
List.filter_map (
fun (mountable, vfs_type) ->
Inspect_fs.check_for_filesystem_on mountable vfs_type
) fses in
if verbose () then (
eprintf "inspect_os: fses:\n";
List.iter (fun fs -> eprintf "%s" (string_of_fs fs)) fses;
flush stderr
);
(* The OS inspection information for CoreOS are gathered by inspecting
* multiple filesystems. Gather all the inspected information in the
* inspect_fs struct of the root filesystem.
*)
let fses = collect_coreos_inspection_info fses in
(* Check if the same filesystem was listed twice as root in fses.
* This may happen for the *BSD root partition where an MBR partition
* is a shadow of the real root partition probably /dev/sda5
*)
let fses = check_for_duplicated_bsd_root fses in
(* For Linux guests with a separate /usr filesystem, merge some of the
* inspected information in that partition to the inspect_fs struct
* of the root filesystem.
*)
let fses = collect_linux_inspection_info fses in
(* Save what we found in a global variable. *)
Inspect_types.inspect_fses := fses;
(* At this point we have, in the handle, a list of all filesystems
* found and data about each one. Now we assemble the list of
* filesystems which are root devices.
*
* Fall through to inspect_get_roots to do that.
*)
inspect_get_roots ()
(* Traverse through the filesystem list and find out if it contains
* the [/] and [/usr] filesystems of a CoreOS image. If this is the
* case, sum up all the collected information on the root fs.
*)
and collect_coreos_inspection_info fses =
(* Split the list into CoreOS root(s), CoreOS usr(s), and
* everything else.
*)
let rec loop roots usrs others = function
| [] -> roots, usrs, others
| ({ role = RoleRoot { distro = Some DISTRO_COREOS } } as r) :: rest ->
loop (r::roots) usrs others rest
| ({ role = RoleUsr { distro = Some DISTRO_COREOS } } as u) :: rest ->
loop roots (u::usrs) others rest
| o :: rest ->
loop roots usrs (o::others) rest
in
let roots, usrs, others = loop [] [] [] fses in
match roots with
(* If there are no CoreOS roots, then there's nothing to do. *)
| [] -> fses
(* If there are more than one CoreOS roots, we cannot inspect the guest. *)
| _::_::_ -> failwith "multiple CoreOS root filesystems found"
| [root] ->
match usrs with
(* If there are no CoreOS usr partitions, nothing to do. *)
| [] -> fses
| usrs ->
(* CoreOS is designed to contain 2 /usr partitions (USR-A, USR-B):
* https://coreos.com/docs/sdk-distributors/sdk/disk-partitions/
* One is active and one passive. During the initial boot, the
* passive partition is empty and it gets filled up when an
* update is performed. Then, when the system reboots, the
* boot loader is instructed to boot from the passive partition.
* If both partitions are valid, we cannot determine which the
* active and which the passive is, unless we peep into the
* boot loader. As a workaround, we check the OS versions and
* pick the one with the higher version as active.
*)
let compare_versions u1 u2 =
let v1 =
match u1 with
| { role = RoleUsr { version = Some v } } -> v
| _ -> (0, 0) in
let v2 =
match u2 with
| { role = RoleUsr { version = Some v } } -> v
| _ -> (0, 0) in
compare v2 v1 (* reverse order *)
in
let usrs = List.sort compare_versions usrs in
let usr = List.hd usrs in
merge usr root;
root :: others
(* On *BSD systems, sometimes [/dev/sda[1234]] is a shadow of the
* real root filesystem that is probably [/dev/sda5] (see:
* [http://www.freebsd.org/doc/handbook/disk-organization.html])
*)
and check_for_duplicated_bsd_root fses =
try
let is_primary_partition = function
| { m_type = (MountablePath | MountableBtrfsVol _) } -> false
| { m_type = MountableDevice; m_device = d } ->
PCRE.matches re_primary_partition d
in
(* Try to find a "BSD primary", if there is one. *)
let bsd_primary =
List.find (
function
| { fs_location = { mountable };
role = RoleRoot { os_type = Some t } } ->
(t = OS_TYPE_FREEBSD || t = OS_TYPE_NETBSD || t = OS_TYPE_OPENBSD)
&& is_primary_partition mountable
| _ -> false
) fses in
let bsd_primary_os_type =
match bsd_primary with
| { role = RoleRoot { os_type = Some t } } -> t
| _ -> assert false in
(* Try to find a shadow of the primary, and if it is found the
* primary is removed.
*)
let fses_without_bsd_primary = List.filter ((!=) bsd_primary) fses in
let shadow_exists =
List.exists (
function
| { role = RoleRoot { os_type = Some t } } ->
t = bsd_primary_os_type
| _ -> false
) fses_without_bsd_primary in
if shadow_exists then fses_without_bsd_primary else fses
with
Not_found -> fses
(* Traverse through the filesystem list and find out if it contains
* the [/] and [/usr] filesystems of a Linux image (but not CoreOS,
* for which there is a separate [collect_coreos_inspection_info]).
*
* If this is the case, sum up all the collected information on each
* root fs from the respective [/usr] filesystems.
*)
and collect_linux_inspection_info fses =
List.map (
function
| { role = RoleRoot { distro = Some d } } as root ->
if d <> DISTRO_COREOS then
collect_linux_inspection_info_for fses root
else
root
| fs -> fs
) fses
(* Traverse through the filesystems and find the /usr filesystem for
* the specified C<root>: if found, merge its basic inspection details
* to the root when they were set (i.e. because the /usr had os-release
* or other ways to identify the OS).
*)
and collect_linux_inspection_info_for fses root =
let root_distro, root_fstab =
match root with
| { role = RoleRoot { distro = Some d; fstab = f } } -> d, f
| _ -> assert false in
try
let usr =
List.find (
function
| { role = RoleUsr { distro = d } }
when d = Some root_distro || d = None -> true
| _ -> false
) fses in
let usr_mountable = usr.fs_location.mountable in
(* This checks that [usr] is found in the fstab of the root
* filesystem. If not, [Not_found] is thrown.
*)
ignore (
List.find (fun (mountable, _) -> usr_mountable = mountable) root_fstab
);
merge usr root;
root
with
Not_found -> root
and inspect_get_roots () =
let fses = !Inspect_types.inspect_fses in
let roots =
List.filter_map (
fun fs -> try Some (root_of_fs fs) with Invalid_argument _ -> None
) fses in
if verbose () then (
eprintf "inspect_get_roots: roots:\n";
List.iter (fun root -> eprintf "%s" (string_of_root root)) roots;
flush stderr
);
(* Only return the list of mountables, since subsequent calls will
* be used to retrieve the other information.
*)
List.map (fun { root_location = { mountable = m } } -> m) roots
and root_of_fs =
function
| { fs_location = location; role = RoleRoot data } ->
{ root_location = location; inspection_data = data }
| { role = (RoleUsr _ | RoleSwap | RoleOther) } ->
invalid_arg "root_of_fs"
and inspect_get_mountpoints root_mountable =
let root = search_for_root root_mountable in
let fstab = root.inspection_data.fstab in
(* If no fstab information (Windows) return just the root. *)
if fstab = [] then
[ "/", root_mountable ]
else (
List.filter_map (
fun (mountable, mp) ->
if String.length mp > 0 && mp.[0] = '/' then
Some (mp, mountable)
else
None
) fstab
)
and inspect_get_filesystems root_mountable =
let root = search_for_root root_mountable in
let fstab = root.inspection_data.fstab in
(* If no fstab information (Windows) return just the root. *)
if fstab = [] then
[ root_mountable ]
else
List.map fst fstab
and inspect_get_format root = "installed"
and inspect_get_type root =
let root = search_for_root root in
match root.inspection_data.os_type with
| Some v -> string_of_os_type v
| None -> "unknown"
and inspect_get_distro root =
let root = search_for_root root in
match root.inspection_data.distro with
| Some v -> string_of_distro v
| None -> "unknown"
and inspect_get_package_format root =
let root = search_for_root root in
match root.inspection_data.package_format with
| Some v -> string_of_package_format v
| None -> "unknown"
and inspect_get_package_management root =
let root = search_for_root root in
match root.inspection_data.package_management with
| Some v -> string_of_package_management v
| None -> "unknown"
and inspect_get_product_name root =
let root = search_for_root root in
match root.inspection_data.product_name with
| Some v -> v
| None -> "unknown"
and inspect_get_product_variant root =
let root = search_for_root root in
match root.inspection_data.product_variant with
| Some v -> v
| None -> "unknown"
and inspect_get_major_version root =
let root = search_for_root root in
match root.inspection_data.version with
| Some (major, _) -> major
| None -> 0
and inspect_get_minor_version root =
let root = search_for_root root in
match root.inspection_data.version with
| Some (_, minor) -> minor
| None -> 0
and inspect_get_arch root =
let root = search_for_root root in
match root.inspection_data.arch with
| Some v -> v
| None -> "unknown"
and inspect_get_hostname root =
let root = search_for_root root in
match root.inspection_data.hostname with
| Some v -> v
| None -> "unknown"
and inspect_get_windows_systemroot root =
let root = search_for_root root in
match root.inspection_data.windows_systemroot with
| Some v -> v
| None ->
failwith "not a Windows guest, or systemroot could not be determined"
and inspect_get_windows_system_hive root =
let root = search_for_root root in
match root.inspection_data.windows_system_hive with
| Some v -> v
| None ->
failwith "not a Windows guest, or system hive not found"
and inspect_get_windows_software_hive root =
let root = search_for_root root in
match root.inspection_data.windows_software_hive with
| Some v -> v
| None ->
failwith "not a Windows guest, or software hive not found"
and inspect_get_windows_current_control_set root =
let root = search_for_root root in
match root.inspection_data.windows_current_control_set with
| Some v -> v
| None ->
failwith "not a Windows guest, or CurrentControlSet could not be determined"
and inspect_is_live root = false
and inspect_is_netinst root = false
and inspect_is_multipart root = false
and inspect_get_drive_mappings root =
let root = search_for_root root in
root.inspection_data.drive_mappings
and search_for_root root =
let fses = !Inspect_types.inspect_fses in
if fses = [] then
failwith "no inspection data: call guestfs_inspect_os first";
let root =
try
List.find (
function
| { fs_location = { mountable = m }; role = RoleRoot _ } -> root = m
| _ -> false
) fses
with
Not_found ->
failwithf "%s: root device not found: only call this function with a root device previously returned by guestfs_inspect_os"
(Mountable.to_string root) in
root_of_fs root
|