mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-22 07:03:38 +00:00
daemon: Implement inspection of Linux and other Unix-like operating systems.
This is essentially a line-for-line translation of the C inspection code.
This commit is contained in:
@@ -252,6 +252,10 @@ SOURCES_MLI = \
|
||||
file.mli \
|
||||
filearch.mli \
|
||||
findfs.mli \
|
||||
inspect.mli \
|
||||
inspect_fs.mli \
|
||||
inspect_fs_unix.mli \
|
||||
inspect_fs_unix_fstab.mli \
|
||||
inspect_types.mli \
|
||||
inspect_utils.mli \
|
||||
is.mli \
|
||||
@@ -291,6 +295,10 @@ SOURCES_ML = \
|
||||
realpath.ml \
|
||||
inspect_types.ml \
|
||||
inspect_utils.ml \
|
||||
inspect_fs_unix_fstab.ml \
|
||||
inspect_fs_unix.ml \
|
||||
inspect_fs.ml \
|
||||
inspect.ml \
|
||||
callbacks.ml \
|
||||
daemon.ml
|
||||
|
||||
|
||||
396
daemon/inspect.ml
Normal file
396
daemon/inspect.ml
Normal file
@@ -0,0 +1,396 @@
|
||||
(* guestfs-inspection
|
||||
* Copyright (C) 2009-2017 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.umount_all ();
|
||||
|
||||
(* Iterate over all detected filesystems. Inspect each one in turn. *)
|
||||
let fses = Listfs.list_filesystems () in
|
||||
|
||||
let fses =
|
||||
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 = 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 =
|
||||
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 (
|
||||
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
|
||||
41
daemon/inspect.mli
Normal file
41
daemon/inspect.mli
Normal file
@@ -0,0 +1,41 @@
|
||||
(* guestfs-inspection
|
||||
* Copyright (C) 2009-2017 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.
|
||||
*)
|
||||
|
||||
val inspect_os : unit -> Mountable.t list
|
||||
val inspect_get_roots : unit -> Mountable.t list
|
||||
val inspect_get_mountpoints : Mountable.t -> (string * Mountable.t) list
|
||||
val inspect_get_filesystems : Mountable.t -> Mountable.t list
|
||||
val inspect_get_format : Mountable.t -> string
|
||||
val inspect_get_type : Mountable.t -> string
|
||||
val inspect_get_distro : Mountable.t -> string
|
||||
val inspect_get_package_format : Mountable.t -> string
|
||||
val inspect_get_package_management : Mountable.t -> string
|
||||
val inspect_get_product_name : Mountable.t -> string
|
||||
val inspect_get_product_variant : Mountable.t -> string
|
||||
val inspect_get_major_version : Mountable.t -> int
|
||||
val inspect_get_minor_version : Mountable.t -> int
|
||||
val inspect_get_arch : Mountable.t -> string
|
||||
val inspect_get_hostname : Mountable.t -> string
|
||||
val inspect_get_windows_systemroot : Mountable.t -> string
|
||||
val inspect_get_windows_software_hive : Mountable.t -> string
|
||||
val inspect_get_windows_system_hive : Mountable.t -> string
|
||||
val inspect_get_windows_current_control_set : Mountable.t -> string
|
||||
val inspect_get_drive_mappings : Mountable.t -> (string * string) list
|
||||
val inspect_is_live : Mountable.t -> bool
|
||||
val inspect_is_netinst : Mountable.t -> bool
|
||||
val inspect_is_multipart : Mountable.t -> bool
|
||||
363
daemon/inspect_fs.ml
Normal file
363
daemon/inspect_fs.ml
Normal file
@@ -0,0 +1,363 @@
|
||||
(* guestfs-inspection
|
||||
* Copyright (C) 2009-2017 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 Mountable
|
||||
open Inspect_types
|
||||
open Inspect_utils
|
||||
|
||||
let rec check_for_filesystem_on mountable vfs_type =
|
||||
if verbose () then
|
||||
eprintf "check_for_filesystem_on: %s (%s)\n%!"
|
||||
(Mountable.to_string mountable) vfs_type;
|
||||
|
||||
let role =
|
||||
let is_swap = vfs_type = "swap" in
|
||||
if is_swap then
|
||||
Some RoleSwap
|
||||
else (
|
||||
(* Try mounting the device. Ignore errors if we can't do this. *)
|
||||
let mounted =
|
||||
if vfs_type = "ufs" then ( (* Hack for the *BSDs. *)
|
||||
(* FreeBSD fs is a variant of ufs called ufs2 ... *)
|
||||
try
|
||||
Mount.mount_vfs (Some "ro,ufstype=ufs2") (Some "ufs")
|
||||
mountable "/";
|
||||
true
|
||||
with _ ->
|
||||
(* while NetBSD and OpenBSD use another variant labeled 44bsd *)
|
||||
try
|
||||
Mount.mount_vfs (Some "ro,ufstype=44bsd") (Some "ufs")
|
||||
mountable "/";
|
||||
true
|
||||
with _ -> false
|
||||
) else (
|
||||
try Mount.mount_ro mountable "/";
|
||||
true
|
||||
with _ -> false
|
||||
) in
|
||||
if not mounted then None
|
||||
else (
|
||||
let role = check_filesystem mountable in
|
||||
Mount.umount_all ();
|
||||
role
|
||||
)
|
||||
) in
|
||||
|
||||
match role with
|
||||
| None -> None
|
||||
| Some role ->
|
||||
Some { fs_location = { mountable = mountable; vfs_type = vfs_type };
|
||||
role = role }
|
||||
|
||||
(* When this function is called, the filesystem is mounted on sysroot (). *)
|
||||
and check_filesystem mountable =
|
||||
let role = ref `Other in
|
||||
(* The following struct is mutated in place by callees. However we
|
||||
* need to make a copy of the object here so we don't mutate the
|
||||
* null_inspection_data struct!
|
||||
*)
|
||||
let data = null_inspection_data () in
|
||||
|
||||
let debug_matching what =
|
||||
if verbose () then
|
||||
eprintf "check_filesystem: %s matched %s\n%!"
|
||||
(Mountable.to_string mountable) what
|
||||
in
|
||||
|
||||
(* Grub /boot? *)
|
||||
if Is.is_file "/grub/menu.lst" ||
|
||||
Is.is_file "/grub/grub.conf" ||
|
||||
Is.is_file "/grub2/grub.cfg" then (
|
||||
debug_matching "Grub /boot";
|
||||
()
|
||||
)
|
||||
(* FreeBSD root? *)
|
||||
else if Is.is_dir "/etc" &&
|
||||
Is.is_dir "/bin" &&
|
||||
Is.is_file "/etc/freebsd-update.conf" &&
|
||||
Is.is_file "/etc/fstab" then (
|
||||
debug_matching "FreeBSD root";
|
||||
role := `Root;
|
||||
Inspect_fs_unix.check_freebsd_root mountable data
|
||||
)
|
||||
(* NetBSD root? *)
|
||||
else if Is.is_dir "/etc" &&
|
||||
Is.is_dir "/bin" &&
|
||||
Is.is_file "/netbsd" &&
|
||||
Is.is_file "/etc/fstab" &&
|
||||
Is.is_file "/etc/release" then (
|
||||
debug_matching "NetBSD root";
|
||||
role := `Root;
|
||||
Inspect_fs_unix.check_netbsd_root mountable data;
|
||||
)
|
||||
(* OpenBSD root? *)
|
||||
else if Is.is_dir "/etc" &&
|
||||
Is.is_dir "/bin" &&
|
||||
Is.is_file "/bsd" &&
|
||||
Is.is_file "/etc/fstab" &&
|
||||
Is.is_file "/etc/motd" then (
|
||||
debug_matching "OpenBSD root";
|
||||
role := `Root;
|
||||
Inspect_fs_unix.check_openbsd_root mountable data;
|
||||
)
|
||||
(* Hurd root? *)
|
||||
else if Is.is_file "/hurd/console" &&
|
||||
Is.is_file "/hurd/hello" &&
|
||||
Is.is_file "/hurd/null" then (
|
||||
debug_matching "Hurd root";
|
||||
role := `Root;
|
||||
Inspect_fs_unix.check_hurd_root mountable data;
|
||||
)
|
||||
(* Minix root? *)
|
||||
else if Is.is_dir "/etc" &&
|
||||
Is.is_dir "/bin" &&
|
||||
Is.is_file "/service/vm" &&
|
||||
Is.is_file "/etc/fstab" &&
|
||||
Is.is_file "/etc/version" then (
|
||||
debug_matching "Minix root";
|
||||
role := `Root;
|
||||
Inspect_fs_unix.check_minix_root data;
|
||||
)
|
||||
(* Linux root? *)
|
||||
else if Is.is_dir "/etc" &&
|
||||
(Is.is_dir "/bin" ||
|
||||
is_symlink_to "/bin" "usr/bin") &&
|
||||
(Is.is_file "/etc/fstab" ||
|
||||
Is.is_file "/etc/hosts") then (
|
||||
debug_matching "Linux root";
|
||||
role := `Root;
|
||||
Inspect_fs_unix.check_linux_root mountable data;
|
||||
)
|
||||
(* CoreOS root? *)
|
||||
else if Is.is_dir "/etc" &&
|
||||
Is.is_dir "/root" &&
|
||||
Is.is_dir "/home" &&
|
||||
Is.is_dir "/usr" &&
|
||||
Is.is_file "/etc/coreos/update.conf" then (
|
||||
debug_matching "CoreOS root";
|
||||
role := `Root;
|
||||
Inspect_fs_unix.check_coreos_root mountable data;
|
||||
)
|
||||
(* Linux /usr/local? *)
|
||||
else if Is.is_dir "/etc" &&
|
||||
Is.is_dir "/bin" &&
|
||||
Is.is_dir "/share" &&
|
||||
not (Is.is_dir "/local") &&
|
||||
not (Is.is_file "/etc/fstab") then (
|
||||
debug_matching "Linux /usr/local";
|
||||
()
|
||||
)
|
||||
(* Linux /usr? *)
|
||||
else if Is.is_dir "/etc" &&
|
||||
Is.is_dir "/bin" &&
|
||||
Is.is_dir "/share" &&
|
||||
Is.is_dir "/local" &&
|
||||
not (Is.is_file "/etc/fstab") then (
|
||||
debug_matching "Linux /usr";
|
||||
role := `Usr;
|
||||
Inspect_fs_unix.check_linux_usr data;
|
||||
)
|
||||
(* CoreOS /usr? *)
|
||||
else if Is.is_dir "/bin" &&
|
||||
Is.is_dir "/share" &&
|
||||
Is.is_dir "/local" &&
|
||||
Is.is_dir "/share/coreos" then (
|
||||
debug_matching "CoreOS /usr";
|
||||
role := `Usr;
|
||||
Inspect_fs_unix.check_coreos_usr mountable data;
|
||||
)
|
||||
(* Linux /var? *)
|
||||
else if Is.is_dir "/log" &&
|
||||
Is.is_dir "/run" &&
|
||||
Is.is_dir "/spool" then (
|
||||
debug_matching "Linux /var";
|
||||
()
|
||||
)
|
||||
(* Windows volume with installed applications (but not root)? *)
|
||||
else if is_dir_nocase "/System Volume Information" &&
|
||||
is_dir_nocase "/Program Files" then (
|
||||
debug_matching "Windows volume with installed applications";
|
||||
()
|
||||
)
|
||||
(* Windows volume (but not root)? *)
|
||||
else if is_dir_nocase "/System Volume Information" then (
|
||||
debug_matching "Windows volume without installed applications";
|
||||
()
|
||||
)
|
||||
(* FreeDOS? *)
|
||||
else if is_dir_nocase "/FDOS" &&
|
||||
is_file_nocase "/FDOS/FREEDOS.BSS" then (
|
||||
debug_matching "FreeDOS";
|
||||
role := `Root;
|
||||
data.os_type <- Some OS_TYPE_DOS;
|
||||
data.distro <- Some DISTRO_FREEDOS;
|
||||
(* FreeDOS is a mix of 16 and 32 bit, but
|
||||
* assume it requires a 32 bit i386 processor.
|
||||
*)
|
||||
data.arch <- Some "i386"
|
||||
)
|
||||
(* None of the above. *)
|
||||
else (
|
||||
debug_matching "no known OS partition"
|
||||
);
|
||||
|
||||
(* The above code should have set [data.os_type] and [data.distro]
|
||||
* fields, so we can now guess the package management system.
|
||||
*)
|
||||
data.package_format <- check_package_format data;
|
||||
data.package_management <- check_package_management data;
|
||||
|
||||
match !role with
|
||||
| `Root -> Some (RoleRoot data)
|
||||
| `Usr -> Some (RoleUsr data)
|
||||
| `Other -> Some RoleOther
|
||||
|
||||
and is_symlink_to file wanted_target =
|
||||
if not (Is.is_symlink file) then false
|
||||
else Link.readlink file = wanted_target
|
||||
|
||||
(* At the moment, package format and package management are just a
|
||||
* simple function of the [distro] and [version[0]] fields, so these
|
||||
* can never return an error. We might be cleverer in future.
|
||||
*)
|
||||
and check_package_format { distro = distro } =
|
||||
match distro with
|
||||
| None -> None
|
||||
| Some DISTRO_FEDORA
|
||||
| Some DISTRO_MEEGO
|
||||
| Some DISTRO_REDHAT_BASED
|
||||
| Some DISTRO_RHEL
|
||||
| Some DISTRO_MAGEIA
|
||||
| Some DISTRO_MANDRIVA
|
||||
| Some DISTRO_SUSE_BASED
|
||||
| Some DISTRO_OPENSUSE
|
||||
| Some DISTRO_SLES
|
||||
| Some DISTRO_CENTOS
|
||||
| Some DISTRO_SCIENTIFIC_LINUX
|
||||
| Some DISTRO_ORACLE_LINUX
|
||||
| Some DISTRO_ALTLINUX ->
|
||||
Some PACKAGE_FORMAT_RPM
|
||||
| Some DISTRO_DEBIAN
|
||||
| Some DISTRO_UBUNTU
|
||||
| Some DISTRO_LINUX_MINT ->
|
||||
Some PACKAGE_FORMAT_DEB
|
||||
| Some DISTRO_ARCHLINUX ->
|
||||
Some PACKAGE_FORMAT_PACMAN
|
||||
| Some DISTRO_GENTOO ->
|
||||
Some PACKAGE_FORMAT_EBUILD
|
||||
| Some DISTRO_PARDUS ->
|
||||
Some PACKAGE_FORMAT_PISI
|
||||
| Some DISTRO_ALPINE_LINUX ->
|
||||
Some PACKAGE_FORMAT_APK
|
||||
| Some DISTRO_VOID_LINUX ->
|
||||
Some PACKAGE_FORMAT_XBPS
|
||||
| Some DISTRO_SLACKWARE
|
||||
| Some DISTRO_TTYLINUX
|
||||
| Some DISTRO_COREOS
|
||||
| Some DISTRO_WINDOWS
|
||||
| Some DISTRO_BUILDROOT
|
||||
| Some DISTRO_CIRROS
|
||||
| Some DISTRO_FREEDOS
|
||||
| Some DISTRO_FREEBSD
|
||||
| Some DISTRO_NETBSD
|
||||
| Some DISTRO_OPENBSD
|
||||
| Some DISTRO_FRUGALWARE
|
||||
| Some DISTRO_PLD_LINUX ->
|
||||
None
|
||||
|
||||
and check_package_management { distro = distro; version = version } =
|
||||
let major = match version with None -> 0 | Some (major, _) -> major in
|
||||
match distro with
|
||||
| None -> None
|
||||
|
||||
| Some DISTRO_MEEGO ->
|
||||
Some PACKAGE_MANAGEMENT_YUM
|
||||
|
||||
| Some DISTRO_FEDORA ->
|
||||
(* If Fedora >= 22 and dnf is installed, say "dnf". *)
|
||||
if major >= 22 && Is.is_file ~followsymlinks:true "/usr/bin/dnf" then
|
||||
Some PACKAGE_MANAGEMENT_DNF
|
||||
else if major >= 1 then
|
||||
Some PACKAGE_MANAGEMENT_YUM
|
||||
else
|
||||
(* Probably parsing the release file failed, see RHBZ#1332025. *)
|
||||
None
|
||||
|
||||
| Some DISTRO_REDHAT_BASED
|
||||
| Some DISTRO_RHEL
|
||||
| Some DISTRO_CENTOS
|
||||
| Some DISTRO_SCIENTIFIC_LINUX
|
||||
| Some DISTRO_ORACLE_LINUX ->
|
||||
if major >= 8 then
|
||||
Some PACKAGE_MANAGEMENT_DNF
|
||||
else if major >= 5 then
|
||||
Some PACKAGE_MANAGEMENT_YUM
|
||||
else if major >= 2 then
|
||||
Some PACKAGE_MANAGEMENT_UP2DATE
|
||||
else
|
||||
(* Probably parsing the release file failed, see RHBZ#1332025. *)
|
||||
None
|
||||
|
||||
| Some DISTRO_DEBIAN
|
||||
| Some DISTRO_UBUNTU
|
||||
| Some DISTRO_LINUX_MINT
|
||||
| Some DISTRO_ALTLINUX ->
|
||||
Some PACKAGE_MANAGEMENT_APT
|
||||
|
||||
| Some DISTRO_ARCHLINUX ->
|
||||
Some PACKAGE_MANAGEMENT_PACMAN
|
||||
|
||||
| Some DISTRO_GENTOO ->
|
||||
Some PACKAGE_MANAGEMENT_PORTAGE
|
||||
|
||||
| Some DISTRO_PARDUS ->
|
||||
Some PACKAGE_MANAGEMENT_PISI
|
||||
|
||||
| Some DISTRO_MAGEIA
|
||||
| Some DISTRO_MANDRIVA ->
|
||||
Some PACKAGE_MANAGEMENT_URPMI
|
||||
|
||||
| Some DISTRO_SUSE_BASED
|
||||
| Some DISTRO_OPENSUSE
|
||||
| Some DISTRO_SLES ->
|
||||
Some PACKAGE_MANAGEMENT_ZYPPER
|
||||
|
||||
| Some DISTRO_ALPINE_LINUX ->
|
||||
Some PACKAGE_MANAGEMENT_APK
|
||||
|
||||
| Some DISTRO_VOID_LINUX ->
|
||||
Some PACKAGE_MANAGEMENT_XBPS;
|
||||
|
||||
| Some DISTRO_SLACKWARE
|
||||
| Some DISTRO_TTYLINUX
|
||||
| Some DISTRO_COREOS
|
||||
| Some DISTRO_WINDOWS
|
||||
| Some DISTRO_BUILDROOT
|
||||
| Some DISTRO_CIRROS
|
||||
| Some DISTRO_FREEDOS
|
||||
| Some DISTRO_FREEBSD
|
||||
| Some DISTRO_NETBSD
|
||||
| Some DISTRO_OPENBSD
|
||||
| Some DISTRO_FRUGALWARE
|
||||
| Some DISTRO_PLD_LINUX ->
|
||||
None
|
||||
|
||||
23
daemon/inspect_fs.mli
Normal file
23
daemon/inspect_fs.mli
Normal file
@@ -0,0 +1,23 @@
|
||||
(* guestfs-inspection
|
||||
* Copyright (C) 2009-2017 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.
|
||||
*)
|
||||
|
||||
val check_for_filesystem_on : Mountable.t -> string ->
|
||||
Inspect_types.fs option
|
||||
(** [check_for_filesystem_on cmdline mountable vfs_type] inspects
|
||||
[mountable] looking for a single mountpoint from an operating
|
||||
system. *)
|
||||
745
daemon/inspect_fs_unix.ml
Normal file
745
daemon/inspect_fs_unix.ml
Normal file
@@ -0,0 +1,745 @@
|
||||
(* guestfs-inspection
|
||||
* Copyright (C) 2009-2017 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 C_utils
|
||||
open Std_utils
|
||||
|
||||
open Utils
|
||||
open Inspect_types
|
||||
open Inspect_utils
|
||||
|
||||
let re_fedora = PCRE.compile "Fedora release (\\d+)"
|
||||
let re_rhel_old = PCRE.compile "Red Hat.*release (\\d+).*Update (\\d+)"
|
||||
let re_rhel = PCRE.compile "Red Hat.*release (\\d+)\\.(\\d+)"
|
||||
let re_rhel_no_minor = PCRE.compile "Red Hat.*release (\\d+)"
|
||||
let re_centos_old = PCRE.compile "CentOS.*release (\\d+).*Update (\\d+)"
|
||||
let re_centos = PCRE.compile "CentOS.*release (\\d+)\\.(\\d+)"
|
||||
let re_centos_no_minor = PCRE.compile "CentOS.*release (\\d+)"
|
||||
let re_scientific_linux_old =
|
||||
PCRE.compile "Scientific Linux.*release (\\d+).*Update (\\d+)"
|
||||
let re_scientific_linux =
|
||||
PCRE.compile "Scientific Linux.*release (\\d+)\\.(\\d+)"
|
||||
let re_scientific_linux_no_minor =
|
||||
PCRE.compile "Scientific Linux.*release (\\d+)"
|
||||
let re_oracle_linux_old =
|
||||
PCRE.compile "Oracle Linux.*release (\\d+).*Update (\\d+)"
|
||||
let re_oracle_linux =
|
||||
PCRE.compile "Oracle Linux.*release (\\d+)\\.(\\d+)"
|
||||
let re_oracle_linux_no_minor = PCRE.compile "Oracle Linux.*release (\\d+)"
|
||||
let re_netbsd = PCRE.compile "^NetBSD (\\d+)\\.(\\d+)"
|
||||
let re_opensuse = PCRE.compile "^(openSUSE|SuSE Linux|SUSE LINUX) "
|
||||
let re_sles = PCRE.compile "^SUSE (Linux|LINUX) Enterprise "
|
||||
let re_nld = PCRE.compile "^Novell Linux Desktop "
|
||||
let re_sles_version = PCRE.compile "^VERSION = (\\d+)"
|
||||
let re_sles_patchlevel = PCRE.compile "^PATCHLEVEL = (\\d+)"
|
||||
let re_minix = PCRE.compile "^(\\d+)\\.(\\d+)(\\.(\\d+))?"
|
||||
let re_openbsd = PCRE.compile "^OpenBSD (\\d+|\\?)\\.(\\d+|\\?)"
|
||||
let re_frugalware = PCRE.compile "Frugalware (\\d+)\\.(\\d+)"
|
||||
let re_pldlinux = PCRE.compile "(\\d+)\\.(\\d+) PLD Linux"
|
||||
|
||||
let arch_binaries =
|
||||
[ "/bin/bash"; "/bin/ls"; "/bin/echo"; "/bin/rm"; "/bin/sh" ]
|
||||
|
||||
(* Parse a os-release file.
|
||||
*
|
||||
* Only few fields are parsed, falling back to the usual detection if we
|
||||
* cannot read all of them.
|
||||
*
|
||||
* For the format of os-release, see also:
|
||||
* http://www.freedesktop.org/software/systemd/man/os-release.html
|
||||
*)
|
||||
let rec parse_os_release release_file data =
|
||||
let chroot = Chroot.create ~name:"parse_os_release" () in
|
||||
let lines = Chroot.f chroot (fun () -> read_small_file release_file) () in
|
||||
|
||||
match lines with
|
||||
| None -> false
|
||||
| Some lines ->
|
||||
List.iter (
|
||||
fun line ->
|
||||
let line = String.trim line in
|
||||
if line = "" || line.[0] = '#' then
|
||||
()
|
||||
else (
|
||||
let key, value = String.split "=" line in
|
||||
let value =
|
||||
let n = String.length value in
|
||||
if n >= 2 && value.[0] = '"' && value.[n-1] = '"' then
|
||||
String.sub value 1 (n-2)
|
||||
else
|
||||
value in
|
||||
if key = "ID" then (
|
||||
let distro = distro_of_os_release_id value in
|
||||
match distro with
|
||||
| Some _ as distro -> data.distro <- distro
|
||||
| None -> ()
|
||||
)
|
||||
else if key = "PRETTY_NAME" then
|
||||
data.product_name <- Some value
|
||||
else if key = "VERSION_ID" then
|
||||
parse_version_from_major_minor value data
|
||||
)
|
||||
) lines;
|
||||
|
||||
(* If we haven't got all the fields, exit right away. *)
|
||||
if data.distro = None || data.product_name = None then
|
||||
false
|
||||
else (
|
||||
match data with
|
||||
(* os-release in Debian and CentOS does not provide the full
|
||||
* version number (VERSION_ID), just the major part of it. If
|
||||
* we detect that situation then bail out and use the release
|
||||
* files instead.
|
||||
*)
|
||||
| { distro = Some (DISTRO_DEBIAN|DISTRO_CENTOS);
|
||||
version = Some (_, 0) } ->
|
||||
false
|
||||
|
||||
(* Rolling releases:
|
||||
* Void Linux has no VERSION_ID and no other version/release-
|
||||
* like file.
|
||||
*)
|
||||
| { distro = Some DISTRO_VOID_LINUX;
|
||||
version = None } ->
|
||||
data.version <- Some (0, 0);
|
||||
true
|
||||
|
||||
| _ -> true
|
||||
)
|
||||
|
||||
(* ID="fedora" => Some DISTRO_FEDORA *)
|
||||
and distro_of_os_release_id = function
|
||||
| "alpine" -> Some DISTRO_ALPINE_LINUX
|
||||
| "altlinux" -> Some DISTRO_ALTLINUX
|
||||
| "arch" -> Some DISTRO_ARCHLINUX
|
||||
| "centos" -> Some DISTRO_CENTOS
|
||||
| "coreos" -> Some DISTRO_COREOS
|
||||
| "debian" -> Some DISTRO_DEBIAN
|
||||
| "fedora" -> Some DISTRO_FEDORA
|
||||
| "frugalware" -> Some DISTRO_FRUGALWARE
|
||||
| "mageia" -> Some DISTRO_MAGEIA
|
||||
| "opensuse" -> Some DISTRO_OPENSUSE
|
||||
| "pld" -> Some DISTRO_PLD_LINUX
|
||||
| "rhel" -> Some DISTRO_RHEL
|
||||
| "sles" | "sled" -> Some DISTRO_SLES
|
||||
| "ubuntu" -> Some DISTRO_UBUNTU
|
||||
| "void" -> Some DISTRO_VOID_LINUX
|
||||
| value ->
|
||||
eprintf "/etc/os-release: unknown ID=%s\n" value;
|
||||
None
|
||||
|
||||
(* Ubuntu has /etc/lsb-release containing:
|
||||
* DISTRIB_ID=Ubuntu # Distro
|
||||
* DISTRIB_RELEASE=10.04 # Version
|
||||
* DISTRIB_CODENAME=lucid
|
||||
* DISTRIB_DESCRIPTION="Ubuntu 10.04.1 LTS" # Product name
|
||||
*
|
||||
* [Ubuntu-derived ...] Linux Mint was found to have this:
|
||||
* DISTRIB_ID=LinuxMint
|
||||
* DISTRIB_RELEASE=10
|
||||
* DISTRIB_CODENAME=julia
|
||||
* DISTRIB_DESCRIPTION="Linux Mint 10 Julia"
|
||||
* Linux Mint also has /etc/linuxmint/info with more information,
|
||||
* but we can use the LSB file.
|
||||
*
|
||||
* Mandriva has:
|
||||
* LSB_VERSION=lsb-4.0-amd64:lsb-4.0-noarch
|
||||
* DISTRIB_ID=MandrivaLinux
|
||||
* DISTRIB_RELEASE=2010.1
|
||||
* DISTRIB_CODENAME=Henry_Farman
|
||||
* DISTRIB_DESCRIPTION="Mandriva Linux 2010.1"
|
||||
* Mandriva also has a normal release file called /etc/mandriva-release.
|
||||
*
|
||||
* CoreOS has a /etc/lsb-release link to /usr/share/coreos/lsb-release containing:
|
||||
* DISTRIB_ID=CoreOS
|
||||
* DISTRIB_RELEASE=647.0.0
|
||||
* DISTRIB_CODENAME="Red Dog"
|
||||
* DISTRIB_DESCRIPTION="CoreOS 647.0.0"
|
||||
*)
|
||||
and parse_lsb_release release_file data =
|
||||
let chroot = Chroot.create ~name:"parse_lsb_release" () in
|
||||
let lines = Chroot.f chroot (fun () -> read_small_file release_file) () in
|
||||
|
||||
match lines with
|
||||
| None -> false
|
||||
| Some lines ->
|
||||
(* Some distros (eg. RHEL 3) have a bare lsb-release file that might
|
||||
* just contain the LSB_VERSION field and nothing else. In that case
|
||||
* we must bail out (return false).
|
||||
*)
|
||||
let ok = ref false in
|
||||
|
||||
List.iter (
|
||||
fun line ->
|
||||
if verbose () then
|
||||
eprintf "parse_lsb_release: parsing: %s\n%!" line;
|
||||
|
||||
if data.distro = None && line = "DISTRIB_ID=Ubuntu" then (
|
||||
ok := true;
|
||||
data. distro <- Some DISTRO_UBUNTU
|
||||
)
|
||||
else if data.distro = None && line = "DISTRIB_ID=LinuxMint" then (
|
||||
ok := true;
|
||||
data.distro <- Some DISTRO_LINUX_MINT
|
||||
)
|
||||
else if data.distro = None && line = "DISTRIB_ID=\"Mageia\"" then (
|
||||
ok := true;
|
||||
data.distro <- Some DISTRO_MAGEIA
|
||||
)
|
||||
else if data.distro = None && line = "DISTRIB_ID=CoreOS" then (
|
||||
ok := true;
|
||||
data.distro <- Some DISTRO_COREOS
|
||||
)
|
||||
else if String.is_prefix line "DISTRIB_RELEASE=" then (
|
||||
let line = String.sub line 16 (String.length line - 16) in
|
||||
parse_version_from_major_minor line data
|
||||
)
|
||||
else if String.is_prefix line "DISTRIB_DESCRIPTION=\"" ||
|
||||
String.is_prefix line "DISTRIB_DESCRIPTION='" then (
|
||||
ok := true;
|
||||
let n = String.length line in
|
||||
let product_name = String.sub line 21 (n-22) in
|
||||
data.product_name <- Some product_name
|
||||
)
|
||||
else if String.is_prefix line "DISTRIB_DESCRIPTION=" then (
|
||||
ok := true;
|
||||
let n = String.length line in
|
||||
let product_name = String.sub line 20 (n-20) in
|
||||
data.product_name <- Some product_name
|
||||
)
|
||||
) lines;
|
||||
|
||||
!ok
|
||||
|
||||
and parse_suse_release release_file data =
|
||||
let chroot = Chroot.create ~name:"parse_suse_release" () in
|
||||
let lines = Chroot.f chroot (fun () -> read_small_file release_file) () in
|
||||
|
||||
match lines with
|
||||
| None
|
||||
| Some [] -> false
|
||||
| Some lines ->
|
||||
(* First line is dist release name. *)
|
||||
let product_name = List.hd lines in
|
||||
data.product_name <- Some product_name;
|
||||
|
||||
(* Match SLES first because openSuSE regex overlaps some SLES
|
||||
* release strings.
|
||||
*)
|
||||
if PCRE.matches re_sles product_name ||
|
||||
PCRE.matches re_nld product_name then (
|
||||
(* Second line contains version string. *)
|
||||
let major =
|
||||
if List.length lines >= 2 then (
|
||||
let line = List.nth lines 1 in
|
||||
if PCRE.matches re_sles_version line then
|
||||
Some (int_of_string (PCRE.sub 1))
|
||||
else None
|
||||
)
|
||||
else None in
|
||||
|
||||
(* Third line contains service pack string. *)
|
||||
let minor =
|
||||
if List.length lines >= 3 then (
|
||||
let line = List.nth lines 2 in
|
||||
if PCRE.matches re_sles_patchlevel line then
|
||||
Some (int_of_string (PCRE.sub 1))
|
||||
else None
|
||||
)
|
||||
else None in
|
||||
|
||||
let version =
|
||||
match major, minor with
|
||||
| Some major, Some minor -> Some (major, minor)
|
||||
| Some major, None -> Some (major, 0)
|
||||
| None, Some _ | None, None -> None in
|
||||
|
||||
data.distro <- Some DISTRO_SLES;
|
||||
data.version <- version
|
||||
)
|
||||
else if PCRE.matches re_opensuse product_name then (
|
||||
(* Second line contains version string. *)
|
||||
if List.length lines >= 2 then (
|
||||
let line = List.nth lines 1 in
|
||||
parse_version_from_major_minor line data
|
||||
);
|
||||
|
||||
data.distro <- Some DISTRO_OPENSUSE
|
||||
);
|
||||
|
||||
true
|
||||
|
||||
(* Parse any generic /etc/x-release file.
|
||||
*
|
||||
* The optional regular expression which may match 0, 1 or 2
|
||||
* substrings, which are used as the major and minor numbers.
|
||||
*
|
||||
* The fixed distro is always set, and the product name is
|
||||
* set to the first line of the release file.
|
||||
*)
|
||||
and parse_generic ?rex distro release_file data =
|
||||
let chroot = Chroot.create ~name:"parse_generic" () in
|
||||
let product_name =
|
||||
Chroot.f chroot (
|
||||
fun () ->
|
||||
if not (is_small_file release_file) then (
|
||||
eprintf "%s: not a regular file or too large\n" release_file;
|
||||
""
|
||||
)
|
||||
else
|
||||
read_first_line_from_file release_file
|
||||
) () in
|
||||
if product_name = "" then
|
||||
false
|
||||
else (
|
||||
if verbose () then
|
||||
eprintf "parse_generic: product_name = %s\n%!" product_name;
|
||||
|
||||
data.product_name <- Some product_name;
|
||||
data.distro <- Some distro;
|
||||
|
||||
(match rex with
|
||||
| Some rex ->
|
||||
(* If ~rex was supplied, then it must match the release file,
|
||||
* else the parsing fails.
|
||||
*)
|
||||
if PCRE.matches rex product_name then (
|
||||
(* Although it's not documented, matched_group raises
|
||||
* Invalid_argument if called with an unknown group number.
|
||||
*)
|
||||
let major =
|
||||
try Some (int_of_string (PCRE.sub 1))
|
||||
with Not_found | Invalid_argument _ | Failure _ -> None in
|
||||
let minor =
|
||||
try Some (int_of_string (PCRE.sub 2))
|
||||
with Not_found | Invalid_argument _ | Failure _ -> None in
|
||||
(match major, minor with
|
||||
| None, None -> ()
|
||||
| None, Some _ -> ()
|
||||
| Some major, None -> data.version <- Some (major, 0)
|
||||
| Some major, Some minor -> data.version <- Some (major, minor)
|
||||
);
|
||||
true
|
||||
)
|
||||
else
|
||||
false (* ... else the parsing fails. *)
|
||||
|
||||
| None ->
|
||||
(* However if no ~rex was supplied, then we make a best
|
||||
* effort attempt to parse a version number, but don't
|
||||
* fail if one cannot be found.
|
||||
*)
|
||||
parse_version_from_major_minor product_name data;
|
||||
true
|
||||
)
|
||||
)
|
||||
|
||||
(* The list of release file tests that we run for Linux root filesystems.
|
||||
* This is processed in order.
|
||||
*
|
||||
* For each test, first we check if the named release file exists.
|
||||
* If so, the parse function is called. If not, we go on to the next
|
||||
* test.
|
||||
*
|
||||
* Each parse function should return true or false. If a parse function
|
||||
* returns true, then we finish, else if it returns false then we continue
|
||||
* to the next test.
|
||||
*)
|
||||
type parse_function = string -> inspection_data -> bool
|
||||
type tests = (string * parse_function) list
|
||||
|
||||
let linux_root_tests : tests = [
|
||||
(* systemd distros include /etc/os-release which is reasonably
|
||||
* standardized. This entry should be first.
|
||||
*)
|
||||
"/etc/os-release", parse_os_release;
|
||||
(* LSB is also a reasonable standard. This entry should be second. *)
|
||||
"/etc/lsb-release", parse_lsb_release;
|
||||
|
||||
(* Now we enter the Wild West ... *)
|
||||
|
||||
(* RHEL-based distros include a [/etc/redhat-release] file, hence their
|
||||
* checks need to be performed before the Red-Hat one.
|
||||
*)
|
||||
"/etc/oracle-release", parse_generic ~rex:re_oracle_linux_old
|
||||
DISTRO_ORACLE_LINUX;
|
||||
"/etc/oracle-release", parse_generic ~rex:re_oracle_linux
|
||||
DISTRO_ORACLE_LINUX;
|
||||
"/etc/oracle-release", parse_generic ~rex:re_oracle_linux_no_minor
|
||||
DISTRO_ORACLE_LINUX;
|
||||
"/etc/centos-release", parse_generic ~rex:re_centos_old
|
||||
DISTRO_CENTOS;
|
||||
"/etc/centos-release", parse_generic ~rex:re_centos
|
||||
DISTRO_CENTOS;
|
||||
"/etc/centos-release", parse_generic ~rex:re_centos_no_minor
|
||||
DISTRO_CENTOS;
|
||||
"/etc/altlinux-release", parse_generic DISTRO_ALTLINUX;
|
||||
"/etc/redhat-release", parse_generic ~rex:re_fedora
|
||||
DISTRO_FEDORA;
|
||||
"/etc/redhat-release", parse_generic ~rex:re_rhel_old
|
||||
DISTRO_RHEL;
|
||||
"/etc/redhat-release", parse_generic ~rex:re_rhel
|
||||
DISTRO_RHEL;
|
||||
"/etc/redhat-release", parse_generic ~rex:re_rhel_no_minor
|
||||
DISTRO_RHEL;
|
||||
"/etc/redhat-release", parse_generic ~rex:re_centos_old
|
||||
DISTRO_CENTOS;
|
||||
"/etc/redhat-release", parse_generic ~rex:re_centos
|
||||
DISTRO_CENTOS;
|
||||
"/etc/redhat-release", parse_generic ~rex:re_centos_no_minor
|
||||
DISTRO_CENTOS;
|
||||
"/etc/redhat-release", parse_generic ~rex:re_scientific_linux_old
|
||||
DISTRO_SCIENTIFIC_LINUX;
|
||||
"/etc/redhat-release", parse_generic ~rex:re_scientific_linux
|
||||
DISTRO_SCIENTIFIC_LINUX;
|
||||
"/etc/redhat-release", parse_generic ~rex:re_scientific_linux_no_minor
|
||||
DISTRO_SCIENTIFIC_LINUX;
|
||||
|
||||
(* If there's an /etc/redhat-release file, but nothing above
|
||||
* matches, then it is a generic Red Hat-based distro.
|
||||
*)
|
||||
"/etc/redhat-release", parse_generic DISTRO_REDHAT_BASED;
|
||||
"/etc/redhat-release",
|
||||
(fun _ data -> data.distro <- Some DISTRO_REDHAT_BASED; true);
|
||||
|
||||
"/etc/debian_version", parse_generic DISTRO_DEBIAN;
|
||||
"/etc/pardus-release", parse_generic DISTRO_PARDUS;
|
||||
|
||||
(* /etc/arch-release file is empty and I can't see a way to
|
||||
* determine the actual release or product string.
|
||||
*)
|
||||
"/etc/arch-release",
|
||||
(fun _ data -> data.distro <- Some DISTRO_ARCHLINUX; true);
|
||||
|
||||
"/etc/gentoo-release", parse_generic DISTRO_GENTOO;
|
||||
"/etc/meego-release", parse_generic DISTRO_MEEGO;
|
||||
"/etc/slackware-version", parse_generic DISTRO_SLACKWARE;
|
||||
"/etc/ttylinux-target", parse_generic DISTRO_TTYLINUX;
|
||||
|
||||
"/etc/SuSE-release", parse_suse_release;
|
||||
"/etc/SuSE-release",
|
||||
(fun _ data -> data.distro <- Some DISTRO_SUSE_BASED; true);
|
||||
|
||||
"/etc/cirros/version", parse_generic DISTRO_CIRROS;
|
||||
"/etc/br-version",
|
||||
(fun release_file data ->
|
||||
let distro =
|
||||
if Is.is_file ~followsymlinks:true "/usr/share/cirros/logo" then
|
||||
DISTRO_CIRROS
|
||||
else
|
||||
DISTRO_BUILDROOT in
|
||||
(* /etc/br-version has the format YYYY.MM[-git/hg/svn release] *)
|
||||
parse_generic distro release_file data);
|
||||
|
||||
"/etc/alpine-release", parse_generic DISTRO_ALPINE_LINUX;
|
||||
"/etc/frugalware-release", parse_generic ~rex:re_frugalware
|
||||
DISTRO_FRUGALWARE;
|
||||
"/etc/pld-release", parse_generic ~rex:re_pldlinux
|
||||
DISTRO_PLD_LINUX;
|
||||
]
|
||||
|
||||
let rec check_tests data = function
|
||||
| (release_file, parse_fun) :: tests ->
|
||||
if verbose () then
|
||||
eprintf "check_tests: checking %s\n%!" release_file;
|
||||
if Is.is_file ~followsymlinks:true release_file then (
|
||||
if parse_fun release_file data then () (* true => finished *)
|
||||
else check_tests data tests
|
||||
) else check_tests data tests
|
||||
| [] -> ()
|
||||
|
||||
let rec check_linux_root mountable data =
|
||||
let os_type = OS_TYPE_LINUX in
|
||||
data.os_type <- Some os_type;
|
||||
|
||||
check_tests data linux_root_tests;
|
||||
|
||||
data.arch <- check_architecture ();
|
||||
data.fstab <-
|
||||
Inspect_fs_unix_fstab.check_fstab ~mdadm_conf:true mountable os_type;
|
||||
data.hostname <- check_hostname_linux ()
|
||||
|
||||
and check_architecture () =
|
||||
let rec loop = function
|
||||
| [] -> None
|
||||
| bin :: bins ->
|
||||
(* Allow symlinks when checking the binaries:,so in case they are
|
||||
* relative ones (which can be resolved within the same partition),
|
||||
* then we can check the architecture of their target.
|
||||
*)
|
||||
if Is.is_file ~followsymlinks:true bin then (
|
||||
try
|
||||
let resolved = Realpath.realpath bin in
|
||||
let arch = Filearch.file_architecture resolved in
|
||||
Some arch
|
||||
with exn ->
|
||||
if verbose () then
|
||||
eprintf "check_architecture: %s: %s\n%!" bin
|
||||
(Printexc.to_string exn);
|
||||
loop bins
|
||||
)
|
||||
else
|
||||
loop bins
|
||||
in
|
||||
loop arch_binaries
|
||||
|
||||
and check_hostname_linux () =
|
||||
(* Red Hat-derived would be in /etc/sysconfig/network or
|
||||
* /etc/hostname (RHEL 7+, F18+). Debian-derived in the file
|
||||
* /etc/hostname. Very old Debian and SUSE use /etc/HOSTNAME.
|
||||
* It's best to just look for each of these files in turn, rather
|
||||
* than try anything clever based on distro.
|
||||
*)
|
||||
let rec loop = function
|
||||
| [] -> None
|
||||
| filename :: rest ->
|
||||
match check_hostname_from_file filename with
|
||||
| Some hostname -> Some hostname
|
||||
| None -> loop rest
|
||||
in
|
||||
let hostname = loop [ "/etc/HOSTNAME"; "/etc/hostname" ] in
|
||||
match hostname with
|
||||
| (Some _) as hostname -> hostname
|
||||
| None ->
|
||||
if Is.is_file "/etc/sysconfig/network" then
|
||||
with_augeas ~name:"check_hostname_from_sysconfig_network"
|
||||
["/etc/sysconfig/network"]
|
||||
check_hostname_from_sysconfig_network
|
||||
else
|
||||
None
|
||||
|
||||
(* Parse the hostname where it is stored directly in a file. *)
|
||||
and check_hostname_from_file filename =
|
||||
let chroot =
|
||||
let name = sprintf "check_hostname_from_file: %s" filename in
|
||||
Chroot.create ~name () in
|
||||
|
||||
let hostname = Chroot.f chroot (fun () -> read_small_file filename) () in
|
||||
match hostname with
|
||||
| None | Some [] | Some [""] -> None
|
||||
| Some (hostname :: _) -> Some hostname
|
||||
|
||||
(* Parse the hostname from /etc/sysconfig/network. This must be
|
||||
* called from the 'with_augeas' wrapper. Note that F18+ and
|
||||
* RHEL7+ use /etc/hostname just like Debian.
|
||||
*)
|
||||
and check_hostname_from_sysconfig_network aug =
|
||||
(* Errors here are not fatal (RHBZ#726739), since it could be
|
||||
* just missing HOSTNAME field in the file.
|
||||
*)
|
||||
aug_get_noerrors aug "/files/etc/sysconfig/network/HOSTNAME"
|
||||
|
||||
(* The currently mounted device looks like a Linux /usr. *)
|
||||
let check_linux_usr data =
|
||||
data.os_type <- Some OS_TYPE_LINUX;
|
||||
|
||||
if Is.is_file "/lib/os-release" ~followsymlinks:true then
|
||||
ignore (parse_os_release "/lib/os-release" data);
|
||||
|
||||
(match check_architecture () with
|
||||
| None -> ()
|
||||
| (Some _) as arch -> data.arch <- arch
|
||||
)
|
||||
|
||||
(* The currently mounted device is a CoreOS root. From this partition we can
|
||||
* only determine the hostname. All immutable OS files are under a separate
|
||||
* read-only /usr partition.
|
||||
*)
|
||||
let check_coreos_root mountable data =
|
||||
data.os_type <- Some OS_TYPE_LINUX;
|
||||
data.distro <- Some DISTRO_COREOS;
|
||||
|
||||
(* Determine hostname. *)
|
||||
data.hostname <- check_hostname_linux ();
|
||||
|
||||
(* CoreOS does not contain /etc/fstab to determine the mount points.
|
||||
* Associate this filesystem with the "/" mount point.
|
||||
*)
|
||||
data.fstab <- [ mountable, "/" ]
|
||||
|
||||
(* The currently mounted device looks like a CoreOS /usr. In CoreOS
|
||||
* the read-only /usr contains the OS version. The /etc/os-release is a
|
||||
* link to /usr/share/coreos/os-release.
|
||||
*)
|
||||
let check_coreos_usr mountable data =
|
||||
data.os_type <- Some OS_TYPE_LINUX;
|
||||
data.distro <- Some DISTRO_COREOS;
|
||||
|
||||
if Is.is_file "/lib/os-release" ~followsymlinks:true then
|
||||
ignore (parse_os_release "/lib/os-release" data)
|
||||
else if Is.is_file "/share/coreos/lsb-release" ~followsymlinks:true then
|
||||
ignore (parse_lsb_release "/share/coreos/lsb-release" data);
|
||||
|
||||
(* Determine the architecture. *)
|
||||
(match check_architecture () with
|
||||
| None -> ()
|
||||
| (Some _) as arch -> data.arch <- arch
|
||||
);
|
||||
|
||||
(* CoreOS does not contain /etc/fstab to determine the mount points.
|
||||
* Associate this filesystem with the "/usr" mount point.
|
||||
*)
|
||||
data.fstab <- [ mountable, "/usr" ]
|
||||
|
||||
let rec check_freebsd_root mountable data =
|
||||
let os_type = OS_TYPE_FREEBSD and distro = DISTRO_FREEBSD in
|
||||
data.os_type <- Some os_type;
|
||||
data.distro <- Some distro;
|
||||
|
||||
(* FreeBSD has no authoritative version file. The version number is
|
||||
* in /etc/motd, which the system administrator might edit, but
|
||||
* we'll use that anyway.
|
||||
*)
|
||||
if Is.is_file "/etc/motd" ~followsymlinks:true then
|
||||
ignore (parse_generic distro "/etc/motd" data);
|
||||
|
||||
(* Determine the architecture. *)
|
||||
data.arch <- check_architecture ();
|
||||
(* We already know /etc/fstab exists because it's part of the test
|
||||
* in the caller.
|
||||
*)
|
||||
data.fstab <- Inspect_fs_unix_fstab.check_fstab mountable os_type;
|
||||
data.hostname <- check_hostname_freebsd ()
|
||||
|
||||
(* Parse the hostname from /etc/rc.conf. On FreeBSD and NetBSD
|
||||
* this file contains comments, blank lines and:
|
||||
* hostname="freebsd8.example.com"
|
||||
* ifconfig_re0="DHCP"
|
||||
* keymap="uk.iso"
|
||||
* sshd_enable="YES"
|
||||
*)
|
||||
and check_hostname_freebsd () =
|
||||
let chroot = Chroot.create ~name:"check_hostname_freebsd" () in
|
||||
let filename = "/etc/rc.conf" in
|
||||
|
||||
try
|
||||
let lines = Chroot.f chroot (fun () -> read_small_file filename) () in
|
||||
let lines =
|
||||
match lines with None -> raise Not_found | Some lines -> lines in
|
||||
let rec loop = function
|
||||
| [] ->
|
||||
raise Not_found
|
||||
| line :: _ when String.is_prefix line "hostname=\"" ||
|
||||
String.is_prefix line "hostname='" ->
|
||||
let len = String.length line - 10 - 1 in
|
||||
String.sub line 10 len
|
||||
| line :: _ when String.is_prefix line "hostname=" ->
|
||||
let len = String.length line - 9 in
|
||||
String.sub line 9 len
|
||||
| _ :: lines ->
|
||||
loop lines
|
||||
in
|
||||
let hostname = loop lines in
|
||||
Some hostname
|
||||
with
|
||||
Not_found -> None
|
||||
|
||||
let rec check_netbsd_root mountable data =
|
||||
let os_type = OS_TYPE_NETBSD and distro = DISTRO_NETBSD in
|
||||
data.os_type <- Some os_type;
|
||||
data.distro <- Some distro;
|
||||
|
||||
if Is.is_file "/etc/release" ~followsymlinks:true then
|
||||
ignore (parse_generic ~rex:re_netbsd distro "/etc/release" data);
|
||||
|
||||
(* Determine the architecture. *)
|
||||
data.arch <- check_architecture ();
|
||||
(* We already know /etc/fstab exists because it's part of the test
|
||||
* in the caller.
|
||||
*)
|
||||
data.fstab <- Inspect_fs_unix_fstab.check_fstab mountable os_type;
|
||||
data.hostname <- check_hostname_freebsd ()
|
||||
|
||||
and check_hostname_netbsd () = check_hostname_freebsd ()
|
||||
|
||||
let rec check_openbsd_root mountable data =
|
||||
let os_type = OS_TYPE_FREEBSD and distro = DISTRO_FREEBSD in
|
||||
data.os_type <- Some os_type;
|
||||
data.distro <- Some distro;
|
||||
|
||||
(* The first line of /etc/motd gets automatically updated at boot. *)
|
||||
if Is.is_file "/etc/motd" ~followsymlinks:true then
|
||||
ignore (parse_generic distro "/etc/motd" data);
|
||||
|
||||
(* Before the first boot, the first line will look like this:
|
||||
*
|
||||
* OpenBSD ?.? (UNKNOWN)
|
||||
*
|
||||
* The previous C code used to check for this case explicitly,
|
||||
* but in this code, parse_generic should be unable to extract
|
||||
* any version and so should return with [data.version = None].
|
||||
*)
|
||||
|
||||
(* Determine the architecture. *)
|
||||
data.arch <- check_architecture ();
|
||||
(* We already know /etc/fstab exists because it's part of the test
|
||||
* in the caller.
|
||||
*)
|
||||
data.fstab <- Inspect_fs_unix_fstab.check_fstab mountable os_type;
|
||||
data.hostname <- check_hostname_freebsd ()
|
||||
|
||||
and check_hostname_openbsd () =
|
||||
check_hostname_from_file "/etc/myname"
|
||||
|
||||
(* The currently mounted device may be a Hurd root. Hurd has distros
|
||||
* just like Linux.
|
||||
*)
|
||||
let rec check_hurd_root mountable data =
|
||||
let os_type = OS_TYPE_HURD in
|
||||
data.os_type <- Some os_type;
|
||||
|
||||
if Is.is_file "/etc/debian_version" ~followsymlinks:true then (
|
||||
let distro = DISTRO_DEBIAN in
|
||||
ignore (parse_generic distro "/etc/debian_version" data)
|
||||
);
|
||||
(* Arch Hurd also exists, but inconveniently it doesn't have
|
||||
* the normal /etc/arch-release file. XXX
|
||||
*)
|
||||
|
||||
(* Determine the architecture. *)
|
||||
data.arch <- check_architecture ();
|
||||
(* We already know /etc/fstab exists because it's part of the test
|
||||
* in the caller.
|
||||
*)
|
||||
data.fstab <- Inspect_fs_unix_fstab.check_fstab mountable os_type;
|
||||
data.hostname <- check_hostname_hurd ()
|
||||
|
||||
and check_hostname_hurd () = check_hostname_linux ()
|
||||
|
||||
let rec check_minix_root data =
|
||||
let os_type = OS_TYPE_MINIX in
|
||||
data.os_type <- Some os_type;
|
||||
|
||||
if Is.is_file "/etc/version" ~followsymlinks:true then (
|
||||
ignore (parse_generic ~rex:re_minix DISTRO_MEEGO (* XXX unset below *)
|
||||
"/etc/version" data);
|
||||
data.distro <- None
|
||||
);
|
||||
|
||||
(* Determine the architecture. *)
|
||||
data.arch <- check_architecture ();
|
||||
(* TODO: enable fstab inspection once resolve_fstab_device
|
||||
* implements the proper mapping from the Minix device names
|
||||
* to the appliance names.
|
||||
*)
|
||||
data.hostname <- check_hostname_minix ()
|
||||
|
||||
and check_hostname_minix () =
|
||||
check_hostname_from_file "/etc/hostname.file"
|
||||
44
daemon/inspect_fs_unix.mli
Normal file
44
daemon/inspect_fs_unix.mli
Normal file
@@ -0,0 +1,44 @@
|
||||
(* guestfs-inspection
|
||||
* Copyright (C) 2009-2017 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.
|
||||
*)
|
||||
|
||||
val check_coreos_usr : Mountable.t -> Inspect_types.inspection_data -> unit
|
||||
(** Inspect the CoreOS [/usr] filesystem mounted on sysroot. *)
|
||||
|
||||
val check_coreos_root : Mountable.t -> Inspect_types.inspection_data -> unit
|
||||
(** Inspect the CoreOS filesystem mounted on sysroot. *)
|
||||
|
||||
val check_freebsd_root : Mountable.t -> Inspect_types.inspection_data -> unit
|
||||
(** Inspect the FreeBSD filesystem mounted on sysroot. *)
|
||||
|
||||
val check_hurd_root : Mountable.t -> Inspect_types.inspection_data -> unit
|
||||
(** Inspect the Hurd filesystem mounted on sysroot. *)
|
||||
|
||||
val check_linux_usr : Inspect_types.inspection_data -> unit
|
||||
(** Inspect the Linux [/usr] filesystem mounted on sysroot. *)
|
||||
|
||||
val check_linux_root : Mountable.t -> Inspect_types.inspection_data -> unit
|
||||
(** Inspect the Linux filesystem mounted on sysroot. *)
|
||||
|
||||
val check_minix_root : Inspect_types.inspection_data -> unit
|
||||
(** Inspect the Minix filesystem mounted on sysroot. *)
|
||||
|
||||
val check_netbsd_root : Mountable.t -> Inspect_types.inspection_data -> unit
|
||||
(** Inspect the NetBSD filesystem mounted on sysroot. *)
|
||||
|
||||
val check_openbsd_root : Mountable.t -> Inspect_types.inspection_data -> unit
|
||||
(** Inspect the OpenBSD filesystem mounted on sysroot. *)
|
||||
533
daemon/inspect_fs_unix_fstab.ml
Normal file
533
daemon/inspect_fs_unix_fstab.ml
Normal file
@@ -0,0 +1,533 @@
|
||||
(* guestfs-inspection
|
||||
* Copyright (C) 2009-2017 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 C_utils
|
||||
open Std_utils
|
||||
|
||||
open Utils
|
||||
open Inspect_types
|
||||
open Inspect_utils
|
||||
|
||||
let re_cciss = PCRE.compile "^/dev/(cciss/c\\d+d\\d+)(?:p(\\d+))?$"
|
||||
let re_diskbyid = PCRE.compile "^/dev/disk/by-id/.*-part(\\d+)$"
|
||||
let re_freebsd_gpt = PCRE.compile "^/dev/(ada{0,1}|vtbd)(\\d+)p(\\d+)$"
|
||||
let re_freebsd_mbr = PCRE.compile "^/dev/(ada{0,1}|vtbd)(\\d+)s(\\d+)([a-z])$"
|
||||
let re_hurd_dev = PCRE.compile "^/dev/(h)d(\\d+)s(\\d+)$"
|
||||
let re_mdN = PCRE.compile "^/dev/md\\d+$"
|
||||
let re_netbsd_dev = PCRE.compile "^/dev/(l|s)d([0-9])([a-z])$"
|
||||
let re_openbsd_dev = PCRE.compile "^/dev/(s|w)d([0-9])([a-z])$"
|
||||
let re_openbsd_duid = PCRE.compile "^[0-9a-f]{16}\\.[a-z]"
|
||||
let re_xdev = PCRE.compile "^/dev/(h|s|v|xv)d([a-z]+)(\\d*)$"
|
||||
|
||||
let rec check_fstab ?(mdadm_conf = false) (root_mountable : Mountable.t)
|
||||
os_type =
|
||||
let configfiles =
|
||||
"/etc/fstab" :: if mdadm_conf then ["/etc/mdadm.conf"] else [] in
|
||||
|
||||
with_augeas ~name:"check_fstab_aug"
|
||||
configfiles (check_fstab_aug mdadm_conf root_mountable os_type)
|
||||
|
||||
and check_fstab_aug mdadm_conf root_mountable os_type aug =
|
||||
(* Generate a map of MD device paths listed in /etc/mdadm.conf
|
||||
* to MD device paths in the guestfs appliance.
|
||||
*)
|
||||
let md_map = if mdadm_conf then map_md_devices aug else StringMap.empty in
|
||||
|
||||
let path = "/files/etc/fstab/*[label() != '#comment']" in
|
||||
let entries = aug_matches_noerrors aug path in
|
||||
filter_map (check_fstab_entry md_map root_mountable os_type aug) entries
|
||||
|
||||
and check_fstab_entry md_map root_mountable os_type aug entry =
|
||||
if verbose () then
|
||||
eprintf "check_fstab_entry: augeas path: %s\n%!" entry;
|
||||
|
||||
let is_bsd =
|
||||
match os_type with
|
||||
| OS_TYPE_FREEBSD | OS_TYPE_NETBSD | OS_TYPE_OPENBSD -> true
|
||||
| OS_TYPE_DOS | OS_TYPE_HURD | OS_TYPE_LINUX | OS_TYPE_MINIX
|
||||
| OS_TYPE_WINDOWS -> false in
|
||||
|
||||
let spec = aug_get_noerrors aug (entry ^ "/spec") in
|
||||
let mp = aug_get_noerrors aug (entry ^ "/file") in
|
||||
let vfstype = aug_get_noerrors aug (entry ^ "/vfstype") in
|
||||
|
||||
match spec, mp, vfstype with
|
||||
| None, _, _ | Some _, None, _ | Some _, Some _, None -> None
|
||||
| Some spec, Some mp, Some vfstype ->
|
||||
if verbose () then
|
||||
eprintf "check_fstab_entry: spec=%s mp=%s vfstype=%s\n%!"
|
||||
spec mp vfstype;
|
||||
|
||||
(* Ignore /dev/fd (floppy disks) (RHBZ#642929) and CD-ROM drives.
|
||||
*
|
||||
* /dev/iso9660/FREEBSD_INSTALL can be found in FreeBSD's
|
||||
* installation discs.
|
||||
*)
|
||||
if (String.is_prefix spec "/dev/fd" &&
|
||||
String.length spec >= 8 && Char.isdigit spec.[7]) ||
|
||||
(String.is_prefix spec "/dev/cd" &&
|
||||
String.length spec >= 8 && Char.isdigit spec.[7]) ||
|
||||
spec = "/dev/floppy" ||
|
||||
spec = "/dev/cdrom" ||
|
||||
String.is_prefix spec "/dev/iso9660/" then
|
||||
None
|
||||
else (
|
||||
(* Canonicalize the path, so "///usr//local//" -> "/usr/local" *)
|
||||
let mp = unix_canonical_path mp in
|
||||
|
||||
(* Ignore certain mountpoints. *)
|
||||
if String.is_prefix mp "/dev/" ||
|
||||
mp = "/dev" ||
|
||||
String.is_prefix mp "/media/" ||
|
||||
String.is_prefix mp "/proc/" ||
|
||||
mp = "/proc" ||
|
||||
String.is_prefix mp "/selinux/" ||
|
||||
mp = "/selinux" ||
|
||||
String.is_prefix mp "/sys/" ||
|
||||
mp = "/sys" then
|
||||
None
|
||||
else (
|
||||
let mountable =
|
||||
(* Resolve UUID= and LABEL= to the actual device. *)
|
||||
if String.is_prefix spec "UUID=" then (
|
||||
let uuid = String.sub spec 5 (String.length spec - 5) in
|
||||
let uuid = shell_unquote uuid in
|
||||
Some (Mountable.of_device (Findfs.findfs_uuid uuid))
|
||||
)
|
||||
else if String.is_prefix spec "LABEL=" then (
|
||||
let label = String.sub spec 6 (String.length spec - 6) in
|
||||
let label = shell_unquote label in
|
||||
Some (Mountable.of_device (Findfs.findfs_label label))
|
||||
)
|
||||
(* Resolve /dev/root to the current device.
|
||||
* Do the same for the / partition of the *BSD
|
||||
* systems, since the BSD -> Linux device
|
||||
* translation is not straight forward.
|
||||
*)
|
||||
else if spec = "/dev/root" || (is_bsd && mp = "/") then
|
||||
Some root_mountable
|
||||
(* Resolve guest block device names. *)
|
||||
else if String.is_prefix spec "/dev/" then
|
||||
Some (resolve_fstab_device spec md_map os_type)
|
||||
(* In OpenBSD's fstab you can specify partitions
|
||||
* on a disk by appending a period and a partition
|
||||
* letter to a Disklable Unique Identifier. The
|
||||
* DUID is a 16 hex digit field found in the
|
||||
* OpenBSD's altered BSD disklabel. For more info
|
||||
* see here:
|
||||
* http://www.openbsd.org/faq/faq14.html#intro
|
||||
*)
|
||||
else if PCRE.matches re_openbsd_duid spec then (
|
||||
let part = spec.[17] in
|
||||
(* We cannot peep into disklabels, we can only
|
||||
* assume that this is the first disk.
|
||||
*)
|
||||
let device = sprintf "/dev/sd0%c" part in
|
||||
Some (resolve_fstab_device device md_map os_type)
|
||||
)
|
||||
(* Ignore "/.swap" (Pardus) and pseudo-devices
|
||||
* like "tmpfs". If we haven't resolved the device
|
||||
* successfully by this point, just ignore it.
|
||||
*)
|
||||
else
|
||||
None in
|
||||
|
||||
match mountable with
|
||||
| None -> None
|
||||
| Some mountable ->
|
||||
let mountable =
|
||||
if vfstype = "btrfs" then
|
||||
get_btrfs_mountable aug entry mountable
|
||||
else mountable in
|
||||
|
||||
Some (mountable, mp)
|
||||
)
|
||||
)
|
||||
|
||||
(* If an fstab entry corresponds to a btrfs filesystem, look for
|
||||
* the 'subvol' option and if it is present then return a btrfs
|
||||
* subvolume (else return the whole device).
|
||||
*)
|
||||
and get_btrfs_mountable aug entry mountable =
|
||||
let device =
|
||||
match mountable with
|
||||
| { Mountable.m_type = Mountable.MountableDevice; m_device = device } ->
|
||||
Some device
|
||||
| { Mountable.m_type =
|
||||
(Mountable.MountablePath|Mountable.MountableBtrfsVol _) } ->
|
||||
None in
|
||||
|
||||
match device with
|
||||
| None -> mountable
|
||||
| Some device ->
|
||||
let opts = aug_matches_noerrors aug (entry ^ "/opt") in
|
||||
let rec loop = function
|
||||
| [] -> mountable (* no subvol, return whole device *)
|
||||
| opt :: opts ->
|
||||
let optname = aug_get_noerrors aug opt in
|
||||
match optname with
|
||||
| None -> loop opts
|
||||
| Some "subvol" ->
|
||||
let subvol = aug_get_noerrors aug (opt ^ "/value") in
|
||||
(match subvol with
|
||||
| None -> loop opts
|
||||
| Some subvol ->
|
||||
Mountable.of_btrfsvol device subvol
|
||||
)
|
||||
| Some _ ->
|
||||
loop opts
|
||||
in
|
||||
loop opts
|
||||
|
||||
(* Get a map of md device names in mdadm.conf to their device names
|
||||
* in the appliance.
|
||||
*)
|
||||
and map_md_devices aug =
|
||||
(* Get a map of md device uuids to their device names in the appliance. *)
|
||||
let uuid_map = map_app_md_devices () in
|
||||
|
||||
(* Nothing to do if there are no md devices. *)
|
||||
if StringMap.is_empty uuid_map then StringMap.empty
|
||||
else (
|
||||
(* Get all arrays listed in mdadm.conf. *)
|
||||
let entries = aug_matches_noerrors aug "/files/etc/mdadm.conf/array" in
|
||||
|
||||
(* Log a debug entry if we've got md devices but nothing in mdadm.conf. *)
|
||||
if verbose () && entries = [] then
|
||||
eprintf "warning: appliance has MD devices, but augeas returned no array matches in /etc/mdadm.conf\n%!";
|
||||
|
||||
List.fold_left (
|
||||
fun md_map entry ->
|
||||
try
|
||||
(* Get device name and uuid for each array. *)
|
||||
let dev = aug_get_noerrors aug (entry ^ "/devicename") in
|
||||
let uuid = aug_get_noerrors aug (entry ^ "/uuid") in
|
||||
let dev =
|
||||
match dev with None -> raise Not_found | Some dev -> dev in
|
||||
let uuid =
|
||||
match uuid with None -> raise Not_found | Some uuid -> uuid in
|
||||
|
||||
(* Parse the uuid into an md_uuid structure so we can look
|
||||
* it up in the uuid_map.
|
||||
*)
|
||||
let uuid = parse_md_uuid uuid in
|
||||
|
||||
let md = StringMap.find uuid uuid_map in
|
||||
|
||||
(* If there's a corresponding uuid in the appliance, create
|
||||
* a new entry in the transitive map.
|
||||
*)
|
||||
StringMap.add dev md md_map
|
||||
with
|
||||
(* No Augeas devicename or uuid node found, or could not parse
|
||||
* uuid, or uuid not present in the uuid_map.
|
||||
*
|
||||
* This is not fatal, just ignore the entry.
|
||||
*)
|
||||
Not_found | Invalid_argument _ -> md_map
|
||||
) StringMap.empty entries
|
||||
)
|
||||
|
||||
(* Create a mapping of uuids to appliance md device names. *)
|
||||
and map_app_md_devices () =
|
||||
let mds = Md.list_md_devices () in
|
||||
List.fold_left (
|
||||
fun map md ->
|
||||
let detail = Md.md_detail md in
|
||||
|
||||
try
|
||||
(* Find the value of the "uuid" key. *)
|
||||
let uuid = List.assoc "uuid" detail in
|
||||
let uuid = parse_md_uuid uuid in
|
||||
StringMap.add uuid md map
|
||||
with
|
||||
(* uuid not found, or could not be parsed - just ignore the entry *)
|
||||
Not_found | Invalid_argument _ -> map
|
||||
) StringMap.empty mds
|
||||
|
||||
(* Taken from parse_uuid in mdadm.
|
||||
*
|
||||
* Raises Invalid_argument if the input is not an MD UUID.
|
||||
*)
|
||||
and parse_md_uuid uuid =
|
||||
let len = String.length uuid in
|
||||
let out = Bytes.create len in
|
||||
let j = ref 0 in
|
||||
|
||||
for i = 0 to len-1 do
|
||||
let c = uuid.[i] in
|
||||
if Char.isxdigit c then (
|
||||
Bytes.set out !j c;
|
||||
incr j
|
||||
)
|
||||
else if c = ':' || c = '.' || c = ' ' || c = '-' then
|
||||
()
|
||||
else
|
||||
invalid_arg "parse_md_uuid: invalid character"
|
||||
done;
|
||||
|
||||
if !j <> 32 then
|
||||
invalid_arg "parse_md_uuid: invalid length";
|
||||
|
||||
Bytes.sub_string out 0 !j
|
||||
|
||||
(* Resolve block device name to the libguestfs device name, eg.
|
||||
* /dev/xvdb1 => /dev/vdb1; and /dev/mapper/VG-LV => /dev/VG/LV. This
|
||||
* assumes that disks were added in the same order as they appear to
|
||||
* the real VM, which is a reasonable assumption to make. Return
|
||||
* anything we don't recognize unchanged.
|
||||
*)
|
||||
and resolve_fstab_device spec md_map os_type =
|
||||
(* In any case where we didn't match a device pattern or there was
|
||||
* another problem, return this default mountable derived from [spec].
|
||||
*)
|
||||
let default = Mountable.of_device spec in
|
||||
|
||||
let debug_matching what =
|
||||
if verbose () then
|
||||
eprintf "resolve_fstab_device: %s matched %s\n%!" spec what
|
||||
in
|
||||
|
||||
if String.is_prefix spec "/dev/mapper" then (
|
||||
debug_matching "/dev/mapper";
|
||||
(* LVM2 does some strange munging on /dev/mapper paths for VGs and
|
||||
* LVs which contain '-' character:
|
||||
*
|
||||
* ><fs> lvcreate LV--test VG--test 32
|
||||
* ><fs> debug ls /dev/mapper
|
||||
* VG----test-LV----test
|
||||
*
|
||||
* This makes it impossible to reverse those paths directly, so
|
||||
* we have implemented lvm_canonical_lv_name in the daemon.
|
||||
*)
|
||||
try
|
||||
match Lvm.lv_canonical spec with
|
||||
| None -> Mountable.of_device spec
|
||||
| Some device -> Mountable.of_device device
|
||||
with
|
||||
(* Ignore devices that don't exist. (RHBZ#811872) *)
|
||||
| Unix.Unix_error (Unix.ENOENT, _, _) -> default
|
||||
)
|
||||
|
||||
else if PCRE.matches re_xdev spec then (
|
||||
debug_matching "xdev";
|
||||
let typ = PCRE.sub 1
|
||||
and disk = PCRE.sub 2
|
||||
and part = int_of_string (PCRE.sub 3) in
|
||||
resolve_xdev typ disk part default
|
||||
)
|
||||
|
||||
else if PCRE.matches re_cciss spec then (
|
||||
debug_matching "cciss";
|
||||
let disk = PCRE.sub 1
|
||||
and part = try Some (int_of_string (PCRE.sub 2)) with Not_found -> None in
|
||||
resolve_cciss disk part default
|
||||
)
|
||||
|
||||
else if PCRE.matches re_mdN spec then (
|
||||
debug_matching "md<N>";
|
||||
try
|
||||
Mountable.of_device (StringMap.find spec md_map)
|
||||
with
|
||||
| Not_found -> default
|
||||
)
|
||||
|
||||
else if PCRE.matches re_diskbyid spec then (
|
||||
debug_matching "diskbyid";
|
||||
let part = int_of_string (PCRE.sub 1) in
|
||||
resolve_diskbyid part default
|
||||
)
|
||||
|
||||
else if PCRE.matches re_freebsd_gpt spec then (
|
||||
debug_matching "FreeBSD GPT";
|
||||
(* group 1 (type) is not used *)
|
||||
let disk = int_of_string (PCRE.sub 2)
|
||||
and part = int_of_string (PCRE.sub 3) in
|
||||
|
||||
(* If the FreeBSD disk contains GPT partitions, the translation to Linux
|
||||
* device names is straight forward. Partitions on a virtio disk are
|
||||
* prefixed with [vtbd]. IDE hard drives used to be prefixed with [ad]
|
||||
* and now prefixed with [ada].
|
||||
*)
|
||||
if disk >= 0 && disk <= 26 && part >= 0 && part <= 128 then (
|
||||
let dev = sprintf "/dev/sd%c%d"
|
||||
(Char.chr (disk + Char.code 'a')) part in
|
||||
Mountable.of_device dev
|
||||
)
|
||||
else default
|
||||
)
|
||||
|
||||
else if PCRE.matches re_freebsd_mbr spec then (
|
||||
debug_matching "FreeBSD MBR";
|
||||
(* group 1 (type) is not used *)
|
||||
let disk = int_of_string (PCRE.sub 2)
|
||||
and slice = int_of_string (PCRE.sub 3)
|
||||
(* partition number counting from 0: *)
|
||||
and part = Char.code (PCRE.sub 4).[0] - Char.code 'a' in
|
||||
|
||||
(* FreeBSD MBR disks are organized quite differently. See:
|
||||
* http://www.freebsd.org/doc/handbook/disk-organization.html
|
||||
* FreeBSD "partitions" are exposed as quasi-extended partitions
|
||||
* numbered from 5 in Linux. I have no idea what happens when you
|
||||
* have multiple "slices" (the FreeBSD term for MBR partitions).
|
||||
*)
|
||||
|
||||
(* Partition 'c' has the size of the enclosing slice.
|
||||
* Not mapped under Linux.
|
||||
*)
|
||||
let part = if part > 2 then part - 1 else part in
|
||||
|
||||
if disk >= 0 && disk <= 26 &&
|
||||
slice > 0 && slice <= 1 (* > 4 .. see comment above *) &&
|
||||
part >= 0 && part < 25 then (
|
||||
let dev = sprintf "/dev/sd%c%d"
|
||||
(Char.chr (disk + Char.code 'a')) (part + 5) in
|
||||
Mountable.of_device dev
|
||||
)
|
||||
else default
|
||||
)
|
||||
|
||||
else if os_type = OS_TYPE_NETBSD && PCRE.matches re_netbsd_dev spec then (
|
||||
debug_matching "NetBSD";
|
||||
(* group 1 (type) is not used *)
|
||||
let disk = int_of_string (PCRE.sub 2)
|
||||
(* partition number counting from 0: *)
|
||||
and part = Char.code (PCRE.sub 3).[0] - Char.code 'a' in
|
||||
|
||||
(* Partition 'c' is the disklabel partition and 'd' the hard disk itself.
|
||||
* Not mapped under Linux.
|
||||
*)
|
||||
let part = if part > 3 then part - 2 else part in
|
||||
|
||||
if disk >= 0 && part >= 0 && part < 24 then (
|
||||
let dev = sprintf "/dev/sd%c%d"
|
||||
(Char.chr (disk + Char.code 'a')) (part + 5) in
|
||||
Mountable.of_device dev
|
||||
)
|
||||
else default
|
||||
)
|
||||
|
||||
else if os_type = OS_TYPE_OPENBSD && PCRE.matches re_openbsd_dev spec then (
|
||||
debug_matching "OpenBSD";
|
||||
(* group 1 (type) is not used *)
|
||||
let disk = int_of_string (PCRE.sub 2)
|
||||
(* partition number counting from 0: *)
|
||||
and part = Char.code (PCRE.sub 3).[0] - Char.code 'a' in
|
||||
|
||||
(* Partition 'c' is the hard disk itself. Not mapped under Linux. *)
|
||||
let part = if part > 2 then part - 1 else part in
|
||||
|
||||
(* In OpenBSD MAXPARTITIONS is defined to 16 for all architectures. *)
|
||||
if disk >= 0 && part >= 0 && part < 15 then (
|
||||
let dev = sprintf "/dev/sd%c%d"
|
||||
(Char.chr (disk + Char.code 'a')) (part + 5) in
|
||||
Mountable.of_device dev
|
||||
)
|
||||
else default
|
||||
)
|
||||
|
||||
else if PCRE.matches re_hurd_dev spec then (
|
||||
debug_matching "Hurd";
|
||||
let typ = PCRE.sub 1
|
||||
and disk = int_of_string (PCRE.sub 2)
|
||||
and part = int_of_string (PCRE.sub 3) in
|
||||
|
||||
(* Hurd disk devices are like /dev/hdNsM, where hdN is the
|
||||
* N-th disk and M is the M-th partition on that disk.
|
||||
* Turn the disk number into a letter-based identifier, so
|
||||
* we can resolve it easily.
|
||||
*)
|
||||
let disk = sprintf "%c" (Char.chr (disk + Char.code 'a')) in
|
||||
|
||||
resolve_xdev typ disk part default
|
||||
)
|
||||
|
||||
else (
|
||||
debug_matching "no known device scheme";
|
||||
default
|
||||
)
|
||||
|
||||
(* type: (h|s|v|xv)
|
||||
* disk: [a-z]+
|
||||
* part: \d*
|
||||
*)
|
||||
and resolve_xdev typ disk part default =
|
||||
let devices = Devsparts.list_devices () in
|
||||
let devices = Array.of_list devices in
|
||||
|
||||
(* XXX Check any hints we were passed for a non-heuristic mapping.
|
||||
* The C code used hints here to map device names as known by
|
||||
* the library user (eg. from metadata) to libguestfs devices here.
|
||||
* However none of the libguestfs tools ever used this feature.
|
||||
* Nevertheless we should reimplement it at some point because
|
||||
* outside callers might require it, and it's a good idea in general.
|
||||
*)
|
||||
|
||||
(* Guess the appliance device name if we didn't find a matching hint. *)
|
||||
let i = drive_index disk in
|
||||
if i >= 0 && i < Array.length devices then (
|
||||
let dev = Array.get devices i in
|
||||
let dev = dev ^ string_of_int part in
|
||||
if is_partition dev then
|
||||
Mountable.of_device dev
|
||||
else
|
||||
default
|
||||
)
|
||||
else
|
||||
default
|
||||
|
||||
(* disk: (cciss/c\d+d\d+)
|
||||
* part: (\d+)?
|
||||
*)
|
||||
and resolve_cciss disk part default =
|
||||
(* XXX Check any hints we were passed for a non-heuristic mapping.
|
||||
* The C code used hints here to map device names as known by
|
||||
* the library user (eg. from metadata) to libguestfs devices here.
|
||||
* However none of the libguestfs tools ever used this feature.
|
||||
* Nevertheless we should reimplement it at some point because
|
||||
* outside callers might require it, and it's a good idea in general.
|
||||
*)
|
||||
|
||||
(* We don't try to guess mappings for cciss devices. *)
|
||||
default
|
||||
|
||||
(* For /dev/disk/by-id there is a limit to what we can do because
|
||||
* original SCSI ID information has likely been lost. This
|
||||
* heuristic will only work for guests that have a single block
|
||||
* device.
|
||||
*
|
||||
* So the main task here is to make sure the assumptions above are
|
||||
* true.
|
||||
*
|
||||
* XXX Use hints from virt-p2v if available.
|
||||
* See also: https://bugzilla.redhat.com/show_bug.cgi?id=836573#c3
|
||||
*)
|
||||
and resolve_diskbyid part default =
|
||||
let nr_devices = Devsparts.nr_devices () in
|
||||
|
||||
(* If #devices isn't 1, give up trying to translate this fstab entry. *)
|
||||
if nr_devices <> 1 then
|
||||
default
|
||||
else (
|
||||
(* Make the partition name and check it exists. *)
|
||||
let dev = sprintf "/dev/sda%d" part in
|
||||
if is_partition dev then Mountable.of_device dev
|
||||
else default
|
||||
)
|
||||
34
daemon/inspect_fs_unix_fstab.mli
Normal file
34
daemon/inspect_fs_unix_fstab.mli
Normal file
@@ -0,0 +1,34 @@
|
||||
(* guestfs-inspection
|
||||
* Copyright (C) 2009-2017 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.
|
||||
*)
|
||||
|
||||
val check_fstab : ?mdadm_conf:bool -> Mountable.t -> Inspect_types.os_type ->
|
||||
(Mountable.t * string) list
|
||||
(** [check_fstab] examines the [/etc/fstab] file of a mounted root
|
||||
filesystem, returning the list of devices and their mount points.
|
||||
Various devices (like CD-ROMs) are ignored in the process, and
|
||||
this function also knows how to map (eg) BSD device names into
|
||||
Linux/libguestfs device names.
|
||||
|
||||
[mdadm_conf] is true if you want to check [/etc/mdadm.conf] as well.
|
||||
|
||||
[root_mountable] is the [Mountable.t] of the root filesystem. (Note
|
||||
that the root filesystem must be mounted on sysroot before this
|
||||
function is called.)
|
||||
|
||||
[os_type] is the presumed operating system type of this root, and
|
||||
is used to make some adjustments to fstab parsing. *)
|
||||
Reference in New Issue
Block a user