]> granicus.if.org Git - zfs/commitdiff
Fix casesensitivity=insensitive deadlock
authorRichard Sharpe <rsharpe@samba.org>
Mon, 28 Dec 2015 00:08:05 +0000 (16:08 -0800)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Fri, 8 Jan 2016 19:05:07 +0000 (11:05 -0800)
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

module/zfs/zpl_inode.c

index e0e8ee3ac58304efd0f013a8d4c180d12d6a0e63..69a5db4daab5983fc10c593c3dfc4ca5c2faddab 100644 (file)
@@ -44,13 +44,24 @@ zpl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
        struct inode *ip;
        int error;
        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)
                return (ERR_PTR(-ENAMETOOLONG));
 
        crhold(cr);
        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);
        ASSERT3S(error, <=, 0);
        crfree(cr);
@@ -63,13 +74,30 @@ zpl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
        spin_unlock(&dentry->d_lock);
 
        if (error) {
+               if (ppn)
+                       kmem_free(pn.pn_buf, ZFS_MAXNAMELEN);
                if (error == -ENOENT)
                        return (d_splice_alias(NULL, dentry));
                else
                        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