2 * Copyright (C) 2010 Andrew Nayenko <resver@gmail.com>
4 * This file may be redistributed under the terms of the
5 * GNU Lesser General Public License.
7 #include "superblocks.h"
9 struct exfat_super_block
{
11 uint8_t FileSystemName
[8];
12 uint8_t MustBeZero
[53];
13 uint64_t PartitionOffset
;
14 uint64_t VolumeLength
;
17 uint32_t ClusterHeapOffset
;
18 uint32_t ClusterCount
;
19 uint32_t FirstClusterOfRootDirectory
;
20 uint8_t VolumeSerialNumber
[4];
26 uint8_t BytesPerSectorShift
;
27 uint8_t SectorsPerClusterShift
;
32 uint8_t BootCode
[390];
33 uint16_t BootSignature
;
34 } __attribute__((__packed__
));
36 struct exfat_entry_label
{
41 } __attribute__((__packed__
));
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)
45 #define EXFAT_FIRST_DATA_CLUSTER 2
46 #define EXFAT_LAST_DATA_CLUSTER 0xffffff6
47 #define EXFAT_ENTRY_SIZE 32
49 #define EXFAT_ENTRY_EOD 0x00
50 #define EXFAT_ENTRY_LABEL 0x83
52 #define EXFAT_MAX_DIR_SIZE (256 * 1024 * 1024)
54 static uint64_t block_to_offset(const struct exfat_super_block
*sb
,
57 return block
<< sb
->BytesPerSectorShift
;
60 static uint64_t cluster_to_block(const struct exfat_super_block
*sb
,
63 return le32_to_cpu(sb
->ClusterHeapOffset
) +
64 ((uint64_t) (cluster
- EXFAT_FIRST_DATA_CLUSTER
)
65 << sb
->SectorsPerClusterShift
);
68 static uint64_t cluster_to_offset(const struct exfat_super_block
*sb
,
71 return block_to_offset(sb
, cluster_to_block(sb
, cluster
));
74 static uint32_t next_cluster(blkid_probe pr
,
75 const struct exfat_super_block
*sb
, uint32_t cluster
)
77 uint32_t *nextp
, next
;
80 fat_offset
= block_to_offset(sb
, le32_to_cpu(sb
->FatOffset
))
81 + (uint64_t) cluster
* sizeof(cluster
);
82 nextp
= (uint32_t *) blkid_probe_get_buffer(pr
, fat_offset
,
86 memcpy(&next
, nextp
, sizeof(next
));
87 return le32_to_cpu(next
);
90 static struct exfat_entry_label
*find_label(blkid_probe pr
,
91 const struct exfat_super_block
*sb
)
93 uint32_t cluster
= le32_to_cpu(sb
->FirstClusterOfRootDirectory
);
94 uint64_t offset
= cluster_to_offset(sb
, cluster
);
96 const size_t max_iter
= EXFAT_MAX_DIR_SIZE
/ EXFAT_ENTRY_SIZE
;
99 for (; i
< max_iter
; i
++) {
100 entry
= (uint8_t *) blkid_probe_get_buffer(pr
, offset
,
104 if (entry
[0] == EXFAT_ENTRY_EOD
)
106 if (entry
[0] == EXFAT_ENTRY_LABEL
)
107 return (struct exfat_entry_label
*) entry
;
109 offset
+= EXFAT_ENTRY_SIZE
;
110 if (CLUSTER_SIZE(sb
) && (offset
% CLUSTER_SIZE(sb
)) == 0) {
111 cluster
= next_cluster(pr
, sb
, cluster
);
112 if (cluster
< EXFAT_FIRST_DATA_CLUSTER
)
114 if (cluster
> EXFAT_LAST_DATA_CLUSTER
)
116 offset
= cluster_to_offset(sb
, cluster
);
123 /* From https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification#34-main-and-backup-boot-checksum-sub-regions */
124 static uint32_t exfat_boot_checksum(const unsigned char *sectors
,
127 uint32_t n_bytes
= sector_size
* 11;
128 uint32_t checksum
= 0;
130 for (size_t i
= 0; i
< n_bytes
; i
++) {
131 if ((i
== 106) || (i
== 107) || (i
== 112))
134 checksum
= ((checksum
& 1) ? 0x80000000 : 0) + (checksum
>> 1)
135 + (uint32_t) sectors
[i
];
141 static int exfat_validate_checksum(blkid_probe pr
,
142 const struct exfat_super_block
*sb
)
144 size_t sector_size
= BLOCK_SIZE(sb
);
145 /* 11 sectors will be checksummed, the 12th contains the expected */
146 const unsigned char *data
= blkid_probe_get_buffer(pr
, 0, sector_size
* 12);
150 uint32_t checksum
= exfat_boot_checksum(data
, sector_size
);
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
))
164 static int exfat_valid_superblock(blkid_probe pr
, const struct exfat_super_block
*sb
)
166 if (le16_to_cpu(sb
->BootSignature
) != 0xAA55)
169 if (!CLUSTER_SIZE(sb
))
172 if (memcmp(sb
->JumpBoot
, "\xEB\x76\x90", 3) != 0)
175 for (size_t i
= 0; i
< sizeof(sb
->MustBeZero
); i
++)
176 if (sb
->MustBeZero
[i
] != 0x00)
179 if (!exfat_validate_checksum(pr
, sb
))
185 /* function prototype to avoid warnings (duplicate in partitions/dos.c) */
186 extern int blkid_probe_is_exfat(blkid_probe pr
);
189 * This function is used by MBR partition table parser to avoid
190 * misinterpretation of exFAT filesystem.
192 int blkid_probe_is_exfat(blkid_probe pr
)
194 struct exfat_super_block
*sb
;
195 const struct blkid_idmag
*mag
= NULL
;
198 rc
= blkid_probe_get_idmag(pr
, &vfat_idinfo
, NULL
, &mag
);
200 return rc
; /* error */
201 if (rc
!= BLKID_PROBE_OK
|| !mag
)
204 sb
= blkid_probe_get_sb(pr
, mag
, struct exfat_super_block
);
208 if (memcmp(sb
->FileSystemName
, "EXFAT ", 8) != 0)
211 return exfat_valid_superblock(pr
, sb
);
214 static int probe_exfat(blkid_probe pr
, const struct blkid_idmag
*mag
)
216 struct exfat_super_block
*sb
;
217 struct exfat_entry_label
*label
;
219 sb
= blkid_probe_get_sb(pr
, mag
, struct exfat_super_block
);
221 return errno
? -errno
: BLKID_PROBE_NONE
;
223 if (!exfat_valid_superblock(pr
, sb
))
224 return BLKID_PROBE_NONE
;
226 label
= find_label(pr
, sb
);
228 blkid_probe_set_utf8label(pr
, label
->name
,
229 min((size_t) label
->length
* 2, sizeof(label
->name
)),
234 blkid_probe_sprintf_uuid(pr
, sb
->VolumeSerialNumber
, 4,
235 "%02hhX%02hhX-%02hhX%02hhX",
236 sb
->VolumeSerialNumber
[3], sb
->VolumeSerialNumber
[2],
237 sb
->VolumeSerialNumber
[1], sb
->VolumeSerialNumber
[0]);
239 blkid_probe_sprintf_version(pr
, "%u.%u",
240 sb
->FileSystemRevision
.vermaj
, sb
->FileSystemRevision
.vermin
);
242 blkid_probe_set_fsblocksize(pr
, BLOCK_SIZE(sb
));
243 blkid_probe_set_block_size(pr
, BLOCK_SIZE(sb
));
244 blkid_probe_set_fssize(pr
, BLOCK_SIZE(sb
) * le64_to_cpu(sb
->VolumeLength
));
246 return BLKID_PROBE_OK
;
249 const struct blkid_idinfo exfat_idinfo
=
252 .usage
= BLKID_USAGE_FILESYSTEM
,
253 .probefunc
= probe_exfat
,
256 { .magic
= "EXFAT ", .len
= 8, .sboff
= 3 },