Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

fatfs: add UTC timestamp option

Provide a new mount option ("tz=UTC") for DOS (vfat/msdos) filesystems,
allowing timestamps to be in coordinated universal time (UTC) rather than
local time in applications where doing this is advantageous.

In particular, portable devices that use fat/vfat (such as digital
cameras) can benefit from using UTC in their internal clocks, thus
avoiding daylight saving time errors and general time ambiguity issues.
The user of the device does not have to worry about changing the time when
moving from place or when daylight saving changes.

The new mount option, when set, disables the counter-adjustment that Linux
currently makes to FAT timestamp info in anticipation of the normal
userspace time zone correction. When used in this new mode, all daylight
saving time and time zone handling is done in userspace as is normal for
many other filesystems (like ext3). The default mode, which remains
unchanged, is still appropriate when mounting volumes written in Windows
(because of its use of local time).

I originally based this patch on one submitted last year by Paul Collins,
but I updated it to work with current source and changed variable/option
naming. Ogawa Hirofumi (who maintains these filesystems) and I discussed
this patch at length on lkml, and he suggested using the option name in
the attached version of the patch. Barry Bouwsma pointed out a good
addition to the patch as well.

Signed-off-by: Joe Peterson <joe@skyrush.com>
Signed-off-by: Paul Collins <paul@ondioline.org>
Acked-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Cc: Barry Bouwsma <free_beer_for_all@yahoo.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Joe Peterson and committed by
Linus Torvalds
b271e067 e8938a62

+35 -17
+1 -1
fs/fat/dir.c
··· 1101 1101 goto error_free; 1102 1102 } 1103 1103 1104 - fat_date_unix2dos(ts->tv_sec, &time, &date); 1104 + fat_date_unix2dos(ts->tv_sec, &time, &date, sbi->options.tz_utc); 1105 1105 1106 1106 de = (struct msdos_dir_entry *)bhs[0]->b_data; 1107 1107 /* filling the new directory slots ("." and ".." entries) */
+20 -7
fs/fat/inode.c
··· 382 382 inode->i_blocks = ((inode->i_size + (sbi->cluster_size - 1)) 383 383 & ~((loff_t)sbi->cluster_size - 1)) >> 9; 384 384 inode->i_mtime.tv_sec = 385 - date_dos2unix(le16_to_cpu(de->time), le16_to_cpu(de->date)); 385 + date_dos2unix(le16_to_cpu(de->time), le16_to_cpu(de->date), 386 + sbi->options.tz_utc); 386 387 inode->i_mtime.tv_nsec = 0; 387 388 if (sbi->options.isvfat) { 388 389 int secs = de->ctime_cs / 100; 389 390 int csecs = de->ctime_cs % 100; 390 391 inode->i_ctime.tv_sec = 391 392 date_dos2unix(le16_to_cpu(de->ctime), 392 - le16_to_cpu(de->cdate)) + secs; 393 + le16_to_cpu(de->cdate), 394 + sbi->options.tz_utc) + secs; 393 395 inode->i_ctime.tv_nsec = csecs * 10000000; 394 396 inode->i_atime.tv_sec = 395 - date_dos2unix(0, le16_to_cpu(de->adate)); 397 + date_dos2unix(0, le16_to_cpu(de->adate), 398 + sbi->options.tz_utc); 396 399 inode->i_atime.tv_nsec = 0; 397 400 } else 398 401 inode->i_ctime = inode->i_atime = inode->i_mtime; ··· 594 591 raw_entry->attr = fat_attr(inode); 595 592 raw_entry->start = cpu_to_le16(MSDOS_I(inode)->i_logstart); 596 593 raw_entry->starthi = cpu_to_le16(MSDOS_I(inode)->i_logstart >> 16); 597 - fat_date_unix2dos(inode->i_mtime.tv_sec, &raw_entry->time, &raw_entry->date); 594 + fat_date_unix2dos(inode->i_mtime.tv_sec, &raw_entry->time, 595 + &raw_entry->date, sbi->options.tz_utc); 598 596 if (sbi->options.isvfat) { 599 597 __le16 atime; 600 - fat_date_unix2dos(inode->i_ctime.tv_sec,&raw_entry->ctime,&raw_entry->cdate); 601 - fat_date_unix2dos(inode->i_atime.tv_sec,&atime,&raw_entry->adate); 598 + fat_date_unix2dos(inode->i_ctime.tv_sec, &raw_entry->ctime, 599 + &raw_entry->cdate, sbi->options.tz_utc); 600 + fat_date_unix2dos(inode->i_atime.tv_sec, &atime, 601 + &raw_entry->adate, sbi->options.tz_utc); 602 602 raw_entry->ctime_cs = (inode->i_ctime.tv_sec & 1) * 100 + 603 603 inode->i_ctime.tv_nsec / 10000000; 604 604 } ··· 842 836 } 843 837 if (sbi->options.flush) 844 838 seq_puts(m, ",flush"); 839 + if (opts->tz_utc) 840 + seq_puts(m, ",tz=UTC"); 845 841 846 842 return 0; 847 843 } ··· 856 848 Opt_charset, Opt_shortname_lower, Opt_shortname_win95, 857 849 Opt_shortname_winnt, Opt_shortname_mixed, Opt_utf8_no, Opt_utf8_yes, 858 850 Opt_uni_xl_no, Opt_uni_xl_yes, Opt_nonumtail_no, Opt_nonumtail_yes, 859 - Opt_obsolate, Opt_flush, Opt_err, 851 + Opt_obsolate, Opt_flush, Opt_tz_utc, Opt_err, 860 852 }; 861 853 862 854 static match_table_t fat_tokens = { ··· 891 883 {Opt_obsolate, "cvf_options=%100s"}, 892 884 {Opt_obsolate, "posix"}, 893 885 {Opt_flush, "flush"}, 886 + {Opt_tz_utc, "tz=UTC"}, 894 887 {Opt_err, NULL}, 895 888 }; 896 889 static match_table_t msdos_tokens = { ··· 956 947 opts->utf8 = opts->unicode_xlate = 0; 957 948 opts->numtail = 1; 958 949 opts->usefree = opts->nocase = 0; 950 + opts->tz_utc = 0; 959 951 *debug = 0; 960 952 961 953 if (!options) ··· 1045 1035 break; 1046 1036 case Opt_flush: 1047 1037 opts->flush = 1; 1038 + break; 1039 + case Opt_tz_utc: 1040 + opts->tz_utc = 1; 1048 1041 break; 1049 1042 1050 1043 /* msdos specific */
+6 -4
fs/fat/misc.c
··· 142 142 }; 143 143 144 144 /* Convert a MS-DOS time/date pair to a UNIX date (seconds since 1 1 70). */ 145 - int date_dos2unix(unsigned short time, unsigned short date) 145 + int date_dos2unix(unsigned short time, unsigned short date, int tz_utc) 146 146 { 147 147 int month, year, secs; 148 148 ··· 156 156 ((date & 31)-1+day_n[month]+(year/4)+year*365-((year & 3) == 0 && 157 157 month < 2 ? 1 : 0)+3653); 158 158 /* days since 1.1.70 plus 80's leap day */ 159 - secs += sys_tz.tz_minuteswest*60; 159 + if (!tz_utc) 160 + secs += sys_tz.tz_minuteswest*60; 160 161 return secs; 161 162 } 162 163 163 164 /* Convert linear UNIX date to a MS-DOS time/date pair. */ 164 - void fat_date_unix2dos(int unix_date, __le16 *time, __le16 *date) 165 + void fat_date_unix2dos(int unix_date, __le16 *time, __le16 *date, int tz_utc) 165 166 { 166 167 int day, year, nl_day, month; 167 168 168 - unix_date -= sys_tz.tz_minuteswest*60; 169 + if (!tz_utc) 170 + unix_date -= sys_tz.tz_minuteswest*60; 169 171 170 172 /* Jan 1 GMT 00:00:00 1980. But what about another time zone? */ 171 173 if (unix_date < 315532800)
+2 -1
fs/msdos/namei.c
··· 237 237 int is_dir, int is_hid, int cluster, 238 238 struct timespec *ts, struct fat_slot_info *sinfo) 239 239 { 240 + struct msdos_sb_info *sbi = MSDOS_SB(dir->i_sb); 240 241 struct msdos_dir_entry de; 241 242 __le16 time, date; 242 243 int err; ··· 247 246 if (is_hid) 248 247 de.attr |= ATTR_HIDDEN; 249 248 de.lcase = 0; 250 - fat_date_unix2dos(ts->tv_sec, &time, &date); 249 + fat_date_unix2dos(ts->tv_sec, &time, &date, sbi->options.tz_utc); 251 250 de.cdate = de.adate = 0; 252 251 de.ctime = 0; 253 252 de.ctime_cs = 0;
+1 -1
fs/vfat/namei.c
··· 621 621 memcpy(de->name, msdos_name, MSDOS_NAME); 622 622 de->attr = is_dir ? ATTR_DIR : ATTR_ARCH; 623 623 de->lcase = lcase; 624 - fat_date_unix2dos(ts->tv_sec, &time, &date); 624 + fat_date_unix2dos(ts->tv_sec, &time, &date, sbi->options.tz_utc); 625 625 de->time = de->ctime = time; 626 626 de->date = de->cdate = de->adate = date; 627 627 de->ctime_cs = 0;
+5 -3
include/linux/msdos_fs.h
··· 203 203 numtail:1, /* Does first alias have a numeric '~1' type tail? */ 204 204 flush:1, /* write things quickly */ 205 205 nocase:1, /* Does this need case conversion? 0=need case conversion*/ 206 - usefree:1; /* Use free_clusters for FAT32 */ 206 + usefree:1, /* Use free_clusters for FAT32 */ 207 + tz_utc:1; /* Filesystem timestamps are in UTC */ 207 208 }; 208 209 209 210 #define FAT_HASH_BITS 8 ··· 435 434 extern void fat_fs_panic(struct super_block *s, const char *fmt, ...); 436 435 extern void fat_clusters_flush(struct super_block *sb); 437 436 extern int fat_chain_add(struct inode *inode, int new_dclus, int nr_cluster); 438 - extern int date_dos2unix(unsigned short time, unsigned short date); 439 - extern void fat_date_unix2dos(int unix_date, __le16 *time, __le16 *date); 437 + extern int date_dos2unix(unsigned short time, unsigned short date, int tz_utc); 438 + extern void fat_date_unix2dos(int unix_date, __le16 *time, __le16 *date, 439 + int tz_utc); 440 440 extern int fat_sync_bhs(struct buffer_head **bhs, int nr_bhs); 441 441 442 442 int fat_cache_init(void);