[parted-devel] map gpt partitions to msdos primary entries

Olaf Hering olh at suse.de
Mon May 14 17:28:54 UTC 2007


This patch adds a new 'set X map_gpt_to_msdos Y' function.
Using it will break the EFI spec (I cant verify it because the EFI spec
is not free).


On Apple systems with Intel hardware it is sometimes required to install
EFI unaware systems, such as Windows, grub or lilo. With this change,
parted will not destroy the existing primary partitions.
But, parted does some checks to make sure the LBA mapping in the
individual partitions match the existing gpt partitions. If they differ,
the msdos entry will be removed.

One issue remains: the msdos partition types. gpt types can not be
mapped 1 to 1 to msdos partition types. I added a simple mapping to
(hopefully) let grub recognize the partition as readable.


This patch needes testing on Apple systems.
I do not have any DOS compatbile hardware.

Example usage:

sudo parted -s /dev/sdb mklabel gpt
sudo parted -s /dev/sdb mkpart primary 17k 1G
sudo parted -s /dev/sdb mkpart primary 2G 3G
sudo parted -s /dev/sdb set 1 map_gpt_to_msdos 2
sudo parted -s /dev/sdb set 1 boot on
sudo parted -s /dev/sdb set 1 boot off

---
 doc/C/parted.8         |    2 
 doc/parted.texi        |    4 +
 include/parted/disk.h  |    5 -
 libparted/disk.c       |    2 
 libparted/labels/gpt.c |  192 +++++++++++++++++++++++++++++++++++++++++--------
 parted/parted.c        |   16 +++-
 6 files changed, 185 insertions(+), 36 deletions(-)

--- a/doc/C/parted.8
+++ b/doc/C/parted.8
@@ -110,7 +110,7 @@ or an LVM logical volume if necessary.
 .B set \fIpartition\fP \fIflag\fP \fIstate\fP
 Change the state of the \fIflag\fP on \fIpartition\fP to \fIstate\fP.
 Supported flags are: "boot", "root", "swap", "hidden", "raid", "lvm", "lba",
-and "palo".
+"hp-service", "palo", "prep", "msftres" and "map_gpt_to_msdos".
 \fIstate\fP should be either "on" or "off".
 .TP
 .B unit \fIunit\fP
--- a/doc/parted.texi
+++ b/doc/parted.texi
@@ -1045,6 +1045,10 @@ by the Linux/PA-RISC boot loader, palo.
 (MS-DOS) - this flag can be enabled so that the partition can be used
 as a PReP boot partition on PowerPC PReP or IBM RS6K/CHRP hardware.
 
+ at item map_gpt_to_msdos
+(GPT) - this flag can be used to establish a mapping between a gpt partition
+entry and an entry for a primary partition in the msdos partition table.
+
 @end table
 
 The print command displays all enabled flags for each partition.
--- a/include/parted/disk.h
+++ b/include/parted/disk.h
@@ -53,10 +53,11 @@ enum _PedPartitionFlag {
         PED_PARTITION_HPSERVICE=8,
         PED_PARTITION_PALO=9,
         PED_PARTITION_PREP=10,
-        PED_PARTITION_MSFT_RESERVED=11
+        PED_PARTITION_MSFT_RESERVED=11,
+	PED_PARTITION_MAP_GPT_TO_MSDOS
 };
 #define PED_PARTITION_FIRST_FLAG        PED_PARTITION_BOOT
-#define PED_PARTITION_LAST_FLAG         PED_PARTITION_MSFT_RESERVED
+#define PED_PARTITION_LAST_FLAG         PED_PARTITION_MAP_GPT_TO_MSDOS
 
 enum _PedDiskTypeFeature {
         PED_DISK_TYPE_EXTENDED=1,       /**< supports extended partitions */
--- a/libparted/disk.c
+++ b/libparted/disk.c
@@ -2182,6 +2182,8 @@ ped_partition_flag_get_name (PedPartitio
 		return N_("prep");
 	case PED_PARTITION_MSFT_RESERVED:
 		return N_("msftres");
+	case PED_PARTITION_MAP_GPT_TO_MSDOS:
+		return N_("map_gpt_to_msdos");
 
 	default:
 		ped_exception_throw (
--- a/libparted/labels/gpt.c
+++ b/libparted/labels/gpt.c
@@ -234,7 +234,10 @@ struct __attribute__ ((packed)) _LegacyM
 /* uses libparted's disk_specific field in PedDisk, to store our info */
 struct __attribute__ ((packed)) _GPTDiskData {
 	PedGeometry	data_area;
+	LegacyMBR_t	mbr;
 	int		entry_count;
+	int		msdos_efi;
+	unsigned short	gpt_to_msdos_map[4];
 	efi_guid_t	uuid;
 };
 
@@ -523,6 +526,20 @@ error_free:
 }
 #endif /* !DISCOVER_ONLY */
 
+/* Generates the protective MBR (to keep DOS happy), during mklabel gpt */
+static void
+gpt_create_pmbr (LegacyMBR_t *pmbr, const PedDevice *dev)
+{
+	memset(pmbr, 0, sizeof(LegacyMBR_t));
+	pmbr->Signature = PED_CPU_TO_LE16(MSDOS_MBR_SIGNATURE);
+	pmbr->PartitionRecord[0].OSType      = EFI_PMBR_OSTYPE_EFI;
+	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);
+}
+
 static PedDisk *
 gpt_alloc (const PedDevice * dev)
 {
@@ -545,6 +562,9 @@ gpt_alloc (const PedDevice * dev)
 	gpt_disk_data->entry_count = GPT_DEFAULT_PARTITION_ENTRIES;
 	uuid_generate ((unsigned char*) &gpt_disk_data->uuid);
 	swap_uuid_and_efi_guid((unsigned char*)(&gpt_disk_data->uuid));
+	memset(&gpt_disk_data->gpt_to_msdos_map[0], 0, sizeof(gpt_disk_data->gpt_to_msdos_map));
+	gpt_create_pmbr(&gpt_disk_data->mbr, dev);
+	gpt_disk_data->msdos_efi = 1;
 	return disk;
 
 error_free_disk:
@@ -770,11 +790,42 @@ _parse_part_entry (PedDisk* disk, GuidPa
 	return part;
 }
 
+static void
+gpt_find_and_map_msdos_entry(PedPartition *part, GPTDiskData *gpt_disk_data)
+{
+	LegacyMBR_t *mbr = &gpt_disk_data->mbr;
+	uint64_t start = (part->geom.dev->sector_size / 512) * part->geom.start;
+	uint64_t end = (part->geom.dev->sector_size / 512) * part->geom.end;
+	unsigned short i;
+
+	/* msdos partitions can not go beyond 2TB */
+	if (end & 0xffffffff00000000ULL)
+		return;
+	for (i = 0; i < 4; i++) {
+		uint32_t msdos_s, msdos_e;
+		if (!mbr->PartitionRecord[i].OSType)
+			continue;
+		msdos_s = PED_LE32_TO_CPU(mbr->PartitionRecord[i].StartingLBA);
+		msdos_e = msdos_s + PED_LE32_TO_CPU(mbr->PartitionRecord[i].SizeInLBA);
+		if (start == msdos_s && end == msdos_e) {
+			if ((i + 1) == gpt_disk_data->msdos_efi)
+				fprintf(stderr, "%s:%s(%u) %d: is EFI partition\n", __FILE__,__func__,__LINE__,i+1);
+			else if (gpt_disk_data->gpt_to_msdos_map[i])
+				fprintf(stderr, "%s:%s(%u) %d: already mapped to gpt #%u\n", __FILE__,__func__,__LINE__,i+1,gpt_disk_data->gpt_to_msdos_map[i]);
+			else
+				gpt_disk_data->gpt_to_msdos_map[i] = part->num;
+			break;
+		}
+	}
+}
+
 /************************************************************
  *  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).
+ *    ?? only a single primary, with 0xee ??
+ *
  *  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).
@@ -806,11 +857,11 @@ gpt_read (PedDisk * disk)
 
 	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))
+	if (!ped_device_read (disk->dev, &gpt_disk_data->mbr, GPT_PMBR_LBA, GPT_PMBR_SECTORS))
+		goto error;
+	/* at least one 0xEE partition is required */
+	gpt_disk_data->msdos_efi = _pmbr_is_valid(&gpt_disk_data->mbr);
+	if (!gpt_disk_data->msdos_efi)
 		goto error;
 
 	if (_read_header (disk->dev, &gpt, 1)) {
@@ -905,6 +956,7 @@ gpt_read (PedDisk * disk)
 			ped_partition_destroy (part);
 			goto error_delete_all;
 		}
+		gpt_find_and_map_msdos_entry(part, gpt_disk_data);
 		ped_constraint_destroy (constraint_exact);
 	}
 	ped_free (ptes);
@@ -927,28 +979,6 @@ error:
 }
 
 #ifndef DISCOVER_ONLY
-/* Writes the protective MBR (to keep DOS happy) */
-static int
-_write_pmbr (PedDevice * dev)
-{
-	LegacyMBR_t pmbr;
-
-	memset(&pmbr, 0, sizeof(pmbr));
-	pmbr.Signature = PED_CPU_TO_LE16(MSDOS_MBR_SIGNATURE);
-	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 ((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);
-
-	return ped_device_write (dev, &pmbr, GPT_PMBR_LBA, GPT_PMBR_SECTORS);
-}
-
 static void
 _generate_header (const PedDisk* disk, int alternate, uint32_t ptes_crc,
 		  GuidPartitionTableHeader_t** gpt_p)
@@ -1016,6 +1046,66 @@ _partition_generate_part_entry (PedParti
 				(uint16_t) gpt_part_data->name[i]);
 }
 
+#define PARTITION_FAT32_LBA	0x0c
+#define PARTITION_LINUX_SWAP	0x82
+#define PARTITION_LINUX		0x83
+#define PARTITION_LINUX_LVM	0x8e
+#define PARTITION_APPLE_HFS	0xaf
+#define PARTITION_LINUX_RAID	0xfd
+static void
+gpt_write_add_msdos_entry(int msdos_nr, GPTDiskData *gpt_disk_data, PedPartition *part)
+{
+	PartitionRecord_t *partition = &gpt_disk_data->mbr.PartitionRecord[msdos_nr];
+	GPTPartitionData *gpt_part_data = part->disk_specific;
+	uint64_t start = (part->geom.dev->sector_size / 512) * part->geom.start;
+	uint64_t end = (part->geom.dev->sector_size / 512) * part->geom.end;
+	uint32_t msdos_s, msdos_e;
+	uint8_t OSType;
+
+	/* msdos partitions can not go beyond 2TB */
+	if (end & 0xffffffff00000000ULL)
+		return;
+
+	msdos_s = start;
+	msdos_e = end - start;
+	partition->StartingLBA = PED_CPU_TO_LE32(msdos_s);
+	partition->SizeInLBA = PED_CPU_TO_LE32(msdos_e);
+
+	if (gpt_part_data->boot)
+		partition->BootIndicator = 0x80;
+	else
+		partition->BootIndicator = 0;
+
+	if (!guid_cmp (gpt_part_data->type, PARTITION_SYSTEM_GUID))
+		OSType = PARTITION_FAT32_LBA;
+	else if (!guid_cmp (gpt_part_data->type, PARTITION_RAID_GUID))
+		OSType = PARTITION_LINUX_RAID;
+	else if (!guid_cmp (gpt_part_data->type, PARTITION_LVM_GUID))
+		OSType = PARTITION_LINUX_LVM;
+	else if (!guid_cmp (gpt_part_data->type, PARTITION_APPLE_HFS_GUID))
+		OSType = PARTITION_APPLE_HFS; /* FIXME */
+	else if (!guid_cmp (gpt_part_data->type, PARTITION_HPSERVICE_GUID))
+		OSType = 0xda;
+        else if (!guid_cmp (gpt_part_data->type, PARTITION_MSFT_RESERVED_GUID))
+		OSType = 0xda;
+	else
+		OSType = PARTITION_LINUX;
+	/* FACT: gpt type can not be mapped to msdos type. */
+	partition->OSType = OSType;
+	/* Anyone who requires CHS must use plain msdos table */
+}
+
+static void
+gpt_write_wipe_empty_msdos_entries(GPTDiskData *gpt_disk_data)
+{
+	int i;
+
+	for (i = 0; i < 4; i++) {
+		if ((i + 1) != gpt_disk_data->msdos_efi && !gpt_disk_data->gpt_to_msdos_map[i])
+			memset(&gpt_disk_data->mbr.PartitionRecord[i], 0, sizeof(PartitionRecord_t));
+	}
+}
+
 static int
 gpt_write(const PedDisk * disk)
 {
@@ -1026,6 +1116,7 @@ gpt_write(const PedDisk * disk)
 	GuidPartitionTableHeader_t* gpt;
 	PedPartition* part;
 	int ptes_size;
+	int i;
 
 	PED_ASSERT (disk != NULL, goto error);
 	PED_ASSERT (disk->dev != NULL, goto error);
@@ -1042,13 +1133,19 @@ gpt_write(const PedDisk * disk)
 	     part = ped_disk_next_partition (disk, part)) {
 		if (part->type != 0)
 			continue;
+		for (i = 0; i < 4; i++) {
+			if (gpt_disk_data->gpt_to_msdos_map[i] == part->num)
+				gpt_write_add_msdos_entry(i, gpt_disk_data, part);
+		}
 		_partition_generate_part_entry (part, &ptes[part->num - 1]);
 	}
 
+	gpt_write_wipe_empty_msdos_entries(gpt_disk_data);
+
 	ptes_crc = efi_crc32 (ptes, ptes_size);
 
-	/* Write protective MBR */
-	if (!_write_pmbr (disk->dev))
+	/* Write MBR */
+	if (!ped_device_write (disk->dev, &gpt_disk_data->mbr, GPT_PMBR_LBA, GPT_PMBR_SECTORS))
 		goto error_free_ptes;
 
 	/* Write PTH and PTEs */
@@ -1177,8 +1274,14 @@ error:
 static void
 gpt_partition_destroy (PedPartition *part)
 {
+	GPTDiskData* gpt_disk_data = part->disk->disk_specific;
+	int i;
+
 	if (part->type == 0) {
 		PED_ASSERT (part->disk_specific != NULL, return);
+		for (i = 0; i < 4; i++)
+			if (gpt_disk_data->gpt_to_msdos_map[i] == part->num)
+				gpt_disk_data->gpt_to_msdos_map[i] = 0;
 		ped_free (part->disk_specific);
 	}
 
@@ -1288,6 +1391,34 @@ gpt_partition_enumerate (PedPartition* p
 	return 0; /* used if debug is disabled */
 }
 
+/*
+ * The whole purpose of this function is to make EFI unaware legacy software happy.
+ * This includes outdated versions of Windows, i386 lilo and grub.
+ * Adding more primary partitions to the msdos table will violate the EFI spec.
+ * It is only required on Apple systems with Intel hardware.
+ */
+static int
+gpt_partition_map_to_msdos(PedPartition *part, int state)
+{
+	GPTDiskData* gpt_disk_data = part->disk->disk_specific;
+	int i, msdos_nr = state;
+
+	if (msdos_nr < 1 || msdos_nr > 4)
+		return 0;
+	if (gpt_disk_data->msdos_efi == msdos_nr) {
+		fprintf(stderr, "%s(%u) can not map gpt partition %d to msdos partition %d because it is the 0xEE efi partition\n",
+			__func__, __LINE__, part->num, msdos_nr);
+		return 0;
+	}
+	/* The gpt partition will appear only once in the table */
+	for (i = 0; i < 4; i++)
+		if (gpt_disk_data->gpt_to_msdos_map[i] == part->num)
+			gpt_disk_data->gpt_to_msdos_map[i] = 0;
+
+	gpt_disk_data->gpt_to_msdos_map[msdos_nr - 1] = part->num;
+	return 1;
+}
+
 static int
 gpt_partition_set_flag(PedPartition *part,
 		       PedPartitionFlag flag,
@@ -1342,6 +1473,8 @@ gpt_partition_set_flag(PedPartition *par
         case PED_PARTITION_HIDDEN:
                 gpt_part_data->hidden = state;
                 return 1;
+	case PED_PARTITION_MAP_GPT_TO_MSDOS:
+		return gpt_partition_map_to_msdos(part, state);
 	case PED_PARTITION_SWAP:
 	case PED_PARTITION_ROOT:
 	case PED_PARTITION_LBA:
@@ -1391,6 +1524,7 @@ gpt_partition_is_flag_available(const Pe
 	case PED_PARTITION_HPSERVICE:
         case PED_PARTITION_MSFT_RESERVED:
         case PED_PARTITION_HIDDEN:        
+        case PED_PARTITION_MAP_GPT_TO_MSDOS:
 		return 1;
 	case PED_PARTITION_SWAP:
 	case PED_PARTITION_ROOT:
--- a/parted/parted.c
+++ b/parted/parted.c
@@ -1903,10 +1903,18 @@ do_set (PedDevice** dev)
                 goto error_destroy_disk;
         state = (ped_partition_get_flag (part, flag) == 0 ? 1 : 0);      
         
-        if (!is_toggle_mode) {
-                if (!command_line_get_state (_("New state?"), &state))
-		            goto error_destroy_disk;
-        }
+	switch (flag) {
+	case PED_PARTITION_MAP_GPT_TO_MSDOS:
+		if (!command_line_get_integer (_("target msdos partition (2 or 3 or 4)?"), &state))
+			goto error_destroy_disk;
+		break;
+	default:
+		if (!is_toggle_mode) {
+			if (!command_line_get_state (_("New state?"), &state))
+				    goto error_destroy_disk;
+		}
+	break;
+	}
     
         if (!ped_partition_set_flag (part, flag, state))
 	        	goto error_destroy_disk;



More information about the parted-devel mailing list