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