bigbiff bigbiff | e60683a | 2013-02-22 20:55:50 -0500 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2010 Andrew Nayenko <resver@gmail.com> |
| 3 | * |
| 4 | * This file may be redistributed under the terms of the |
| 5 | * GNU Lesser General Public License. |
| 6 | */ |
| 7 | #include "superblocks.h" |
| 8 | |
| 9 | struct exfat_super_block { |
| 10 | uint8_t jump[3]; |
| 11 | uint8_t oem_name[8]; |
| 12 | uint8_t __unused1[53]; |
| 13 | uint64_t block_start; |
| 14 | uint64_t block_count; |
| 15 | uint32_t fat_block_start; |
| 16 | uint32_t fat_block_count; |
| 17 | uint32_t cluster_block_start; |
| 18 | uint32_t cluster_count; |
| 19 | uint32_t rootdir_cluster; |
| 20 | uint8_t volume_serial[4]; |
| 21 | struct { |
| 22 | uint8_t minor; |
| 23 | uint8_t major; |
| 24 | } version; |
| 25 | uint16_t volume_state; |
| 26 | uint8_t block_bits; |
| 27 | uint8_t bpc_bits; |
| 28 | uint8_t fat_count; |
| 29 | uint8_t drive_no; |
| 30 | uint8_t allocated_percent; |
| 31 | } __attribute__((__packed__)); |
| 32 | |
| 33 | struct exfat_entry_label { |
| 34 | uint8_t type; |
| 35 | uint8_t length; |
| 36 | uint8_t name[30]; |
| 37 | } __attribute__((__packed__)); |
| 38 | |
| 39 | #define BLOCK_SIZE(sb) (1 << (sb)->block_bits) |
| 40 | #define CLUSTER_SIZE(sb) (BLOCK_SIZE(sb) << (sb)->bpc_bits) |
| 41 | #define EXFAT_FIRST_DATA_CLUSTER 2 |
| 42 | #define EXFAT_LAST_DATA_CLUSTER 0xffffff6 |
| 43 | #define EXFAT_ENTRY_SIZE 32 |
| 44 | |
| 45 | #define EXFAT_ENTRY_EOD 0x00 |
| 46 | #define EXFAT_ENTRY_LABEL 0x83 |
| 47 | |
| 48 | static blkid_loff_t block_to_offset(const struct exfat_super_block *sb, |
| 49 | blkid_loff_t block) |
| 50 | { |
| 51 | return (blkid_loff_t) block << sb->block_bits; |
| 52 | } |
| 53 | |
| 54 | static blkid_loff_t cluster_to_block(const struct exfat_super_block *sb, |
| 55 | uint32_t cluster) |
| 56 | { |
| 57 | return le32_to_cpu(sb->cluster_block_start) + |
| 58 | ((blkid_loff_t) (cluster - EXFAT_FIRST_DATA_CLUSTER) |
| 59 | << sb->bpc_bits); |
| 60 | } |
| 61 | |
| 62 | static blkid_loff_t cluster_to_offset(const struct exfat_super_block *sb, |
| 63 | uint32_t cluster) |
| 64 | { |
| 65 | return block_to_offset(sb, cluster_to_block(sb, cluster)); |
| 66 | } |
| 67 | |
| 68 | static uint32_t next_cluster(blkid_probe pr, |
| 69 | const struct exfat_super_block *sb, uint32_t cluster) |
| 70 | { |
| 71 | uint32_t *next; |
| 72 | blkid_loff_t fat_offset; |
| 73 | |
| 74 | fat_offset = block_to_offset(sb, le32_to_cpu(sb->fat_block_start)) |
| 75 | + (blkid_loff_t) cluster * sizeof(cluster); |
| 76 | next = (uint32_t *) blkid_probe_get_buffer(pr, fat_offset, |
| 77 | sizeof(uint32_t)); |
| 78 | if (!next) |
| 79 | return 0; |
| 80 | return le32_to_cpu(*next); |
| 81 | } |
| 82 | |
| 83 | static struct exfat_entry_label *find_label(blkid_probe pr, |
| 84 | const struct exfat_super_block *sb) |
| 85 | { |
| 86 | uint32_t cluster = le32_to_cpu(sb->rootdir_cluster); |
| 87 | blkid_loff_t offset = cluster_to_offset(sb, cluster); |
| 88 | uint8_t *entry; |
| 89 | |
| 90 | for (;;) { |
| 91 | entry = (uint8_t *) blkid_probe_get_buffer(pr, offset, |
| 92 | EXFAT_ENTRY_SIZE); |
| 93 | if (!entry) |
| 94 | return NULL; |
| 95 | if (entry[0] == EXFAT_ENTRY_EOD) |
| 96 | return NULL; |
| 97 | if (entry[0] == EXFAT_ENTRY_LABEL) |
| 98 | return (struct exfat_entry_label *) entry; |
| 99 | offset += EXFAT_ENTRY_SIZE; |
| 100 | if (offset % CLUSTER_SIZE(sb) == 0) { |
| 101 | cluster = next_cluster(pr, sb, cluster); |
| 102 | if (cluster < EXFAT_FIRST_DATA_CLUSTER) |
| 103 | return NULL; |
| 104 | if (cluster > EXFAT_LAST_DATA_CLUSTER) |
| 105 | return NULL; |
| 106 | offset = cluster_to_offset(sb, cluster); |
| 107 | } |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | static int probe_exfat(blkid_probe pr, const struct blkid_idmag *mag) |
| 112 | { |
| 113 | struct exfat_super_block *sb; |
| 114 | struct exfat_entry_label *label; |
| 115 | |
| 116 | sb = blkid_probe_get_sb(pr, mag, struct exfat_super_block); |
| 117 | if (!sb) |
| 118 | return -1; |
| 119 | |
| 120 | label = find_label(pr, sb); |
| 121 | if (label) |
| 122 | blkid_probe_set_utf8label(pr, label->name, |
| 123 | min(label->length * 2, 30), BLKID_ENC_UTF16LE); |
| 124 | |
| 125 | blkid_probe_sprintf_uuid(pr, sb->volume_serial, 4, |
| 126 | "%02hhX%02hhX-%02hhX%02hhX", |
| 127 | sb->volume_serial[3], sb->volume_serial[2], |
| 128 | sb->volume_serial[1], sb->volume_serial[0]); |
| 129 | |
| 130 | blkid_probe_sprintf_version(pr, "%u.%u", |
| 131 | sb->version.major, sb->version.minor); |
| 132 | |
| 133 | return 0; |
| 134 | } |
| 135 | |
| 136 | const struct blkid_idinfo exfat_idinfo = |
| 137 | { |
| 138 | .name = "exfat", |
| 139 | .usage = BLKID_USAGE_FILESYSTEM, |
| 140 | .probefunc = probe_exfat, |
| 141 | .magics = |
| 142 | { |
| 143 | { .magic = "EXFAT ", .len = 8, .sboff = 3 }, |
| 144 | { NULL } |
| 145 | } |
| 146 | }; |