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:
Richard W.M. Jones
2017-07-17 17:31:00 +01:00
parent cb7696a5d2
commit 394d11be49
9 changed files with 2187 additions and 0 deletions

View File

@@ -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
View 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
View 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
View 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
View 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
View 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"

View 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. *)

View 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
)

View 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. *)