summaryrefslogtreecommitdiffstats
path: root/libblkid/src/superblocks/exfat.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 19:10:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 19:10:49 +0000
commitcfe5e3905201349e9cf3f95d52ff4bd100bde37d (patch)
treed0baf160cbee3195249d095f85e52d20c21acf02 /libblkid/src/superblocks/exfat.c
parentInitial commit. (diff)
downloadutil-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.c259
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 }
+ }
+};