/* * 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 jump[3]; uint8_t oem_name[8]; uint8_t __unused1[53]; uint64_t block_start; uint64_t block_count; uint32_t fat_block_start; uint32_t fat_block_count; uint32_t cluster_block_start; uint32_t cluster_count; uint32_t rootdir_cluster; uint8_t volume_serial[4]; struct { uint8_t vermin; uint8_t vermaj; } version; uint16_t volume_state; uint8_t block_bits; uint8_t bpc_bits; uint8_t fat_count; uint8_t drive_no; uint8_t allocated_percent; } __attribute__((__packed__)); struct exfat_entry_label { uint8_t type; uint8_t length; uint8_t name[30]; } __attribute__((__packed__)); #define BLOCK_SIZE(sb) (1u << (sb)->block_bits) #define CLUSTER_SIZE(sb) (BLOCK_SIZE(sb) << (sb)->bpc_bits) #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->block_bits; } static uint64_t cluster_to_block(const struct exfat_super_block *sb, uint32_t cluster) { return le32_to_cpu(sb->cluster_block_start) + ((uint64_t) (cluster - EXFAT_FIRST_DATA_CLUSTER) << sb->bpc_bits); } 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 *next; uint64_t fat_offset; fat_offset = block_to_offset(sb, le32_to_cpu(sb->fat_block_start)) + (uint64_t) cluster * sizeof(cluster); next = (uint32_t *) blkid_probe_get_buffer(pr, fat_offset, sizeof(uint32_t)); if (!next) return 0; 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->rootdir_cluster); 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 (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; label = find_label(pr, sb); if (label) blkid_probe_set_utf8label(pr, label->name, min(label->length * 2, 30), BLKID_ENC_UTF16LE); else if (errno) return -errno; blkid_probe_sprintf_uuid(pr, sb->volume_serial, 4, "%02hhX%02hhX-%02hhX%02hhX", sb->volume_serial[3], sb->volume_serial[2], sb->volume_serial[1], sb->volume_serial[0]); blkid_probe_sprintf_version(pr, "%u.%u", sb->version.vermaj, sb->version.vermin); 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 } } };