[PATCH] vfat+affs case preservation

Jan Kratochvil (rcpt-linux-kernel.AT.vger.kernel.org@jankratochvil.net)
Wed, 2 Jul 2003 12:25:38 +0200


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/