From bfe882d5ff4eeebb8f57c8a0f9b9e767a57870d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= Date: Thu, 23 Feb 2023 19:26:14 +0100 Subject: libblkid-tiny: add exfat superblock support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- 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 --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 + * + * 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; -- cgit v1.2.1