Created
November 5, 2017 17:48
-
-
Save paurkedal/80b89c8fabe041e62eccc596d51f382b to your computer and use it in GitHub Desktop.
A reformatted file from https://github.com/libguestfs/libguestfs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(* 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)) | |
else | |
(* 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. | |
*) | |
if spec = "/dev/root" || (is_bsd && mp = "/") then | |
Some root_mountable else | |
(* Resolve guest block device names. *) | |
if String.is_prefix spec "/dev/" then | |
Some (resolve_fstab_device spec md_map os_type) else | |
(* 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 | |
*) | |
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) | |
else | |
(* Ignore "/.swap" (Pardus) and pseudo-devices | |
* like "tmpfs". If we haven't resolved the device | |
* successfully by this point, just ignore it. | |
*) | |
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. *) | |
let () = if verbose () && entries = [] then | |
eprintf "warning: appliance has MD devices, but augeas returned no \ | |
array matches in /etc/mdadm.conf\n%!" in | |
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment