[PATCH][RFC] fat: Save FAT root directory timestamps to volume label

From: Jorg Schummer
Date: Wed Jul 22 2009 - 11:01:00 EST


Standard FAT implementations cannot store any of the FAT root directory's
timestamps. This commit adds the mount option 'rootts', which allows saving
the FAT root directory timestamps as the timestamps of the FAT volume label
directory entry. At least Mac OS X is known to support the same mechanism
and interoperate with this commit.

When mounting, the following values can be specified for the 'rootts' mount
option:

"rootts=ignore" ignores root directory timestamps. All timestamps are
reset to 0 (1/1/1970). This is the default.

"rootts=load" only tries to load the root directory's timestamps from
a volume label entry when mounting.

"rootts=preserve" tries to load and save the root directory's timestamps
if a volume label entry exists.

"rootts=save" tries to load and save the root directory's timestamps.
If the root directory was accessed but no volume label
entry exists, the label "NO NAME" is created.

Signed-off-by: Jorg Schummer <ext-jorg.2.schummer@xxxxxxxxx>
---
Documentation/filesystems/vfat.txt | 7 ++
fs/fat/dir.c | 44 ++++++++++++
fs/fat/fat.h | 27 +++++++
fs/fat/inode.c | 135 +++++++++++++++++++++++++-----------
fs/fat/misc.c | 27 +++++++
5 files changed, 198 insertions(+), 42 deletions(-)

diff --git a/Documentation/filesystems/vfat.txt b/Documentation/filesystems/vfat.txt
index b58b84b..7fc28a4 100644
--- a/Documentation/filesystems/vfat.txt
+++ b/Documentation/filesystems/vfat.txt
@@ -137,6 +137,13 @@ errors=panic|continue|remount-ro
without doing anything or remount the partition in
read-only mode (default behavior).

+root_ts=ignore|load|preserve|save
+ -- Specify whether to load/store the root dir timestamps as the
+ timestamp of the volume label entry: The default is to ignore,
+ 'preserve' stores the ts only if a volume label already exists,
+ and 'save' creates a volume label if the root ts has changed
+ but no volume label is present.
+
<bool>: 0,1,yes,no,true,false

TODO
diff --git a/fs/fat/dir.c b/fs/fat/dir.c
index 530b4ca..82b42c3 100644
--- a/fs/fat/dir.c
+++ b/fs/fat/dir.c
@@ -1362,3 +1362,47 @@ error_remove:
}

EXPORT_SYMBOL_GPL(fat_add_entries);
+
+int fat_get_label_entry(struct inode *root_inode, struct buffer_head **bh,
+ struct msdos_dir_entry **de)
+{
+ loff_t pos;
+
+ BUG_ON(root_inode->i_ino != MSDOS_ROOT_INO);
+
+ pos = 0;
+ *bh = NULL;
+ while (fat_get_entry(root_inode, &pos, bh, de) >= 0) {
+ /* volume label: note that it is not enough to check only
+ whether the ATTR_VOLUME bit is set, since this would yield
+ true on any vfat extended entry */
+ if ((*de)->attr != ATTR_EXT && ((*de)->attr & ATTR_VOLUME))
+ return 0;
+ }
+ return -ENOENT;
+}
+EXPORT_SYMBOL_GPL(fat_get_label_entry);
+
+int fat_create_label_entry(struct inode *root_inode,
+ const unsigned char *name)
+{
+ struct msdos_dir_entry de;
+ struct fat_slot_info sinfo;
+ int err;
+
+ BUG_ON(root_inode->i_ino != MSDOS_ROOT_INO);
+
+ memcpy(de.name,
+ (name) ? name : (const unsigned char *) FAT_LABEL_NONAME,
+ MSDOS_NAME);
+ de.attr = ATTR_VOLUME;
+ de.lcase = 0;
+ de.start = de.starthi = 0;
+ de.size = 0;
+ fat_time_inode2de(MSDOS_SB(root_inode->i_sb), root_inode, &de);
+
+ err = fat_add_entries(root_inode, &de, 1, &sinfo);
+ brelse(sinfo.bh);
+ return err;
+}
+EXPORT_SYMBOL_GPL(fat_create_label_entry);
diff --git a/fs/fat/fat.h b/fs/fat/fat.h
index adb0e72..69a91f9 100644
--- a/fs/fat/fat.h
+++ b/fs/fat/fat.h
@@ -21,6 +21,19 @@
#define FAT_ERRORS_PANIC 2 /* panic on error */
#define FAT_ERRORS_RO 3 /* remount r/o on error */

+/*
+ * fat root directory timestamp backup
+ */
+#define FAT_ROOT_TS_READ 0x01 /* read root dir ts from volume label ts */
+#define FAT_ROOT_TS_WRITE 0x02 /* write root dir ts to volume label ts */
+#define FAT_ROOT_TS_CREATE 0x04 /* create volume label if there is none */
+
+#define FAT_ROOT_TS_IGNORE 0
+#define FAT_ROOT_TS_LOAD FAT_ROOT_TS_READ
+#define FAT_ROOT_TS_PRESERVE (FAT_ROOT_TS_READ | FAT_ROOT_TS_WRITE)
+#define FAT_ROOT_TS_SAVE (FAT_ROOT_TS_READ | FAT_ROOT_TS_WRITE | \
+ FAT_ROOT_TS_CREATE)
+
struct fat_mount_options {
uid_t fs_uid;
gid_t fs_gid;
@@ -32,6 +45,8 @@ struct fat_mount_options {
unsigned char name_check; /* r = relaxed, n = normal, s = strict */
unsigned char errors; /* On error: continue, panic, remount-ro */
unsigned short allow_utime;/* permission for setting the [am]time */
+ unsigned short root_ts; /* root dir timestamps:
+ ignore, load, preserve, save */
unsigned quiet:1, /* set = fake successful chmods and chowns */
showexec:1, /* set = only set x bit for com/exe/bat */
sys_immutable:1, /* set = system files are immutable */
@@ -50,6 +65,8 @@ struct fat_mount_options {
#define FAT_HASH_BITS 8
#define FAT_HASH_SIZE (1UL << FAT_HASH_BITS)

+#define FAT_LABEL_NONAME "NO NAME "
+
/*
* MS-DOS file system in-core superblock data
*/
@@ -247,6 +264,12 @@ extern int fat_add_entries(struct inode *dir, void *slots, int nr_slots,
struct fat_slot_info *sinfo);
extern int fat_remove_entries(struct inode *dir, struct fat_slot_info *sinfo);

+extern int fat_get_label_entry(struct inode *root_inode,
+ struct buffer_head **bh,
+ struct msdos_dir_entry **de);
+extern int fat_create_label_entry(struct inode *root_inode,
+ const unsigned char *name);
+
/* fat/fatent.c */
struct fat_entry {
int entry;
@@ -329,6 +352,10 @@ extern void fat_time_fat2unix(struct msdos_sb_info *sbi, struct timespec *ts,
__le16 __time, __le16 __date, u8 time_cs);
extern void fat_time_unix2fat(struct msdos_sb_info *sbi, struct timespec *ts,
__le16 *time, __le16 *date, u8 *time_cs);
+extern void fat_time_de2inode(struct msdos_sb_info *sbi, struct inode *inode,
+ struct msdos_dir_entry *de);
+extern void fat_time_inode2de(struct msdos_sb_info *sbi, struct inode *inode,
+ struct msdos_dir_entry *de);
extern int fat_sync_bhs(struct buffer_head **bhs, int nr_bhs);

int fat_cache_init(void);
diff --git a/fs/fat/inode.c b/fs/fat/inode.c
index 8970d8c..e52ff62 100644
--- a/fs/fat/inode.c
+++ b/fs/fat/inode.c
@@ -384,13 +384,7 @@ static int fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de)
inode->i_blocks = ((inode->i_size + (sbi->cluster_size - 1))
& ~((loff_t)sbi->cluster_size - 1)) >> 9;

- fat_time_fat2unix(sbi, &inode->i_mtime, de->time, de->date, 0);
- if (sbi->options.isvfat) {
- fat_time_fat2unix(sbi, &inode->i_ctime, de->ctime,
- de->cdate, de->ctime_cs);
- fat_time_fat2unix(sbi, &inode->i_atime, 0, de->adate, 0);
- } else
- inode->i_ctime = inode->i_atime = inode->i_mtime;
+ fat_time_de2inode(sbi, inode, de);

return 0;
}
@@ -590,45 +584,61 @@ static int fat_write_inode(struct inode *inode, int wait)
loff_t i_pos;
int err;

- if (inode->i_ino == MSDOS_ROOT_INO)
- return 0;
+ if (inode->i_ino == MSDOS_ROOT_INO) {
+ if (!(sbi->options.root_ts & FAT_ROOT_TS_WRITE))
+ return 0;
+
+ /* Write the timestamps of a FAT file system's root directory
+ * as the timestamps of the file system's label file. */
+
+ spin_lock(&sbi->inode_hash_lock);
+ err = fat_get_label_entry(inode, &bh, &raw_entry);
+ if (err) {
+ if (err == -ENOENT &&
+ (sbi->options.root_ts & FAT_ROOT_TS_CREATE)) {
+
+ printk(KERN_INFO "FAT: creating volume label on"
+ " %s to save root dir timestamps\n",
+ sb->s_id);
+ err = fat_create_label_entry(inode, NULL);
+ }
+ spin_unlock(&sbi->inode_hash_lock);
+ return err;
+ }
+
+ } else { /* inodes other than root directory */

retry:
- i_pos = fat_i_pos_read(sbi, inode);
- if (!i_pos)
- return 0;
+ i_pos = fat_i_pos_read(sbi, inode);
+ if (!i_pos)
+ return 0;

- bh = sb_bread(sb, i_pos >> sbi->dir_per_block_bits);
- if (!bh) {
- printk(KERN_ERR "FAT: unable to read inode block "
- "for updating (i_pos %lld)\n", i_pos);
- return -EIO;
- }
- spin_lock(&sbi->inode_hash_lock);
- if (i_pos != MSDOS_I(inode)->i_pos) {
- spin_unlock(&sbi->inode_hash_lock);
- brelse(bh);
- goto retry;
- }
+ bh = sb_bread(sb, i_pos >> sbi->dir_per_block_bits);
+ if (!bh) {
+ printk(KERN_ERR "FAT: unable to read inode block "
+ "for updating (i_pos %lld)\n", i_pos);
+ return -EIO;
+ }
+ spin_lock(&sbi->inode_hash_lock);
+ if (i_pos != MSDOS_I(inode)->i_pos) {
+ spin_unlock(&sbi->inode_hash_lock);
+ brelse(bh);
+ goto retry;
+ }

- raw_entry = &((struct msdos_dir_entry *) (bh->b_data))
- [i_pos & (sbi->dir_per_block - 1)];
- if (S_ISDIR(inode->i_mode))
- raw_entry->size = 0;
- else
- raw_entry->size = cpu_to_le32(inode->i_size);
- raw_entry->attr = fat_make_attrs(inode);
- raw_entry->start = cpu_to_le16(MSDOS_I(inode)->i_logstart);
- raw_entry->starthi = cpu_to_le16(MSDOS_I(inode)->i_logstart >> 16);
- fat_time_unix2fat(sbi, &inode->i_mtime, &raw_entry->time,
- &raw_entry->date, NULL);
- if (sbi->options.isvfat) {
- __le16 atime;
- fat_time_unix2fat(sbi, &inode->i_ctime, &raw_entry->ctime,
- &raw_entry->cdate, &raw_entry->ctime_cs);
- fat_time_unix2fat(sbi, &inode->i_atime, &atime,
- &raw_entry->adate, NULL);
+ raw_entry = &((struct msdos_dir_entry *) (bh->b_data))
+ [i_pos & (sbi->dir_per_block - 1)];
+ if (S_ISDIR(inode->i_mode))
+ raw_entry->size = 0;
+ else
+ raw_entry->size = cpu_to_le32(inode->i_size);
+ raw_entry->attr = fat_make_attrs(inode);
+ raw_entry->start = cpu_to_le16(MSDOS_I(inode)->i_logstart);
+ raw_entry->starthi =
+ cpu_to_le16(MSDOS_I(inode)->i_logstart >> 16);
}
+
+ fat_time_inode2de(sbi, inode, raw_entry);
spin_unlock(&sbi->inode_hash_lock);
mark_buffer_dirty(bh);
err = 0;
@@ -863,6 +873,17 @@ static int fat_show_options(struct seq_file *m, struct vfsmount *mnt)
else
seq_puts(m, ",errors=remount-ro");

+ switch (opts->root_ts) {
+ case FAT_ROOT_TS_LOAD:
+ seq_puts(m, ",rootts=load");
+ break;
+ case FAT_ROOT_TS_PRESERVE:
+ seq_puts(m, ",rootts=preserve");
+ break;
+ case FAT_ROOT_TS_SAVE:
+ seq_puts(m, ",rootts=save");
+ break;
+ }
return 0;
}

@@ -875,7 +896,8 @@ enum {
Opt_shortname_winnt, Opt_shortname_mixed, Opt_utf8_no, Opt_utf8_yes,
Opt_uni_xl_no, Opt_uni_xl_yes, Opt_nonumtail_no, Opt_nonumtail_yes,
Opt_obsolate, Opt_flush, Opt_tz_utc, Opt_rodir, Opt_err_cont,
- Opt_err_panic, Opt_err_ro, Opt_err,
+ Opt_err_panic, Opt_err_ro, Opt_rootts_load, Opt_rootts_preserve,
+ Opt_rootts_save, Opt_rootts_ignore, Opt_err,
};

static const match_table_t fat_tokens = {
@@ -903,6 +925,10 @@ static const match_table_t fat_tokens = {
{Opt_err_cont, "errors=continue"},
{Opt_err_panic, "errors=panic"},
{Opt_err_ro, "errors=remount-ro"},
+ {Opt_rootts_load, "rootts=load"},
+ {Opt_rootts_preserve, "rootts=preserve"},
+ {Opt_rootts_save, "rootts=save"},
+ {Opt_rootts_ignore, "rootts=ignore"},
{Opt_obsolate, "conv=binary"},
{Opt_obsolate, "conv=text"},
{Opt_obsolate, "conv=auto"},
@@ -984,6 +1010,7 @@ static int parse_options(char *options, int is_vfat, int silent, int *debug,
opts->usefree = opts->nocase = 0;
opts->tz_utc = 0;
opts->errors = FAT_ERRORS_RO;
+ opts->root_ts = FAT_ROOT_TS_IGNORE;
*debug = 0;

if (!options)
@@ -1085,6 +1112,18 @@ static int parse_options(char *options, int is_vfat, int silent, int *debug,
case Opt_err_ro:
opts->errors = FAT_ERRORS_RO;
break;
+ case Opt_rootts_ignore:
+ opts->root_ts = FAT_ROOT_TS_IGNORE;
+ break;
+ case Opt_rootts_load:
+ opts->root_ts = FAT_ROOT_TS_LOAD;
+ break;
+ case Opt_rootts_preserve:
+ opts->root_ts = FAT_ROOT_TS_PRESERVE;
+ break;
+ case Opt_rootts_save:
+ opts->root_ts = FAT_ROOT_TS_SAVE;
+ break;

/* msdos specific */
case Opt_dots:
@@ -1207,6 +1246,18 @@ static int fat_read_root(struct inode *inode)
inode->i_mtime.tv_nsec = inode->i_atime.tv_nsec = inode->i_ctime.tv_nsec = 0;
inode->i_nlink = fat_subdirs(inode)+2;

+ /* Try to restore the root dir's timestamps from the FAT volume label
+ entry */
+ if (sbi->options.root_ts & FAT_ROOT_TS_READ) {
+ struct buffer_head *bh;
+ struct msdos_dir_entry *de;
+
+ if (!fat_get_label_entry(inode, &bh, &de)) {
+ fat_time_de2inode(sbi, inode, de);
+ brelse(bh);
+ }
+ }
+
return 0;
}

diff --git a/fs/fat/misc.c b/fs/fat/misc.c
index a6c2047..3652096 100644
--- a/fs/fat/misc.c
+++ b/fs/fat/misc.c
@@ -268,6 +268,33 @@ void fat_time_unix2fat(struct msdos_sb_info *sbi, struct timespec *ts,
}
EXPORT_SYMBOL_GPL(fat_time_unix2fat);

+void fat_time_de2inode(struct msdos_sb_info *sbi, struct inode *inode,
+ struct msdos_dir_entry *de)
+{
+ fat_time_fat2unix(sbi, &inode->i_mtime, de->time, de->date, 0);
+ if (sbi->options.isvfat) {
+ fat_time_fat2unix(sbi, &inode->i_ctime, de->ctime,
+ de->cdate, de->ctime_cs);
+ fat_time_fat2unix(sbi, &inode->i_atime, 0, de->adate, 0);
+ } else
+ inode->i_ctime = inode->i_atime = inode->i_mtime;
+}
+EXPORT_SYMBOL_GPL(fat_time_de2inode);
+
+void fat_time_inode2de(struct msdos_sb_info *sbi, struct inode *inode,
+ struct msdos_dir_entry *de)
+{
+ fat_time_unix2fat(sbi, &inode->i_mtime, &de->time, &de->date, NULL);
+ if (sbi->options.isvfat) {
+ __le16 atime;
+ fat_time_unix2fat(sbi, &inode->i_ctime, &de->ctime,
+ &de->cdate, &de->ctime_cs);
+ fat_time_unix2fat(sbi, &inode->i_atime, &atime,
+ &de->adate, NULL);
+ }
+}
+EXPORT_SYMBOL_GPL(fat_time_inode2de);
+
int fat_sync_bhs(struct buffer_head **bhs, int nr_bhs)
{
int i, err = 0;
--
1.5.4.3

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/