From aa55572c980da038e675fc963d98a90964a2ce33 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Fri, 28 Mar 2014 17:10:01 +0000 Subject: GPT syncing for Intel Macs On Intel Mac systems, write a synced MBR rather than a protective MBR. From: Colin Watson Forwarded: no Last-Update: 2022-04-19 Patch-Name: gptsync.patch --- libparted/labels/gpt.c | 205 +++++++++++++++++++++++++++++++++++++++-- tests/Makefile.am | 1 + tests/t0290-gptsync.sh | 175 +++++++++++++++++++++++++++++++++++ 3 files changed, 371 insertions(+), 10 deletions(-) create mode 100644 tests/t0290-gptsync.sh diff --git a/libparted/labels/gpt.c b/libparted/labels/gpt.c index 130b9bd1..bebedb47 100644 --- a/libparted/labels/gpt.c +++ b/libparted/labels/gpt.c @@ -369,6 +369,40 @@ typedef struct _GPTPartitionData static PedDiskType gpt_disk_type; + +#if (defined(__i386__) || defined(__x86_64__)) && defined(__linux__) +# define USE_DMI +#endif + +#define APPLE_DMI "Apple Computer, Inc." +#define APPLE_DMI_2 "Apple Inc." +static int is_apple = 0; + +static char * +dmi_system_manufacturer (void) +{ +#ifdef USE_DMI + FILE *dmidecode; + char *manufacturer = NULL; + size_t manufacturer_len = 0; + + dmidecode = popen ("dmidecode -s system-manufacturer 2>/dev/null", "r"); + if (getline (&manufacturer, &manufacturer_len, dmidecode) < 0) { + /* ignore; will return NULL */ + } + pclose (dmidecode); + if (manufacturer) { + char *newline = strchr (manufacturer, '\n'); + if (newline) + *newline = '\0'; + } + return manufacturer; +#else /* !USE_DMI */ + return NULL; +#endif /* USE_DMI */ +} + + static inline uint32_t pth_get_size (const PedDevice *dev) { @@ -544,16 +578,19 @@ gpt_probe (const PedDevice *dev) if (dev->length <= 1) return 0; - void *label; - if (!ptt_read_sector (dev, 0, &label)) - return 0; - - if (!_pmbr_is_valid (label)) + if (!is_apple) { + void *label; + if (!ptt_read_sector (dev, 0, &label)) + return 0; + + if (!_pmbr_is_valid (label)) + { + free (label); + return 0; + } free (label); - return 0; } - free (label); void *pth_raw = ped_malloc (pth_get_size (dev)); if (ped_device_read (dev, pth_raw, 1, GPT_HEADER_SECTORS) @@ -973,6 +1010,10 @@ gpt_read_headers (PedDisk const *disk, * warn if it's not there, and treat the disk as MSDOS, with a note * for users to use Parted to "fix up" their disk if they * really want it to be considered GPT. + * + * Of course, this is incompatible with how Apple handle things. For + * legacy BIOS compatibility on Apple machines, we need a valid legacy MBR + * rather than a protective one. Aren't standards wonderful? ************************************************************/ static int gpt_read (PedDisk *disk) @@ -1181,6 +1222,129 @@ _write_pmbr (PedDevice *dev, bool pmbr_boot) return write_ok; } +static void +fill_raw_part (PartitionRecord_t* raw_part, PedPartition *part, PedSector offset, int number) +{ + GPTPartitionData* gpt_part_data = part->disk_specific; + + if (part->fs_type) { + if (strncmp (part->fs_type->name, "fat", 3) == 0) + raw_part->OSType = 0x0b; + else if (strncmp (part->fs_type->name, "ntfs", 4) == 0) + raw_part->OSType = 0x07; + else if (strncmp (part->fs_type->name, "hfs", 3) == 0) + raw_part->OSType = 0xaf; + else if (strncmp (part->fs_type->name, "linux-swap", 10) == 0) + raw_part->OSType = 0x82; + else + raw_part->OSType = 0x83; + } else + raw_part->OSType = 0xda; + + /* Apple's firmware appears to become unhappy if the second partition + isn't bootable */ + + if (number == 2) + raw_part->BootIndicator = 0x80; + + raw_part->StartingLBA = PED_CPU_TO_LE32 ((part->geom.start - offset) + / (part->disk->dev->sector_size / 512)); + + raw_part->SizeInLBA = PED_CPU_TO_LE32 (part->geom.length + / (part->disk->dev->sector_size / 512)); + + /* EFI system partitions will have a FAT filesystem and + PARTITION_SYSTEM_GUID; however, it is not wise to rely on filesystem + probing */ + + if (number == 1) { + if (!guid_cmp (gpt_part_data->type, PARTITION_SYSTEM_GUID) || + !guid_cmp (gpt_part_data->type, PARTITION_BIOS_GRUB_GUID)) { + raw_part->OSType = EFI_PMBR_OSTYPE_EFI; + raw_part->OSType = EFI_PMBR_OSTYPE_EFI; + } + } + + /* Apple's firmware also appears to be unhappy if the EFI system + partition doesn't extend all the way to the start of the disk */ + + if (number == 1 && raw_part->OSType == EFI_PMBR_OSTYPE_EFI) { + raw_part->StartSector = 1; + raw_part->SizeInLBA += raw_part->StartingLBA - 1; + raw_part->StartingLBA = 1; + } else { + raw_part->StartHead = 0xfe; + raw_part->StartSector = 0xff; + raw_part->StartTrack = 0xff; + } + + raw_part->EndHead = 0xfe; + raw_part->EndSector = 0xff; + raw_part->EndTrack = 0xff; +} + +static int +_gptsync (const PedDisk *disk) +{ + void *s0; + PedPartition* part; + int i; + + if (!ptt_read_sector (disk->dev, GPT_PMBR_LBA, &s0)) + return 0; + LegacyMBR_t *pmbr = s0; + + int ok = 0; + + memset(&pmbr->PartitionRecord, 0, sizeof(pmbr->PartitionRecord)); + pmbr->Signature = PED_CPU_TO_LE16(MSDOS_MBR_SIGNATURE); + + bool prot = false; /* have we found a protective partition? */ + for (i=1; i<=4; i++) { + part = ped_disk_get_partition (disk, i); + if (!part) + continue; + + fill_raw_part (&pmbr->PartitionRecord [i - 1], part, 0, i); + if (pmbr->PartitionRecord[i - 1].OSType == EFI_PMBR_OSTYPE_EFI) + prot = true; + } + + if (!prot) { /* create one covering the gpt entries */ + uint32_t prot_size; + for (i=2; i>=0; i--) + pmbr->PartitionRecord[i + 1] = pmbr->PartitionRecord[i]; + memset (&pmbr->PartitionRecord[0], 0, sizeof pmbr->PartitionRecord[0]); + pmbr->PartitionRecord[0].OSType = EFI_PMBR_OSTYPE_EFI; + pmbr->PartitionRecord[0].StartSector = 1; + pmbr->PartitionRecord[0].EndHead = 0xfe; + pmbr->PartitionRecord[0].EndSector = 0xff; + pmbr->PartitionRecord[0].EndTrack = 0xff; + pmbr->PartitionRecord[0].StartingLBA = PED_CPU_TO_LE32 (1); + if ((disk->dev->length - 1ULL) > 0xFFFFFFFFULL) + prot_size = 0xFFFFFFFF; + else + prot_size = disk->dev->length - 1UL; + for (i=1; i<=3; i++) { + if (pmbr->PartitionRecord[i].StartingLBA) { + uint32_t starting_lba = + PED_LE32_TO_CPU (pmbr->PartitionRecord[i].StartingLBA); + if (starting_lba - 1 < prot_size) + prot_size = starting_lba - 1; + } + } + pmbr->PartitionRecord[0].SizeInLBA = PED_CPU_TO_LE32 (prot_size); + } + + if (!ped_device_write (disk->dev, pmbr, GPT_PMBR_LBA, GPT_PMBR_SECTORS)) + goto error; + + ok = ped_device_sync (disk->dev); +error: + free (s0); + return ok; +} + static int _generate_header (const PedDisk *disk, int alternate, uint32_t ptes_crc, GuidPartitionTableHeader_t **gpt_p) @@ -1287,9 +1451,15 @@ gpt_write (const PedDisk *disk) ptes_crc = efi_crc32 (ptes, ptes_bytes); - /* Write protective MBR */ - if (!_write_pmbr (disk->dev, gpt_disk_data->pmbr_boot)) - goto error_free_ptes; + if (is_apple) { + /* Write synced MBR */ + if (!_gptsync (disk)) + goto error_free_ptes; + } else { + /* Write protective MBR */ + if (!_write_pmbr (disk->dev, gpt_disk_data->pmbr_boot)) + goto error_free_ptes; + } /* Write PTH and PTEs */ /* FIXME: Caution: this code is nearly identical to what's just below. */ @@ -1938,6 +2108,21 @@ void ped_disk_gpt_init () { ped_disk_type_register (&gpt_disk_type); + + char *force_gpt_apple = getenv ("PARTED_GPT_APPLE"); + if (force_gpt_apple) { + if (strcmp (force_gpt_apple, "1") == 0) + is_apple = 1; + } else { + char *manufacturer = dmi_system_manufacturer (); + if (manufacturer && + (strncasecmp (APPLE_DMI, manufacturer, + strlen (APPLE_DMI)) == 0 || + strncasecmp (APPLE_DMI_2, manufacturer, + strlen (APPLE_DMI_2)) == 0)) + is_apple = 1; + free (manufacturer); + } } void diff --git a/tests/Makefile.am b/tests/Makefile.am index da093119..3c1f929a 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -32,6 +32,7 @@ TESTS = \ t0282-gpt-move-backup.sh \ t0283-overlap-partitions.sh \ t0290-gpt-name.sh \ + t0290-gptsync.sh \ t0300-dos-on-gpt.sh \ t0301-overwrite-gpt-pmbr.sh \ t0350-mac-PT-increases-sector-size.sh \ diff --git a/tests/t0290-gptsync.sh b/tests/t0290-gptsync.sh new file mode 100644 index 00000000..367d61bd --- /dev/null +++ b/tests/t0290-gptsync.sh @@ -0,0 +1,175 @@ +#!/bin/sh +# test GPT -> hybrid MBR syncing for Apple systems +# http://www.rodsbooks.com/gdisk/hybrid.html + +# Copyright (C) 2012 Canonical Ltd. + +# 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 3 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, see . + +if test "$VERBOSE" = yes; then + set -x + parted --version +fi + +: ${srcdir=.} +. $srcdir/t-lib.sh + +require_root_ +require_scsi_debug_module_ + +ss=$sector_size_ +# must be big enough for a 32MiB partition in order to be big enough for FAT16 +n_sectors=131072 +bootcode_size=446 +mbr_table_size=$((512 - $bootcode_size)) + +dump_mbr_table () { + dd if=$dev bs=1 skip=$bootcode_size count=$mbr_table_size 2>/dev/null | od -v -An -tx1 +} + +# create memory-backed device +sectors_per_MiB=$((1024 * 1024 / $ss)) +n_MiB=$((($n_sectors + $sectors_per_MiB - 1) / $sectors_per_MiB)) +scsi_debug_setup_ dev_size_mb=$n_MiB > dev-name || + skip_test_ 'failed to create scsi_debug device' +dev=$(cat dev-name) + +# force Apple mode +export PARTED_GPT_APPLE=1 + +# create gpt label +parted -s $dev mklabel gpt > empty 2>&1 || fail=1 +compare /dev/null empty || fail=1 + +# print the empty table +parted -m -s $dev unit s print > t 2>&1 || fail=1 +sed "s,.*/$dev:,$dev:," t > out || fail=1 + +# check for expected output +printf "BYT;\n$dev:${n_sectors}s:scsi:$sector_size_:$sector_size_:gpt:Linux scsi_debug;\n" \ + > exp || fail=1 +compare exp out || fail=1 + +# the empty table should have a MBR containing only a protective entry +dump_mbr_table > out || fail=1 +cat < exp || fail=1 + 00 00 01 00 ee fe ff ff 01 00 00 00 ff ff 01 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 55 aa +EOF +compare exp out || fail=1 + +# create a 32MiB FAT16 EFI System Partition +parted -s $dev mkpart p1 fat16 2048s 67583s set 1 boot on > empty 2>&1 || fail=1 +compare /dev/null empty || fail=1 +mkfs.vfat -F 16 ${dev}1 >/dev/null || skip_ "mkfs.vfat failed" + +# this is represented as a protective partition, but now it only extends as +# far as the end of the first partition rather than covering the whole disk +# (matching refit gptsync's strategy); it still starts at sector one +dump_mbr_table > out || fail=1 +cat < exp || fail=1 + 00 00 01 00 ee fe ff ff 01 00 00 00 ff 07 01 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 55 aa +EOF +compare exp out || fail=1 + +# add a 2MiB ext3 partition +parted -s $dev mkpart p2 ext3 67584s 71679s > empty 2>&1 || fail=1 +compare /dev/null empty || fail=1 +mkfs.ext3 ${dev}2 >/dev/null 2>&1 || skip_ "mkfs.ext3 failed" + +# this should have an MBR representing both partitions; the second partition +# should be marked bootable +dump_mbr_table > out || fail=1 +cat < exp || fail=1 + 00 00 01 00 ee fe ff ff 01 00 00 00 ff 07 01 00 + 80 fe ff ff 83 fe ff ff 00 08 01 00 00 10 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 55 aa +EOF +compare exp out || fail=1 + +# add a 1MiB partition with no filesystem +parted -s $dev mkpart p3 71680s 73727s > empty 2>&1 || fail=1 +compare /dev/null empty || fail=1 + +# the new partition should be represented as 0xda (Non-FS data) +dump_mbr_table > out || fail=1 +cat < exp || fail=1 + 00 00 01 00 ee fe ff ff 01 00 00 00 ff 07 01 00 + 80 fe ff ff 83 fe ff ff 00 08 01 00 00 10 00 00 + 00 fe ff ff da fe ff ff 00 18 01 00 00 08 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 55 aa +EOF +compare exp out || fail=1 + +# add two more 1MiB partitions +parted -s $dev mkpart p4 73728s 75775s > empty 2>&1 || fail=1 +parted -s $dev mkpart p5 75776s 77823s > empty 2>&1 || fail=1 + +# only the first four partitions will be represented +dump_mbr_table > out || fail=1 +cat < exp || fail=1 + 00 00 01 00 ee fe ff ff 01 00 00 00 ff 07 01 00 + 80 fe ff ff 83 fe ff ff 00 08 01 00 00 10 00 00 + 00 fe ff ff da fe ff ff 00 18 01 00 00 08 00 00 + 00 fe ff ff da fe ff ff 00 20 01 00 00 08 00 00 + 55 aa +EOF +compare exp out || fail=1 + +# convert first partition to a BIOS Boot Partition +parted -s $dev set 1 boot off set 1 bios_grub on > empty 2>&1 || fail=1 +compare /dev/null empty || fail=1 + +# this should be represented in the same way as an EFI System Partition +dump_mbr_table > out || fail=1 +compare exp out || fail=1 + +# convert first partition to an ordinary FAT partition +parted -s $dev set 1 bios_grub off > empty 2>&1 || fail=1 +compare /dev/null empty || fail=1 + +# this should result in a protective partition covering the GPT data up to +# the start of the first partition, and then representations of the first +# three partitions +dump_mbr_table > out || fail=1 +cat < exp || fail=1 + 00 00 01 00 ee fe ff ff 01 00 00 00 ff 07 00 00 + 00 fe ff ff 0b fe ff ff 00 08 00 00 00 00 01 00 + 80 fe ff ff 83 fe ff ff 00 08 01 00 00 10 00 00 + 00 fe ff ff da fe ff ff 00 18 01 00 00 08 00 00 + 55 aa +EOF +compare exp out || fail=1 + +# convert third partition to a BIOS Boot Partition +parted -s $dev set 3 bios_grub on > empty 2>&1 || fail=1 +compare /dev/null empty || fail=1 + +# since this isn't the first partition, it shouldn't become a protective +# partition or have its starting LBA address set to 1 (and GRUB doesn't care +# whether it's in the hybrid MBR anyway) +dump_mbr_table > out || fail=1 +compare exp out || fail=1 + +Exit $fail