Lines 25-41
Link Here
|
25 |
*/ |
25 |
*/ |
26 |
|
26 |
|
27 |
#include <sys/cdefs.h> |
27 |
#include <sys/cdefs.h> |
28 |
__FBSDID("$FreeBSD$"); |
28 |
__FBSDID("$FreeBSD$"); |
29 |
|
29 |
|
|
|
30 |
#include <sys/param.h> |
31 |
#include <sys/endian.h> |
32 |
|
33 |
#include <assert.h> |
34 |
#include <err.h> |
35 |
#include <errno.h> |
36 |
#include <iconv.h> |
37 |
#include <stdbool.h> |
30 |
#include <stdint.h> |
38 |
#include <stdint.h> |
31 |
#include <stdio.h> |
39 |
#include <stdio.h> |
32 |
#include <stdlib.h> |
40 |
#include <stdlib.h> |
33 |
#include <string.h> |
41 |
#include <string.h> |
34 |
|
42 |
|
35 |
#include "fstyp.h" |
43 |
#include "fstyp.h" |
36 |
|
44 |
|
|
|
45 |
/* |
46 |
* https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification |
47 |
*/ |
48 |
|
37 |
struct exfat_vbr { |
49 |
struct exfat_vbr { |
38 |
char ev_jmp[3]; |
50 |
char ev_jmp[3]; |
39 |
char ev_fsname[8]; |
51 |
char ev_fsname[8]; |
40 |
char ev_zeros[53]; |
52 |
char ev_zeros[53]; |
41 |
uint64_t ev_part_offset; |
53 |
uint64_t ev_part_offset; |
Lines 53-75
struct exfat_vbr {
Link Here
|
53 |
uint8_t ev_num_fats; |
65 |
uint8_t ev_num_fats; |
54 |
uint8_t ev_drive_sel; |
66 |
uint8_t ev_drive_sel; |
55 |
uint8_t ev_percent_used; |
67 |
uint8_t ev_percent_used; |
56 |
} __packed; |
68 |
} __packed; |
57 |
|
69 |
|
|
|
70 |
struct exfat_dirent { |
71 |
uint8_t xde_type; |
72 |
#define XDE_TYPE_INUSE_MASK 0x80 /* 1=in use */ |
73 |
#define XDE_TYPE_INUSE_SHIFT 7 |
74 |
#define XDE_TYPE_CATEGORY_MASK 0x40 /* 0=primary */ |
75 |
#define XDE_TYPE_CATEGORY_SHIFT 6 |
76 |
#define XDE_TYPE_IMPORTNC_MASK 0x20 /* 0=critical */ |
77 |
#define XDE_TYPE_IMPORTNC_SHIFT 5 |
78 |
#define XDE_TYPE_CODE_MASK 0x1f |
79 |
/* InUse=0, ..., TypeCode=0: EOD. */ |
80 |
#define XDE_TYPE_EOD 0x00 |
81 |
#define XDE_TYPE_ALLOC_BITMAP (XDE_TYPE_INUSE_MASK | 0x01) |
82 |
#define XDE_TYPE_UPCASE_TABLE (XDE_TYPE_INUSE_MASK | 0x02) |
83 |
#define XDE_TYPE_VOL_LABEL (XDE_TYPE_INUSE_MASK | 0x03) |
84 |
#define XDE_TYPE_FILE (XDE_TYPE_INUSE_MASK | 0x05) |
85 |
#define XDE_TYPE_VOL_GUID (XDE_TYPE_INUSE_MASK | XDE_TYPE_IMPORTNC_MASK) |
86 |
#define XDE_TYPE_STREAM_EXT (XDE_TYPE_INUSE_MASK | XDE_TYPE_CATEGORY_MASK) |
87 |
#define XDE_TYPE_FILE_NAME (XDE_TYPE_INUSE_MASK | XDE_TYPE_CATEGORY_MASK | 0x01) |
88 |
#define XDE_TYPE_VENDOR (XDE_TYPE_INUSE_MASK | XDE_TYPE_CATEGORY_MASK | XDE_TYPE_IMPORTNC_MASK) |
89 |
#define XDE_TYPE_VENDOR_ALLOC (XDE_TYPE_INUSE_MASK | XDE_TYPE_CATEGORY_MASK | XDE_TYPE_IMPORTNC_MASK | 0x01) |
90 |
union { |
91 |
uint8_t xde_generic_[19]; |
92 |
struct exde_primary { |
93 |
// XXX: count of following dirents, "secondary" |
94 |
uint8_t xde_secondary_count_; |
95 |
uint16_t xde_set_chksum_; |
96 |
uint16_t xde_prim_flags_; |
97 |
uint8_t xde_prim_generic_[14]; |
98 |
} __packed xde_primary_; |
99 |
struct exde_secondary { |
100 |
uint8_t xde_sec_flags_; |
101 |
uint8_t xde_sec_generic_[18]; |
102 |
} __packed xde_secondary_; |
103 |
} u; |
104 |
uint32_t xde_first_cluster; |
105 |
uint64_t xde_data_len; |
106 |
} __packed; |
107 |
#define xde_generic u.xde_generic_ |
108 |
#define xde_secondary_count u.xde_primary_.xde_secondary_count |
109 |
#define xde_set_chksum u.xde_primary_.xde_set_chksum_ |
110 |
#define xde_prim_flags u.xde_primary_.xde_prim_flags_ |
111 |
#define xde_sec_flags u.xde_secondary_.xde_sec_flags_ |
112 |
_Static_assert(sizeof(struct exfat_dirent) == 32, "spec"); |
113 |
|
114 |
struct exfat_de_label { |
115 |
uint8_t xdel_type; /* XDE_TYPE_VOL_LABEL */ |
116 |
uint8_t xdel_char_cnt; /* Length of UCS-2 label */ |
117 |
uint16_t xdel_vol_lbl[11]; |
118 |
uint8_t xdel_reserved[8]; |
119 |
} __packed; |
120 |
_Static_assert(sizeof(struct exfat_de_label) == 32, "spec"); |
121 |
|
122 |
#define MAIN_BOOT_REGION_SECT 0 |
123 |
#define BACKUP_BOOT_REGION_SECT 12 |
124 |
|
125 |
#define SUBREGION_CHKSUM_SECT 11 |
126 |
|
127 |
#define FIRST_CLUSTER 2 |
128 |
#define BAD_BLOCK_SENTINEL 0xfffffff7u |
129 |
#define END_CLUSTER_SENTINEL 0xffffffffu |
130 |
|
131 |
static inline void * |
132 |
read_sectn(FILE *fp, off_t sect, unsigned count, unsigned bytespersec) |
133 |
{ |
134 |
return (read_buf(fp, sect * bytespersec, bytespersec * count)); |
135 |
} |
136 |
|
137 |
static inline void * |
138 |
read_sect(FILE *fp, off_t sect, unsigned bytespersec) |
139 |
{ |
140 |
return (read_sectn(fp, sect, 1, bytespersec)); |
141 |
} |
142 |
|
143 |
/* |
144 |
* Compute the byte-by-byte multi-sector checksum of the given boot region |
145 |
* (MAIN or BACKUP), for a given bytespersec (typically 512 or 4096). |
146 |
* |
147 |
* Endian-safe; result is host endian. |
148 |
*/ |
149 |
static int |
150 |
exfat_compute_boot_chksum(FILE *fp, unsigned region, unsigned bytespersec, |
151 |
uint32_t *result) |
152 |
{ |
153 |
unsigned char *sector; |
154 |
unsigned n, sect; |
155 |
uint32_t checksum; |
156 |
|
157 |
checksum = 0; |
158 |
for (sect = 0; sect < 11; sect++) { |
159 |
sector = read_sect(fp, region + sect, bytespersec); |
160 |
if (sector == NULL) |
161 |
return (ENXIO); |
162 |
for (n = 0; n < bytespersec; n++) { |
163 |
if (sect == 0) { |
164 |
switch (n) { |
165 |
case 106: |
166 |
case 107: |
167 |
case 112: |
168 |
continue; |
169 |
} |
170 |
} |
171 |
checksum = ((checksum & 1) ? 0x80000000u : 0u) + |
172 |
(checksum >> 1) + (uint32_t)sector[n]; |
173 |
} |
174 |
free(sector); |
175 |
} |
176 |
|
177 |
*result = checksum; |
178 |
return (0); |
179 |
} |
180 |
|
181 |
static void |
182 |
convert_label(const uint16_t *ucs2label /* LE */, unsigned ucs2len, char |
183 |
*label_out, size_t label_sz) |
184 |
{ |
185 |
const char *label; |
186 |
char *label_out_orig; |
187 |
iconv_t cd; |
188 |
size_t srcleft, rc; |
189 |
|
190 |
/* Currently hardcoded in fstype.c as 256 or so. */ |
191 |
assert(label_sz > 1); |
192 |
|
193 |
if (ucs2len == 0) { |
194 |
/* |
195 |
* Kind of seems bogus, but the spec allows an empty label |
196 |
* entry with the same meaning as no label. |
197 |
*/ |
198 |
return; |
199 |
} |
200 |
|
201 |
if (ucs2len > 11) { |
202 |
warnx("exfat: Bogus volume label length: %u", ucs2len); |
203 |
return; |
204 |
} |
205 |
|
206 |
/* dstname="" means convert to the current locale. */ |
207 |
cd = iconv_open("", EXFAT_ENC); |
208 |
if (cd == (iconv_t)-1) { |
209 |
warn("exfat: Could not open iconv"); |
210 |
return; |
211 |
} |
212 |
|
213 |
label_out_orig = label_out; |
214 |
|
215 |
/* Dummy up the byte pointer and byte length iconv's API wants. */ |
216 |
label = (const void *)ucs2label; |
217 |
srcleft = ucs2len * sizeof(*ucs2label); |
218 |
|
219 |
rc = iconv(cd, __DECONST(char **, &label), &srcleft, &label_out, |
220 |
&label_sz); |
221 |
if (rc == (size_t)-1) { |
222 |
warn("exfat: iconv()"); |
223 |
*label_out_orig = '\0'; |
224 |
} else { |
225 |
/* NUL-terminate result (iconv advances label_out). */ |
226 |
if (label_sz == 0) |
227 |
label_out--; |
228 |
*label_out = '\0'; |
229 |
} |
230 |
|
231 |
iconv_close(cd); |
232 |
} |
233 |
|
234 |
/* |
235 |
* Using the FAT table, look up the next cluster in this chain. |
236 |
*/ |
237 |
static uint32_t |
238 |
exfat_fat_next(FILE *fp, const struct exfat_vbr *ev, unsigned BPS, |
239 |
uint32_t cluster) |
240 |
{ |
241 |
uint32_t fat_offset_sect, clsect, clsectoff; |
242 |
uint32_t *fatsect, nextclust; |
243 |
|
244 |
fat_offset_sect = le32toh(ev->ev_fat_offset); |
245 |
clsect = fat_offset_sect + (cluster / (BPS / sizeof(cluster))); |
246 |
clsectoff = (cluster % (BPS / sizeof(cluster))); |
247 |
|
248 |
/* XXX This is pretty wasteful without a block cache for the FAT. */ |
249 |
fatsect = read_sect(fp, clsect, BPS); |
250 |
nextclust = le32toh(fatsect[clsectoff]); |
251 |
free(fatsect); |
252 |
|
253 |
return (nextclust); |
254 |
} |
255 |
|
256 |
static void |
257 |
exfat_find_label(FILE *fp, const struct exfat_vbr *ev, unsigned BPS, |
258 |
char *label_out, size_t label_sz) |
259 |
{ |
260 |
uint32_t rootdir_cluster, sects_per_clust, cluster_offset_sect; |
261 |
off_t rootdir_sect; |
262 |
struct exfat_dirent *declust, *it; |
263 |
|
264 |
cluster_offset_sect = le32toh(ev->ev_cluster_offset); |
265 |
rootdir_cluster = le32toh(ev->ev_rootdir_cluster); |
266 |
sects_per_clust = (1u << ev->ev_log_sect_per_clust); |
267 |
|
268 |
if (rootdir_cluster < FIRST_CLUSTER) { |
269 |
warnx("%s: invalid rootdir cluster %u < %d", __func__, |
270 |
rootdir_cluster, FIRST_CLUSTER); |
271 |
return; |
272 |
} |
273 |
|
274 |
|
275 |
for (; rootdir_cluster != END_CLUSTER_SENTINEL; |
276 |
rootdir_cluster = exfat_fat_next(fp, ev, BPS, rootdir_cluster)) { |
277 |
if (rootdir_cluster == BAD_BLOCK_SENTINEL) { |
278 |
warnx("%s: Bogus bad block in root directory chain", |
279 |
__func__); |
280 |
return; |
281 |
} |
282 |
|
283 |
rootdir_sect = (rootdir_cluster - FIRST_CLUSTER) * |
284 |
sects_per_clust + cluster_offset_sect; |
285 |
declust = read_sectn(fp, rootdir_sect, sects_per_clust, BPS); |
286 |
for (it = declust; |
287 |
it < declust + (sects_per_clust * BPS / sizeof(*it)); it++) { |
288 |
bool eod = false; |
289 |
|
290 |
/* |
291 |
* Simplistic directory traversal doesn't do any |
292 |
* validation of MUST requirements in spec. |
293 |
*/ |
294 |
switch (it->xde_type) { |
295 |
case XDE_TYPE_EOD: |
296 |
eod = true; |
297 |
break; |
298 |
case XDE_TYPE_VOL_LABEL: { |
299 |
struct exfat_de_label *lde = (void*)it; |
300 |
convert_label(lde->xdel_vol_lbl, |
301 |
lde->xdel_char_cnt, label_out, label_sz); |
302 |
free(declust); |
303 |
return; |
304 |
} |
305 |
} |
306 |
|
307 |
if (eod) |
308 |
break; |
309 |
} |
310 |
free(declust); |
311 |
} |
312 |
} |
313 |
|
58 |
int |
314 |
int |
59 |
fstyp_exfat(FILE *fp, char *label, size_t size) |
315 |
fstyp_exfat(FILE *fp, char *label, size_t size) |
60 |
{ |
316 |
{ |
61 |
struct exfat_vbr *ev; |
317 |
struct exfat_vbr *ev; |
|
|
318 |
uint32_t *cksect; |
319 |
unsigned bytespersec; |
320 |
uint32_t chksum; |
321 |
int error; |
62 |
|
322 |
|
|
|
323 |
cksect = NULL; |
63 |
ev = (struct exfat_vbr *)read_buf(fp, 0, 512); |
324 |
ev = (struct exfat_vbr *)read_buf(fp, 0, 512); |
64 |
if (ev == NULL || strncmp(ev->ev_fsname, "EXFAT ", 8) != 0) |
325 |
if (ev == NULL || strncmp(ev->ev_fsname, "EXFAT ", 8) != 0) |
65 |
goto fail; |
326 |
goto fail; |
66 |
|
327 |
|
|
|
328 |
if (ev->ev_log_bytes_per_sect < 9 || ev->ev_log_bytes_per_sect > 12) { |
329 |
warnx("exfat: Invalid BytesPerSectorShift"); |
330 |
goto done; |
331 |
} |
332 |
|
333 |
bytespersec = (1u << ev->ev_log_bytes_per_sect); |
334 |
|
335 |
error = exfat_compute_boot_chksum(fp, MAIN_BOOT_REGION_SECT, |
336 |
bytespersec, &chksum); |
337 |
if (error != 0) |
338 |
goto done; |
339 |
|
340 |
cksect = read_sect(fp, MAIN_BOOT_REGION_SECT + SUBREGION_CHKSUM_SECT, |
341 |
bytespersec); |
342 |
|
67 |
/* |
343 |
/* |
68 |
* Reading the volume label requires walking the root directory to look |
344 |
* Technically the entire sector should be full of repeating 4-byte |
69 |
* for a special label file. Left as an exercise for the reader. |
345 |
* checksum pattern, but we only verify the first. |
70 |
*/ |
346 |
*/ |
|
|
347 |
if (chksum != le32toh(cksect[0])) { |
348 |
warnx("exfat: Found checksum 0x%08x != computed 0x%08x", |
349 |
le32toh(cksect[0]), chksum); |
350 |
goto done; |
351 |
} |
352 |
|
353 |
exfat_find_label(fp, ev, bytespersec, label, size); |
354 |
|
355 |
done: |
356 |
free(cksect); |
71 |
free(ev); |
357 |
free(ev); |
72 |
return (0); |
358 |
return (0); |
73 |
|
359 |
|
74 |
fail: |
360 |
fail: |
75 |
free(ev); |
361 |
free(ev); |