Subject: kern/22717: Explicit time zone handling for MS-DOS file systems
To: None <gnats-bugs@gnats.netbsd.org>
From: None <michael.eriksson@era-t.ericsson.se>
List: netbsd-bugs
Date: 09/07/2003 23:56:29
>Number:         22717
>Category:       kern
>Synopsis:       Explicit time zone handling for MS-DOS file systems
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sun Sep 07 21:57:00 UTC 2003
>Closed-Date:
>Last-Modified:
>Originator:     Michael Eriksson
>Release:        NetBSD 1.6X
>Organization:
Ericsson Research
>Environment:
>Description:

MS-DOS file systems, i.e., FAT, store time stamps in the local time
zone (in contrast to the Unix file systems which use UTC). For this
reason, the kernel adjusts the MS-DOS file stamps with rtc_offset,
with the undocumented presumption that the only use of MS-DOS file
systems is on machines which dual-boot operating systems which keeps
the real-time clock in local time.

Now, most digital cameras write the image files to flash-based FAT
file systems. When these file systems are mounted (with, e.g., a USB
flash reader) on machines that don't dual-boot W*ndows, the rtc_offset
hack goes from ugly to plain wrong.

>How-To-Repeat:

datan$ mount /dev/sd0e /flash
datan$ ls -l /flash

Note bogus time stamps (unless you happen to be in the UK winter time
zone).

>Fix:

The patch below adds a '-t' option to mount_msdos, to set the time
zone offset (in seconds) from UTC. If not set, the user's current time
zone is used. (Maybe a larger unit of time (minutes) should be used,
but seconds is most generic, and can also be used by a perfectionist
to adjust for, e.g., a camera that doesn't keep perfect time. :-))


Index: sys/fs/msdosfs/denode.h
===================================================================
RCS file: /cvsroot/src/sys/fs/msdosfs/denode.h,v
retrieving revision 1.3
diff -u -r1.3 denode.h
--- sys/fs/msdosfs/denode.h	2003/06/29 22:31:09	1.3
+++ sys/fs/msdosfs/denode.h	2003/09/07 21:21:46
@@ -221,18 +221,18 @@
 #define	VTODE(vp)	((struct denode *)(vp)->v_data)
 #define	DETOV(de)	((de)->de_vnode)
 
-#define	DETIMES(dep, acc, mod, cre) \
+#define	DETIMES(dep, acc, mod, cre, gmtoff) \
 	if ((dep)->de_flag & (DE_UPDATE | DE_CREATE | DE_ACCESS)) { \
 		(dep)->de_flag |= DE_MODIFIED; \
 		if ((dep)->de_flag & DE_UPDATE) { \
-			unix2dostime((mod), &(dep)->de_MDate, &(dep)->de_MTime, NULL); \
+			unix2dostime((mod), gmtoff, &(dep)->de_MDate, &(dep)->de_MTime, NULL); \
 			(dep)->de_Attributes |= ATTR_ARCHIVE; \
 		} \
 		if (!((dep)->de_pmp->pm_flags & MSDOSFSMNT_NOWIN95)) { \
 			if ((dep)->de_flag & DE_ACCESS) \
-				unix2dostime((acc), &(dep)->de_ADate, NULL, NULL); \
+				unix2dostime((acc), gmtoff, &(dep)->de_ADate, NULL, NULL); \
 			if ((dep)->de_flag & DE_CREATE) \
-				unix2dostime((cre), &(dep)->de_CDate, &(dep)->de_CTime, &(dep)->de_CHun); \
+				unix2dostime((cre), gmtoff, &(dep)->de_CDate, &(dep)->de_CTime, &(dep)->de_CHun); \
 		} \
 		(dep)->de_flag &= ~(DE_UPDATE | DE_CREATE | DE_ACCESS); \
 	}
Index: sys/fs/msdosfs/direntry.h
===================================================================
RCS file: /cvsroot/src/sys/fs/msdosfs/direntry.h,v
retrieving revision 1.1
diff -u -r1.1 direntry.h
--- sys/fs/msdosfs/direntry.h	2002/12/26 12:31:33	1.1
+++ sys/fs/msdosfs/direntry.h	2003/09/07 21:21:46
@@ -119,9 +119,10 @@
 #define DD_YEAR_SHIFT		9
 
 #ifdef _KERNEL
-void	unix2dostime __P((struct timespec *tsp, u_int16_t *ddp,
+void	unix2dostime __P((struct timespec *tsp, int gmtoff, u_int16_t *ddp,
 	    u_int16_t *dtp, u_int8_t *dhp));
-void	dos2unixtime __P((u_int dd, u_int dt, u_int dh, struct timespec *tsp));
+void	dos2unixtime __P((u_int dd, u_int dt, u_int dh, int gmtoff,
+	    struct timespec *tsp));
 int	dos2unixfn __P((u_char dn[11], u_char *un, int lower));
 int	unix2dosfn __P((const u_char *un, u_char dn[12], int unlen,
 	    u_int gen));
Index: sys/fs/msdosfs/msdosfs_conv.c
===================================================================
RCS file: /cvsroot/src/sys/fs/msdosfs/msdosfs_conv.c,v
retrieving revision 1.1
diff -u -r1.1 msdosfs_conv.c
--- sys/fs/msdosfs/msdosfs_conv.c	2002/12/26 12:31:34	1.1
+++ sys/fs/msdosfs/msdosfs_conv.c	2003/09/07 21:21:46
@@ -96,8 +96,9 @@
  * file timestamps. The passed in unix time is assumed to be in GMT.
  */
 void
-unix2dostime(tsp, ddp, dtp, dhp)
+unix2dostime(tsp, gmtoff, ddp, dtp, dhp)
 	struct timespec *tsp;
+	int gmtoff;
 	u_int16_t *ddp;
 	u_int16_t *dtp;
 	u_int8_t *dhp;
@@ -113,8 +114,7 @@
 	 * If the time from the last conversion is the same as now, then
 	 * skip the computations and use the saved result.
 	 */
-	t = tsp->tv_sec - (rtc_offset * 60)
-	     /* +- daylight savings time correction */ ;
+	t = tsp->tv_sec + gmtoff; /* time zone correction */
 	t &= ~1;
 	if (lasttime != t) {
 		lasttime = t;
@@ -177,10 +177,11 @@
  * not be too efficient.
  */
 void
-dos2unixtime(dd, dt, dh, tsp)
+dos2unixtime(dd, dt, dh, gmtoff, tsp)
 	u_int dd;
 	u_int dt;
 	u_int dh;
+	int gmtoff;
 	struct timespec *tsp;
 {
 	u_long seconds;
@@ -227,8 +228,8 @@
 		days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1;
 		lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980;
 	}
-	tsp->tv_sec = seconds + lastseconds + (rtc_offset * 60)
-	     /* -+ daylight savings time correction */ ;
+	tsp->tv_sec = seconds + lastseconds;
+	tsp->tv_sec -= gmtoff;	/* time zone correction */
 	tsp->tv_nsec = (dh % 100) * 10000000;
 }
 
Index: sys/fs/msdosfs/msdosfs_vfsops.c
===================================================================
RCS file: /cvsroot/src/sys/fs/msdosfs/msdosfs_vfsops.c,v
retrieving revision 1.8
diff -u -r1.8 msdosfs_vfsops.c
--- sys/fs/msdosfs/msdosfs_vfsops.c	2003/08/02 11:41:21	1.8
+++ sys/fs/msdosfs/msdosfs_vfsops.c	2003/09/07 21:21:46
@@ -147,6 +147,7 @@
 	pmp->pm_uid = argp->uid;
 	pmp->pm_mask = argp->mask & ALLPERMS;
 	pmp->pm_dirmask = argp->dirmask & ALLPERMS;
+	pmp->pm_gmtoff = argp->gmtoff;
 	pmp->pm_flags |= argp->flags & MSDOSFSMNT_MNTOPT;
 
 	/*
Index: sys/fs/msdosfs/msdosfs_vnops.c
===================================================================
RCS file: /cvsroot/src/sys/fs/msdosfs/msdosfs_vnops.c,v
retrieving revision 1.6
diff -u -r1.6 msdosfs_vnops.c
--- sys/fs/msdosfs/msdosfs_vnops.c	2003/08/02 11:41:21	1.6
+++ sys/fs/msdosfs/msdosfs_vnops.c	2003/09/07 21:21:47
@@ -156,7 +156,7 @@
 	ndirent.de_pmp = pdep->de_pmp;
 	ndirent.de_flag = DE_ACCESS | DE_CREATE | DE_UPDATE;
 	TIMEVAL_TO_TIMESPEC(&time, &ts);
-	DETIMES(&ndirent, &ts, &ts, &ts);
+	DETIMES(&ndirent, &ts, &ts, &ts, pdep->de_pmp->pm_gmtoff);
 	if ((error = createde(&ndirent, pdep, &dep, cnp)) != 0)
 		goto bad;
 	if ((cnp->cn_flags & SAVESTART) == 0)
@@ -221,7 +221,7 @@
 	simple_lock(&vp->v_interlock);
 	if (vp->v_usecount > 1) {
 		TIMEVAL_TO_TIMESPEC(&time, &ts);
-		DETIMES(dep, &ts, &ts, &ts);
+		DETIMES(dep, &ts, &ts, &ts, dep->de_pmp->pm_gmtoff);
 	}
 	simple_unlock(&vp->v_interlock);
 	return (0);
@@ -287,7 +287,7 @@
 	u_long fileid;
 
 	TIMEVAL_TO_TIMESPEC(&time, &ts);
-	DETIMES(dep, &ts, &ts, &ts);
+	DETIMES(dep, &ts, &ts, &ts, pmp->pm_gmtoff);
 	vap->va_fsid = dep->de_dev;
 	/*
 	 * The following computation of the fileid must be the same as that
@@ -316,10 +316,13 @@
 	vap->va_nlink = 1;
 	vap->va_rdev = 0;
 	vap->va_size = ap->a_vp->v_size;
-	dos2unixtime(dep->de_MDate, dep->de_MTime, 0, &vap->va_mtime);
+	dos2unixtime(dep->de_MDate, dep->de_MTime, 0, pmp->pm_gmtoff,
+	    &vap->va_mtime);
 	if (dep->de_pmp->pm_flags & MSDOSFSMNT_LONGNAME) {
-		dos2unixtime(dep->de_ADate, 0, 0, &vap->va_atime);
-		dos2unixtime(dep->de_CDate, dep->de_CTime, dep->de_CHun, &vap->va_ctime);
+		dos2unixtime(dep->de_ADate, 0, 0, pmp->pm_gmtoff,
+		    &vap->va_atime);
+		dos2unixtime(dep->de_CDate, dep->de_CTime, dep->de_CHun,
+		    pmp->pm_gmtoff, &vap->va_ctime);
 	} else {
 		vap->va_atime = vap->va_mtime;
 		vap->va_ctime = vap->va_mtime;
@@ -398,9 +401,9 @@
 			return (error);
 		if ((pmp->pm_flags & MSDOSFSMNT_NOWIN95) == 0 &&
 		    vap->va_atime.tv_sec != VNOVAL)
-			unix2dostime(&vap->va_atime, &dep->de_ADate, NULL, NULL);
+			unix2dostime(&vap->va_atime, pmp->pm_gmtoff, &dep->de_ADate, NULL, NULL);
 		if (vap->va_mtime.tv_sec != VNOVAL)
-			unix2dostime(&vap->va_mtime, &dep->de_MDate, &dep->de_MTime, NULL);
+			unix2dostime(&vap->va_mtime, pmp->pm_gmtoff, &dep->de_MDate, &dep->de_MTime, NULL);
 		dep->de_Attributes |= ATTR_ARCHIVE;
 		dep->de_flag |= DE_MODIFIED;
 		de_changed = 1;
@@ -707,7 +710,7 @@
 	TIMEVAL_TO_TIMESPEC(&time, &ts);
 	DETIMES(dep,
 	    ap->a_access ? ap->a_access : &ts,
-	    ap->a_modify ? ap->a_modify : &ts, &ts);
+	    ap->a_modify ? ap->a_modify : &ts, &ts, dep->de_pmp->pm_gmtoff);
 	if ((dep->de_flag & DE_MODIFIED) == 0)
 		return (0);
 	dep->de_flag &= ~DE_MODIFIED;
@@ -1232,7 +1235,7 @@
 	ndirent.de_pmp = pmp;
 	ndirent.de_flag = DE_ACCESS | DE_CREATE | DE_UPDATE;
 	TIMEVAL_TO_TIMESPEC(&time, &ts);
-	DETIMES(&ndirent, &ts, &ts, &ts);
+	DETIMES(&ndirent, &ts, &ts, &ts, pmp->pm_gmtoff);
 
 	/*
 	 * Now fill the cluster with the "." and ".." entries. And write
Index: sys/fs/msdosfs/msdosfsmount.h
===================================================================
RCS file: /cvsroot/src/sys/fs/msdosfs/msdosfsmount.h,v
retrieving revision 1.3
diff -u -r1.3 msdosfsmount.h
--- sys/fs/msdosfs/msdosfsmount.h	2003/08/02 11:41:21	1.3
+++ sys/fs/msdosfs/msdosfsmount.h	2003/09/07 21:21:47
@@ -56,6 +56,7 @@
 	uid_t	uid;		/* uid that owns msdosfs files */
 	gid_t	gid;		/* gid that owns msdosfs files */
 	mode_t  mask;		/* mask to be applied for msdosfs perms */
+	int	gmtoff;		/* offset from UTC in seconds */
 	int	flags;		/* see below */
 
 	/* Following items added after versioning support */
@@ -102,6 +103,7 @@
 				   for files */
 	mode_t pm_dirmask;	/* mask to and with file protection bits
 				   for directories */
+	int pm_gmtoff;		/* offset from UTC in seconds */
 	struct vnode *pm_devvp;	/* vnode for block device mntd */
 	struct bpb50 pm_bpb;	/* BIOS parameter blk for this fs */
 	u_long pm_FATsecs;	/* actual number of fat sectors */
Index: sbin/mount_msdos/mount_msdos.8
===================================================================
RCS file: /cvsroot/src/sbin/mount_msdos/mount_msdos.8,v
retrieving revision 1.27
diff -u -r1.27 mount_msdos.8
--- sbin/mount_msdos/mount_msdos.8	2003/08/02 11:43:21	1.27
+++ sbin/mount_msdos/mount_msdos.8	2003/09/07 21:21:47
@@ -45,6 +45,7 @@
 .Op Fl g Ar gid
 .Op Fl m Ar mask
 .Op Fl M Ar mask
+.Op Fl t Ar gmtoff
 .Op Fl s
 .Op Fl l
 .Op Fl 9
@@ -113,6 +114,11 @@
 is used if it is supplied and
 .Fl M
 is omitted. See description of previous option for details.
+.It Fl t Ar gmtoff
+Set the time zone offset (in seconds) from UTC to
+.Ar gmtoff ,
+with positive values indicating east of the Prime Meridian.
+If not set, the user's current time zone will be used.
 .It Fl s
 Force behaviour to
 ignore and not generate Win'95 long filenames.
Index: sbin/mount_msdos/mount_msdos.c
===================================================================
RCS file: /cvsroot/src/sbin/mount_msdos/mount_msdos.c,v
retrieving revision 1.30
diff -u -r1.30 mount_msdos.c
--- sbin/mount_msdos/mount_msdos.c	2003/08/02 11:42:20	1.30
+++ sbin/mount_msdos/mount_msdos.c	2003/09/07 21:21:47
@@ -51,6 +51,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <time.h>
 #include <unistd.h>
 #include <util.h>
 
@@ -87,13 +88,15 @@
 {
 	struct msdosfs_args args;
 	struct stat sb;
-	int c, mntflags, set_gid, set_uid, set_mask, set_dirmask;
+	int c, mntflags, set_gid, set_uid, set_mask, set_dirmask, set_gmtoff;
 	char *dev, *dir, ndir[MAXPATHLEN+1];
+	time_t now;
+	struct tm *tm;
 
-	mntflags = set_gid = set_uid = set_mask = set_dirmask = 0;
+	mntflags = set_gid = set_uid = set_mask = set_dirmask = set_gmtoff = 0;
 	(void)memset(&args, '\0', sizeof(args));
 
-	while ((c = getopt(argc, argv, "Gsl9u:g:m:M:o:")) != -1) {
+	while ((c = getopt(argc, argv, "Gsl9u:g:m:M:o:t:")) != -1) {
 		switch (c) {
 		case 'G':
 			args.flags |= MSDOSFSMNT_GEMDOSFS;
@@ -126,6 +129,10 @@
 		case 'o':
 			getmntopts(optarg, mopts, &mntflags, 0);
 			break;
+		case 't':
+			args.gmtoff = atoi(optarg);
+			set_gmtoff = 1;
+			break;
 		case '?':
 		default:
 			usage();
@@ -174,6 +181,14 @@
 			args.mask = args.dirmask =
 				sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
 		}
+	}
+
+	if (!set_gmtoff) {
+		/* use user's time zone as default */
+		time(&now);
+		tm = localtime(&now);
+		args.gmtoff = tm->tm_gmtoff;
+
 	}
 	args.flags |= MSDOSFSMNT_VERSIONED;
 	args.version = MSDOSFSMNT_VERSION;

>Release-Note:
>Audit-Trail:
>Unformatted: