Skip to content

Commit

Permalink
fs: change d_compare for rcu-walk
Browse files Browse the repository at this point in the history
Change d_compare so it may be called from lock-free RCU lookups. This
does put significant restrictions on what may be done from the callback,
however there don't seem to have been any problems with in-tree fses.
If some strange use case pops up that _really_ cannot cope with the
rcu-walk rules, we can just add new rcu-unaware callbacks, which would
cause name lookup to drop out of rcu-walk mode.

For in-tree filesystems, this is just a mechanical change.

Signed-off-by: Nick Piggin <[email protected]>
  • Loading branch information
Nick Piggin committed Jan 7, 2011
1 parent fb2d5b8 commit 621e155
Show file tree
Hide file tree
Showing 23 changed files with 242 additions and 157 deletions.
4 changes: 3 additions & 1 deletion Documentation/filesystems/Locking
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ be able to use diff(1).
prototypes:
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)(const struct dentry *, const struct inode *,
const struct dentry *, const struct inode *,
unsigned int, const char *, const struct qstr *);
int (*d_delete)(struct dentry *);
void (*d_release)(struct dentry *);
void (*d_iput)(struct dentry *, struct inode *);
Expand Down
7 changes: 7 additions & 0 deletions Documentation/filesystems/porting
Original file line number Diff line number Diff line change
Expand Up @@ -326,3 +326,10 @@ to it.
unreferenced dentries, and is now only called when the dentry refcount goes to
0. Even on 0 refcount transition, it must be able to tolerate being called 0,
1, or more times (eg. constant, idempotent).

---
[mandatory]

.d_compare() calling convention and locking rules are significantly
changed. Read updated documentation in Documentation/filesystems/vfs.txt (and
look at examples of other filesystems) for guidance.
26 changes: 23 additions & 3 deletions Documentation/filesystems/vfs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -848,7 +848,9 @@ defined:
struct dentry_operations {
int (*d_revalidate)(struct dentry *, struct nameidata *);
int (*d_hash)(struct dentry *, struct qstr *);
int (*d_compare)(struct dentry *, struct qstr *, struct qstr *);
int (*d_compare)(const struct dentry *, const struct inode *,
const struct dentry *, const struct inode *,
unsigned int, const char *, const struct qstr *);
int (*d_delete)(const struct dentry *);
void (*d_release)(struct dentry *);
void (*d_iput)(struct dentry *, struct inode *);
Expand All @@ -860,9 +862,27 @@ struct dentry_operations {
dcache. Most filesystems leave this as NULL, because all their
dentries in the dcache are valid

d_hash: called when the VFS adds a dentry to the hash table
d_hash: called when the VFS adds a dentry to the hash table. The first
dentry passed to d_hash is the parent directory that the name is
to be hashed into.

d_compare: called when a dentry should be compared with another
d_compare: called to compare a dentry name with a given name. The first
dentry is the parent of the dentry to be compared, the second is
the parent's inode, then the dentry and inode (may be NULL) of the
child dentry. len and name string are properties of the dentry to be
compared. qstr is the name to compare it with.

Must be constant and idempotent, and should not take locks if
possible, and should not or store into the dentry or inodes.
Should not dereference pointers outside the dentry or inodes without
lots of care (eg. d_parent, d_inode, d_name should not be used).

However, our vfsmount is pinned, and RCU held, so the dentries and
inodes won't disappear, neither will our sb or filesystem module.
->i_sb and ->d_sb may be used.

It is a tricky calling convention because it needs to be called under
"rcu-walk", ie. without any locks or references on things.

d_delete: called when the last reference to a dentry is dropped and the
dcache is deciding whether or not to cache it. Return 1 to delete
Expand Down
16 changes: 11 additions & 5 deletions drivers/staging/smbfs/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,10 @@ smb_dir_open(struct inode *dir, struct file *file)
*/
static int smb_lookup_validate(struct dentry *, struct nameidata *);
static int smb_hash_dentry(struct dentry *, struct qstr *);
static int smb_compare_dentry(struct dentry *, struct qstr *, struct qstr *);
static int smb_compare_dentry(const struct dentry *,
const struct inode *,
const struct dentry *, const struct inode *,
unsigned int, const char *, const struct qstr *);
static int smb_delete_dentry(const struct dentry *);

static const struct dentry_operations smbfs_dentry_operations =
Expand Down Expand Up @@ -347,14 +350,17 @@ smb_hash_dentry(struct dentry *dir, struct qstr *this)
}

static int
smb_compare_dentry(struct dentry *dir, struct qstr *a, struct qstr *b)
smb_compare_dentry(const struct dentry *parent,
const struct inode *pinode,
const struct dentry *dentry, const struct inode *inode,
unsigned int len, const char *str, const struct qstr *name)
{
int i, result = 1;

if (a->len != b->len)
if (len != name->len)
goto out;
for (i=0; i < a->len; i++) {
if (tolower(a->name[i]) != tolower(b->name[i]))
for (i=0; i < len; i++) {
if (tolower(str[i]) != tolower(name->name[i]))
goto out;
}
result = 0;
Expand Down
8 changes: 5 additions & 3 deletions fs/adfs/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -237,17 +237,19 @@ adfs_hash(struct dentry *parent, struct qstr *qstr)
* requirements of the underlying filesystem.
*/
static int
adfs_compare(struct dentry *parent, struct qstr *entry, struct qstr *name)
adfs_compare(const struct dentry *parent, const struct inode *pinode,
const struct dentry *dentry, const struct inode *inode,
unsigned int len, const char *str, const struct qstr *name)
{
int i;

if (entry->len != name->len)
if (len != name->len)
return 1;

for (i = 0; i < name->len; i++) {
char a, b;

a = entry->name[i];
a = str[i];
b = name->name[i];

if (a >= 'A' && a <= 'Z')
Expand Down
46 changes: 28 additions & 18 deletions fs/affs/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@ typedef int (*toupper_t)(int);

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_compare_dentry(const struct dentry *parent,
const struct inode *pinode,
const struct dentry *dentry, const struct inode *inode,
unsigned int len, const char *str, const struct qstr *name);
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_intl_compare_dentry(const struct dentry *parent,
const struct inode *pinode,
const struct dentry *dentry, const struct inode *inode,
unsigned int len, const char *str, const struct qstr *name);

const struct dentry_operations affs_dentry_operations = {
.d_hash = affs_hash_dentry,
Expand Down Expand Up @@ -88,29 +94,29 @@ affs_intl_hash_dentry(struct dentry *dentry, struct qstr *qstr)
return __affs_hash_dentry(dentry, qstr, affs_intl_toupper);
}

static inline int
__affs_compare_dentry(struct dentry *dentry, struct qstr *a, struct qstr *b, toupper_t toupper)
static inline int __affs_compare_dentry(unsigned int len,
const char *str, const struct qstr *name, toupper_t toupper)
{
const u8 *aname = a->name;
const u8 *bname = b->name;
int len;
const u8 *aname = str;
const u8 *bname = name->name;

/* 'a' is the qstr of an already existing dentry, so the name
* must be valid. 'b' must be validated first.
/*
* 'str' is the name of an already existing dentry, so the name
* must be valid. 'name' must be validated first.
*/

if (affs_check_name(b->name,b->len))
if (affs_check_name(name->name, name->len))
return 1;

/* If the names are longer than the allowed 30 chars,
/*
* If the names are longer than the allowed 30 chars,
* the excess is ignored, so their length may differ.
*/
len = a->len;
if (len >= 30) {
if (b->len < 30)
if (name->len < 30)
return 1;
len = 30;
} else if (len != b->len)
} else if (len != name->len)
return 1;

for (; len > 0; len--)
Expand All @@ -121,14 +127,18 @@ __affs_compare_dentry(struct dentry *dentry, struct qstr *a, struct qstr *b, tou
}

static int
affs_compare_dentry(struct dentry *dentry, struct qstr *a, struct qstr *b)
affs_compare_dentry(const struct dentry *parent, const struct inode *pinode,
const struct dentry *dentry, const struct inode *inode,
unsigned int len, const char *str, const struct qstr *name)
{
return __affs_compare_dentry(dentry, a, b, affs_toupper);
return __affs_compare_dentry(len, str, name, affs_toupper);
}
static int
affs_intl_compare_dentry(struct dentry *dentry, struct qstr *a, struct qstr *b)
affs_intl_compare_dentry(const struct dentry *parent,const struct inode *pinode,
const struct dentry *dentry, const struct inode *inode,
unsigned int len, const char *str, const struct qstr *name)
{
return __affs_compare_dentry(dentry, a, b, affs_intl_toupper);
return __affs_compare_dentry(len, str, name, affs_intl_toupper);
}

/*
Expand Down
12 changes: 7 additions & 5 deletions fs/cifs/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -715,13 +715,15 @@ static int cifs_ci_hash(struct dentry *dentry, struct qstr *q)
return 0;
}

static int cifs_ci_compare(struct dentry *dentry, struct qstr *a,
struct qstr *b)
static int cifs_ci_compare(const struct dentry *parent,
const struct inode *pinode,
const struct dentry *dentry, const struct inode *inode,
unsigned int len, const char *str, const struct qstr *name)
{
struct nls_table *codepage = CIFS_SB(dentry->d_inode->i_sb)->local_nls;
struct nls_table *codepage = CIFS_SB(pinode->i_sb)->local_nls;

if ((a->len == b->len) &&
(nls_strnicmp(codepage, a->name, b->name, a->len) == 0))
if ((name->len == len) &&
(nls_strnicmp(codepage, name->name, str, len) == 0))
return 0;
return 1;
}
Expand Down
4 changes: 3 additions & 1 deletion fs/dcache.c
Original file line number Diff line number Diff line change
Expand Up @@ -1437,7 +1437,9 @@ struct dentry * __d_lookup(struct dentry * parent, struct qstr * name)
*/
qstr = &dentry->d_name;
if (parent->d_op && parent->d_op->d_compare) {
if (parent->d_op->d_compare(parent, qstr, name))
if (parent->d_op->d_compare(parent, parent->d_inode,
dentry, dentry->d_inode,
qstr->len, qstr->name, name))
goto next;
} else {
if (qstr->len != len)
Expand Down
14 changes: 8 additions & 6 deletions fs/fat/namei_msdos.c
Original file line number Diff line number Diff line change
Expand Up @@ -164,16 +164,18 @@ static int msdos_hash(struct dentry *dentry, struct qstr *qstr)
* Compare two msdos names. If either of the names are invalid,
* we fall back to doing the standard name comparison.
*/
static int msdos_cmp(struct dentry *dentry, struct qstr *a, struct qstr *b)
static int msdos_cmp(const struct dentry *parent, const struct inode *pinode,
const struct dentry *dentry, const struct inode *inode,
unsigned int len, const char *str, const struct qstr *name)
{
struct fat_mount_options *options = &MSDOS_SB(dentry->d_sb)->options;
struct fat_mount_options *options = &MSDOS_SB(parent->d_sb)->options;
unsigned char a_msdos_name[MSDOS_NAME], b_msdos_name[MSDOS_NAME];
int error;

error = msdos_format_name(a->name, a->len, a_msdos_name, options);
error = msdos_format_name(name->name, name->len, a_msdos_name, options);
if (error)
goto old_compare;
error = msdos_format_name(b->name, b->len, b_msdos_name, options);
error = msdos_format_name(str, len, b_msdos_name, options);
if (error)
goto old_compare;
error = memcmp(a_msdos_name, b_msdos_name, MSDOS_NAME);
Expand All @@ -182,8 +184,8 @@ static int msdos_cmp(struct dentry *dentry, struct qstr *a, struct qstr *b)

old_compare:
error = 1;
if (a->len == b->len)
error = memcmp(a->name, b->name, a->len);
if (name->len == len)
error = memcmp(name->name, str, len);
goto out;
}

Expand Down
33 changes: 20 additions & 13 deletions fs/fat/namei_vfat.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,18 @@ static int vfat_revalidate_ci(struct dentry *dentry, struct nameidata *nd)
}

/* 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(unsigned int len, const char *name)
{
unsigned int len = qstr->len;

while (len && qstr->name[len - 1] == '.')
while (len && name[len - 1] == '.')
len--;
return len;
}

static unsigned int vfat_striptail_len(const struct qstr *qstr)
{
return __vfat_striptail_len(qstr->len, qstr->name);
}

/*
* Compute the hash for the vfat name corresponding to the dentry.
* Note: if the name is invalid, we leave the hash code unchanged so
Expand Down Expand Up @@ -133,16 +136,18 @@ static int vfat_hashi(struct dentry *dentry, struct qstr *qstr)
/*
* Case insensitive compare of two vfat names.
*/
static int vfat_cmpi(struct dentry *dentry, struct qstr *a, struct qstr *b)
static int vfat_cmpi(const struct dentry *parent, const struct inode *pinode,
const struct dentry *dentry, const struct inode *inode,
unsigned int len, const char *str, const struct qstr *name)
{
struct nls_table *t = MSDOS_SB(dentry->d_inode->i_sb)->nls_io;
struct nls_table *t = MSDOS_SB(parent->d_sb)->nls_io;
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_len(name);
blen = __vfat_striptail_len(len, str);
if (alen == blen) {
if (nls_strnicmp(t, a->name, b->name, alen) == 0)
if (nls_strnicmp(t, name->name, str, alen) == 0)
return 0;
}
return 1;
Expand All @@ -151,15 +156,17 @@ static int vfat_cmpi(struct dentry *dentry, struct qstr *a, struct qstr *b)
/*
* Case sensitive compare of two vfat names.
*/
static int vfat_cmp(struct dentry *dentry, struct qstr *a, struct qstr *b)
static int vfat_cmp(const struct dentry *parent, const struct inode *pinode,
const struct dentry *dentry, const struct inode *inode,
unsigned int len, const char *str, const struct qstr *name)
{
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_len(name);
blen = __vfat_striptail_len(len, str);
if (alen == blen) {
if (strncmp(a->name, b->name, alen) == 0)
if (strncmp(name->name, str, alen) == 0)
return 0;
}
return 1;
Expand Down
5 changes: 4 additions & 1 deletion fs/hfs/hfs_fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,10 @@ extern const struct dentry_operations hfs_dentry_operations;
extern int hfs_hash_dentry(struct dentry *, struct qstr *);
extern int hfs_strcmp(const unsigned char *, unsigned int,
const unsigned char *, unsigned int);
extern int hfs_compare_dentry(struct dentry *, struct qstr *, struct qstr *);
extern int hfs_compare_dentry(const struct dentry *parent,
const struct inode *pinode,
const struct dentry *dentry, const struct inode *inode,
unsigned int len, const char *str, const struct qstr *name);

/* trans.c */
extern void hfs_asc2mac(struct super_block *, struct hfs_name *, struct qstr *);
Expand Down
14 changes: 7 additions & 7 deletions fs/hfs/string.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,21 +92,21 @@ int hfs_strcmp(const unsigned char *s1, unsigned int len1,
* Test for equality of two strings in the HFS filename character ordering.
* return 1 on failure and 0 on success
*/
int hfs_compare_dentry(struct dentry *dentry, struct qstr *s1, struct qstr *s2)
int hfs_compare_dentry(const struct dentry *parent, const struct inode *pinode,
const struct dentry *dentry, const struct inode *inode,
unsigned int len, const char *str, const struct qstr *name)
{
const unsigned char *n1, *n2;
int len;

len = s1->len;
if (len >= HFS_NAMELEN) {
if (s2->len < HFS_NAMELEN)
if (name->len < HFS_NAMELEN)
return 1;
len = HFS_NAMELEN;
} else if (len != s2->len)
} else if (len != name->len)
return 1;

n1 = s1->name;
n2 = s2->name;
n1 = str;
n2 = name->name;
while (len--) {
if (caseorder[*n1++] != caseorder[*n2++])
return 1;
Expand Down
5 changes: 4 additions & 1 deletion fs/hfsplus/hfsplus_fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,10 @@ int hfsplus_strcmp(const struct hfsplus_unistr *, const struct hfsplus_unistr *)
int hfsplus_uni2asc(struct super_block *, const struct hfsplus_unistr *, char *, int *);
int hfsplus_asc2uni(struct super_block *, struct hfsplus_unistr *, const char *, int);
int hfsplus_hash_dentry(struct dentry *dentry, struct qstr *str);
int hfsplus_compare_dentry(struct dentry *dentry, struct qstr *s1, struct qstr *s2);
int hfsplus_compare_dentry(const struct dentry *parent,
const struct inode *pinode,
const struct dentry *dentry, const struct inode *inode,
unsigned int len, const char *str, const struct qstr *name);

/* wrapper.c */
int hfsplus_read_wrapper(struct super_block *);
Expand Down
Loading

0 comments on commit 621e155

Please sign in to comment.