[PATCH] vfat+affs case preservation

From: Jan Kratochvil (rcpt-linux-kernel.AT.vger.kernel.org@jankratochvil.net)
Date: Wed Jul 02 2003 - 05:25:38 EST


Hi,

Patch gets working
        mv somecase SomeCase

on case-insensitive case-preserving vfat and affs filesystems.
Needed for Samba using vfat volumes - for Linux-doubting small bussiness ppl.

Thanks to Jan Kara (past patch review) and Pavouk (affs image).

http://www.jankratochvil.net/priv/vfat/linux-2.4.22-pre2-vfat6.diff
http://www.jankratochvil.net/priv/vfat/linux-2.5.73-bk9-vfat6.diff

Lace

linux-2.5.73-bk9-vfat6.diff:

diff -u -ru linux-2.5.73-bk9-orig/Documentation/filesystems/vfs.txt linux-2.5.73-bk9-vfat6/Documentation/filesystems/vfs.txt
--- linux-2.5.73-bk9-orig/Documentation/filesystems/vfs.txt Tue May 27 03:00:43 2003
+++ linux-2.5.73-bk9-vfat6/Documentation/filesystems/vfs.txt Mon Jun 30 18:45:06 2003
@@ -419,6 +419,7 @@
         int (*d_revalidate)(struct dentry *);
         int (*d_hash) (struct dentry *, struct qstr *);
         int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
+ int (*d_compare_rename) (struct dentry *, struct qstr *, struct qstr *);
         void (*d_delete)(struct dentry *);
         void (*d_release)(struct dentry *);
         void (*d_iput)(struct dentry *, struct inode *);
@@ -431,7 +432,13 @@
 
   d_hash: called when the VFS adds a dentry to the hash table
 
- d_compare: called when a dentry should be compared with another
+ d_compare: called when a dentry should be compared with another.
+ Case-sensitive for all case-preserving filesystems no matter if the
+ filesystem structure is case-sensitive itself
+
+ d_compare_rename: compare equivalency of two dentries no matter their case.
+ This method is optional - useful for case-insensitive case-preserving
+ filesystems. dentries are simply compared by d_compare if not provided
 
   d_delete: called when the last reference to a dentry is
         deleted. This means no-one is using the dentry, however it is
diff -u -ru linux-2.5.73-bk9-orig/fs/affs/namei.c linux-2.5.73-bk9-vfat6/fs/affs/namei.c
--- linux-2.5.73-bk9-orig/fs/affs/namei.c Tue May 27 03:00:27 2003
+++ linux-2.5.73-bk9-vfat6/fs/affs/namei.c Mon Jun 30 18:45:06 2003
@@ -25,24 +25,57 @@
 
 extern struct inode_operations affs_symlink_inode_operations;
 
-static int affs_toupper(int ch);
-static int affs_hash_dentry(struct dentry *, struct qstr *);
-static int affs_compare_dentry(struct dentry *, struct qstr *, struct qstr *);
-static int affs_intl_toupper(int ch);
-static int affs_intl_hash_dentry(struct dentry *, struct qstr *);
-static int affs_intl_compare_dentry(struct dentry *, struct qstr *, struct qstr *);
+static int affs_revalidate(struct dentry *dentry, int flags);
+static int affs_strict_toupper(int ch);
+static int affs_toupper(int ch);
+static int affs_intl_toupper(int ch);
+static int affs_hash_strictcase_dentry(struct dentry *, struct qstr *);
+static int affs_compare_strictcase_dentry(struct dentry *, struct qstr *, struct qstr *);
+static int affs_compare_anycase_dentry(struct dentry *, struct qstr *, struct qstr *);
+static int affs_intl_compare_anycase_dentry(struct dentry *, struct qstr *, struct qstr *);
+
+/* We have to always do the revalidate as after unlink (etc.) there still may
+ * exist other case-different dentries for the same inode. It would be also
+ * possible to discard such aliases by going through d_alias links during the
+ * unlink.
+ */
 
 struct dentry_operations affs_dentry_operations = {
- .d_hash = affs_hash_dentry,
- .d_compare = affs_compare_dentry,
+ .d_revalidate = affs_revalidate,
+ .d_hash = affs_hash_strictcase_dentry,
+ .d_compare = affs_compare_strictcase_dentry,
+ .d_compare_rename = affs_compare_anycase_dentry,
 };
 
 struct dentry_operations affs_intl_dentry_operations = {
- .d_hash = affs_intl_hash_dentry,
- .d_compare = affs_intl_compare_dentry,
+ .d_revalidate = affs_revalidate,
+ .d_hash = affs_hash_strictcase_dentry,
+ .d_compare = affs_compare_strictcase_dentry,
+ .d_compare_rename = affs_intl_compare_anycase_dentry,
 };
 
 
+static int affs_revalidate(struct dentry *dentry, int flags)
+{
+ pr_debug("AFFS: revalidate(\"%.*s\")\n", (int)dentry->d_name.len, dentry->d_name.name);
+ spin_lock(&dcache_lock);
+ if (dentry->d_parent == dentry /* root is always valid */
+ || dentry->d_time == dentry->d_parent->d_inode->i_version) {
+ spin_unlock(&dcache_lock);
+ return 1;
+ }
+ spin_unlock(&dcache_lock);
+ return 0;
+}
+
+/* toupper() for case-sensitive dentries matching */
+
+static int
+affs_strict_toupper(int ch)
+{
+ return ch;
+}
+
 /* Simple toupper() for DOS\1 */
 
 static int
@@ -91,14 +124,9 @@
 }
 
 static int
-affs_hash_dentry(struct dentry *dentry, struct qstr *qstr)
+affs_hash_strictcase_dentry(struct dentry *dentry, struct qstr *qstr)
 {
- return __affs_hash_dentry(dentry, qstr, affs_toupper);
-}
-static int
-affs_intl_hash_dentry(struct dentry *dentry, struct qstr *qstr)
-{
- return __affs_hash_dentry(dentry, qstr, affs_intl_toupper);
+ return __affs_hash_dentry(dentry, qstr, affs_strict_toupper);
 }
 
 static inline int
@@ -134,12 +162,17 @@
 }
 
 static int
-affs_compare_dentry(struct dentry *dentry, struct qstr *a, struct qstr *b)
+affs_compare_strictcase_dentry(struct dentry *dentry, struct qstr *a, struct qstr *b)
+{
+ return __affs_compare_dentry(dentry, a, b, affs_strict_toupper);
+}
+static int
+affs_compare_anycase_dentry(struct dentry *dentry, struct qstr *a, struct qstr *b)
 {
         return __affs_compare_dentry(dentry, a, b, affs_toupper);
 }
 static int
-affs_intl_compare_dentry(struct dentry *dentry, struct qstr *a, struct qstr *b)
+affs_intl_compare_anycase_dentry(struct dentry *dentry, struct qstr *a, struct qstr *b)
 {
         return __affs_compare_dentry(dentry, a, b, affs_intl_toupper);
 }
@@ -242,6 +275,7 @@
                 }
         }
         dentry->d_op = AFFS_SB(sb)->s_flags & SF_INTL ? &affs_intl_dentry_operations : &affs_dentry_operations;
+ dentry->d_time = dentry->d_parent->d_inode->i_version;
         d_add(dentry, inode);
         return NULL;
 }
@@ -282,6 +316,7 @@
                 iput(inode);
                 return error;
         }
+ dentry->d_time = dentry->d_parent->d_inode->i_version;
         return 0;
 }
 
@@ -311,6 +346,7 @@
                 iput(inode);
                 return error;
         }
+ dentry->d_time = dentry->d_parent->d_inode->i_version;
         return 0;
 }
 
@@ -423,7 +459,12 @@
                 return retval;
 
         /* Unlink destination if it already exists */
- if (new_dentry->d_inode) {
+ if (new_dentry->d_inode
+ /* Do not remove case-different aliases twice.
+ * Hardlinks cannot be passed here - checked at top of vfs_rename().
+ */
+ && old_dentry->d_inode != new_dentry->d_inode
+ ) {
                 retval = affs_remove_header(new_dentry);
                 if (retval)
                         return retval;
diff -u -ru linux-2.5.73-bk9-orig/fs/namei.c linux-2.5.73-bk9-vfat6/fs/namei.c
--- linux-2.5.73-bk9-orig/fs/namei.c Mon Jun 30 17:35:52 2003
+++ linux-2.5.73-bk9-vfat6/fs/namei.c Mon Jun 30 18:45:06 2003
@@ -1953,9 +1953,19 @@
         int error;
         int is_dir = S_ISDIR(old_dentry->d_inode->i_mode);
 
- if (old_dentry->d_inode == new_dentry->d_inode)
- return 0;
-
+ /*
+ * Rename to the same inode but possibly different name.
+ * Pass such call only to case-insensitive case-preserving filesystems
+ * (vfat) implementing d_compare_rename. Other filesystems would crash
+ * as they do not expect rename to the same target inode.
+ * Renaming devoted to johanka.
+ */
+ if (old_dentry->d_inode == new_dentry->d_inode
+ && (!old_dentry->d_op || !old_dentry->d_op->d_compare_rename
+ || (old_dir != new_dir
+ || old_dentry->d_op->d_compare_rename(old_dentry, &old_dentry->d_name, &new_dentry->d_name))))
+ return 0;
+
         error = may_delete(old_dir, old_dentry, is_dir);
         if (error)
                 return error;
diff -u -ru linux-2.5.73-bk9-orig/fs/vfat/namei.c linux-2.5.73-bk9-vfat6/fs/vfat/namei.c
--- linux-2.5.73-bk9-orig/fs/vfat/namei.c Mon Jun 30 17:35:12 2003
+++ linux-2.5.73-bk9-vfat6/fs/vfat/namei.c Wed Jul 2 10:30:57 2003
@@ -41,38 +41,35 @@
 # define PRINTK3(x)
 #endif
 
-static int vfat_hashi(struct dentry *parent, struct qstr *qstr);
 static int vfat_hash(struct dentry *parent, struct qstr *qstr);
 static int vfat_cmpi(struct dentry *dentry, struct qstr *a, struct qstr *b);
 static int vfat_cmp(struct dentry *dentry, struct qstr *a, struct qstr *b);
 static int vfat_revalidate(struct dentry *dentry, int);
 
-static struct dentry_operations vfat_dentry_ops[4] = {
- {
- .d_hash = vfat_hashi,
- .d_compare = vfat_cmpi,
- },
- {
- .d_revalidate = vfat_revalidate,
- .d_hash = vfat_hashi,
- .d_compare = vfat_cmpi,
- },
- {
- .d_hash = vfat_hash,
- .d_compare = vfat_cmp,
- },
- {
- .d_revalidate = vfat_revalidate,
- .d_hash = vfat_hash,
- .d_compare = vfat_cmp,
- }
+/* We have to always do the revalidate as after unlink (etc.) there still may
+ * exist other case-different dentries for the same inode. It would be also
+ * possible to discard such aliases by going through d_alias links during the
+ * unlink. "strictcase" does not have case-different dentries but "longna~1"
+ * style aliases still exist there.
+ */
+static struct dentry_operations vfat_dentry_ops_anycase = {
+ .d_revalidate = vfat_revalidate,
+ .d_hash = vfat_hash,
+ .d_compare = vfat_cmp,
+ .d_compare_rename = vfat_cmpi,
+};
+static struct dentry_operations vfat_dentry_ops_strictcase = {
+ .d_revalidate = vfat_revalidate,
+ .d_hash = vfat_hash,
+ .d_compare = vfat_cmp,
 };
 
 static int vfat_revalidate(struct dentry *dentry, int flags)
 {
         PRINTK1(("vfat_revalidate: %s\n", dentry->d_name.name));
         spin_lock(&dcache_lock);
- if (dentry->d_time == dentry->d_parent->d_inode->i_version) {
+ if (dentry->d_parent == dentry /* root is always valid */
+ || dentry->d_time == dentry->d_parent->d_inode->i_version) {
                 spin_unlock(&dcache_lock);
                 return 1;
         }
@@ -129,32 +126,6 @@
 }
 
 /*
- * Compute the hash for the vfat name corresponding to the dentry.
- * Note: if the name is invalid, we leave the hash code unchanged so
- * that the existing dentry can be used. The vfat fs routines will
- * return ENOENT or EINVAL as appropriate.
- */
-static int vfat_hashi(struct dentry *dentry, struct qstr *qstr)
-{
- struct nls_table *t = MSDOS_SB(dentry->d_inode->i_sb)->nls_io;
- const char *name;
- int len;
- unsigned long hash;
-
- len = qstr->len;
- name = qstr->name;
- while (len && name[len-1] == '.')
- len--;
-
- hash = init_name_hash();
- while (len--)
- hash = partial_name_hash(vfat_tolower(t, *name++), hash);
- qstr->hash = end_name_hash(hash);
-
- return 0;
-}
-
-/*
  * Case insensitive compare of two vfat names.
  */
 static int vfat_cmpi(struct dentry *dentry, struct qstr *a, struct qstr *b)
@@ -865,22 +836,21 @@
         int res;
         struct vfat_slot_info sinfo;
         struct inode *inode;
- struct dentry *alias;
         struct buffer_head *bh = NULL;
         struct msdos_dir_entry *de;
- int table;
+ struct dentry_operations *dentry_ops;
         
         PRINTK2(("vfat_lookup: name=%s, len=%d\n",
                  dentry->d_name.name, dentry->d_name.len));
 
         lock_kernel();
- table = (MSDOS_SB(dir->i_sb)->options.name_check == 's') ? 2 : 0;
- dentry->d_op = &vfat_dentry_ops[table];
+ dentry_ops = (MSDOS_SB(dir->i_sb)->options.name_check == 's'
+ ? &vfat_dentry_ops_strictcase : &vfat_dentry_ops_anycase);
+ dentry->d_op = dentry_ops;
 
         inode = NULL;
         res = vfat_find(dir,&dentry->d_name,&sinfo,&bh,&de);
         if (res < 0) {
- table++;
                 goto error;
         }
         inode = fat_build_inode(dir->i_sb, de, sinfo.i_pos, &res);
@@ -889,24 +859,13 @@
                 unlock_kernel();
                 return ERR_PTR(res);
         }
- alias = d_find_alias(inode);
- if (alias) {
- if (d_invalidate(alias)==0)
- dput(alias);
- else {
- iput(inode);
- unlock_kernel();
- return alias;
- }
-
- }
 error:
         unlock_kernel();
- dentry->d_op = &vfat_dentry_ops[table];
+ dentry->d_op = dentry_ops;
         dentry->d_time = dentry->d_parent->d_inode->i_version;
         dentry = d_splice_alias(inode, dentry);
         if (dentry) {
- dentry->d_op = &vfat_dentry_ops[table];
+ dentry->d_op = dentry_ops;
                 dentry->d_time = dentry->d_parent->d_inode->i_version;
         }
         return dentry;
@@ -1082,11 +1041,14 @@
         loff_t dotdot_i_pos;
         struct inode *old_inode, *new_inode;
         int res, is_dir;
- struct vfat_slot_info old_sinfo,sinfo;
+ struct vfat_slot_info old_sinfo,new_sinfo;
 
         old_bh = new_bh = dotdot_bh = NULL;
         old_inode = old_dentry->d_inode;
         new_inode = new_dentry->d_inode;
+ /* Rename of case-different aliases in the same dir. */
+ if (old_inode == new_inode)
+ new_inode = NULL;
         lock_kernel();
         res = vfat_find(old_dir,&old_dentry->d_name,&old_sinfo,&old_bh,&old_de);
         PRINTK3(("vfat_rename 2\n"));
@@ -1098,10 +1060,10 @@
                                 &dotdot_de,&dotdot_i_pos)) < 0)
                 goto rename_done;
 
- if (new_dentry->d_inode) {
- res = vfat_find(new_dir,&new_dentry->d_name,&sinfo,&new_bh,
+ if (new_inode) {
+ res = vfat_find(new_dir,&new_dentry->d_name,&new_sinfo,&new_bh,
                                 &new_de);
- if (res < 0 || MSDOS_I(new_inode)->i_pos != sinfo.i_pos) {
+ if (res < 0 || MSDOS_I(new_inode)->i_pos != new_sinfo.i_pos) {
                         /* WTF??? Cry and fail. */
                         printk(KERN_WARNING "vfat_rename: fs corrupted\n");
                         goto rename_done;
@@ -1112,11 +1074,10 @@
                         if (res)
                                 goto rename_done;
                 }
+ /* releases new_bh */
+ vfat_remove_entry(new_dir,&new_sinfo,new_bh,new_de);
+ new_bh=NULL;
                 fat_detach(new_inode);
- } else {
- res = vfat_add_entry(new_dir,&new_dentry->d_name,is_dir,&sinfo,
- &new_bh,&new_de);
- if (res < 0) goto rename_done;
         }
 
         new_dir->i_version++;
@@ -1124,8 +1085,12 @@
         /* releases old_bh */
         vfat_remove_entry(old_dir,&old_sinfo,old_bh,old_de);
         old_bh=NULL;
+ res = vfat_add_entry(new_dir,&new_dentry->d_name,is_dir,&new_sinfo,
+ &new_bh,&new_de);
+ if (res < 0) goto rename_done;
+
         fat_detach(old_inode);
- fat_attach(old_inode, sinfo.i_pos);
+ fat_attach(old_inode, new_sinfo.i_pos);
         mark_inode_dirty(old_inode);
 
         old_dir->i_version++;
@@ -1179,10 +1144,8 @@
         if (res)
                 return res;
 
- if (MSDOS_SB(sb)->options.name_check != 's')
- sb->s_root->d_op = &vfat_dentry_ops[0];
- else
- sb->s_root->d_op = &vfat_dentry_ops[2];
+ sb->s_root->d_op = (MSDOS_SB(sb)->options.name_check == 's'
+ ? &vfat_dentry_ops_strictcase : &vfat_dentry_ops_anycase);
 
         return 0;
 }
diff -u -ru linux-2.5.73-bk9-orig/include/linux/dcache.h linux-2.5.73-bk9-vfat6/include/linux/dcache.h
--- linux-2.5.73-bk9-orig/include/linux/dcache.h Mon Jun 30 17:35:17 2003
+++ linux-2.5.73-bk9-vfat6/include/linux/dcache.h Mon Jun 30 18:44:33 2003
@@ -109,6 +109,7 @@
         int (*d_revalidate)(struct dentry *, int);
         int (*d_hash) (struct dentry *, struct qstr *);
         int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
+ int (*d_compare_rename) (struct dentry *, struct qstr *, struct qstr *);
         int (*d_delete)(struct dentry *);
         void (*d_release)(struct dentry *);
         void (*d_iput)(struct dentry *, struct inode *);
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Mon Jul 07 2003 - 22:00:15 EST