Fix casesensitivity=insensitive deadlock

When casesensitivity=insensitive is set for the
file system, we can deadlock in a rename if the user uses different case
for each path. For example rename("A/some-file.txt", "a/some-file.txt").

The simple test for this is:

1. mkdir some-dir in a ZFS file system
2. touch some-dir/some-file.txt
3. mv Some-dir/some-file.txt some-dir/some-other-file.txt

This last request deadlocks trying to relock the i_mutex on the inode for
the parent directory.

The solution is to use d_add_ci in zpl_lookup if we are on a file system
that has the casesensitivity=insensitive attribute set.

This patch checks if we are working on a case insensitive file system and if
so, allocates storage for the case insensitive name and passes it to
zfs_lookup and then calls d_add_ci instead of d_splice_alias.

The performance impact seems to be minimal even though we have introduced a
kmalloc and kfree in the lookup path.

The problem was found when running Microsoft's FSCT against Samba on top of
ZFS On Linux.

Signed-off-by: Richard Sharpe <realrichardsharpe@gmail.com>
Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov>
Closes #4136
This commit is contained in:
Richard Sharpe 2015-12-27 16:08:05 -08:00 committed by Brian Behlendorf
parent d41e763c72
commit a99c845fdc
1 changed files with 30 additions and 2 deletions

View File

@ -44,13 +44,24 @@ zpl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
struct inode *ip; struct inode *ip;
int error; int error;
fstrans_cookie_t cookie; fstrans_cookie_t cookie;
pathname_t *ppn = NULL;
pathname_t pn;
zfs_sb_t *zsb = dentry->d_sb->s_fs_info;
if (dlen(dentry) > ZFS_MAXNAMELEN) if (dlen(dentry) > ZFS_MAXNAMELEN)
return (ERR_PTR(-ENAMETOOLONG)); return (ERR_PTR(-ENAMETOOLONG));
crhold(cr); crhold(cr);
cookie = spl_fstrans_mark(); cookie = spl_fstrans_mark();
error = -zfs_lookup(dir, dname(dentry), &ip, 0, cr, NULL, NULL);
/* If we are a case insensitive fs, we need the real name */
if (zsb->z_case == ZFS_CASE_INSENSITIVE) {
pn.pn_bufsize = ZFS_MAXNAMELEN;
pn.pn_buf = kmem_zalloc(ZFS_MAXNAMELEN, KM_SLEEP);
ppn = &pn;
}
error = -zfs_lookup(dir, dname(dentry), &ip, 0, cr, NULL, ppn);
spl_fstrans_unmark(cookie); spl_fstrans_unmark(cookie);
ASSERT3S(error, <=, 0); ASSERT3S(error, <=, 0);
crfree(cr); crfree(cr);
@ -63,13 +74,30 @@ zpl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
spin_unlock(&dentry->d_lock); spin_unlock(&dentry->d_lock);
if (error) { if (error) {
if (ppn)
kmem_free(pn.pn_buf, ZFS_MAXNAMELEN);
if (error == -ENOENT) if (error == -ENOENT)
return (d_splice_alias(NULL, dentry)); return (d_splice_alias(NULL, dentry));
else else
return (ERR_PTR(error)); return (ERR_PTR(error));
} }
return (d_splice_alias(ip, dentry)); /*
* If we are case insensitive, call the correct function
* to install the name.
*/
if (ppn) {
struct dentry *new_dentry;
struct qstr ci_name;
ci_name.name = pn.pn_buf;
ci_name.len = strlen(pn.pn_buf);
new_dentry = d_add_ci(dentry, ip, &ci_name);
kmem_free(pn.pn_buf, ZFS_MAXNAMELEN);
return (new_dentry);
} else {
return (d_splice_alias(ip, dentry));
}
} }
void void