vfat: shortname=always

Maximum compatibility is achieved by only creating (potentially
mangled) short filenames for vfat.  A per-mount cache is kept, so
common Linux operations like "stat" etc. on just-created files doesn't
fail.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
---
 fs/fat/fat.h         |    8 ++-
 fs/fat/inode.c       |   21 ++++++-
 fs/fat/namei_msdos.c |    3 -
 fs/fat/namei_vfat.c  |  135 ++++++++++++++++++++++++++++++++++++++++++++-------
 4 files changed, 143 insertions(+), 24 deletions(-)

diff --git a/fs/fat/fat.h b/fs/fat/fat.h
--- a/fs/fat/fat.h
+++ b/fs/fat/fat.h
@@ -16,6 +16,7 @@
 #define VFAT_SFN_DISPLAY_WINNT	0x0004 /* emulate winnt rule for display */
 #define VFAT_SFN_CREATE_WIN95	0x0100 /* emulate win95 rule for create */
 #define VFAT_SFN_CREATE_WINNT	0x0200 /* emulate winnt rule for create */
+#define VFAT_SFN_CREATE_SHORTALWAYS 0x0400 /* create shortname only */
 
 #define FAT_ERRORS_CONT		1      /* ignore error and continue */
 #define FAT_ERRORS_PANIC	2      /* panic on error */
@@ -83,6 +84,9 @@ struct msdos_sb_info {
 
 	spinlock_t inode_hash_lock;
 	struct hlist_head inode_hashtable[FAT_HASH_SIZE];
+
+	struct list_head short_long_map;
+	void (*put_super)(struct msdos_sb_info *);
 };
 
 #define FAT_CACHE_VALID	0	/* special case for valid cache */
@@ -316,7 +320,9 @@ extern struct inode *fat_build_inode(str
 			struct msdos_dir_entry *de, loff_t i_pos);
 extern int fat_sync_inode(struct inode *inode);
 extern int fat_fill_super(struct super_block *sb, void *data, int silent,
-			const struct inode_operations *fs_dir_inode_ops, int isvfat);
+			  const struct inode_operations *fs_dir_inode_ops,
+			  int isvfat,
+			  void (*put_super)(struct msdos_sb_info *));
 
 extern int fat_flush_inodes(struct super_block *sb, struct inode *i1,
 		            struct inode *i2);
diff --git a/fs/fat/inode.c b/fs/fat/inode.c
--- a/fs/fat/inode.c
+++ b/fs/fat/inode.c
@@ -485,6 +485,8 @@ static void fat_put_super(struct super_b
 	}
 
 	sb->s_fs_info = NULL;
+	if (sbi->put_super)
+		sbi->put_super(sbi);
 	kfree(sbi);
 
 	unlock_kernel();
@@ -872,10 +874,10 @@ enum {
 	Opt_usefree, Opt_nocase, Opt_quiet, Opt_showexec, Opt_debug,
 	Opt_immutable, Opt_dots, Opt_nodots,
 	Opt_charset, Opt_shortname_lower, Opt_shortname_win95,
-	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_shortname_winnt, Opt_shortname_mixed, Opt_shortname_always,
+	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,
 };
 
 static const match_table_t fat_tokens = {
@@ -929,6 +931,7 @@ static const match_table_t vfat_tokens =
 	{Opt_shortname_win95, "shortname=win95"},
 	{Opt_shortname_winnt, "shortname=winnt"},
 	{Opt_shortname_mixed, "shortname=mixed"},
+	{Opt_shortname_always, "shortname=always"},
 	{Opt_utf8_no, "utf8=0"},		/* 0 or no or false */
 	{Opt_utf8_no, "utf8=no"},
 	{Opt_utf8_no, "utf8=false"},
@@ -1119,6 +1122,11 @@ static int parse_options(char *options, 
 			opts->shortname = VFAT_SFN_DISPLAY_WINNT
 					| VFAT_SFN_CREATE_WIN95;
 			break;
+		case Opt_shortname_always:
+			opts->shortname = VFAT_SFN_DISPLAY_WINNT
+					| VFAT_SFN_CREATE_WINNT
+					| VFAT_SFN_CREATE_SHORTALWAYS;
+			break;
 		case Opt_utf8_no:		/* 0 or no or false */
 			opts->utf8 = 0;
 			break;
@@ -1214,7 +1222,8 @@ static int fat_read_root(struct inode *i
  * Read the super block of an MS-DOS FS.
  */
 int fat_fill_super(struct super_block *sb, void *data, int silent,
-		   const struct inode_operations *fs_dir_inode_ops, int isvfat)
+		   const struct inode_operations *fs_dir_inode_ops, int isvfat,
+		   void (*put_super)(struct msdos_sb_info *))
 {
 	struct inode *root_inode = NULL, *fat_inode = NULL;
 	struct buffer_head *bh;
@@ -1243,6 +1252,8 @@ int fat_fill_super(struct super_block *s
 	sb->s_op = &fat_sops;
 	sb->s_export_op = &fat_export_ops;
 	sbi->dir_ops = fs_dir_inode_ops;
+	sbi->put_super = put_super;
+	INIT_LIST_HEAD(&sbi->short_long_map);
 
 	error = parse_options(data, isvfat, silent, &debug, &sbi->options);
 	if (error)
diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c
--- a/fs/fat/namei_msdos.c
+++ b/fs/fat/namei_msdos.c
@@ -662,7 +662,8 @@ static int msdos_fill_super(struct super
 {
 	int res;
 
-	res = fat_fill_super(sb, data, silent, &msdos_dir_inode_operations, 0);
+	res = fat_fill_super(sb, data, silent, &msdos_dir_inode_operations, 0,
+			     NULL);
 	if (res)
 		return res;
 
diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c
--- a/fs/fat/namei_vfat.c
+++ b/fs/fat/namei_vfat.c
@@ -85,15 +85,19 @@ static int vfat_revalidate_ci(struct den
 }
 
 /* returns the length of a struct qstr, ignoring trailing dots */
-static unsigned int vfat_striptail_len(struct qstr *qstr)
+static unsigned int vfat_striptail_len(const unsigned char *name,
+				       unsigned int len)
 {
-	unsigned int len = qstr->len;
-
-	while (len && qstr->name[len - 1] == '.')
+	while (len && name[len - 1] == '.')
 		len--;
 	return len;
 }
 
+static unsigned int vfat_striptail_qstr(struct qstr *qstr)
+{
+	return vfat_striptail_len(qstr->name, qstr->len);
+}
+
 /*
  * Compute the hash for the vfat name corresponding to the dentry.
  * Note: if the name is invalid, we leave the hash code unchanged so
@@ -102,7 +106,7 @@ static unsigned int vfat_striptail_len(s
  */
 static int vfat_hash(struct dentry *dentry, struct qstr *qstr)
 {
-	qstr->hash = full_name_hash(qstr->name, vfat_striptail_len(qstr));
+	qstr->hash = full_name_hash(qstr->name, vfat_striptail_qstr(qstr));
 	return 0;
 }
 
@@ -120,7 +124,7 @@ static int vfat_hashi(struct dentry *den
 	unsigned long hash;
 
 	name = qstr->name;
-	len = vfat_striptail_len(qstr);
+	len = vfat_striptail_qstr(qstr);
 
 	hash = init_name_hash();
 	while (len--)
@@ -139,8 +143,8 @@ static int vfat_cmpi(struct dentry *dent
 	unsigned int alen, blen;
 
 	/* A filename cannot end in '.' or we treat it like it has none */
-	alen = vfat_striptail_len(a);
-	blen = vfat_striptail_len(b);
+	alen = vfat_striptail_qstr(a);
+	blen = vfat_striptail_qstr(b);
 	if (alen == blen) {
 		if (nls_strnicmp(t, a->name, b->name, alen) == 0)
 			return 0;
@@ -156,8 +160,8 @@ static int vfat_cmp(struct dentry *dentr
 	unsigned int alen, blen;
 
 	/* A filename cannot end in '.' or we treat it like it has none */
-	alen = vfat_striptail_len(a);
-	blen = vfat_striptail_len(b);
+	alen = vfat_striptail_qstr(a);
+	blen = vfat_striptail_qstr(b);
 	if (alen == blen) {
 		if (strncmp(a->name, b->name, alen) == 0)
 			return 0;
@@ -585,6 +589,70 @@ xlate_to_uni(const unsigned char *name, 
 	return 0;
 }
 
+struct vfat_slmap {
+	struct list_head list;
+	unsigned char msdos_name[MSDOS_NAME];
+	unsigned int len;
+	unsigned char name[0];
+};
+
+/* sb must already be locked for all of these: */
+static int vfat_slmap_add(struct msdos_sb_info *sbi,
+			  const unsigned char *name, unsigned int len,
+			  const unsigned char msdos_name[MSDOS_NAME])
+{
+	struct vfat_slmap *sl;
+
+	sl = kmalloc(sizeof(*sl) + len, GFP_KERNEL);
+	if (!sl)
+		return -ENOMEM;
+
+	memcpy(sl->msdos_name, msdos_name, sizeof(sl->msdos_name));
+	memcpy(sl->name, name, len);
+	sl->len = vfat_striptail_len(name, len);
+	list_add(&sl->list, &sbi->short_long_map);
+}
+
+static struct vfat_slmap *vfat_slmap_find(struct msdos_sb_info *sbi,
+					  const unsigned char *name,
+					  unsigned int len)
+{
+	struct vfat_slmap *sl;
+
+	len = vfat_striptail_len(name, len);
+	list_for_each_entry(sl, &sbi->short_long_map, list) {
+		if (sl->len != len)
+			continue;
+		if (memcmp(sl->name, name, len) != 0)
+			continue;
+		return sl;
+	}
+	return NULL;
+}
+
+static void vfat_slmap_del(struct msdos_sb_info *sbi,
+			   const unsigned char *name, unsigned int len)
+{
+	struct vfat_slmap *sl;
+
+	sl = vfat_slmap_find(sbi, name, len);
+	if (sl) {
+		list_del(&sl->list);
+		kfree(sl);
+	}
+}
+
+/* We simply throw away cache of long names here. */
+static void vfat_put_super(struct msdos_sb_info *sbi)
+{
+	struct vfat_slmap *sl, *next;
+
+	list_for_each_entry_safe(sl, next, &sbi->short_long_map, list) {
+		list_del(&sl->list);
+		kfree(sl);
+	}
+}
+
 static int vfat_build_slots(struct inode *dir, const unsigned char *name,
 			    int len, int is_dir, int cluster,
 			    struct timespec *ts,
@@ -627,6 +695,14 @@ static int vfat_build_slots(struct inode
 		goto shortname;
 	}
 
+	if (opts->shortname & VFAT_SFN_CREATE_SHORTALWAYS) {
+		de = (struct msdos_dir_entry *)slots;
+		err = vfat_slmap_add(sbi, name, len, msdos_name);
+		if (err)
+			goto out_free;
+		goto shortname;
+	}
+
 	/* build the entry of long file name */
 	cksum = fat_checksum(msdos_name);
 
@@ -671,7 +747,7 @@ static int vfat_add_entry(struct inode *
 	unsigned int len;
 	int err, nr_slots;
 
-	len = vfat_striptail_len(qname);
+	len = vfat_striptail_qstr(qname);
 	if (len == 0)
 		return -ENOENT;
 
@@ -695,17 +771,38 @@ static int vfat_add_entry(struct inode *
 	else
 		mark_inode_dirty(dir);
 cleanup:
+	if (err)
+		vfat_slmap_del(MSDOS_SB(dir->i_sb), qname->name, qname->len);
 	kfree(slots);
 	return err;
 }
 
+static int vfat_remove_entries(struct inode *dir,
+			       struct qstr *qname,
+			       struct fat_slot_info *sinfo)
+{
+	int err = fat_remove_entries(dir, sinfo);
+	if (!err)
+		vfat_slmap_del(MSDOS_SB(dir->i_sb), qname->name, qname->len);
+	return err;
+}
+
 static int vfat_find(struct inode *dir, struct qstr *qname,
 		     struct fat_slot_info *sinfo)
 {
-	unsigned int len = vfat_striptail_len(qname);
+	int err;
+	unsigned int len = vfat_striptail_qstr(qname);
 	if (len == 0)
 		return -ENOENT;
-	return fat_search_long(dir, qname->name, len, sinfo);
+	err = fat_search_long(dir, qname->name, len, sinfo);
+	if (err == -ENOENT) {
+		struct vfat_slmap *sl;
+		sl = vfat_slmap_find(MSDOS_SB(dir->i_sb),
+				     qname->name, qname->len);
+		if (sl)
+			err = fat_scan(dir, sl->msdos_name, sinfo);
+	}
+	return err;
 }
 
 static struct dentry *vfat_lookup(struct inode *dir, struct dentry *dentry,
@@ -817,7 +914,8 @@ static int vfat_rmdir(struct inode *dir,
 	if (err)
 		goto out;
 
-	err = fat_remove_entries(dir, &sinfo);	/* and releases bh */
+	/* and releases bh */
+	err = vfat_remove_entries(dir, &dentry->d_name, &sinfo);
 	if (err)
 		goto out;
 	drop_nlink(dir);
@@ -844,7 +942,8 @@ static int vfat_unlink(struct inode *dir
 	if (err)
 		goto out;
 
-	err = fat_remove_entries(dir, &sinfo);	/* and releases bh */
+	/* and releases bh */
+	err = vfat_remove_entries(dir, &dentry->d_name, &sinfo);
 	if (err)
 		goto out;
 	clear_nlink(inode);
@@ -975,7 +1074,8 @@ static int vfat_rename(struct inode *old
  			inc_nlink(new_dir);
 	}
 
-	err = fat_remove_entries(old_dir, &old_sinfo);	/* and releases bh */
+	/* and releases bh */
+	err = vfat_remove_entries(old_dir, &old_dentry->d_name, &old_sinfo);
 	old_sinfo.bh = NULL;
 	if (err)
 		goto error_dotdot;
@@ -1051,7 +1151,8 @@ static int vfat_fill_super(struct super_
 {
 	int res;
 
-	res = fat_fill_super(sb, data, silent, &vfat_dir_inode_operations, 1);
+	res = fat_fill_super(sb, data, silent, &vfat_dir_inode_operations, 1,
+			     vfat_put_super);
 	if (res)
 		return res;
 
