daemon: Reimplement partition GPT functions using sfdisk

sfdisk can now do everything with GPT that sgdisk was needed for
before.  In particular we are able to reimplement the following
functions using sfdisk:

- part_set_disk_guid   (replace with sfdisk --disk-id)
- part_get_disk_guid
- part_set_disk_guid_random
- part_set_gpt_attributes           (sfdisk --part-attrs)
- part_get_gpt_attributes
- part_set_gpt_guid                 (sfdisk --part-uuid)
- part_get_gpt_guid
- part_set_gpt_type                 (sfdisk --part-type)
- part_get_gpt_type

This allows us to drop the requirement for gdisk in many cases.

There is only one API remaining which requires gdisk, part_expand_gpt,
which we do not use in our tools.  In a prior commit I already moved
this solitary function to a new source file (daemon/gdisk.c).

Fixes: https://issues.redhat.com/browse/RHEL-35998
This commit is contained in:
Richard W.M. Jones
2024-05-10 13:27:22 +01:00
parent a25f419802
commit c6c266a85d
8 changed files with 189 additions and 251 deletions

1
.gitignore vendored
View File

@@ -108,6 +108,7 @@ Makefile.in
/daemon/parted.mli
/daemon/realpath.mli
/daemon/rpm.mli
/daemon/sfdisk.mli
/daemon/stamp-guestfsd.pod
/daemon/statvfs.mli
/daemon/structs-cleanups.c

View File

@@ -59,6 +59,7 @@ generator_built = \
parted.mli \
realpath.mli \
rpm.mli \
sfdisk.mli \
statvfs.mli \
structs.ml \
structs.mli
@@ -306,6 +307,7 @@ SOURCES_MLI = \
parted.mli \
realpath.mli \
rpm.mli \
sfdisk.mli \
statvfs.mli \
structs.mli \
sysroot.mli \
@@ -337,6 +339,7 @@ SOURCES_ML = \
md.ml \
mount.ml \
mount_utils.ml \
sfdisk.ml \
parted.ml \
listfs.ml \
realpath.ml \

View File

@@ -419,7 +419,7 @@ and map_registry_disk_blob_gpt partitions blob =
let typ = Parted.part_get_parttype device in
if typ <> "gpt" then false
else (
let guid = Parted.part_get_gpt_guid device partnum in
let guid = Sfdisk.part_get_gpt_guid device partnum in
String.lowercase_ascii guid = blob_guid
)
) partitions in

View File

@@ -114,7 +114,7 @@ and is_partition_can_hold_filesystem partition =
else if is_mbr then
true
else (
let gpt_type = Parted.part_get_gpt_type device partnum in
let gpt_type = Sfdisk.part_get_gpt_type device partnum in
match gpt_type with
(* Windows Logical Disk Manager metadata partition. *)
| "5808C8AA-7E8F-42E0-85D2-E1E90434CFB3"

View File

@@ -456,58 +456,6 @@ do_part_set_mbr_id (const char *device, int partnum, int idbyte)
return 0;
}
int
do_part_set_gpt_type (const char *device, int partnum, const char *guid)
{
if (partnum <= 0) {
reply_with_error ("partition number must be >= 1");
return -1;
}
CLEANUP_FREE char *typecode = NULL;
if (asprintf (&typecode, "%i:%s", partnum, guid) == -1) {
reply_with_perror ("asprintf");
return -1;
}
CLEANUP_FREE char *err = NULL;
int r = commandf (NULL, &err, COMMAND_FLAG_FOLD_STDOUT_ON_STDERR,
"sgdisk", device, "-t", typecode, NULL);
if (r == -1) {
reply_with_error ("%s %s -t %s: %s", "sgdisk", device, typecode, err);
return -1;
}
return 0;
}
int
do_part_set_gpt_guid (const char *device, int partnum, const char *guid)
{
if (partnum <= 0) {
reply_with_error ("partition number must be >= 1");
return -1;
}
CLEANUP_FREE char *typecode = NULL;
if (asprintf (&typecode, "%i:%s", partnum, guid) == -1) {
reply_with_perror ("asprintf");
return -1;
}
CLEANUP_FREE char *err = NULL;
int r = commandf (NULL, &err, COMMAND_FLAG_FOLD_STDOUT_ON_STDERR,
"sgdisk", device, "-u", typecode, NULL);
if (r == -1) {
reply_with_error ("%s %s -u %s: %s", "sgdisk", device, typecode, err);
return -1;
}
return 0;
}
char *
do_part_get_name (const char *device, int partnum)
{
@@ -564,95 +512,3 @@ do_part_get_name (const char *device, int partnum)
return NULL;
}
}
static char *
extract_uuid (const char *value)
{
/* The value contains only valid GUID characters */
const size_t value_len = strspn (value, "-0123456789ABCDEF");
char *ret = malloc (value_len + 1);
if (ret == NULL) {
reply_with_perror ("malloc");
return NULL;
}
memcpy (ret, value, value_len);
ret[value_len] = '\0';
return ret;
}
char *
do_part_get_disk_guid (const char *device)
{
const char *pattern = "Disk identifier (GUID):";
size_t i;
CLEANUP_FREE char *err = NULL;
int r = commandf (NULL, &err, COMMAND_FLAG_FOLD_STDOUT_ON_STDERR,
"sgdisk", device, "-p", NULL);
if (r == -1) {
reply_with_error ("%s %s -p: %s", "sgdisk", device, err);
return NULL;
}
CLEANUP_FREE_STRING_LIST char **lines = split_lines (err);
if (lines == NULL) {
reply_with_error ("'%s %s -p' returned no output",
"sgdisk", device);
return NULL;
}
for (i = 0; lines[i] != NULL; ++i) {
if (STRPREFIX (lines[i], pattern)) {
char *value = lines[i] + strlen (pattern);
/* Skip any leading whitespace */
value += strspn (value, " \t");
/* Extract the actual information from the field. */
char *ret = extract_uuid (value);
if (ret == NULL) {
/* The extraction function already sends the error. */
return NULL;
}
return ret;
}
}
/* If we got here it means we didn't find the field */
reply_with_error ("sgdisk output did not contain disk GUID. "
"See LIBGUESTFS_DEBUG output for more details");
return NULL;
}
int
do_part_set_disk_guid (const char *device, const char *guid)
{
CLEANUP_FREE char *err = NULL;
int r = commandf (NULL, &err, COMMAND_FLAG_FOLD_STDOUT_ON_STDERR,
"sgdisk", device, "-U", guid, NULL);
if (r == -1) {
reply_with_error ("%s %s -U %s: %s", "sgdisk", device, guid, err);
return -1;
}
return 0;
}
int
do_part_set_disk_guid_random (const char *device)
{
CLEANUP_FREE char *err = NULL;
int r = commandf (NULL, &err, COMMAND_FLAG_FOLD_STDOUT_ON_STDERR,
"sgdisk", device, "-U", "R", NULL);
if (r == -1) {
reply_with_error ("%s %s -U R: %s", "sgdisk", device, err);
return -1;
}
return 0;
}

View File

@@ -25,18 +25,6 @@ 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.
*)
@@ -110,7 +98,7 @@ let part_get_parttype 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
let mbr_id = Sfdisk.part_get_mbr_id device partnum in
(* 0x05 - extended partition.
* 0x0f - extended partition using BIOS INT 13h extensions.
@@ -120,81 +108,3 @@ let part_get_mbr_part_type device partnum =
| "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

172
daemon/sfdisk.ml Normal file
View File

@@ -0,0 +1,172 @@
(* 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
let part_get_gpt_type 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 ();
String.trimr out
let part_set_gpt_type device partnum typ =
if partnum <= 0 then
failwith "partition number must be >= 1";
udev_settle ();
let cmd =
sprintf "sfdisk --part-type %s %d %s"
(quote device) partnum (quote typ) in
if verbose () then eprintf "%s\n%!" cmd;
if Sys.command cmd <> 0 then failwith "sfdisk --part-type failed";
udev_settle ()
let part_get_gpt_guid device partnum =
if partnum <= 0 then
failwith "partition number must be >= 1";
udev_settle ();
let out =
command "sfdisk" ["--part-uuid"; device; string_of_int partnum] in
udev_settle ();
String.trimr out
let part_set_gpt_guid device partnum guid =
if partnum <= 0 then
failwith "partition number must be >= 1";
udev_settle ();
let cmd =
sprintf "sfdisk --part-uuid %s %d %s"
(quote device) partnum (quote guid) in
if verbose () then eprintf "%s\n%!" cmd;
if Sys.command cmd <> 0 then failwith "sfdisk --part-uuid failed";
udev_settle ()
let part_get_disk_guid device =
udev_settle ();
let out =
command "sfdisk" ["--disk-id"; device] in
udev_settle ();
String.trimr out
let part_set_disk_guid device guid =
udev_settle ();
let cmd =
sprintf "sfdisk --disk-id %s %s"
(quote device) (quote guid) in
if verbose () then eprintf "%s\n%!" cmd;
if Sys.command cmd <> 0 then failwith "sfdisk --disk-id failed";
udev_settle ()
let part_set_disk_guid_random device =
let random_uuid = Utils.get_random_uuid () in
let random_uuid = String.trimr random_uuid in
part_set_disk_guid device random_uuid
let part_get_gpt_attributes device partnum =
if partnum <= 0 then
failwith "partition number must be >= 1";
udev_settle ();
let out =
command "sfdisk" ["--part-attrs"; device; string_of_int partnum] in
udev_settle ();
(* The output is a whitespace-separated list of:
* "RequiredPartition" (equivalent to bit 0)
* "NoBlockIOProtocol" (equivalent to bit 1)
* "LegacyBIOSBootable" (equivalent to bit 2)
* "48", "49", ..., "63"
*)
let out = String.trimr out in
let attrs = String.nsplit " " out in
List.fold_left (
fun bits attr ->
let bit =
match attr with
| "" -> -1
| "RequiredPartition" -> 0
| "NoBlockIOProtocol" -> 1
| "LegacyBIOSBootable" -> 2
| n -> int_of_string n in
if bit >= 0 then
Int64.logor bits (Int64.shift_left 1_L bit)
else
bits
) 0_L attrs
let part_set_gpt_attributes device partnum attrs =
if partnum <= 0 then
failwith "partition number must be >= 1";
(* The input to sfdisk --part-attrs is a comma-separated list of
* attribute names or bit positions. Note you have to use the
* names, you can't use "0", "1" or "2".
*)
let s = ref [] in
let rec loop i =
let b = Int64.logand attrs (Int64.shift_left 1_L i) <> Int64.zero in
(match i with
| 0 -> if b then List.push_front "RequiredPartition" s
| 1 -> if b then List.push_front "NoBlockIOProtocol" s
| 2 -> if b then List.push_front "LegacyBIOSBootable" s
| i when i >= 3 && i <= 47 ->
if b then
failwith "bits 3..47 are reserved and cannot be set"
| i when i >= 48 && i <= 63 ->
if b then List.push_front (string_of_int i) s
| _ -> assert false
);
if i < 63 then loop (i+1)
in
loop 0;
udev_settle ();
let cmd =
sprintf "sfdisk --part-attrs %s %d %s"
(quote device) partnum (quote (String.concat "," !s)) in
if verbose () then eprintf "%s\n%!" cmd;
if Sys.command cmd <> 0 then failwith "sfdisk --part-attrs failed";
udev_settle ()

View File

@@ -5309,7 +5309,7 @@ See also C<guestfs_part_set_bootable>." };
{ defaults with
name = "part_get_mbr_id"; added = (1, 3, 2);
style = RInt "idbyte", [String (Device, "device"); Int "partnum"], [];
impl = OCaml "Parted.part_get_mbr_id";
impl = OCaml "Sfdisk.part_get_mbr_id";
fish_output = Some FishOutputHexadecimal;
tests = [
InitEmpty, Always, TestResult (
@@ -8136,7 +8136,7 @@ group with GUID C<diskgroup>." };
{ defaults with
name = "part_set_gpt_type"; added = (1, 21, 1);
style = RErr, [String (Device, "device"); Int "partnum"; String (GUID, "guid")], [];
optional = Some "gdisk";
impl = OCaml "Sfdisk.part_set_gpt_type";
tests = [
InitGPT, Always, TestLastFail (
[["part_set_gpt_type"; "/dev/sda"; "1"; "f"]]), [];
@@ -8158,8 +8158,7 @@ for a useful list of type GUIDs." };
{ defaults with
name = "part_get_gpt_type"; added = (1, 21, 1);
style = RString (RPlainString, "guid"), [String (Device, "device"); Int "partnum"], [];
impl = OCaml "Parted.part_get_gpt_type";
optional = Some "gdisk";
impl = OCaml "Sfdisk.part_get_gpt_type";
tests = [
InitGPT, Always, TestResultString (
[["part_set_gpt_type"; "/dev/sda"; "1";
@@ -8174,8 +8173,7 @@ Return the type GUID of numbered GPT partition C<partnum>." };
{ defaults with
name = "part_set_gpt_attributes"; added = (1, 21, 1);
style = RErr, [String (Device, "device"); Int "partnum"; Int64 "attributes"], [];
impl = OCaml "Parted.part_set_gpt_attributes";
optional = Some "gdisk";
impl = OCaml "Sfdisk.part_set_gpt_attributes";
tests = [
InitGPT, Always, TestResult (
[["part_set_gpt_attributes"; "/dev/sda"; "1";
@@ -8194,8 +8192,7 @@ for a useful list of partition attributes." };
{ defaults with
name = "part_get_gpt_attributes"; added = (1, 21, 1);
style = RInt64 "attributes", [String (Device, "device"); Int "partnum"], [];
impl = OCaml "Parted.part_get_gpt_attributes";
optional = Some "gdisk";
impl = OCaml "Sfdisk.part_get_gpt_attributes";
tests = [
InitGPT, Always, TestResult (
[["part_set_gpt_attributes"; "/dev/sda"; "1";
@@ -8995,7 +8992,7 @@ Recover bad superblocks from good copies." };
{ defaults with
name = "part_set_gpt_guid"; added = (1, 29, 25);
style = RErr, [String (Device, "device"); Int "partnum"; String (GUID, "guid")], [];
optional = Some "gdisk";
impl = OCaml "Sfdisk.part_set_gpt_guid";
tests = [
InitGPT, Always, TestLastFail (
[["part_set_gpt_guid"; "/dev/sda"; "1"; "f"]]), [];
@@ -9014,8 +9011,7 @@ valid GUID." };
{ defaults with
name = "part_get_gpt_guid"; added = (1, 29, 25);
style = RString (RPlainString, "guid"), [String (Device, "device"); Int "partnum"], [];
impl = OCaml "Parted.part_get_gpt_guid";
optional = Some "gdisk";
impl = OCaml "Sfdisk.part_get_gpt_guid";
tests = [
InitGPT, Always, TestResultString (
[["part_set_gpt_guid"; "/dev/sda"; "1";
@@ -9214,7 +9210,7 @@ This is the internal call which implements C<guestfs_feature_available>." };
{ defaults with
name = "part_set_disk_guid"; added = (1, 33, 2);
style = RErr, [String (Device, "device"); String (GUID, "guid")], [];
optional = Some "gdisk";
impl = OCaml "Sfdisk.part_set_disk_guid";
tests = [
InitGPT, Always, TestLastFail (
[["part_set_disk_guid"; "/dev/sda"; "f"]]), [];
@@ -9233,7 +9229,7 @@ or if C<guid> is not a valid GUID." };
{ defaults with
name = "part_get_disk_guid"; added = (1, 33, 2);
style = RString (RPlainString, "guid"), [String (Device, "device")], [];
optional = Some "gdisk";
impl = OCaml "Sfdisk.part_get_disk_guid";
tests = [
InitGPT, Always, TestResultString (
[["part_set_disk_guid"; "/dev/sda";
@@ -9249,7 +9245,7 @@ Behaviour is undefined for other partition types." };
{ defaults with
name = "part_set_disk_guid_random"; added = (1, 33, 2);
style = RErr, [String (Device, "device")], [];
optional = Some "gdisk";
impl = OCaml "Sfdisk.part_set_disk_guid_random";
tests = [
InitGPT, Always, TestRun (
[["part_set_disk_guid_random"; "/dev/sda"]]), [];