diff mbox series

[fstools] libblkid-tiny: add exfat superblock support

Message ID 20230223182614.7452-1-zajec5@gmail.com
State Accepted
Delegated to: Rafał Miłecki
Headers show
Series [fstools] libblkid-tiny: add exfat superblock support | expand

Commit Message

Rafał Miłecki Feb. 23, 2023, 6:26 p.m. UTC
From: Rafał Miłecki <rafal@milecki.pl>

exFAT became very popular over last years and also well supported with
Linux kernel driver and macOS support. It's commonly used for USB
drivers. Port a simple support for it from util-linux / libblkid.

Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
---
 CMakeLists.txt                |   1 +
 libblkid-tiny/exfat.c         | 175 ++++++++++++++++++++++++++++++++++
 libblkid-tiny/libblkid-tiny.c |   1 +
 libblkid-tiny/superblocks.h   |   1 +
 4 files changed, 178 insertions(+)
 create mode 100644 libblkid-tiny/exfat.c
diff mbox series

Patch

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9a87979..3421fec 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -25,6 +25,7 @@  ADD_LIBRARY(blkid-tiny SHARED
 		libblkid-tiny/libblkid-tiny.c
 		libblkid-tiny/mkdev.c
 		libblkid-tiny/probe.c
+		libblkid-tiny/exfat.c
 		libblkid-tiny/ext.c
 		libblkid-tiny/jffs2.c
 		libblkid-tiny/vfat.c
diff --git a/libblkid-tiny/exfat.c b/libblkid-tiny/exfat.c
new file mode 100644
index 0000000..85d6f82
--- /dev/null
+++ b/libblkid-tiny/exfat.c
@@ -0,0 +1,175 @@ 
+/*
+ * Copyright (C) 2010 Andrew Nayenko <resver@gmail.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include "superblocks.h"
+
+struct exfat_super_block {
+	uint8_t JumpBoot[3];
+	uint8_t FileSystemName[8];
+	uint8_t MustBeZero[53];
+	uint64_t PartitionOffset;
+	uint64_t VolumeLength;
+	uint32_t FatOffset;
+	uint32_t FatLength;
+	uint32_t ClusterHeapOffset;
+	uint32_t ClusterCount;
+	uint32_t FirstClusterOfRootDirectory;
+	uint8_t VolumeSerialNumber[4];
+	struct {
+		uint8_t vermin;
+		uint8_t vermaj;
+	} FileSystemRevision;
+	uint16_t VolumeFlags;
+	uint8_t BytesPerSectorShift;
+	uint8_t SectorsPerClusterShift;
+	uint8_t NumberOfFats;
+	uint8_t DriveSelect;
+	uint8_t PercentInUse;
+	uint8_t Reserved[7];
+	uint8_t BootCode[390];
+	uint16_t BootSignature;
+} __attribute__((__packed__));
+
+struct exfat_entry_label {
+	uint8_t type;
+	uint8_t length;
+	uint8_t name[22];
+	uint8_t reserved[8];
+} __attribute__((__packed__));
+
+#define BLOCK_SIZE(sb) ((sb)->BytesPerSectorShift < 32 ? (1u << (sb)->BytesPerSectorShift) : 0)
+#define CLUSTER_SIZE(sb) ((sb)->SectorsPerClusterShift < 32 ? (BLOCK_SIZE(sb) << (sb)->SectorsPerClusterShift) : 0)
+#define EXFAT_FIRST_DATA_CLUSTER 2
+#define EXFAT_LAST_DATA_CLUSTER 0xffffff6
+#define EXFAT_ENTRY_SIZE 32
+
+#define EXFAT_ENTRY_EOD		0x00
+#define EXFAT_ENTRY_LABEL	0x83
+
+static uint64_t block_to_offset(const struct exfat_super_block *sb,
+		uint64_t block)
+{
+	return block << sb->BytesPerSectorShift;
+}
+
+static uint64_t cluster_to_block(const struct exfat_super_block *sb,
+		uint32_t cluster)
+{
+	return le32_to_cpu(sb->ClusterHeapOffset) +
+			((uint64_t) (cluster - EXFAT_FIRST_DATA_CLUSTER)
+					<< sb->SectorsPerClusterShift);
+}
+
+static uint64_t cluster_to_offset(const struct exfat_super_block *sb,
+		uint32_t cluster)
+{
+	return block_to_offset(sb, cluster_to_block(sb, cluster));
+}
+
+static uint32_t next_cluster(blkid_probe pr,
+		const struct exfat_super_block *sb, uint32_t cluster)
+{
+	uint32_t *nextp, next;
+	uint64_t fat_offset;
+
+	fat_offset = block_to_offset(sb, le32_to_cpu(sb->FatOffset))
+		+ (uint64_t) cluster * sizeof(cluster);
+	nextp = (uint32_t *) blkid_probe_get_buffer(pr, fat_offset,
+			sizeof(uint32_t));
+	if (!nextp)
+		return 0;
+	memcpy(&next, nextp, sizeof(next));
+	return le32_to_cpu(next);
+}
+
+static struct exfat_entry_label *find_label(blkid_probe pr,
+		const struct exfat_super_block *sb)
+{
+	uint32_t cluster = le32_to_cpu(sb->FirstClusterOfRootDirectory);
+	uint64_t offset = cluster_to_offset(sb, cluster);
+	uint8_t *entry;
+	const size_t max_iter = 10000;
+	size_t i = 0;
+
+	for (; i < max_iter; i++) {
+		entry = (uint8_t *) blkid_probe_get_buffer(pr, offset,
+				EXFAT_ENTRY_SIZE);
+		if (!entry)
+			return NULL;
+		if (entry[0] == EXFAT_ENTRY_EOD)
+			return NULL;
+		if (entry[0] == EXFAT_ENTRY_LABEL)
+			return (struct exfat_entry_label *) entry;
+
+		offset += EXFAT_ENTRY_SIZE;
+		if (CLUSTER_SIZE(sb) && (offset % CLUSTER_SIZE(sb)) == 0) {
+			cluster = next_cluster(pr, sb, cluster);
+			if (cluster < EXFAT_FIRST_DATA_CLUSTER)
+				return NULL;
+			if (cluster > EXFAT_LAST_DATA_CLUSTER)
+				return NULL;
+			offset = cluster_to_offset(sb, cluster);
+		}
+	}
+
+	return NULL;
+}
+
+static int probe_exfat(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct exfat_super_block *sb;
+	struct exfat_entry_label *label;
+
+	sb = blkid_probe_get_sb(pr, mag, struct exfat_super_block);
+	if (!sb || !CLUSTER_SIZE(sb))
+		return errno ? -errno : BLKID_PROBE_NONE;
+
+	if (le16_to_cpu(sb->BootSignature) != 0xAA55)
+		return BLKID_PROBE_NONE;
+
+	if (memcmp(sb->JumpBoot, "\xEB\x76\x90", 3) != 0)
+		return BLKID_PROBE_NONE;
+
+	for (size_t i = 0; i < sizeof(sb->MustBeZero); i++)
+		if (sb->MustBeZero[i] != 0x00)
+			return BLKID_PROBE_NONE;
+
+	label = find_label(pr, sb);
+	if (label)
+		blkid_probe_set_utf8label(pr, label->name,
+				min((size_t) label->length * 2, sizeof(label->name)),
+				BLKID_ENC_UTF16LE);
+	else if (errno)
+		return -errno;
+
+	blkid_probe_sprintf_uuid(pr, sb->VolumeSerialNumber, 4,
+			"%02hhX%02hhX-%02hhX%02hhX",
+			sb->VolumeSerialNumber[3], sb->VolumeSerialNumber[2],
+			sb->VolumeSerialNumber[1], sb->VolumeSerialNumber[0]);
+
+	blkid_probe_sprintf_version(pr, "%u.%u",
+			sb->FileSystemRevision.vermaj, sb->FileSystemRevision.vermin);
+
+#if 0
+	blkid_probe_set_fsblocksize(pr, BLOCK_SIZE(sb));
+	blkid_probe_set_block_size(pr, BLOCK_SIZE(sb));
+	blkid_probe_set_fssize(pr, BLOCK_SIZE(sb) * le64_to_cpu(sb->VolumeLength));
+#endif
+
+	return BLKID_PROBE_OK;
+}
+
+const struct blkid_idinfo exfat_idinfo =
+{
+	.name		= "exfat",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_exfat,
+	.magics		=
+	{
+		{ .magic = "EXFAT   ", .len = 8, .sboff = 3 },
+		{ NULL }
+	}
+};
diff --git a/libblkid-tiny/libblkid-tiny.c b/libblkid-tiny/libblkid-tiny.c
index 8520f8a..9e67439 100644
--- a/libblkid-tiny/libblkid-tiny.c
+++ b/libblkid-tiny/libblkid-tiny.c
@@ -174,6 +174,7 @@  static const struct blkid_idinfo *idinfos[] =
 	&vfat_idinfo,
 	&swsuspend_idinfo,
 	&swap_idinfo,
+	&exfat_idinfo,
 	&ext4dev_idinfo,
 	&ext4_idinfo,
 	&ext3_idinfo,
diff --git a/libblkid-tiny/superblocks.h b/libblkid-tiny/superblocks.h
index ade2ae0..66053e6 100644
--- a/libblkid-tiny/superblocks.h
+++ b/libblkid-tiny/superblocks.h
@@ -22,6 +22,7 @@  extern const struct blkid_idinfo pdcraid_idinfo;
 extern const struct blkid_idinfo silraid_idinfo;
 extern const struct blkid_idinfo viaraid_idinfo;
 extern const struct blkid_idinfo linuxraid_idinfo;
+extern const struct blkid_idinfo exfat_idinfo;
 extern const struct blkid_idinfo ext4dev_idinfo;
 extern const struct blkid_idinfo ext4_idinfo;
 extern const struct blkid_idinfo ext3_idinfo;