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;
 				}
 			}

Reply via email to