1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
|
/*
* Copyright (c) 2017 Conrad Meyer <cem@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/endian.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#ifdef WITH_ICONV
#include <iconv.h>
#endif
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "fstyp.h"
/*
* https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification
*/
struct exfat_vbr {
char ev_jmp[3];
char ev_fsname[8];
char ev_zeros[53];
uint64_t ev_part_offset;
uint64_t ev_vol_length;
uint32_t ev_fat_offset;
uint32_t ev_fat_length;
uint32_t ev_cluster_offset;
uint32_t ev_cluster_count;
uint32_t ev_rootdir_cluster;
uint32_t ev_vol_serial;
uint16_t ev_fs_revision;
uint16_t ev_vol_flags;
uint8_t ev_log_bytes_per_sect;
uint8_t ev_log_sect_per_clust;
uint8_t ev_num_fats;
uint8_t ev_drive_sel;
uint8_t ev_percent_used;
} __packed;
struct exfat_dirent {
uint8_t xde_type;
#define XDE_TYPE_INUSE_MASK 0x80 /* 1=in use */
#define XDE_TYPE_INUSE_SHIFT 7
#define XDE_TYPE_CATEGORY_MASK 0x40 /* 0=primary */
#define XDE_TYPE_CATEGORY_SHIFT 6
#define XDE_TYPE_IMPORTNC_MASK 0x20 /* 0=critical */
#define XDE_TYPE_IMPORTNC_SHIFT 5
#define XDE_TYPE_CODE_MASK 0x1f
/* InUse=0, ..., TypeCode=0: EOD. */
#define XDE_TYPE_EOD 0x00
#define XDE_TYPE_ALLOC_BITMAP (XDE_TYPE_INUSE_MASK | 0x01)
#define XDE_TYPE_UPCASE_TABLE (XDE_TYPE_INUSE_MASK | 0x02)
#define XDE_TYPE_VOL_LABEL (XDE_TYPE_INUSE_MASK | 0x03)
#define XDE_TYPE_FILE (XDE_TYPE_INUSE_MASK | 0x05)
#define XDE_TYPE_VOL_GUID (XDE_TYPE_INUSE_MASK | XDE_TYPE_IMPORTNC_MASK)
#define XDE_TYPE_STREAM_EXT (XDE_TYPE_INUSE_MASK | XDE_TYPE_CATEGORY_MASK)
#define XDE_TYPE_FILE_NAME (XDE_TYPE_INUSE_MASK | XDE_TYPE_CATEGORY_MASK | 0x01)
#define XDE_TYPE_VENDOR (XDE_TYPE_INUSE_MASK | XDE_TYPE_CATEGORY_MASK | XDE_TYPE_IMPORTNC_MASK)
#define XDE_TYPE_VENDOR_ALLOC (XDE_TYPE_INUSE_MASK | XDE_TYPE_CATEGORY_MASK | XDE_TYPE_IMPORTNC_MASK | 0x01)
union {
uint8_t xde_generic_[19];
struct exde_primary {
/*
* Count of "secondary" dirents following this one.
*
* A single logical entity may be composed of a
* sequence of several dirents, starting with a primary
* one; the rest are secondary dirents.
*/
uint8_t xde_secondary_count_;
uint16_t xde_set_chksum_;
uint16_t xde_prim_flags_;
uint8_t xde_prim_generic_[14];
} __packed xde_primary_;
struct exde_secondary {
uint8_t xde_sec_flags_;
uint8_t xde_sec_generic_[18];
} __packed xde_secondary_;
} u;
uint32_t xde_first_cluster;
uint64_t xde_data_len;
};
#define xde_generic u.xde_generic_
#define xde_secondary_count u.xde_primary_.xde_secondary_count
#define xde_set_chksum u.xde_primary_.xde_set_chksum_
#define xde_prim_flags u.xde_primary_.xde_prim_flags_
#define xde_sec_flags u.xde_secondary_.xde_sec_flags_
_Static_assert(sizeof(struct exfat_dirent) == 32, "spec");
struct exfat_de_label {
uint8_t xdel_type; /* XDE_TYPE_VOL_LABEL */
uint8_t xdel_char_cnt; /* Length of UCS-2 label */
uint16_t xdel_vol_lbl[11];
uint8_t xdel_reserved[8];
};
_Static_assert(sizeof(struct exfat_de_label) == 32, "spec");
#define MAIN_BOOT_REGION_SECT 0
#define BACKUP_BOOT_REGION_SECT 12
#define SUBREGION_CHKSUM_SECT 11
#define FIRST_CLUSTER 2
#define BAD_BLOCK_SENTINEL 0xfffffff7u
#define END_CLUSTER_SENTINEL 0xffffffffu
static inline void *
read_sectn(FILE *fp, off_t sect, unsigned count, unsigned bytespersec)
{
return (read_buf(fp, sect * bytespersec, bytespersec * count));
}
static inline void *
read_sect(FILE *fp, off_t sect, unsigned bytespersec)
{
return (read_sectn(fp, sect, 1, bytespersec));
}
/*
* Compute the byte-by-byte multi-sector checksum of the given boot region
* (MAIN or BACKUP), for a given bytespersec (typically 512 or 4096).
*
* Endian-safe; result is host endian.
*/
static int
exfat_compute_boot_chksum(FILE *fp, unsigned region, unsigned bytespersec,
uint32_t *result)
{
unsigned char *sector;
unsigned n, sect;
uint32_t checksum;
checksum = 0;
for (sect = 0; sect < 11; sect++) {
sector = read_sect(fp, region + sect, bytespersec);
if (sector == NULL)
return (ENXIO);
for (n = 0; n < bytespersec; n++) {
if (sect == 0) {
switch (n) {
case 106:
case 107:
case 112:
continue;
}
}
checksum = ((checksum & 1) ? 0x80000000u : 0u) +
(checksum >> 1) + (uint32_t)sector[n];
}
free(sector);
}
*result = checksum;
return (0);
}
#ifdef WITH_ICONV
static void
convert_label(const uint16_t *ucs2label /* LE */, unsigned ucs2len, char
*label_out, size_t label_sz)
{
const char *label;
char *label_out_orig;
iconv_t cd;
size_t srcleft, rc;
/* Currently hardcoded in fstyp.c as 256 or so. */
assert(label_sz > 1);
if (ucs2len == 0) {
/*
* Kind of seems bogus, but the spec allows an empty label
* entry with the same meaning as no label.
*/
return;
}
if (ucs2len > 11) {
warnx("exfat: Bogus volume label length: %u", ucs2len);
return;
}
/* dstname="" means convert to the current locale. */
cd = iconv_open("", EXFAT_ENC);
if (cd == (iconv_t)-1) {
warn("exfat: Could not open iconv");
return;
}
label_out_orig = label_out;
/* Dummy up the byte pointer and byte length iconv's API wants. */
label = (const void *)ucs2label;
srcleft = ucs2len * sizeof(*ucs2label);
rc = iconv(cd, __DECONST(char **, &label), &srcleft, &label_out,
&label_sz);
if (rc == (size_t)-1) {
warn("exfat: iconv()");
*label_out_orig = '\0';
} else {
/* NUL-terminate result (iconv advances label_out). */
if (label_sz == 0)
label_out--;
*label_out = '\0';
}
iconv_close(cd);
}
/*
* Using the FAT table, look up the next cluster in this chain.
*/
static uint32_t
exfat_fat_next(FILE *fp, const struct exfat_vbr *ev, unsigned BPS,
uint32_t cluster)
{
uint32_t fat_offset_sect, clsect, clsectoff;
uint32_t *fatsect, nextclust;
fat_offset_sect = le32toh(ev->ev_fat_offset);
clsect = fat_offset_sect + (cluster / (BPS / sizeof(cluster)));
clsectoff = (cluster % (BPS / sizeof(cluster)));
/* XXX This is pretty wasteful without a block cache for the FAT. */
fatsect = read_sect(fp, clsect, BPS);
nextclust = le32toh(fatsect[clsectoff]);
free(fatsect);
return (nextclust);
}
static void
exfat_find_label(FILE *fp, const struct exfat_vbr *ev, unsigned BPS,
char *label_out, size_t label_sz)
{
uint32_t rootdir_cluster, sects_per_clust, cluster_offset_sect;
off_t rootdir_sect;
struct exfat_dirent *declust, *it;
cluster_offset_sect = le32toh(ev->ev_cluster_offset);
rootdir_cluster = le32toh(ev->ev_rootdir_cluster);
sects_per_clust = (1u << ev->ev_log_sect_per_clust);
if (rootdir_cluster < FIRST_CLUSTER) {
warnx("%s: invalid rootdir cluster %u < %d", __func__,
rootdir_cluster, FIRST_CLUSTER);
return;
}
for (; rootdir_cluster != END_CLUSTER_SENTINEL;
rootdir_cluster = exfat_fat_next(fp, ev, BPS, rootdir_cluster)) {
if (rootdir_cluster == BAD_BLOCK_SENTINEL) {
warnx("%s: Bogus bad block in root directory chain",
__func__);
return;
}
rootdir_sect = (rootdir_cluster - FIRST_CLUSTER) *
sects_per_clust + cluster_offset_sect;
declust = read_sectn(fp, rootdir_sect, sects_per_clust, BPS);
for (it = declust;
it < declust + (sects_per_clust * BPS / sizeof(*it)); it++) {
bool eod = false;
/*
* Simplistic directory traversal; doesn't do any
* validation of "MUST" requirements in spec.
*/
switch (it->xde_type) {
case XDE_TYPE_EOD:
eod = true;
break;
case XDE_TYPE_VOL_LABEL: {
struct exfat_de_label *lde = (void*)it;
convert_label(lde->xdel_vol_lbl,
lde->xdel_char_cnt, label_out, label_sz);
free(declust);
return;
}
}
if (eod)
break;
}
free(declust);
}
}
#endif /* WITH_ICONV */
int
fstyp_exfat(FILE *fp, char *label, size_t size)
{
struct exfat_vbr *ev;
uint32_t *cksect;
unsigned bytespersec;
uint32_t chksum;
int error;
error = 1;
cksect = NULL;
ev = (struct exfat_vbr *)read_buf(fp, 0, 512);
if (ev == NULL || strncmp(ev->ev_fsname, "EXFAT ", 8) != 0)
goto out;
if (ev->ev_log_bytes_per_sect < 9 || ev->ev_log_bytes_per_sect > 12) {
warnx("exfat: Invalid BytesPerSectorShift");
goto out;
}
bytespersec = (1u << ev->ev_log_bytes_per_sect);
error = exfat_compute_boot_chksum(fp, MAIN_BOOT_REGION_SECT,
bytespersec, &chksum);
if (error != 0)
goto out;
cksect = read_sect(fp, MAIN_BOOT_REGION_SECT + SUBREGION_CHKSUM_SECT,
bytespersec);
/*
* Technically the entire sector should be full of repeating 4-byte
* checksum pattern, but we only verify the first.
*/
if (chksum != le32toh(cksect[0])) {
warnx("exfat: Found checksum 0x%08x != computed 0x%08x",
le32toh(cksect[0]), chksum);
error = 1;
goto out;
}
#ifdef WITH_ICONV
if (show_label)
exfat_find_label(fp, ev, bytespersec, label, size);
#else
if (show_label) {
warnx("label not available without iconv support");
memset(label, 0, size);
}
#endif
out:
free(cksect);
free(ev);
return (error != 0);
}
|