Files
libguestfs/daemon/parted.ml
Richard W.M. Jones c56d8e9da1 daemon/parted: Assume sfdisk --part-type exists
This "new" parameter was added in 2014:

  commit 8eab3194ce1737a167812d5e84d83b0dfc253fac
  Author: Karel Zak <kzak@redhat.com>
  Date:   Mon Sep 15 12:37:52 2014 +0200

    sfdisk: add --parttype

    The patch also makes --{id,change-id,print-id} deprecated in favour
    of --parttype. The original --id is too generic option name and the
    --print-id and --change-id are unnecessary and inconsistent with
    another sfdisk options (e.g. we don't have --change-bootable)

Also remove an extraneous / incorrect comment about parted.  As
history has played out, sfdisk proves to be the better tool and parted
is a PITA.

(cherry picked from commit 857615d6d2)
(cherry picked from commit c749314646)
2024-07-09 14:08:41 +01:00

201 lines
6.5 KiB
OCaml

(* guestfs-inspection
* Copyright (C) 2009-2023 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
let part_get_mbr_id device partnum =
if partnum <= 0 then
failwith "partition number must be >= 1";
udev_settle ();
let out =
command "sfdisk" ["--part-type"; 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 "getting %S: sgdisk: %s" field 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