mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Tom Talpey <tom@talpey.com>
To: NeilBrown <neil@brown.name>, Paulo Alcantara <pc@manguebit.org>,
	Namjae Jeon <linkinjeon@kernel.org>,
	Alexander Viro <viro@zeniv.linux.org.uk>,
	Christian Brauner <brauner@kernel.org>
Cc: Jeff Layton <jlayton@kernel.org>, Jan Kara <jack@suse.cz>,
	linux-fsdevel@vger.kernel.org, linux-cifs@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH 1/3] smb/client: don't unhash and rehash to prevent new opens.
Date: Wed, 21 Oct 2026 11:30:14 -0400	[thread overview]
Message-ID: <6233e33a-e6e2-4c9d-8372-5aa75f4f3314@talpey.com> (raw)
In-Reply-To: <20260928012812.2890035-2-neilb@ownmail.net>

One nit below...

On 9/27/2026 9:25 PM, NeilBrown wrote:
> From: NeilBrown <neil@brown.name>
> 
> smb/client needs to block new opens of the target of unlink and rename
> while the operation is progressing.  This stablises d_count() and allows
> a determination of whether a "silly-rename" is required.
> 
> It currently unhashes the dentry which will cause lookup to block on
> the parent directory i_rwsem.  Proposed changes to locking will cause
> this approach to stop working as the exclusivity will be provided for
> the dentry only, and only while it is hashed.
> 
> So we introduce a new machanism similar to that used by nfs.
> DCACHE_PRIVATE (given the name DCACHE_BLOCKED) is set when lookups need
> to be blocked.  ->d_revalidate checks for this and blocks.  This might
> still allow d_count() to increment, but once it has been tested as 1,
> there can be no new opens completed.
> 
> Unlike unhash which does not need to be reverted on error, and which
> must not be reverted on a successful d_move, blocking of opens must
> always be reverted.  So we don't block the open until after the last
> early "return", and we always unblock on the final "return".
> 
> Strictly speaking it is only necessary to block opens before the value
> of d_count() is tested under ->d_lock. But we block as early as possible
> to discourage new opens from starting.  As open is path-based in cifs a
> concurrent rename can be confused a rename of one other the paths.  Once
> cifs_open() has generated the full_path it doesn't hold any locks to
> prevent a rename from making that path invalid.
> 
> The important details of the interlock between __cifs_unlink (which both
> unlink and rename use) and "open" are that __cifs_unlink() sets
> DCACHE_BLOCKED  *before* testing d_count() which is
> in a d_lock locked region,  and cifs_d_revalidate() tests the bit *after*
> ->d_lock is taken by e.g. __d_lookup() to increment d_count().
> Thus ->d_lock provide serialization between the set and the test
> even though the test isn't in a locked region.
> (The fact that cifs_d_revalidate() always returned -ECHILD when
>   LOOKUP_RCU is important for this sequencing to work as it ensures
>   d_revalidate() is called *after* d_count() is incremented).
> 
> Signed-off-by: NeilBrown <neil@brown.name>
> ---
>   fs/smb/client/cifsfs.h |  8 +++++
>   fs/smb/client/dir.c    |  3 ++
>   fs/smb/client/inode.c  | 68 ++++++++++++++++++++++++------------------
>   3 files changed, 50 insertions(+), 29 deletions(-)
> 
> diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h
> index 0c85daa8386e..bcc0fb5c2c9b 100644
> --- a/fs/smb/client/cifsfs.h
> +++ b/fs/smb/client/cifsfs.h
> @@ -42,6 +42,14 @@ static inline unsigned long cifs_get_time(struct dentry *dentry)
>   	return (unsigned long) dentry->d_fsdata;
>   }
>   
> +/*
> + * This is set to block d_revalidate on a dentry that is being removed -
> + * the target of unlink or rename.  This causes any open attempt to
> + * block.  There may be existing opens but they can be detected by
> + * checking d_count() under ->d_lock.
> + */
> +#define DCACHE_BLOCKED DCACHE_PRIVATE

Why the alias? The blocking behavior is reasonable but #defining a
flag value which is purely local to this file seems unnecessary and
confusing. What if another file in fs/smb/client also overloads the
DCACHE_PRIVATE flag, for example. Consider removing this hunk.

Tom.


> +
>   extern struct file_system_type cifs_fs_type, smb3_fs_type;
>   extern const struct address_space_operations cifs_addr_ops;
>   extern const struct address_space_operations cifs_addr_ops_smallbuf;
> diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c
> index 6fa6d48fdfd3..88fff65320f5 100644
> --- a/fs/smb/client/dir.c
> +++ b/fs/smb/client/dir.c
> @@ -872,6 +872,9 @@ cifs_d_revalidate(struct inode *dir, const struct qstr *name,
>   	if (flags & LOOKUP_RCU)
>   		return -ECHILD;
>   
> +	/* Wait for pending rename/unlink */
> +	wait_var_event(&direntry->d_flags, !(direntry->d_flags & DCACHE_BLOCKED));
> +
>   	if (d_really_is_positive(direntry)) {
>   		int rc;
>   		struct inode *inode = d_inode(direntry);
> diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c
> index 12ed8db10e00..4d34c5622cd4 100644
> --- a/fs/smb/client/inode.c
> +++ b/fs/smb/client/inode.c
> @@ -1967,24 +1967,31 @@ static int __cifs_unlink(struct inode *dir, struct dentry *dentry, bool sillyren
>   	__u32 dosattr = 0, origattr = 0;
>   	struct TCP_Server_Info *server;
>   	struct iattr *attrs = NULL;
> -	bool rehash = false;
> +	bool unblock = false;
>   
>   	cifs_dbg(FYI, "cifs_unlink, dir=0x%p, dentry=0x%p\n", dir, dentry);
>   
>   	if (unlikely(cifs_forced_shutdown(cifs_sb)))
>   		return smb_EIO(smb_eio_trace_forced_shutdown);
>   
> -	/* Unhash dentry in advance to prevent any concurrent opens */
> -	spin_lock(&dentry->d_lock);
> -	if (!d_unhashed(dentry)) {
> -		__d_drop(dentry);
> -		rehash = true;
> -	}
> -	spin_unlock(&dentry->d_lock);
> -
>   	tlink = cifs_sb_tlink(cifs_sb);
>   	if (IS_ERR(tlink))
>   		return PTR_ERR(tlink);
> +
> +	/* opens might already be blocked by rename */
> +	if (!(dentry->d_flags & DCACHE_BLOCKED)) {
> +		/*
> +		 * Block opens - and all lookups that involve d_revalidate.
> +		 * This guarantees that if another thread tries to open(), it
> +		 * will either block, or will increment d_count()
> +		 * before we test it below.
> +		 */
> +		spin_lock(&dentry->d_lock);
> +		dentry->d_flags |= DCACHE_BLOCKED;
> +		spin_unlock(&dentry->d_lock);
> +		unblock = true;
> +	}
> +
>   	tcon = tlink_tcon(tlink);
>   	server = tcon->ses->server;
>   
> @@ -2107,8 +2114,13 @@ static int __cifs_unlink(struct inode *dir, struct dentry *dentry, bool sillyren
>   	kfree(attrs);
>   	free_xid(xid);
>   	cifs_put_tlink(tlink);
> -	if (rehash)
> -		d_rehash(dentry);
> +	/* Allow lookups/opens */
> +	if (unblock) {
> +		spin_lock(&dentry->d_lock);
> +		store_release_wake_up(&dentry->d_flags,
> +				      dentry->d_flags &~ DCACHE_BLOCKED);
> +		spin_unlock(&dentry->d_lock);
> +	}
>   	return rc;
>   }
>   
> @@ -2536,7 +2548,6 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir,
>   	struct cifs_sb_info *cifs_sb;
>   	struct tcon_link *tlink;
>   	struct cifs_tcon *tcon;
> -	bool rehash = false;
>   	unsigned int xid;
>   	int rc, tmprc;
>   	int retry_count = 0;
> @@ -2552,20 +2563,20 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir,
>   	if (unlikely(cifs_forced_shutdown(cifs_sb)))
>   		return smb_EIO(smb_eio_trace_forced_shutdown);
>   
> -	/*
> -	 * Prevent any concurrent opens on the target by unhashing the dentry.
> -	 * VFS already unhashes the target when renaming directories.
> -	 */
> -	if (d_is_positive(target_dentry) && !d_is_dir(target_dentry)) {
> -		if (!d_unhashed(target_dentry)) {
> -			d_drop(target_dentry);
> -			rehash = true;
> -		}
> -	}
> -
>   	tlink = cifs_sb_tlink(cifs_sb);
>   	if (IS_ERR(tlink))
>   		return PTR_ERR(tlink);
> +
> +	/*
> +	 * Block opens - and all lookups that involve d_revalidate.
> +	 * This guarantees that if another thread tries to open(), it
> +	 * will either block, or will increment d_count()
> +	 * before we test it in __cifs_unlink().
> +	 */
> +	spin_lock(&target_dentry->d_lock);
> +	target_dentry->d_flags |= DCACHE_BLOCKED;
> +	spin_unlock(&target_dentry->d_lock);
> +
>   	tcon = tlink_tcon(tlink);
>   	server = tcon->ses->server;
>   
> @@ -2605,8 +2616,6 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir,
>   		}
>   	}
>   
> -	if (!rc)
> -		rehash = false;
>   	/*
>   	 * No-replace is the natural behavior for CIFS, so skip unlink hacks.
>   	 */
> @@ -2698,8 +2707,6 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir,
>   			}
>   			rc = cifs_do_rename(xid, source_dentry, from_name,
>   					    target_dentry, to_name);
> -			if (!rc)
> -				rehash = false;
>   		}
>   	}
>   
> @@ -2713,8 +2720,11 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir,
>   	CIFS_I(source_dir)->time = CIFS_I(target_dir)->time = 0;
>   
>   cifs_rename_exit:
> -	if (rehash)
> -		d_rehash(target_dentry);
> +	/* Allow lookups/opens */
> +	spin_lock(&target_dentry->d_lock);
> +	store_release_wake_up(&target_dentry->d_flags,
> +			      target_dentry->d_flags &~ DCACHE_BLOCKED);
> +	spin_unlock(&target_dentry->d_lock);
>   	kfree(info_buf_source);
>   	free_dentry_path(page2);
>   	free_dentry_path(page1);
> 
> base-commit: 3879f51857325da9bf3cfb073280257cd16ae067


  reply	other threads:[~2026-10-21 15:30 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-28  1:25 [PATCH 0/3] smb/client(cifs): prepare for locking changes NeilBrown
2026-09-28  1:25 ` [PATCH 1/3] smb/client: don't unhash and rehash to prevent new opens NeilBrown
2026-10-21 15:30   ` Tom Talpey [this message]
2026-09-28  1:25 ` [PATCH 2/3] smb/client: use d_alloc_trylock in cifs_silly_fullpath() NeilBrown
2026-09-28  1:25 ` [PATCH 3/3] smb/client: Use d_alloc_trylock() in cifs_prime_dcache() NeilBrown
2026-10-21  9:55 ` [PATCH 0/3] smb/client(cifs): prepare for locking changes Christian Brauner
2026-10-21 15:31   ` Tom Talpey

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=6233e33a-e6e2-4c9d-8372-5aa75f4f3314@talpey.com \
    --to=tom@talpey.com \
    --cc=brauner@kernel.org \
    --cc=jack@suse.cz \
    --cc=jlayton@kernel.org \
    --cc=linkinjeon@kernel.org \
    --cc=linux-cifs@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=neil@brown.name \
    --cc=pc@manguebit.org \
    --cc=viro@zeniv.linux.org.uk \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®