diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 19:10:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 19:10:49 +0000 |
commit | cfe5e3905201349e9cf3f95d52ff4bd100bde37d (patch) | |
tree | d0baf160cbee3195249d095f85e52d20c21acf02 /libblkid/src/superblocks/exfat.c | |
parent | Initial commit. (diff) | |
download | util-linux-cfe5e3905201349e9cf3f95d52ff4bd100bde37d.tar.xz util-linux-cfe5e3905201349e9cf3f95d52ff4bd100bde37d.zip |
Adding upstream version 2.39.3.upstream/2.39.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'libblkid/src/superblocks/exfat.c')
-rw-r--r-- | libblkid/src/superblocks/exfat.c | 259 |
1 files changed, 259 insertions, 0 deletions
diff --git a/libblkid/src/superblocks/exfat.c b/libblkid/src/superblocks/exfat.c new file mode 100644 index 0000000..fda1ecd --- /dev/null +++ b/libblkid/src/superblocks/exfat.c @@ -0,0 +1,259 @@ +/* + * 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 + +#define EXFAT_MAX_DIR_SIZE (256 * 1024 * 1024) + +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 = EXFAT_MAX_DIR_SIZE / EXFAT_ENTRY_SIZE; + 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; +} + +/* From https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification#34-main-and-backup-boot-checksum-sub-regions */ +static uint32_t exfat_boot_checksum(unsigned char *sectors, + size_t sector_size) +{ + uint32_t n_bytes = sector_size * 11; + uint32_t checksum = 0; + + for (size_t i = 0; i < n_bytes; i++) { + if ((i == 106) || (i == 107) || (i == 112)) + continue; + + checksum = ((checksum & 1) ? 0x80000000 : 0) + (checksum >> 1) + + (uint32_t) sectors[i]; + } + + return checksum; +} + +static int exfat_validate_checksum(blkid_probe pr, + const struct exfat_super_block *sb) +{ + size_t sector_size = BLOCK_SIZE(sb); + /* 11 sectors will be checksummed, the 12th contains the expected */ + unsigned char *data = blkid_probe_get_buffer(pr, 0, sector_size * 12); + if (!data) + return 0; + + uint32_t checksum = exfat_boot_checksum(data, sector_size); + + /* The expected checksum is repeated, check all of them */ + for (size_t i = 0; i < sector_size / sizeof(uint32_t); i++) { + size_t offset = sector_size * 11 + i * 4; + uint32_t *expected_addr = (uint32_t *) &data[offset]; + uint32_t expected = le32_to_cpu(*expected_addr); + if (!blkid_probe_verify_csum(pr, checksum, expected)) + return 0; + }; + + return 1; +} + +static int exfat_valid_superblock(blkid_probe pr, const struct exfat_super_block *sb) +{ + if (le16_to_cpu(sb->BootSignature) != 0xAA55) + return 0; + + if (!CLUSTER_SIZE(sb)) + return 0; + + if (memcmp(sb->JumpBoot, "\xEB\x76\x90", 3) != 0) + return 0; + + for (size_t i = 0; i < sizeof(sb->MustBeZero); i++) + if (sb->MustBeZero[i] != 0x00) + return 0; + + if (!exfat_validate_checksum(pr, sb)) + return 0; + + return 1; +} + +/* function prototype to avoid warnings (duplicate in partitions/dos.c) */ +extern int blkid_probe_is_exfat(blkid_probe pr); + +/* + * This function is used by MBR partition table parser to avoid + * misinterpretation of exFAT filesystem. + */ +int blkid_probe_is_exfat(blkid_probe pr) +{ + struct exfat_super_block *sb; + const struct blkid_idmag *mag = NULL; + int rc; + + rc = blkid_probe_get_idmag(pr, &vfat_idinfo, NULL, &mag); + if (rc < 0) + return rc; /* error */ + if (rc != BLKID_PROBE_OK || !mag) + return 0; + + sb = blkid_probe_get_sb(pr, mag, struct exfat_super_block); + if (!sb) + return 0; + + if (memcmp(sb->FileSystemName, "EXFAT ", 8) != 0) + return 0; + + return exfat_valid_superblock(pr, sb); +} + +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) + return errno ? -errno : BLKID_PROBE_NONE; + + if (!exfat_valid_superblock(pr, sb)) + 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)), + UL_ENCODE_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); + + 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)); + + 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 } + } +}; |