Module Name: src
Committed By: tkusumi
Date: Sat Dec 28 08:00:08 UTC 2019
Modified Files:
src/usr.sbin/fstyp: exfat.c fstyp.c fstyp.h
Log Message:
fstyp: Show exFAT volume labels with -l flag
taken-from: FreeBSD (freebsd/freebsd@73773fcda9f69ce7ee0c73292f273bab940223bf)
To generate a diff of this commit:
cvs rdiff -u -r1.1 -r1.2 src/usr.sbin/fstyp/exfat.c
cvs rdiff -u -r1.5 -r1.6 src/usr.sbin/fstyp/fstyp.c
cvs rdiff -u -r1.4 -r1.5 src/usr.sbin/fstyp/fstyp.h
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/usr.sbin/fstyp/exfat.c
diff -u src/usr.sbin/fstyp/exfat.c:1.1 src/usr.sbin/fstyp/exfat.c:1.2
--- src/usr.sbin/fstyp/exfat.c:1.1 Mon Nov 18 14:53:34 2019
+++ src/usr.sbin/fstyp/exfat.c Sat Dec 28 08:00:08 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: exfat.c,v 1.1 2019/11/18 14:53:34 tkusumi Exp $ */
+/* $NetBSD: exfat.c,v 1.2 2019/12/28 08:00:08 tkusumi Exp $ */
/*
* Copyright (c) 2017 Conrad Meyer <[email protected]>
@@ -26,8 +26,16 @@
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
-__RCSID("$NetBSD: exfat.c,v 1.1 2019/11/18 14:53:34 tkusumi Exp $");
+__RCSID("$NetBSD: exfat.c,v 1.2 2019/12/28 08:00:08 tkusumi Exp $");
+#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>
@@ -35,6 +43,10 @@ __RCSID("$NetBSD: exfat.c,v 1.1 2019/11/
#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];
@@ -56,19 +68,301 @@ struct exfat_vbr {
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;
+} __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 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, __UNCONST(&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 / (uint32_t)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;
+ }
+
+ if (show_label)
+ exfat_find_label(fp, ev, bytespersec, label, size);
+
+done:
+ free(cksect);
free(ev);
return (0);
Index: src/usr.sbin/fstyp/fstyp.c
diff -u src/usr.sbin/fstyp/fstyp.c:1.5 src/usr.sbin/fstyp/fstyp.c:1.6
--- src/usr.sbin/fstyp/fstyp.c:1.5 Fri Dec 27 11:15:06 2019
+++ src/usr.sbin/fstyp/fstyp.c Sat Dec 28 08:00:08 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: fstyp.c,v 1.5 2019/12/27 11:15:06 tkusumi Exp $ */
+/* $NetBSD: fstyp.c,v 1.6 2019/12/28 08:00:08 tkusumi Exp $ */
/*-
* Copyright (c) 2017 The NetBSD Foundation, Inc.
@@ -35,7 +35,7 @@
*
*/
#include <sys/cdefs.h>
-__RCSID("$NetBSD: fstyp.c,v 1.5 2019/12/27 11:15:06 tkusumi Exp $");
+__RCSID("$NetBSD: fstyp.c,v 1.6 2019/12/28 08:00:08 tkusumi Exp $");
#include <sys/disklabel.h>
#include <sys/dkio.h>
@@ -43,6 +43,8 @@ __RCSID("$NetBSD: fstyp.c,v 1.5 2019/12/
#include <sys/stat.h>
#include <err.h>
#include <errno.h>
+#include <iconv.h>
+#include <locale.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
@@ -55,6 +57,8 @@ __RCSID("$NetBSD: fstyp.c,v 1.5 2019/12/
#define LABEL_LEN 256
+bool show_label = false;
+
typedef int (*fstyp_function)(FILE *, char *, size_t);
typedef int (*fsvtyp_function)(const char *, char *, size_t);
@@ -62,19 +66,20 @@ static struct {
const char *name;
fstyp_function function;
bool unmountable;
+ const char *precache_encoding;
} fstypes[] = {
- { "apfs", &fstyp_apfs, true },
- { "cd9660", &fstyp_cd9660, false },
- { "exfat", &fstyp_exfat, false },
- { "ext2fs", &fstyp_ext2fs, false },
- { "hfs+", &fstyp_hfsp, false },
- { "msdosfs", &fstyp_msdosfs, false },
- { "ntfs", &fstyp_ntfs, false },
- { "ufs", &fstyp_ufs, false },
+ { "apfs", &fstyp_apfs, true, NULL },
+ { "cd9660", &fstyp_cd9660, false, NULL },
+ { "exfat", &fstyp_exfat, false, EXFAT_ENC },
+ { "ext2fs", &fstyp_ext2fs, false, NULL },
+ { "hfs+", &fstyp_hfsp, false, 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 }
};
static struct {
@@ -173,7 +178,7 @@ int
main(int argc, char **argv)
{
int ch, error, i, nbytes;
- bool ignore_type = false, show_label = false, show_unmountable = false;
+ bool ignore_type = false, show_unmountable = false;
char label[LABEL_LEN + 1], strvised[LABEL_LEN * 4 + 1];
char *path;
const char *name = NULL;
@@ -204,6 +209,25 @@ main(int argc, char **argv)
path = argv[0];
+ if (setlocale(LC_CTYPE, "") == NULL)
+ err(1, "setlocale");
+
+ /* Cache iconv conversion data before entering capability mode. */
+ if (show_label) {
+ for (i = 0; i < (int)__arraycount(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)
goto fsvtyp; /* DragonFly */
Index: src/usr.sbin/fstyp/fstyp.h
diff -u src/usr.sbin/fstyp/fstyp.h:1.4 src/usr.sbin/fstyp/fstyp.h:1.5
--- src/usr.sbin/fstyp/fstyp.h:1.4 Fri Dec 27 11:15:06 2019
+++ src/usr.sbin/fstyp/fstyp.h Sat Dec 28 08:00:08 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: fstyp.h,v 1.4 2019/12/27 11:15:06 tkusumi Exp $ */
+/* $NetBSD: fstyp.h,v 1.5 2019/12/28 08:00:08 tkusumi Exp $ */
/*-
* Copyright (c) 2017 The NetBSD Foundation, Inc.
@@ -39,6 +39,13 @@
#ifndef FSTYP_H
#define FSTYP_H
+#include <stdbool.h>
+
+/* The spec doesn't seem to permit UTF-16 surrogates; definitely LE. */
+#define EXFAT_ENC "UCS-2LE"
+
+extern bool show_label; /* -l flag */
+
void *read_buf(FILE *, off_t, size_t);
char *checked_strdup(const char *);
void rtrim(char *, size_t);