(* guestfs-inspection * Copyright (C) 2009-2020 Red Hat Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *) open Scanf open Printf open Std_utils open Utils include Structs (* Test if [sfdisk] is recent enough to have [--part-type], to be used * instead of [--print-id] and [--change-id]. *) let test_sfdisk_has_part_type = lazy ( let out = command "sfdisk" ["--help"] in String.find out "--part-type" >= 0 ) (* Currently we use sfdisk for getting and setting the ID byte. In * future, extend parted to provide this functionality. As a result * of using sfdisk, this won't work for non-MBR-style partitions, but * that limitation is noted in the documentation and we can extend it * later without breaking the ABI. *) let part_get_mbr_id device partnum = if partnum <= 0 then failwith "partition number must be >= 1"; let param = if Lazy.force test_sfdisk_has_part_type then "--part-type" else "--print-id" in udev_settle (); let out = command "sfdisk" [param; device; string_of_int partnum] in udev_settle (); (* It's printed in hex, possibly with a leading space. *) sscanf out " %x" identity (* This is almost equivalent to print_partition_table in the C code. The * difference is that here we enforce the "BYT;" header internally. *) let print_partition_table_machine_readable device = udev_settle (); let args = ref [] in List.push_back args "-m"; List.push_back args "-s"; List.push_back args "--"; List.push_back args device; List.push_back args "unit"; List.push_back args "b"; List.push_back args "print"; let out = try command "parted" !args with (* Translate "unrecognised disk label" into an errno code. *) Failure str when String.find str "unrecognised disk label" >= 0 -> raise (Unix.Unix_error (Unix.EINVAL, "parted", device ^ ": " ^ str)) in udev_settle (); (* Split the output into lines. *) let out = String.trim out in let lines = String.nsplit "\n" out in (* lines[0] is "BYT;", lines[1] is the device line, * lines[2..] are the partitions themselves. *) match lines with | "BYT;" :: device_line :: lines -> device_line, lines | [] | [_] -> failwith "too few rows of output from 'parted print' command" | _ -> failwith "did not see 'BYT;' magic value in 'parted print' command" let part_list device = let _, lines = print_partition_table_machine_readable device in List.map ( fun line -> try sscanf line "%d:%LdB:%LdB:%LdB" (fun num start end_ size -> { part_num = Int32.of_int num; part_start = start; part_end = end_; part_size = size }) with Scan_failure err -> failwithf "could not parse row from output of 'parted print' command: %s: %s" line err ) lines let part_get_parttype device = let device_line, _ = print_partition_table_machine_readable device in (* device_line is something like: * "/dev/sda:1953525168s:scsi:512:512:msdos:ATA Hitachi HDT72101;" *) let fields = String.nsplit ":" device_line in match fields with | _::_::_::_::_::"loop"::_ -> (* If "loop" return an error (RHBZ#634246). *) (* ... Unless parted failed to recognize the fake MBR that mkfs.fat from * dosfstools-4.2+ created. In that case, return "msdos" for MBR * (RHBZ#1931821). *) if Utils.has_bogus_mbr device then "msdos" else failwithf "%s: not a partitioned device" device | _::_::_::_::_::ret::_ -> ret | _ -> failwithf "%s: cannot parse the output of parted" device let part_get_mbr_part_type device partnum = let parttype = part_get_parttype device in let mbr_id = part_get_mbr_id device partnum in (* 0x05 - extended partition. * 0x0f - extended partition using BIOS INT 13h extensions. *) match parttype, partnum, mbr_id with | "msdos", (1|2|3|4), (0x05|0x0f) -> "extended" | "msdos", (1|2|3|4), _ -> "primary" | "msdos", _, _ -> "logical" | _, _, _ -> "primary" let part_set_gpt_attributes device partnum attributes = if partnum <= 0 then failwith "partition number must be >= 1"; udev_settle (); let arg = sprintf "%d:=:%LX" partnum attributes in let r, _, err = commandr ~fold_stdout_on_stderr:true "sgdisk" [ device; "-A"; arg ] in if r <> 0 then failwithf "sgdisk: %s" err; udev_settle () let extract_guid value = (* The value contains only valid GUID characters. *) String.sub value 0 (String.span value "-0123456789ABCDEF") let extract_hex value = (* The value contains only valid numeric characters. *) let str = String.sub value 0 (String.span value "0123456789ABCDEF") in Int64.of_string ("0x" ^ str) let sgdisk_info_extract_field device partnum field extractor = if partnum <= 0 then failwith "partition number must be >= 1"; udev_settle (); let r, _, err = commandr ~fold_stdout_on_stderr:true "sgdisk" [ device; "-i"; string_of_int partnum ] in if r <> 0 then failwithf "sgdisk: %s" err; udev_settle (); let err = String.trim err in let lines = String.nsplit "\n" err in (* Parse the output of sgdisk -i: * Partition GUID code: 21686148-6449-6E6F-744E-656564454649 (BIOS boot partition) * Partition unique GUID: 19AEC5FE-D63A-4A15-9D37-6FCBFB873DC0 * First sector: 2048 (at 1024.0 KiB) * Last sector: 411647 (at 201.0 MiB) * Partition size: 409600 sectors (200.0 MiB) * Attribute flags: 0000000000000000 * Partition name: 'EFI System Partition' *) let field_len = String.length field in let rec loop = function | [] -> failwithf "%s: sgdisk output did not contain '%s'" device field | line :: _ when String.is_prefix line field && String.length line >= field_len + 2 && line.[field_len] = ':' -> let value = String.sub line (field_len+1) (String.length line - field_len - 1) in (* Skip any whitespace after the colon. *) let value = String.triml value in (* Extract the value. *) extractor value | _ :: lines -> loop lines in loop lines let rec part_get_gpt_type device partnum = sgdisk_info_extract_field device partnum "Partition GUID code" extract_guid and part_get_gpt_guid device partnum = sgdisk_info_extract_field device partnum "Partition unique GUID" extract_guid and part_get_gpt_attributes device partnum = sgdisk_info_extract_field device partnum "Attribute flags" extract_hex