FreeBSD Bugzilla – Attachment 209496 Details for
Bug 242225
"fstyp -l" does not report exfat filesystem volume label
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Fixes the bug and adds matching test case, v2
fstyp_exfat_label.patch (text/plain), 13.75 KB, created by
Conrad Meyer
on 2019-11-27 20:56:28 UTC
(
hide
)
Description:
Fixes the bug and adds matching test case, v2
Filename:
MIME Type:
Creator:
Conrad Meyer
Created:
2019-11-27 20:56:28 UTC
Size:
13.75 KB
patch
obsolete
>From 1d9cf71b6a4ebbe60e8997302d63f3eccb7877d8 Mon Sep 17 00:00:00 2001 >From: Conrad Meyer <cem@FreeBSD.org> >Date: Wed, 27 Nov 2019 12:40:18 -0800 >Subject: [PATCH] fstyp(8): Read exFAT volume labels > >The superblock is slightly different from FAT32 and the dirent structure is >a little different, but basically it's the same process: find the root >directory data cluster, walk it (including chasing the FAT chain for the >next data cluster if needed) until we find the special dirent indicating a >volume label, and then return it to the generic fstyp code. > >Unlike msdosfs, exFAT labels are UCS-2LE, so this change uses iconv to >convert it to the user's locale. > >It isn't clear to me if any of it is covered by the OIN (previously: >Microsoft) patents on exFAT, as I have no familiarity with them. >If one squints a little and considers 'fstyp' a Linux Component (OIN term), >the patent coverage might not be an issue. (fstyp could easily run on a >Linux System with a Linux Kernel.) >--- > usr.sbin/fstyp/exfat.c | 290 ++++++++++++++++++++++++++++- > usr.sbin/fstyp/fstyp.c | 41 +++- > usr.sbin/fstyp/fstyp.h | 2 + > usr.sbin/fstyp/tests/fstyp_test.sh | 10 + > 4 files changed, 332 insertions(+), 11 deletions(-) > >diff --git a/usr.sbin/fstyp/exfat.c b/usr.sbin/fstyp/exfat.c >index 9c379df0c3f6..d2bbc53e4654 100644 >--- a/usr.sbin/fstyp/exfat.c >+++ b/usr.sbin/fstyp/exfat.c >@@ -25,17 +25,29 @@ > */ > > #include <sys/cdefs.h> > __FBSDID("$FreeBSD$"); > >+#include <sys/param.h> >+#include <sys/endian.h> >+ >+#include <assert.h> >+#include <err.h> >+#include <errno.h> >+#include <iconv.h> >+#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; >@@ -53,23 +65,297 @@ struct exfat_vbr { > 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 { >+ // XXX: count of following dirents, "secondary" >+ 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; >+} __packed; >+#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]; >+} __packed; >+_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); >+} >+ >+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 fstype.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); >+ } >+} >+ > int > fstyp_exfat(FILE *fp, char *label, size_t size) > { > struct exfat_vbr *ev; >+ uint32_t *cksect; >+ unsigned bytespersec; >+ uint32_t chksum; >+ int error; > >+ cksect = NULL; > ev = (struct exfat_vbr *)read_buf(fp, 0, 512); > if (ev == NULL || strncmp(ev->ev_fsname, "EXFAT ", 8) != 0) > goto fail; > >+ if (ev->ev_log_bytes_per_sect < 9 || ev->ev_log_bytes_per_sect > 12) { >+ warnx("exfat: Invalid BytesPerSectorShift"); >+ goto done; >+ } >+ >+ bytespersec = (1u << ev->ev_log_bytes_per_sect); >+ >+ error = exfat_compute_boot_chksum(fp, MAIN_BOOT_REGION_SECT, >+ bytespersec, &chksum); >+ if (error != 0) >+ goto done; >+ >+ cksect = read_sect(fp, MAIN_BOOT_REGION_SECT + SUBREGION_CHKSUM_SECT, >+ bytespersec); >+ > /* >- * Reading the volume label requires walking the root directory to look >- * for a special label file. Left as an exercise for the reader. >+ * 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); >+ goto done; >+ } >+ >+ exfat_find_label(fp, ev, bytespersec, label, size); >+ >+done: >+ free(cksect); > free(ev); > return (0); > > fail: > free(ev); >diff --git a/usr.sbin/fstyp/fstyp.c b/usr.sbin/fstyp/fstyp.c >index 31f7b34bde40..dabaf1c88bb2 100644 >--- a/usr.sbin/fstyp/fstyp.c >+++ b/usr.sbin/fstyp/fstyp.c >@@ -36,10 +36,12 @@ __FBSDID("$FreeBSD$"); > #include <sys/ioctl.h> > #include <sys/stat.h> > #include <capsicum_helpers.h> > #include <err.h> > #include <errno.h> >+#include <iconv.h> >+#include <locale.h> > #include <stdbool.h> > #include <stddef.h> > #include <stdio.h> > #include <stdlib.h> > #include <string.h> >@@ -54,22 +56,23 @@ typedef int (*fstyp_function)(FILE *, char *, size_t); > > static struct { > const char *name; > fstyp_function function; > bool unmountable; >+ char *precache_encoding; > } fstypes[] = { >- { "cd9660", &fstyp_cd9660, false }, >- { "exfat", &fstyp_exfat, false }, >- { "ext2fs", &fstyp_ext2fs, false }, >- { "geli", &fstyp_geli, true }, >- { "msdosfs", &fstyp_msdosfs, false }, >- { "ntfs", &fstyp_ntfs, false }, >- { "ufs", &fstyp_ufs, false }, >+ { "cd9660", &fstyp_cd9660, false, NULL }, >+ { "exfat", &fstyp_exfat, false, EXFAT_ENC }, >+ { "ext2fs", &fstyp_ext2fs, false, NULL }, >+ { "geli", &fstyp_geli, true, NULL }, >+ { "msdosfs", &fstyp_msdosfs, false, NULL }, >+ { "ntfs", &fstyp_ntfs, false, NULL }, >+ { "ufs", &fstyp_ufs, false, NULL }, > #ifdef HAVE_ZFS >- { "zfs", &fstyp_zfs, true }, >+ { "zfs", &fstyp_zfs, true, NULL }, > #endif >- { NULL, NULL, NULL } >+ { NULL, NULL, NULL, NULL } > }; > > void * > read_buf(FILE *fp, off_t off, size_t len) > { >@@ -186,10 +189,30 @@ main(int argc, char **argv) > if (argc != 1) > usage(); > > path = argv[0]; > >+ if (setlocale(LC_CTYPE, "") == NULL) >+ err(1, "setlocale"); >+ caph_cache_catpages(); >+ >+ /* Cache iconv conversion data before entering capability mode. */ >+ if (show_label) { >+ for (i = 0; i < nitems(fstypes); i++) { >+ iconv_t cd; >+ >+ if (fstypes[i].precache_encoding == NULL) >+ continue; >+ cd = iconv_open("", fstypes[i].precache_encoding); >+ if (cd == (iconv_t)-1) >+ err(1, "%s: iconv_open %s", fstypes[i].name, >+ fstypes[i].precache_encoding); >+ /* Iconv keeps a small cache of unused encodings. */ >+ iconv_close(cd); >+ } >+ } >+ > fp = fopen(path, "r"); > if (fp == NULL) > err(1, "%s", path); > > if (caph_enter() < 0) >diff --git a/usr.sbin/fstyp/fstyp.h b/usr.sbin/fstyp/fstyp.h >index f858b0c53827..b3be13235d22 100644 >--- a/usr.sbin/fstyp/fstyp.h >+++ b/usr.sbin/fstyp/fstyp.h >@@ -31,10 +31,12 @@ > > #ifndef FSTYP_H > #define FSTYP_H > > #define MIN(a,b) (((a)<(b))?(a):(b)) >+/* The spec doesn't seem to permit UTF-16 surrogates. Definitely LE. */ >+#define EXFAT_ENC "UCS-2LE" > > void *read_buf(FILE *fp, off_t off, size_t len); > char *checked_strdup(const char *s); > void rtrim(char *label, size_t size); > >diff --git a/usr.sbin/fstyp/tests/fstyp_test.sh b/usr.sbin/fstyp/tests/fstyp_test.sh >index 5103ba042746..8f76424f5f75 100755 >--- a/usr.sbin/fstyp/tests/fstyp_test.sh >+++ b/usr.sbin/fstyp/tests/fstyp_test.sh >@@ -66,10 +66,19 @@ exfat_head() { > exfat_body() { > bzcat $(atf_get_srcdir)/dfr-01-xfat.img.bz2 > exfat.img > atf_check -s exit:0 -o inline:"exfat\n" fstyp -u exfat.img > } > >+atf_test_case exfat_label >+exfat_label_head() { >+ atf_set "descr" "fstyp(8) can read exFAT labels" >+} >+exfat_label_body() { >+ bzcat $(atf_get_srcdir)/dfr-01-xfat.img.bz2 > exfat.img >+ atf_check -s exit:0 -o inline:"exfat exFat\n" fstyp -u -l exfat.img >+} >+ > atf_test_case empty > empty_head() { > atf_set "descr" "fstyp(8) should fail on an empty file" > } > empty_body() { >@@ -251,10 +260,11 @@ atf_init_test_cases() { > atf_add_test_case cd9660 > atf_add_test_case cd9660_label > atf_add_test_case dir > atf_add_test_case empty > atf_add_test_case exfat >+ atf_add_test_case exfat_label > atf_add_test_case ext2 > atf_add_test_case ext3 > atf_add_test_case ext4 > atf_add_test_case ext4_label > atf_add_test_case fat12 >-- >2.23.0 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 242225
:
209495
| 209496