Files
libguestfs/daemon/inspect.ml
Richard W.M. Jones 05d4fcb64d Update copyright dates for 2019.
This command run over the source:

perl -pi.bak -e 's/(20[01][0-9])-2018/$1-2019/g' `git ls-files`
2019-01-08 11:58:30 +00:00

397 lines
12 KiB
OCaml

(* guestfs-inspection
* Copyright (C) 2009-2019 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