|
@@ -24,27 +24,67 @@
|
|
|
#include <linux/namei.h>
|
|
|
#include "fat.h"
|
|
|
|
|
|
-static int vfat_revalidate(struct dentry *dentry, struct nameidata *nd)
|
|
|
+/*
|
|
|
+ * If new entry was created in the parent, it could create the 8.3
|
|
|
+ * alias (the shortname of logname). So, the parent may have the
|
|
|
+ * negative-dentry which matches the created 8.3 alias.
|
|
|
+ *
|
|
|
+ * If it happened, the negative dentry isn't actually negative
|
|
|
+ * anymore. So, drop it.
|
|
|
+ */
|
|
|
+static int vfat_revalidate_shortname(struct dentry *dentry)
|
|
|
{
|
|
|
int ret = 1;
|
|
|
-
|
|
|
- if (!dentry->d_inode &&
|
|
|
- nd && !(nd->flags & LOOKUP_CONTINUE) && (nd->flags & LOOKUP_CREATE))
|
|
|
- /*
|
|
|
- * negative dentry is dropped, in order to make sure
|
|
|
- * to use the name which a user desires if this is
|
|
|
- * create path.
|
|
|
- */
|
|
|
+ spin_lock(&dentry->d_lock);
|
|
|
+ if (dentry->d_time != dentry->d_parent->d_inode->i_version)
|
|
|
ret = 0;
|
|
|
- else {
|
|
|
- spin_lock(&dentry->d_lock);
|
|
|
- if (dentry->d_time != dentry->d_parent->d_inode->i_version)
|
|
|
- ret = 0;
|
|
|
- spin_unlock(&dentry->d_lock);
|
|
|
- }
|
|
|
+ spin_unlock(&dentry->d_lock);
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+static int vfat_revalidate(struct dentry *dentry, struct nameidata *nd)
|
|
|
+{
|
|
|
+ /* This is not negative dentry. Always valid. */
|
|
|
+ if (dentry->d_inode)
|
|
|
+ return 1;
|
|
|
+ return vfat_revalidate_shortname(dentry);
|
|
|
+}
|
|
|
+
|
|
|
+static int vfat_revalidate_ci(struct dentry *dentry, struct nameidata *nd)
|
|
|
+{
|
|
|
+ /*
|
|
|
+ * This is not negative dentry. Always valid.
|
|
|
+ *
|
|
|
+ * Note, rename() to existing directory entry will have ->d_inode,
|
|
|
+ * and will use existing name which isn't specified name by user.
|
|
|
+ *
|
|
|
+ * We may be able to drop this positive dentry here. But dropping
|
|
|
+ * positive dentry isn't good idea. So it's unsupported like
|
|
|
+ * rename("filename", "FILENAME") for now.
|
|
|
+ */
|
|
|
+ if (dentry->d_inode)
|
|
|
+ return 1;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * This may be nfsd (or something), anyway, we can't see the
|
|
|
+ * intent of this. So, since this can be for creation, drop it.
|
|
|
+ */
|
|
|
+ if (!nd)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Drop the negative dentry, in order to make sure to use the
|
|
|
+ * case sensitive name which is specified by user if this is
|
|
|
+ * for creation.
|
|
|
+ */
|
|
|
+ if (!(nd->flags & (LOOKUP_CONTINUE | LOOKUP_PARENT))) {
|
|
|
+ if (nd->flags & LOOKUP_CREATE)
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ return vfat_revalidate_shortname(dentry);
|
|
|
+}
|
|
|
+
|
|
|
/* returns the length of a struct qstr, ignoring trailing dots */
|
|
|
static unsigned int vfat_striptail_len(struct qstr *qstr)
|
|
|
{
|
|
@@ -126,25 +166,16 @@ static int vfat_cmp(struct dentry *dentry, struct qstr *a, struct qstr *b)
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
-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,
|
|
|
- }
|
|
|
+static struct dentry_operations vfat_ci_dentry_ops = {
|
|
|
+ .d_revalidate = vfat_revalidate_ci,
|
|
|
+ .d_hash = vfat_hashi,
|
|
|
+ .d_compare = vfat_cmpi,
|
|
|
+};
|
|
|
+
|
|
|
+static struct dentry_operations vfat_dentry_ops = {
|
|
|
+ .d_revalidate = vfat_revalidate,
|
|
|
+ .d_hash = vfat_hash,
|
|
|
+ .d_compare = vfat_cmp,
|
|
|
};
|
|
|
|
|
|
/* Characters that are undesirable in an MS-DOS file name */
|
|
@@ -685,29 +716,35 @@ static struct dentry *vfat_lookup(struct inode *dir, struct dentry *dentry,
|
|
|
struct fat_slot_info sinfo;
|
|
|
struct inode *inode;
|
|
|
struct dentry *alias;
|
|
|
- int err, table;
|
|
|
+ int err;
|
|
|
|
|
|
lock_super(sb);
|
|
|
- table = (MSDOS_SB(sb)->options.name_check == 's') ? 2 : 0;
|
|
|
- dentry->d_op = &vfat_dentry_ops[table];
|
|
|
|
|
|
err = vfat_find(dir, &dentry->d_name, &sinfo);
|
|
|
if (err) {
|
|
|
if (err == -ENOENT) {
|
|
|
- table++;
|
|
|
inode = NULL;
|
|
|
goto out;
|
|
|
}
|
|
|
goto error;
|
|
|
}
|
|
|
+
|
|
|
inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos);
|
|
|
brelse(sinfo.bh);
|
|
|
if (IS_ERR(inode)) {
|
|
|
err = PTR_ERR(inode);
|
|
|
goto error;
|
|
|
}
|
|
|
+
|
|
|
alias = d_find_alias(inode);
|
|
|
- if (alias) {
|
|
|
+ if (alias && !(alias->d_flags & DCACHE_DISCONNECTED)) {
|
|
|
+ /*
|
|
|
+ * This inode has non DCACHE_DISCONNECTED dentry. This
|
|
|
+ * means, the user did ->lookup() by an another name
|
|
|
+ * (longname vs 8.3 alias of it) in past.
|
|
|
+ *
|
|
|
+ * Switch to new one for reason of locality if possible.
|
|
|
+ */
|
|
|
if (d_invalidate(alias) == 0)
|
|
|
dput(alias);
|
|
|
else {
|
|
@@ -715,15 +752,14 @@ static struct dentry *vfat_lookup(struct inode *dir, struct dentry *dentry,
|
|
|
unlock_super(sb);
|
|
|
return alias;
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
out:
|
|
|
unlock_super(sb);
|
|
|
- dentry->d_op = &vfat_dentry_ops[table];
|
|
|
+ dentry->d_op = sb->s_root->d_op;
|
|
|
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 = sb->s_root->d_op;
|
|
|
dentry->d_time = dentry->d_parent->d_inode->i_version;
|
|
|
}
|
|
|
return dentry;
|
|
@@ -1022,9 +1058,9 @@ static int vfat_fill_super(struct super_block *sb, void *data, int silent)
|
|
|
return res;
|
|
|
|
|
|
if (MSDOS_SB(sb)->options.name_check != 's')
|
|
|
- sb->s_root->d_op = &vfat_dentry_ops[0];
|
|
|
+ sb->s_root->d_op = &vfat_ci_dentry_ops;
|
|
|
else
|
|
|
- sb->s_root->d_op = &vfat_dentry_ops[2];
|
|
|
+ sb->s_root->d_op = &vfat_dentry_ops;
|
|
|
|
|
|
return 0;
|
|
|
}
|