Index: sys/conf/options =================================================================== --- sys/conf/options +++ sys/conf/options @@ -282,6 +282,9 @@ MSDOSFS_ICONV opt_dontuse.h UDF_ICONV opt_dontuse.h +# Build exFAT support in msdosfs. +MSDOS_EXFAT opt_global.h + # If you are following the conditions in the copyright, # you can enable soft-updates which will speed up a lot of thigs # and make the system safer from crashes at the same time. Index: sys/fs/msdosfs/bootsect.h =================================================================== --- sys/fs/msdosfs/bootsect.h +++ sys/fs/msdosfs/bootsect.h @@ -70,10 +70,42 @@ #define BOOTSIG1 0xaa }; +/* The Volume Boot Record format for exFAT. */ +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; + uint8_t ev_fs_revision_minor; + uint8_t ev_fs_revision_major; +#define XF_VF_ACTIVEFAT 0x1 +#define XF_VF_VOLDIRTY 0x2 +#define XF_VF_MEDIAFAIL 0x4 +#define XF_VF_CLZERO 0x8 + 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; + uint8_t ev_reserved[7]; + uint8_t ev_bootcode[390]; + uint8_t ev_bootsign[2]; +} __packed; +_Static_assert(offsetof(struct exfat_vbr, ev_bootsign) == 510, ""); + union bootsector { struct bootsector33 bs33; struct bootsector50 bs50; struct bootsector710 bs710; + struct exfat_vbr bsxf; }; #if 0 Index: sys/fs/msdosfs/denode.h =================================================================== --- sys/fs/msdosfs/denode.h +++ sys/fs/msdosfs/denode.h @@ -142,10 +142,10 @@ */ struct denode { struct vnode *de_vnode; /* addr of vnode we are part of */ - u_long de_flag; /* flag bits */ - u_long de_dirclust; /* cluster of the directory file containing this entry */ - u_long de_diroffset; /* offset of this entry in the directory cluster */ - u_long de_fndoffset; /* offset of found dir entry */ + uint64_t de_flag; /* flag bits */ + uint64_t de_dirclust; /* cluster of the directory file containing this entry */ + uint64_t de_diroffset; /* offset of this entry in the directory cluster */ + uint64_t de_fndoffset; /* offset of found dir entry */ int de_fndcnt; /* number of slots before de_fndoffset */ long de_refcnt; /* reference count */ struct msdosfsmount *de_pmp; /* addr of our mount struct */ @@ -158,11 +158,14 @@ u_short de_ADate; /* access date */ u_short de_MTime; /* modification time */ u_short de_MDate; /* modification date */ - u_long de_StartCluster; /* starting cluster of file */ - u_long de_FileSize; /* size of file in bytes */ + uint64_t de_StartCluster; /* starting cluster of file */ + uint64_t de_FileSize; /* size of file in bytes */ struct fatcache de_fc[FC_SIZE]; /* FAT cache */ u_quad_t de_modrev; /* Revision level for lease. */ uint64_t de_inode; /* Inode number (really byte offset of direntry) */ + + uint64_t de_AllocatedSize; /* exFAT: allocated size */ + bool de_Contiguous; /* exFAT: allocation is contiguous ("NoFatChain"). */ }; /* @@ -182,22 +185,7 @@ * dep is a struct denode * (internal form), * dp is a struct direntry * (external form). */ -#define DE_INTERNALIZE32(dep, dp) \ - ((dep)->de_StartCluster |= getushort((dp)->deHighClust) << 16) -#define DE_INTERNALIZE(dep, dp) \ - (memcpy((dep)->de_Name, (dp)->deName, 11), \ - (dep)->de_Attributes = (dp)->deAttributes, \ - (dep)->de_LowerCase = (dp)->deLowerCase, \ - (dep)->de_CHun = (dp)->deCHundredth, \ - (dep)->de_CTime = getushort((dp)->deCTime), \ - (dep)->de_CDate = getushort((dp)->deCDate), \ - (dep)->de_ADate = getushort((dp)->deADate), \ - (dep)->de_MTime = getushort((dp)->deMTime), \ - (dep)->de_MDate = getushort((dp)->deMDate), \ - (dep)->de_StartCluster = getushort((dp)->deStartCluster), \ - (dep)->de_FileSize = getulong((dp)->deFileSize), \ - (FAT32((dep)->de_pmp) ? DE_INTERNALIZE32((dep), (dp)) : 0)) - +/* XXX */ #define DE_EXTERNALIZE(dp, dep) \ (memcpy((dp)->deName, (dep)->de_Name, 11), \ (dp)->deAttributes = (dep)->de_Attributes, \ @@ -275,6 +263,7 @@ int uniqdosname(struct denode *, struct componentname *, u_char *); int readep(struct msdosfsmount *pmp, u_long dirclu, u_long dirofs, struct buf **bpp, struct direntry **epp); +int readexfatep(struct msdosfsmount *pmp, u_long dirclu, u_long dirofs, struct denode *dep); int readde(struct denode *dep, struct buf **bpp, struct direntry **epp); int deextend(struct denode *dep, u_long length, struct ucred *cred); int fillinusemap(struct msdosfsmount *pmp); Index: sys/fs/msdosfs/direntry.h =================================================================== --- sys/fs/msdosfs/direntry.h +++ sys/fs/msdosfs/direntry.h @@ -99,11 +99,16 @@ uint8_t wePart3[4]; }; #define WIN_CHARS 13 /* Number of chars per winentry */ +#define EXFAT_CHARS 15 /* Number of chars per exfat_de_filename */ /* * Maximum number of winentries for a filename. */ #define WIN_MAXSUBENTRIES 20 +/* + * Same, but for exFAT FILE_NAME entries (15 chars/entry). + */ +#define EXFAT_MAXSUBENTRIES 17 /* * Maximum filename length in Win95 @@ -111,6 +116,119 @@ */ #define WIN_MAXLEN 255 +/* + * Same, for exFAT. Like windows, it is exactly 255 UCS2LE characters. Unlike + * the constant above, no relationship with sizeof(dirent.d_name). + */ +#define EXFAT_MAXNAMLEN 255 + +/* + * Structure of an exFAT long name directory entry + */ +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"); + +/* xde_prim_flags/xde_sec_flags/xdes_flags */ +#define XDE_FLAG_ALLOCATION_POSSIBLE (1u << 0) +#define XDE_FLAG_NOFATCHAIN (1u << 1) + +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"); + +struct exfat_de_file { + uint8_t xdef_type; /* XDE_TYPE_FILE */ + uint8_t xdef_secondary_count; + uint16_t xdef_set_chksum; +#define EXFAT_VALID_ATTR \ + (ATTR_READONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_DIRECTORY | ATTR_ARCHIVE) + uint16_t xdef_attributes; + uint16_t xdef_reserved1; + uint32_t xdef_ctime; + uint32_t xdef_mtime; + uint32_t xdef_atime; + uint8_t xdef_ctime_frac; /* sub-second ctime, 10ms units */ + uint8_t xdef_mtime_frac; + uint8_t xdef_ctime_offset; /* ctime offset from UTC */ + uint8_t xdef_mtime_offset; + uint8_t xdef_atime_offset; + uint8_t xdef_reserved2[7]; +} __packed; +_Static_assert(sizeof(struct exfat_de_file) == 32, "spec"); + +struct exfat_de_stream { + uint8_t xdes_type; /* XDE_TYPE_STREAM_EXT */ + uint8_t xdes_flags; + uint8_t xdes_reserved1; + uint8_t xdes_namelen; + uint16_t xdes_namehash; + uint16_t xdes_reserved2; + uint64_t xdes_validdatalen; /* True file/directory len. */ + uint32_t xdes_reserved3; + uint32_t xdes_firstcluster; + /* exFAT supports preallocated data files. */ + uint64_t xdes_allocateddatalen; /* Allocated disk space. */ +} __packed; +_Static_assert(sizeof(struct exfat_de_stream) == 32, "spec"); + +struct exfat_de_filename { + uint8_t xdefn_type; /* XDE_TYPE_FILE_NAME */ + uint8_t xdefn_flags; /* GeneralSecondaryFlags */ + uint16_t xdefn_filename[15]; /* UCS2-LE */ +} __packed; +_Static_assert(sizeof(struct exfat_de_filename) == 32, "spec"); + /* * This is the format of the contents of the deTime field in the direntry * structure. @@ -148,6 +266,11 @@ char *mbnambuf_flush(struct mbnambuf *nbp, struct dirent *dp); void mbnambuf_init(struct mbnambuf *nbp); int mbnambuf_write(struct mbnambuf *nbp, char *name, int id); +int msdosfs_cnp_to_ucs2(struct msdosfsmount *pmp, uint16_t *ucs2name, + size_t ucs2len, const struct componentname *cnp); +int msdosfs_ucs2_to_dirent(struct msdosfsmount *pmp, struct dirent *dirbuf, + uint16_t *wcnambuf); +size_t msdosfs_ucs2len(uint16_t *ucs2name); int dos2unixfn(u_char dn[11], u_char *un, int lower, struct msdosfsmount *pmp); int unix2dosfn(const u_char *un, u_char dn[12], size_t unlen, u_int gen, Index: sys/fs/msdosfs/fat.h =================================================================== --- sys/fs/msdosfs/fat.h +++ sys/fs/msdosfs/fat.h @@ -67,6 +67,16 @@ #define FAT12_MASK 0x00000fff /* mask for 12 bit cluster numbers */ #define FAT16_MASK 0x0000ffff /* mask for 16 bit cluster numbers */ #define FAT32_MASK 0x0fffffff /* mask for FAT32 cluster numbers */ +#define EXFAT_MASK 0xffffffff /* exFAT uses full uint32 space. */ + +/* + * Magic sector numbers. + */ +#define EXFAT_SECT_MAIN_BOOTREGION 0 +#define EXFAT_SECT_BKUP_BOOTREGION 12 + +/* Offsets inside a boot region. */ +#define EXFAT_BOOTREG_OFFSET_CHKSUM 11 /* * MSDOSFS: @@ -79,6 +89,11 @@ #define FAT12(pmp) (pmp->pm_fatmask == FAT12_MASK) #define FAT16(pmp) (pmp->pm_fatmask == FAT16_MASK) #define FAT32(pmp) (pmp->pm_fatmask == FAT32_MASK) +#ifdef MSDOS_EXFAT +#define EXFAT(pmp) (pmp->pm_fatmask == EXFAT_MASK) +#else +#define EXFAT(pmp) (false) +#endif #define MSDOSFSEOF(pmp, cn) ((((cn) | ~(pmp)->pm_fatmask) & CLUST_EOFS) == CLUST_EOFS) Index: sys/fs/msdosfs/msdosfs_conv.c =================================================================== --- sys/fs/msdosfs/msdosfs_conv.c +++ sys/fs/msdosfs/msdosfs_conv.c @@ -55,6 +55,7 @@ #include #include #include +#include #include #include @@ -510,6 +511,54 @@ return conv; } +/* + * Convert a unix name to a UCS2-LE exFAT ucs2name (buffer size ucs2len in + * units of uint16_t, including trailing NUL). + */ +static int +msdosfs_unix_to_ucs2(struct msdosfsmount *pmp, uint16_t *ucs2name, + size_t ucs2len, const char *un, size_t unlen) +{ + size_t wlen; + char *wnp; + + if (pmp->pm_flags & MSDOSFSMNT_KICONV && msdosfs_iconv) { + wlen = (ucs2len - 1) * 2; + wnp = (void *)ucs2name; + msdosfs_iconv->conv(pmp->pm_u2w, &un, &unlen, &wnp, &wlen); + if (unlen > 0) + return (ENAMETOOLONG); + ucs2name[wlen/2] = 0; + return (0); + } + + if (unlen > ucs2len - 1) + return (ENAMETOOLONG); + /* Parity with win2unixchr(). */ + for (; unlen > 0; un++, unlen--) + *ucs2name++ = htole16(*un); + *ucs2name = 0; + return (0); +} + +int +msdosfs_cnp_to_ucs2(struct msdosfsmount *pmp, uint16_t *ucs2name, + size_t ucs2len, const struct componentname *cnp) +{ + return (msdosfs_unix_to_ucs2(pmp, ucs2name, ucs2len, cnp->cn_nameptr, + cnp->cn_namelen)); +} + +size_t +msdosfs_ucs2len(uint16_t *ucs2name) +{ + size_t i; + + for (i = 0; *ucs2name++ != 0; i++) + ; + return (i); +} + /* * Create a Win95 long name directory entry * Note: assumes that the filename is valid, @@ -720,6 +769,48 @@ return chksum; } +/* + * Convert a NUL-terminated UCS2-LE string in wcnambuf to d_name and d_namlen + * fields of dirbuf. + */ +int +msdosfs_ucs2_to_dirent(struct msdosfsmount *pmp, struct dirent *dirbuf, + uint16_t *wcnambuf) +{ + u_char tmpbuf[5]; /* MB_LEN_MAX + \0 */ + uint16_t code; + unsigned db_i, rem, convlen; + int error; + + db_i = 0; + error = 0; + rem = sizeof(dirbuf->d_name) - 1; + + for (; *wcnambuf != 0; wcnambuf++) { + code = le16toh(*wcnambuf); + + if (code == '/') { + error = EINTEGRITY; + break; + } + + win2unixchr(tmpbuf, code, pmp); + convlen = strlen(tmpbuf); + if (convlen > rem) { + error = ENAMETOOLONG; + break; + } + + memcpy(&dirbuf->d_name[db_i], tmpbuf, convlen); + db_i += convlen; + rem -= convlen; + } + + dirbuf->d_namlen = db_i; + dirbuf->d_name[db_i] = '\0'; + return (error); +} + /* * Compute the unrolled checksum of a DOS filename for Win95 LFN use. */ Index: sys/fs/msdosfs/msdosfs_denode.c =================================================================== --- sys/fs/msdosfs/msdosfs_denode.c +++ sys/fs/msdosfs/msdosfs_denode.c @@ -82,6 +82,29 @@ return (de->de_inode != *a) || (de->de_refcnt <= 0); } +/* + * Convert "external" (on-disk) directory entries to internal (denode) form. + */ +static void +DE_INTERNALIZE(struct denode *dep, const struct direntry *dp) +{ + MPASS(!EXFAT(dep->de_pmp)); + + memcpy(dep->de_Name, dp->deName, 11); + dep->de_Attributes = dp->deAttributes; + dep->de_LowerCase = dp->deLowerCase; + dep->de_CHun = dp->deCHundredth; + dep->de_CTime = getushort(dp->deCTime); + dep->de_CDate = getushort(dp->deCDate); + dep->de_ADate = getushort(dp->deADate); + dep->de_MTime = getushort(dp->deMTime); + dep->de_MDate = getushort(dp->deMDate); + dep->de_StartCluster = getushort(dp->deStartCluster); + dep->de_FileSize = getulong(dp->deFileSize); + if (FAT32(dep->de_pmp)) + dep->de_StartCluster |= getushort(dp->deHighClust) << 16; +} + /* * If deget() succeeds it returns with the gotten denode locked(). * @@ -107,7 +130,7 @@ struct buf *bp; #ifdef MSDOSFS_DEBUG - printf("deget(pmp %p, dirclust %lu, diroffset %lx, depp %p)\n", + printf("deget(pmp %p, dirclust %lu, diroffset 0x%lx, depp %p)\n", pmp, dirclust, diroffset, depp); #endif @@ -115,7 +138,7 @@ * On FAT32 filesystems, root is a (more or less) normal * directory */ - if (FAT32(pmp) && dirclust == MSDOSFSROOT) + if ((FAT32(pmp) || EXFAT(pmp)) && dirclust == MSDOSFSROOT) dirclust = pmp->pm_rootdirblk; /* @@ -187,7 +210,7 @@ * Copy the directory entry into the denode area of the vnode. */ if ((dirclust == MSDOSFSROOT - || (FAT32(pmp) && dirclust == pmp->pm_rootdirblk)) + || ((FAT32(pmp) || EXFAT(pmp)) && dirclust == pmp->pm_rootdirblk)) && diroffset == MSDOSFSROOT_OFS) { /* * Directory entry for the root directory. There isn't one, @@ -200,7 +223,7 @@ ldep->de_Attributes = ATTR_DIRECTORY; ldep->de_LowerCase = 0; - if (FAT32(pmp)) + if (FAT32(pmp) || EXFAT(pmp)) ldep->de_StartCluster = pmp->pm_rootdirblk; /* de_FileSize will be filled in further down */ else { @@ -222,7 +245,11 @@ ldep->de_MDate = ldep->de_CDate; /* leave the other fields as garbage */ } else { - error = readep(pmp, dirclust, diroffset, &bp, &direntptr); + if (EXFAT(pmp)) + error = readexfatep(pmp, dirclust, diroffset, ldep); + else + error = readep(pmp, dirclust, diroffset, &bp, + &direntptr); if (error) { /* * The denode does not contain anything useful, so @@ -235,8 +262,10 @@ *depp = NULL; return (error); } - (void)DE_INTERNALIZE(ldep, direntptr); - brelse(bp); + if (!EXFAT(pmp)) { + DE_INTERNALIZE(ldep, direntptr); + brelse(bp); + } } /* @@ -256,8 +285,15 @@ * XXX it sometimes happens that the "." entry has cluster * number 0 when it shouldn't. Use the actual cluster number * instead of what is written in directory entry. + * + * exFAT note: in exFAT, directories are represented by the + * denode entry in their parent directory, like ordinary files. + * There is no concrete self-referencing '.' entry at diroffset + * zero. If an exFAT diretory is at diroffset==0, it's because + * it's the very first entry in its parent directory. */ - if (diroffset == 0 && ldep->de_StartCluster != dirclust) { + if (!EXFAT(pmp) && diroffset == 0 && + ldep->de_StartCluster != dirclust) { #ifdef MSDOSFS_DEBUG printf("deget(): \".\" entry at clust %lu != %lu\n", dirclust, ldep->de_StartCluster); Index: sys/fs/msdosfs/msdosfs_fat.c =================================================================== --- sys/fs/msdosfs/msdosfs_fat.c +++ sys/fs/msdosfs/msdosfs_fat.c @@ -178,6 +178,29 @@ if (sp) *sp = pmp->pm_bpcluster; + /* + * exFAT files and directories can be marked as contiguous, in which + * case mapping is straightforward (and the corresponding FAT chain + * entries are unused). + */ + if (EXFAT(pmp) && dep->de_Contiguous) { + uint64_t num_cn; + + num_cn = howmany(dep->de_AllocatedSize, pmp->pm_bpcluster); + if (findcn >= num_cn) { + /* EOF. */ + if (cnp) + *cnp = num_cn; + return (E2BIG); + } + + if (cnp) + *cnp = cn + findcn; + if (bnp) + *bnp = cntobn(pmp, cn + findcn); + return (0); + } + /* * Rummage around in the FAT cache, maybe we can avoid tromping * through every FAT entry for the file. And, keep track of how far @@ -212,7 +235,7 @@ brelse(bp); return (EIO); } - if (FAT32(pmp)) + if (FAT32(pmp) || EXFAT(pmp)) cn = getulong(bp->b_data + bo); else cn = getushort(bp->b_data + bo); @@ -509,7 +532,7 @@ } if (function & FAT_GET) { - if (FAT32(pmp)) + if (FAT32(pmp) || EXFAT(pmp)) readcn = getulong(bp->b_data + bo); else readcn = getushort(bp->b_data + bo); @@ -881,6 +904,220 @@ return (0); } +#ifdef MSDOS_EXFAT +static uint32_t +exfat_boot_chksum_update(const struct msdosfsmount *pmp, const uint8_t *data, + uint32_t sector, uint32_t chksum) +{ + unsigned n; + + for (n = 0; n < pmp->pm_BytesPerSec; n++) { + /* These specific bytes are skipped per spec. */ + if (sector == 0) { + switch (n) { + case 106: + case 107: + case 112: + continue; + } + } + chksum = ((chksum & 1) ? 0x80000000u : 0u) + (chksum >> 1) + + (uint32_t)data[n]; + } + return (chksum); +} + +static int +exfat_fillusemap(struct msdosfsmount *pmp) +{ + struct denode *root_dne, *bm_dne; + struct buf *bp; + size_t blkoff; + u_long bm_de_cluster; + uint32_t sn, chksum, cn; + int error; + + bp = NULL; + + /* First, verify the boot region checksum. */ + chksum = 0; + for (sn = 0; sn < 11; sn++) { + error = bread(pmp->pm_devvp, + (EXFAT_SECT_MAIN_BOOTREGION + sn) * pmp->pm_BlkPerSec, + pmp->pm_BytesPerSec, NOCRED, &bp); + if (error) { +#ifdef MSDOSFS_DEBUG + printf("%s(): bread bootregion: %d\n", __func__, error); +#endif + return (error); + } + + chksum = exfat_boot_chksum_update(pmp, bp->b_data, sn, chksum); + brelse(bp); + } + + error = bread(pmp->pm_devvp, pmp->pm_BlkPerSec * + (EXFAT_SECT_MAIN_BOOTREGION + EXFAT_BOOTREG_OFFSET_CHKSUM), + pmp->pm_BytesPerSec, NOCRED, &bp); + if (error) { +#ifdef MSDOSFS_DEBUG + printf("%s(): bread: checksum sector: %d\n", __func__, error); +#endif + return (error); + } + + /* + * The entire sector should repeat the pattern, but we only verify the + * first. + */ + if (chksum != le32dec(bp->b_data)) { + printf("exFAT: Bad boot parameters checksum: " + "0x%08x (actual) != 0x%08x (expected)\n", chksum, + le32dec(bp->b_data)); + brelse(bp); + return (EFTYPE); + } + brelse(bp); + + /* + * Then load the allocation bitmap from the special entry in the root + * directory. (It's not valid to just do the same FAT-scan as FAT32. + * Contig-allocated files are not reflected in the FAT.) + */ + error = deget(pmp, MSDOSFSROOT, MSDOSFSROOT_OFS, &root_dne); + if (error) + return (error); + bm_dne = NULL; + + /* Scan root directory for allocation bitmap special file. */ + for (cn = 0;; cn++) { + struct exfat_dirent *it; + daddr_t bn; + bool bm_found = false; + + error = pcbmap(root_dne, cn, &bn, &bm_de_cluster, NULL); + if (error == E2BIG) + break; + if (error) + goto out; + + error = bread(pmp->pm_devvp, bn, pmp->pm_bpcluster, NOCRED, + &bp); + if (error) + goto out; + + /* Scan this directory sector. */ + for (it = (void *)bp->b_data; + (char *)it < bp->b_data + bp->b_bufsize; + it++) { + if (it->xde_type == XDE_TYPE_EOD) + break; + if (it->xde_type != XDE_TYPE_ALLOC_BITMAP) + continue; + + if (it->xde_sec_flags & 0x1) { + printf("exFAT: Second FAT bitmap\n"); + error = EFTYPE; + brelse(bp); + goto out; + } + +#ifdef MSDOSFS_DEBUG + uint32_t bm_cn = le32dec(&it->xde_first_cluster); + uint64_t bm_len = le64dec(&it->xde_data_len); + printf("%s(): cn %u bm_cn %u bm_len %ju\n", __func__, + cn, bm_cn, (uintmax_t)bm_len); +#endif + /* + * XXX I don't think this would handle an allocation + * bitmap in the second or later cluster of a directory + * correctly. + */ + blkoff = (char *)it - bp->b_data; + bm_found = true; + break; + } + + brelse(bp); + if (bm_found) + break; + } +#ifdef MSDOSFS_DEBUG + printf("%s(): scanned %u clusters (maxcluster: %lu)\n", __func__, cn, + pmp->pm_maxcluster); +#endif + if (error) + goto out; + + /* Open allocation bitmap denode. */ + vput(DETOV(root_dne)); + root_dne = NULL; + error = deget(pmp, bm_de_cluster, blkoff, &bm_dne); + if (error) { +#ifdef MSDOSFS_DEBUG + printf("%s(): deget alloc bitmap: %d\n", __func__, error); +#endif + goto out; + } + + /* Load allocation bitmap from bitmap denode. */ + cn = 2; + for (daddr_t lbn = 0; cn < pmp->pm_maxcluster; lbn++) { +#if 0 + error = bread(DETOV(bm_dne), lbn, pmp->pm_bpcluster, NOCRED, + &bp); +#else + daddr_t pbn; + + error = pcbmap(bm_dne, lbn, &pbn, NULL, NULL); + if (error) { +#ifdef MSDOSFS_DEBUG + printf("%s(): alloc bitmap pcbmap: %d\n", __func__, error); +#endif + goto out; + } +#ifdef MSDOSFS_DEBUG + printf("%s(): alloc bitmap pcbmap: ok\n", __func__); +#endif + error = bread(pmp->pm_devvp, pbn, pmp->pm_bpcluster, NOCRED, + &bp); +#endif /* 0 */ + if (error) { +#ifdef MSDOSFS_DEBUG + printf("%s(): alloc bitmap bread: %d\n", __func__, error); +#endif + goto out; + } +#ifdef MSDOSFS_DEBUG + printf("%s(): alloc bitmap bread: ok\n", __func__); +#endif + + for (size_t i = 0; + i < pmp->pm_bpcluster && cn < pmp->pm_maxcluster; i++) { + for (unsigned j = 0; j < 8 && cn < pmp->pm_maxcluster; + j++, cn++) { + /* Allocated? */ + if ((bp->b_data[i] & (1 << j))) + continue; + /* Free. */ + pmp->pm_inusemap[cn / N_INUSEBITS] &= + ~(1U << (cn % N_INUSEBITS)); + pmp->pm_freeclustercount++; + } + } + + brelse(bp); + } + +out: + if (root_dne != NULL) + vput(DETOV(root_dne)); + if (bm_dne != NULL) + vput(DETOV(bm_dne)); + return (error); +} +#endif + /* * Read in FAT blocks looking for free clusters. For every free cluster * found turn off its corresponding bit in the pm_inusemap. @@ -902,12 +1139,24 @@ for (cn = 0; cn < (pmp->pm_maxcluster + N_INUSEBITS) / N_INUSEBITS; cn++) pmp->pm_inusemap[cn] = FULL_RUN; + pmp->pm_freeclustercount = 0; + +#ifdef MSDOS_EXFAT + /* + * EXFAT allocation bitmap is stored on disk and must be loaded + * directly. + */ + if (EXFAT(pmp)) { + error = exfat_fillusemap(pmp); + return (error); + } +#endif + /* * Figure how many free clusters are in the filesystem by ripping * through the FAT counting the number of entries whose content is * zero. These represent free clusters. */ - pmp->pm_freeclustercount = 0; for (cn = 0; cn <= pmp->pm_maxcluster; cn++) { byteoffset = FATOFS(pmp, cn); bo = byteoffset % pmp->pm_fatblocksize; Index: sys/fs/msdosfs/msdosfs_lookup.c =================================================================== --- sys/fs/msdosfs/msdosfs_lookup.c +++ sys/fs/msdosfs/msdosfs_lookup.c @@ -110,6 +110,578 @@ * out to disk. This way disk blocks containing directory entries and in * memory denode's will be in synch. */ +static int +exfat_lookup_(struct vnode *vdp, struct vnode **vpp, + struct componentname *cnp, uint64_t *dd_inum) +{ + //struct mbnambuf nb; + daddr_t bn; + int error; + int slotcount; + int slotoffset = 0; + int frcn; + u_long cluster, file_cluster; + int blkoff, file_blkoff; + int diroff; + int blsize; + int isadir; /* ~0 if found direntry is a directory */ + u_long scn; /* starting cluster number */ + struct vnode *pdp; + struct denode *dp; + struct denode *tdp; + struct msdosfsmount *pmp; + struct buf *bp = NULL; + struct exfat_dirent *dep = NULL; + struct deget_dotdot dd_arg; + uint16_t ucs2filename[EXFAT_MAXNAMLEN + 1]; + int flags = cnp->cn_flags; + int nameiop = cnp->cn_nameiop; + //int unlen; + uint64_t inode1; + + int wincnt = 1; + int chksum = -1; //, chksum_ok; + + dp = VTODE(vdp); + pmp = dp->de_pmp; +#ifdef MSDOSFS_DEBUG + printf("%s(): vdp %p, dp %p, Attr %02x\n", __func__, + vdp, dp, dp->de_Attributes); +#endif + + restart: + if (vpp != NULL) + *vpp = NULL; + /* + * If they are going after the . or .. entry, they won't find it. + * exFAT filesystems don't have them on-disk. So, we fake it. deget() + * is in on this scam too. + */ + if (cnp->cn_nameptr[0] == '.' && cnp->cn_namelen == 1) { + isadir = ATTR_DIRECTORY; + file_cluster = cluster = scn = dp->de_dirclust; + file_blkoff = blkoff = dp->de_diroffset; + goto foundroot; + } + + if ((vdp->v_vflag & VV_ROOT) && cnp->cn_nameptr[0] == '.' && + cnp->cn_namelen == 2 && cnp->cn_nameptr[1] == '.') { + isadir = ATTR_DIRECTORY; + file_cluster = cluster = scn = MSDOSFSROOT; + file_blkoff = blkoff = MSDOSFSROOT_OFS; + goto foundroot; + } + + error = msdosfs_cnp_to_ucs2(pmp, ucs2filename, nitems(ucs2filename), + cnp); + if (error != 0) + return (error); + /* + * A given file is represented by a file slot, a stream extension slot, + * and 1 or more filename slots, in that order. + */ + wincnt = 2 + howmany(msdosfs_ucs2len(ucs2filename), EXFAT_CHARS); + //unlen = winLenFixup(cnp->cn_nameptr, cnp->cn_namelen); + + /* + * Suppress search for slots unless creating + * file and at end of pathname, in which case + * we watch for a place to put the new file in + * case it doesn't already exist. + */ + slotcount = wincnt; + if ((nameiop == CREATE || nameiop == RENAME) && + (flags & ISLASTCN)) + slotcount = 0; + + /* + * Search the directory pointed at by vdp for the name pointed at + * by cnp->cn_nameptr. + */ + tdp = NULL; + //mbnambuf_init(&nb); + + /* Name buffer for most recent file. */ + uint16_t wcnambuf[EXFAT_MAXNAMLEN + 1]; + size_t namlen; + enum exfat_dir_state { + XD_START, + XD_FILE, + XD_STREAM, + XD_FILENAME, + } dirstate; + struct exfat_de_file file_dirent; + struct exfat_de_stream stream_dirent; + unsigned remsecondary; + off_t file_de_offset; + + /* + * The outer loop ranges over the clusters that make up the + * directory. + */ + diroff = 0; + dirstate = XD_START; + namlen = 0; + for (frcn = 0;; frcn++) { + error = pcbmap(dp, frcn, &bn, &cluster, &blsize); + if (error) { + if (error == E2BIG) + break; + return (error); + } + error = bread(pmp->pm_devvp, bn, blsize, NOCRED, &bp); + if (error) + return (error); + for (blkoff = 0; blkoff < blsize; + blkoff += sizeof(struct exfat_dirent), + diroff += sizeof(struct exfat_dirent)) { + dep = (struct exfat_dirent *)(bp->b_data + blkoff); + /* + * If this is an end of directory marker, we can stop. + */ + if (dep->xde_type == XDE_TYPE_EOD) { + dirstate = XD_START; + brelse(bp); + goto notfound; + } + /* + * If the slot is empty and we are still looking + * for an empty then remember this one. If the + * slot is not empty then check to see if it + * matches what we are looking for. If the slot + * has never been filled with anything, then the + * remainder of the directory has never been used, + * so there is no point in searching it. + */ + if ((dep->xde_type & XDE_TYPE_INUSE_MASK) == 0) { + dirstate = XD_START; + chksum = -1; + if (slotcount < wincnt) { + slotcount++; + //slotoffset = diroff; + } + continue; + } + + /* + * If there wasn't enough space for our winentries, + * forget about the empty space + */ + if (slotcount < wincnt) + slotcount = 0; + + /* + * File, Stream Extension, and File Name entries + * compose the parts of entries we recognize. + * + * We must tolerate and ignore vendor extension + * entries, which may only follow Stream+File entries. + */ + switch (dep->xde_type) { + default: + dirstate = XD_START; + chksum = -1; + //mbnambuf_init(&nb); + continue; + case XDE_TYPE_FILE: + case XDE_TYPE_STREAM_EXT: + case XDE_TYPE_FILE_NAME: + case XDE_TYPE_VENDOR: + case XDE_TYPE_VENDOR_ALLOC: + break; + } + + /* XXX SHALL verify dirent-set checksum */ + if (dep->xde_type == XDE_TYPE_FILE) { + struct exfat_de_file *xdf; + + xdf = (void *)dep; + + dirstate = XD_FILE; + namlen = 0; + file_de_offset = diroff; + file_cluster = cluster; + file_blkoff = blkoff; + remsecondary = xdf->xdef_secondary_count; + memcpy(&file_dirent, xdf, sizeof(file_dirent)); + continue; + } + + if (dep->xde_type == XDE_TYPE_STREAM_EXT) { + struct exfat_de_stream *xds; + + xds = (void *)dep; + + /* + * Stream Extension dirents must immediately + * follow File dirents. + */ + if (dirstate != XD_FILE || remsecondary < 1 || + xds->xdes_namelen < 1) { +#ifdef MSDOSFS_DEBUG + printf("%s(): invalid stream ext dirent\n", + __func__); +#endif + brelse(bp); + return (EINTEGRITY); + } + + remsecondary--; + dirstate = XD_STREAM; + memcpy(&stream_dirent, xds, + sizeof(stream_dirent)); + continue; + } + + if (dep->xde_type == XDE_TYPE_FILE_NAME) { + struct exfat_de_filename *xdfn; + + if ((dirstate != XD_STREAM && + dirstate != XD_FILENAME) || + remsecondary < 1) { +#ifdef MSDOSFS_DEBUG + printf("%s(): invalid filename dirent\n", + __func__); +#endif + brelse(bp); + return (EINTEGRITY); + } + + xdfn = (void *)dep; + + dirstate = XD_FILENAME; + + size_t rem; + + rem = min(nitems(xdfn->xdefn_filename), + stream_dirent.xdes_namelen - namlen); + if (rem == 0) { +#ifdef MSDOSFS_DEBUG + printf("%s(): invalid extra filename dirent\n", + __func__); +#endif + brelse(bp); + return (EINTEGRITY); + } + + memcpy(&wcnambuf[namlen], xdfn->xdefn_filename, + rem * sizeof(wcnambuf[0])); + namlen += rem; + wcnambuf[namlen] = 0; + + remsecondary--; + if (remsecondary > 0) + continue; + /* FALLTHROUGH */ + } else if (dep->xde_type == XDE_TYPE_VENDOR || + dep->xde_type == XDE_TYPE_VENDOR_ALLOC) { + if (dirstate != XD_FILENAME || + remsecondary < 1) { +#ifdef MSDOSFS_DEBUG + printf("%s(): invalid vendor dirent\n", + __func__); +#endif + brelse(bp); + return (EINTEGRITY); + } + + remsecondary--; + if (remsecondary > 0) + continue; + /* FALLTHROUGH */ + } + + /* + * Check for a checksum XXX or name match + */ + //chksum_ok = (chksum == winChksum(dep->deName)); + if (msdosfs_ucs2len(ucs2filename) != + msdosfs_ucs2len(wcnambuf) || + memcmp(ucs2filename, wcnambuf, + msdosfs_ucs2len(ucs2filename)) != 0) { + dirstate = XD_START; + chksum = -1; + continue; + } +#ifdef MSDOSFS_DEBUG + printf("%s(): match blkoff %x, diroff %jx\n", __func__, + file_blkoff, (uintmax_t)file_de_offset); +#endif + + /* + * Remember where this directory entry came from for + * whoever did this lookup. + */ + dp->de_fndoffset = file_de_offset; + if (nameiop == RENAME) { + /* + * Target had correct long name directory + * entries, reuse them as needed. + */ + dp->de_fndcnt = wincnt - 2; + } else { + /* + * Long name directory entries not present or + * corrupt, can only reuse dos directory entry. + */ + dp->de_fndcnt = 0; + } + + goto found; + } /* for (blkoff = 0; .... */ + /* + * Release the buffer holding the directory cluster just + * searched. + */ + brelse(bp); + } /* for (frcn = 0; ; frcn++) */ + +notfound: + /* + * We hold no disk buffers at this point. + */ + + /* + * Fixup the slot description to point to the place where + * we might put the new DOS direntry (putting the Win95 + * long name entries before that) + */ + /* XXX this is wrong */ + if (!slotcount) { + slotcount = 1; + slotoffset = diroff; + } + if (wincnt > slotcount) + slotoffset += sizeof(struct direntry) * (wincnt - slotcount); + + /* + * If we get here we didn't find the entry we were looking for. But + * that's ok if we are creating or renaming and are at the end of + * the pathname and the directory hasn't been removed. + */ +#ifdef MSDOSFS_DEBUG + printf("%s(): op %d, refcnt %ld\n", __func__, nameiop, dp->de_refcnt); + printf(" slotcount %d, slotoffset %d\n", + slotcount, slotoffset); +#endif + if ((nameiop == CREATE || nameiop == RENAME) && + (flags & ISLASTCN) && dp->de_refcnt != 0) { + /* + * Access for write is interpreted as allowing + * creation of files in the directory. + */ + error = VOP_ACCESS(vdp, VWRITE, cnp->cn_cred, cnp->cn_thread); + if (error) + return (error); + /* + * Return an indication of where the new directory + * entry should be put. + */ + dp->de_fndoffset = slotoffset; + dp->de_fndcnt = wincnt - 1; + + /* + * We return with the directory locked, so that + * the parameters we set up above will still be + * valid if we actually decide to do a direnter(). + * We return ni_vp == NULL to indicate that the entry + * does not currently exist; we leave a pointer to + * the (locked) directory inode in ndp->ni_dvp. + * The pathname buffer is saved so that the name + * can be obtained later. + * + * NB - if the directory is unlocked, then this + * information cannot be used. + */ + cnp->cn_flags |= SAVENAME; + return (EJUSTRETURN); + } +#if 0 + /* + * Insert name into cache (as non-existent) if appropriate. + * + * XXX Negative caching is broken for msdosfs because the name + * cache doesn't understand peculiarities such as case insensitivity + * and 8.3 filenames. Hence, it may not invalidate all negative + * entries if a file with this name is later created. + */ + if ((cnp->cn_flags & MAKEENTRY) != 0) + cache_enter(vdp, *vpp, cnp); +#endif + return (ENOENT); + +found: + isadir = le16toh(file_dirent.xdef_attributes) & ATTR_DIRECTORY; + printf("%s(): attributes: 0x%x\n", __func__, + le16toh(file_dirent.xdef_attributes)); + scn = le32toh(stream_dirent.xdes_firstcluster); + if (scn == pmp->pm_rootdirblk) { + /* + * XXX not sure this ever happens for exFAT; no concrete . or + * .. entries. + */ + /* + * There should actually be 0 here. Just ignore the error. + */ + scn = MSDOSFSROOT; + } + +#if 0 + if (isadir) { + file_cluster = scn; + if (file_cluster == MSDOSFSROOT) + file_blkoff = MSDOSFSROOT_OFS; + else + file_blkoff = 0; + } else if (file_cluster == MSDOSFSROOT) + file_blkoff = file_de_offset; +#endif + + /* + * Now release buf to allow deget to read the entry again. + * Reserving it here and giving it to deget could result + * in a deadlock. + */ + brelse(bp); + bp = NULL; + +foundroot: +#ifdef MSDOSFS_DEBUG + printf("%s(): looking for '%.*s': %lu, 0x%x\n", __func__, + (int)cnp->cn_namelen, cnp->cn_nameptr, file_cluster, file_blkoff); +#endif + /* + * If we entered at foundroot, then we are looking for the . or .. + * entry of the filesystems root directory. isadir and scn were + * setup before jumping here. And, bp is already null. + */ + if (scn == MSDOSFSROOT) + scn = pmp->pm_rootdirblk; + + if (dd_inum != NULL) { + *dd_inum = (uint64_t)pmp->pm_bpcluster * scn + file_blkoff; + return (0); + } + + /* + * If deleting, and at end of pathname, return + * parameters which can be used to remove file. + */ + if (nameiop == DELETE && (flags & ISLASTCN)) { + /* + * Don't allow deleting the root. + */ + if (file_blkoff == MSDOSFSROOT_OFS) + return (EBUSY); + + /* + * Write access to directory required to delete files. + */ + error = VOP_ACCESS(vdp, VWRITE, cnp->cn_cred, cnp->cn_thread); + if (error) + return (error); + + /* + * Return pointer to current entry in dp->i_offset. + * Save directory inode pointer in ndp->ni_dvp for dirremove(). + */ + if (dp->de_StartCluster == scn && isadir) { /* "." */ + VREF(vdp); + *vpp = vdp; + return (0); + } + error = deget(pmp, file_cluster, file_blkoff, &tdp); + if (error) + return (error); + *vpp = DETOV(tdp); + return (0); + } + + /* + * If rewriting (RENAME), return the inode and the + * information required to rewrite the present directory + * Must get inode of directory entry to verify it's a + * regular file, or empty directory. + */ + if (nameiop == RENAME && (flags & ISLASTCN)) { + if (file_blkoff == MSDOSFSROOT_OFS) + return (EBUSY); + + error = VOP_ACCESS(vdp, VWRITE, cnp->cn_cred, cnp->cn_thread); + if (error) + return (error); + + /* + * Careful about locking second inode. + * This can only occur if the target is ".". + */ + if (dp->de_StartCluster == scn && isadir) + return (EISDIR); + + if ((error = deget(pmp, file_cluster, file_blkoff, &tdp)) != 0) + return (error); + *vpp = DETOV(tdp); + cnp->cn_flags |= SAVENAME; + return (0); + } + + /* + * Step through the translation in the name. We do not `vput' the + * directory because we may need it again if a symbolic link + * is relative to the current directory. Instead we save it + * unlocked as "pdp". We must get the target inode before unlocking + * the directory to insure that the inode will not be removed + * before we get it. We prevent deadlock by always fetching + * inodes from the root, moving down the directory tree. Thus + * when following backward pointers ".." we must unlock the + * parent directory before getting the requested directory. + */ + pdp = vdp; + if (flags & ISDOTDOT) { + dd_arg.cluster = file_cluster; + dd_arg.blkoff = file_blkoff; + error = vn_vget_ino_gen(vdp, msdosfs_deget_dotdot, + &dd_arg, cnp->cn_lkflags, vpp); + if (error != 0) { + *vpp = NULL; + return (error); + } + /* + * Recheck that ".." still points to the inode we + * looked up before pdp lock was dropped. + */ + error = exfat_lookup_(pdp, NULL, cnp, &inode1); + if (error) { + vput(*vpp); + *vpp = NULL; + return (error); + } + if (VTODE(*vpp)->de_inode != inode1) { + vput(*vpp); + goto restart; + } + } else if (dp->de_StartCluster == scn && isadir) { + VREF(vdp); /* we want ourself, ie "." */ + *vpp = vdp; + } else { + if ((error = deget(pmp, file_cluster, file_blkoff, &tdp)) != 0) + return (error); +#ifdef MSDOSFS_DEBUG + printf("%s(): '%.*s/..' = %lu, 0x%lx\n", __func__, + (int)cnp->cn_namelen, cnp->cn_nameptr, dp->de_dirclust, + dp->de_diroffset); +#endif + *vpp = DETOV(tdp); + } + + /* + * Insert name into cache if appropriate. + */ + if (cnp->cn_flags & MAKEENTRY) + cache_enter(vdp, *vpp, cnp); + return (0); +} + static int msdosfs_lookup_(struct vnode *vdp, struct vnode **vpp, struct componentname *cnp, uint64_t *dd_inum) @@ -148,6 +720,8 @@ #endif dp = VTODE(vdp); pmp = dp->de_pmp; + if (EXFAT(pmp)) + return (exfat_lookup_(vdp, vpp, cnp, dd_inum)); #ifdef MSDOSFS_DEBUG printf("msdosfs_lookup(): vdp %p, dp %p, Attr %02x\n", vdp, dp, dp->de_Attributes); @@ -313,7 +887,7 @@ continue; } #ifdef MSDOSFS_DEBUG - printf("msdosfs_lookup(): match blkoff %d, diroff %d\n", + printf("msdosfs_lookup(): match blkoff %x, diroff %x\n", blkoff, diroff); #endif /* @@ -445,6 +1019,11 @@ if (cluster == MSDOSFSROOT) blkoff = MSDOSFSROOT_OFS; else + /* + * Here we assume that each directory contains a + * concrete '.' entry at offset 0x0. This is true for + * classic FAT12/16/32, but not exFAT. + */ blkoff = 0; } else if (cluster == MSDOSFSROOT) blkoff = diroff; @@ -921,6 +1500,138 @@ bpp, epp)); } +static int +nextdirent(struct msdosfsmount *pmp, daddr_t *bn, struct buf **bpp, + const struct exfat_dirent **dentp) +{ + size_t offset; + int error; + + offset = (const char *)(*dentp) - (*bpp)->b_data; + if (offset + sizeof(**dentp) >= pmp->pm_bpcluster) { + brelse(*bpp); + + (*bn)++; + error = bread(pmp->pm_devvp, *bn, pmp->pm_bpcluster, NOCRED, + bpp); + if (error != 0) + return (error); + *dentp = (void *)(*bpp)->b_data; + } else + (*dentp)++; + return (0); +} + +/* + * Read the denode starting at dirclu+dirofs in pmp and decode the on-disk + * structures into the in-memory representation in 'dep'. + * + * Similar to readep() + DE_INTERNALIZE() + brelse(), for MSDOS denodes. + */ +int +readexfatep(struct msdosfsmount *pmp, u_long dirclust, u_long diroffset, + struct denode *dep) +{ + const struct exfat_dirent *dentp; + struct buf *bp; + daddr_t bn; + int error; + +#ifdef MSDOSFS_DEBUG + printf("%s(): cluster %lu, diroffset: 0x%lx\n", __func__, dirclust, + diroffset); +#endif + + bn = detobn(pmp, dirclust, diroffset); + error = bread(pmp->pm_devvp, bn, pmp->pm_bpcluster, NOCRED, &bp); + if (error != 0) + goto out; + + dentp = bptoxp(pmp, bp, diroffset); + if (dentp->xde_type == XDE_TYPE_ALLOC_BITMAP) { + strcpy(dep->de_Name, ".BMP"); + dep->de_Attributes = ATTR_SYSTEM | ATTR_HIDDEN; + dep->de_StartCluster = le32toh(dentp->xde_first_cluster); + dep->de_FileSize = le64toh(dentp->xde_data_len); +#ifdef MSDOSFS_DEBUG + printf("%s(): startcluster %ju, filesize: %ju\n", __func__, + (uintmax_t)dep->de_StartCluster, + (uintmax_t)dep->de_FileSize); +#endif + goto out; + } + + if (dentp->xde_type != XDE_TYPE_FILE) { + printf("%s(): Can't handle unexpected xde_type 0x%x\n", + __func__, (unsigned)dentp->xde_type); + error = EINVAL; + goto out; + } + + const struct exfat_de_file *xfdp; + unsigned secondary; + xfdp = (const void *)dentp; + + secondary = xfdp->xdef_secondary_count; + if (secondary < 2) { + /* Bogus value we should have caught in lookup. */ + error = EINTEGRITY; + goto out; + } + + /* Decode File bits. */ + dep->de_Attributes = (le16toh(xfdp->xdef_attributes) & + EXFAT_VALID_ATTR); + /* XXX unpack time values + * https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification#748-timestamp-fields + */ + /* + * exFAT File dirents do not have startcluster or size + * information in them. Instead they are followed by a + * XDE_TYPE_STREAM_EXT entry which contains those values. + */ + error = nextdirent(pmp, &bn, &bp, &dentp); + if (error != 0) + goto out; + + const struct exfat_de_stream *xds; + unsigned namelen; + xds = (const void *)dentp; + + /* Decode Stream Ext bits. */ + secondary--; + namelen = xds->xdes_namelen; + dep->de_FileSize = le64toh(xds->xdes_validdatalen); + dep->de_StartCluster = le32toh(xds->xdes_firstcluster); + dep->de_AllocatedSize = le64toh(xds->xdes_allocateddatalen); + dep->de_Contiguous = (xds->xdes_flags & XDE_FLAG_NOFATCHAIN); + + /* Decode File Name bits -- do we care about de_Name, actually? */ + (void)namelen; +#if 0 + while (secondary--) { + const struct exfat_de_filename *xdfn; + + error = nextdirent(pmp, &bn, &bp, &dentp); + if (error != 0) + goto out; + + xdfn = (const void *)dentp; + XXX; + } +#endif +#ifdef MSDOSFS_DEBUG + printf("%s(): EXFAT attributes=0x%x filesize=%ld(alloc=%ld) " + "startcluster=%ld contiguous=%d\n", __func__, dep->de_Attributes, + dep->de_FileSize, dep->de_AllocatedSize, dep->de_StartCluster, + (int)dep->de_Contiguous); +#endif + +out: + brelse(bp); + return (error); +} + /* * Remove a directory entry. At this point the file represented by the * directory entry to be removed is still full length until no one has it Index: sys/fs/msdosfs/msdosfs_vfsops.c =================================================================== --- sys/fs/msdosfs/msdosfs_vfsops.c +++ sys/fs/msdosfs/msdosfs_vfsops.c @@ -183,7 +183,23 @@ pmp->pm_flags |= MSDOSFSMNT_SHORTNAME; else pmp->pm_flags |= MSDOSFSMNT_LONGNAME; - return 0; + +#ifdef MSDOS_EXFAT + /* + * exFAT is UTF-16LE only, no 8.3 shortnames. The msdosfs kiconv mount + * option isn't applicable here; it allows specifying a specific 8-bit + * encoding for legacy filesystems. exFAT will always iconv from + * UTF-16LE to native and vice versa. + */ + /* XXX: Actually open and close iconv at mount. */ + if ((pmp->pm_flags & (MSDOSFSMNT_SHORTNAME | MSDOSFSMNT_NOWIN95 | + MSDOSFSMNT_KICONV)) != 0) { + printf("exFAT: invalid mount options " + "(shortname, nowin95, or kiconv)\n"); + return (EFTYPE); + } +#endif + return (0); } static int @@ -236,6 +252,10 @@ accmode_t accmode; char *from; +#ifdef MSDOSFS_DEBUG + printf("msdosfs_mount(): ENTER\n"); +#endif + td = curthread; if (vfs_filteropt(mp->mnt_optnew, msdosfs_opts)) return (EINVAL); @@ -380,6 +400,9 @@ return (EINVAL); /* XXX needs translation */ } if (error) { +#ifdef MSDOSFS_DEBUG + printf("msdosfs_mount(): mountmsdosfs=%d\n", error); +#endif vrele(devvp); return (error); } @@ -398,6 +421,100 @@ return (0); } +#ifdef MSDOS_EXFAT +static int +mountexfat(const union bootsector *bsp, struct msdosfsmount *pmp) +{ + static const unsigned char jmpsig[] = { 0xEB, 0x76, 0x90 }; + static const char fsnamsig[] = "EXFAT "; + + const struct exfat_vbr *bsxf; + uint64_t clusters; + + _Static_assert(sizeof(jmpsig) == sizeof(bsxf->ev_jmp), ""); + _Static_assert(sizeof(fsnamsig) == sizeof(bsxf->ev_fsname) + 1, ""); + + bsxf = &bsp->bsxf; + + if (memcmp(bsxf->ev_jmp, jmpsig, sizeof(jmpsig)) != 0 || + memcmp(bsxf->ev_fsname, fsnamsig, sizeof(fsnamsig) - 1) != 0 || + bsxf->ev_bootsign[0] != BOOTSIG0 || + bsxf->ev_bootsign[1] != BOOTSIG1) { + return (EINVAL); + } + if (bsxf->ev_log_bytes_per_sect < 9 || bsxf->ev_log_bytes_per_sect > 12) { +#ifdef MSDOSFS_DEBUG + printf("%s: Invalid BytesPerSectorShift %d\n", __func__, + (int)bsxf->ev_log_bytes_per_sect); +#endif + return (EINVAL); + } + /* Maximum cluster size is 32MB, regardless of disk sector size. */ + if (bsxf->ev_log_sect_per_clust > 25 - bsxf->ev_log_bytes_per_sect) + return (EINVAL); + + if (bsxf->ev_fs_revision_major != 1) { + printf("%s: Unsupported exFAT revision: %d.%d\n", __func__, + (int)bsxf->ev_fs_revision_major, + (int)bsxf->ev_fs_revision_minor); + return (EFTYPE); + } + +#ifdef MSDOSFS_DEBUG + printf("%s: Filesystem has exFAT revision: %d.%d\n", __func__, + (int)bsxf->ev_fs_revision_major, (int)bsxf->ev_fs_revision_minor); +#endif + + pmp->pm_BytesPerSec = (1u << bsxf->ev_log_bytes_per_sect); + pmp->pm_FATs = bsxf->ev_num_fats; + pmp->pm_RootDirEnts = 0; + + pmp->pm_BlkPerSec = pmp->pm_BytesPerSec / DEV_BSIZE; + + pmp->pm_Sectors = 0; + pmp->pm_HugeSectors = le64toh(bsxf->ev_vol_length) * pmp->pm_BlkPerSec; + pmp->pm_FATsecs = le32toh(bsxf->ev_fat_length) * pmp->pm_BlkPerSec; + pmp->pm_fatsize = pmp->pm_FATsecs * DEV_BSIZE; + + pmp->pm_fatmask = EXFAT_MASK; + pmp->pm_fatmult = 4; + pmp->pm_fatdiv = 1; + + pmp->pm_curfat = (bsxf->ev_vol_flags & XF_VF_ACTIVEFAT); + if (pmp->pm_curfat == 1 && pmp->pm_FATs < 2) { + printf("Active FAT does not exist.\n"); + return (EINVAL); + } + if (pmp->pm_curfat == 1) { + printf("This implementation does not support TexFat\n"); + return (EFTYPE); + } + + pmp->pm_rootdirblk = le32toh(bsxf->ev_rootdir_cluster); + printf("%s(): pm_rootdirblk: %lu\n", __func__, pmp->pm_rootdirblk); + pmp->pm_fatblk = le32toh(bsxf->ev_fat_offset) * pmp->pm_BlkPerSec; + pmp->pm_firstcluster = le32toh(bsxf->ev_cluster_offset) * pmp->pm_BlkPerSec; + pmp->pm_maxcluster = le32toh(bsxf->ev_cluster_count) + CLUST_FIRST; + clusters = pmp->pm_fatsize / pmp->pm_fatmult; + if (pmp->pm_maxcluster >= clusters) { + printf("Warning: number of clusters (%lu) exceeds FAT " + "capacity (%ju)\n", pmp->pm_maxcluster + 1, + (uintmax_t)clusters); + pmp->pm_maxcluster = clusters - 1; + } + + pmp->pm_fatblocksize = PAGE_SIZE; + pmp->pm_fatblocksec = PAGE_SIZE / DEV_BSIZE; + pmp->pm_bnshift = ffs(DEV_BSIZE) - 1; + + pmp->pm_bpcluster = (1u << bsxf->ev_log_sect_per_clust) * + pmp->pm_BytesPerSec; + pmp->pm_crbomask = pmp->pm_bpcluster - 1; + pmp->pm_cnshift = ffs(pmp->pm_bpcluster) - 1; + return (0); +} +#endif + static int mountmsdosfs(struct vnode *devvp, struct mount *mp) { @@ -479,6 +596,22 @@ pmp->pm_mask = pmp->pm_dirmask = S_IXUSR | S_IXGRP | S_IXOTH | S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR; +#ifdef MSDOS_EXFAT + /* + * exFAT specifies the BPB region in FAT12/16/32 as all zeroes to + * prevent FAT12/16/32 from accidentally mounting it. Grab similar + * BPB-like PMP parameters from the exFAT filesystem with slightly + * different logic. + */ + if (memcmp(bsp->bsxf.ev_zeros, zero_region, + sizeof(bsp->bsxf.ev_zeros)) == 0) { + error = mountexfat(bsp, pmp); + if (error) + goto error_exit; + goto done_parsing_bootsector; + } +#endif + /* * Compute several useful quantities from the bpb in the * bootsector. Copy in the dos 5 variant of the bpb then fix up @@ -637,6 +770,9 @@ goto error_exit; } +#ifdef MSDOS_EXFAT +done_parsing_bootsector: +#endif /* * Release the bootsector buffer. */ @@ -692,12 +828,19 @@ pmp->pm_devvp = devvp; pmp->pm_dev = dev; +#ifdef MSDOSFS_DEBUG + printf("%s() before fillinusemap\n", __func__); +#endif + /* * Have the inuse map filled in. */ MSDOSFS_LOCK_MP(pmp); error = fillinusemap(pmp); MSDOSFS_UNLOCK_MP(pmp); +#ifdef MSDOSFS_DEBUG + printf("%s() after fillinusemap: error=%d\n", __func__, error); +#endif if (error != 0) goto error_exit; @@ -988,6 +1131,10 @@ struct denode *dep; int error; + /* XXX: exFAT: cannot populate correct .. references in this path. */ + if (EXFAT(pmp)) + return (EOPNOTSUPP); + error = deget(pmp, defhp->defid_dirclust, defhp->defid_dirofs, &dep); if (error) { *vpp = NULLVP; Index: sys/fs/msdosfs/msdosfs_vnops.c =================================================================== --- sys/fs/msdosfs/msdosfs_vnops.c +++ sys/fs/msdosfs/msdosfs_vnops.c @@ -272,7 +272,7 @@ * used in msdosfs_readdir() to compute d_fileno. If not, pwd * doesn't work. */ - if (dep->de_Attributes & ATTR_DIRECTORY) { + if (!EXFAT(pmp) && dep->de_Attributes & ATTR_DIRECTORY) { fileid = (uint64_t)cntobn(pmp, dep->de_StartCluster) * dirsperblk; if (dep->de_StartCluster == MSDOSFSROOT) @@ -1461,6 +1461,384 @@ return (EOPNOTSUPP); } +static int +exfat_readdir(struct vop_readdir_args *ap) +{ + struct dirent dirbuf; + struct denode *dep; + struct exfat_dirent *dentp; + struct msdosfsmount *pmp; + struct buf *bp; + struct uio *uio; + daddr_t bn, lbn; + u_long *cookies; + u_long cn, dirsperblk; + off_t offset, off; + long bias, n, on; + int blsize, chksum, diff, error, ncookies; + + dep = VTODE(ap->a_vp); + pmp = dep->de_pmp; + uio = ap->a_uio; + cookies = NULL; + ncookies = 0; + chksum = -1; + bias = 0; + + MPASS(dep->de_Attributes & ATTR_DIRECTORY); + + memset(&dirbuf, 0, sizeof(dirbuf)); + + /* + * If the user buffer is smaller than the size of one dos directory + * entry or the file offset is not a multiple of the size of a + * directory entry, then we fail the read. + * + * XXX: Output vdirent format has nothing to do with size of on-disk + * dirents, why are we checking resid for that? + */ + off = offset = uio->uio_offset; + if (uio->uio_resid < sizeof(struct exfat_dirent) || + (offset & (sizeof(struct exfat_dirent) - 1))) + return (EINVAL); + + if (ap->a_ncookies) { + ncookies = uio->uio_resid / 16; + cookies = malloc(ncookies * sizeof(u_long), M_TEMP, + M_WAITOK); + *ap->a_cookies = cookies; + *ap->a_ncookies = ncookies; + } + + dirsperblk = pmp->pm_BytesPerSec / sizeof(struct exfat_dirent); + + /* + * XXX: not .. + * We simulate . and .. entries since these don't exist in exFAT + * directories. We also set the offset bias to make up for having to + * simulate these entries. I.e., at file offset 64 we read the first + * entry in the directory that lives on disk. + */ +#if 0 + printf("%s(): going after . or .., offset %d\n", __func__, offset); +#endif + bias = 1 * sizeof(struct exfat_dirent); + if (offset < bias) { + for (n = (int)offset / sizeof(struct exfat_dirent); + n < bias / sizeof(struct exfat_dirent); n++) { + dirbuf.d_type = DT_DIR; + switch (n) { + case 0: + dirbuf.d_namlen = 1; + dirbuf.d_name[0] = '.'; + dirbuf.d_fileno = + (uint64_t)cntobn(pmp, dep->de_dirclust) * + dirsperblk + (uoff_t)dep->de_diroffset / + sizeof(struct exfat_dirent); + break; +#if 0 + case 1: + dirbuf.d_namlen = 2; + dirbuf.d_name[0] = '.'; + dirbuf.d_name[1] = '.'; + dirbuf.d_fileno = XXX; + break; +#endif + } + dirbuf.d_reclen = GENERIC_DIRSIZ(&dirbuf); + /* NOTE: d_off is the offset of the *next* entry. */ + dirbuf.d_off = offset + sizeof(struct exfat_dirent); + dirent_terminate(&dirbuf); + if (uio->uio_resid < dirbuf.d_reclen) + goto out; + error = uiomove(&dirbuf, dirbuf.d_reclen, uio); + if (error) + goto out; + offset += sizeof(struct exfat_dirent); + off = offset; + if (cookies) { + *cookies++ = offset; + if (--ncookies <= 0) + goto out; + } + } + } + + uint16_t wcnambuf[EXFAT_MAXNAMLEN + 1]; + size_t namlen; + enum exfat_dir_state { + XD_START, + XD_FILE, + XD_STREAM, + XD_FILENAME, + } dirstate; + struct exfat_de_file file_dirent; + struct exfat_de_stream stream_dirent; + unsigned remsecondary; + off_t file_de_offset; + u_long file_cluster; + + dirstate = XD_START; + namlen = 0; + + //mbnambuf_init(&nb); + off = offset; + while (uio->uio_resid > 0) { + lbn = de_cluster(pmp, offset - bias); + on = (offset - bias) & pmp->pm_crbomask; + n = min(pmp->pm_bpcluster - on, uio->uio_resid); + diff = dep->de_FileSize - (offset - bias); + if (diff <= 0) + break; + n = min(n, diff); + error = pcbmap(dep, lbn, &bn, &cn, &blsize); + if (error) + break; + error = bread(pmp->pm_devvp, bn, blsize, NOCRED, &bp); + if (error) { + return (error); + } + n = min(n, blsize - bp->b_resid); + if (n == 0) { + brelse(bp); + return (EIO); + } + + /* + * Convert from dos directory entries to fs-independent + * directory entries. + */ + for (dentp = (struct exfat_dirent *)(bp->b_data + on); + (char *)dentp < bp->b_data + on + n; + dentp++, offset += sizeof(struct exfat_dirent)) { +#if 0 + printf("rd: dentp %08x prev %08x crnt %08x deName %02x attr %02x\n", + dentp, prev, crnt, dentp->deName[0], dentp->deAttributes); +#endif + /* + * If this is an end of directory marker, we can stop. + */ + if (dentp->xde_type == XDE_TYPE_EOD) { + brelse(bp); + goto out; + } + + /* + * If this is an unused slot, skip. + */ + if ((dentp->xde_type & XDE_TYPE_INUSE_MASK) == 0) { + dirstate = XD_START; + chksum = -1; + //mbnambuf_init(&nb); + continue; + } + + /* + * File, Stream Extension, and File Name entries + * compose the parts of entries we recognize. + * + * We must tolerate and ignore vendor extension + * entries, which may only follow Stream+File entries. + */ + switch (dentp->xde_type) { + default: + dirstate = XD_START; + chksum = -1; + //mbnambuf_init(&nb); + continue; + case XDE_TYPE_FILE: + case XDE_TYPE_STREAM_EXT: + case XDE_TYPE_FILE_NAME: + case XDE_TYPE_VENDOR: + case XDE_TYPE_VENDOR_ALLOC: + break; + } + + /* XXX SHALL verify dirent-set checksum */ + if (dentp->xde_type == XDE_TYPE_FILE) { + struct exfat_de_file *xdf; + + xdf = (void *)dentp; + + dirstate = XD_FILE; + namlen = 0; + file_de_offset = offset; + file_cluster = cn; + remsecondary = xdf->xdef_secondary_count; + memcpy(&file_dirent, xdf, sizeof(file_dirent)); + continue; + } + + if (dentp->xde_type == XDE_TYPE_STREAM_EXT) { + struct exfat_de_stream *xds; + + xds = (void *)dentp; + + /* + * Stream Extension dirents must immediately + * follow File dirents. + */ + if (dirstate != XD_FILE || remsecondary < 1 || + xds->xdes_namelen < 1) { +#ifdef MSDOSFS_DEBUG + printf("%s(): invalid stream ext dirent\n", + __func__); +#endif + brelse(bp); + error = EINTEGRITY; + goto out; + } + + remsecondary--; + dirstate = XD_STREAM; + memcpy(&stream_dirent, xds, + sizeof(stream_dirent)); + continue; + } + + if (dentp->xde_type == XDE_TYPE_FILE_NAME) { + struct exfat_de_filename *xdfn; + + if ((dirstate != XD_STREAM && + dirstate != XD_FILENAME) || + remsecondary < 1) { +#ifdef MSDOSFS_DEBUG + printf("%s(): invalid filename dirent\n", + __func__); +#endif + brelse(bp); + error = EINTEGRITY; + goto out; + } + + xdfn = (void *)dentp; + + dirstate = XD_FILENAME; + + size_t rem; + + rem = min(nitems(xdfn->xdefn_filename), + stream_dirent.xdes_namelen - namlen); + if (rem == 0) { +#ifdef MSDOSFS_DEBUG + printf("%s(): invalid extra filename dirent\n", + __func__); +#endif + brelse(bp); + error = EINTEGRITY; + goto out; + } + + memcpy(&wcnambuf[namlen], xdfn->xdefn_filename, + rem * sizeof(wcnambuf[0])); + namlen += rem; + wcnambuf[namlen] = 0; + + remsecondary--; + if (remsecondary > 0) + continue; + /* FALLTHROUGH */ + } else if (dentp->xde_type == XDE_TYPE_VENDOR || + dentp->xde_type == XDE_TYPE_VENDOR_ALLOC) { + if (dirstate != XD_FILENAME || + remsecondary < 1) { +#ifdef MSDOSFS_DEBUG + printf("%s(): invalid vendor dirent\n", + __func__); +#endif + brelse(bp); + error = EINTEGRITY; + goto out; + } + + remsecondary--; + if (remsecondary > 0) + continue; + /* FALLTHROUGH */ + } + + /* + * This computation of d_fileno must match + * the computation of va_fileid in + * msdosfs_getattr. + */ + dirbuf.d_fileno = (uint64_t)cntobn(pmp, file_cluster) * + dirsperblk + (uoff_t)file_de_offset / + sizeof(struct exfat_dirent); + if (le16toh(file_dirent.xdef_attributes) & + ATTR_DIRECTORY) + dirbuf.d_type = DT_DIR; + else + dirbuf.d_type = DT_REG; + + error = msdosfs_ucs2_to_dirent(pmp, &dirbuf, wcnambuf); + if (error == ENAMETOOLONG) { +#ifdef MSDOSFS_DEBUG + printf("%s(): name starting '%s' too long\n", + __func__, dirbuf.d_name); +#endif + dirstate = XD_START; + chksum = -1; + continue; + } else if (error != 0) { +#ifdef MSDOSFS_DEBUG + printf("%s(): msdosfs_ucs2_to_dirent: %d\n", + __func__, error); +#endif + brelse(bp); + goto out; + } +#ifdef MSDOSFS_DEBUG + printf("%s(): fileno %jd type %d name '%.*s'\n", + __func__, (intmax_t)dirbuf.d_fileno, (int)dirbuf.d_type, + (int)dirbuf.d_namlen, dirbuf.d_name); +#endif + + chksum = -1; + dirstate = XD_START; + + dirbuf.d_reclen = GENERIC_DIRSIZ(&dirbuf); + /* NOTE: d_off is the offset of the *next* entry. */ + dirbuf.d_off = offset + sizeof(struct exfat_dirent); + if (uio->uio_resid < dirbuf.d_reclen) { + brelse(bp); + goto out; + } + error = uiomove(&dirbuf, dirbuf.d_reclen, uio); + if (error) { + brelse(bp); + goto out; + } + if (cookies) { + *cookies++ = offset + sizeof(struct exfat_dirent); + if (--ncookies <= 0) { + brelse(bp); + goto out; + } + } + off = offset + sizeof(struct exfat_dirent); + } + brelse(bp); + } +out: + /* Subtract unused cookies */ + if (ap->a_ncookies) + *ap->a_ncookies -= ncookies; + + uio->uio_offset = off; + + /* + * Set the eofflag (NFS uses it) + */ + if (ap->a_eofflag) { + if (dep->de_FileSize - (offset - bias) <= 0) + *ap->a_eofflag = 1; + else + *ap->a_eofflag = 0; + } + return (error); +} + static int msdosfs_readdir(struct vop_readdir_args *ap) { @@ -1496,9 +1874,13 @@ * retrieve the wrong block from the buffer cache for a plain file. * So, fail attempts to readdir() on a plain file. */ + /* Also, this operation only makes sense on directory nodes. */ if ((dep->de_Attributes & ATTR_DIRECTORY) == 0) return (ENOTDIR); + if (EXFAT(pmp)) + return (exfat_readdir(ap)); + /* * To be safe, initialize dirbuf */ Index: sys/fs/msdosfs/msdosfsmount.h =================================================================== --- sys/fs/msdosfs/msdosfsmount.h +++ sys/fs/msdosfs/msdosfsmount.h @@ -86,6 +86,7 @@ struct vnode *pm_devvp; /* vnode for character device mounted */ struct cdev *pm_dev; /* character device mounted */ struct bpb50 pm_bpb; /* BIOS parameter blk for this fs */ + uint64_t pm_HugeSectors; /* Total # of sectors */ u_long pm_BlkPerSec; /* How many DEV_BSIZE blocks fit inside a physical sector */ u_long pm_FATsecs; /* actual number of FAT sectors */ u_long pm_fatblk; /* block # of first FAT */ @@ -149,14 +150,16 @@ #define pm_SecPerTrack pm_bpb.bpbSecPerTrack #define pm_Heads pm_bpb.bpbHeads #define pm_HiddenSects pm_bpb.bpbHiddenSecs -#define pm_HugeSectors pm_bpb.bpbHugeSectors /* * Convert pointer to buffer -> pointer to direntry */ +#define _bptoep(pmp, bp, dirofs) \ + (((bp)->b_data) + ((dirofs) & (pmp)->pm_crbomask)) #define bptoep(pmp, bp, dirofs) \ - ((struct direntry *)(((bp)->b_data) \ - + ((dirofs) & (pmp)->pm_crbomask))) + ((struct direntry *)_bptoep((pmp), (bp), (dirofs))) +#define bptoxp(pmp, bp, dirofs) \ + ((struct exfat_dirent *)_bptoep((pmp), (bp), (dirofs))) /* * Convert block number to cluster number