diff mbox series

[RFC,3/6] block: partitions: efi: Separate out GPT-specific code

Message ID 20231211151244.289349-4-romain.gantois@bootlin.com
State New
Headers show
Series Add GPT parser to MTD layer | expand

Commit Message

Romain Gantois Dec. 11, 2023, 3:12 p.m. UTC
The current GPT parser implemented in the block layer mixes
blockdev-specific code with GPT concepts. Separate out device-agnostic GPT
functions from the rest of the parser, in preparation for the creation of a
new generic purpose GPT parser library.

This mostly implies renaming functions and changing argument types. The
only significant change is the new gpt_validate_header function, which has
been separated out from the is_gpt_valid function.

Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
---
 block/partitions/efi.c | 199 ++++++++++++++++++++++-------------------
 include/linux/gpt.h    |  37 +++++++-
 2 files changed, 143 insertions(+), 93 deletions(-)
diff mbox series

Patch

diff --git a/block/partitions/efi.c b/block/partitions/efi.c
index bac514a62d61..3630ebf4b997 100644
--- a/block/partitions/efi.c
+++ b/block/partitions/efi.c
@@ -151,7 +151,7 @@  static inline int pmbr_part_valid(gpt_mbr_record *part)
 }
 
 /**
- * is_pmbr_valid(): test Protective MBR for validity
+ * gpt_is_pmbr_valid(): test Protective MBR for validity
  * @mbr: pointer to a legacy mbr structure
  * @total_sectors: amount of sectors in the device
  *
@@ -168,7 +168,7 @@  static inline int pmbr_part_valid(gpt_mbr_record *part)
  * Returns 0 upon invalid MBR, or GPT_MBR_PROTECTIVE or
  * GPT_MBR_HYBRID depending on the device layout.
  */
-static int is_pmbr_valid(legacy_mbr *mbr, sector_t total_sectors)
+int gpt_is_pmbr_valid(legacy_mbr *mbr, sector_t total_sectors)
 {
 	uint32_t sz = 0;
 	int i, part = 0, ret = 0; /* invalid by default */
@@ -324,166 +324,183 @@  static gpt_header *alloc_read_gpt_header(struct parsed_partitions *state,
 }
 
 /**
- * is_gpt_valid() - tests one GPT header and PTEs for validity
- * @state: disk parsed partitions
- * @lba: logical block address of the GPT header to test
- * @gpt: GPT header ptr, filled on return.
- * @ptes: PTEs ptr, filled on return.
+ * gpt_validate_header() - tests one GPT header for validity
+ * @gpt:      header to check
+ * @lba:      logical block address of the GPT header to test
+ * @lba_size: logical block size of the partitioned device
+ * @lastlba:  last logical block on the partitioned device
  *
- * Description: returns 1 if valid,  0 on error.
- * If valid, returns pointers to newly allocated GPT header and PTEs.
+ * Returns 0 if validation was successful.
  */
-static int is_gpt_valid(struct parsed_partitions *state, u64 lba,
-			gpt_header **gpt, gpt_entry **ptes)
+int gpt_validate_header(gpt_header *gpt, u64 lba, unsigned int lba_size,
+			u64 lastlba)
 {
 	u32 crc, origcrc;
-	u64 lastlba, pt_size;
-
-	if (!ptes)
-		return 0;
-	if (!(*gpt = alloc_read_gpt_header(state, lba)))
-		return 0;
+	u64 pt_size;
 
 	/* Check the GUID Partition Table signature */
-	if (le64_to_cpu((*gpt)->signature) != GPT_HEADER_SIGNATURE) {
-		pr_debug("GUID Partition Table Header signature is wrong:"
-			 "%lld != %lld\n",
-			 (unsigned long long)le64_to_cpu((*gpt)->signature),
+	if (le64_to_cpu(gpt->signature) != GPT_HEADER_SIGNATURE) {
+		pr_debug("GUID Partition Table Header signature is wrong: %lld != %lld\n",
+			 (unsigned long long)le64_to_cpu(gpt->signature),
 			 (unsigned long long)GPT_HEADER_SIGNATURE);
-		goto fail;
+		return -EINVAL;
 	}
 
 	/* Check the GUID Partition Table header size is too big */
-	if (le32_to_cpu((*gpt)->header_size) >
-			queue_logical_block_size(state->disk->queue)) {
+	if (le32_to_cpu(gpt->header_size) > lba_size) {
 		pr_debug("GUID Partition Table Header size is too large: %u > %u\n",
-			le32_to_cpu((*gpt)->header_size),
-			queue_logical_block_size(state->disk->queue));
-		goto fail;
+			 le32_to_cpu(gpt->header_size), lba_size);
+		return -EINVAL;
 	}
 
 	/* Check the GUID Partition Table header size is too small */
-	if (le32_to_cpu((*gpt)->header_size) < sizeof(gpt_header)) {
+	if (le32_to_cpu(gpt->header_size) < sizeof(gpt_header)) {
 		pr_debug("GUID Partition Table Header size is too small: %u < %zu\n",
-			le32_to_cpu((*gpt)->header_size),
-			sizeof(gpt_header));
-		goto fail;
+			 le32_to_cpu(gpt->header_size),
+			 sizeof(gpt_header));
+		return -EINVAL;
 	}
 
 	/* Check the GUID Partition Table CRC */
-	origcrc = le32_to_cpu((*gpt)->header_crc32);
-	(*gpt)->header_crc32 = 0;
-	crc = efi_crc32((const unsigned char *) (*gpt), le32_to_cpu((*gpt)->header_size));
+	origcrc = le32_to_cpu(gpt->header_crc32);
+	gpt->header_crc32 = 0;
+	crc = efi_crc32((const unsigned char *)gpt, le32_to_cpu(gpt->header_size));
 
 	if (crc != origcrc) {
 		pr_debug("GUID Partition Table Header CRC is wrong: %x != %x\n",
 			 crc, origcrc);
-		goto fail;
+		return -EINVAL;
 	}
-	(*gpt)->header_crc32 = cpu_to_le32(origcrc);
+	gpt->header_crc32 = cpu_to_le32(origcrc);
 
 	/* Check that the my_lba entry points to the LBA that contains
-	 * the GUID Partition Table */
-	if (le64_to_cpu((*gpt)->my_lba) != lba) {
+	 * the GUID Partition Table
+	 */
+	if (le64_to_cpu(gpt->my_lba) != lba) {
 		pr_debug("GPT my_lba incorrect: %lld != %lld\n",
-			 (unsigned long long)le64_to_cpu((*gpt)->my_lba),
+			 (unsigned long long)le64_to_cpu(gpt->my_lba),
 			 (unsigned long long)lba);
-		goto fail;
+		return -EINVAL;
 	}
 
 	/* Check the first_usable_lba and last_usable_lba are
 	 * within the disk.
 	 */
-	lastlba = last_lba(state->disk);
-	if (le64_to_cpu((*gpt)->first_usable_lba) > lastlba) {
+	if (le64_to_cpu(gpt->first_usable_lba) > lastlba) {
 		pr_debug("GPT: first_usable_lba incorrect: %lld > %lld\n",
-			 (unsigned long long)le64_to_cpu((*gpt)->first_usable_lba),
+			 (unsigned long long)le64_to_cpu(gpt->first_usable_lba),
 			 (unsigned long long)lastlba);
-		goto fail;
+		return -EINVAL;
 	}
-	if (le64_to_cpu((*gpt)->last_usable_lba) > lastlba) {
+	if (le64_to_cpu(gpt->last_usable_lba) > lastlba) {
 		pr_debug("GPT: last_usable_lba incorrect: %lld > %lld\n",
-			 (unsigned long long)le64_to_cpu((*gpt)->last_usable_lba),
+			 (unsigned long long)le64_to_cpu(gpt->last_usable_lba),
 			 (unsigned long long)lastlba);
-		goto fail;
+		return -EINVAL;
 	}
-	if (le64_to_cpu((*gpt)->last_usable_lba) < le64_to_cpu((*gpt)->first_usable_lba)) {
+	if (le64_to_cpu(gpt->last_usable_lba) < le64_to_cpu(gpt->first_usable_lba)) {
 		pr_debug("GPT: last_usable_lba incorrect: %lld > %lld\n",
-			 (unsigned long long)le64_to_cpu((*gpt)->last_usable_lba),
-			 (unsigned long long)le64_to_cpu((*gpt)->first_usable_lba));
-		goto fail;
+			 (unsigned long long)le64_to_cpu(gpt->last_usable_lba),
+			 (unsigned long long)le64_to_cpu(gpt->first_usable_lba));
+		return -EINVAL;
 	}
+
 	/* Check that sizeof_partition_entry has the correct value */
-	if (le32_to_cpu((*gpt)->sizeof_partition_entry) != sizeof(gpt_entry)) {
+	if (le32_to_cpu(gpt->sizeof_partition_entry) != sizeof(gpt_entry)) {
 		pr_debug("GUID Partition Entry Size check failed.\n");
-		goto fail;
+		return -EINVAL;
 	}
 
 	/* Sanity check partition table size */
-	pt_size = (u64)le32_to_cpu((*gpt)->num_partition_entries) *
-		le32_to_cpu((*gpt)->sizeof_partition_entry);
+	pt_size = (u64)get_pt_size(gpt);
 	if (pt_size > KMALLOC_MAX_SIZE) {
 		pr_debug("GUID Partition Table is too large: %llu > %lu bytes\n",
 			 (unsigned long long)pt_size, KMALLOC_MAX_SIZE);
-		goto fail;
+		return -EINVAL;
 	}
 
-	if (!(*ptes = alloc_read_gpt_entries(state, *gpt)))
-		goto fail;
+	return 0;
+}
 
-	/* Check the GUID Partition Entry Array CRC */
-	crc = efi_crc32((const unsigned char *) (*ptes), pt_size);
+/* Check the GUID Partition Entry Array CRC */
+int gpt_check_pte_array_crc(gpt_header *gpt, gpt_entry *ptes)
+{
+	u32 crc;
 
-	if (crc != le32_to_cpu((*gpt)->partition_entry_array_crc32)) {
+	crc = efi_crc32((const unsigned char *)ptes, get_pt_size(gpt));
+	if (crc != le32_to_cpu(gpt->partition_entry_array_crc32)) {
 		pr_debug("GUID Partition Entry Array CRC check failed.\n");
-		goto fail_ptes;
+		return -EINVAL;
 	}
 
-	/* We're done, all's well */
-	return 1;
-
- fail_ptes:
-	kfree(*ptes);
-	*ptes = NULL;
- fail:
-	kfree(*gpt);
-	*gpt = NULL;
 	return 0;
 }
 
 /**
- * is_pte_valid() - tests one PTE for validity
- * @pte:pte to check
- * @lastlba: last lba of the disk
+ * is_gpt_valid() - tests one GPT header and PTEs for validity
+ * @state: disk parsed partitions
+ * @lba: logical block address of the GPT header to test
+ * @gpt: GPT header ptr, filled on return.
+ * @ptes: PTEs ptr, filled on return.
  *
  * Description: returns 1 if valid,  0 on error.
+ * If valid, returns pointers to newly allocated GPT header and PTEs.
  */
-static inline int
-is_pte_valid(const gpt_entry *pte, const u64 lastlba)
+static int is_gpt_valid(struct parsed_partitions *state, u64 lba,
+			gpt_header **gpt, gpt_entry **ptes)
 {
-	if ((!efi_guidcmp(pte->partition_type_guid, NULL_GUID)) ||
-	    le64_to_cpu(pte->starting_lba) > lastlba         ||
-	    le64_to_cpu(pte->ending_lba)   > lastlba)
+	u64 lastlba;
+
+	if (!ptes)
 		return 0;
+
+	*gpt = alloc_read_gpt_header(state, lba);
+	if (!(*gpt))
+		return 0;
+
+	lastlba = last_lba(state->disk);
+	if (gpt_validate_header(*gpt, lba,
+				queue_logical_block_size(state->disk->queue),
+				lastlba))
+		goto fail;
+
+	*ptes = alloc_read_gpt_entries(state, *gpt);
+	if (!(*ptes))
+		goto fail;
+
+	if (gpt_check_pte_array_crc(*gpt, *ptes))
+		goto fail_ptes;
+
+	/* We're done, all's well */
 	return 1;
+
+fail_ptes:
+	kfree(*ptes);
+	*ptes = NULL;
+fail:
+	kfree(*gpt);
+	*gpt = NULL;
+	return 0;
 }
 
 /**
- * compare_gpts() - Search disk for valid GPT headers and PTEs
+ * gpt_compare_alt() - Compares the Primary and Alternate GPT headers
  * @pgpt: primary GPT header
  * @agpt: alternate GPT header
  * @lastlba: last LBA number
  *
- * Description: Returns nothing.  Sanity checks pgpt and agpt fields
- * and prints warnings on discrepancies.
- * 
+ * Description: Sanity checks pgpt and agpt fields and prints warnings
+ * on discrepancies. Returns error count. GPT parsers can choose to
+ * ignore this or not.
+ *
  */
-static void
-compare_gpts(gpt_header *pgpt, gpt_header *agpt, u64 lastlba)
+int gpt_compare_alt(gpt_header *pgpt, gpt_header *agpt, u64 lastlba)
 {
 	int error_found = 0;
+
 	if (!pgpt || !agpt)
-		return;
+		return -EINVAL;
+
 	if (le64_to_cpu(pgpt->my_lba) != le64_to_cpu(agpt->alternate_lba)) {
 		pr_warn("GPT:Primary header LBA != Alt. header alternate_lba\n");
 		pr_warn("GPT:%lld != %lld\n",
@@ -557,7 +574,7 @@  compare_gpts(gpt_header *pgpt, gpt_header *agpt, u64 lastlba)
 
 	if (error_found)
 		pr_warn("GPT: Use GNU Parted to correct GPT errors.\n");
-	return;
+	return error_found;
 }
 
 /**
@@ -601,7 +618,7 @@  static int find_valid_gpt(struct parsed_partitions *state, gpt_header **gpt,
 			goto fail;
 
 		read_lba(state, 0, (u8 *)legacymbr, sizeof(*legacymbr));
-		good_pmbr = is_pmbr_valid(legacymbr, total_sectors);
+		good_pmbr = gpt_is_pmbr_valid(legacymbr, total_sectors);
 		kfree(legacymbr);
 
 		if (!good_pmbr)
@@ -635,7 +652,7 @@  static int find_valid_gpt(struct parsed_partitions *state, gpt_header **gpt,
 	if (!good_pgpt && !good_agpt)
 		goto fail;
 
-        compare_gpts(pgpt, agpt, lastlba);
+	gpt_compare_alt(pgpt, agpt, lastlba);
 
 	/* The good cases */
 	if (good_pgpt) {
@@ -674,7 +691,7 @@  static int find_valid_gpt(struct parsed_partitions *state, gpt_header **gpt,
  * Description: Converts @size UTF16-LE symbols from @in string to 7-bit
  * ASCII characters and stores them to @out. Adds trailing zero to @out array.
  */
-static void utf16_le_to_7bit(const __le16 *in, unsigned int size, u8 *out)
+void utf16_le_to_7bit(const __le16 *in, unsigned int size, u8 *out)
 {
 	unsigned int i = 0;
 
@@ -731,7 +748,7 @@  int efi_partition(struct parsed_partitions *state)
 		u64 size = le64_to_cpu(ptes[i].ending_lba) -
 			le64_to_cpu(ptes[i].starting_lba) + 1ULL;
 
-		if (!is_pte_valid(&ptes[i], last_lba(state->disk)))
+		if (!gpt_is_pte_valid(&ptes[i], last_lba(state->disk)))
 			continue;
 
 		put_partition(state, i + 1, start * ssz, size * ssz);
diff --git a/include/linux/gpt.h b/include/linux/gpt.h
index 633be6bc826c..f7f5892fe256 100644
--- a/include/linux/gpt.h
+++ b/include/linux/gpt.h
@@ -8,8 +8,8 @@ 
  *   Copyright 2000,2001 Dell Inc.
  ************************************************************/
 
-#ifndef FS_PART_EFI_H_INCLUDED
-#define FS_PART_EFI_H_INCLUDED
+#ifndef _GPT_H
+#define _GPT_H
 
 #include <linux/types.h>
 #include <linux/fs.h>
@@ -111,4 +111,37 @@  typedef struct _legacy_mbr {
 	__le16 signature;
 } __packed legacy_mbr;
 
+// Helpers for validating GPT metadata
+int gpt_is_pmbr_valid(legacy_mbr *mbr, sector_t total_sectors);
+int gpt_validate_header(gpt_header *gpt, u64 lba, unsigned int lba_size,
+			u64 lastlba);
+int gpt_check_pte_array_crc(gpt_header *gpt, gpt_entry *ptes);
+int gpt_compare_alt(gpt_header *pgpt, gpt_header *agpt, u64 lastlba);
+
+/**
+ * is_pte_valid() - tests one PTE for validity
+ * @pte:pte to check
+ * @lastlba: last lba of the disk
+ *
+ * returns 1 if valid,  0 on error.
+ */
+	static inline bool
+gpt_is_pte_valid(const gpt_entry *pte, const u64 lastlba)
+{
+	if ((!efi_guidcmp(pte->partition_type_guid, NULL_GUID)) ||
+	    le64_to_cpu(pte->starting_lba) > lastlba         ||
+	    le64_to_cpu(pte->ending_lba)   > lastlba)
+		return 0;
+	return 1;
+}
+
+// Returns size in bytes of PTE array
+static inline int get_pt_size(gpt_header *gpt)
+{
+	return le32_to_cpu(gpt->num_partition_entries)
+		* le32_to_cpu(gpt->sizeof_partition_entry);
+}
+
+void utf16_le_to_7bit(const __le16 *in, unsigned int size, u8 *out);
+
 #endif