Subject: kern/23773: Cannot mount msdos filesystems if filesystem size > 128GB
To: None <gnats-bugs@gnats.netbsd.org>
From: None <kivinen@iki.fi>
List: netbsd-bugs
Date: 12/16/2003 11:44:48
>Number:         23773
>Category:       kern
>Synopsis:       Cannot mount msdos filesystems if filesystem size > 128GB
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Tue Dec 16 10:47:00 UTC 2003
>Closed-Date:
>Last-Modified:
>Originator:     Tero Kivinen
>Release:        NetBSD 1.6ZF
>Organization:
>Environment:
System: NetBSD tero.kivinen.iki.fi 1.6ZF NetBSD 1.6ZF (KAAKELI) #1: Wed Dec 3 20:03:46 EET 2003 root@tero.kivinen.iki.fi:/usr/obj/sys/arch/i386/compile/KAAKELI i386
Architecture: i386
Machine: i386
>Description:

	Mount of msdos filesystems larger than 128GB returns invalid
	argument. This is because of the check that fileid
	calculations do not overwrap the 32 bit field.

>How-To-Repeat:

	Get one Maxtor one touch 300 GB FireWire & USB 2.0/1.1 drive
	and try to mount the pre-generated filesystem.

	Also creating new filesystem with newfs_msdos succeeds, but
	the resultant filesystem cannot be mounted. 

>Fix:

	Here is a patch that will change the fileid calculation to use
	32-bit hash of the 64-bit fileid number if the filesystem size
	exeeds 128GB. This can result invalid output of the pwd, if
	the parent directory contains two files which happen to have
	same fileid numbers.

Index: msdosfs_denode.c
===================================================================
RCS file: /cvsroot/src/sys/fs/msdosfs/msdosfs_denode.c,v
retrieving revision 1.4
diff -u -r1.4 msdosfs_denode.c
--- msdosfs_denode.c	29 Jun 2003 22:31:09 -0000	1.4
+++ msdosfs_denode.c	3 Dec 2003 18:40:21 -0000
@@ -680,3 +680,16 @@
 {
 	return 0;
 }
+
+u_long
+msdos_fileid_hash(uint64_t fileid)
+{
+	uint64_t c1 = 0x6e5ea73858134343LL;
+	uint64_t c2 = 0xb34e8f99a2ec9ef5LL;
+	fileid ^= ((c1 ^ fileid) >> 32);
+	fileid *= c1;
+	fileid ^= ((c2 ^ fileid) >> 31);
+	fileid *= c2;
+	fileid ^= ((c1 ^ fileid) >> 32);
+	return (u_long) (fileid & 0xffffffffL);
+}
Index: msdosfs_vfsops.c
===================================================================
RCS file: /cvsroot/src/sys/fs/msdosfs/msdosfs_vfsops.c,v
retrieving revision 1.11
diff -u -r1.11 msdosfs_vfsops.c
--- msdosfs_vfsops.c	14 Oct 2003 14:02:56 -0000	1.11
+++ msdosfs_vfsops.c	3 Dec 2003 18:40:21 -0000
@@ -516,8 +516,8 @@
 		 * due to fileid limitations (see msdosfs_getattr and
 		 * msdosfs_readdir)
 		 */
-		error = EINVAL;
-		goto error_exit;
+	  	printf("Warning: filesystem size > 128GB using hashing for fileids\n");
+		pmp->pm_flags |= MSDOSFS_HASHFILEIDS;
 	}
 
 	if (pmp->pm_RootDirEnts == 0) {
Index: msdosfs_vnops.c
===================================================================
RCS file: /cvsroot/src/sys/fs/msdosfs/msdosfs_vnops.c,v
retrieving revision 1.7
diff -u -r1.7 msdosfs_vnops.c
--- msdosfs_vnops.c	7 Sep 2003 22:09:11 -0000	1.7
+++ msdosfs_vnops.c	3 Dec 2003 18:40:22 -0000
@@ -284,7 +284,7 @@
 	mode_t mode;
 	struct timespec ts;
 	u_long dirsperblk = pmp->pm_BytesPerSec / sizeof(struct direntry);
-	u_long fileid;
+	uint64_t fileid;
 
 	TIMEVAL_TO_TIMESPEC(&time, &ts);
 	DETIMES(dep, &ts, &ts, &ts, pmp->pm_gmtoff);
@@ -304,7 +304,7 @@
 			fileid = roottobn(pmp, 0) * dirsperblk;
 		fileid += dep->de_diroffset / sizeof(struct direntry);
 	}
-	vap->va_fileid = fileid;
+	vap->va_fileid = msdosdirenttofileid(pmp, fileid);
 	if ((dep->de_Attributes & ATTR_READONLY) == 0)
 		mode = S_IRWXU|S_IRWXG|S_IRWXO;
 	else
@@ -1418,7 +1418,7 @@
 	long lost;
 	long count;
 	u_long cn;
-	u_long fileno;
+	uint64_t fileno;
 	u_long dirsperblk;
 	long bias = 0;
 	daddr_t bn, lbn;
@@ -1492,11 +1492,11 @@
 			for (n = (int)offset / sizeof(struct direntry);
 			     n < 2; n++) {
 				if (FAT32(pmp))
-					dirbuf.d_fileno = cntobn(pmp,
+					dirbuf.d_fileno = msdosdirenttofileid(pmp, cntobn(pmp,
 								 pmp->pm_rootdirblk)
-							  * dirsperblk;
+							  * dirsperblk);
 				else
-					dirbuf.d_fileno = 1;
+					dirbuf.d_fileno = msdosdirenttofileid(pmp, 1);
 				dirbuf.d_type = DT_DIR;
 				switch (n) {
 				case 0:
@@ -1609,10 +1609,10 @@
 						fileno = 1;
 				else
 					fileno = cntobn(pmp, fileno) * dirsperblk;
-				dirbuf.d_fileno = fileno;
+				dirbuf.d_fileno = msdosdirenttofileid(pmp, fileno);
 				dirbuf.d_type = DT_DIR;
 			} else {
-				dirbuf.d_fileno = offset / sizeof(struct direntry);
+				dirbuf.d_fileno = msdosdirenttofileid(pmp, offset / sizeof(struct direntry));
 				dirbuf.d_type = DT_REG;
 			}
 			if (chksum != winChksum(dentp->deName))
Index: msdosfsmount.h
===================================================================
RCS file: /cvsroot/src/sys/fs/msdosfs/msdosfsmount.h,v
retrieving revision 1.5
diff -u -r1.5 msdosfsmount.h
--- msdosfsmount.h	3 Oct 2003 16:34:31 -0000	1.5
+++ msdosfsmount.h	3 Dec 2003 18:40:22 -0000
@@ -82,6 +82,7 @@
 #define	MSDOSFSMNT_RONLY	0x80000000	/* mounted read-only	*/
 #define	MSDOSFSMNT_WAITONFAT	0x40000000	/* mounted synchronous	*/
 #define	MSDOSFS_FATMIRROR	0x20000000	/* FAT is mirrored */
+#define MSDOSFS_HASHFILEIDS	0x10000000	/* Hash fileids */
 
 #define MSDOSFSMNT_BITS "\177\20" \
     "b\00shortname\0b\01longname\0b\02nowin95\0b\03gemdosfs\0b\04mntversioned\0" \
@@ -224,10 +225,20 @@
 	 : cntobn((pmp), (dirclu)))
 
 /*
+ * Calculate va_fileid based on the 64 bit fileid.
+ */
+#define msdosdirenttofileid(pmp, fileid) \
+  ((pmp->pm_flags & MSDOSFS_HASHFILEIDS) \
+    ? (u_long) msdos_fileid_hash(fileid) \
+    : (u_long) (fileid & 0xffffffffL))
+         
+
+/*
  * Prototypes for MSDOSFS virtual filesystem operations
  */
 void msdosfs_init __P((void));
 void msdosfs_reinit __P((void));
 void msdosfs_done __P((void));
+u_long msdos_fileid_hash __P((uint64_t));
 
 #endif /* _KERNEL */

>Release-Note:
>Audit-Trail:
>Unformatted:
 NetBSD current checkout date 2003-12-01