diff options
Diffstat (limited to '')
-rw-r--r-- | libparted/labels/gpt.c | 1941 |
1 files changed, 1941 insertions, 0 deletions
diff --git a/libparted/labels/gpt.c b/libparted/labels/gpt.c new file mode 100644 index 0000000..780fb70 --- /dev/null +++ b/libparted/labels/gpt.c @@ -0,0 +1,1941 @@ +/* + libparted - a library for manipulating disk partitions + + original version by Matt Domsch <Matt_Domsch@dell.com> + Disclaimed into the Public Domain + + Portions Copyright (C) 2001-2003, 2005-2012 Free Software Foundation, Inc. + + EFI GUID Partition Table handling + Per Intel EFI Specification v1.02 + http://developer.intel.com/technology/efi/efi.htm + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <config.h> + +#include <parted/parted.h> +#include <parted/debug.h> +#include <parted/endian.h> +#include <parted/crc32.h> +#include <inttypes.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <unistd.h> +#include <uuid/uuid.h> +#include <stdbool.h> +#include <errno.h> +#include <iconv.h> +#include <langinfo.h> +#include "xalloc.h" +#include "xalloc-oversized.h" +#include "verify.h" + +#include "pt-tools.h" + +#if ENABLE_NLS +# include <libintl.h> +# define _(String) gettext (String) +#else +# define _(String) (String) +#endif /* ENABLE_NLS */ + +#define EFI_PMBR_OSTYPE_EFI 0xEE +#define MSDOS_MBR_SIGNATURE 0xaa55 + +#define GPT_HEADER_SIGNATURE 0x5452415020494645LL + +/* NOTE: the document that describes revision 1.00 is labelled "version 1.02", + * so some implementors got confused... + */ +#define GPT_HEADER_REVISION_V1_02 0x00010200 +#define GPT_HEADER_REVISION_V1_00 0x00010000 +#define GPT_HEADER_REVISION_V0_99 0x00009900 + +typedef uint16_t efi_char16_t; /* UNICODE character */ +typedef struct _GuidPartitionTableHeader_t GuidPartitionTableHeader_t; +typedef struct _GuidPartitionEntryAttributes_t GuidPartitionEntryAttributes_t; +typedef struct _GuidPartitionEntry_t GuidPartitionEntry_t; +typedef struct _PartitionRecord_t PartitionRecord_t; +typedef struct _LegacyMBR_t LegacyMBR_t; +typedef struct _GPTDiskData GPTDiskData; + + +typedef struct +{ + uint32_t time_low; + uint16_t time_mid; + uint16_t time_hi_and_version; + uint8_t clock_seq_hi_and_reserved; + uint8_t clock_seq_low; + uint8_t node[6]; +} /* __attribute__ ((packed)) */ efi_guid_t; +/* commented out "__attribute__ ((packed))" to work around gcc bug (fixed + * in gcc3.1): __attribute__ ((packed)) breaks addressing on initialized + * data. It turns out we don't need it in this case, so it doesn't break + * anything :) + */ + +#define UNUSED_ENTRY_GUID \ + ((efi_guid_t) { 0x00000000, 0x0000, 0x0000, 0x00, 0x00, \ + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}) +#define PARTITION_SYSTEM_GUID \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0xC12A7328), PED_CPU_TO_LE16 (0xF81F), \ + PED_CPU_TO_LE16 (0x11d2), 0xBA, 0x4B, \ + { 0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B }}) +#define PARTITION_BIOS_GRUB_GUID \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0x21686148), PED_CPU_TO_LE16 (0x6449), \ + PED_CPU_TO_LE16 (0x6E6f), 0x74, 0x4E, \ + { 0x65, 0x65, 0x64, 0x45, 0x46, 0x49 }}) +#define LEGACY_MBR_PARTITION_GUID \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0x024DEE41), PED_CPU_TO_LE16 (0x33E7), \ + PED_CPU_TO_LE16 (0x11d3, 0x9D, 0x69, \ + { 0x00, 0x08, 0xC7, 0x81, 0xF3, 0x9F }}) +#define PARTITION_MSFT_RESERVED_GUID \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0xE3C9E316), PED_CPU_TO_LE16 (0x0B5C), \ + PED_CPU_TO_LE16 (0x4DB8), 0x81, 0x7D, \ + { 0xF9, 0x2D, 0xF0, 0x02, 0x15, 0xAE }}) +#define PARTITION_MSFT_RECOVERY \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0xDE94BBA4), PED_CPU_TO_LE16 (0x06D1), \ + PED_CPU_TO_LE16 (0x4D40), 0xA1, 0x6A, \ + { 0xBF, 0xD5, 0x01, 0x79, 0xD6, 0xAC }}) +#define PARTITION_BASIC_DATA_GUID \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0xEBD0A0A2), PED_CPU_TO_LE16 (0xB9E5), \ + PED_CPU_TO_LE16 (0x4433), 0x87, 0xC0, \ + { 0x68, 0xB6, 0xB7, 0x26, 0x99, 0xC7 }}) +#define PARTITION_RAID_GUID \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0xa19d880f), PED_CPU_TO_LE16 (0x05fc), \ + PED_CPU_TO_LE16 (0x4d3b), 0xa0, 0x06, \ + { 0x74, 0x3f, 0x0f, 0x84, 0x91, 0x1e }}) +#define PARTITION_SWAP_GUID \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0x0657fd6d), PED_CPU_TO_LE16 (0xa4ab), \ + PED_CPU_TO_LE16 (0x43c4), 0x84, 0xe5, \ + { 0x09, 0x33, 0xc8, 0x4b, 0x4f, 0x4f }}) +#define PARTITION_LINUX_DATA_GUID \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0x0FC63DAF), PED_CPU_TO_LE16 (0x8483), \ + PED_CPU_TO_LE16 (0x4772), 0x8E, 0x79, \ + { 0x3D, 0x69, 0xD8, 0x47, 0x7D, 0xE4 }}) +#define PARTITION_LVM_GUID \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0xe6d6d379), PED_CPU_TO_LE16 (0xf507), \ + PED_CPU_TO_LE16 (0x44c2), 0xa2, 0x3c, \ + { 0x23, 0x8f, 0x2a, 0x3d, 0xf9, 0x28 }}) +#define PARTITION_RESERVED_GUID \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0x8da63339), PED_CPU_TO_LE16 (0x0007), \ + PED_CPU_TO_LE16 (0x60c0), 0xc4, 0x36, \ + { 0x08, 0x3a, 0xc8, 0x23, 0x09, 0x08 }}) +#define PARTITION_HPSERVICE_GUID \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0xe2a1e728), PED_CPU_TO_LE16 (0x32e3), \ + PED_CPU_TO_LE16 (0x11d6), 0xa6, 0x82, \ + { 0x7b, 0x03, 0xa0, 0x00, 0x00, 0x00 }}) +#define PARTITION_APPLE_HFS_GUID \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0x48465300), PED_CPU_TO_LE16 (0x0000), \ + PED_CPU_TO_LE16 (0x11AA), 0xaa, 0x11, \ + { 0x00, 0x30, 0x65, 0x43, 0xEC, 0xAC }}) +#define PARTITION_APPLE_TV_RECOVERY_GUID \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0x5265636F), PED_CPU_TO_LE16 (0x7665), \ + PED_CPU_TO_LE16 (0x11AA), 0xaa, 0x11, \ + { 0x00, 0x30, 0x65, 0x43, 0xEC, 0xAC }}) +#define PARTITION_PREP_GUID \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0x9e1a2d38), PED_CPU_TO_LE16 (0xc612), \ + PED_CPU_TO_LE16 (0x4316), 0xaa, 0x26, \ + { 0x8b, 0x49, 0x52, 0x1e, 0x5a, 0x8b }}) +#define PARTITION_IRST_GUID \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0xD3BFE2DE), PED_CPU_TO_LE16 (0x3DAF), \ + PED_CPU_TO_LE16 (0x11DF), 0xba, 0x40, \ + { 0xE3, 0xA5, 0x56, 0xD8, 0x95, 0x93 }}) +#define PARTITION_CHROMEOS_KERNEL_GUID \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0xfe3a2a5d), PED_CPU_TO_LE16 (0x4f32), \ + PED_CPU_TO_LE16 (0x41a7), 0xb7, 0x25, \ + { 0xac, 0xcc, 0x32, 0x85, 0xa3, 0x09 }}) +#define PARTITION_BLS_BOOT_GUID \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0xbc13c2ff), PED_CPU_TO_LE16 (0x59e6), \ + PED_CPU_TO_LE16 (0x4262), 0xa3, 0x52, \ + { 0xb2, 0x75, 0xfd, 0x6f, 0x71, 0x72 }}) +#define PARTITION_LINUX_HOME_GUID \ + ((efi_guid_t) { PED_CPU_TO_LE32 (0x933ac7e1), PED_CPU_TO_LE16 (0x2eb4), \ + PED_CPU_TO_LE16 (0x4f13), 0xb8, 0x44, \ + { 0x0e, 0x14, 0xe2, 0xae, 0xf9, 0x15 }}) + +struct flag_uuid_mapping_t +{ + enum _PedPartitionFlag flag; + efi_guid_t type_uuid; +}; + +static const struct flag_uuid_mapping_t flag_uuid_mapping[] = +{ + { PED_PARTITION_APPLE_TV_RECOVERY, PARTITION_APPLE_TV_RECOVERY_GUID }, + { PED_PARTITION_BIOS_GRUB, PARTITION_BIOS_GRUB_GUID }, + { PED_PARTITION_BLS_BOOT, PARTITION_BLS_BOOT_GUID }, + { PED_PARTITION_BOOT, PARTITION_SYSTEM_GUID }, + { PED_PARTITION_CHROMEOS_KERNEL, PARTITION_CHROMEOS_KERNEL_GUID }, + { PED_PARTITION_DIAG, PARTITION_MSFT_RECOVERY }, + { PED_PARTITION_ESP, PARTITION_SYSTEM_GUID }, + { PED_PARTITION_HPSERVICE, PARTITION_HPSERVICE_GUID }, + { PED_PARTITION_IRST, PARTITION_IRST_GUID }, + { PED_PARTITION_LINUX_HOME, PARTITION_LINUX_HOME_GUID }, + { PED_PARTITION_LVM, PARTITION_LVM_GUID }, + { PED_PARTITION_MSFT_DATA, PARTITION_BASIC_DATA_GUID }, + { PED_PARTITION_MSFT_RESERVED, PARTITION_MSFT_RESERVED_GUID }, + { PED_PARTITION_PREP, PARTITION_PREP_GUID }, + { PED_PARTITION_RAID, PARTITION_RAID_GUID }, + { PED_PARTITION_SWAP, PARTITION_SWAP_GUID }, +}; + +static const efi_guid_t skip_set_system_guids[] = +{ + PARTITION_LVM_GUID, + PARTITION_SWAP_GUID, + PARTITION_RAID_GUID, + PARTITION_PREP_GUID, + PARTITION_SYSTEM_GUID, + PARTITION_BIOS_GRUB_GUID, + PARTITION_HPSERVICE_GUID, + PARTITION_MSFT_RESERVED_GUID, + PARTITION_BASIC_DATA_GUID, + PARTITION_MSFT_RECOVERY, + PARTITION_APPLE_TV_RECOVERY_GUID, + PARTITION_IRST_GUID, + PARTITION_CHROMEOS_KERNEL_GUID, + PARTITION_BLS_BOOT_GUID, +}; + +static const struct flag_uuid_mapping_t* _GL_ATTRIBUTE_CONST +gpt_find_flag_uuid_mapping (PedPartitionFlag flag) +{ + int n = sizeof(flag_uuid_mapping) / sizeof(flag_uuid_mapping[0]); + + for (int i = 0; i < n; ++i) + if (flag_uuid_mapping[i].flag == flag) + return &flag_uuid_mapping[i]; + + return NULL; +} + +struct __attribute__ ((packed)) _GuidPartitionTableHeader_t +{ + uint64_t Signature; + uint32_t Revision; + uint32_t HeaderSize; + uint32_t HeaderCRC32; + uint32_t Reserved1; + uint64_t MyLBA; + uint64_t AlternateLBA; + uint64_t FirstUsableLBA; + uint64_t LastUsableLBA; + efi_guid_t DiskGUID; + uint64_t PartitionEntryLBA; + uint32_t NumberOfPartitionEntries; + uint32_t SizeOfPartitionEntry; + uint32_t PartitionEntryArrayCRC32; + uint8_t *Reserved2; +}; + +struct __attribute__ ((packed)) _GuidPartitionEntryAttributes_t +{ +#ifdef __GNUC__ /* XXX narrow this down to !TinyCC */ + uint64_t RequiredToFunction:1; + uint64_t NoBlockIOProtocol:1; + uint64_t LegacyBIOSBootable:1; + uint64_t Reserved:45; + uint64_t GuidSpecific:15; + uint64_t NoAutomount:1; +#else +# warning "Using crippled partition entry type" + uint32_t RequiredToFunction:1; + uint32_t NoBlockIOProtocol:1; + uint32_t LegacyBIOSBootable:1; + uint32_t Reserved:30; + uint32_t LOST:5; + uint32_t GuidSpecific:15; + uint32_t NoAutomount:1; +#endif +}; + +struct __attribute__ ((packed)) _GuidPartitionEntry_t +{ + efi_guid_t PartitionTypeGuid; + efi_guid_t UniquePartitionGuid; + uint64_t StartingLBA; + uint64_t EndingLBA; + GuidPartitionEntryAttributes_t Attributes; + efi_char16_t PartitionName[36]; +}; + +#define GPT_PMBR_LBA 0 +#define GPT_PMBR_SECTORS 1 +#define GPT_PRIMARY_HEADER_LBA 1 +#define GPT_HEADER_SECTORS 1 +#define GPT_PRIMARY_PART_TABLE_LBA 2 + +/* + These values are only defaults. The actual on-disk structures + may define different sizes, so use those unless creating a new GPT disk! +*/ + +#define GPT_DEFAULT_PARTITION_ENTRY_ARRAY_SIZE 16384 + +/* Number of actual partition entries should be calculated as: */ +#define GPT_DEFAULT_PARTITION_ENTRIES \ + (GPT_DEFAULT_PARTITION_ENTRY_ARRAY_SIZE / \ + sizeof(GuidPartitionEntry_t)) + +struct __attribute__ ((packed)) _PartitionRecord_t +{ + /* Not used by EFI firmware. Set to 0x80 to indicate that this + is the bootable legacy partition. */ + uint8_t BootIndicator; + + /* Start of partition in CHS address, not used by EFI firmware. */ + uint8_t StartHead; + + /* Start of partition in CHS address, not used by EFI firmware. */ + uint8_t StartSector; + + /* Start of partition in CHS address, not used by EFI firmware. */ + uint8_t StartTrack; + + /* OS type. A value of 0xEF defines an EFI system partition. + Other values are reserved for legacy operating systems, and + allocated independently of the EFI specification. */ + uint8_t OSType; + + /* End of partition in CHS address, not used by EFI firmware. */ + uint8_t EndHead; + + /* End of partition in CHS address, not used by EFI firmware. */ + uint8_t EndSector; + + /* End of partition in CHS address, not used by EFI firmware. */ + uint8_t EndTrack; + + /* Starting LBA address of the partition on the disk. Used by + EFI firmware to define the start of the partition. */ + uint32_t StartingLBA; + + /* Size of partition in LBA. Used by EFI firmware to determine + the size of the partition. */ + uint32_t SizeInLBA; +}; + +/* Protected Master Boot Record & Legacy MBR share same structure */ +/* Needs to be packed because the u16s force misalignment. */ +struct __attribute__ ((packed)) _LegacyMBR_t +{ + uint8_t BootCode[440]; + uint32_t UniqueMBRSignature; + uint16_t Unknown; + PartitionRecord_t PartitionRecord[4]; + uint16_t Signature; +}; + +/* uses libparted's disk_specific field in PedDisk, to store our info */ +struct __attribute__ ((packed, aligned(8))) _GPTDiskData +{ + PedGeometry data_area; + int entry_count; + efi_guid_t uuid; + int pmbr_boot; + PedSector AlternateLBA; +}; + +/* uses libparted's disk_specific field in PedPartition, to store our info */ +typedef struct _GPTPartitionData +{ + efi_guid_t type; + efi_guid_t uuid; + efi_char16_t name[37]; + char *translated_name; + GuidPartitionEntryAttributes_t attributes; +} GPTPartitionData; + +static PedDiskType gpt_disk_type; + +static inline uint32_t +pth_get_size (const PedDevice *dev) +{ + return GPT_HEADER_SECTORS * dev->sector_size; +} + +static inline uint32_t +pth_get_size_static (const PedDevice *dev) +{ + return sizeof (GuidPartitionTableHeader_t) - sizeof (uint8_t *); +} + +static inline uint32_t +pth_get_size_rsv2 (const PedDevice *dev) +{ + return pth_get_size (dev) - pth_get_size_static (dev); +} + +static GuidPartitionTableHeader_t * +pth_new (const PedDevice *dev) +{ + GuidPartitionTableHeader_t *pth = + ped_malloc (sizeof (GuidPartitionTableHeader_t) + sizeof (uint8_t)); + + pth->Reserved2 = ped_malloc (pth_get_size_rsv2 (dev)); + + return pth; +} + +static GuidPartitionTableHeader_t * +pth_new_zeroed (const PedDevice *dev) +{ + GuidPartitionTableHeader_t *pth = pth_new (dev); + + memset (pth, 0, pth_get_size_static (dev)); + memset (pth->Reserved2, 0, pth_get_size_rsv2 (dev)); + + return (pth); +} + +static GuidPartitionTableHeader_t * +pth_new_from_raw (const PedDevice *dev, const uint8_t *pth_raw) +{ + GuidPartitionTableHeader_t *pth = pth_new (dev); + + PED_ASSERT (pth_raw != NULL); + + memcpy (pth, pth_raw, pth_get_size_static (dev)); + memcpy (pth->Reserved2, pth_raw + pth_get_size_static (dev), + pth_get_size_rsv2 (dev)); + + return pth; +} + +static void +pth_free (GuidPartitionTableHeader_t *pth) +{ + if (pth == NULL) + return; + PED_ASSERT (pth->Reserved2 != NULL); + + free (pth->Reserved2); + free (pth); +} + +static uint8_t * +pth_get_raw (const PedDevice *dev, const GuidPartitionTableHeader_t *pth) +{ + PED_ASSERT (pth != NULL); + PED_ASSERT (pth->Reserved2 != NULL); + + int size_static = pth_get_size_static (dev); + uint8_t *pth_raw = ped_malloc (pth_get_size (dev)); + if (pth_raw == NULL) + return NULL; + + memcpy (pth_raw, pth, size_static); + memcpy (pth_raw + size_static, pth->Reserved2, pth_get_size_rsv2 (dev)); + + return pth_raw; +} + +/** + * swap_uuid_and_efi_guid() - converts between uuid formats + * @uuid - uuid_t in either format (converts it to the other) + * + * There are two different representations for Globally Unique Identifiers + * (GUIDs or UUIDs). + * + * The RFC specifies a UUID as a string of 16 bytes, essentially + * a big-endian array of char. + * Intel, in their EFI Specification, references the same RFC, but + * then defines a GUID as a structure of little-endian fields. + * Coincidentally, both structures have the same format when unparsed. + * + * When read from disk, EFI GUIDs are in struct of little endian format, + * and need to be converted to be treated as uuid_t in memory. + * + * When writing to disk, uuid_ts need to be converted into EFI GUIDs. + * + * Blame Intel. + */ +static void +swap_uuid_and_efi_guid (efi_guid_t *guid) +{ + PED_ASSERT (guid != NULL); + guid->time_low = PED_SWAP32 (guid->time_low); + guid->time_mid = PED_SWAP16 (guid->time_mid); + guid->time_hi_and_version = PED_SWAP16 (guid->time_hi_and_version); +} + +/* returns the EFI-style CRC32 value for buf + * This function uses the crc32 function by Gary S. Brown, + * but seeds the function with ~0, and xor's with ~0 at the end. + */ +static inline uint32_t +efi_crc32 (const void *buf, unsigned long len) +{ + return (__efi_crc32 (buf, len, ~0L) ^ ~0L); +} + +/* Compute the crc32 checksum of the partition table header + and store it in *CRC32. Return 0 upon success. Return 1 + upon failure to allocate space. */ +static int +pth_crc32 (const PedDevice *dev, const GuidPartitionTableHeader_t *pth, + uint32_t *crc32) +{ + PED_ASSERT (dev != NULL); + PED_ASSERT (pth != NULL); + + uint8_t *pth_raw = pth_get_raw (dev, pth); + if (pth_raw == NULL) + return 1; + + *crc32 = efi_crc32 (pth_raw, PED_LE32_TO_CPU (pth->HeaderSize)); + free (pth_raw); + + return 0; +} + +static inline int +guid_cmp (efi_guid_t left, efi_guid_t right) +{ + return memcmp (&left, &right, sizeof (efi_guid_t)); +} + +/* checks if 'mbr' is a protective MBR partition table */ +static inline int _GL_ATTRIBUTE_PURE +_pmbr_is_valid (const LegacyMBR_t *mbr) +{ + int i; + + PED_ASSERT (mbr != NULL); + + if (mbr->Signature != PED_CPU_TO_LE16 (MSDOS_MBR_SIGNATURE)) + return 0; + for (i = 0; i < 4; i++) + { + if (mbr->PartitionRecord[i].OSType == EFI_PMBR_OSTYPE_EFI) + return 1; + } + return 0; +} + +static int +gpt_probe (const PedDevice *dev) +{ + int gpt_sig_found = 0; + + PED_ASSERT (dev != NULL); + + if (dev->length <= 1) + return 0; + + void *label; + if (!ptt_read_sector (dev, 0, &label)) + return 0; + + if (!_pmbr_is_valid (label)) + { + 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) + || ped_device_read (dev, pth_raw, dev->length - 1, GPT_HEADER_SECTORS)) + { + GuidPartitionTableHeader_t *gpt = pth_new_from_raw (dev, pth_raw); + if (gpt->Signature == PED_CPU_TO_LE64 (GPT_HEADER_SIGNATURE)) + gpt_sig_found = 1; + pth_free (gpt); + } + free (pth_raw); + + return gpt_sig_found; +} + +static PedDisk * +gpt_alloc (const PedDevice *dev) +{ + PedDisk *disk; + GPTDiskData *gpt_disk_data; + PedSector data_start, data_end; + + disk = _ped_disk_alloc ((PedDevice *) dev, &gpt_disk_type); + if (!disk) + goto error; + + data_start = 2 + GPT_DEFAULT_PARTITION_ENTRY_ARRAY_SIZE / dev->sector_size; + data_end = dev->length - 2 + - GPT_DEFAULT_PARTITION_ENTRY_ARRAY_SIZE / dev->sector_size; + + /* If the device is too small to accommodate GPT headers and one data + sector, reject it. */ + if (data_end < data_start) + { + ped_exception_throw (PED_EXCEPTION_ERROR, + PED_EXCEPTION_OK, + _("device is too small for GPT")); + goto error_free_disk; + } + + disk->disk_specific = gpt_disk_data = ped_malloc (sizeof (GPTDiskData)); + if (!disk->disk_specific) + goto error_free_disk; + + gpt_disk_data->AlternateLBA = dev->length - 1; + ped_geometry_init (&gpt_disk_data->data_area, dev, data_start, + data_end - data_start + 1); + gpt_disk_data->entry_count = GPT_DEFAULT_PARTITION_ENTRIES; + uuid_generate ((unsigned char *) &gpt_disk_data->uuid); + swap_uuid_and_efi_guid (&gpt_disk_data->uuid); + gpt_disk_data->pmbr_boot = 0; + return disk; + +error_free_disk: + free (disk); +error: + return NULL; +} + +static PedDisk * +gpt_duplicate (const PedDisk *disk) +{ + PedDisk *new_disk; + GPTDiskData *new_disk_data; + GPTDiskData *old_disk_data; + + new_disk = ped_disk_new_fresh (disk->dev, &gpt_disk_type); + if (!new_disk) + return NULL; + + old_disk_data = disk->disk_specific; + new_disk_data = new_disk->disk_specific; + + ped_geometry_init (&new_disk_data->data_area, disk->dev, + old_disk_data->data_area.start, + old_disk_data->data_area.length); + new_disk_data->entry_count = old_disk_data->entry_count; + new_disk_data->uuid = old_disk_data->uuid; + new_disk_data->pmbr_boot = old_disk_data->pmbr_boot; + return new_disk; +} + +static void +gpt_free (PedDisk *disk) +{ + ped_disk_delete_all (disk); + free (disk->disk_specific); + _ped_disk_free (disk); +} + +/* Given GUID Partition table header, GPT, read its partition array + entries from DISK into malloc'd storage. Set *PTES_BYTES to the + number of bytes required. Upon success, return a pointer to the + resulting buffer. Otherwise, set errno and return NULL. */ +static void * +gpt_read_PE_array (PedDisk const *disk, GuidPartitionTableHeader_t const *gpt, + size_t *ptes_bytes) +{ + uint32_t p_ent_size = PED_LE32_TO_CPU (gpt->SizeOfPartitionEntry); + *ptes_bytes = p_ent_size * PED_LE32_TO_CPU(gpt->NumberOfPartitionEntries); + size_t ptes_sectors = ped_div_round_up (*ptes_bytes, + disk->dev->sector_size); + + if (xalloc_oversized (ptes_sectors, disk->dev->sector_size)) + { + errno = ENOMEM; + return NULL; + } + void *ptes = ped_malloc (ptes_sectors * disk->dev->sector_size); + if (ptes == NULL) + return NULL; + + if (!ped_device_read (disk->dev, ptes, + PED_LE64_TO_CPU (gpt->PartitionEntryLBA), ptes_sectors)) + { + int saved_errno = errno; + free (ptes); + errno = saved_errno; + return NULL; + } + + return ptes; +} + +static int +check_PE_array_CRC (PedDisk const *disk, + GuidPartitionTableHeader_t const *gpt, bool *valid) +{ + size_t ptes_bytes; + void *ptes = gpt_read_PE_array (disk, gpt, &ptes_bytes); + if (ptes == NULL) + return 1; + + uint32_t ptes_crc = efi_crc32 (ptes, ptes_bytes); + *valid = (ptes_crc == PED_LE32_TO_CPU (gpt->PartitionEntryArrayCRC32)); + free (ptes); + return 0; +} + +static int +_header_is_valid (PedDisk const *disk, GuidPartitionTableHeader_t *gpt, + PedSector my_lba) +{ + uint32_t crc, origcrc; + PedDevice const *dev = disk->dev; + + if (PED_LE64_TO_CPU (gpt->Signature) != GPT_HEADER_SIGNATURE) + return 0; + /* + * "While the GUID Partition Table Header's size may increase + * in the future it cannot span more than one block on the + * device." EFI Specification, version 1.10, 11.2.2.1 + */ + if (PED_LE32_TO_CPU (gpt->HeaderSize) < pth_get_size_static (dev) + || PED_LE32_TO_CPU (gpt->HeaderSize) > dev->sector_size) + return 0; + + /* The SizeOfPartitionEntry must be a multiple of 8 and + no smaller than the size of the PartitionEntry structure. + We also require that it be no larger than 1/16th of UINT32_MAX, + as an additional sanity check. */ + uint32_t pe_size = PED_LE32_TO_CPU (gpt->SizeOfPartitionEntry); + if (pe_size % 8 != 0 + || ! (sizeof (GuidPartitionEntry_t) <= pe_size + && pe_size <= (UINT32_MAX >> 4))) + return 0; + + if (PED_LE64_TO_CPU (gpt->MyLBA) != my_lba) + return 0; + + PedSector alt_lba = PED_LE64_TO_CPU (gpt->AlternateLBA); + /* The backup table's AlternateLBA must be 1. */ + if (my_lba != 1 && alt_lba != 1) + return 0; + + /* The alt_lba must never be the same as my_lba. */ + if (alt_lba == my_lba) + return 0; + + bool crc_match; + if (check_PE_array_CRC (disk, gpt, &crc_match) != 0 || !crc_match) + return 0; + + PedSector first_usable = PED_LE64_TO_CPU (gpt->FirstUsableLBA); + if (first_usable < 3) + return 0; + + PedSector last_usable = PED_LE64_TO_CPU (gpt->LastUsableLBA); + if (last_usable < first_usable) + return 0; + + origcrc = gpt->HeaderCRC32; + gpt->HeaderCRC32 = 0; + if (pth_crc32 (dev, gpt, &crc) != 0) + return 0; + gpt->HeaderCRC32 = origcrc; + + return crc == PED_LE32_TO_CPU (origcrc); +} + +/* Return the number of sectors that should be used by the + * partition entry table. + */ +static PedSector +_ptes_sectors(PedDisk const *disk, GuidPartitionTableHeader_t const *gpt) +{ + size_t ptes_bytes = PED_LE32_TO_CPU (gpt->SizeOfPartitionEntry) * + PED_LE32_TO_CPU (gpt->NumberOfPartitionEntries); + /* Minimum amount of space reserved is 128 128 byte entries */ + if (ptes_bytes < 128*128) + ptes_bytes = 128*128; + return ped_div_round_up (ptes_bytes, disk->dev->sector_size); +} + +/* Return the header's idea of the last sector of the disk + * based on LastUsableLBA and the Partition Entry table. + */ +static PedSector +_hdr_disk_end(PedDisk const *disk, GuidPartitionTableHeader_t const *gpt) +{ + return PED_LE64_TO_CPU (gpt->LastUsableLBA) + 1 + _ptes_sectors(disk, gpt); +} + +static int +_parse_header (PedDisk *disk, const GuidPartitionTableHeader_t *gpt, + int *update_needed) +{ + GPTDiskData *gpt_disk_data = disk->disk_specific; + PedSector first_usable; + PedSector last_usable; + PedSector last_usable_if_grown; + +#ifndef DISCOVER_ONLY + if (PED_LE32_TO_CPU (gpt->Revision) > GPT_HEADER_REVISION_V1_02) + { + if (ped_exception_throw + (PED_EXCEPTION_WARNING, + PED_EXCEPTION_IGNORE_CANCEL, + _("The format of the GPT partition table is version " + "%x, which is newer than what Parted can " + "recognise. Please report this!"), + PED_LE32_TO_CPU (gpt->Revision)) != PED_EXCEPTION_IGNORE) + return 0; + } +#endif + + first_usable = PED_LE64_TO_CPU (gpt->FirstUsableLBA); + last_usable = PED_LE64_TO_CPU (gpt->LastUsableLBA); + + /* Need to check whether the volume has grown, the LastUsableLBA is + normally set to disk->dev->length - 2 - ptes_size (at least for parted + created volumes), where ptes_size is the number of entries * + size of each entry / sector size or 16k / sector size, whatever the greater. + If the volume has grown, offer the user the chance to use the new + space or continue with the current usable area. Only ask once per + parted invocation. */ + + last_usable_if_grown = disk->dev->length - 2 - _ptes_sectors(disk, gpt); + + if (last_usable <= first_usable + || disk->dev->length < last_usable) + return 0; + + if (last_usable_if_grown <= first_usable + || disk->dev->length < last_usable_if_grown) + return 0; + + if (last_usable < last_usable_if_grown) + { + PedExceptionOption q; + + q = ped_exception_throw + (PED_EXCEPTION_WARNING, + PED_EXCEPTION_FIX | PED_EXCEPTION_IGNORE, + _("Not all of the space available to %s appears " + "to be used, you can fix the GPT to use all of the " + "space (an extra %llu blocks) or continue with the " + "current setting? "), disk->dev->path, + (uint64_t) (last_usable_if_grown - last_usable)); + + if (q == PED_EXCEPTION_FIX) + { + last_usable = last_usable_if_grown; + /* clear the old backup gpt header */ + ptt_clear_sectors (disk->dev, + gpt_disk_data->AlternateLBA, 1); + gpt_disk_data->AlternateLBA = disk->dev->length - 1; + *update_needed = 1; + } + } + + ped_geometry_init (&gpt_disk_data->data_area, disk->dev, + first_usable, last_usable - first_usable + 1); + + gpt_disk_data->entry_count + = PED_LE32_TO_CPU (gpt->NumberOfPartitionEntries); + PED_ASSERT (gpt_disk_data->entry_count > 0); + PED_ASSERT (gpt_disk_data->entry_count <= 8192); + + gpt_disk_data->uuid = gpt->DiskGUID; + + return 1; +} + +static PedPartition * +_parse_part_entry (PedDisk *disk, GuidPartitionEntry_t *pte) +{ + PedPartition *part; + GPTPartitionData *gpt_part_data; + unsigned int i; + + part = ped_partition_new (disk, PED_PARTITION_NORMAL, NULL, + PED_LE64_TO_CPU (pte->StartingLBA), + PED_LE64_TO_CPU (pte->EndingLBA)); + if (!part) + return NULL; + + gpt_part_data = part->disk_specific; + gpt_part_data->type = pte->PartitionTypeGuid; + gpt_part_data->uuid = pte->UniquePartitionGuid; + for (i = 0; i < 36; i++) + gpt_part_data->name[i] = (efi_char16_t) pte->PartitionName[i]; + gpt_part_data->name[i] = 0; + gpt_part_data->translated_name = 0; + gpt_part_data->attributes = pte->Attributes; + + return part; +} + +/* Read the primary GPT at sector 1 of DEV. + Verify its CRC and that of its partition entry array. + If they are valid, read the backup GPT specified by AlternateLBA. + If not, read the backup GPT in the last sector of the disk. + Return 1 if any read fails. + Upon successful verification of the primary GPT, set *PRIMARY_GPT, else NULL. + Upon successful verification of the backup GPT, set *BACKUP_GPT, else NULL. + If we've set *BACKUP_GPT to non-NULL, set *BACKUP_SECTOR_NUM_P to the sector + number in which it was found. */ +static int +gpt_read_headers (PedDisk const *disk, + GuidPartitionTableHeader_t **primary_gpt, + GuidPartitionTableHeader_t **backup_gpt, + PedSector *backup_sector_num_p) +{ + *primary_gpt = NULL; + *backup_gpt = NULL; + PedDevice const *dev = disk->dev; + GPTDiskData *gpt_disk_data = disk->disk_specific; + LegacyMBR_t *mbr; + + if (!ptt_read_sector (dev, 0, (void *)&mbr)) + return 1; + + if (mbr->PartitionRecord[0].BootIndicator == 0x80) + gpt_disk_data->pmbr_boot = 1; + free (mbr); + + void *s1; + if (!ptt_read_sector (dev, 1, &s1)) + return 1; + + GuidPartitionTableHeader_t *t = pth_new_from_raw (dev, s1); + free (s1); + if (t == NULL) + return 1; + GuidPartitionTableHeader_t *pri = t; + + bool valid_primary = _header_is_valid (disk, pri, 1); + if (valid_primary) + *primary_gpt = pri; + else + pth_free (pri); + + gpt_disk_data->AlternateLBA = + (valid_primary + ? PED_LE64_TO_CPU (pri->AlternateLBA) + : dev->length - 1); + + void *s_bak; + if (!ptt_read_sector (dev, gpt_disk_data->AlternateLBA ,&s_bak)) + return 1; + t = pth_new_from_raw (dev, s_bak); + free (s_bak); + if (t == NULL) + return 1; + + GuidPartitionTableHeader_t *bak = t; + if (_header_is_valid (disk, bak, gpt_disk_data->AlternateLBA)) + { + *backup_gpt = bak; + *backup_sector_num_p = gpt_disk_data->AlternateLBA; + } + else + pth_free (bak); + + return 0; +} + +/************************************************************ + * Intel is changing the EFI Spec. (after v1.02) to say that a + * disk is considered to have a GPT label only if the GPT + * structures are correct, and the MBR is actually a Protective + * MBR (has one 0xEE type partition). + * Problem occurs when a GPT-partitioned disk is then + * edited with a legacy (non-GPT-aware) application, such as + * fdisk (which doesn't generally erase the PGPT or AGPT). + * How should such a disk get handled? As a GPT disk (throwing + * away the fdisk changes), or as an MSDOS disk (throwing away + * the GPT information). Previously, I've taken the GPT-is-right, + * MBR is wrong, approach, to stay consistent with the EFI Spec. + * Intel disagrees, saying the disk should then be treated + * as having a msdos label, not a GPT label. If this is true, + * then what's the point of having an AGPT, since if the PGPT + * is screwed up, likely the PMBR is too, and the PMBR becomes + * a single point of failure. + * So, in the Linux kernel, I'm going to test for PMBR, and + * 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. + ************************************************************/ +static int +gpt_read (PedDisk *disk) +{ + GPTDiskData *gpt_disk_data = disk->disk_specific; + int i; +#ifndef DISCOVER_ONLY + int write_back = 0; +#endif + + ped_disk_delete_all (disk); + + /* motivation: let the user decide about the pmbr... during + ped_disk_probe(), they probably didn't get a choice... */ + if (!gpt_probe (disk->dev)) + goto error; + + GuidPartitionTableHeader_t *gpt = NULL; + GuidPartitionTableHeader_t *primary_gpt; + GuidPartitionTableHeader_t *backup_gpt; + PedSector backup_sector_num; + int read_failure = gpt_read_headers (disk, &primary_gpt, &backup_gpt, + &backup_sector_num); + if (read_failure) + { + /* This includes the case in which there used to be a GPT partition + table here, with an alternate LBA that extended beyond the current + end-of-device. It's treated as a non-match. */ + + /* Another possibility: + The primary header is ok, but backup is corrupt. + In the UEFI spec, this means the primary GUID table + is officially invalid. */ + pth_free (backup_gpt); + pth_free (primary_gpt); + return 0; + } + + if (primary_gpt && backup_gpt) + { + /* Both are valid. */ +#ifndef DISCOVER_ONLY + /* The backup header must be at the end of the disk, or at what the primary + * header thinks is the end of the disk. + */ + gpt_disk_data->AlternateLBA = PED_LE64_TO_CPU (primary_gpt->AlternateLBA); + PedSector pri_disk_end = _hdr_disk_end(disk, primary_gpt); + + if (gpt_disk_data->AlternateLBA != disk->dev->length -1 && + gpt_disk_data->AlternateLBA != pri_disk_end) + { + if (ped_exception_throw + (PED_EXCEPTION_ERROR, + (PED_EXCEPTION_FIX | PED_EXCEPTION_IGNORE), + _("The backup GPT table is not at the end of the disk, as it " + "should be. Fix, by moving the backup to the end " + "(and removing the old backup)?")) == PED_EXCEPTION_FIX) + { + ptt_clear_sectors (disk->dev, + PED_LE64_TO_CPU (primary_gpt->AlternateLBA), 1); + gpt_disk_data->AlternateLBA = disk->dev->length -1; + write_back = 1; + } + } +#endif /* !DISCOVER_ONLY */ + pth_free (backup_gpt); + gpt = primary_gpt; + } + else if (!primary_gpt && !backup_gpt) + { + /* Both are corrupt. */ + ped_exception_throw (PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, + _("Both the primary and backup GPT tables " + "are corrupt. Try making a fresh table, " + "and using Parted's rescue feature to " + "recover partitions.")); + goto error; + } + else if (primary_gpt && !backup_gpt) + { + /* The primary header is ok, but backup is corrupt. */ + if (ped_exception_throw + (PED_EXCEPTION_ERROR, PED_EXCEPTION_OK_CANCEL, + _("The backup GPT table is corrupt, but the " + "primary appears OK, so that will be used.")) + == PED_EXCEPTION_CANCEL) + goto error_free_gpt; + + gpt = primary_gpt; + } + else /* !primary_gpt && backup_gpt */ + { + /* primary GPT corrupt, backup is ok. */ + if (ped_exception_throw + (PED_EXCEPTION_ERROR, PED_EXCEPTION_OK_CANCEL, + _("The primary GPT table is corrupt, but the " + "backup appears OK, so that will be used.")) + == PED_EXCEPTION_CANCEL) + goto error_free_gpt; + + gpt = backup_gpt; + } + backup_gpt = NULL; + primary_gpt = NULL; + + if (!_parse_header (disk, gpt, &write_back)) + goto error_free_gpt; + + size_t ptes_bytes; + void *ptes = gpt_read_PE_array (disk, gpt, &ptes_bytes); + if (ptes == NULL) + goto error_free_gpt; + + uint32_t ptes_crc = efi_crc32 (ptes, ptes_bytes); + if (ptes_crc != PED_LE32_TO_CPU (gpt->PartitionEntryArrayCRC32)) + { + ped_exception_throw + (PED_EXCEPTION_ERROR, + PED_EXCEPTION_CANCEL, + _("primary partition table array CRC mismatch")); + goto error_free_ptes; + } + + uint32_t p_ent_size = PED_LE32_TO_CPU (gpt->SizeOfPartitionEntry); + for (i = 0; i < gpt_disk_data->entry_count; i++) + { + GuidPartitionEntry_t *pte + = (GuidPartitionEntry_t *) ((char *) ptes + i * p_ent_size); + PedPartition *part; + + if (!guid_cmp (pte->PartitionTypeGuid, UNUSED_ENTRY_GUID)) + continue; + + part = _parse_part_entry (disk, pte); + if (!part) + goto error_delete_all; + + part->fs_type = ped_file_system_probe (&part->geom); + part->num = i + 1; + + PedConstraint *constraint_exact = ped_constraint_exact (&part->geom); + if (!ped_disk_add_partition (disk, part, constraint_exact)) + { + ped_constraint_destroy (constraint_exact); + ped_partition_destroy (part); + goto error_delete_all; + } + ped_constraint_destroy (constraint_exact); + } + free (ptes); + +#ifndef DISCOVER_ONLY + if (write_back) + ped_disk_commit_to_dev (disk); +#endif + + pth_free (gpt); + return 1; + +error_delete_all: + ped_disk_delete_all (disk); +error_free_ptes: + free (ptes); +error_free_gpt: + pth_free (primary_gpt); + pth_free (backup_gpt); + pth_free (gpt); +error: + return 0; +} + +#ifndef DISCOVER_ONLY +/* Write the protective MBR (to keep DOS happy) */ +static int +_write_pmbr (PedDevice *dev, bool pmbr_boot) +{ + /* The UEFI spec is not clear about what to do with the following + elements of the Protective MBR (pmbr): BootCode (0-440B), + UniqueMBRSignature (440B-444B) and Unknown (444B-446B). + With this in mind, we try not to modify these elements. */ + void *s0; + if (!ptt_read_sector (dev, 0, &s0)) + return 0; + LegacyMBR_t *pmbr = s0; + + /* Zero out the legacy partitions. */ + memset (pmbr->PartitionRecord, 0, sizeof pmbr->PartitionRecord); + + pmbr->Signature = PED_CPU_TO_LE16 (MSDOS_MBR_SIGNATURE); + pmbr->PartitionRecord[0].OSType = EFI_PMBR_OSTYPE_EFI; + pmbr->PartitionRecord[0].StartSector = 2; + pmbr->PartitionRecord[0].EndHead = 0xFF; + pmbr->PartitionRecord[0].EndSector = 0xFF; + pmbr->PartitionRecord[0].EndTrack = 0xFF; + pmbr->PartitionRecord[0].StartingLBA = PED_CPU_TO_LE32 (1); + if ((dev->length - 1ULL) > 0xFFFFFFFFULL) + pmbr->PartitionRecord[0].SizeInLBA = PED_CPU_TO_LE32 (0xFFFFFFFF); + else + pmbr->PartitionRecord[0].SizeInLBA = PED_CPU_TO_LE32 (dev->length - 1UL); + if (pmbr_boot) + pmbr->PartitionRecord[0].BootIndicator = 0x80; + + int write_ok = ped_device_write (dev, pmbr, GPT_PMBR_LBA, + GPT_PMBR_SECTORS); + free (s0); + return write_ok; +} + +static int +_generate_header (const PedDisk *disk, int alternate, uint32_t ptes_crc, + GuidPartitionTableHeader_t **gpt_p) +{ + GPTDiskData *gpt_disk_data = disk->disk_specific; + GuidPartitionTableHeader_t *gpt; + + *gpt_p = pth_new_zeroed (disk->dev); + + gpt = *gpt_p; + + gpt->Signature = PED_CPU_TO_LE64 (GPT_HEADER_SIGNATURE); + gpt->Revision = PED_CPU_TO_LE32 (GPT_HEADER_REVISION_V1_00); + + /* per 1.00 spec */ + gpt->HeaderSize = PED_CPU_TO_LE32 (pth_get_size_static (disk->dev)); + gpt->HeaderCRC32 = 0; + gpt->Reserved1 = 0; + + if (alternate) + { + size_t ss = disk->dev->sector_size; + PedSector ptes_bytes = (gpt_disk_data->entry_count + * sizeof (GuidPartitionEntry_t)); + PedSector ptes_sectors = (ptes_bytes + ss - 1) / ss; + + gpt->MyLBA = PED_CPU_TO_LE64 (gpt_disk_data->AlternateLBA); + gpt->AlternateLBA = PED_CPU_TO_LE64 (1); + gpt->PartitionEntryLBA + = PED_CPU_TO_LE64 (gpt_disk_data->AlternateLBA - ptes_sectors); + } + else + { + gpt->MyLBA = PED_CPU_TO_LE64 (1); + gpt->AlternateLBA = PED_CPU_TO_LE64 (gpt_disk_data->AlternateLBA); + gpt->PartitionEntryLBA = PED_CPU_TO_LE64 (2); + } + + gpt->FirstUsableLBA = PED_CPU_TO_LE64 (gpt_disk_data->data_area.start); + gpt->LastUsableLBA = PED_CPU_TO_LE64 (gpt_disk_data->data_area.end); + gpt->DiskGUID = gpt_disk_data->uuid; + gpt->NumberOfPartitionEntries + = PED_CPU_TO_LE32 (gpt_disk_data->entry_count); + gpt->SizeOfPartitionEntry = PED_CPU_TO_LE32 (sizeof (GuidPartitionEntry_t)); + gpt->PartitionEntryArrayCRC32 = PED_CPU_TO_LE32 (ptes_crc); + + uint32_t crc; + if (pth_crc32 (disk->dev, gpt, &crc) != 0) + return 1; + + gpt->HeaderCRC32 = PED_CPU_TO_LE32 (crc); + return 0; +} + +static void +_partition_generate_part_entry (PedPartition *part, GuidPartitionEntry_t *pte) +{ + GPTPartitionData *gpt_part_data = part->disk_specific; + unsigned int i; + + PED_ASSERT (gpt_part_data != NULL); + + pte->PartitionTypeGuid = gpt_part_data->type; + pte->UniquePartitionGuid = gpt_part_data->uuid; + pte->StartingLBA = PED_CPU_TO_LE64 (part->geom.start); + pte->EndingLBA = PED_CPU_TO_LE64 (part->geom.end); + pte->Attributes = gpt_part_data->attributes; + + for (i = 0; i < 36; i++) + pte->PartitionName[i] = gpt_part_data->name[i]; +} + +static int +gpt_write (const PedDisk *disk) +{ + GPTDiskData *gpt_disk_data; + uint32_t ptes_crc; + uint8_t *pth_raw; + GuidPartitionTableHeader_t *gpt; + PedPartition *part; + + PED_ASSERT (disk != NULL); + PED_ASSERT (disk->dev != NULL); + PED_ASSERT (disk->disk_specific != NULL); + + gpt_disk_data = disk->disk_specific; + + size_t ptes_bytes = (gpt_disk_data->entry_count + * sizeof (GuidPartitionEntry_t)); + size_t ss = disk->dev->sector_size; + PedSector ptes_sectors = (ptes_bytes + ss - 1) / ss; + /* Note that we allocate a little more than ptes_bytes, + when that number is not a multiple of sector size. */ + GuidPartitionEntry_t *ptes = calloc (ptes_sectors, ss); + if (!ptes) + goto error; + for (part = ped_disk_next_partition (disk, NULL); part; + part = ped_disk_next_partition (disk, part)) + { + if (part->type != 0) + continue; + _partition_generate_part_entry (part, &ptes[part->num - 1]); + } + + ptes_crc = efi_crc32 (ptes, ptes_bytes); + + /* 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. */ + if (_generate_header (disk, 0, ptes_crc, &gpt) != 0) { + pth_free(gpt); + goto error_free_ptes; + } + pth_raw = pth_get_raw (disk->dev, gpt); + pth_free (gpt); + if (pth_raw == NULL) + goto error_free_ptes; + int write_ok = ped_device_write (disk->dev, pth_raw, 1, 1); + free (pth_raw); + if (!write_ok) + goto error_free_ptes; + if (!ped_device_write (disk->dev, ptes, 2, ptes_sectors)) + goto error_free_ptes; + + /* Write Alternate PTH & PTEs */ + /* FIXME: Caution: this code is nearly identical to what's just above. */ + if (_generate_header (disk, 1, ptes_crc, &gpt) != 0) { + pth_free(gpt); + goto error_free_ptes; + } + pth_raw = pth_get_raw (disk->dev, gpt); + pth_free (gpt); + if (pth_raw == NULL) + goto error_free_ptes; + write_ok = ped_device_write (disk->dev, pth_raw, gpt_disk_data->AlternateLBA, 1); + free (pth_raw); + if (!write_ok) + goto error_free_ptes; + if (!ped_device_write (disk->dev, ptes, + gpt_disk_data->AlternateLBA - ptes_sectors, ptes_sectors)) + goto error_free_ptes; + + free (ptes); + return ped_device_sync (disk->dev); + +error_free_ptes: + free (ptes); +error: + return 0; +} +#endif /* !DISCOVER_ONLY */ + +static int +add_metadata_part (PedDisk *disk, PedSector start, PedSector length) +{ + PedPartition *part; + PedConstraint *constraint_exact; + PED_ASSERT (disk != NULL); + + part = ped_partition_new (disk, PED_PARTITION_METADATA, NULL, + start, start + length - 1); + if (!part) + goto error; + + constraint_exact = ped_constraint_exact (&part->geom); + if (!ped_disk_add_partition (disk, part, constraint_exact)) + goto error_destroy_constraint; + ped_constraint_destroy (constraint_exact); + return 1; + +error_destroy_constraint: + ped_constraint_destroy (constraint_exact); + ped_partition_destroy (part); +error: + return 0; +} + +static PedPartition * +gpt_partition_new (const PedDisk *disk, + PedPartitionType part_type, + const PedFileSystemType *fs_type, PedSector start, + PedSector end) +{ + PedPartition *part; + GPTPartitionData *gpt_part_data; + + part = _ped_partition_alloc (disk, part_type, fs_type, start, end); + if (!part) + goto error; + + if (part_type != 0) + return part; + + gpt_part_data = part->disk_specific = + ped_malloc (sizeof (GPTPartitionData)); + if (!gpt_part_data) + goto error_free_part; + + gpt_part_data->type = PARTITION_LINUX_DATA_GUID; + gpt_part_data->translated_name = 0; + uuid_generate ((unsigned char *) &gpt_part_data->uuid); + swap_uuid_and_efi_guid (&gpt_part_data->uuid); + memset (gpt_part_data->name, 0, sizeof gpt_part_data->name); + memset (&gpt_part_data->attributes, 0, sizeof gpt_part_data->attributes); + return part; + +error_free_part: + _ped_partition_free (part); +error: + return NULL; +} + +static PedPartition * +gpt_partition_duplicate (const PedPartition *part) +{ + PedPartition *result; + GPTPartitionData *part_data = part->disk_specific; + GPTPartitionData *result_data; + + result = _ped_partition_alloc (part->disk, part->type, part->fs_type, + part->geom.start, part->geom.end); + if (!result) + goto error; + result->num = part->num; + + if (result->type != 0) + return result; + + result_data = result->disk_specific = + ped_malloc (sizeof (GPTPartitionData)); + if (!result_data) + goto error_free_part; + + *result_data = *part_data; + if (part_data->translated_name) { + result_data->translated_name = xstrdup (part_data->translated_name); + } else { + result_data->translated_name = 0; + } + return result; + +error_free_part: + _ped_partition_free (result); +error: + return NULL; +} + +static void +gpt_partition_destroy (PedPartition *part) +{ + if (part->type == 0) + { + PED_ASSERT (part->disk_specific != NULL); + GPTPartitionData *gpt_part_data = part->disk_specific; + free (gpt_part_data->translated_name); + free (part->disk_specific); + } + + _ped_partition_free (part); +} + +/* is_skip_guid checks the guid against the list of guids that should not be + * overridden by set_system. It returns a 1 if it is in the list. +*/ +static bool +is_skip_guid(efi_guid_t guid) { + int n = sizeof(skip_set_system_guids) / sizeof(skip_set_system_guids[0]); + for (int i = 0; i < n; ++i) { + if (guid_cmp(guid, skip_set_system_guids[i]) == 0) { + return true; + } + } + + return false; +} + +static int +gpt_partition_set_system (PedPartition *part, + const PedFileSystemType *fs_type) +{ + GPTPartitionData *gpt_part_data = part->disk_specific; + + PED_ASSERT (gpt_part_data != NULL); + + part->fs_type = fs_type; + + // Is this a GUID that should skip fs_type checking? + if (is_skip_guid(gpt_part_data->type)) { + return 1; + } + + if (fs_type) + { + if (strncmp (fs_type->name, "fat", 3) == 0 + || strcmp (fs_type->name, "udf") == 0 + || strcmp (fs_type->name, "ntfs") == 0) + { + gpt_part_data->type = PARTITION_BASIC_DATA_GUID; + return 1; + } + if (strncmp (fs_type->name, "hfs", 3) == 0) + { + gpt_part_data->type = PARTITION_APPLE_HFS_GUID; + return 1; + } + if (strstr (fs_type->name, "swap")) + { + gpt_part_data->type = PARTITION_SWAP_GUID; + return 1; + } + } + + gpt_part_data->type = PARTITION_LINUX_DATA_GUID; + return 1; +} + +/* Allocate metadata partitions for the GPTH and PTES */ +static int +gpt_alloc_metadata (PedDisk *disk) +{ + PedSector gptlength, pteslength = 0; + GPTDiskData *gpt_disk_data; + + PED_ASSERT (disk != NULL); + PED_ASSERT (disk->dev != NULL); + PED_ASSERT (disk->disk_specific != NULL); + gpt_disk_data = disk->disk_specific; + + gptlength = ped_div_round_up (sizeof (GuidPartitionTableHeader_t), + disk->dev->sector_size); + pteslength = ped_div_round_up (gpt_disk_data->entry_count + * sizeof (GuidPartitionEntry_t), + disk->dev->sector_size); + + /* metadata at the start of the disk includes the MBR */ + if (!add_metadata_part (disk, GPT_PMBR_LBA, + GPT_PMBR_SECTORS + gptlength + pteslength)) + return 0; + + /* metadata at the end of the disk */ + if (!add_metadata_part (disk, disk->dev->length - gptlength - pteslength, + gptlength + pteslength)) + return 0; + + return 1; +} + +/* Does nothing, as the read/new/destroy functions maintain part->num */ +static int +gpt_partition_enumerate (PedPartition *part) +{ + GPTDiskData *gpt_disk_data = part->disk->disk_specific; + int i; + + /* never change the partition numbers */ + if (part->num != -1) + return 1; + + for (i = 1; i <= gpt_disk_data->entry_count; i++) + { + if (!ped_disk_get_partition (part->disk, i)) + { + part->num = i; + return 1; + } + } + + PED_ASSERT (0); + + return 0; /* used if debug is disabled */ +} + +static int +gpt_disk_set_flag (PedDisk *disk, PedDiskFlag flag, int state) +{ + GPTDiskData *gpt_disk_data = disk->disk_specific; + switch (flag) + { + case PED_DISK_GPT_PMBR_BOOT: + gpt_disk_data->pmbr_boot = state; + return 1; + default: + return 0; + } +} + +static int +gpt_disk_is_flag_available(const PedDisk *disk, PedDiskFlag flag) +{ + switch (flag) + { + case PED_DISK_GPT_PMBR_BOOT: + return 1; + default: + return 0; + } +} + +static uint8_t* +gpt_disk_get_uuid (const PedDisk *disk) +{ + GPTDiskData *gpt_disk_data = disk->disk_specific; + + efi_guid_t uuid = gpt_disk_data->uuid; + + /* uuid is always LE, while uint8_t is always kind of BE */ + + uuid.time_low = PED_SWAP32(uuid.time_low); + uuid.time_mid = PED_SWAP16(uuid.time_mid); + uuid.time_hi_and_version = PED_SWAP16(uuid.time_hi_and_version); + + uint8_t *buf = ped_malloc(sizeof (uuid_t)); + memcpy(buf, &uuid, sizeof (uuid_t)); + return buf; +} + +static int +gpt_disk_get_flag (const PedDisk *disk, PedDiskFlag flag) +{ + GPTDiskData *gpt_disk_data = disk->disk_specific; + switch (flag) + { + case PED_DISK_GPT_PMBR_BOOT: + return gpt_disk_data->pmbr_boot; + break; + default: + return 0; + } +} + +static int +gpt_partition_set_flag (PedPartition *part, PedPartitionFlag flag, int state) +{ + GPTPartitionData *gpt_part_data; + PED_ASSERT (part != NULL); + PED_ASSERT (part->disk_specific != NULL); + gpt_part_data = part->disk_specific; + + const struct flag_uuid_mapping_t* p = gpt_find_flag_uuid_mapping (flag); + if (p) + { + if (state) { + gpt_part_data->type = p->type_uuid; + } else if (guid_cmp (gpt_part_data->type, p->type_uuid) == 0) { + // Clear the GUID so that fs_type will be used to return it to the default + gpt_part_data->type = PARTITION_LINUX_DATA_GUID; + return gpt_partition_set_system (part, part->fs_type); + } + return 1; + } + + switch (flag) + { + case PED_PARTITION_HIDDEN: + gpt_part_data->attributes.RequiredToFunction = state; + return 1; + case PED_PARTITION_LEGACY_BOOT: + gpt_part_data->attributes.LegacyBIOSBootable = state; + return 1; + case PED_PARTITION_NO_AUTOMOUNT: + gpt_part_data->attributes.NoAutomount = state; + return 1; + case PED_PARTITION_ROOT: + case PED_PARTITION_LBA: + default: + return 0; + } + return 1; +} + +static int _GL_ATTRIBUTE_PURE +gpt_partition_get_flag (const PedPartition *part, PedPartitionFlag flag) +{ + GPTPartitionData *gpt_part_data; + PED_ASSERT (part->disk_specific != NULL); + gpt_part_data = part->disk_specific; + + const struct flag_uuid_mapping_t* p = gpt_find_flag_uuid_mapping (flag); + if (p) + return guid_cmp (gpt_part_data->type, p->type_uuid) == 0; + + switch (flag) + { + case PED_PARTITION_HIDDEN: + return gpt_part_data->attributes.RequiredToFunction; + case PED_PARTITION_LEGACY_BOOT: + return gpt_part_data->attributes.LegacyBIOSBootable; + case PED_PARTITION_NO_AUTOMOUNT: + return gpt_part_data->attributes.NoAutomount; + case PED_PARTITION_LBA: + case PED_PARTITION_ROOT: + default: + return 0; + } + return 0; +} + +static int +gpt_partition_is_flag_available (const PedPartition *part, + PedPartitionFlag flag) +{ + if (gpt_find_flag_uuid_mapping (flag)) + return 1; + + switch (flag) + { + case PED_PARTITION_HIDDEN: + case PED_PARTITION_LEGACY_BOOT: + case PED_PARTITION_NO_AUTOMOUNT: + return 1; + case PED_PARTITION_ROOT: + case PED_PARTITION_LBA: + default: + return 0; + } + return 0; +} + +static void +gpt_partition_set_name (PedPartition *part, const char *name) +{ + GPTPartitionData *gpt_part_data = part->disk_specific; + + free(gpt_part_data->translated_name); + gpt_part_data->translated_name = xstrdup(name); + iconv_t conv = iconv_open ("UCS-2LE", nl_langinfo (CODESET)); + if (conv == (iconv_t)-1) + goto err; + char *inbuff = gpt_part_data->translated_name; + char *outbuff = (char *)&gpt_part_data->name; + size_t inbuffsize = strlen (inbuff) + 1; + size_t outbuffsize = 72; + if (iconv (conv, &inbuff, &inbuffsize, &outbuff, &outbuffsize) == -1) + goto err; + iconv_close (conv); + return; + err: + ped_exception_throw (PED_EXCEPTION_WARNING, + PED_EXCEPTION_IGNORE, + _("failed to translate partition name")); + iconv_close (conv); +} + +static const char * +gpt_partition_get_name (const PedPartition *part) +{ + GPTPartitionData *gpt_part_data = part->disk_specific; + if (gpt_part_data->translated_name == NULL) + { + char buffer[200]; + iconv_t conv = iconv_open (nl_langinfo (CODESET), "UCS-2LE"); + if (conv == (iconv_t)-1) + goto err; + char *inbuff = (char *)&gpt_part_data->name; + char *outbuff = buffer; + size_t inbuffsize = 72; + size_t outbuffsize = sizeof(buffer); + if (iconv (conv, &inbuff, &inbuffsize, &outbuff, &outbuffsize) == -1) + goto err; + iconv_close (conv); + *outbuff = 0; + gpt_part_data->translated_name = xstrdup (buffer); + return gpt_part_data->translated_name; + err: + ped_exception_throw (PED_EXCEPTION_WARNING, + PED_EXCEPTION_IGNORE, + _("failed to translate partition name")); + iconv_close (conv); + return ""; + } + return gpt_part_data->translated_name; +} + + +static int +gpt_partition_set_type_uuid (PedPartition *part, const uint8_t *uuid) +{ + GPTPartitionData *gpt_part_data = part->disk_specific; + + efi_guid_t* type_uuid = &gpt_part_data->type; + memcpy(type_uuid, uuid, sizeof (efi_guid_t)); + + /* type_uuid is always LE, while uint8_t is always kind of BE */ + + type_uuid->time_low = PED_SWAP32(type_uuid->time_low); + type_uuid->time_mid = PED_SWAP16(type_uuid->time_mid); + type_uuid->time_hi_and_version = PED_SWAP16(type_uuid->time_hi_and_version); + + return 1; +} + + +static uint8_t* +gpt_partition_get_type_uuid (const PedPartition *part) +{ + const GPTPartitionData *gpt_part_data = part->disk_specific; + + efi_guid_t type_uuid = gpt_part_data->type; + + /* type_uuid is always LE, while uint8_t is always kind of BE */ + + type_uuid.time_low = PED_SWAP32(type_uuid.time_low); + type_uuid.time_mid = PED_SWAP16(type_uuid.time_mid); + type_uuid.time_hi_and_version = PED_SWAP16(type_uuid.time_hi_and_version); + + uint8_t *buf = ped_malloc(sizeof (uuid_t)); + memcpy(buf, &type_uuid, sizeof (uuid_t)); + return buf; +} + +static uint8_t* +gpt_partition_get_uuid (const PedPartition *part) +{ + const GPTPartitionData *gpt_part_data = part->disk_specific; + + efi_guid_t uuid = gpt_part_data->uuid; + + /* uuid is always LE, while uint8_t is always kind of BE */ + + uuid.time_low = PED_SWAP32(uuid.time_low); + uuid.time_mid = PED_SWAP16(uuid.time_mid); + uuid.time_hi_and_version = PED_SWAP16(uuid.time_hi_and_version); + + uint8_t *buf = ped_malloc(sizeof (uuid_t)); + memcpy(buf, &uuid, sizeof (uuid_t)); + return buf; +} + +static int +gpt_get_max_primary_partition_count (const PedDisk *disk) +{ + const GPTDiskData *gpt_disk_data = disk->disk_specific; + return gpt_disk_data->entry_count; +} + +/* + * From (http://developer.apple.com/technotes/tn2006/tn2166.html Chapter 5). + * According to the specs the first LBA (LBA0) is not relevant (it exists + * to maintain compatibility). on the second LBA(LBA1) gpt places the + * header. The header is as big as the block size. After the header we + * find the Entry array. Each element of said array, describes each + * partition. One can have as much elements as can fit between the end of + * the second LBA (where the header ends) and the FirstUsableLBA. + * FirstUsableLBA is the first logical block that is used for contents + * and is defined in header. + * + * /---------------------------------------------------\ + * | BLOCK0 | HEADER | Entry Array | First Usable LBA | + * | | BLOCK1 | | | + * \---------------------------------------------------/ + * / \ + * /----------/ \----------\ + * /-----------------------------------------\ + * | E1 | E2 | E3 |...............| EN | + * \-----------------------------------------/ + * + * The number of possible partitions or supported partitions is: + * SP = FirstUsableLBA*Blocksize - 2*Blocksize / SizeOfPartitionEntry + * SP = Blocksize(FirstusableLBA - 2) / SizeOfPartitoinEntry + */ +static bool +gpt_get_max_supported_partition_count (const PedDisk *disk, int *max_n) +{ + GuidPartitionTableHeader_t *pth = NULL; + uint8_t *pth_raw = ped_malloc (pth_get_size (disk->dev)); + + if (ped_device_read (disk->dev, pth_raw, 1, GPT_HEADER_SECTORS) + || ped_device_read (disk->dev, pth_raw, + disk->dev->length, GPT_HEADER_SECTORS)) + pth = pth_new_from_raw (disk->dev, pth_raw); + free (pth_raw); + + if (pth == NULL) + return false; + + if (!_header_is_valid (disk, pth, 1)) + { + pth->FirstUsableLBA = PED_CPU_TO_LE64 (34); + pth->SizeOfPartitionEntry + = PED_CPU_TO_LE32 (sizeof (GuidPartitionEntry_t)); + } + + *max_n = (disk->dev->sector_size * (PED_LE64_TO_CPU (pth->FirstUsableLBA) - 2) + / PED_LE32_TO_CPU (pth->SizeOfPartitionEntry)); + pth_free (pth); + return true; +} + +static PedConstraint * +_non_metadata_constraint (const PedDisk *disk) +{ + GPTDiskData *gpt_disk_data = disk->disk_specific; + + return ped_constraint_new_from_max (&gpt_disk_data->data_area); +} + +static int +gpt_partition_align (PedPartition *part, const PedConstraint *constraint) +{ + PED_ASSERT (part != NULL); + + if (_ped_partition_attempt_align (part, constraint, + _non_metadata_constraint (part->disk))) + return 1; + +#ifndef DISCOVER_ONLY + ped_exception_throw (PED_EXCEPTION_ERROR, + PED_EXCEPTION_CANCEL, + _("Unable to satisfy all constraints on the partition.")); +#endif + return 0; +} + +#include "pt-common.h" +PT_define_limit_functions (gpt) + +static PedDiskOps gpt_disk_ops = +{ + clobber: NULL, + write: NULL_IF_DISCOVER_ONLY (gpt_write), + + partition_set_name: gpt_partition_set_name, + partition_get_name: gpt_partition_get_name, + partition_set_type_id: NULL, + partition_get_type_id: NULL, + partition_set_type_uuid: gpt_partition_set_type_uuid, + partition_get_type_uuid: gpt_partition_get_type_uuid, + partition_get_uuid: gpt_partition_get_uuid, + disk_set_flag: gpt_disk_set_flag, + disk_get_flag: gpt_disk_get_flag, + disk_is_flag_available: gpt_disk_is_flag_available, + disk_get_uuid: gpt_disk_get_uuid, + + PT_op_function_initializers (gpt) +}; + +static PedDiskType gpt_disk_type = +{ + next: NULL, + name: "gpt", + ops: &gpt_disk_ops, + features: PED_DISK_TYPE_PARTITION_NAME | PED_DISK_TYPE_PARTITION_TYPE_UUID | + PED_DISK_TYPE_DISK_UUID | PED_DISK_TYPE_PARTITION_UUID +}; + +void +ped_disk_gpt_init () +{ + ped_disk_type_register (&gpt_disk_type); +} + +void +ped_disk_gpt_done () +{ + ped_disk_type_unregister (&gpt_disk_type); +} + +verify (sizeof (GuidPartitionEntryAttributes_t) == 8); +verify (sizeof (GuidPartitionEntry_t) == 128); |