Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

VFS: filename_create(): fix incorrect intent.

When asked to create a path ending '/', but which is not to be a
directory (LOOKUP_DIRECTORY not set), filename_create() will never try
to create the file. If it doesn't exist, -ENOENT is reported.

However, it still passes LOOKUP_CREATE|LOOKUP_EXCL to the filesystems
->lookup() function, even though there is no intent to create. This is
misleading and can cause incorrect behaviour.

If you try

ln -s foo /path/dir/

where 'dir' is a directory on an NFS filesystem which is not currently
known in the dcache, this will fail with ENOENT.

But as the name is not in the dcache, nfs_lookup gets called with
LOOKUP_CREATE|LOOKUP_EXCL and so it returns NULL without performing any
lookup, with the expectation that a subsequent call to create the target
will be made, and the lookup can be combined with the creation. In the
case with a trailing '/' and no LOOKUP_DIRECTORY, that call is never
made. Instead filename_create() sees that the dentry is not (yet)
positive and returns -ENOENT - even though the directory actually
exists.

So only set LOOKUP_CREATE|LOOKUP_EXCL if there really is an intent to
create, and use the absence of these flags to decide if -ENOENT should
be returned.

Note that filename_parentat() is only interested in LOOKUP_REVAL, so we
split that out and store it in 'reval_flag'. __lookup_hash() then gets
reval_flag combined with whatever create flags were determined to be
needed.

Reviewed-by: David Disseldorp <ddiss@suse.de>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: NeilBrown <neilb@suse.de>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

NeilBrown and committed by
Linus Torvalds
b3d4650d 115acbb5

+10 -12
+10 -12
fs/namei.c
··· 3673 3673 { 3674 3674 struct dentry *dentry = ERR_PTR(-EEXIST); 3675 3675 struct qstr last; 3676 + bool want_dir = lookup_flags & LOOKUP_DIRECTORY; 3677 + unsigned int reval_flag = lookup_flags & LOOKUP_REVAL; 3678 + unsigned int create_flags = LOOKUP_CREATE | LOOKUP_EXCL; 3676 3679 int type; 3677 3680 int err2; 3678 3681 int error; 3679 - bool is_dir = (lookup_flags & LOOKUP_DIRECTORY); 3680 3682 3681 - /* 3682 - * Note that only LOOKUP_REVAL and LOOKUP_DIRECTORY matter here. Any 3683 - * other flags passed in are ignored! 3684 - */ 3685 - lookup_flags &= LOOKUP_REVAL; 3686 - 3687 - error = filename_parentat(dfd, name, lookup_flags, path, &last, &type); 3683 + error = filename_parentat(dfd, name, reval_flag, path, &last, &type); 3688 3684 if (error) 3689 3685 return ERR_PTR(error); 3690 3686 ··· 3694 3698 /* don't fail immediately if it's r/o, at least try to report other errors */ 3695 3699 err2 = mnt_want_write(path->mnt); 3696 3700 /* 3697 - * Do the final lookup. 3701 + * Do the final lookup. Suppress 'create' if there is a trailing 3702 + * '/', and a directory wasn't requested. 3698 3703 */ 3699 - lookup_flags |= LOOKUP_CREATE | LOOKUP_EXCL; 3704 + if (last.name[last.len] && !want_dir) 3705 + create_flags = 0; 3700 3706 inode_lock_nested(path->dentry->d_inode, I_MUTEX_PARENT); 3701 - dentry = __lookup_hash(&last, path->dentry, lookup_flags); 3707 + dentry = __lookup_hash(&last, path->dentry, reval_flag | create_flags); 3702 3708 if (IS_ERR(dentry)) 3703 3709 goto unlock; 3704 3710 ··· 3714 3716 * all is fine. Let's be bastards - you had / on the end, you've 3715 3717 * been asking for (non-existent) directory. -ENOENT for you. 3716 3718 */ 3717 - if (unlikely(!is_dir && last.name[last.len])) { 3719 + if (unlikely(!create_flags)) { 3718 3720 error = -ENOENT; 3719 3721 goto fail; 3720 3722 }