All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] VFS: filename_create(): fix incorrect intent.
@ 2022-03-28  0:56 NeilBrown
  2022-03-29 15:29 ` Jeff Layton
  2022-03-30  8:14 ` David Disseldorp
  0 siblings, 2 replies; 6+ messages in thread
From: NeilBrown @ 2022-03-28  0:56 UTC (permalink / raw)
  To: Al Viro; +Cc: linux-nfs, linux-fsdevel, LKML


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.
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 as 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.

Signed-off-by: NeilBrown <neilb@suse.de>
---
 fs/namei.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index 3f1829b3ab5b..3ffb42e56a8e 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -3676,7 +3676,6 @@ static struct dentry *filename_create(int dfd, struct filename *name,
 	int type;
 	int err2;
 	int error;
-	bool is_dir = (lookup_flags & LOOKUP_DIRECTORY);
 
 	/*
 	 * Note that only LOOKUP_REVAL and LOOKUP_DIRECTORY matter here. Any
@@ -3698,9 +3697,11 @@ static struct dentry *filename_create(int dfd, struct filename *name,
 	/* don't fail immediately if it's r/o, at least try to report other errors */
 	err2 = mnt_want_write(path->mnt);
 	/*
-	 * Do the final lookup.
+	 * Do the final lookup.  Request 'create' only if there is no trailing
+	 * '/', or if directory is requested.
 	 */
-	lookup_flags |= LOOKUP_CREATE | LOOKUP_EXCL;
+	if (!last.name[last.len] || (lookup_flags & LOOKUP_DIRECTORY))
+		lookup_flags |= LOOKUP_CREATE | LOOKUP_EXCL;
 	inode_lock_nested(path->dentry->d_inode, I_MUTEX_PARENT);
 	dentry = __lookup_hash(&last, path->dentry, lookup_flags);
 	if (IS_ERR(dentry))
@@ -3716,7 +3717,7 @@ static struct dentry *filename_create(int dfd, struct filename *name,
 	 * all is fine. Let's be bastards - you had / on the end, you've
 	 * been asking for (non-existent) directory. -ENOENT for you.
 	 */
-	if (unlikely(!is_dir && last.name[last.len])) {
+	if (!likely(lookup_flags & LOOKUP_CREATE)) {
 		error = -ENOENT;
 		goto fail;
 	}
-- 
2.35.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH] VFS: filename_create(): fix incorrect intent.
  2022-03-28  0:56 [PATCH] VFS: filename_create(): fix incorrect intent NeilBrown
@ 2022-03-29 15:29 ` Jeff Layton
  2022-03-30  8:14 ` David Disseldorp
  1 sibling, 0 replies; 6+ messages in thread
From: Jeff Layton @ 2022-03-29 15:29 UTC (permalink / raw)
  To: NeilBrown, Al Viro; +Cc: linux-nfs, linux-fsdevel, LKML

On Mon, 2022-03-28 at 11:56 +1100, NeilBrown wrote:
> 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.
> 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 as 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.
> 
> Signed-off-by: NeilBrown <neilb@suse.de>
> ---
>  fs/namei.c | 9 +++++----
>  1 file changed, 5 insertions(+), 4 deletions(-)
> 
> diff --git a/fs/namei.c b/fs/namei.c
> index 3f1829b3ab5b..3ffb42e56a8e 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -3676,7 +3676,6 @@ static struct dentry *filename_create(int dfd, struct filename *name,
>  	int type;
>  	int err2;
>  	int error;
> -	bool is_dir = (lookup_flags & LOOKUP_DIRECTORY);
>  
>  	/*
>  	 * Note that only LOOKUP_REVAL and LOOKUP_DIRECTORY matter here. Any
> @@ -3698,9 +3697,11 @@ static struct dentry *filename_create(int dfd, struct filename *name,
>  	/* don't fail immediately if it's r/o, at least try to report other errors */
>  	err2 = mnt_want_write(path->mnt);
>  	/*
> -	 * Do the final lookup.
> +	 * Do the final lookup.  Request 'create' only if there is no trailing
> +	 * '/', or if directory is requested.
>  	 */
> -	lookup_flags |= LOOKUP_CREATE | LOOKUP_EXCL;
> +	if (!last.name[last.len] || (lookup_flags & LOOKUP_DIRECTORY))
> +		lookup_flags |= LOOKUP_CREATE | LOOKUP_EXCL;
>  	inode_lock_nested(path->dentry->d_inode, I_MUTEX_PARENT);
>  	dentry = __lookup_hash(&last, path->dentry, lookup_flags);
>  	if (IS_ERR(dentry))
> @@ -3716,7 +3717,7 @@ static struct dentry *filename_create(int dfd, struct filename *name,
>  	 * all is fine. Let's be bastards - you had / on the end, you've
>  	 * been asking for (non-existent) directory. -ENOENT for you.
>  	 */
> -	if (unlikely(!is_dir && last.name[last.len])) {
> +	if (!likely(lookup_flags & LOOKUP_CREATE)) {
>  		error = -ENOENT;
>  		goto fail;
>  	}

Seems like a sane enough fix. Nice catch.

Reviewed-by: Jeff Layton <jlayton@kernel.org>

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] VFS: filename_create(): fix incorrect intent.
  2022-03-28  0:56 [PATCH] VFS: filename_create(): fix incorrect intent NeilBrown
  2022-03-29 15:29 ` Jeff Layton
@ 2022-03-30  8:14 ` David Disseldorp
  2022-03-30 22:59   ` NeilBrown
  1 sibling, 1 reply; 6+ messages in thread
From: David Disseldorp @ 2022-03-30  8:14 UTC (permalink / raw)
  To: NeilBrown; +Cc: Al Viro, linux-nfs, linux-fsdevel, LKML

Hi Neil,

I gave this a spin and was wondering why xfstests wouldn't start with
this change...

On Mon, 28 Mar 2022 11:56:48 +1100, NeilBrown wrote:
...
> 
> diff --git a/fs/namei.c b/fs/namei.c
> index 3f1829b3ab5b..3ffb42e56a8e 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -3676,7 +3676,6 @@ static struct dentry *filename_create(int dfd, struct filename *name,
>  	int type;
>  	int err2;
>  	int error;
> -	bool is_dir = (lookup_flags & LOOKUP_DIRECTORY);
>  
>  	/*
>  	 * Note that only LOOKUP_REVAL and LOOKUP_DIRECTORY matter here. Any
> @@ -3698,9 +3697,11 @@ static struct dentry *filename_create(int dfd, struct filename *name,
>  	/* don't fail immediately if it's r/o, at least try to report other errors */
>  	err2 = mnt_want_write(path->mnt);
>  	/*
> -	 * Do the final lookup.
> +	 * Do the final lookup.  Request 'create' only if there is no trailing
> +	 * '/', or if directory is requested.
>  	 */
> -	lookup_flags |= LOOKUP_CREATE | LOOKUP_EXCL;
> +	if (!last.name[last.len] || (lookup_flags & LOOKUP_DIRECTORY))
> +		lookup_flags |= LOOKUP_CREATE | LOOKUP_EXCL;

This doesn't look right, as any LOOKUP_DIRECTORY flag gets dropped via
the prior "lookup_flags &= LOOKUP_REVAL;".

Cheers, David

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] VFS: filename_create(): fix incorrect intent.
  2022-03-30  8:14 ` David Disseldorp
@ 2022-03-30 22:59   ` NeilBrown
  2022-03-31  9:49     ` David Disseldorp
  0 siblings, 1 reply; 6+ messages in thread
From: NeilBrown @ 2022-03-30 22:59 UTC (permalink / raw)
  To: David Disseldorp; +Cc: Al Viro, linux-nfs, linux-fsdevel, LKML

On Wed, 30 Mar 2022, David Disseldorp wrote:
> Hi Neil,
> 
> I gave this a spin and was wondering why xfstests wouldn't start with
> this change...
> 
> On Mon, 28 Mar 2022 11:56:48 +1100, NeilBrown wrote:
> ...
> > 
> > diff --git a/fs/namei.c b/fs/namei.c
> > index 3f1829b3ab5b..3ffb42e56a8e 100644
> > --- a/fs/namei.c
> > +++ b/fs/namei.c
> > @@ -3676,7 +3676,6 @@ static struct dentry *filename_create(int dfd, struct filename *name,
> >  	int type;
> >  	int err2;
> >  	int error;
> > -	bool is_dir = (lookup_flags & LOOKUP_DIRECTORY);
> >  
> >  	/*
> >  	 * Note that only LOOKUP_REVAL and LOOKUP_DIRECTORY matter here. Any
> > @@ -3698,9 +3697,11 @@ static struct dentry *filename_create(int dfd, struct filename *name,
> >  	/* don't fail immediately if it's r/o, at least try to report other errors */
> >  	err2 = mnt_want_write(path->mnt);
> >  	/*
> > -	 * Do the final lookup.
> > +	 * Do the final lookup.  Request 'create' only if there is no trailing
> > +	 * '/', or if directory is requested.
> >  	 */
> > -	lookup_flags |= LOOKUP_CREATE | LOOKUP_EXCL;
> > +	if (!last.name[last.len] || (lookup_flags & LOOKUP_DIRECTORY))
> > +		lookup_flags |= LOOKUP_CREATE | LOOKUP_EXCL;
> 
> This doesn't look right, as any LOOKUP_DIRECTORY flag gets dropped via
> the prior "lookup_flags &= LOOKUP_REVAL;".

Arg.. thanks for testing - I clearly should have tested more broadly.

I could leave the "is_dir" variable there I guess.
Or maybe the masking statement should be 
    lookup_flags &= LOOKUP_REVAL | LOOKUP_DIRECTORY;
as that is a better match for the comment.

Thanks,
NeilBrown

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] VFS: filename_create(): fix incorrect intent.
  2022-03-30 22:59   ` NeilBrown
@ 2022-03-31  9:49     ` David Disseldorp
  2022-03-31 10:40       ` NeilBrown
  0 siblings, 1 reply; 6+ messages in thread
From: David Disseldorp @ 2022-03-31  9:49 UTC (permalink / raw)
  To: NeilBrown; +Cc: Al Viro, linux-nfs, linux-fsdevel, LKML

On Thu, 31 Mar 2022 09:59:48 +1100, NeilBrown wrote:

> On Wed, 30 Mar 2022, David Disseldorp wrote:
> > Hi Neil,
> > 
> > I gave this a spin and was wondering why xfstests wouldn't start with
> > this change...
> > 
> > On Mon, 28 Mar 2022 11:56:48 +1100, NeilBrown wrote:
> > ...  
> > > 
> > > diff --git a/fs/namei.c b/fs/namei.c
> > > index 3f1829b3ab5b..3ffb42e56a8e 100644
> > > --- a/fs/namei.c
> > > +++ b/fs/namei.c
> > > @@ -3676,7 +3676,6 @@ static struct dentry *filename_create(int dfd, struct filename *name,
> > >  	int type;
> > >  	int err2;
> > >  	int error;
> > > -	bool is_dir = (lookup_flags & LOOKUP_DIRECTORY);
> > >  
> > >  	/*
> > >  	 * Note that only LOOKUP_REVAL and LOOKUP_DIRECTORY matter here. Any
> > > @@ -3698,9 +3697,11 @@ static struct dentry *filename_create(int dfd, struct filename *name,
> > >  	/* don't fail immediately if it's r/o, at least try to report other errors */
> > >  	err2 = mnt_want_write(path->mnt);
> > >  	/*
> > > -	 * Do the final lookup.
> > > +	 * Do the final lookup.  Request 'create' only if there is no trailing
> > > +	 * '/', or if directory is requested.
> > >  	 */
> > > -	lookup_flags |= LOOKUP_CREATE | LOOKUP_EXCL;
> > > +	if (!last.name[last.len] || (lookup_flags & LOOKUP_DIRECTORY))
> > > +		lookup_flags |= LOOKUP_CREATE | LOOKUP_EXCL;  
> > 
> > This doesn't look right, as any LOOKUP_DIRECTORY flag gets dropped via
> > the prior "lookup_flags &= LOOKUP_REVAL;".  
> 
> Arg.. thanks for testing - I clearly should have tested more broadly.
> 
> I could leave the "is_dir" variable there I guess.
> Or maybe the masking statement should be 
>     lookup_flags &= LOOKUP_REVAL | LOOKUP_DIRECTORY;
> as that is a better match for the comment.

Modifying "lookup_flags" results in changed filename_parentat() and
__lookup_hash() parameters, which isn't an intended consequence IIUC. I
think retaining "is_dir" would make sense.

Cheers, David

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] VFS: filename_create(): fix incorrect intent.
  2022-03-31  9:49     ` David Disseldorp
@ 2022-03-31 10:40       ` NeilBrown
  0 siblings, 0 replies; 6+ messages in thread
From: NeilBrown @ 2022-03-31 10:40 UTC (permalink / raw)
  To: David Disseldorp; +Cc: Al Viro, linux-nfs, linux-fsdevel, LKML

On Thu, 31 Mar 2022, David Disseldorp wrote:
> On Thu, 31 Mar 2022 09:59:48 +1100, NeilBrown wrote:
> 
> > On Wed, 30 Mar 2022, David Disseldorp wrote:
> > > Hi Neil,
> > > 
> > > I gave this a spin and was wondering why xfstests wouldn't start with
> > > this change...
> > > 
> > > On Mon, 28 Mar 2022 11:56:48 +1100, NeilBrown wrote:
> > > ...  
> > > > 
> > > > diff --git a/fs/namei.c b/fs/namei.c
> > > > index 3f1829b3ab5b..3ffb42e56a8e 100644
> > > > --- a/fs/namei.c
> > > > +++ b/fs/namei.c
> > > > @@ -3676,7 +3676,6 @@ static struct dentry *filename_create(int dfd, struct filename *name,
> > > >  	int type;
> > > >  	int err2;
> > > >  	int error;
> > > > -	bool is_dir = (lookup_flags & LOOKUP_DIRECTORY);
> > > >  
> > > >  	/*
> > > >  	 * Note that only LOOKUP_REVAL and LOOKUP_DIRECTORY matter here. Any
> > > > @@ -3698,9 +3697,11 @@ static struct dentry *filename_create(int dfd, struct filename *name,
> > > >  	/* don't fail immediately if it's r/o, at least try to report other errors */
> > > >  	err2 = mnt_want_write(path->mnt);
> > > >  	/*
> > > > -	 * Do the final lookup.
> > > > +	 * Do the final lookup.  Request 'create' only if there is no trailing
> > > > +	 * '/', or if directory is requested.
> > > >  	 */
> > > > -	lookup_flags |= LOOKUP_CREATE | LOOKUP_EXCL;
> > > > +	if (!last.name[last.len] || (lookup_flags & LOOKUP_DIRECTORY))
> > > > +		lookup_flags |= LOOKUP_CREATE | LOOKUP_EXCL;  
> > > 
> > > This doesn't look right, as any LOOKUP_DIRECTORY flag gets dropped via
> > > the prior "lookup_flags &= LOOKUP_REVAL;".  
> > 
> > Arg.. thanks for testing - I clearly should have tested more broadly.
> > 
> > I could leave the "is_dir" variable there I guess.
> > Or maybe the masking statement should be 
> >     lookup_flags &= LOOKUP_REVAL | LOOKUP_DIRECTORY;
> > as that is a better match for the comment.
> 
> Modifying "lookup_flags" results in changed filename_parentat() and
> __lookup_hash() parameters, which isn't an intended consequence IIUC. I
> think retaining "is_dir" would make sense.

I think retaining is_dir is ugly.
Given that LOOKUP_DIRECTORY is meaningful, why mask it off?

The only flag *ever* passed to filename_parentat() is LOOKUP_REVAL, so
maybe it would make sense to change the parameter to be called "reval"
to make the meaning more obvious.

The only other use of lookup_flags is to pass it to ->lookup().
I guess LOOKUP_DIRECTORY isn't really meaningful there .. though it does
say "this lookup is never for a non-directory"... might that be helpful?

Maybe I'll have another look in the morning.

Thanks,
NeilBrown

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2022-03-31 10:40 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-03-28  0:56 [PATCH] VFS: filename_create(): fix incorrect intent NeilBrown
2022-03-29 15:29 ` Jeff Layton
2022-03-30  8:14 ` David Disseldorp
2022-03-30 22:59   ` NeilBrown
2022-03-31  9:49     ` David Disseldorp
2022-03-31 10:40       ` NeilBrown

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.