]>
Commit | Line | Data |
---|---|---|
8604c255 AN |
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 { | |
6c2484cd TW |
10 | uint8_t JumpBoot[3]; |
11 | uint8_t FileSystemName[8]; | |
12 | uint8_t MustBeZero[53]; | |
13 | uint64_t PartitionOffset; | |
14 | uint64_t VolumeLength; | |
15 | uint32_t FatOffset; | |
16 | uint32_t FatLength; | |
17 | uint32_t ClusterHeapOffset; | |
18 | uint32_t ClusterCount; | |
19 | uint32_t FirstClusterOfRootDirectory; | |
20 | uint8_t VolumeSerialNumber[4]; | |
8604c255 | 21 | struct { |
8f6a58ef KZ |
22 | uint8_t vermin; |
23 | uint8_t vermaj; | |
6c2484cd TW |
24 | } FileSystemRevision; |
25 | uint16_t VolumeFlags; | |
26 | uint8_t BytesPerSectorShift; | |
27 | uint8_t SectorsPerClusterShift; | |
28 | uint8_t NumberOfFats; | |
29 | uint8_t DriveSelect; | |
30 | uint8_t PercentInUse; | |
31 | uint8_t Reserved[7]; | |
32 | uint8_t BootCode[390]; | |
33 | uint16_t BootSignature; | |
8604c255 AN |
34 | } __attribute__((__packed__)); |
35 | ||
36 | struct exfat_entry_label { | |
37 | uint8_t type; | |
38 | uint8_t length; | |
fb4ed8a5 PR |
39 | uint8_t name[22]; |
40 | uint8_t reserved[8]; | |
8604c255 AN |
41 | } __attribute__((__packed__)); |
42 | ||
6c2484cd TW |
43 | #define BLOCK_SIZE(sb) ((sb)->BytesPerSectorShift < 32 ? (1u << (sb)->BytesPerSectorShift) : 0) |
44 | #define CLUSTER_SIZE(sb) ((sb)->SectorsPerClusterShift < 32 ? (BLOCK_SIZE(sb) << (sb)->SectorsPerClusterShift) : 0) | |
8604c255 AN |
45 | #define EXFAT_FIRST_DATA_CLUSTER 2 |
46 | #define EXFAT_LAST_DATA_CLUSTER 0xffffff6 | |
47 | #define EXFAT_ENTRY_SIZE 32 | |
48 | ||
49 | #define EXFAT_ENTRY_EOD 0x00 | |
50 | #define EXFAT_ENTRY_LABEL 0x83 | |
51 | ||
07b2450d YM |
52 | #define EXFAT_MAX_DIR_SIZE (256 * 1024 * 1024) |
53 | ||
f12cd8d1 KZ |
54 | static uint64_t block_to_offset(const struct exfat_super_block *sb, |
55 | uint64_t block) | |
8604c255 | 56 | { |
6c2484cd | 57 | return block << sb->BytesPerSectorShift; |
8604c255 AN |
58 | } |
59 | ||
f12cd8d1 | 60 | static uint64_t cluster_to_block(const struct exfat_super_block *sb, |
8604c255 AN |
61 | uint32_t cluster) |
62 | { | |
6c2484cd | 63 | return le32_to_cpu(sb->ClusterHeapOffset) + |
f12cd8d1 | 64 | ((uint64_t) (cluster - EXFAT_FIRST_DATA_CLUSTER) |
6c2484cd | 65 | << sb->SectorsPerClusterShift); |
8604c255 AN |
66 | } |
67 | ||
f12cd8d1 | 68 | static uint64_t cluster_to_offset(const struct exfat_super_block *sb, |
8604c255 AN |
69 | uint32_t cluster) |
70 | { | |
71 | return block_to_offset(sb, cluster_to_block(sb, cluster)); | |
72 | } | |
73 | ||
74 | static uint32_t next_cluster(blkid_probe pr, | |
75 | const struct exfat_super_block *sb, uint32_t cluster) | |
76 | { | |
2a71e291 | 77 | uint32_t *nextp, next; |
f12cd8d1 | 78 | uint64_t fat_offset; |
8604c255 | 79 | |
6c2484cd | 80 | fat_offset = block_to_offset(sb, le32_to_cpu(sb->FatOffset)) |
f12cd8d1 | 81 | + (uint64_t) cluster * sizeof(cluster); |
2a71e291 | 82 | nextp = (uint32_t *) blkid_probe_get_buffer(pr, fat_offset, |
8604c255 | 83 | sizeof(uint32_t)); |
2a71e291 | 84 | if (!nextp) |
8604c255 | 85 | return 0; |
2a71e291 MB |
86 | memcpy(&next, nextp, sizeof(next)); |
87 | return le32_to_cpu(next); | |
8604c255 AN |
88 | } |
89 | ||
90 | static struct exfat_entry_label *find_label(blkid_probe pr, | |
91 | const struct exfat_super_block *sb) | |
92 | { | |
6c2484cd | 93 | uint32_t cluster = le32_to_cpu(sb->FirstClusterOfRootDirectory); |
f12cd8d1 | 94 | uint64_t offset = cluster_to_offset(sb, cluster); |
8604c255 | 95 | uint8_t *entry; |
07b2450d | 96 | const size_t max_iter = EXFAT_MAX_DIR_SIZE / EXFAT_ENTRY_SIZE; |
f98b5632 | 97 | size_t i = 0; |
8604c255 | 98 | |
f98b5632 | 99 | for (; i < max_iter; i++) { |
8604c255 AN |
100 | entry = (uint8_t *) blkid_probe_get_buffer(pr, offset, |
101 | EXFAT_ENTRY_SIZE); | |
102 | if (!entry) | |
103 | return NULL; | |
104 | if (entry[0] == EXFAT_ENTRY_EOD) | |
105 | return NULL; | |
106 | if (entry[0] == EXFAT_ENTRY_LABEL) | |
107 | return (struct exfat_entry_label *) entry; | |
f98b5632 | 108 | |
8604c255 | 109 | offset += EXFAT_ENTRY_SIZE; |
48229f8e | 110 | if (CLUSTER_SIZE(sb) && (offset % CLUSTER_SIZE(sb)) == 0) { |
8604c255 AN |
111 | cluster = next_cluster(pr, sb, cluster); |
112 | if (cluster < EXFAT_FIRST_DATA_CLUSTER) | |
113 | return NULL; | |
114 | if (cluster > EXFAT_LAST_DATA_CLUSTER) | |
115 | return NULL; | |
116 | offset = cluster_to_offset(sb, cluster); | |
117 | } | |
118 | } | |
f98b5632 RS |
119 | |
120 | return NULL; | |
8604c255 AN |
121 | } |
122 | ||
81bbdd0b | 123 | /* From https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification#34-main-and-backup-boot-checksum-sub-regions */ |
7eba8f98 | 124 | static uint32_t exfat_boot_checksum(const unsigned char *sectors, |
81bbdd0b TW |
125 | size_t sector_size) |
126 | { | |
127 | uint32_t n_bytes = sector_size * 11; | |
128 | uint32_t checksum = 0; | |
129 | ||
130 | for (size_t i = 0; i < n_bytes; i++) { | |
131 | if ((i == 106) || (i == 107) || (i == 112)) | |
132 | continue; | |
133 | ||
134 | checksum = ((checksum & 1) ? 0x80000000 : 0) + (checksum >> 1) | |
135 | + (uint32_t) sectors[i]; | |
136 | } | |
137 | ||
138 | return checksum; | |
139 | } | |
140 | ||
141 | static int exfat_validate_checksum(blkid_probe pr, | |
142 | const struct exfat_super_block *sb) | |
143 | { | |
144 | size_t sector_size = BLOCK_SIZE(sb); | |
145 | /* 11 sectors will be checksummed, the 12th contains the expected */ | |
7eba8f98 | 146 | const unsigned char *data = blkid_probe_get_buffer(pr, 0, sector_size * 12); |
81bbdd0b TW |
147 | if (!data) |
148 | return 0; | |
149 | ||
150 | uint32_t checksum = exfat_boot_checksum(data, sector_size); | |
151 | ||
152 | /* The expected checksum is repeated, check all of them */ | |
153 | for (size_t i = 0; i < sector_size / sizeof(uint32_t); i++) { | |
154 | size_t offset = sector_size * 11 + i * 4; | |
155 | uint32_t *expected_addr = (uint32_t *) &data[offset]; | |
156 | uint32_t expected = le32_to_cpu(*expected_addr); | |
157 | if (!blkid_probe_verify_csum(pr, checksum, expected)) | |
158 | return 0; | |
159 | }; | |
160 | ||
161 | return 1; | |
162 | } | |
163 | ||
8a0f04e6 | 164 | static int exfat_valid_superblock(blkid_probe pr, const struct exfat_super_block *sb) |
8604c255 | 165 | { |
1a73b23e | 166 | if (le16_to_cpu(sb->BootSignature) != 0xAA55) |
8a0f04e6 TW |
167 | return 0; |
168 | ||
169 | if (!CLUSTER_SIZE(sb)) | |
170 | return 0; | |
1a73b23e TW |
171 | |
172 | if (memcmp(sb->JumpBoot, "\xEB\x76\x90", 3) != 0) | |
8a0f04e6 | 173 | return 0; |
1a73b23e TW |
174 | |
175 | for (size_t i = 0; i < sizeof(sb->MustBeZero); i++) | |
176 | if (sb->MustBeZero[i] != 0x00) | |
8a0f04e6 | 177 | return 0; |
1a73b23e | 178 | |
81bbdd0b | 179 | if (!exfat_validate_checksum(pr, sb)) |
8a0f04e6 TW |
180 | return 0; |
181 | ||
182 | return 1; | |
183 | } | |
184 | ||
185 | /* function prototype to avoid warnings (duplicate in partitions/dos.c) */ | |
186 | extern int blkid_probe_is_exfat(blkid_probe pr); | |
187 | ||
188 | /* | |
189 | * This function is used by MBR partition table parser to avoid | |
190 | * misinterpretation of exFAT filesystem. | |
191 | */ | |
192 | int blkid_probe_is_exfat(blkid_probe pr) | |
193 | { | |
4583e6b2 | 194 | const struct exfat_super_block *sb; |
8a0f04e6 TW |
195 | const struct blkid_idmag *mag = NULL; |
196 | int rc; | |
197 | ||
198 | rc = blkid_probe_get_idmag(pr, &vfat_idinfo, NULL, &mag); | |
199 | if (rc < 0) | |
200 | return rc; /* error */ | |
201 | if (rc != BLKID_PROBE_OK || !mag) | |
202 | return 0; | |
203 | ||
204 | sb = blkid_probe_get_sb(pr, mag, struct exfat_super_block); | |
205 | if (!sb) | |
206 | return 0; | |
207 | ||
208 | if (memcmp(sb->FileSystemName, "EXFAT ", 8) != 0) | |
209 | return 0; | |
210 | ||
211 | return exfat_valid_superblock(pr, sb); | |
212 | } | |
213 | ||
214 | static int probe_exfat(blkid_probe pr, const struct blkid_idmag *mag) | |
215 | { | |
4583e6b2 | 216 | const struct exfat_super_block *sb; |
8a0f04e6 TW |
217 | struct exfat_entry_label *label; |
218 | ||
219 | sb = blkid_probe_get_sb(pr, mag, struct exfat_super_block); | |
220 | if (!sb) | |
221 | return errno ? -errno : BLKID_PROBE_NONE; | |
222 | ||
223 | if (!exfat_valid_superblock(pr, sb)) | |
81bbdd0b TW |
224 | return BLKID_PROBE_NONE; |
225 | ||
8604c255 AN |
226 | label = find_label(pr, sb); |
227 | if (label) | |
228 | blkid_probe_set_utf8label(pr, label->name, | |
81826929 | 229 | min((size_t) label->length * 2, sizeof(label->name)), |
35c6ed61 | 230 | UL_ENCODE_UTF16LE); |
37f40602 HR |
231 | else if (errno) |
232 | return -errno; | |
8604c255 | 233 | |
6c2484cd | 234 | blkid_probe_sprintf_uuid(pr, sb->VolumeSerialNumber, 4, |
8604c255 | 235 | "%02hhX%02hhX-%02hhX%02hhX", |
6c2484cd TW |
236 | sb->VolumeSerialNumber[3], sb->VolumeSerialNumber[2], |
237 | sb->VolumeSerialNumber[1], sb->VolumeSerialNumber[0]); | |
8604c255 | 238 | |
07ce7003 | 239 | blkid_probe_sprintf_version(pr, "%u.%u", |
6c2484cd | 240 | sb->FileSystemRevision.vermaj, sb->FileSystemRevision.vermin); |
8604c255 | 241 | |
0f447d49 | 242 | blkid_probe_set_fsblocksize(pr, BLOCK_SIZE(sb)); |
cd129b7d | 243 | blkid_probe_set_block_size(pr, BLOCK_SIZE(sb)); |
60a5e8d8 | 244 | blkid_probe_set_fssize(pr, BLOCK_SIZE(sb) * le64_to_cpu(sb->VolumeLength)); |
cd129b7d | 245 | |
37f40602 | 246 | return BLKID_PROBE_OK; |
8604c255 AN |
247 | } |
248 | ||
249 | const struct blkid_idinfo exfat_idinfo = | |
250 | { | |
251 | .name = "exfat", | |
252 | .usage = BLKID_USAGE_FILESYSTEM, | |
253 | .probefunc = probe_exfat, | |
254 | .magics = | |
255 | { | |
256 | { .magic = "EXFAT ", .len = 8, .sboff = 3 }, | |
257 | { NULL } | |
258 | } | |
259 | }; |