[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

GPT partlabel support for Mach



Hi!

I wanted to test some packages on the Hurd but my target machine is GPT,
and Mach only saw the protective MBR (which, well, working as designed,
but still suboptimal), so I implemented it based on the UEFI 2.8A spec;
it's probably not the most optimal code in a few places, but, well,
partition scanning is hardly a hot path.

Speaking of correct: as noted in a comment, AFAICT no driver
sets the sector size except for SCSI, and SCSI only ever sets it to 512;
this will be a problem on 4k sector drives with 4k-based pointers.

Tested on IDE, one hard drive, and up to three IDE and AHCI drives
in QEMU, all of which behave as expected.

A screenshot is worth a thousand words, so there's some here
<https://lfs.nabijaczleweli.xyz/0003-hurd-GPT>, but:

Dirty/broken primary GPT falls back to the backup GPT,
if that's also dirty, then partitioning is refused.

Defining DEBUG dumps the structures, but errors (bad CRC/signature,
switch to backup GPT, &c.) are logged regardless; with no errors
each partition is followed by its name in parentheses, if present, e.g.
  hd0: hd0s1 (szarotka-bios-boot) hd0s2 (szarotka-root-ng) hd0s3 (zest)
and if the signature is bad, e.g.:
  hd0: bad GPT signature "EFI!PART"; main GPT dirty, trying backup at 125045423;
  hd0s1 (szarotka-bios-boot) hd0s2 (szarotka-root-ng) hd0s3 (zest)

The patch applies cleanly on top of clean debian/2:1.8+git20200309-2 tag
from Salsa and has no conflicts with other debian patches; however, I'm
not sure what your patch policy is, or, indeed, if this is the right
place for the implementation in the code, but, well,
one way to find out!

Best,
наб

(Please keep me in CC, as I'm not subscribed to the list)

-- >8 --
Date: Thu, 28 May 2020 03:22:30 +0200
Subject: [PATCH] Support GPT disklabels and build them by default

Based on UEFI 2.8A spec
---
 i386/linux/dev/include/linux/autoconf.h |   2 +
 linux/dev/drivers/block/genhd.c         | 251 ++++++++++++++++++++++++
 linux/dev/include/linux/genhd.h         |  67 +++++++
 3 files changed, 320 insertions(+)

diff --git a/i386/linux/dev/include/linux/autoconf.h b/i386/linux/dev/include/linux/autoconf.h
index 75ff2aa..bd035d4 100644
--- a/i386/linux/dev/include/linux/autoconf.h
+++ b/i386/linux/dev/include/linux/autoconf.h
@@ -246,6 +246,8 @@
 #undef	CONFIG_BSD_DISKLABEL
 #undef	CONFIG_SMD_DISKLABEL
 
+#define CONFIG_GPT_DISKLABEL 1
+
 /*
  * Character devices
  */
diff --git a/linux/dev/drivers/block/genhd.c b/linux/dev/drivers/block/genhd.c
index f38654b..dda9007 100644
--- a/linux/dev/drivers/block/genhd.c
+++ b/linux/dev/drivers/block/genhd.c
@@ -29,6 +29,11 @@
 #endif
 #include <linux/hdreg.h>
 #include <alloca.h>
+#ifdef CONFIG_GPT_DISKLABEL
+#include <linux/blkdev.h>
+#include <kern/kalloc.h>
+#include <stddef.h>
+#endif
 
 #include <asm/system.h>
 
@@ -276,6 +281,246 @@ static void bsd_disklabel_partition(struct gendisk *hd, kdev_t dev)
 }
 #endif
 
+#ifdef CONFIG_GPT_DISKLABEL
+/*
+ * Compute a CRC32 but treat some range as if it were zeros.
+ *
+ * Straight copy of ether_crc_le() from linux/pcmcia-cs/include/linux/crc32.h, except for the first if/else
+ */
+static inline unsigned ether_crc_le_hole(int length, unsigned char *data, unsigned int skip_offset, unsigned int skip_length)
+{
+	static unsigned const ethernet_polynomial_le = 0xedb88320U;
+	unsigned int crc = 0xffffffff;      /* Initial value. */
+	while(--length >= 0) {
+		unsigned char current_octet = *data++;
+		if(skip_offset == 0 && skip_length-- != 0)
+			current_octet = 0;
+		else
+			--skip_offset;
+		int bit;
+		for (bit = 8; --bit >= 0; current_octet >>= 1) {
+			if ((crc ^ current_octet) & 1) {
+				crc >>= 1;
+				crc ^= ethernet_polynomial_le;
+			} else
+				crc >>= 1;
+		}
+	}
+	return crc;
+}
+
+/*
+ * Read in a full GPT array into a contiguous chunk, allocates *PP_S bytes into *PP.
+ *
+ * An attempt to do as few round-trips as possible is made by reading a PAGE_SIZE at a time,
+ * since that's the bread() maximum.
+ */
+static int gpt_read_part_table(void **pp, vm_size_t *pp_s, kdev_t dev, int bsize, __u64 first_sector, struct gpt_disklabel_header *h)
+{
+	__u64 lba = first_sector + h->h_part_table_lba;
+	__u32 bytes_left;
+	struct buffer_head *bh;
+	void *cur = *pp = (void *)kalloc(bytes_left = *pp_s = h->h_part_table_len * h->h_part_table_entry_size);
+	if (!cur) {
+		printk(" unable to allocate GPT partition table buffer");
+		return -2;
+	}
+
+	while (bytes_left) {
+		unsigned bytes_to_read = MIN(bytes_left, PAGE_SIZE);
+		if(!(bh = bread(dev, lba, bytes_to_read))) {
+			printk(" unable to read partition table array");
+			return -3;
+		}
+
+		memcpy(cur, bh->b_data, bytes_to_read);
+		cur += bytes_to_read;
+		bytes_left -= bytes_to_read;
+		lba += PAGE_SIZE / bsize;
+
+		brelse(bh);
+	}
+
+	return 0;
+}
+
+/*
+ * Sequence from section 5.3.2 of spec 2.8A:
+ * signature, CRC, lba_current matches, partition table CRC, primary: check backup for validity
+ */
+static int gpt_verify_header(void **pp, vm_size_t *pp_s, kdev_t dev, int bsize, __u64 first_sector, __u64 lba, struct gpt_disklabel_header *h)
+{
+	int res;
+	__u32 crc;
+
+	if (memcmp(h->h_signature, GPT_SIGNATURE, strlen(GPT_SIGNATURE)) != 0) {
+		printk(" bad GPT signature \"%c%c%c%c%c%c%c%c\";",
+			h->h_signature[0], h->h_signature[1], h->h_signature[2], h->h_signature[3],
+			h->h_signature[4], h->h_signature[5], h->h_signature[6], h->h_signature[7]);
+		return 1;
+	}
+
+	crc = ether_crc_le_hole(h->h_header_size, (void *)h,
+		offsetof(struct gpt_disklabel_header, h_header_crc), sizeof(h->h_header_crc)) ^ ~0;
+	if (crc != h->h_header_crc) {
+		printk(" bad header CRC: %x != %x;", crc, h->h_header_crc);
+		return 2;
+	}
+
+	if (h->h_lba_current != lba) {
+		printk(" current LBA mismatch: %lld != %lld;", h->h_lba_current, lba);
+		return 3;
+	}
+
+	if (*pp) {
+		kfree((vm_offset_t)*pp, *pp_s);
+		*pp = NULL;
+	}
+	if ((res = gpt_read_part_table(pp, pp_s, dev, bsize, first_sector, h)))
+		return res;
+
+	crc = ether_crc_le_hole(*pp_s, *pp, 0, 0) ^ ~0;
+	if (crc != h->h_part_table_crc) {
+		printk(" bad partition table CRC: %x != %x;", crc, h->h_part_table_crc);
+		return 4;
+	}
+
+	for (int i = h->h_header_size; i < bsize; ++i)
+		res |= ((char*)h)[i];
+	if (res) {
+		printk(" rest of GPT block dirty;");
+		return 5;
+	}
+
+	return 0;
+}
+
+static void gpt_print_part_name(struct gpt_disklabel_part *p)
+{
+	for(int n = 0; n < sizeof(p->p_name) / sizeof(*p->p_name) && p->p_name[n]; ++n)
+		if(p->p_name[n] & ~0xFF)
+			printk("?");	/* Can't support all of Unicode, but don't print garbage at least... */
+		else
+			printk("%c", p->p_name[n]);
+}
+
+#ifdef DEBUG
+static void gpt_print_guid(struct gpt_guid *guid)
+{
+	printk("%08X-%04X-%04X-%02X%02X-", guid->g_time_low, guid->g_time_mid, guid->g_time_high_version, guid->g_clock_sec_high, guid->g_clock_sec_low);
+	for (int i = 0; i < sizeof(guid->g_node_id); ++i)
+		printk("%02X", guid->g_node_id[i]);
+}
+
+static void gpt_dump_header(struct gpt_disklabel_header *h)
+{
+	printk(" [h_signature: \"%c%c%c%c%c%c%c%c\"; ",
+		h->h_signature[0], h->h_signature[1], h->h_signature[2], h->h_signature[3],
+		h->h_signature[4], h->h_signature[5], h->h_signature[6], h->h_signature[7]);
+	printk("h_revision: %x; ", h->h_revision);
+	printk("h_header_size: %u; ", h->h_header_size);
+	printk("h_header_crc: %x; ", h->h_header_crc);
+	printk("h_reserved: %u; ", h->h_reserved);
+	printk("h_lba_current: %llu; ", h->h_lba_current);
+	printk("h_lba_backup: %llu; ", h->h_lba_backup);
+	printk("h_lba_usable_first: %llu; ", h->h_lba_usable_first);
+	printk("h_lba_usable_last: %llu; ", h->h_lba_usable_last);
+	printk("h_guid: "); gpt_print_guid(&h->h_guid); printk("; ");
+	printk("h_part_table_lba: %llu; ", h->h_part_table_lba);
+	printk("h_part_table_len: %u; ", h->h_part_table_len);
+	printk("h_part_table_crc: %x]", h->h_part_table_crc);
+}
+
+static void gpt_dump_part(struct gpt_disklabel_part *p, int i)
+{
+	printk(" part#%d:[", i);
+	printk("p_type: "); gpt_print_guid(&p->p_type);
+	printk("; p_guid:"); gpt_print_guid(&p->p_guid);
+	printk("; p_lba_first: %llu", p->p_lba_first);
+	printk("; p_lba_last: %llu", p->p_lba_last);
+	printk("; p_attrs: %llx", p->p_attrs);
+	printk("; p_name: \""); gpt_print_part_name(p); printk("\"]");
+}
+#else
+static void gpt_dump_header(struct gpt_disklabel_header *h) {}
+static void gpt_dump_part(struct gpt_disklabel_part *p, int i) {}
+#endif
+
+static int gpt_partition(struct gendisk *hd, kdev_t dev, __u64 first_sector, int minor)
+{
+	struct buffer_head *bh;
+	struct gpt_disklabel_header *h;
+	void *pp = NULL; vm_size_t pp_s = 0;
+	int res, bsize = 512;
+	/* Note: this must be set by the driver; SCSI does --
+	 *       only, in practice, it always sets this to 512, see sd_init() in sd.c */
+	if (hardsect_size[MAJOR(dev)] && hardsect_size[MAJOR(dev)][MINOR(dev)])
+		bsize = hardsect_size[MAJOR(dev)][MINOR(dev)];
+	set_blocksize(dev,bsize);	/* Must override read block size since GPT has pointers, stolen from amiga_partition(). */
+	if (!(bh = bread(dev, first_sector + 1, bsize))) {
+		printk("unable to read GPT");
+		res = -1;
+		goto done;
+	}
+
+	h = (struct gpt_disklabel_header *)bh->b_data;
+	gpt_dump_header(h);
+
+	res = gpt_verify_header(&pp, &pp_s, dev, bsize, first_sector, 1, h);
+	if (res < 0)
+		goto done;
+	else if (res > 0) {
+		printk(" main GPT dirty, trying backup at %llu;", h->h_lba_backup);
+		__u64 lba = h->h_lba_backup;
+		brelse(bh);
+
+		if (!(bh = bread(dev, first_sector + lba, bsize))) {
+			printk("unable to read backup GPT");
+			res = -4;
+			goto done;
+		}
+
+		h = (struct gpt_disklabel_header *)bh->b_data;
+		gpt_dump_header(h);
+
+		res = gpt_verify_header(&pp, &pp_s, dev, bsize, first_sector, lba, h);
+		if (res < 0)
+			goto done;
+		else if (res > 0) {
+			printk(" backup GPT dirty as well; cowardly refusing to continue");
+			res = -5;
+			goto done;
+		}
+	}
+
+	/* At least one good GPT+array */
+
+	for(int i = 0; i < h->h_part_table_len; ++i, ++minor) {
+		struct gpt_disklabel_part *p =
+			(struct gpt_disklabel_part *) (pp + i * h->h_part_table_entry_size);
+		if(memcmp(&p->p_type, &GPT_GUID_TYPE_UNUSED, sizeof(struct gpt_guid)) == 0)
+			continue;
+		gpt_dump_part(p, i);
+
+		if (minor > hd->max_nr * hd->max_p) {
+			printk(" [ignoring GPT partition %d \"", i); gpt_print_part_name(p); printk("\": too many partitions (max %d)]", hd->max_p);
+		} else {
+			add_partition(hd, minor, first_sector + p->p_lba_first, p->p_lba_last - p->p_lba_first + 1);
+			if(p->p_name[0]) {
+				printk(" ("); gpt_print_part_name(p); printk(")");
+			}
+		}
+	}
+
+done:
+	brelse(bh);
+	set_blocksize(dev,BLOCK_SIZE);
+	kfree((vm_offset_t)pp, pp_s);
+	printk("\n");
+	return !res;
+}
+#endif
+
 static int msdos_partition(struct gendisk *hd, kdev_t dev, unsigned long first_sector)
 {
 	int i, minor = current_minor;
@@ -381,6 +626,12 @@ check_table:
 	for (i=1 ; i<=4 ; minor++,i++,p++) {
 		if (!NR_SECTS(p))
 			continue;
+#ifdef CONFIG_GPT_DISKLABEL
+		if (SYS_IND(p) == GPT_PARTITION) {
+			brelse(bh);
+			return gpt_partition(hd, dev, first_sector, minor);
+		} else
+#endif
 		add_partition(hd, minor, first_sector+START_SECT(p), NR_SECTS(p));
 		if (is_extended_partition(p)) {
 			printk(" <");
diff --git a/linux/dev/include/linux/genhd.h b/linux/dev/include/linux/genhd.h
index 20a6c97..f19015d 100644
--- a/linux/dev/include/linux/genhd.h
+++ b/linux/dev/include/linux/genhd.h
@@ -128,6 +128,73 @@ struct bsd_disklabel {
 
 #endif	/* CONFIG_BSD_DISKLABEL */
 
+#ifdef CONFIG_GPT_DISKLABEL
+/*
+ * GPT disklabel support by наб <nabijaczleweli@gmail.com>
+ *
+ * Based on UEFI specification 2.8A (current as of May 2020):
+ * https://uefi.org/specifications
+ * https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_A_Feb14.pdf
+ *
+ * CRC32 behaviour (final ^ ~0) courtesy of util-linux documentation:
+ * https://git.kernel.org/pub/scm/utils/util-linux/util-linux.git/tree/libblkid/src/partitions/gpt.c?id=042f62dfc514da177c148c257e4dcb32e5f8379d#n104
+ */
+
+#define GPT_PARTITION		0xee	/* Partition ID in MBR */
+
+#define GPT_GUID_SIZE	16
+struct gpt_guid {
+	__u32	g_time_low;		/* Low field of timestamp */
+	__u16	g_time_mid;		/* Medium field of timestamp */
+	__u16	g_time_high_version;		/* High field of timestamp and version */
+	__u8	g_clock_sec_high;		/* High field of clock sequence and variant */
+	__u8	g_clock_sec_low;		/* Low field of clock sequence */
+	__u8	g_node_id[6];		/* Spatially unique node identifier (MAC address or urandom) */
+} __attribute((packed));
+typedef char __gpt_guid_right_size[(sizeof(struct gpt_guid) == GPT_GUID_SIZE) ? 1 : -1];
+
+static const struct gpt_guid GPT_GUID_TYPE_UNUSED = {0,0,0,0,0,{0,0,0,0,0,0}};
+
+#define GPT_SIGNATURE	"EFI PART"		/* The header signauture */
+#define GPT_REVISION	(0x00010000UL)	/* Little-endian on disk */
+#define GPT_HEADER_SIZE	92
+#define GPT_MAXPARTITIONS	128
+struct gpt_disklabel_header {
+	char	h_signature[8];		/* Must match GPT_SIGNATURE */
+	__u32	h_revision;			/* Disklabel revision, must match GPT_REVISION */
+	__u32	h_header_size;		/* Must match GPT_HEADER_SIZE */
+	__u32	h_header_crc;		/* CRC32 of header, zero for calculation */
+	__u32	h_reserved;		/* Must be zero */
+	__u64	h_lba_current;		/* LBA of this copy of the header */
+	__u64	h_lba_backup;		/* LBA of the second (backup) copy of the header */
+	__u64	h_lba_usable_first;		/* First usable LBA for partitions (last LBA of primary table + 1) */
+	__u64	h_lba_usable_last;		/* Last usable LBA for partitions (first LBA of secondary table - 1) */
+	struct gpt_guid	h_guid;		/* ID of the disk */
+	__u64	h_part_table_lba;		/* First LBA of the partition table (usually 2 in primary header) */
+	__u32	h_part_table_len;		/* Amount of entries in the partition table */
+	__u32	h_part_table_entry_size;		/* Size of each partition entry (usually 128) */
+	__u32	h_part_table_crc;		/* CRC32 of entire partition table, starts at h_part_table_lba, is h_part_table_len*h_part_table_entry_size long */
+						/* Rest of block must be zero */
+} __attribute((packed));
+typedef char __gpt_header_right_size[(sizeof(struct gpt_disklabel_header) == GPT_HEADER_SIZE) ? 1 : -1];
+
+/* 3-47: reserved; 48-63: defined for individual partition types. */
+#define GPT_PARTITION_ATTR_PLATFORM_REQUIRED	(1ULL << 0)		/* Required by the platform to function */
+#define GPT_PARTITION_ATTR_EFI_IGNORE	(1ULL << 1)		/* To be ignored by the EFI firmware */
+#define GPT_PARTITION_ATTR_BIOS_BOOTABLE	(1ULL << 2)		/* Equivalent to MBR active flag */
+
+#define GPT_PARTITION_ENTRY_SIZE	128		/* Minimum size, implementations must respect bigger vendor-specific entries */
+struct gpt_disklabel_part {
+	struct gpt_guid	p_type;		/* Partition type GUID */
+	struct gpt_guid	p_guid;		/* ID of the partition */
+	__u64	p_lba_first;		/* First LBA of the partition */
+	__u64	p_lba_last;		/* Last LBA of the partition */
+	__u64	p_attrs;		/* Partition attribute bitfield, see above */
+	__u16	p_name[36];		/* Display name of partition, UTF-16 */
+} __attribute((packed));
+typedef char __gpt_part_entry_right_size[(sizeof(struct gpt_disklabel_part) == GPT_PARTITION_ENTRY_SIZE) ? 1 : -1];
+#endif	/* CONFIG_GPT_DISKLABEL */
+
 extern struct gendisk *gendisk_head;	/* linked list of disks */
 
 /*
-- 
2.27.0.rc0

Attachment: signature.asc
Description: PGP signature


Reply to: