|
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); |