hush/util-linux/volume_id/exfat.c
Denys Vlasenko f560422fa0 Big cleanup in config help and description
Redundant help texts (one which only repeats the description)
are deleted.

Descriptions and help texts are trimmed.

Some config options are moved, even across menus.

No config option _names_ are changed.

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
2017-01-10 14:58:54 +01:00

142 lines
4.8 KiB
C

/*
* volume_id - reads filesystem label and uuid
*
* Copyright (C) 2012 S-G Bergh <sgb@systemasis.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
//kbuild:lib-$(CONFIG_FEATURE_VOLUMEID_EXFAT) += exfat.o
//config:config FEATURE_VOLUMEID_EXFAT
//config: bool "exFAT filesystem"
//config: default y
//config: depends on VOLUMEID
//config: help
//config: exFAT (extended FAT) is a proprietary file system designed especially
//config: for flash drives. It has many features from NTFS, but with less
//config: overhead. exFAT is used on most SDXC cards for consumer electronics.
#include "volume_id_internal.h"
#define EXFAT_SB_OFFSET 0
#define EXFAT_DIR_ENTRY_SZ 32
#define EXFAT_MAX_DIR_ENTRIES 100
struct exfat_super_block {
/* 0x00 */ uint8_t boot_jump[3];
/* 0x03 */ uint8_t fs_name[8];
/* 0x0B */ uint8_t must_be_zero[53];
/* 0x40 */ uint64_t partition_offset;
/* 0x48 */ uint64_t volume_length;
/* 0x50 */ uint32_t fat_offset; // Sector address of 1st FAT
/* 0x54 */ uint32_t fat_size; // In sectors
/* 0x58 */ uint32_t cluster_heap_offset; // Sector address of Data Region
/* 0x5C */ uint32_t cluster_count;
/* 0x60 */ uint32_t root_dir; // Cluster address of Root Directory
/* 0x64 */ uint8_t vol_serial_nr[4]; // Volume ID
/* 0x68 */ uint16_t fs_revision; // VV.MM
/* 0x6A */ uint16_t vol_flags;
/* 0x6C */ uint8_t bytes_per_sector; // Power of 2: 9 => 512, 12 => 4096
/* 0x6D */ uint8_t sectors_per_cluster; // Power of 2
/* 0x6E */ uint8_t nr_of_fats; // 2 for TexFAT
/* 0x6F */ // ...
} PACKED;
struct exfat_dir_entry {
/* 0x00 */ uint8_t entry_type;
union {
struct volume_label {
/* 0x01 */ uint8_t char_count; // Length of label
/* 0x02 */ uint16_t vol_label[11]; // UTF16 string without null termination
/* 0x18 */ uint8_t reserved[8];
/* 0x20 */ } PACKED label;
struct volume_guid {
/* 0x01 */ uint8_t sec_count;
/* 0x02 */ uint16_t set_checksum;
/* 0x04 */ uint16_t flags;
/* 0x06 */ uint8_t vol_guid[16];
/* 0x16 */ uint8_t reserved[10];
/* 0x20 */ } PACKED guid;
} PACKED type;
} PACKED;
int FAST_FUNC volume_id_probe_exfat(struct volume_id *id /*,uint64_t off*/)
{
struct exfat_super_block *sb;
struct exfat_dir_entry *de;
unsigned sector_sz;
unsigned cluster_sz;
uint64_t root_dir_off;
unsigned count;
unsigned need_lbl_guid;
// Primary super block
dbg("exFAT: probing at offset 0x%x", EXFAT_SB_OFFSET);
sb = volume_id_get_buffer(id, EXFAT_SB_OFFSET, sizeof(*sb));
if (!sb)
return -1;
if (memcmp(sb->fs_name, "EXFAT ", 8) != 0)
return -1;
sector_sz = 1 << sb->bytes_per_sector;
cluster_sz = sector_sz << sb->sectors_per_cluster;
// There are no clusters 0 and 1, so the first cluster is 2.
root_dir_off = (uint64_t)EXFAT_SB_OFFSET +
// Hmm... should we cast sector_sz/cluster_sz to uint64_t?
(le32_to_cpu(sb->cluster_heap_offset)) * sector_sz +
(le32_to_cpu(sb->root_dir) - 2) * cluster_sz;
dbg("exFAT: sector size 0x%x bytes", sector_sz);
dbg("exFAT: cluster size 0x%x bytes", cluster_sz);
dbg("exFAT: root dir is at 0x%llx", (long long)root_dir_off);
// Use DOS uuid as fallback, if no GUID set
volume_id_set_uuid(id, sb->vol_serial_nr, UUID_DOS);
// EXFAT_MAX_DIR_ENTRIES is used as a safety belt.
// The Root Directory may hold an unlimited number of entries,
// so we do not want to check all. Usually label and GUID
// are in the beginning, but there are no guarantees.
need_lbl_guid = (1 << 0) | (1 << 1);
for (count = 0; count < EXFAT_MAX_DIR_ENTRIES; count++) {
de = volume_id_get_buffer(id, root_dir_off + (count * EXFAT_DIR_ENTRY_SZ), EXFAT_DIR_ENTRY_SZ);
if (de == NULL)
break;
if (de->entry_type == 0x00) {
// End of Directory Marker
dbg("exFAT: End of root directory reached after %u entries", count);
break;
}
if (de->entry_type == 0x83) {
// Volume Label Directory Entry
volume_id_set_label_unicode16(id, (uint8_t *)de->type.label.vol_label,
LE, 2 * de->type.label.char_count);
need_lbl_guid &= ~(1 << 0);
}
if (de->entry_type == 0xA0) {
// Volume GUID Directory Entry
volume_id_set_uuid(id, de->type.guid.vol_guid, UUID_DCE);
need_lbl_guid &= ~(1 << 1);
}
if (!need_lbl_guid)
break;
}
IF_FEATURE_BLKID_TYPE(id->type = "exfat";)
return 0;
}