Module Name: src Committed By: perseant Date: Wed Jul 24 00:38:27 UTC 2024
Modified Files: src/sys/fs/exfatfs [perseant-exfatfs]: exfatfs_extern.c exfatfs_extern.h exfatfs_tables.c exfatfs_tables.h exfatfs_vfsops.c exfatfs_vnops.c Log Message: Actually use the upcase table when determining whether a file exists. Forbid certain filenames that, while not listed in the exFAT spec, nevertheless cause severe interoperability problems. Mark the filesystem dirty when mounted read/write, and clean again when the filesystem is unmounted. Correctly terminate the FAT chain when a file is converted to FAT. To generate a diff of this commit: cvs rdiff -u -r1.1.2.5 -r1.1.2.6 src/sys/fs/exfatfs/exfatfs_extern.c cvs rdiff -u -r1.1.2.2 -r1.1.2.3 src/sys/fs/exfatfs/exfatfs_extern.h \ src/sys/fs/exfatfs/exfatfs_tables.c src/sys/fs/exfatfs/exfatfs_tables.h cvs rdiff -u -r1.1.2.4 -r1.1.2.5 src/sys/fs/exfatfs/exfatfs_vfsops.c cvs rdiff -u -r1.1.2.7 -r1.1.2.8 src/sys/fs/exfatfs/exfatfs_vnops.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/fs/exfatfs/exfatfs_extern.c diff -u src/sys/fs/exfatfs/exfatfs_extern.c:1.1.2.5 src/sys/fs/exfatfs/exfatfs_extern.c:1.1.2.6 --- src/sys/fs/exfatfs/exfatfs_extern.c:1.1.2.5 Fri Jul 19 16:19:15 2024 +++ src/sys/fs/exfatfs/exfatfs_extern.c Wed Jul 24 00:38:26 2024 @@ -1,4 +1,4 @@ -/* $NetBSD: exfatfs_extern.c,v 1.1.2.5 2024/07/19 16:19:15 perseant Exp $ */ +/* $NetBSD: exfatfs_extern.c,v 1.1.2.6 2024/07/24 00:38:26 perseant Exp $ */ /*- * Copyright (c) 2022 The NetBSD Foundation, Inc. @@ -59,6 +59,7 @@ typedef struct uvvnode uvnode_t; #include <fs/exfatfs/exfatfs_extern.h> #include <fs/exfatfs/exfatfs_inode.h> #include <fs/exfatfs/exfatfs_extern.h> +#include <fs/exfatfs/exfatfs_tables.h> #include <fs/exfatfs/exfatfs_vfsops.h> /* #define EXFATFS_EXTERN_DEBUG */ @@ -258,6 +259,7 @@ int exfatfs_mountfs_shared(struct vnode { struct exfatfs *fs = NULL; struct buf *bp; + uint16_t *uctable; int error; unsigned secshift; const char *errstr; @@ -265,6 +267,7 @@ int exfatfs_mountfs_shared(struct vnode uint8_t boot_ignore[3] = { 106, 107, 112 }; int bn; uint32_t sum, badsb; + off_t res, off; DPRINTF(("exfatfs_mountfs_shared(%p, %u, %p)\n", devvp, secsize, fsp)); @@ -369,11 +372,18 @@ int exfatfs_mountfs_shared(struct vnode return EINVAL; } + LIST_INIT(&fs->xf_newxip); fs->xf_devvp = devvp; fs->xf_mp = xmp; - if (xmp != NULL) + if (xmp != NULL) { xmp->xm_fs = fs; - LIST_INIT(&fs->xf_newxip); + + /* If mounting for write, mark the fs dirty */ + if (!(xmp->xm_flags & EXFATFSMNT_RONLY)) { + fs->xf_VolumeFlags |= EXFATFS_VOLUME_DIRTY; + exfatfs_write_sb(fs); + } + } exfatfs_finish_mountfs(fs); @@ -382,7 +392,31 @@ int exfatfs_mountfs_shared(struct vnode */ read_rootdir(fs); exfatfs_check_fence(fs); - + + /* + * Load the upcase table + */ + uctable = malloc(GET_DSE_DATALENGTH(VTOXI(fs->xf_upcasevp)) +#ifdef _KERNEL + , M_EXFATFSBOOT, M_WAITOK +#endif /* _KERNEL */ + ); + res = (off_t)GET_DSE_DATALENGTH(VTOXI(fs->xf_upcasevp)); + for (off = 0; res > 0; off += EXFATFS_LSIZE(fs), res -= EXFATFS_LSIZE(fs)) { + bread(fs->xf_upcasevp, EXFATFS_B2L(fs, off), + EXFATFS_LSIZE(fs), 0, &bp); + memcpy(uctable + off, bp->b_data, MIN(res, EXFATFS_LSIZE(fs))); + brelse(bp, 0); + } + exfatfs_load_uctable(fs, uctable, + GET_DSE_DATALENGTH(VTOXI(fs->xf_upcasevp)) + / sizeof(uint16_t)); + free(uctable +#ifdef _KERNEL + , M_EXFATFSBOOT +#endif /* _KERNEL */ + ); + /* * Initialize data structure for finding free clusters. */ @@ -1065,3 +1099,55 @@ int exfatfs_set_file_name(struct xfinode return 0; } + +/* + * Write the boot block to disk, and checksum the boot block set. + */ +int +exfatfs_write_sb(struct exfatfs *fs) +{ + daddr_t base; + int i, error; + size_t j; + uint32_t cksum; + uint8_t boot_ignore[3] = { 106, 107, 112 }; + struct buf *bp; + + for (base = 0; base < 24; base += 12) { + /* Write superblock to disk */ + if ((error = bread(fs->xf_devvp, base + 0, BSSIZE(fs), + 0, &bp)) != 0) + return error; + memcpy(bp->b_data, &fs->xf_exfatdfs, + sizeof(fs->xf_exfatdfs)); + cksum = exfatfs_cksum32(0, + (uint8_t *)bp->b_data, + BSSIZE(fs), boot_ignore, + sizeof(boot_ignore)); + bwrite(bp); + + /* Checksum but do not write other sectors */ + for (i = 1; i < 11; i++) { + if ((error = bread(fs->xf_devvp, base + i, BSSIZE(fs), + 0, &bp)) != 0) + return error; + cksum = exfatfs_cksum32(cksum, + (uint8_t *)bp->b_data, + BSSIZE(fs), + NULL, 0); + brelse(bp, 0); + } + + /* Populate checksum block and write it */ + bp = getblk(fs->xf_devvp, base + i, BSSIZE(fs) +#ifdef _KERNEL + , 0, 0 +#endif /* _KERNEL */ + ); + for (j = 0; j < BSSIZE(fs) / sizeof(uint32_t); j++) + ((uint32_t *)bp->b_data)[j] = cksum; + bwrite(bp); + } + + return 0; +} Index: src/sys/fs/exfatfs/exfatfs_extern.h diff -u src/sys/fs/exfatfs/exfatfs_extern.h:1.1.2.2 src/sys/fs/exfatfs/exfatfs_extern.h:1.1.2.3 --- src/sys/fs/exfatfs/exfatfs_extern.h:1.1.2.2 Mon Jul 1 22:15:21 2024 +++ src/sys/fs/exfatfs/exfatfs_extern.h Wed Jul 24 00:38:26 2024 @@ -1,4 +1,4 @@ -/* $NetBSD: exfatfs_extern.h,v 1.1.2.2 2024/07/01 22:15:21 perseant Exp $ */ +/* $NetBSD: exfatfs_extern.h,v 1.1.2.3 2024/07/24 00:38:26 perseant Exp $ */ /*- * Copyright (c) 2022 The NetBSD Foundation, Inc. @@ -51,5 +51,6 @@ int exfatfs_scandir(struct vnode *, off_ void *arg); #define SCANDIR_STOP 0x00000001 #define SCANDIR_DONTFREE 0x00000002 +int exfatfs_write_sb(struct exfatfs *); #endif /* EXFATFS_EXTERN_H_ */ Index: src/sys/fs/exfatfs/exfatfs_tables.c diff -u src/sys/fs/exfatfs/exfatfs_tables.c:1.1.2.2 src/sys/fs/exfatfs/exfatfs_tables.c:1.1.2.3 --- src/sys/fs/exfatfs/exfatfs_tables.c:1.1.2.2 Mon Jul 1 22:15:21 2024 +++ src/sys/fs/exfatfs/exfatfs_tables.c Wed Jul 24 00:38:26 2024 @@ -1,4 +1,4 @@ -/* $NetBSD: exfatfs_tables.c,v 1.1.2.2 2024/07/01 22:15:21 perseant Exp $ */ +/* $NetBSD: exfatfs_tables.c,v 1.1.2.3 2024/07/24 00:38:26 perseant Exp $ */ /*- * Copyright (c) 2022 The NetBSD Foundation, Inc. @@ -27,7 +27,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: exfatfs_tables.c,v 1.1.2.2 2024/07/01 22:15:21 perseant Exp $"); +__KERNEL_RCSID(0, "$NetBSD: exfatfs_tables.c,v 1.1.2.3 2024/07/24 00:38:26 perseant Exp $"); #include <sys/types.h> #include <sys/queue.h> @@ -449,15 +449,44 @@ const uint16_t exfat_invalid_filename_ch 0x003C, 0x003E, 0x003F, 0x005C, 0x007C }; +#define MAX_INVALID_LENGTH 4 +static uint16_t invalid_names[] = { + '.', 0, 0, 0, + '.', '.', 0, 0, + 'C', 'O', 'N', 0, + 'P', 'R', 'N', 0, + 'N', 'U', 'L', 0, + 'C', 'O', 'M', '1', + 'C', 'O', 'M', '2', + 'C', 'O', 'M', '3', + 'C', 'O', 'M', '4', + 'C', 'O', 'M', '5', + 'C', 'O', 'M', '6', + 'C', 'O', 'M', '7', + 'C', 'O', 'M', '8', + 'C', 'O', 'M', '9', + 'C', 'O', 'M', '0', + 'L', 'P', 'T', '1', + 'L', 'P', 'T', '2', + 'L', 'P', 'T', '3', + 'L', 'P', 'T', '4', + 'L', 'P', 'T', '5', + 'L', 'P', 'T', '6', + 'L', 'P', 'T', '7', + 'L', 'P', 'T', '8', + 0, 0, 0, 0 +}; + /* * Check whether a filename is valid. * Returns 0 if valid, non-zero on error. */ int -exfatfs_check_filename_ucs2(uint16_t *name, int len) +exfatfs_check_filename_ucs2(struct exfatfs *fs, uint16_t *name, int len) { int i; unsigned j; + uint16_t *ucs2cp; for (i = 0; i < len; i++) { for (j = 0; j < sizeof(exfat_invalid_filename_chars) @@ -466,6 +495,19 @@ exfatfs_check_filename_ucs2(uint16_t *na return -1; } } + + /* Check name against list of invalid names */ + /* XXX invalid filenames are not in the spec */ + if (len <= MAX_INVALID_LENGTH) { + for (ucs2cp = invalid_names; *ucs2cp != 0; + ucs2cp += MAX_INVALID_LENGTH) { + if (exfatfs_upcase_cmp(fs, name, len, + ucs2cp, MAX_INVALID_LENGTH) + == 0) + return -2; + } + } + return 0; } @@ -473,7 +515,7 @@ MALLOC_JUSTDEFINE(M_EURO, "Exfatfs upcas "Exfatfs upcase table list entry"); void -exfatfs_load_uctable(struct exfatfs *fs, uint16_t *table, int len) +exfatfs_load_uctable(struct exfatfs *fs, const uint16_t *table, int len) { uint16_t begin = 0; int16_t ucoff; @@ -511,6 +553,7 @@ exfatfs_load_uctable(struct exfatfs *fs, begin = current; } } + ++current; } /* Finish up last entry, if any */ if (ucoff != 0) { @@ -548,7 +591,7 @@ exfatfs_upcase(struct exfatfs *fs, uint1 STAILQ_FOREACH(europ, &fs->xf_eurolist, euro_list) { if (europ->euro_begin > wc) break; - if (europ->euro_begin >= wc && wc < europ->euro_end) + if (europ->euro_begin <= wc && wc < europ->euro_end) return wc + europ->euro_ucoff; } return wc; Index: src/sys/fs/exfatfs/exfatfs_tables.h diff -u src/sys/fs/exfatfs/exfatfs_tables.h:1.1.2.2 src/sys/fs/exfatfs/exfatfs_tables.h:1.1.2.3 --- src/sys/fs/exfatfs/exfatfs_tables.h:1.1.2.2 Mon Jul 1 22:15:21 2024 +++ src/sys/fs/exfatfs/exfatfs_tables.h Wed Jul 24 00:38:26 2024 @@ -1,4 +1,4 @@ -/* $NetBSD: exfatfs_tables.h,v 1.1.2.2 2024/07/01 22:15:21 perseant Exp $ */ +/* $NetBSD: exfatfs_tables.h,v 1.1.2.3 2024/07/24 00:38:26 perseant Exp $ */ /*- * Copyright (c) 2022, 2024 The NetBSD Foundation, Inc. @@ -38,8 +38,8 @@ struct exfatfs_upcase_range_offset { int16_t euro_ucoff; /* Offset of uppercase version of characters */ }; -int exfatfs_check_filename_ucs2(uint16_t *, int); -void exfatfs_load_uctable(struct exfatfs *, uint16_t *, int); +int exfatfs_check_filename_ucs2(struct exfatfs *, uint16_t *, int); +void exfatfs_load_uctable(struct exfatfs *, const uint16_t *, int); void exfatfs_destroy_uctable(struct exfatfs *); void exfatfs_upcase_str(struct exfatfs *, uint16_t *, int); int exfatfs_upcase_cmp(struct exfatfs *, uint16_t *, int, uint16_t *, int); Index: src/sys/fs/exfatfs/exfatfs_vfsops.c diff -u src/sys/fs/exfatfs/exfatfs_vfsops.c:1.1.2.4 src/sys/fs/exfatfs/exfatfs_vfsops.c:1.1.2.5 --- src/sys/fs/exfatfs/exfatfs_vfsops.c:1.1.2.4 Fri Jul 19 16:19:16 2024 +++ src/sys/fs/exfatfs/exfatfs_vfsops.c Wed Jul 24 00:38:26 2024 @@ -1,4 +1,4 @@ -/* $NetBSD: exfatfs_vfsops.c,v 1.1.2.4 2024/07/19 16:19:16 perseant Exp $ */ +/* $NetBSD: exfatfs_vfsops.c,v 1.1.2.5 2024/07/24 00:38:26 perseant Exp $ */ /*- * Copyright (c) 2022 The NetBSD Foundation, Inc. @@ -27,7 +27,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: exfatfs_vfsops.c,v 1.1.2.4 2024/07/19 16:19:16 perseant Exp $"); +__KERNEL_RCSID(0, "$NetBSD: exfatfs_vfsops.c,v 1.1.2.5 2024/07/24 00:38:26 perseant Exp $"); struct vm_page; @@ -60,6 +60,7 @@ struct vm_page; #include <fs/exfatfs/exfatfs_extern.h> #include <fs/exfatfs/exfatfs_inode.h> #include <fs/exfatfs/exfatfs_mount.h> +#include <fs/exfatfs/exfatfs_tables.h> #include <fs/exfatfs/exfatfs_vfsops.h> /* #define EXFATFS_VFSOPS_DEBUG */ @@ -615,6 +616,11 @@ exfatfs_unmount(struct mount *mp, int mn DPRINTF((" spec_node_setmountedfs...\n")); if (fs->xf_devvp->v_type != VBAD) spec_node_setmountedfs(fs->xf_devvp, NULL); + DPRINTF((" clear dirty and update free percent...\n")); + fs->xf_VolumeFlags &= ~EXFATFS_VOLUME_DIRTY; + fs->xf_PercentInUse = (fs->xf_ClusterCount - fs->xf_FreeClusterCount) + / fs->xf_ClusterCount; + exfatfs_write_sb(fs); DPRINTF((" lock devvp...\n")); vn_lock(fs->xf_devvp, LK_EXCLUSIVE | LK_RETRY); DPRINTF((" close devvp...\n")); @@ -622,6 +628,8 @@ exfatfs_unmount(struct mount *mp, int mn xmp->xm_flags & EXFATFSMNT_RONLY ? FREAD : FREAD|FWRITE, NOCRED); DPRINTF((" vput devvp...\n")); vput(fs->xf_devvp); + DPRINTF((" free upcase table...\n")); + exfatfs_destroy_uctable(fs); DPRINTF((" free bitmap...\n")); exfatfs_bitmap_destroy(fs); DPRINTF((" destroy lock...\n")); Index: src/sys/fs/exfatfs/exfatfs_vnops.c diff -u src/sys/fs/exfatfs/exfatfs_vnops.c:1.1.2.7 src/sys/fs/exfatfs/exfatfs_vnops.c:1.1.2.8 --- src/sys/fs/exfatfs/exfatfs_vnops.c:1.1.2.7 Fri Jul 19 16:19:16 2024 +++ src/sys/fs/exfatfs/exfatfs_vnops.c Wed Jul 24 00:38:27 2024 @@ -1,4 +1,4 @@ -/* $NetBSD: exfatfs_vnops.c,v 1.1.2.7 2024/07/19 16:19:16 perseant Exp $ */ +/* $NetBSD: exfatfs_vnops.c,v 1.1.2.8 2024/07/24 00:38:27 perseant Exp $ */ /*- * Copyright (c) 2022 The NetBSD Foundation, Inc. @@ -27,7 +27,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: exfatfs_vnops.c,v 1.1.2.7 2024/07/19 16:19:16 perseant Exp $"); +__KERNEL_RCSID(0, "$NetBSD: exfatfs_vnops.c,v 1.1.2.8 2024/07/24 00:38:27 perseant Exp $"); #include <sys/buf.h> #include <sys/dirent.h> @@ -1003,17 +1003,23 @@ exfatfs_alloc(struct vnode *dvp, struct fs = dxip->xi_fs; KASSERT(fs != NULL); - /* Create a new inode */ - xip = exfatfs_newxfinode(fs, 0, 0); - /* * Find an empty location in a directory. If there is none, * extend the directory by enough to hold three entries. * Remember the logical byte offset of the empty space. */ + + /* Convert filename to UCS2 */ ucs2len = exfatfs_utf8ucs2str(cnp->cn_nameptr, cnp->cn_namelen, ucs2filename, EXFATFS_MAX_NAMELEN); - exfatfs_upcase_str(fs, ucs2filename, ucs2len); + /* exfatfs_upcase_str(fs, ucs2filename, ucs2len); */ + + /* Check name for forbidden characters: from section 7.7.3 */ + if (exfatfs_check_filename_ucs2(fs, ucs2filename, ucs2len) != 0) + return EINVAL; + + /* Create a new inode */ + xip = exfatfs_newxfinode(fs, 0, 0); contig = 2 + howmany(ucs2len, EXFATFS_NAME_CHUNKSIZE); DPRINTF(("alloc: namelen %lu -> ucs2len=%lu, contig=%lu\n", @@ -2404,7 +2410,7 @@ detrunc(struct xfinode *xip, off_t bytes * fragmented but has become so. */ static int -rewrite_fat(struct xfinode *xip, uint32_t clustercount) +rewrite_fat(struct xfinode *xip, uint32_t clustercount, int ioflags) { uint32_t lcn, pcn; struct buf *bp = NULL; @@ -2424,13 +2430,20 @@ rewrite_fat(struct xfinode *xip, uint32_ = (lcn == clustercount - 1 ? 0xffffffff : pcn + 1); if (EXFATFS_FATBLK(fs, pcn) != EXFATFS_FATBLK(fs, pcn + 1)) { - bdwrite(bp); + if (ioflags) + bwrite(bp); + else + bdwrite(bp); bp = NULL; } } - if (bp != NULL) - bdwrite(bp); + if (bp != NULL) { + if (ioflags) + bwrite(bp); + else + bdwrite(bp); + } return 0; } @@ -2543,7 +2556,7 @@ deextend(struct xfinode *xip, off_t byte " with 0x%x != 0x%x+1\n", INUM(xip), pcn, opcn)); CLR_DSE_NOFATCHAIN(xip); - if ((error = rewrite_fat(xip, lcn)) != 0) + if ((error = rewrite_fat(xip, lcn, ioflags)) != 0) return error; } if (!IS_DSE_NOFATCHAIN(xip)) { @@ -2561,6 +2574,21 @@ deextend(struct xfinode *xip, off_t byte bwrite(bp); else bdwrite(bp); + + /* And it has no successor */ + if ((error = bread(fs->xf_devvp, + EXFATFS_FATBLK(fs, pcn), + FATBSIZE(fs), 0, &bp)) != 0) + return error; + ((uint32_t *)bp->b_data)[EXFATFS_FATOFF(pcn)] + = 0xffffffff; + DPRINTF(("FAT %lu -> -1\n", + (unsigned long)pcn)); + if (ioflags) + bwrite(bp); + else + bdwrite(bp); + bp = NULL; } }