changeset 2720:a82b4631f707

PSARC 2005/361 PCFS timestamp handling cleanup 4401668 RFE: pcfs should support force unmount 6248624 pcfs file timestamp handling needs cleanup 6278733 pcfs statvfs implementation is slow 6351785 pcfs should support cluster sizes over 32Kb for Fat32 despite violating the spec 6364444 pcfs can still crash in pc_getcluster with one-off overflow
author frankho
date Wed, 13 Sep 2006 07:42:50 -0700
parents 836fea220612
children a1fe0f523f8f
files usr/src/cmd/fs.d/pcfs/fsck/Makefile usr/src/cmd/fs.d/pcfs/fsck/dir.c usr/src/cmd/fs.d/pcfs/mount/mount.c usr/src/uts/common/fs/pcfs/pc_alloc.c usr/src/uts/common/fs/pcfs/pc_dir.c usr/src/uts/common/fs/pcfs/pc_node.c usr/src/uts/common/fs/pcfs/pc_subr.c usr/src/uts/common/fs/pcfs/pc_vfsops.c usr/src/uts/common/fs/pcfs/pc_vnops.c usr/src/uts/common/sys/fs/pc_dir.h usr/src/uts/common/sys/fs/pc_fs.h
diffstat 11 files changed, 585 insertions(+), 531 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/cmd/fs.d/pcfs/fsck/Makefile	Wed Sep 13 02:19:44 2006 -0700
+++ b/usr/src/cmd/fs.d/pcfs/fsck/Makefile	Wed Sep 13 07:42:50 2006 -0700
@@ -2,9 +2,8 @@
 # CDDL HEADER START
 #
 # The contents of this file are subject to the terms of the
-# Common Development and Distribution License, Version 1.0 only
-# (the "License").  You may not use this file except in compliance
-# with the License.
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
 #
 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 # or http://www.opensolaris.org/os/licensing.
@@ -22,8 +21,8 @@
 #
 #ident	"%Z%%M%	%I%	%E% SMI"
 #
-# Copyright (c) 1999,2001 by Sun Microsystems, Inc.
-# All rights reserved.
+# Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
 #
 # cmd/fs.d/pcfs/fsck/Makefile
 
@@ -42,18 +41,8 @@
 #DEBUGOBJS=	inject.o
 #DEBUGSRCS=	$(DEBUGOBJS:%.o=%.c)
 
-#
-# We grab one file from the kernel.  It has a relatively complex routine
-# for manipulating time values into the format expected within directory
-# entries in the FAT file system [pc_tvtopct()].  It seemed better not to
-# duplicate this code.
-#
-PCFSDIR=	../../../../uts/common/fs/pcfs
-PCFSOBJS=	pc_subr.o
-PCFSSRCS=	$(PCFSOBJS:%.o=%.c)
-
-OBJS=		$(FSCKOBJS) $(PCFSOBJS) $(DEBUGOBJS)
-SRCS=		$(FSCKSRCS) $(PCFSDIR)/$(PCFSSRCS) $(DEBUGSRCS)
+OBJS=		$(FSCKOBJS) $(DEBUGOBJS)
+SRCS=		$(FSCKSRCS) $(DEBUGSRCS)
 LSRCS=		$(FSCKSRCS) $(DEBUGSRCS)
 
 # for messaging catalog
@@ -79,13 +68,10 @@
 		$(LINK.c) -o $@ $(OBJS) $(LDLIBS)
 		$(POST_PROCESS)
 
-%.o:	$(PCFSDIR)/%.c
-	$(COMPILE.c) -D_KERNEL $<
-
 lint_LSRCS:
 	$(LINT.c) $(LSRCS) $(LDLIBS)
 
 lint:	lint_LSRCS
 
 clean:
-	$(RM) $(FSCKOBJS) $(PCFSOBJS) $(DEBUGOBJS)
+	$(RM) $(FSCKOBJS) $(DEBUGOBJS)
--- a/usr/src/cmd/fs.d/pcfs/fsck/dir.c	Wed Sep 13 02:19:44 2006 -0700
+++ b/usr/src/cmd/fs.d/pcfs/fsck/dir.c	Wed Sep 13 07:42:50 2006 -0700
@@ -2,9 +2,8 @@
  * CDDL HEADER START
  *
  * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -20,8 +19,8 @@
  * CDDL HEADER END
  */
 /*
- * Copyright (c) 1999,2000 by Sun Microsystems, Inc.
- * All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
  */
 
 #pragma ident	"%Z%%M%	%I%	%E% SMI"
@@ -35,8 +34,10 @@
 #include <stdlib.h>
 #include <libintl.h>
 #include <ctype.h>
+#include <time.h>
 #include <sys/param.h>
 #include <sys/time.h>
+#include <sys/byteorder.h>
 #include <sys/dktp/fdisk.h>
 #include <sys/fs/pc_fs.h>
 #include <sys/fs/pc_dir.h>
@@ -67,12 +68,6 @@
 int	OkayToRelink = 1;
 
 /*
- *  We import these routines from the pc_subr.c file in the kernel.
- */
-extern void pc_tvtopct(timestruc_t *, struct pctime *);
-extern int pc_validchar(char);
-
-/*
  * We have a bunch of routines for handling CHK names.  A CHK name is
  * simply a file name of the form "FILEnnnn.CHK", where the n's are the
  * digits in the numbers from 1 to 9999.  There are always four digits
@@ -822,40 +817,66 @@
 	markClusterModified(clusterWithSlot);
 }
 
+/*
+ *  Convert current UNIX time into a PCFS timestamp (which is in local time).
+ *
+ *  Since the "seconds" field of that is only accurate to 2sec precision,
+ *  we allow for the optional (used only for creation times on FAT) "msec"
+ *  parameter that takes the fractional part.
+ */
 static void
-getNow(timestruc_t *ts)
+getNow(struct pctime *pctp, uchar_t *msec)
 {
-	struct timeval	tv;
+	time_t		now;
+	struct tm	tm;
+	ushort_t	tim, dat;
+
+	/*
+	 * Disable daylight savings corrections - Solaris PCFS doesn't
+	 * support such conversions yet. Save timestamps in local time.
+	 */
+	daylight = 0;
 
-	if (gettimeofday(&tv, NULL) == 0) {
-		ts->tv_sec = tv.tv_sec;
-		ts->tv_nsec = tv.tv_usec * 1000;
-	} else {
-		/* Failed to get time, set create time to the Solaris epoch */
-		ts->tv_sec = 0;
-		ts->tv_nsec = 0;
-	}
+	(void) time(&now);
+	(void) localtime_r(&now, &tm);
+
+	dat = (tm.tm_year - 80) << YEARSHIFT;
+	dat |= tm.tm_mon << MONSHIFT;
+	dat |= tm.tm_mday << DAYSHIFT;
+	tim = tm.tm_hour << HOURSHIFT;
+	tim |= tm.tm_min << MINSHIFT;
+	tim |= (tm.tm_sec / 2) << SECSHIFT;
+
+	/*
+	 * Sanity check. If we overflow the PCFS timestamp range
+	 * we set the time to 01/01/1980, 00:00:00
+	 */
+	if (dat < 80 || dat > 227)
+		dat = tim = 0;
+
+	pctp->pct_date = LE_16(dat);
+	pctp->pct_time = LE_16(tim);
+	if (msec)
+		*msec = (tm.tm_sec & 1) ? 100 : 0;
 }
 
 /*
  *  FAT file systems store the following time information in a directory
  *  entry:
- *		creation time
- *		creation date
- *		last access date
- *		last modify time
- *		last modify date
+ *		timestamp		member of "struct pcdir"
+ * ======================================================================
+ *		creation time		pcd_crtime.pct_time
+ *		creation date		pcd_crtime.pct_date
+ *		last access date	pcd_ladate
+ *		last modify time	pcd_mtime.pct_time
+ *		last modify date	pcd_mtime.pct_date
  *
  *  No access time is kept.
  */
 static void
 updateDirEnt_CreatTime(struct pcdir *dp)
 {
-	timestruc_t	ts;
-
-	getNow(&ts);
-	pc_tvtopct(&ts, &(dp->pcd_crtime)); /* sets creation time/date */
-	dp->pcd_crtime_msec = 0;
+	getNow(&dp->pcd_crtime, &dp->pcd_crtime_msec);
 	markClusterModified(findImpactedCluster(dp));
 }
 
@@ -864,9 +885,8 @@
 {
 	timestruc_t	ts;
 
-	getNow(&ts);
-	pc_tvtopct(&ts, &(dp->pcd_mtime)); /* sets modification time/date */
-	dp->pcd_ladate = dp->pcd_mtime.pct_date; /* sets access date */
+	getNow(&dp->pcd_mtime, NULL);
+	dp->pcd_ladate = dp->pcd_mtime.pct_date;
 	dp->pcd_attr |= PCA_ARCH;
 	markClusterModified(findImpactedCluster(dp));
 }
--- a/usr/src/cmd/fs.d/pcfs/mount/mount.c	Wed Sep 13 02:19:44 2006 -0700
+++ b/usr/src/cmd/fs.d/pcfs/mount/mount.c	Wed Sep 13 07:42:50 2006 -0700
@@ -2,9 +2,8 @@
  * CDDL HEADER START
  *
  * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -22,7 +21,7 @@
 #pragma ident	"%Z%%M%	%I%	%E% SMI"
 
 /*
- * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -131,6 +130,7 @@
 		    "special mount_point\n"), typename);
 		(void) fprintf(stderr, gettext(
 		    "\tpcfs-specific suboptions are:\n"
+		    "\t     clamptime,noclamptime\n"
 		    "\t     foldcase,nofoldcase\n"));
 		exit(32);
 	}
--- a/usr/src/uts/common/fs/pcfs/pc_alloc.c	Wed Sep 13 02:19:44 2006 -0700
+++ b/usr/src/uts/common/fs/pcfs/pc_alloc.c	Wed Sep 13 07:42:50 2006 -0700
@@ -2,9 +2,8 @@
  * CDDL HEADER START
  *
  * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -20,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -331,18 +330,26 @@
 pc_freeclusters(struct pcfs *fsp)
 {
 	pc_cluster32_t cn;
-	int free;
+	int free = 0;
+
+	if (IS_FAT32(fsp) &&
+	    fsp->fsinfo_native.fs_free_clusters != FSINFO_UNKNOWN)
+		return (fsp->fsinfo_native.fs_free_clusters);
 
 	/*
 	 * make sure the FAT is in core
 	 */
-	free = 0;
 	for (cn = PCF_FIRSTCLUSTER;
-	    (int)cn <= fsp->pcfs_ncluster; cn++) {
+	    (int)cn < fsp->pcfs_ncluster; cn++) {
 		if (pc_getcluster(fsp, cn) == PCF_FREECLUSTER) {
 			free++;
 		}
 	}
+
+	if (IS_FAT32(fsp)) {
+		ASSERT(fsp->fsinfo_native.fs_free_clusters == FSINFO_UNKNOWN);
+		fsp->fsinfo_native.fs_free_clusters = free;
+	}
 	return (free);
 }
 
@@ -446,7 +453,7 @@
 		panic("pc_addcluster: no FAT");
 
 	for (cn = fsp->pcfs_nxfrecls;
-	    (int)cn <= fsp->pcfs_ncluster; cn++) {
+	    (int)cn < fsp->pcfs_ncluster; cn++) {
 		if (pc_getcluster(fsp, cn) == PCF_FREECLUSTER) {
 			struct buf *bp;
 
--- a/usr/src/uts/common/fs/pcfs/pc_dir.c	Wed Sep 13 02:19:44 2006 -0700
+++ b/usr/src/uts/common/fs/pcfs/pc_dir.c	Wed Sep 13 07:42:50 2006 -0700
@@ -285,7 +285,9 @@
 
 	ep = &direntries[ndirentries - 1];
 	gethrestime(&now);
-	pc_tvtopct(&now, &ep->pcd_mtime);
+	if (error = pc_tvtopct(&now, &ep->pcd_mtime))
+		return (error);
+
 	ep->pcd_crtime = ep->pcd_mtime;
 	ep->pcd_ladate = ep->pcd_mtime.pct_date;
 	ep->pcd_crtime_msec = 0;
--- a/usr/src/uts/common/fs/pcfs/pc_node.c	Wed Sep 13 02:19:44 2006 -0700
+++ b/usr/src/uts/common/fs/pcfs/pc_node.c	Wed Sep 13 07:42:50 2006 -0700
@@ -2,9 +2,8 @@
  * CDDL HEADER START
  *
  * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -20,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -61,7 +60,7 @@
  * fake entry for root directory, since this does not have a parent
  * pointing to it.
  */
-static struct pcdir rootentry = {
+struct pcdir pcfs_rootdirentry = {
 	"",
 	"",
 	PCA_DIR
@@ -96,7 +95,7 @@
 
 	ASSERT(fsp->pcfs_flags & PCFS_LOCKED);
 	if (ep == (struct pcdir *)0) {
-		ep = &rootentry;
+		ep = &pcfs_rootdirentry;
 		scluster = 0;
 	} else {
 		scluster = pc_getstartcluster(fsp, ep);
@@ -176,6 +175,7 @@
 		pcp->pc_size = ltohi(ep->pcd_size);
 	}
 	fsp->pcfs_nrefs++;
+	VFS_HOLD(PCFSTOVFS(fsp));
 	vp->v_data = (caddr_t)pcp;
 	vp->v_vfsp = PCFSTOVFS(fsp);
 	vn_exists(vp);
@@ -262,6 +262,7 @@
 		fsp->pcfs_frefs--;
 	}
 	fsp->pcfs_nrefs--;
+	VFS_RELE(vp->v_vfsp);
 
 	if (fsp->pcfs_nrefs < 0) {
 		panic("pc_rele: nrefs count");
@@ -286,7 +287,10 @@
 
 	if (PCTOV(pcp)->v_type == VREG) {
 		gethrestime(&now);
-		pc_tvtopct(&now, &pcp->pc_entry.pcd_mtime);
+		if (pc_tvtopct(&now, &pcp->pc_entry.pcd_mtime))
+			PC_DPRINTF1(2, "pc_mark_mod failed timestamp "
+			    "conversion, curtime = %lld\n",
+			    (long long)now.tv_sec);
 		pcp->pc_flags |= PC_CHG;
 	}
 }
@@ -297,12 +301,15 @@
 void
 pc_mark_acc(struct pcnode *pcp)
 {
-	struct pctime pt;
+	struct pctime pt = { 0, 0 };
 	timestruc_t now;
 
 	if (PCTOV(pcp)->v_type == VREG) {
 		gethrestime(&now);
-		pc_tvtopct(&now, &pt);
+		if (pc_tvtopct(&now, &pcp->pc_entry.pcd_mtime))
+			PC_DPRINTF1(2, "pc_mark_acc failed timestamp "
+			    "conversion, curtime = %lld\n",
+			    (long long)now.tv_sec);
 		pcp->pc_entry.pcd_ladate = pt.pct_date;
 		pcp->pc_flags |= PC_CHG;
 	}
@@ -619,10 +626,11 @@
 void
 pc_diskchanged(struct pcfs *fsp)
 {
-	struct pcnode *pcp, *npcp = NULL;
-	struct pchead *hp;
-	struct vnode  *vp;
-	extern vfs_t    EIO_vfs;
+	struct pcnode	*pcp, *npcp = NULL;
+	struct pchead	*hp;
+	struct vnode	*vp;
+	extern vfs_t	EIO_vfs;
+	struct vfs	*vfsp;
 
 	/*
 	 * Eliminate all pcnodes (dir & file) associated with this fs.
@@ -633,12 +641,14 @@
 	 */
 	PC_DPRINTF1(1, "pc_diskchanged fsp=0x%p\n", (void *)fsp);
 
+	vfsp = PCFSTOVFS(fsp);
+
 	for (hp = pcdhead; hp < &pcdhead[NPCHASH]; hp++) {
 		for (pcp = hp->pch_forw;
 		    pcp != (struct pcnode *)hp; pcp = npcp) {
 			npcp = pcp -> pc_forw;
 			vp = PCTOV(pcp);
-			if (VFSTOPCFS(vp->v_vfsp) == fsp &&
+			if ((vp->v_vfsp == vfsp) &&
 			    !(pcp->pc_flags & PC_RELEHOLD)) {
 				mutex_enter(&(vp)->v_lock);
 				if (vp->v_count > 0) {
@@ -652,10 +662,16 @@
 				vp->v_vfsp = &EIO_vfs;
 				vp->v_type = VBAD;
 				VN_RELE(vp);
-				if (!(pcp->pc_flags & PC_EXTERNAL))
+				if (!(pcp->pc_flags & PC_EXTERNAL)) {
+					(void) pvn_vplist_dirty(vp,
+					    (u_offset_t)0, pcfs_putapage,
+					    B_INVAL | B_TRUNC,
+					    (struct cred *)NULL);
 					vn_free(vp);
+				}
 				kmem_free(pcp, sizeof (struct pcnode));
 				fsp->pcfs_nrefs --;
+				VFS_RELE(vfsp);
 			}
 		}
 	}
@@ -664,7 +680,7 @@
 		    pcp != (struct pcnode *)hp; pcp = npcp) {
 			npcp = pcp -> pc_forw;
 			vp = PCTOV(pcp);
-			if (VFSTOPCFS(vp->v_vfsp) == fsp &&
+			if ((vp->v_vfsp == vfsp) &&
 			    !(pcp->pc_flags & PC_RELEHOLD)) {
 				mutex_enter(&(vp)->v_lock);
 				if (vp->v_count > 0) {
@@ -678,11 +694,17 @@
 				vp->v_vfsp = &EIO_vfs;
 				vp->v_type = VBAD;
 				VN_RELE(vp);
-				if (!(pcp->pc_flags & PC_EXTERNAL))
+				if (!(pcp->pc_flags & PC_EXTERNAL)) {
+					(void) pvn_vplist_dirty(vp,
+					    (u_offset_t)0, pcfs_putapage,
+					    B_INVAL | B_TRUNC,
+					    (struct cred *)NULL);
 					vn_free(vp);
+				}
 				kmem_free(pcp, sizeof (struct pcnode));
 				fsp->pcfs_frefs --;
 				fsp->pcfs_nrefs --;
+				VFS_RELE(vfsp);
 			}
 		}
 	}
@@ -696,7 +718,8 @@
 		panic("pc_diskchanged: nrefs");
 	}
 #endif
-	if (fsp->pcfs_fatp != (uchar_t *)0) {
+	if (!(vfsp->vfs_flag & VFS_UNMOUNTED) &&
+	    fsp->pcfs_fatp != (uchar_t *)0) {
 		pc_invalfat(fsp);
 	} else {
 		binval(fsp->pcfs_xdev);
--- a/usr/src/uts/common/fs/pcfs/pc_subr.c	Wed Sep 13 02:19:44 2006 -0700
+++ b/usr/src/uts/common/fs/pcfs/pc_subr.c	Wed Sep 13 07:42:50 2006 -0700
@@ -1,6 +1,26 @@
 /*
- * Copyright (c) 1989, 1998 by Sun Microsystems, Inc.
- * All rights reserved.
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
  */
 
 /*
@@ -17,410 +37,214 @@
 
 #include <sys/param.h>
 #include <sys/time.h>
-#include <sys/buf.h>
 #include <sys/conf.h>
 #include <sys/sysmacros.h>
-#include <sys/kmem.h>
 #include <sys/vfs.h>
 #include <sys/debug.h>
 #include <sys/errno.h>
+#include <sys/cmn_err.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/byteorder.h>
 #include <sys/fs/pc_fs.h>
 #include <sys/fs/pc_label.h>
 #include <sys/fs/pc_dir.h>
 #include <sys/fs/pc_node.h>
 
 /*
- * Structure returned by gmtime and localtime calls (see ctime(3)).
- */
-struct tm {
-	short	tm_sec;
-	short	tm_min;
-	short	tm_hour;
-	short	tm_mday;
-	short	tm_mon;
-	short	tm_year;
-	short	tm_wday;
-	short	tm_yday;
-	short	tm_isdst;
-};
-
-void pc_tvtopct(timestruc_t *, struct pctime *);
-void pc_pcttotv(struct pctime *, timestruc_t *);
-int pc_validchar(char);
-
-static struct tm *localtime(time_t *tim);
-static int sunday(struct tm *, int);
-static int dysize(int);
-static struct tm *gmtime(int);
-static time_t ctime(struct tm *);
-
-/* The cm struct defines tm_year relative to 1900 */
-#define	YEAR_ZERO	1900
-
-/*
- * convert timestruct to pctime
+ * Convert time between DOS formats:
+ *	- years since 1980
+ *	- months/days/hours/minutes/seconds, local TZ
+ * and the UNIX format (seconds since 01/01/1970, 00:00:00 UT).
+ *
+ * Timezones are adjusted for via mount option arg (secondswest),
+ * but daylight savings time corrections are not made. Calculated
+ * time may therefore end up being wrong by an hour, but this:
+ *	a) will happen as well if media is interchanged between
+ *	   two DOS/Windows-based systems that use different
+ *	   timezone settings
+ *	b) is the best option we have unless we decide to put
+ *	   a full ctime(3C) framework into the kernel, including
+ *	   all conversion tables - AND keeping them current ...
  */
-void
-pc_tvtopct(
-	timestruc_t	*tvp,			/* time input */
-	struct pctime *pctp)		/* pctime output */
-{
-	struct tm *ctp;
 
-	ctp = localtime(&tvp->tv_sec);
-#define	setfield(S, FIELD, SFT, MSK)	\
-	S = (ltohs(S) & ~(MSK << SFT)) | (((FIELD) & MSK) << SFT); S = htols(S);
-
-	setfield(pctp->pct_time, ctp->tm_sec / 2, SECSHIFT, SECMASK);
-	setfield(pctp->pct_time, ctp->tm_min, MINSHIFT, MINMASK);
-	setfield(pctp->pct_time, ctp->tm_hour, HOURSHIFT, HOURMASK);
-	setfield(pctp->pct_date, ctp->tm_mday, DAYSHIFT, DAYMASK);
-	setfield(pctp->pct_date, ctp->tm_mon + 1, MONSHIFT, MONMASK);
-	setfield(pctp->pct_date, ctp->tm_year - 80, YEARSHIFT, YEARMASK);
-#undef setfield
-}
-
-/*
- * convert pctime to timeval
- */
-void
-pc_pcttotv(
-	struct pctime *pctp,		/* ptime input */
-	timestruc_t *tvp)		/* tinmeval output */
-{
-	struct tm tm;
-
-#define	getfield(S, SFT, M)	(((int)(ltohs(S)) >> SFT) & M)
-	tm.tm_sec = getfield(pctp->pct_time, SECSHIFT, SECMASK) * 2;
-	tm.tm_min = getfield(pctp->pct_time, MINSHIFT, MINMASK);
-	tm.tm_hour = getfield(pctp->pct_time, HOURSHIFT, HOURMASK);
-	tm.tm_mday =  getfield(pctp->pct_date, DAYSHIFT, DAYMASK);
-	tm.tm_mon = getfield(pctp->pct_date, MONSHIFT, MONMASK) - 1;
-	tm.tm_year = 80 + getfield(pctp->pct_date, YEARSHIFT, YEARMASK);
-#undef getfield
-	tvp->tv_nsec = 0;
-	tvp->tv_sec = ctime(&tm);
-}
+int pc_tvtopct(timestruc_t *, struct pctime *);
+void pc_pcttotv(struct pctime *, int64_t *);
 
 /*
- * This routine converts time as follows.
- * The epoch is 0000 Jan 1 1970 GMT.
- * The argument time is in seconds since then.
- * The localtime(t) entry returns a pointer to an array
- * containing
- *  seconds (0-59)
- *  minutes (0-59)
- *  hours (0-23)
- *  day of month (1-31)
- *  month (0-11)
- *  year-1900
- *  weekday (0-6, Sun is 0)
- *  day of the year
- *  daylight savings flag
- *
- * The routine calls the system to determine the local
- * timezone and whether Daylight Saving Time is permitted locally.
- * (DST is then determined by the current local rules)
- *
- * The routine does not work
- * in Saudi Arabia which runs on Solar time.
- *
- */
-
-static	int	dmsize[12] =
-{
-	31,
-	28,
-	31,
-	30,
-	31,
-	30,
-	31,
-	31,
-	30,
-	31,
-	30,
-	31
-};
-
-/*
- * The following table is used for 1974 and 1975 and
- * gives the day number of the first day after the Sunday of the
- * change.
+ * Macros/Definitons required to convert between DOS-style and
+ * UNIX-style time recording.
+ * DOS year zero is 1980.
  */
-struct dstab {
-	int	dayyr;
-	int	daylb;
-	int	dayle;
-};
+static int daysinmonth[] =
+	    { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
 
-static struct dstab usdaytab[] = {
-	1974,	5,	333,	/* 1974: Jan 6 - last Sun. in Nov */
-	1975,	58,	303,	/* 1975: Last Sun. in Feb - last Sun in Oct */
-	0,	119,	303,	/* all other years: end Apr - end Oct */
-};
-static struct dstab ausdaytab[] = {
-	1970,	400,	0,	/* 1970: no daylight saving at all */
-	1971,	303,	0,	/* 1971: daylight saving from Oct 31 */
-	1972,	303,	58,	/* 1972: Jan 1 -> Feb 27 & Oct 31 -> dec 31 */
-	0,	303,	65,	/* others: -> Mar 7, Oct 31 -> */
-};
+#define	YEAR_ZERO	1980
+#define	YZ_SECS	(((8 * 365) + (2 * 366)) * 86400)
+#define	FAT_ENDOFTIME	\
+	LE_16(23 << HOURSHIFT | 59 << MINSHIFT | (59/2) << SECSHIFT)
+#define	FAT_ENDOFDATE	\
+	LE_16(127 << YEARSHIFT | 12 << MONSHIFT | 31 << DAYSHIFT)
+#define	leap_year(y) \
+	(((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
 
-/*
- * The European tables ... based on hearsay
- * Believed correct for:
- *	WE:	Great Britain, Ireland, Portugal
- *	ME:	Belgium, Luxembourg, Netherlands, Denmark, Norway,
- *		Austria, Poland, Czechoslovakia, Sweden, Switzerland,
- *		DDR, DBR, France, Spain, Hungary, Italy, Jugoslavia
- * Eastern European dst is unknown, we'll make it ME until someone speaks up.
- *	EE:	Bulgaria, Finland, Greece, Rumania, Turkey, Western Russia
- */
-static struct dstab wedaytab[] = {
-	1983,	86,	303,	/* 1983: end March - end Oct */
-	1984,	86,	303,	/* 1984: end March - end Oct */
-	1985,	86,	303,	/* 1985: end March - end Oct */
-	0,	400,	0,	/* others: no daylight saving at all ??? */
-};
+static int
+days_in_year(int y)
+{
+	return (leap_year((y)) ? 366 : 365);
+}
 
-static struct dstab medaytab[] = {
-	1983,	86,	272,	/* 1983: end March - end Sep */
-	1984,	86,	272,	/* 1984: end March - end Sep */
-	1985,	86,	272,	/* 1985: end March - end Sep */
-	0,	400,	0,	/* others: no daylight saving at all ??? */
-};
-
-static struct dayrules {
-	int		dst_type;	/* number obtained from system */
-	int		dst_hrs;	/* hours to add when dst on */
-	struct	dstab	*dst_rules;	/* one of the above */
-	enum {STH, NTH}	dst_hemi;	/* southern, northern hemisphere */
-} dayrules [] = {
-	DST_USA,	1,	usdaytab,	NTH,
-	DST_AUST,	1,	ausdaytab,	STH,
-	DST_WET,	1,	wedaytab,	NTH,
-	DST_MET,	1,	medaytab,	NTH,
-	DST_EET,	1,	medaytab,	NTH,	/* XXX */
-	-1,
-};
+static int
+days_in_month(int m, int y)
+{
+	if (m == 2 && leap_year(y))
+		return (29);
+	else
+		return (daysinmonth[m-1]);
+}
 
 struct pcfs_args pc_tz; /* this is set by pcfs_mount */
 
-static struct tm *
-localtime(time_t *tim)
+/*
+ * Convert time from UNIX to DOS format.
+ * Return EOVERFLOW in case no valid DOS time representation
+ * exists for the given UNIX time.
+ */
+int
+pc_tvtopct(
+	timestruc_t	*tvp,		/* UNIX time input */
+	struct pctime *pctp)		/* pctime output */
 {
-	int dayno;
-	struct tm *ct;
-	int	dalybeg, daylend;
-	struct dayrules *dr;
-	struct dstab *ds;
-	int year;
-	int copyt;
+	uint_t year, month, day, hour, min, sec;
+	int64_t unixtime;
+
+	unixtime = (int64_t)tvp->tv_sec;
+	unixtime -= YZ_SECS;
+	unixtime -= pc_tz.secondswest;
+	if (unixtime <= 0) {
+		/*
+		 * "before beginning of all time" for DOS ...
+		 */
+		return (EOVERFLOW);
+	}
+	for (year = YEAR_ZERO; unixtime >= days_in_year(year) * 86400;
+	    year++)
+		unixtime -= 86400 * days_in_year(year);
+
+	if (year > 127 + YEAR_ZERO) {
+		/*
+		 * "past end of all time" for DOS - can happen
+		 * on a 64bit kernel via utimes() syscall ...
+		 */
+		return (EOVERFLOW);
+	}
 
-	copyt = *tim - (int)pc_tz.secondswest;
-	ct = gmtime(copyt);
-	dayno = ct->tm_yday;
-	for (dr = dayrules; dr->dst_type >= 0; dr++)
-		if (dr->dst_type == pc_tz.dsttime)
-			break;
-	if (dr->dst_type >= 0) {
-		year = ct->tm_year + 1900;
-		for (ds = dr->dst_rules; ds->dayyr; ds++) {
-			if (ds->dayyr == year) {
-				break;
-			}
-		}
-		dalybeg = ds->daylb;	/* first Sun after dst starts */
-		daylend = ds->dayle;	/* first Sun after dst ends */
-		dalybeg = sunday(ct, dalybeg);
-		daylend = sunday(ct, daylend);
-		switch (dr->dst_hemi) {
-		case NTH:
-			if (!(
-			    (dayno > dalybeg ||
-			    (dayno == dalybeg && ct->tm_hour >= 2)) &&
-			    (dayno < daylend ||
-			    (dayno == daylend && ct->tm_hour < 1)))) {
-				return (ct);
-			}
-			break;
-		case STH:
-			if (!(
-			    (dayno > dalybeg ||
-			    (dayno == dalybeg && ct->tm_hour >= 2)) ||
-			    (dayno < daylend ||
-			    (dayno == daylend && ct->tm_hour < 2)))) {
-				return (ct);
-			}
-			break;
-		default:
-		    return (ct);
-		}
-		copyt += dr->dst_hrs*60*60;
-		ct = gmtime(copyt);
-		ct->tm_isdst++;
-	}
-	return (ct);
+	for (month = 1; unixtime >= 86400 * days_in_month(month, year);
+	    month++)
+		unixtime -= 86400 * days_in_month(month, year);
+
+	year -= YEAR_ZERO;
+
+	day = (int)(unixtime / 86400);
+	unixtime -= 86400 * day++;	/* counting starts at 1 */
+
+	hour = (int)(unixtime / 3600);
+	unixtime -= 3600 * hour;
+
+	min = (int)(unixtime / 60);
+	unixtime -= 60 * min;
+
+	sec = (int)unixtime;
+
+	PC_DPRINTF3(1, "ux2pc date: %d.%d.%d\n", day, month, YEAR_ZERO + year);
+	PC_DPRINTF3(1, "ux2pc time: %dh%dm%ds\n", hour, min, sec);
+	PC_DPRINTF1(1, "ux2pc unixtime: %lld\n", (long long)(unixtime));
+
+	ASSERT(year >= 0 && year < 128);
+	ASSERT(month >= 1 && month <= 12);
+	ASSERT(day >= 1 && day <= days_in_month(month, year));
+	ASSERT(hour < 24);
+	ASSERT(min < 60);
+	ASSERT(sec < 60);
+
+	pctp->pct_time =
+	    LE_16(hour << HOURSHIFT | min << MINSHIFT | (sec / 2) << SECSHIFT);
+	pctp->pct_date =
+	    LE_16(year << YEARSHIFT | month << MONSHIFT | day << DAYSHIFT);
+
+	return (0);
 }
 
 /*
- * The argument is a 0-origin day number.
- * The value is the day number of the first
- * Sunday on or after the day.
+ * Convert time from DOS to UNIX time format.
+ * Since FAT timestamps cannot be expressed in 32bit time_t,
+ * the calculation is performed using 64bit values. It's up to
+ * the caller to decide what to do for out-of-UNIX-range values.
  */
-static int
-sunday(struct tm *t, int d)
+void
+pc_pcttotv(
+	struct pctime *pctp,		/* DOS time input */
+	int64_t *unixtime)		/* caller converts to time_t */
 {
-	if (d >= 58)
-		d += dysize(YEAR_ZERO + t->tm_year) - 365;
-	return (d - (d - t->tm_yday + t->tm_wday + 700) % 7);
-}
-
-static int
-dysize(int y)
-{
-	if (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
-		return (366);
-	return (365);
-}
-
-static struct tm *
-gmtime(int tim)
-{
-	int d0, d1;
-	int hms, day;
-	short *tp;
-	static struct tm xtime;
+	uint_t year, month, day, hour, min, sec;
 
-	/*
-	 * break initial number into days
-	 */
-	hms = tim % 86400;
-	day = tim / 86400;
-	if (hms < 0) {
-		hms += 86400;
-		day -= 1;
-	}
-	tp = (short *)&xtime;
-
-	/*
-	 * generate hours:minutes:seconds
-	 */
-	*tp++ = hms%60;
-	d1 = hms/60;
-	*tp++ = d1%60;
-	d1 /= 60;
-	*tp++ = (short)d1;
-
-	/*
-	 * day is the day number.
-	 * generate day of the week.
-	 * The addend is 4 mod 7 (1/1/1970 was Thursday)
-	 */
-
-	xtime.tm_wday = (day+7340036)%7;
-
-	/*
-	 * year number
-	 */
-	if (day >= 0)
-		for (d1 = 70; day >= dysize(YEAR_ZERO + d1); d1++)
-			day -= dysize(YEAR_ZERO + d1);
-	else
-		for (d1 = 70; day < 0; d1--)
-			day += dysize(YEAR_ZERO + d1 - 1);
-	xtime.tm_year = (short)d1;
-	xtime.tm_yday = d0 = day;
+	sec = 2 * ((LE_16(pctp->pct_time) >> SECSHIFT) & SECMASK);
+	min = (LE_16(pctp->pct_time) >> MINSHIFT) & MINMASK;
+	hour = (LE_16(pctp->pct_time) >> HOURSHIFT) & HOURMASK;
+	day = (LE_16(pctp->pct_date) >> DAYSHIFT) & DAYMASK;
+	month = (LE_16(pctp->pct_date) >> MONSHIFT) & MONMASK;
+	year = (LE_16(pctp->pct_date) >> YEARSHIFT) & YEARMASK;
+	year += YEAR_ZERO;
 
 	/*
-	 * generate month
+	 * Basic sanity checks. The FAT timestamp bitfields allow for
+	 * impossible dates/times - return the "FAT epoch" for these.
 	 */
-
-	if (dysize(YEAR_ZERO + d1) == 366)
-		dmsize[1] = 29;
-	for (d1 = 0; d0 >= dmsize[d1]; d1++)
-		d0 -= dmsize[d1];
-	dmsize[1] = 28;
-	*tp++ = d0+1;
-	*tp++ = (short)d1;
-	xtime.tm_isdst = 0;
-	return (&xtime);
-}
-
-/*
- * convert year, month, day, hour, minute, sec to (int)time.
- */
-static time_t
-ctime(struct tm *tp)
-{
-	int i;
-	time_t ct;
-
-	if (tp->tm_mon < 0 || tp->tm_mon > 11 ||
-	    tp->tm_mday < 1 || tp->tm_mday > 31 ||
-	    tp->tm_hour < 0 || tp->tm_hour > 23 ||
-	    tp->tm_min < 0 || tp->tm_min > 59 ||
-	    tp->tm_sec < 0 || tp->tm_sec > 59) {
-		return (0);
+	if (pctp->pct_date == 0) {
+		year = YEAR_ZERO;
+		month = 1;
+		day = 1;
+	}
+	if (month > 12 || month < 1 ||
+	    day < 1 || day > days_in_month(month, year) ||
+	    hour > 23 || min > 59 || sec > 59) {
+		cmn_err(CE_NOTE, "impossible FAT timestamp, "
+		    "d/m/y %d/%d/%d, h:m:s %d:%d:%d",
+		    day, month, year, hour, min, sec);
+		*unixtime = YZ_SECS + pc_tz.secondswest;
+		return;
 	}
-	ct = 0;
-	for (i = /* 19 */ 70; i < tp->tm_year; i++)
-		ct += dysize(YEAR_ZERO + i);
-	/* Leap year */
-	if (dysize(YEAR_ZERO + tp->tm_year) == 366 && tp->tm_mon >= 2)
-		ct++;
-	i = tp->tm_mon + 1;
-	while (--i)
-		ct += dmsize[i-1];
-	ct += tp->tm_mday-1;
-	ct = 24*ct + tp->tm_hour;
-	ct = 60*ct + tp->tm_min;
-	ct = 60*ct + tp->tm_sec;
-	/* convert to GMT assuming local time */
-	ct += (int)pc_tz.secondswest;
-	/* now fix up local daylight time */
-	if (localtime(&ct)->tm_isdst)
-		ct -= 60*60;
-	return (ct);
-}
+
+	PC_DPRINTF3(1, "pc2ux date: %d.%d.%d\n", day, month, year);
+	PC_DPRINTF3(1, "pc2ux time: %dh%dm%ds\n", hour, min, sec);
 
-/*
- * Determine whether a character is valid for a pc 8.3 file system file name.
- * The Windows 95 Resource Kit claims that these are valid:
- *	uppercase letters and numbers
- *	blank
- *	ASCII characters greater than 127
- *	$%'-_@~`!()^#&
- * Long file names can also have
- *	lowercase letters
- *	+,;=[]
- */
-int
-pc_validchar(char c)
-{
-	char *cp;
-	int n;
-	static char valtab[] = {
-		"$#&@!%()-{}<>`_^~|' "
-	};
+	*unixtime = (int64_t)sec;
+	*unixtime += 60 * (int64_t)min;
+	*unixtime += 3600 * (int64_t)hour;
+	*unixtime += 86400 * (int64_t)day;
+	while (month > 1) {
+		month--;
+		*unixtime += 86400 * (int64_t)days_in_month(month, year);
+	}
+	while (year > YEAR_ZERO) {
+		*unixtime += 86400 * (int64_t)days_in_year(year);
+		year--;
+	}
+	/*
+	 * For FAT, the beginning of all time is 01/01/1980,
+	 * and years are counted relative to that.
+	 * We adjust this base value by the timezone offset
+	 * that is passed in to pcfs at mount time.
+	 */
+	*unixtime += YZ_SECS;
+	*unixtime += pc_tz.secondswest;
 
 	/*
-	 * Should be "$#&@!%()-{}`_^~' " ??
-	 * From experiment in DOSWindows, *+=|\[];:",<>.?/ are illegal.
-	 * See IBM DOS4.0 Tech Ref. B-57.
+	 * FAT epoch is past UNIX epoch - negative UNIX times
+	 * cannot result from the conversion.
 	 */
-
-	if (c >= 'A' && c <= 'Z')
-		return (1);
-	if (c >= '0' && c <= '9')
-		return (1);
-	cp = valtab;
-	n = sizeof (valtab);
-	while (n--) {
-		if (c == *cp++)
-			return (1);
-	}
-	return (0);
+	ASSERT(*unixtime > 0);
+	PC_DPRINTF1(1, "pc2ux unixtime: %lld\n", (long long)(*unixtime));
 }
 
 /*
--- a/usr/src/uts/common/fs/pcfs/pc_vfsops.c	Wed Sep 13 02:19:44 2006 -0700
+++ b/usr/src/uts/common/fs/pcfs/pc_vfsops.c	Wed Sep 13 07:42:50 2006 -0700
@@ -63,6 +63,7 @@
 #include <sys/open.h>
 #include <sys/mntent.h>
 #include <sys/policy.h>
+#include <sys/atomic.h>
 
 /*
  * The majority of PC media use a 512 sector size, but
@@ -84,6 +85,7 @@
 static int pc_syncfsnodes(struct pcfs *);
 static int pcfs_sync(struct vfs *, short, struct cred *);
 static int pcfs_vget(struct vfs *vfsp, struct vnode **vpp, struct fid *fidp);
+static void pcfs_freevfs(vfs_t *vfsp);
 
 static int pc_getfattype(struct vnode *, int, daddr_t *, int *);
 static int pc_readfat(struct pcfs *fsp, uchar_t *fatp, daddr_t start,
@@ -94,24 +96,23 @@
  * pcfs mount options table
  */
 
-static char *nohidden_cancel[] = {MNTOPT_PCFS_HIDDEN, NULL};
-static char *hidden_cancel[] = {MNTOPT_PCFS_NOHIDDEN, NULL};
-static char *nofoldcase_cancel[] = {MNTOPT_PCFS_FOLDCASE, NULL};
-static char *foldcase_cancel[] = {MNTOPT_PCFS_NOFOLDCASE, NULL};
+static char *nohidden_cancel[] = { MNTOPT_PCFS_HIDDEN, NULL };
+static char *hidden_cancel[] = { MNTOPT_PCFS_NOHIDDEN, NULL };
+static char *nofoldcase_cancel[] = { MNTOPT_PCFS_FOLDCASE, NULL };
+static char *foldcase_cancel[] = { MNTOPT_PCFS_NOFOLDCASE, NULL };
+static char *clamptime_cancel[] = { MNTOPT_PCFS_NOCLAMPTIME, NULL };
+static char *noclamptime_cancel[] = { MNTOPT_PCFS_CLAMPTIME, NULL };
 
 static mntopt_t mntopts[] = {
 /*
- *	option name		cancel option	default arg	flags
- *		opt data
+ *	option name	cancel option	default arg	flags	opt data
  */
-	{ MNTOPT_PCFS_NOHIDDEN,	nohidden_cancel, NULL,		0,
-		NULL },
-	{ MNTOPT_PCFS_HIDDEN,	hidden_cancel, NULL,		MO_DEFAULT,
-		NULL },
-	{ MNTOPT_PCFS_NOFOLDCASE, nofoldcase_cancel, NULL,	MO_DEFAULT,
-		NULL },
-	{ MNTOPT_PCFS_FOLDCASE,	foldcase_cancel, NULL,		0,
-		NULL }
+	{ MNTOPT_PCFS_NOHIDDEN, nohidden_cancel, NULL, 0, NULL },
+	{ MNTOPT_PCFS_HIDDEN, hidden_cancel, NULL, MO_DEFAULT, NULL },
+	{ MNTOPT_PCFS_NOFOLDCASE, nofoldcase_cancel, NULL, MO_DEFAULT, NULL },
+	{ MNTOPT_PCFS_FOLDCASE, foldcase_cancel, NULL, 0, NULL },
+	{ MNTOPT_PCFS_CLAMPTIME, clamptime_cancel, NULL, MO_DEFAULT, NULL },
+	{ MNTOPT_PCFS_NOCLAMPTIME, noclamptime_cancel, NULL, NULL, NULL }
 };
 
 static mntopts_t pcfs_mntopts = {
@@ -129,9 +130,18 @@
  * pcnodes_lock: protects the pcnode hash table "pcdhead", "pcfhead".
  *
  * Lock hierarchy: pcfslock > pcfs_lock > pcnodes_lock
+ *
+ * pcfs_mountcount:	used to prevent module unloads while there is still
+ *			pcfs state from a former mount hanging around. With
+ *			forced umount support, the filesystem module must not
+ *			be allowed to go away before the last VFS_FREEVFS()
+ *			call has been made.
+ *			Since this is just an atomic counter, there's no need
+ *			for locking.
  */
 kmutex_t	pcfslock;
-krwlock_t pcnodes_lock; /* protect the pcnode hash table "pcdhead", "pcfhead" */
+krwlock_t	pcnodes_lock;
+uint32_t	pcfs_mountcount;
 
 static int pcfstype;
 
@@ -182,6 +192,15 @@
 {
 	int	error;
 
+	/*
+	 * If a forcedly unmounted instance is still hanging around,
+	 * we cannot allow the module to be unloaded because that would
+	 * cause panics once the VFS framework decides it's time to call
+	 * into VFS_FREEVFS().
+	 */
+	if (pcfs_mountcount)
+		return (EBUSY);
+
 	error = mod_remove(&modlinkage);
 	if (error)
 		return (error);
@@ -213,6 +232,7 @@
 		VFSNAME_STATVFS, pcfs_statvfs,
 		VFSNAME_SYNC, (fs_generic_func_p) pcfs_sync,
 		VFSNAME_VGET, pcfs_vget,
+		VFSNAME_FREEVFS, (fs_generic_func_p) pcfs_freevfs,
 		NULL, NULL
 	};
 	int error;
@@ -240,6 +260,7 @@
 
 	pcfstype = fstype;
 	(void) pc_init();
+	pcfs_mountcount = 0;
 	return (0);
 }
 
@@ -313,6 +334,7 @@
 		struct pcfs_args tmp_tz;
 		int hidden = 0;
 		int foldcase = 0;
+		int noclamptime = 0;
 
 		tmp_tz.flags = 0;
 		if (copyin(data, &tmp_tz, datalen)) {
@@ -321,12 +343,15 @@
 		if (datalen == sizeof (struct pcfs_args)) {
 			hidden = tmp_tz.flags & PCFS_MNT_HIDDEN;
 			foldcase = tmp_tz.flags & PCFS_MNT_FOLDCASE;
+			noclamptime = tmp_tz.flags & PCFS_MNT_NOCLAMPTIME;
 		}
 
 		if (hidden)
 			vfs_setmntopt(vfsp, MNTOPT_PCFS_HIDDEN,	NULL, 0);
 		if (foldcase)
 			vfs_setmntopt(vfsp, MNTOPT_PCFS_FOLDCASE, NULL, 0);
+		if (noclamptime)
+			vfs_setmntopt(vfsp, MNTOPT_PCFS_NOCLAMPTIME, NULL, 0);
 		/*
 		 * more than one pc filesystem can be mounted on x86
 		 * so the pc_tz structure is now a critical region
@@ -542,6 +567,8 @@
 		fsp->pcfs_flags |= PCFS_HIDDEN;
 	if (vfs_optionisset(vfsp, MNTOPT_PCFS_FOLDCASE, NULL))
 		fsp->pcfs_flags |= PCFS_FOLDCASE;
+	if (vfs_optionisset(vfsp, MNTOPT_PCFS_NOCLAMPTIME, NULL))
+		fsp->pcfs_flags |= PCFS_NOCLAMPTIME;
 	vfsp->vfs_dev = pseudodev;
 	vfsp->vfs_fstype = pcfstype;
 	vfs_make_fsid(&vfsp->vfs_fsid, pseudodev, pcfstype);
@@ -561,6 +588,7 @@
 	fsp->pcfs_nxt = pc_mounttab;
 	pc_mounttab = fsp;
 	mutex_exit(&pcfslock);
+	atomic_inc_32(&pcfs_mountcount);
 	return (0);
 }
 
@@ -580,38 +608,36 @@
 	if (secpolicy_fs_unmount(cr, vfsp) != 0)
 		return (EPERM);
 
-	/*
-	 * forced unmount is not supported by this file system
-	 * and thus, ENOTSUP, is being returned.
-	 */
-	if (flag & MS_FORCE)
-		return (ENOTSUP);
-
 	PC_DPRINTF0(4, "pcfs_unmount\n");
 	fsp = VFSTOPCFS(vfsp);
+
 	/*
 	 * We don't have to lock fsp because the VVFSLOCK in vfs layer will
 	 * prevent lookuppn from crossing the mount point.
+	 * If this is not a forced umount request and there's ongoing I/O,
+	 * don't allow the mount to proceed.
 	 */
-	if (fsp->pcfs_nrefs) {
+	if (flag & MS_FORCE)
+		vfsp->vfs_flag |= VFS_UNMOUNTED;
+	else if (fsp->pcfs_nrefs)
 		return (EBUSY);
-	}
+
+	mutex_enter(&pcfslock);
 
 	/*
-	 * Allow an unmount (regardless of state) if the fs instance has
-	 * been marked as beyond recovery.
+	 * If this is a forced umount request or if the fs instance has
+	 * been marked as beyond recovery, allow the umount to proceed
+	 * regardless of state. pc_diskchanged() forcibly releases all
+	 * inactive vnodes/pcnodes.
 	 */
-	if (fsp->pcfs_flags & PCFS_IRRECOV) {
-		mutex_enter(&pcfslock);
+	if (flag & MS_FORCE || fsp->pcfs_flags & PCFS_IRRECOV) {
 		rw_enter(&pcnodes_lock, RW_WRITER);
 		pc_diskchanged(fsp);
 		rw_exit(&pcnodes_lock);
-		mutex_exit(&pcfslock);
 	}
 
 	/* now there should be no pcp node on pcfhead or pcdhead. */
 
-	mutex_enter(&pcfslock);
 	if (fsp == pc_mounttab) {
 		pc_mounttab = fsp->pcfs_nxt;
 	} else {
@@ -620,14 +646,14 @@
 				fsp1->pcfs_nxt = fsp->pcfs_nxt;
 	}
 
-	if (fsp->pcfs_fatp != (uchar_t *)0) {
-		pc_invalfat(fsp);
-	}
 	mutex_exit(&pcfslock);
 
-	VN_RELE(fsp->pcfs_devvp);
-	mutex_destroy(&fsp->pcfs_lock);
-	kmem_free(fsp, (uint_t)sizeof (struct pcfs));
+	/*
+	 * Since we support VFS_FREEVFS(), there's no need to
+	 * free the fsp right now. The framework will tell us
+	 * when the right time to do so has arrived by calling
+	 * into pcfs_freevfs.
+	 */
 	return (0);
 }
 
@@ -646,6 +672,7 @@
 	fsp = VFSTOPCFS(vfsp);
 	if (error = pc_lockfs(fsp, 0, 0))
 		return (error);
+
 	pcp = pc_getnode(fsp, (daddr_t)0, 0, (struct pcdir *)0);
 	PC_DPRINTF2(9, "pcfs_root(0x%p) pcp= 0x%p\n",
 	    (void *)vfsp, (void *)pcp);
@@ -768,6 +795,8 @@
 int
 pc_lockfs(struct pcfs *fsp, int diskchanged, int releasing)
 {
+	int err;
+
 	if ((fsp->pcfs_flags & PCFS_IRRECOV) && !releasing)
 		return (EIO);
 
@@ -784,9 +813,10 @@
 		 * had grabbed the lock and set the bit)
 		 */
 		if (!diskchanged && !(fsp->pcfs_flags & PCFS_IRRECOV)) {
-			int err;
-			if ((err = pc_getfat(fsp)))
+			if ((err = pc_getfat(fsp))) {
+				mutex_exit(&fsp->pcfs_lock);
 				return (err);
+			}
 		}
 		fsp->pcfs_flags |= PCFS_LOCKED;
 		fsp->pcfs_owner = curthread;
@@ -1365,7 +1395,7 @@
 		(ltohs(cp[510]) != MBB_MAGIC) ||
 		!VALID_SECSIZE(secsize) ||
 		!VALID_SPCL(bpb->spcl) ||
-		(secsize * bpb->spcl >= (64 * 1024)) ||
+		(secsize * bpb->spcl > (64 * 1024)) ||
 		!(ltohs(bpb->res_sec[0])))
 		return (0);
 
@@ -1599,8 +1629,8 @@
 		fsp->pcfs_spcl = (int)bootp->spcl;
 		fsp->pcfs_fatsec = fatsec;
 		fsp->pcfs_spt = (int)ltohs(bootp->spt);
-		fsp->pcfs_rdirsec = (int)ltohs(bootp->rdirents[0])
-		    * sizeof (struct pcdir) / secsize;
+		fsp->pcfs_rdirsec = ((int)ltohs(bootp->rdirents[0])
+		    * sizeof (struct pcdir) + (secsize - 1)) / secsize;
 		fsp->pcfs_clsize = fsp->pcfs_spcl * secsize;
 		fsp->pcfs_fatstart = fsp->pcfs_dosstart +
 		    (daddr_t)ltohs(bootp->res_sec[0]);
@@ -2157,3 +2187,29 @@
 {
 	return (fsp->pcfs_fat_changemap[bn] == 1);
 }
+
+/*
+ * Implementation of VFS_FREEVFS() to support forced umounts.
+ * This is called by the vfs framework after umount, to trigger
+ * the release of any resources still associated with the given
+ * vfs_t once the need to keep them has gone away.
+ */
+void
+pcfs_freevfs(vfs_t *vfsp)
+{
+	struct pcfs *fsp = VFSTOPCFS(vfsp);
+
+	mutex_enter(&pcfslock);
+	if (fsp->pcfs_fatp != (uchar_t *)0)
+		pc_invalfat(fsp);
+	mutex_exit(&pcfslock);
+
+	VN_RELE(fsp->pcfs_devvp);
+	mutex_destroy(&fsp->pcfs_lock);
+	kmem_free(fsp, (uint_t)sizeof (struct pcfs));
+
+	/*
+	 * Allow _fini() to succeed now, if so desired.
+	 */
+	atomic_dec_32(&pcfs_mountcount);
+}
--- a/usr/src/uts/common/fs/pcfs/pc_vnops.c	Wed Sep 13 02:19:44 2006 -0700
+++ b/usr/src/uts/common/fs/pcfs/pc_vnops.c	Wed Sep 13 07:42:50 2006 -0700
@@ -2,9 +2,8 @@
  * CDDL HEADER START
  *
  * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -20,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -55,6 +54,7 @@
 #include <sys/conf.h>
 #include <sys/flock.h>
 #include <sys/policy.h>
+#include <sys/sdt.h>
 
 #include <vm/seg.h>
 #include <vm/page.h>
@@ -293,6 +293,12 @@
 	rlim64_t limit = uio->uio_llimit;
 	int oresid = uio->uio_resid;
 
+	/*
+	 * If the filesystem was umounted by force, return immediately.
+	 */
+	if (vp->v_vfsp->vfs_flag & VFS_UNMOUNTED)
+		return (EIO);
+
 	PC_DPRINTF4(5, "rwpcp pcp=%p off=%lld resid=%ld size=%u\n", (void *)pcp,
 	    uio->uio_loffset, uio->uio_resid, pcp->pc_size);
 
@@ -514,6 +520,7 @@
 	int error;
 	char attr;
 	struct pctime atime;
+	int64_t unixtime;
 
 	PC_DPRINTF1(8, "pcfs_getattr: vp=%p\n", (void *)vp);
 
@@ -559,11 +566,53 @@
 	    pc_getstartcluster(fsp, &pcp->pc_entry), fsp->pcfs_entps);
 	vap->va_nlink = 1;
 	vap->va_size = (u_offset_t)pcp->pc_size;
-	pc_pcttotv(&pcp->pc_entry.pcd_mtime, &vap->va_mtime);
+
+	pc_pcttotv(&pcp->pc_entry.pcd_mtime, &unixtime);
+	if ((fsp->pcfs_flags & PCFS_NOCLAMPTIME) == 0) {
+		if (unixtime > INT32_MAX)
+			DTRACE_PROBE1(pcfs__mtimeclamped, int64_t, unixtime);
+		unixtime = MIN(unixtime, INT32_MAX);
+	} else if (unixtime > INT32_MAX &&
+	    get_udatamodel() == DATAMODEL_ILP32) {
+		pc_unlockfs(fsp);
+		DTRACE_PROBE1(pcfs__mtimeoverflowed, int64_t, unixtime);
+		return (EOVERFLOW);
+	}
+
+	vap->va_mtime.tv_sec = (time_t)unixtime;
+	vap->va_mtime.tv_nsec = 0;
+
+	/*
+	 * FAT doesn't know about POSIX ctime.
+	 * Best approximation is to always set it to mtime.
+	 */
 	vap->va_ctime = vap->va_mtime;
-	atime.pct_time = 0;
+
+	/*
+	 * FAT only stores "last access date". If that's the
+	 * same as the date of last modification then the time
+	 * of last access is known. Otherwise, use midnight.
+	 */
 	atime.pct_date = pcp->pc_entry.pcd_ladate;
-	pc_pcttotv(&atime, &vap->va_atime);
+	if (atime.pct_date == pcp->pc_entry.pcd_mtime.pct_date)
+		atime.pct_time = pcp->pc_entry.pcd_mtime.pct_time;
+	else
+		atime.pct_time = 0;
+	pc_pcttotv(&atime, &unixtime);
+	if ((fsp->pcfs_flags & PCFS_NOCLAMPTIME) == 0) {
+		if (unixtime > INT32_MAX)
+			DTRACE_PROBE1(pcfs__atimeclamped, int64_t, unixtime);
+		unixtime = MIN(unixtime, INT32_MAX);
+	} else if (unixtime > INT32_MAX &&
+	    get_udatamodel() == DATAMODEL_ILP32) {
+		pc_unlockfs(fsp);
+		DTRACE_PROBE1(pcfs__atimeoverflowed, int64_t, unixtime);
+		return (EOVERFLOW);
+	}
+
+	vap->va_atime.tv_sec = (time_t)unixtime;
+	vap->va_atime.tv_nsec = 0;
+
 	vap->va_rdev = 0;
 	vap->va_nblocks = (fsblkcnt64_t)howmany((offset_t)pcp->pc_size,
 				DEV_BSIZE);
@@ -586,7 +635,7 @@
 	mode_t mask = vap->va_mask;
 	int error;
 	struct pcfs *fsp;
-	timestruc_t now;
+	timestruc_t now, *timep;
 
 	PC_DPRINTF2(6, "pcfs_setattr: vp=%p mask=%x\n", (void *)vp, (int)mask);
 	/*
@@ -596,7 +645,7 @@
 		return (EINVAL);
 	}
 	/*
-	 * pcfs_settar is now allowed on directories to avoid silly warnings
+	 * pcfs_setattr is now allowed on directories to avoid silly warnings
 	 * from 'tar' when it tries to set times on a directory, and console
 	 * printf's on the NFS server when it gets EINVAL back on such a
 	 * request. One possible problem with that since a directory entry
@@ -660,7 +709,7 @@
 	/*
 	 * Change file modified times.
 	 */
-	if ((mask & (AT_MTIME | AT_CTIME)) && (vap->va_mtime.tv_sec != -1)) {
+	if (mask & (AT_MTIME | AT_CTIME)) {
 		/*
 		 * If SysV-compatible option to set access and
 		 * modified times if privileged, owner, or write access,
@@ -668,18 +717,25 @@
 		 *
 		 * XXX - va_mtime.tv_sec == -1 flags this.
 		 */
+		timep = &vap->va_mtime;
 		if (vap->va_mtime.tv_sec == -1) {
 			gethrestime(&now);
-			pc_tvtopct(&now, &pcp->pc_entry.pcd_mtime);
-		} else {
-			pc_tvtopct(&vap->va_mtime, &pcp->pc_entry.pcd_mtime);
+			timep = &now;
 		}
+		if ((fsp->pcfs_flags & PCFS_NOCLAMPTIME) == 0 &&
+		    timep->tv_sec > INT32_MAX) {
+			error = EOVERFLOW;
+			goto out;
+		}
+		error = pc_tvtopct(timep, &pcp->pc_entry.pcd_mtime);
+		if (error)
+			goto out;
 		pcp->pc_flags |= PC_CHG;
 	}
 	/*
 	 * Change file access times.
 	 */
-	if ((mask & (AT_ATIME)) && (vap->va_atime.tv_sec != -1)) {
+	if (mask & AT_ATIME) {
 		/*
 		 * If SysV-compatible option to set access and
 		 * modified times if privileged, owner, or write access,
@@ -689,12 +745,19 @@
 		 */
 		struct pctime	atime;
 
+		timep = &vap->va_atime;
 		if (vap->va_atime.tv_sec == -1) {
 			gethrestime(&now);
-			pc_tvtopct(&now, &atime);
-		} else {
-			pc_tvtopct(&vap->va_atime, &atime);
+			timep = &now;
 		}
+		if ((fsp->pcfs_flags & PCFS_NOCLAMPTIME) == 0 &&
+		    timep->tv_sec > INT32_MAX) {
+			error = EOVERFLOW;
+			goto out;
+		}
+		error = pc_tvtopct(timep, &atime);
+		if (error)
+			goto out;
 		pcp->pc_entry.pcd_ladate = atime.pct_date;
 		pcp->pc_flags |= PC_CHG;
 	}
@@ -778,6 +841,26 @@
 	fsp = VFSTOPCFS(vp->v_vfsp);
 	error = pc_lockfs(fsp, 0, 1);
 
+	/*
+	 * If the filesystem was umounted by force, all dirty
+	 * pages associated with this vnode are invalidated
+	 * and then the vnode will be freed.
+	 */
+	if (vp->v_vfsp->vfs_flag & VFS_UNMOUNTED) {
+		pcp = VTOPC(vp);
+		if (vn_has_cached_data(vp)) {
+			(void) pvn_vplist_dirty(vp, (u_offset_t)0,
+			    pcfs_putapage, B_INVAL, (struct cred *)NULL);
+		}
+		remque(pcp);
+		if (error == 0)
+			pc_unlockfs(fsp);
+		vn_free(vp);
+		kmem_free(pcp, sizeof (struct pcnode));
+		VFS_RELE(PCFSTOVFS(fsp));
+		return;
+	}
+
 	mutex_enter(&vp->v_lock);
 	ASSERT(vp->v_count >= 1);
 	if (vp->v_count > 1) {
@@ -793,10 +876,15 @@
 	 * with a subsequent pc_diskchanged() call has released
 	 * the pcnode.  If it has then release the vnode as above.
 	 */
-	if ((pcp = VTOPC(vp)) == NULL)
+	if ((pcp = VTOPC(vp)) == NULL) {
+		if (vn_has_cached_data(vp))
+			(void) pvn_vplist_dirty(vp, (u_offset_t)0,
+			    pcfs_putapage, B_INVAL | B_TRUNC,
+			    (struct cred *)NULL);
 		vn_free(vp);
-	else
+	} else {
 		pc_rele(pcp);
+	}
 
 	if (!error)
 		pc_unlockfs(fsp);
@@ -818,6 +906,12 @@
 	int error;
 
 	/*
+	 * If the filesystem was umounted by force, return immediately.
+	 */
+	if (dvp->v_vfsp->vfs_flag & VFS_UNMOUNTED)
+		return (EIO);
+
+	/*
 	 * verify that the dvp is still valid on the disk
 	 */
 	fsp = VFSTOPCFS(dvp->v_vfsp);
@@ -1116,6 +1210,12 @@
 	struct pc_dirent *ld = &lbp;
 	int error;
 
+	/*
+	 * If the filesystem was umounted by force, return immediately.
+	 */
+	if (dvp->v_vfsp->vfs_flag & VFS_UNMOUNTED)
+		return (EIO);
+
 	if ((uiop->uio_iovcnt != 1) ||
 	    (uiop->uio_loffset % sizeof (struct pcdir)) != 0) {
 		return (EINVAL);
@@ -1249,6 +1349,12 @@
 	page_t *pagefound;
 	int err;
 
+	/*
+	 * If the filesystem was umounted by force, return immediately.
+	 */
+	if (vp->v_vfsp->vfs_flag & VFS_UNMOUNTED)
+		return (EIO);
+
 	PC_DPRINTF3(5, "pcfs_getapage: vp=%p off=%lld len=%lu\n",
 	    (void *)vp, off, len);
 
@@ -1434,6 +1540,12 @@
 	offset_t eoff;
 	int err;
 
+	/*
+	 * If the filesystem was umounted by force, return immediately.
+	 */
+	if (vp->v_vfsp->vfs_flag & VFS_UNMOUNTED)
+		return (EIO);
+
 	PC_DPRINTF1(6, "pcfs_putpage vp=0x%p\n", (void *)vp);
 	if (vp->v_flag & VNOMAP)
 		return (ENOSYS);
--- a/usr/src/uts/common/sys/fs/pc_dir.h	Wed Sep 13 02:19:44 2006 -0700
+++ b/usr/src/uts/common/sys/fs/pc_dir.h	Wed Sep 13 07:42:50 2006 -0700
@@ -242,19 +242,39 @@
 	char		d_name[PCMAXNAMLEN + 1];
 };
 
+/*
+ * Check FAT 8.3 filename characters for validity.
+ * Lacking a kernel iconv, codepage support for short filenames
+ * is not provided.
+ * Short names must be uppercase ASCII (no support for MSDOS
+ * codepages right now, sorry) and may not contain any of
+ * *+=|\[];:",<>.?/ which are explicitly forbidden by the
+ * FAT specifications.
+ */
+#define	pc_validchar(_c) 						\
+	(((_c) >= ' ' && !((_c) & ~0177)) &&				\
+	(((_c) >= 'A' && (_c) <= 'Z') ||				\
+	((_c) >= '0' && (_c) <= '9') ||					\
+	((_c) != '"' && (_c) != '*' && (_c) != '+' && (_c) != ',' &&	\
+	(_c) != '.' && (_c) != '/' && (_c) != ':' && (_c) != ';' &&	\
+	(_c) != '<' && (_c) != '=' && (_c) != '>' && (_c) != '?' &&	\
+	(_c) != '[' && (_c) != '\\' && (_c) != ']' && (_c) != '|')))
+
+
 #ifdef _KERNEL
 
 /*
- * macros for converting to/from upper or lower case.
+ * macros for converting ASCII to/from upper or lower case.
  * users may give and get names in lower case, but they are stored on the
- * disk in upper case to be PCDOS compatible
+ * disk in upper case to be PCDOS compatible.
+ * These would better come from some shared source in <sys/...> but
+ * there is no such place yet.
  */
 #define	toupper(C)	(((C) >= 'a' && (C) <= 'z') ? (C) - 'a' + 'A' : (C))
 #define	tolower(C)	(((C) >= 'A' && (C) <= 'Z') ? (C) - 'A' + 'a' : (C))
 
-extern void pc_tvtopct(timestruc_t *, struct pctime *);	/* timeval to pctime */
-extern void pc_pcttotv(struct pctime *, timestruc_t *);	/* pctime to timeval */
-extern int pc_validchar(char);			/* valid filename ch */
+extern int pc_tvtopct(timestruc_t *, struct pctime *);	/* timeval to pctime */
+extern void pc_pcttotv(struct pctime *, int64_t *);	/* pctime to timeval */
 extern int pc_valid_lfn_char(char);		/* valid long filename ch */
 
 extern int pc_read_long_fn(struct vnode *, struct uio *,
--- a/usr/src/uts/common/sys/fs/pc_fs.h	Wed Sep 13 02:19:44 2006 -0700
+++ b/usr/src/uts/common/sys/fs/pc_fs.h	Wed Sep 13 07:42:50 2006 -0700
@@ -187,23 +187,24 @@
 	uint32_t	f32fsinfo_sector; /* where to read/write fsinfo */
 	struct pcfs *pcfs_nxt;		/* linked list of all mounts */
 	int pcfs_fatjustread;		/* Used to flag a freshly found FAT */
+	struct vnode *pcfs_root;	/* vnode for the root dir of the fs */
 };
 
 /*
  * flags
  */
-#define	PCFS_FATMOD	0x01		/* FAT has been modified */
-#define	PCFS_LOCKED	0x02		/* fs is locked */
-#define	PCFS_WANTED	0x04		/* locked fs is wanted */
-#define	PCFS_FAT16	0x400		/* 16 bit FAT */
-#define	PCFS_NOCHK	0x800		/* don't resync fat on error */
-#define	PCFS_BOOTPART	0x1000		/* boot partition type */
-#define	PCFS_HIDDEN	0x2000		/* show hidden files */
-#define	PCFS_PCMCIA_NO_CIS 0x4000	/* PCMCIA psuedo floppy */
-#define	PCFS_FOLDCASE	0x8000		/* fold all names from media to */
-					/* lowercase */
-#define	PCFS_FAT32	0x10000		/* 32 bit FAT */
-#define	PCFS_IRRECOV	0x20000		/* FS was messed with during write */
+#define	PCFS_FATMOD		0x01	/* FAT has been modified */
+#define	PCFS_LOCKED		0x02	/* fs is locked */
+#define	PCFS_WANTED		0x04	/* locked fs is wanted */
+#define	PCFS_FAT16		0x400	/* 16 bit FAT */
+#define	PCFS_NOCHK		0x800	/* don't resync fat on error */
+#define	PCFS_BOOTPART		0x1000	/* boot partition type */
+#define	PCFS_HIDDEN		0x2000	/* show hidden files */
+#define	PCFS_PCMCIA_NO_CIS	0x4000	/* PCMCIA psuedo floppy */
+#define	PCFS_FOLDCASE		0x8000	/* fold filenames to lowercase */
+#define	PCFS_FAT32		0x10000	/* 32 bit FAT */
+#define	PCFS_IRRECOV		0x20000	/* FS was messed with during write */
+#define	PCFS_NOCLAMPTIME	0x40000	/* expose full FAT timestamp range */
 
 /* for compatibility */
 struct old_pcfs_args {
@@ -225,6 +226,7 @@
 #define	PCFS_MNT_HIDDEN		0x01	/* show hidden files */
 #define	PCFS_MNT_FOLDCASE	0x02	/* fold all names from media to */
 					/* lowercase */
+#define	PCFS_MNT_NOCLAMPTIME	0x04	/* expose full FAT timestamp range */
 
 /*
  * pcfs mount options.
@@ -233,6 +235,8 @@
 #define	MNTOPT_PCFS_NOHIDDEN	"nohidden"
 #define	MNTOPT_PCFS_FOLDCASE	"foldcase"
 #define	MNTOPT_PCFS_NOFOLDCASE	"nofoldcase"
+#define	MNTOPT_PCFS_CLAMPTIME	"clamptime"
+#define	MNTOPT_PCFS_NOCLAMPTIME	"noclamptime"
 
 /*
  * Disk timeout value in sec.