Linux CIFS filesystem development
 help / color / mirror / Atom feed
* [PATCH 00/20] smb: client: cached dir fixes and improvements
@ 2025-09-29 13:27 Enzo Matsumiya
  2025-09-29 13:27 ` [PATCH 01/20] smb: client: remove cfids_invalidation_worker Enzo Matsumiya
                   ` (20 more replies)
  0 siblings, 21 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:27 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

Hi,

This patch series aims to refactor cached dir related code in order to
improve performance, improve code maintenance/readability, and of course
fix several, existing and potential, bugs.

Please note that the below only makes sense to the whole series applied.

Semantic fixes:
- cfid->has_lease vs cfid->is_open: when opening a cached dir, we get a fid
  (is_open) and a lease (has_lease), however, has_lease is used differently
  throughout the code, meaning, most of the time, that the cfid is 'usable'
  (fix in patch 11)
- refcounting also follows has_lease, up to a point, when we need to
  'steal' the reference, then we might have a cfid with 2 refs but
  has_lease == false (fix in patches 1-5)
- cfid lookup: currently done with open_cached_dir() with @lookup_only arg,
  but that is not visibly good-looking and also highly inflexible (because
  it only works for paths (char *).


Technical fixes:
- due to the many "Dentry still in use" bugs, cleaning up a cfid has become
  too complex -- there are 3 workers to do that asynchronously, and the
  release callback itself.  Complexity aside, this still has bugs because
  open_cached_dir() design doesn't account for any concurrent invalidation,
  leading sometimes to double opens/closes, sometimes straight UAF/deadlock
  bugs (examples upon request).
  (fix in patches 1-11)
- locking: the list lock is not used consistently; sometimes protecting only
  the list, sometimes protecting only a cfid, sometimes both.
  cfid->fid_lock only protects ->dentry, nothing else.  This leads to
  inconsistent data being read when a concurrent invalidation occurs, e.g.
  cached_dir_lease_break() (sets ->time = 0) vs cifs_dentry_needs_reval()
  (reads ->time unlocked)
  * also, open_cached_dir() always assume it has >1 refs, but such
    assumption is proven wrong when SMB2_open_init() triggers
    smb2_reconnect(), and kref_put() is ran locked in the rc != 0 case,
    leading to a deadlock because the extra ref has been dropped async
  (both fixed in patch 19 and others)

Improvements:
Having all above fixes and changes allows a cleaner code with a simpler
design:
- code readability is improved (cf. whole series)
- usage of cached dirs in places that weren't making use of it (cf. patches
  12-18)
- patch 19 (locking) not only fixes the synchronization problems, but RCU +
  seqcounting allows faster lookups (read-mostly) while also allowing
  consistent reads and stability for callers (prevents UAF)
- because a directory is always a parent, bake-in support for when opening
  a path, ParentLeaseKey can be set for any target child (cf. patch 12)


Cheers,

Enzo Matsumiya (20):
  smb: client: remove cfids_invalidation_worker
  smb: client: remove cached_dir_offload_close/close_work
  smb: client: remove cached_dir_put_work/put_work
  smb: client: remove cached_fids->dying list
  smb: client: remove cached_fid->on_list
  smb: client: merge {close,invalidate}_all_cached_dirs()
  smb: client: merge free_cached_dir in release callback
  smb: client: split find_or_create_cached_dir()
  smb: client: enhance cached dir lookups
  smb: client: refactor dropping cached dirs
  smb: client: simplify cached_fid state checking
  smb: client: prevent lease breaks of cached parents when opening
    children
  smb: client: actually use cached dirs on readdir
  smb: client: wait for concurrent caching of dirents in cifs_readdir()
  smb: client: remove cached_dirent->fattr
  smb: client: add is_dir argument to query_path_info
  smb: client: use cached dir on queryfs/smb2_compound_op
  smb: client: fix dentry revalidation of cached root
  smb: client: rework cached dirs synchronization
  smb: client: cleanup open_cached_dir()

 fs/smb/client/cached_dir.c | 946 ++++++++++++++++---------------------
 fs/smb/client/cached_dir.h |  74 +--
 fs/smb/client/cifs_debug.c |   7 +-
 fs/smb/client/cifsfs.c     |   2 +-
 fs/smb/client/cifsglob.h   |   5 +-
 fs/smb/client/dir.c        |  27 +-
 fs/smb/client/file.c       |   2 +-
 fs/smb/client/inode.c      |  38 +-
 fs/smb/client/misc.c       |   9 +-
 fs/smb/client/readdir.c    | 146 +++---
 fs/smb/client/smb1ops.c    |   6 +-
 fs/smb/client/smb2inode.c  |  48 +-
 fs/smb/client/smb2misc.c   |   2 +-
 fs/smb/client/smb2ops.c    |  49 +-
 fs/smb/client/smb2pdu.c    |  99 +++-
 fs/smb/client/smb2proto.h  |  10 +-
 16 files changed, 733 insertions(+), 737 deletions(-)

-- 
2.49.0


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

* [PATCH 01/20] smb: client: remove cfids_invalidation_worker
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
@ 2025-09-29 13:27 ` Enzo Matsumiya
  2025-09-29 13:27 ` [PATCH 02/20] smb: client: remove cached_dir_offload_close/close_work Enzo Matsumiya
                   ` (19 subsequent siblings)
  20 siblings, 0 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:27 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

We can do the same cleanup on laundromat.

On invalidate_all_cached_dirs(), run laundromat worker with 0 timeout
and flush it for immediate + sync cleanup.

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cached_dir.c | 39 ++++++++++----------------------------
 fs/smb/client/cached_dir.h |  1 -
 2 files changed, 10 insertions(+), 30 deletions(-)

diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index b69daeb1301b..f61fef810a23 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -553,13 +553,13 @@ void invalidate_all_cached_dirs(struct cifs_tcon *tcon)
 	struct cached_fids *cfids = tcon->cfids;
 	struct cached_fid *cfid, *q;
 
-	if (cfids == NULL)
+	if (!cfids)
 		return;
 
 	/*
 	 * Mark all the cfids as closed, and move them to the cfids->dying list.
-	 * They'll be cleaned up later by cfids_invalidation_worker. Take
-	 * a reference to each cfid during this process.
+	 * They'll be cleaned up by laundromat.  Take a reference to each cfid
+	 * during this process.
 	 */
 	spin_lock(&cfids->cfid_list_lock);
 	list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {
@@ -576,12 +576,11 @@ void invalidate_all_cached_dirs(struct cifs_tcon *tcon)
 		} else
 			kref_get(&cfid->refcount);
 	}
-	/*
-	 * Queue dropping of the dentries once locks have been dropped
-	 */
-	if (!list_empty(&cfids->dying))
-		queue_work(cfid_put_wq, &cfids->invalidation_work);
 	spin_unlock(&cfids->cfid_list_lock);
+
+	/* run laundromat unconditionally now as there might have been previously queued work */
+	mod_delayed_work(cfid_put_wq, &cfids->laundromat_work, 0);
+	flush_delayed_work(&cfids->laundromat_work);
 }
 
 static void
@@ -702,25 +701,6 @@ static void free_cached_dir(struct cached_fid *cfid)
 	kfree(cfid);
 }
 
-static void cfids_invalidation_worker(struct work_struct *work)
-{
-	struct cached_fids *cfids = container_of(work, struct cached_fids,
-						 invalidation_work);
-	struct cached_fid *cfid, *q;
-	LIST_HEAD(entry);
-
-	spin_lock(&cfids->cfid_list_lock);
-	/* move cfids->dying to the local list */
-	list_cut_before(&entry, &cfids->dying, &cfids->dying);
-	spin_unlock(&cfids->cfid_list_lock);
-
-	list_for_each_entry_safe(cfid, q, &entry, entry) {
-		list_del(&cfid->entry);
-		/* Drop the ref-count acquired in invalidate_all_cached_dirs */
-		kref_put(&cfid->refcount, smb2_close_cached_fid);
-	}
-}
-
 static void cfids_laundromat_worker(struct work_struct *work)
 {
 	struct cached_fids *cfids;
@@ -731,6 +711,9 @@ static void cfids_laundromat_worker(struct work_struct *work)
 	cfids = container_of(work, struct cached_fids, laundromat_work.work);
 
 	spin_lock(&cfids->cfid_list_lock);
+	/* move cfids->dying to the local list */
+	list_cut_before(&entry, &cfids->dying, &cfids->dying);
+
 	list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {
 		if (cfid->last_access_time &&
 		    time_after(jiffies, cfid->last_access_time + HZ * dir_cache_timeout)) {
@@ -787,7 +770,6 @@ struct cached_fids *init_cached_dirs(void)
 	INIT_LIST_HEAD(&cfids->entries);
 	INIT_LIST_HEAD(&cfids->dying);
 
-	INIT_WORK(&cfids->invalidation_work, cfids_invalidation_worker);
 	INIT_DELAYED_WORK(&cfids->laundromat_work, cfids_laundromat_worker);
 	queue_delayed_work(cfid_put_wq, &cfids->laundromat_work,
 			   dir_cache_timeout * HZ);
@@ -808,7 +790,6 @@ void free_cached_dirs(struct cached_fids *cfids)
 		return;
 
 	cancel_delayed_work_sync(&cfids->laundromat_work);
-	cancel_work_sync(&cfids->invalidation_work);
 
 	spin_lock(&cfids->cfid_list_lock);
 	list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {
diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h
index 46b5a2fdf15b..a3757a736d3e 100644
--- a/fs/smb/client/cached_dir.h
+++ b/fs/smb/client/cached_dir.h
@@ -60,7 +60,6 @@ struct cached_fids {
 	int num_entries;
 	struct list_head entries;
 	struct list_head dying;
-	struct work_struct invalidation_work;
 	struct delayed_work laundromat_work;
 };
 
-- 
2.49.0


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

* [PATCH 02/20] smb: client: remove cached_dir_offload_close/close_work
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
  2025-09-29 13:27 ` [PATCH 01/20] smb: client: remove cfids_invalidation_worker Enzo Matsumiya
@ 2025-09-29 13:27 ` Enzo Matsumiya
  2025-09-29 13:27 ` [PATCH 03/20] smb: client: remove cached_dir_put_work/put_work Enzo Matsumiya
                   ` (18 subsequent siblings)
  20 siblings, 0 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:27 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

Make put_work an 'async dput' and then move cfid to dying list so
laundromat can cleanup the rest.

Other changes:
- add drop_cfid() helper to drop entries counter, dput and close
  synchronously, when possible

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cached_dir.c | 142 +++++++++++++++++--------------------
 fs/smb/client/cached_dir.h |   1 -
 2 files changed, 66 insertions(+), 77 deletions(-)

diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index f61fef810a23..8689ee4a883d 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -22,6 +22,36 @@ struct cached_dir_dentry {
 	struct dentry *dentry;
 };
 
+static inline void drop_cfid(struct cached_fid *cfid)
+{
+	struct dentry *dentry = NULL;
+
+	spin_lock(&cfid->cfids->cfid_list_lock);
+	if (cfid->on_list) {
+		list_del(&cfid->entry);
+		cfid->on_list = false;
+		cfid->cfids->num_entries--;
+	}
+
+	spin_lock(&cfid->fid_lock);
+	swap(cfid->dentry, dentry);
+	spin_unlock(&cfid->fid_lock);
+	spin_unlock(&cfid->cfids->cfid_list_lock);
+
+	dput(dentry);
+
+	if (cfid->is_open) {
+		int rc;
+
+		cfid->is_open = false;
+		rc = SMB2_close(0, cfid->tcon, cfid->fid.persistent_fid, cfid->fid.volatile_fid);
+
+		/* SMB2_close should handle -EBUSY or -EAGAIN */
+		if (rc)
+			cifs_dbg(VFS, "close cached dir rc %d\n", rc);
+	}
+}
+
 static struct cached_fid *find_or_create_cached_dir(struct cached_fids *cfids,
 						    const char *path,
 						    bool lookup_only,
@@ -434,28 +464,9 @@ int open_cached_dir_by_dentry(struct cifs_tcon *tcon,
 static void
 smb2_close_cached_fid(struct kref *ref)
 {
-	struct cached_fid *cfid = container_of(ref, struct cached_fid,
-					       refcount);
-	int rc;
-
-	spin_lock(&cfid->cfids->cfid_list_lock);
-	if (cfid->on_list) {
-		list_del(&cfid->entry);
-		cfid->on_list = false;
-		cfid->cfids->num_entries--;
-	}
-	spin_unlock(&cfid->cfids->cfid_list_lock);
-
-	dput(cfid->dentry);
-	cfid->dentry = NULL;
-
-	if (cfid->is_open) {
-		rc = SMB2_close(0, cfid->tcon, cfid->fid.persistent_fid,
-			   cfid->fid.volatile_fid);
-		if (rc) /* should we retry on -EBUSY or -EAGAIN? */
-			cifs_dbg(VFS, "close cached dir rc %d\n", rc);
-	}
+	struct cached_fid *cfid = container_of(ref, struct cached_fid, refcount);
 
+	drop_cfid(cfid);
 	free_cached_dir(cfid);
 }
 
@@ -530,6 +541,9 @@ void close_all_cached_dirs(struct cifs_sb_info *cifs_sb)
 			list_add_tail(&tmp_list->entry, &entry);
 		}
 		spin_unlock(&cfids->cfid_list_lock);
+
+		/* run laundromat now as it might not have been queued */
+		mod_delayed_work(cfid_put_wq, &cfids->laundromat_work, 0);
 	}
 	spin_unlock(&cifs_sb->tlink_tree_lock);
 
@@ -583,30 +597,15 @@ void invalidate_all_cached_dirs(struct cifs_tcon *tcon)
 	flush_delayed_work(&cfids->laundromat_work);
 }
 
-static void
-cached_dir_offload_close(struct work_struct *work)
-{
-	struct cached_fid *cfid = container_of(work,
-				struct cached_fid, close_work);
-	struct cifs_tcon *tcon = cfid->tcon;
-
-	WARN_ON(cfid->on_list);
-
-	kref_put(&cfid->refcount, smb2_close_cached_fid);
-	cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cached_close);
-}
-
 /*
- * Release the cached directory's dentry, and then queue work to drop cached
- * directory itself (closing on server if needed).
- *
- * Must be called with a reference to the cached_fid and a reference to the
- * tcon.
+ * Release the cached directory's dentry and schedule immediate cleanup on laundromat.
+ * Must be called with a reference to the cached_fid and a reference to the tcon.
  */
 static void cached_dir_put_work(struct work_struct *work)
 {
-	struct cached_fid *cfid = container_of(work, struct cached_fid,
-					       put_work);
+	struct cached_fid *cfid = container_of(work, struct cached_fid, put_work);
+	struct cached_fids *cfids = cfid->cfids;
+	struct cifs_tcon *tcon = cfid->tcon;
 	struct dentry *dentry;
 
 	spin_lock(&cfid->fid_lock);
@@ -615,7 +614,16 @@ static void cached_dir_put_work(struct work_struct *work)
 	spin_unlock(&cfid->fid_lock);
 
 	dput(dentry);
-	queue_work(serverclose_wq, &cfid->close_work);
+
+	/* move to dying list so laundromat can clean it up */
+	spin_lock(&cfids->cfid_list_lock);
+	list_move(&cfid->entry, &cfids->dying);
+	cfid->on_list = false;
+	cfids->num_entries--;
+	spin_unlock(&cfids->cfid_list_lock);
+
+	cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cached_close);
+	mod_delayed_work(cfid_put_wq, &cfids->laundromat_work, 0);
 }
 
 bool cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16])
@@ -634,13 +642,6 @@ bool cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16])
 			    SMB2_LEASE_KEY_SIZE)) {
 			cfid->has_lease = false;
 			cfid->time = 0;
-			/*
-			 * We found a lease remove it from the list
-			 * so no threads can access it.
-			 */
-			list_del(&cfid->entry);
-			cfid->on_list = false;
-			cfids->num_entries--;
 
 			++tcon->tc_count;
 			trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
@@ -667,7 +668,6 @@ static struct cached_fid *init_cached_dir(const char *path)
 		return NULL;
 	}
 
-	INIT_WORK(&cfid->close_work, cached_dir_offload_close);
 	INIT_WORK(&cfid->put_work, cached_dir_put_work);
 	INIT_LIST_HEAD(&cfid->entry);
 	INIT_LIST_HEAD(&cfid->dirents.entries);
@@ -681,12 +681,8 @@ static void free_cached_dir(struct cached_fid *cfid)
 {
 	struct cached_dirent *dirent, *q;
 
-	WARN_ON(work_pending(&cfid->close_work));
 	WARN_ON(work_pending(&cfid->put_work));
 
-	dput(cfid->dentry);
-	cfid->dentry = NULL;
-
 	/*
 	 * Delete all cached dirent names
 	 */
@@ -705,7 +701,6 @@ static void cfids_laundromat_worker(struct work_struct *work)
 {
 	struct cached_fids *cfids;
 	struct cached_fid *cfid, *q;
-	struct dentry *dentry;
 	LIST_HEAD(entry);
 
 	cfids = container_of(work, struct cached_fids, laundromat_work.work);
@@ -735,28 +730,22 @@ static void cfids_laundromat_worker(struct work_struct *work)
 	list_for_each_entry_safe(cfid, q, &entry, entry) {
 		list_del(&cfid->entry);
 
-		spin_lock(&cfid->fid_lock);
-		dentry = cfid->dentry;
-		cfid->dentry = NULL;
-		spin_unlock(&cfid->fid_lock);
-
-		dput(dentry);
-		if (cfid->is_open) {
-			spin_lock(&cifs_tcp_ses_lock);
-			++cfid->tcon->tc_count;
-			trace_smb3_tcon_ref(cfid->tcon->debug_id, cfid->tcon->tc_count,
-					    netfs_trace_tcon_ref_get_cached_laundromat);
-			spin_unlock(&cifs_tcp_ses_lock);
-			queue_work(serverclose_wq, &cfid->close_work);
-		} else
-			/*
-			 * Drop the ref-count from above, either the lease-ref (if there
-			 * was one) or the extra one acquired.
-			 */
-			kref_put(&cfid->refcount, smb2_close_cached_fid);
+		/*
+		 * If a cfid reached here, we must cleanup anything unrelated to it, i.e. dentry and
+		 * remote fid.
+		 *
+		 * For the cfid itself, we only drop our own ref (kref_init).  If there are still
+		 * concurrent ref-holders, they'll drop it later (cfid is already invalid at this
+		 * point, so can't be found anymore).
+		 *
+		 * No risk for a double list_del() here because cfid->on_list is always false at
+		 * this point.
+		 */
+		drop_cfid(cfid);
+		kref_put(&cfid->refcount, smb2_close_cached_fid);
 	}
-	queue_delayed_work(cfid_put_wq, &cfids->laundromat_work,
-			   dir_cache_timeout * HZ);
+
+	queue_delayed_work(cfid_put_wq, &cfids->laundromat_work, dir_cache_timeout * HZ);
 }
 
 struct cached_fids *init_cached_dirs(void)
@@ -806,6 +795,7 @@ void free_cached_dirs(struct cached_fids *cfids)
 
 	list_for_each_entry_safe(cfid, q, &entry, entry) {
 		list_del(&cfid->entry);
+		drop_cfid(cfid);
 		free_cached_dir(cfid);
 	}
 
diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h
index a3757a736d3e..e5445e3a7bd3 100644
--- a/fs/smb/client/cached_dir.h
+++ b/fs/smb/client/cached_dir.h
@@ -45,7 +45,6 @@ struct cached_fid {
 	struct cifs_tcon *tcon;
 	struct dentry *dentry;
 	struct work_struct put_work;
-	struct work_struct close_work;
 	struct smb2_file_all_info file_all_info;
 	struct cached_dirents dirents;
 };
-- 
2.49.0


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

* [PATCH 03/20] smb: client: remove cached_dir_put_work/put_work
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
  2025-09-29 13:27 ` [PATCH 01/20] smb: client: remove cfids_invalidation_worker Enzo Matsumiya
  2025-09-29 13:27 ` [PATCH 02/20] smb: client: remove cached_dir_offload_close/close_work Enzo Matsumiya
@ 2025-09-29 13:27 ` Enzo Matsumiya
  2025-09-29 13:27 ` [PATCH 04/20] smb: client: remove cached_fids->dying list Enzo Matsumiya
                   ` (17 subsequent siblings)
  20 siblings, 0 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:27 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

Move cfid to dying list directly on cached_dir_lease_break(), and
schedule laundromat for cleanup there too.

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cached_dir.c | 56 ++++++++++++--------------------------
 fs/smb/client/cached_dir.h |  1 -
 2 files changed, 17 insertions(+), 40 deletions(-)

diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index 8689ee4a883d..ab37a025ea1c 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -597,39 +597,11 @@ void invalidate_all_cached_dirs(struct cifs_tcon *tcon)
 	flush_delayed_work(&cfids->laundromat_work);
 }
 
-/*
- * Release the cached directory's dentry and schedule immediate cleanup on laundromat.
- * Must be called with a reference to the cached_fid and a reference to the tcon.
- */
-static void cached_dir_put_work(struct work_struct *work)
-{
-	struct cached_fid *cfid = container_of(work, struct cached_fid, put_work);
-	struct cached_fids *cfids = cfid->cfids;
-	struct cifs_tcon *tcon = cfid->tcon;
-	struct dentry *dentry;
-
-	spin_lock(&cfid->fid_lock);
-	dentry = cfid->dentry;
-	cfid->dentry = NULL;
-	spin_unlock(&cfid->fid_lock);
-
-	dput(dentry);
-
-	/* move to dying list so laundromat can clean it up */
-	spin_lock(&cfids->cfid_list_lock);
-	list_move(&cfid->entry, &cfids->dying);
-	cfid->on_list = false;
-	cfids->num_entries--;
-	spin_unlock(&cfids->cfid_list_lock);
-
-	cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cached_close);
-	mod_delayed_work(cfid_put_wq, &cfids->laundromat_work, 0);
-}
-
 bool cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16])
 {
 	struct cached_fids *cfids = tcon->cfids;
 	struct cached_fid *cfid;
+	bool found = false;
 
 	if (cfids == NULL)
 		return false;
@@ -643,16 +615,25 @@ bool cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16])
 			cfid->has_lease = false;
 			cfid->time = 0;
 
-			++tcon->tc_count;
-			trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
-					    netfs_trace_tcon_ref_get_cached_lease_break);
-			queue_work(cfid_put_wq, &cfid->put_work);
-			spin_unlock(&cfids->cfid_list_lock);
-			return true;
+			/*
+			 * We found a lease, move it to the dying list and schedule immediate
+			 * cleanup on laundromat.
+			 * No need to take a ref here, as we still hold our initial one.
+			 */
+			list_move(&cfid->entry, &cfids->dying);
+			cfids->num_entries--;
+			cfid->on_list = false;
+			found = true;
+			break;
 		}
 	}
 	spin_unlock(&cfids->cfid_list_lock);
-	return false;
+
+	/* avoid unnecessary scheduling */
+	if (found)
+		mod_delayed_work(cfid_put_wq, &cfids->laundromat_work, 0);
+
+	return found;
 }
 
 static struct cached_fid *init_cached_dir(const char *path)
@@ -668,7 +649,6 @@ static struct cached_fid *init_cached_dir(const char *path)
 		return NULL;
 	}
 
-	INIT_WORK(&cfid->put_work, cached_dir_put_work);
 	INIT_LIST_HEAD(&cfid->entry);
 	INIT_LIST_HEAD(&cfid->dirents.entries);
 	mutex_init(&cfid->dirents.de_mutex);
@@ -681,8 +661,6 @@ static void free_cached_dir(struct cached_fid *cfid)
 {
 	struct cached_dirent *dirent, *q;
 
-	WARN_ON(work_pending(&cfid->put_work));
-
 	/*
 	 * Delete all cached dirent names
 	 */
diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h
index e5445e3a7bd3..5e892d53a67a 100644
--- a/fs/smb/client/cached_dir.h
+++ b/fs/smb/client/cached_dir.h
@@ -44,7 +44,6 @@ struct cached_fid {
 	spinlock_t fid_lock;
 	struct cifs_tcon *tcon;
 	struct dentry *dentry;
-	struct work_struct put_work;
 	struct smb2_file_all_info file_all_info;
 	struct cached_dirents dirents;
 };
-- 
2.49.0


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

* [PATCH 04/20] smb: client: remove cached_fids->dying list
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
                   ` (2 preceding siblings ...)
  2025-09-29 13:27 ` [PATCH 03/20] smb: client: remove cached_dir_put_work/put_work Enzo Matsumiya
@ 2025-09-29 13:27 ` Enzo Matsumiya
  2025-09-29 13:27 ` [PATCH 05/20] smb: client: remove cached_fid->on_list Enzo Matsumiya
                   ` (16 subsequent siblings)
  20 siblings, 0 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:27 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

Since any cleanup is now done on the local list in laundromat, the
dying list can be removed.

- entries stays on the main list until they're scheduled for cleanup
  (->last_access_time == 1)
- cached_fids->num_entries is decremented only when cfid transitions
  from on_list true -> false

cached_fid lifecycle on the list becomes:

- list_add() on find_or_create_cached_dir()
- list_move() to local list on laundromat
- list_del() on release callback, if on_list == true (unlikely, see
  comment in the function)

Other changes:
- add invalidate_cfid() helper

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cached_dir.c | 104 +++++++++++++++++--------------------
 fs/smb/client/cached_dir.h |   3 +-
 2 files changed, 48 insertions(+), 59 deletions(-)

diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index ab37a025ea1c..2fea7f6c5678 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -22,16 +22,26 @@ struct cached_dir_dentry {
 	struct dentry *dentry;
 };
 
+static inline void invalidate_cfid(struct cached_fid *cfid)
+{
+	/* callers must hold the list lock and do any list operations (del/move) themselves */
+	lockdep_assert_held(&cfid->cfids->cfid_list_lock);
+
+	if (cfid->on_list)
+		cfid->cfids->num_entries--;
+
+	/* do not change other fields here! */
+	cfid->on_list = false;
+	cfid->time = 0;
+	cfid->last_access_time = 1;
+}
+
 static inline void drop_cfid(struct cached_fid *cfid)
 {
 	struct dentry *dentry = NULL;
 
 	spin_lock(&cfid->cfids->cfid_list_lock);
-	if (cfid->on_list) {
-		list_del(&cfid->entry);
-		cfid->on_list = false;
-		cfid->cfids->num_entries--;
-	}
+	invalidate_cfid(cfid);
 
 	spin_lock(&cfid->fid_lock);
 	swap(cfid->dentry, dentry);
@@ -466,6 +476,25 @@ smb2_close_cached_fid(struct kref *ref)
 {
 	struct cached_fid *cfid = container_of(ref, struct cached_fid, refcount);
 
+	/*
+	 * There's no way a cfid can reach here with ->on_list == true.
+	 *
+	 * This is because we hould our own ref, and whenever we put it, we invalidate the cfid
+	 * (which sets ->on_list to false).
+	 *
+	 * So even if an external caller puts the last ref, ->on_list will already have been set to
+	 * false by then by one of the invalidations that can happen concurrently, e.g. lease break,
+	 * invalidate_all_cached_dirs().
+	 *
+	 * So this check is mostly for precaution, but since we can still take the correct actions
+	 * if it's the case, do so.
+	 */
+	if (WARN_ON(cfid->on_list)) {
+		list_del(&cfid->entry);
+		cfid->on_list = false;
+		cfid->cfids->num_entries--;
+	}
+
 	drop_cfid(cfid);
 	free_cached_dir(cfid);
 }
@@ -481,15 +510,10 @@ void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon,
 		cifs_dbg(FYI, "no cached dir found for rmdir(%s)\n", name);
 		return;
 	}
-	spin_lock(&cfid->cfids->cfid_list_lock);
-	if (cfid->has_lease) {
-		cfid->has_lease = false;
-		kref_put(&cfid->refcount, smb2_close_cached_fid);
-	}
-	spin_unlock(&cfid->cfids->cfid_list_lock);
-	close_cached_dir(cfid);
-}
 
+	drop_cfid(cfid);
+	kref_put(&cfid->refcount, smb2_close_cached_fid);
+}
 
 void close_cached_dir(struct cached_fid *cfid)
 {
@@ -521,6 +545,8 @@ void close_all_cached_dirs(struct cifs_sb_info *cifs_sb)
 			continue;
 		spin_lock(&cfids->cfid_list_lock);
 		list_for_each_entry(cfid, &cfids->entries, entry) {
+			invalidate_cfid(cfid);
+
 			tmp_list = kmalloc(sizeof(*tmp_list), GFP_ATOMIC);
 			if (tmp_list == NULL) {
 				/*
@@ -570,25 +596,11 @@ void invalidate_all_cached_dirs(struct cifs_tcon *tcon)
 	if (!cfids)
 		return;
 
-	/*
-	 * Mark all the cfids as closed, and move them to the cfids->dying list.
-	 * They'll be cleaned up by laundromat.  Take a reference to each cfid
-	 * during this process.
-	 */
+	/* mark all the cfids as closed and invalidate them for laundromat cleanup */
 	spin_lock(&cfids->cfid_list_lock);
 	list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {
-		list_move(&cfid->entry, &cfids->dying);
-		cfids->num_entries--;
+		invalidate_cfid(cfid);
 		cfid->is_open = false;
-		cfid->on_list = false;
-		if (cfid->has_lease) {
-			/*
-			 * The lease was never cancelled from the server,
-			 * so steal that reference.
-			 */
-			cfid->has_lease = false;
-		} else
-			kref_get(&cfid->refcount);
 	}
 	spin_unlock(&cfids->cfid_list_lock);
 
@@ -612,17 +624,13 @@ bool cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16])
 		    !memcmp(lease_key,
 			    cfid->fid.lease_key,
 			    SMB2_LEASE_KEY_SIZE)) {
-			cfid->has_lease = false;
-			cfid->time = 0;
-
 			/*
-			 * We found a lease, move it to the dying list and schedule immediate
-			 * cleanup on laundromat.
+			 * We found a lease, invalidate cfid and schedule immediate cleanup on
+			 * laundromat.
 			 * No need to take a ref here, as we still hold our initial one.
 			 */
-			list_move(&cfid->entry, &cfids->dying);
-			cfids->num_entries--;
-			cfid->on_list = false;
+			invalidate_cfid(cfid);
+			cfid->has_lease = false;
 			found = true;
 			break;
 		}
@@ -684,23 +692,11 @@ static void cfids_laundromat_worker(struct work_struct *work)
 	cfids = container_of(work, struct cached_fids, laundromat_work.work);
 
 	spin_lock(&cfids->cfid_list_lock);
-	/* move cfids->dying to the local list */
-	list_cut_before(&entry, &cfids->dying, &cfids->dying);
-
 	list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {
 		if (cfid->last_access_time &&
 		    time_after(jiffies, cfid->last_access_time + HZ * dir_cache_timeout)) {
-			cfid->on_list = false;
+			invalidate_cfid(cfid);
 			list_move(&cfid->entry, &entry);
-			cfids->num_entries--;
-			if (cfid->has_lease) {
-				/*
-				 * Our lease has not yet been cancelled from the
-				 * server. Steal that reference.
-				 */
-				cfid->has_lease = false;
-			} else
-				kref_get(&cfid->refcount);
 		}
 	}
 	spin_unlock(&cfids->cfid_list_lock);
@@ -735,7 +731,6 @@ struct cached_fids *init_cached_dirs(void)
 		return NULL;
 	spin_lock_init(&cfids->cfid_list_lock);
 	INIT_LIST_HEAD(&cfids->entries);
-	INIT_LIST_HEAD(&cfids->dying);
 
 	INIT_DELAYED_WORK(&cfids->laundromat_work, cfids_laundromat_worker);
 	queue_delayed_work(cfid_put_wq, &cfids->laundromat_work,
@@ -760,12 +755,7 @@ void free_cached_dirs(struct cached_fids *cfids)
 
 	spin_lock(&cfids->cfid_list_lock);
 	list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {
-		cfid->on_list = false;
-		cfid->is_open = false;
-		list_move(&cfid->entry, &entry);
-	}
-	list_for_each_entry_safe(cfid, q, &cfids->dying, entry) {
-		cfid->on_list = false;
+		invalidate_cfid(cfid);
 		cfid->is_open = false;
 		list_move(&cfid->entry, &entry);
 	}
diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h
index 5e892d53a67a..e549f019b923 100644
--- a/fs/smb/client/cached_dir.h
+++ b/fs/smb/client/cached_dir.h
@@ -52,12 +52,11 @@ struct cached_fid {
 struct cached_fids {
 	/* Must be held when:
 	 * - accessing the cfids->entries list
-	 * - accessing the cfids->dying list
+	 * - accessing cfids->num_entries
 	 */
 	spinlock_t cfid_list_lock;
 	int num_entries;
 	struct list_head entries;
-	struct list_head dying;
 	struct delayed_work laundromat_work;
 };
 
-- 
2.49.0


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

* [PATCH 05/20] smb: client: remove cached_fid->on_list
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
                   ` (3 preceding siblings ...)
  2025-09-29 13:27 ` [PATCH 04/20] smb: client: remove cached_fids->dying list Enzo Matsumiya
@ 2025-09-29 13:27 ` Enzo Matsumiya
  2025-09-29 13:27 ` [PATCH 06/20] smb: client: merge {close,invalidate}_all_cached_dirs() Enzo Matsumiya
                   ` (15 subsequent siblings)
  20 siblings, 0 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:27 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

That field is currently used to indicate when a cfid should be removed
from the entries list.

Since we now keep cfids on the list until they're going down, we can
remove the field and use the other existing fields for the same effect.

Other changes:
- cfids->num_entries follows semantics of list_*() ops
- add cfid_expired() and cfid_is_valid() helpers
- check cfid_is_valid() even on success when leaving open_cached_dir()

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cached_dir.c | 77 ++++++++++++++------------------------
 fs/smb/client/cached_dir.h | 12 +++++-
 fs/smb/client/dir.c        | 15 ++++----
 3 files changed, 47 insertions(+), 57 deletions(-)

diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index 2fea7f6c5678..9b4045a57f12 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -15,7 +15,6 @@
 static struct cached_fid *init_cached_dir(const char *path);
 static void free_cached_dir(struct cached_fid *cfid);
 static void smb2_close_cached_fid(struct kref *ref);
-static void cfids_laundromat_worker(struct work_struct *work);
 
 struct cached_dir_dentry {
 	struct list_head entry;
@@ -27,11 +26,10 @@ static inline void invalidate_cfid(struct cached_fid *cfid)
 	/* callers must hold the list lock and do any list operations (del/move) themselves */
 	lockdep_assert_held(&cfid->cfids->cfid_list_lock);
 
-	if (cfid->on_list)
+	if (cfid_is_valid(cfid))
 		cfid->cfids->num_entries--;
 
 	/* do not change other fields here! */
-	cfid->on_list = false;
 	cfid->time = 0;
 	cfid->last_access_time = 1;
 }
@@ -76,9 +74,9 @@ static struct cached_fid *find_or_create_cached_dir(struct cached_fids *cfids,
 			 * fully cached or it may be in the process of
 			 * being deleted due to a lease break.
 			 */
-			if (!cfid->time || !cfid->has_lease) {
+			if (!cfid_is_valid(cfid))
 				return NULL;
-			}
+
 			kref_get(&cfid->refcount);
 			return cfid;
 		}
@@ -96,7 +94,6 @@ static struct cached_fid *find_or_create_cached_dir(struct cached_fids *cfids,
 	cfid->cfids = cfids;
 	cfids->num_entries++;
 	list_add(&cfid->entry, &cfids->entries);
-	cfid->on_list = true;
 	kref_get(&cfid->refcount);
 	/*
 	 * Set @cfid->has_lease to true during construction so that the lease
@@ -263,6 +260,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
 	} else {
 		dentry = path_to_dentry(cifs_sb, npath);
 		if (IS_ERR(dentry)) {
+			dentry = NULL;
 			rc = -ENOENT;
 			goto out;
 		}
@@ -272,14 +270,13 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
 			spin_lock(&cfids->cfid_list_lock);
 			list_for_each_entry(parent_cfid, &cfids->entries, entry) {
 				if (parent_cfid->dentry == dentry->d_parent) {
+					if (!cfid_is_valid(parent_cfid))
+						break;
+
 					cifs_dbg(FYI, "found a parent cached file handle\n");
-					if (parent_cfid->has_lease && parent_cfid->time) {
-						lease_flags
-							|= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
-						memcpy(pfid->parent_lease_key,
-						       parent_cfid->fid.lease_key,
-						       SMB2_LEASE_KEY_SIZE);
-					}
+					lease_flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
+					memcpy(pfid->parent_lease_key, parent_cfid->fid.lease_key,
+					       SMB2_LEASE_KEY_SIZE);
 					break;
 				}
 			}
@@ -288,6 +285,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
 	}
 	cfid->dentry = dentry;
 	cfid->tcon = tcon;
+	dentry = NULL;
 
 	/*
 	 * We do not hold the lock for the open because in case
@@ -415,24 +413,12 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
 	free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base);
 	free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base);
 out:
-	if (rc) {
-		spin_lock(&cfids->cfid_list_lock);
-		if (cfid->on_list) {
-			list_del(&cfid->entry);
-			cfid->on_list = false;
-			cfids->num_entries--;
-		}
-		if (cfid->has_lease) {
-			/*
-			 * We are guaranteed to have two references at this
-			 * point. One for the caller and one for a potential
-			 * lease. Release one here, and the second below.
-			 */
-			cfid->has_lease = false;
-			kref_put(&cfid->refcount, smb2_close_cached_fid);
-		}
-		spin_unlock(&cfids->cfid_list_lock);
+	/* cfid invalidated in the mean time, drop it below */
+	if (!rc && !cfid_is_valid(cfid))
+		rc = -ENOENT;
 
+	if (rc) {
+		drop_cfid(cfid);
 		kref_put(&cfid->refcount, smb2_close_cached_fid);
 	} else {
 		*ret_cfid = cfid;
@@ -459,7 +445,7 @@ int open_cached_dir_by_dentry(struct cifs_tcon *tcon,
 
 	spin_lock(&cfids->cfid_list_lock);
 	list_for_each_entry(cfid, &cfids->entries, entry) {
-		if (dentry && cfid->dentry == dentry) {
+		if (cfid_is_valid(cfid) && cfid->dentry == dentry) {
 			cifs_dbg(FYI, "found a cached file handle by dentry\n");
 			kref_get(&cfid->refcount);
 			*ret_cfid = cfid;
@@ -477,23 +463,20 @@ smb2_close_cached_fid(struct kref *ref)
 	struct cached_fid *cfid = container_of(ref, struct cached_fid, refcount);
 
 	/*
-	 * There's no way a cfid can reach here with ->on_list == true.
+	 * There's no way a valid cfid can reach here.
 	 *
-	 * This is because we hould our own ref, and whenever we put it, we invalidate the cfid
-	 * (which sets ->on_list to false).
+	 * This is because we hould our own ref, and whenever we put it, we invalidate the cfid.
 	 *
-	 * So even if an external caller puts the last ref, ->on_list will already have been set to
-	 * false by then by one of the invalidations that can happen concurrently, e.g. lease break,
+	 * So even if an external caller puts the last ref, cfid will already have been invalidated
+	 * by then by one of the invalidations that can happen concurrently, e.g. lease break,
 	 * invalidate_all_cached_dirs().
 	 *
-	 * So this check is mostly for precaution, but since we can still take the correct actions
-	 * if it's the case, do so.
+	 * So this check is mostly for precaution, but since we can still take the correct action
+	 * (just list_del()) if it's the case, do so.
 	 */
-	if (WARN_ON(cfid->on_list)) {
+	if (WARN_ON(cfid_is_valid(cfid)))
+		/* remaining invalidation done by drop_cfid() below */
 		list_del(&cfid->entry);
-		cfid->on_list = false;
-		cfid->cfids->num_entries--;
-	}
 
 	drop_cfid(cfid);
 	free_cached_dir(cfid);
@@ -591,14 +574,14 @@ void close_all_cached_dirs(struct cifs_sb_info *cifs_sb)
 void invalidate_all_cached_dirs(struct cifs_tcon *tcon)
 {
 	struct cached_fids *cfids = tcon->cfids;
-	struct cached_fid *cfid, *q;
+	struct cached_fid *cfid;
 
 	if (!cfids)
 		return;
 
 	/* mark all the cfids as closed and invalidate them for laundromat cleanup */
 	spin_lock(&cfids->cfid_list_lock);
-	list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {
+	list_for_each_entry(cfid, &cfids->entries, entry) {
 		invalidate_cfid(cfid);
 		cfid->is_open = false;
 	}
@@ -693,8 +676,7 @@ static void cfids_laundromat_worker(struct work_struct *work)
 
 	spin_lock(&cfids->cfid_list_lock);
 	list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {
-		if (cfid->last_access_time &&
-		    time_after(jiffies, cfid->last_access_time + HZ * dir_cache_timeout)) {
+		if (cfid_expired(cfid)) {
 			invalidate_cfid(cfid);
 			list_move(&cfid->entry, &entry);
 		}
@@ -712,8 +694,7 @@ static void cfids_laundromat_worker(struct work_struct *work)
 		 * concurrent ref-holders, they'll drop it later (cfid is already invalid at this
 		 * point, so can't be found anymore).
 		 *
-		 * No risk for a double list_del() here because cfid->on_list is always false at
-		 * this point.
+		 * No risk for a double list_del() here because cfid is only on this list now.
 		 */
 		drop_cfid(cfid);
 		kref_put(&cfid->refcount, smb2_close_cached_fid);
diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h
index e549f019b923..92e95c56fd1c 100644
--- a/fs/smb/client/cached_dir.h
+++ b/fs/smb/client/cached_dir.h
@@ -35,7 +35,6 @@ struct cached_fid {
 	const char *path;
 	bool has_lease:1;
 	bool is_open:1;
-	bool on_list:1;
 	bool file_all_info_is_valid:1;
 	unsigned long time; /* jiffies of when lease was taken */
 	unsigned long last_access_time; /* jiffies of when last accessed */
@@ -60,6 +59,17 @@ struct cached_fids {
 	struct delayed_work laundromat_work;
 };
 
+static inline bool cfid_expired(const struct cached_fid *cfid)
+{
+	return (cfid->last_access_time &&
+		time_is_before_jiffies(cfid->last_access_time + HZ * dir_cache_timeout));
+}
+
+static inline bool cfid_is_valid(const struct cached_fid *cfid)
+{
+	return (cfid->has_lease && cfid->time && !cfid_expired(cfid));
+}
+
 extern struct cached_fids *init_cached_dirs(void);
 extern void free_cached_dirs(struct cached_fids *cfids);
 extern int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c
index 5223edf6d11a..e5372c2c799d 100644
--- a/fs/smb/client/dir.c
+++ b/fs/smb/client/dir.c
@@ -321,15 +321,14 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
 		spin_lock(&tcon->cfids->cfid_list_lock);
 		list_for_each_entry(parent_cfid, &tcon->cfids->entries, entry) {
 			if (parent_cfid->dentry == direntry->d_parent) {
+				if (!cfid_is_valid(parent_cfid))
+					break;
+
 				cifs_dbg(FYI, "found a parent cached file handle\n");
-				if (parent_cfid->has_lease && parent_cfid->time) {
-					lease_flags
-						|= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
-					memcpy(fid->parent_lease_key,
-					       parent_cfid->fid.lease_key,
-					       SMB2_LEASE_KEY_SIZE);
-					parent_cfid->dirents.is_valid = false;
-				}
+				lease_flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
+				memcpy(fid->parent_lease_key, parent_cfid->fid.lease_key,
+				       SMB2_LEASE_KEY_SIZE);
+				parent_cfid->dirents.is_valid = false;
 				break;
 			}
 		}
-- 
2.49.0


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

* [PATCH 06/20] smb: client: merge {close,invalidate}_all_cached_dirs()
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
                   ` (4 preceding siblings ...)
  2025-09-29 13:27 ` [PATCH 05/20] smb: client: remove cached_fid->on_list Enzo Matsumiya
@ 2025-09-29 13:27 ` Enzo Matsumiya
  2025-09-29 13:27 ` [PATCH 07/20] smb: client: merge free_cached_dir in release callback Enzo Matsumiya
                   ` (14 subsequent siblings)
  20 siblings, 0 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:27 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

close_all_cached_dirs(), invalidate_all_cached_dirs() and
free_cached_dirs() have become too similar now, merge their
functionality in a single static invalidate_all_cfids() function.

This also allows removing free_cached_dirs() altogether as it only
requires cancelling the work afterwards (done directly in
tconInfoFree()).

Other changes:
- remove struct cached_dir_dentry

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cached_dir.c | 114 ++++++++-----------------------------
 fs/smb/client/cached_dir.h |   4 +-
 fs/smb/client/file.c       |   2 +-
 fs/smb/client/misc.c       |   9 ++-
 fs/smb/client/smb2pdu.c    |   2 +-
 5 files changed, 35 insertions(+), 96 deletions(-)

diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index 9b4045a57f12..36a1e1436502 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -16,11 +16,6 @@ static struct cached_fid *init_cached_dir(const char *path);
 static void free_cached_dir(struct cached_fid *cfid);
 static void smb2_close_cached_fid(struct kref *ref);
 
-struct cached_dir_dentry {
-	struct list_head entry;
-	struct dentry *dentry;
-};
-
 static inline void invalidate_cfid(struct cached_fid *cfid)
 {
 	/* callers must hold the list lock and do any list operations (del/move) themselves */
@@ -503,6 +498,27 @@ void close_cached_dir(struct cached_fid *cfid)
 	kref_put(&cfid->refcount, smb2_close_cached_fid);
 }
 
+static void invalidate_all_cfids(struct cached_fids *cfids, bool closed)
+{
+	struct cached_fid *cfid, *q;
+
+	if (!cfids)
+		return;
+
+	/* mark all the cfids as closed and invalidate them for laundromat cleanup */
+	spin_lock(&cfids->cfid_list_lock);
+	list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {
+		invalidate_cfid(cfid);
+		cfid->has_lease = false;
+		if (closed)
+			cfid->is_open = false;
+	}
+	spin_unlock(&cfids->cfid_list_lock);
+
+	/* run laundromat unconditionally now as there might have been previously queued work */
+	mod_delayed_work(cfid_put_wq, &cfids->laundromat_work, 0);
+}
+
 /*
  * Called from cifs_kill_sb when we unmount a share
  */
@@ -510,12 +526,8 @@ void close_all_cached_dirs(struct cifs_sb_info *cifs_sb)
 {
 	struct rb_root *root = &cifs_sb->tlink_tree;
 	struct rb_node *node;
-	struct cached_fid *cfid;
 	struct cifs_tcon *tcon;
 	struct tcon_link *tlink;
-	struct cached_fids *cfids;
-	struct cached_dir_dentry *tmp_list, *q;
-	LIST_HEAD(entry);
 
 	spin_lock(&cifs_sb->tlink_tree_lock);
 	for (node = rb_first(root); node; node = rb_next(node)) {
@@ -523,46 +535,11 @@ void close_all_cached_dirs(struct cifs_sb_info *cifs_sb)
 		tcon = tlink_tcon(tlink);
 		if (IS_ERR(tcon))
 			continue;
-		cfids = tcon->cfids;
-		if (cfids == NULL)
-			continue;
-		spin_lock(&cfids->cfid_list_lock);
-		list_for_each_entry(cfid, &cfids->entries, entry) {
-			invalidate_cfid(cfid);
-
-			tmp_list = kmalloc(sizeof(*tmp_list), GFP_ATOMIC);
-			if (tmp_list == NULL) {
-				/*
-				 * If the malloc() fails, we won't drop all
-				 * dentries, and unmounting is likely to trigger
-				 * a 'Dentry still in use' error.
-				 */
-				cifs_tcon_dbg(VFS, "Out of memory while dropping dentries\n");
-				spin_unlock(&cfids->cfid_list_lock);
-				spin_unlock(&cifs_sb->tlink_tree_lock);
-				goto done;
-			}
-			spin_lock(&cfid->fid_lock);
-			tmp_list->dentry = cfid->dentry;
-			cfid->dentry = NULL;
-			spin_unlock(&cfid->fid_lock);
 
-			list_add_tail(&tmp_list->entry, &entry);
-		}
-		spin_unlock(&cfids->cfid_list_lock);
-
-		/* run laundromat now as it might not have been queued */
-		mod_delayed_work(cfid_put_wq, &cfids->laundromat_work, 0);
+		invalidate_all_cfids(tcon->cfids, false);
 	}
 	spin_unlock(&cifs_sb->tlink_tree_lock);
 
-done:
-	list_for_each_entry_safe(tmp_list, q, &entry, entry) {
-		list_del(&tmp_list->entry);
-		dput(tmp_list->dentry);
-		kfree(tmp_list);
-	}
-
 	/* Flush any pending work that will drop dentries */
 	flush_workqueue(cfid_put_wq);
 }
@@ -571,24 +548,12 @@ void close_all_cached_dirs(struct cifs_sb_info *cifs_sb)
  * Invalidate all cached dirs when a TCON has been reset
  * due to a session loss.
  */
-void invalidate_all_cached_dirs(struct cifs_tcon *tcon)
+void invalidate_all_cached_dirs(struct cached_fids *cfids)
 {
-	struct cached_fids *cfids = tcon->cfids;
-	struct cached_fid *cfid;
-
 	if (!cfids)
 		return;
 
-	/* mark all the cfids as closed and invalidate them for laundromat cleanup */
-	spin_lock(&cfids->cfid_list_lock);
-	list_for_each_entry(cfid, &cfids->entries, entry) {
-		invalidate_cfid(cfid);
-		cfid->is_open = false;
-	}
-	spin_unlock(&cfids->cfid_list_lock);
-
-	/* run laundromat unconditionally now as there might have been previously queued work */
-	mod_delayed_work(cfid_put_wq, &cfids->laundromat_work, 0);
+	invalidate_all_cfids(cfids, true);
 	flush_delayed_work(&cfids->laundromat_work);
 }
 
@@ -719,34 +684,3 @@ struct cached_fids *init_cached_dirs(void)
 
 	return cfids;
 }
-
-/*
- * Called from tconInfoFree when we are tearing down the tcon.
- * There are no active users or open files/directories at this point.
- */
-void free_cached_dirs(struct cached_fids *cfids)
-{
-	struct cached_fid *cfid, *q;
-	LIST_HEAD(entry);
-
-	if (cfids == NULL)
-		return;
-
-	cancel_delayed_work_sync(&cfids->laundromat_work);
-
-	spin_lock(&cfids->cfid_list_lock);
-	list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {
-		invalidate_cfid(cfid);
-		cfid->is_open = false;
-		list_move(&cfid->entry, &entry);
-	}
-	spin_unlock(&cfids->cfid_list_lock);
-
-	list_for_each_entry_safe(cfid, q, &entry, entry) {
-		list_del(&cfid->entry);
-		drop_cfid(cfid);
-		free_cached_dir(cfid);
-	}
-
-	kfree(cfids);
-}
diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h
index 92e95c56fd1c..47c0404ba84a 100644
--- a/fs/smb/client/cached_dir.h
+++ b/fs/smb/client/cached_dir.h
@@ -71,7 +71,6 @@ static inline bool cfid_is_valid(const struct cached_fid *cfid)
 }
 
 extern struct cached_fids *init_cached_dirs(void);
-extern void free_cached_dirs(struct cached_fids *cfids);
 extern int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
 			   const char *path,
 			   struct cifs_sb_info *cifs_sb,
@@ -85,7 +84,6 @@ extern void drop_cached_dir_by_name(const unsigned int xid,
 				    const char *name,
 				    struct cifs_sb_info *cifs_sb);
 extern void close_all_cached_dirs(struct cifs_sb_info *cifs_sb);
-extern void invalidate_all_cached_dirs(struct cifs_tcon *tcon);
+extern void invalidate_all_cached_dirs(struct cached_fids *cfids);
 extern bool cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16]);
-
 #endif			/* _CACHED_DIR_H */
diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c
index cb907e18cc35..5c195ffa3ead 100644
--- a/fs/smb/client/file.c
+++ b/fs/smb/client/file.c
@@ -386,7 +386,7 @@ cifs_mark_open_files_invalid(struct cifs_tcon *tcon)
 	}
 	spin_unlock(&tcon->open_file_lock);
 
-	invalidate_all_cached_dirs(tcon);
+	invalidate_all_cached_dirs(tcon->cfids);
 	spin_lock(&tcon->tc_lock);
 	if (tcon->status == TID_IN_FILES_INVALIDATE)
 		tcon->status = TID_NEED_TCON;
diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c
index da23cc12a52c..8d70333a6a3d 100644
--- a/fs/smb/client/misc.c
+++ b/fs/smb/client/misc.c
@@ -169,7 +169,14 @@ tconInfoFree(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace)
 		return;
 	}
 	trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, trace);
-	free_cached_dirs(tcon->cfids);
+
+	if (tcon->cfids) {
+		invalidate_all_cached_dirs(tcon->cfids);
+		cancel_delayed_work_sync(&tcon->cfids->laundromat_work);
+		kfree(tcon->cfids);
+		tcon->cfids = NULL;
+	}
+
 	atomic_dec(&tconInfoAllocCount);
 	kfree(tcon->nativeFileSystem);
 	kfree_sensitive(tcon->password);
diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index c3b9d3f6210f..07ba61583114 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -2197,7 +2197,7 @@ SMB2_tdis(const unsigned int xid, struct cifs_tcon *tcon)
 	}
 	spin_unlock(&ses->chan_lock);
 
-	invalidate_all_cached_dirs(tcon);
+	invalidate_all_cached_dirs(tcon->cfids);
 
 	rc = smb2_plain_req_init(SMB2_TREE_DISCONNECT, tcon, server,
 				 (void **) &req,
-- 
2.49.0


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

* [PATCH 07/20] smb: client: merge free_cached_dir in release callback
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
                   ` (5 preceding siblings ...)
  2025-09-29 13:27 ` [PATCH 06/20] smb: client: merge {close,invalidate}_all_cached_dirs() Enzo Matsumiya
@ 2025-09-29 13:27 ` Enzo Matsumiya
  2025-09-29 13:27 ` [PATCH 08/20] smb: client: split find_or_create_cached_dir() Enzo Matsumiya
                   ` (13 subsequent siblings)
  20 siblings, 0 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:27 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

free_cached_dir() is no longer used anywhere else.

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cached_dir.c | 32 ++++++++++++--------------------
 1 file changed, 12 insertions(+), 20 deletions(-)

diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index 36a1e1436502..8c8ead6e96bd 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -13,7 +13,6 @@
 #include "cached_dir.h"
 
 static struct cached_fid *init_cached_dir(const char *path);
-static void free_cached_dir(struct cached_fid *cfid);
 static void smb2_close_cached_fid(struct kref *ref);
 
 static inline void invalidate_cfid(struct cached_fid *cfid)
@@ -456,6 +455,7 @@ static void
 smb2_close_cached_fid(struct kref *ref)
 {
 	struct cached_fid *cfid = container_of(ref, struct cached_fid, refcount);
+	struct cached_dirent *de, *q;
 
 	/*
 	 * There's no way a valid cfid can reach here.
@@ -474,7 +474,17 @@ smb2_close_cached_fid(struct kref *ref)
 		list_del(&cfid->entry);
 
 	drop_cfid(cfid);
-	free_cached_dir(cfid);
+
+	/* Delete all cached dirent names */
+	list_for_each_entry_safe(de, q, &cfid->dirents.entries, entry) {
+		list_del(&de->entry);
+		kfree(de->name);
+		kfree(de);
+	}
+
+	kfree(cfid->path);
+	cfid->path = NULL;
+	kfree(cfid);
 }
 
 void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon,
@@ -613,24 +623,6 @@ static struct cached_fid *init_cached_dir(const char *path)
 	return cfid;
 }
 
-static void free_cached_dir(struct cached_fid *cfid)
-{
-	struct cached_dirent *dirent, *q;
-
-	/*
-	 * Delete all cached dirent names
-	 */
-	list_for_each_entry_safe(dirent, q, &cfid->dirents.entries, entry) {
-		list_del(&dirent->entry);
-		kfree(dirent->name);
-		kfree(dirent);
-	}
-
-	kfree(cfid->path);
-	cfid->path = NULL;
-	kfree(cfid);
-}
-
 static void cfids_laundromat_worker(struct work_struct *work)
 {
 	struct cached_fids *cfids;
-- 
2.49.0


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

* [PATCH 08/20] smb: client: split find_or_create_cached_dir()
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
                   ` (6 preceding siblings ...)
  2025-09-29 13:27 ` [PATCH 07/20] smb: client: merge free_cached_dir in release callback Enzo Matsumiya
@ 2025-09-29 13:27 ` Enzo Matsumiya
  2025-09-29 13:27 ` [PATCH 09/20] smb: client: enhance cached dir lookups Enzo Matsumiya
                   ` (12 subsequent siblings)
  20 siblings, 0 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:27 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

This patch splits the function into 2 separate ones; it not only makes
the code clearer, but also allows further and easier enhancements to
both.

So move the initialization part into init_cached_dir() and add
find_cached_dir() for lookups.

Other:
- drop_cached_dir_by_name():
  * use find_cached_dir()
  * remove no longer used args

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cached_dir.c | 95 +++++++++++++++++++-------------------
 fs/smb/client/cached_dir.h |  5 +-
 fs/smb/client/inode.c      |  2 +-
 fs/smb/client/smb2inode.c  |  6 +--
 4 files changed, 53 insertions(+), 55 deletions(-)

diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index 8c8ead6e96bd..92898880d20f 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -54,10 +54,7 @@ static inline void drop_cfid(struct cached_fid *cfid)
 	}
 }
 
-static struct cached_fid *find_or_create_cached_dir(struct cached_fids *cfids,
-						    const char *path,
-						    bool lookup_only,
-						    __u32 max_cached_dirs)
+static struct cached_fid *find_cached_dir(struct cached_fids *cfids, const char *path)
 {
 	struct cached_fid *cfid;
 
@@ -71,35 +68,13 @@ static struct cached_fid *find_or_create_cached_dir(struct cached_fids *cfids,
 			if (!cfid_is_valid(cfid))
 				return NULL;
 
+			cfid->last_access_time = jiffies;
 			kref_get(&cfid->refcount);
 			return cfid;
 		}
 	}
-	if (lookup_only) {
-		return NULL;
-	}
-	if (cfids->num_entries >= max_cached_dirs) {
-		return NULL;
-	}
-	cfid = init_cached_dir(path);
-	if (cfid == NULL) {
-		return NULL;
-	}
-	cfid->cfids = cfids;
-	cfids->num_entries++;
-	list_add(&cfid->entry, &cfids->entries);
-	kref_get(&cfid->refcount);
-	/*
-	 * Set @cfid->has_lease to true during construction so that the lease
-	 * reference can be put in cached_dir_lease_break() due to a potential
-	 * lease break right after the request is sent or while @cfid is still
-	 * being cached, or if a reconnection is triggered during construction.
-	 * Concurrent processes won't be to use it yet due to @cfid->time being
-	 * zero.
-	 */
-	cfid->has_lease = true;
 
-	return cfid;
+	return NULL;
 }
 
 static struct dentry *
@@ -197,9 +172,8 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
 	ses = tcon->ses;
 	cfids = tcon->cfids;
 
-	if (cfids == NULL)
+	if (!cfids)
 		return -EOPNOTSUPP;
-
 replay_again:
 	/* reinitialize for possible replay */
 	flags = 0;
@@ -214,24 +188,31 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
 		return -ENOMEM;
 
 	spin_lock(&cfids->cfid_list_lock);
-	cfid = find_or_create_cached_dir(cfids, path, lookup_only, tcon->max_cached_dirs);
-	if (cfid == NULL) {
+	if (cfids->num_entries >= tcon->max_cached_dirs) {
 		spin_unlock(&cfids->cfid_list_lock);
 		kfree(utf16_path);
 		return -ENOENT;
 	}
-	/*
-	 * Return cached fid if it is valid (has a lease and has a time).
-	 * Otherwise, it is either a new entry or laundromat worker removed it
-	 * from @cfids->entries.  Caller will put last reference if the latter.
-	 */
-	if (cfid->has_lease && cfid->time) {
-		cfid->last_access_time = jiffies;
-		spin_unlock(&cfids->cfid_list_lock);
+
+	/* find_cached_dir() already checks if has_lease and time, so no need to check here */
+	cfid = find_cached_dir(cfids, path);
+	if (cfid || lookup_only) {
 		*ret_cfid = cfid;
+		spin_unlock(&cfids->cfid_list_lock);
+		kfree(utf16_path);
+		return cfid ? 0 : -ENOENT;
+	}
+
+	cfid = init_cached_dir(path);
+	if (!cfid) {
+		spin_unlock(&cfids->cfid_list_lock);
 		kfree(utf16_path);
-		return 0;
+		return -ENOMEM;
 	}
+
+	cfid->cfids = cfids;
+	cfids->num_entries++;
+	list_add(&cfid->entry, &cfids->entries);
 	spin_unlock(&cfids->cfid_list_lock);
 
 	pfid = &cfid->fid;
@@ -487,19 +468,22 @@ smb2_close_cached_fid(struct kref *ref)
 	kfree(cfid);
 }
 
-void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon,
-			     const char *name, struct cifs_sb_info *cifs_sb)
+void drop_cached_dir_by_name(struct cached_fids *cfids, const char *name)
 {
-	struct cached_fid *cfid = NULL;
-	int rc;
+	struct cached_fid *cfid;
 
-	rc = open_cached_dir(xid, tcon, name, cifs_sb, true, &cfid);
-	if (rc) {
+	if (!cfids)
+		return;
+
+	cfid = find_cached_dir(cfids, name);
+	if (!cfid) {
 		cifs_dbg(FYI, "no cached dir found for rmdir(%s)\n", name);
 		return;
 	}
 
 	drop_cfid(cfid);
+
+	/* put lookup ref */
 	kref_put(&cfid->refcount, smb2_close_cached_fid);
 }
 
@@ -609,6 +593,7 @@ static struct cached_fid *init_cached_dir(const char *path)
 	cfid = kzalloc(sizeof(*cfid), GFP_ATOMIC);
 	if (!cfid)
 		return NULL;
+
 	cfid->path = kstrdup(path, GFP_ATOMIC);
 	if (!cfid->path) {
 		kfree(cfid);
@@ -619,7 +604,23 @@ static struct cached_fid *init_cached_dir(const char *path)
 	INIT_LIST_HEAD(&cfid->dirents.entries);
 	mutex_init(&cfid->dirents.de_mutex);
 	spin_lock_init(&cfid->fid_lock);
+
+	/* this is our ref */
 	kref_init(&cfid->refcount);
+
+	/* this is caller/lease ref */
+	kref_get(&cfid->refcount);
+
+	/*
+	 * Set @cfid->has_lease to true during construction so that the lease
+	 * reference can be put in cached_dir_lease_break() due to a potential
+	 * lease break right after the request is sent or while @cfid is still
+	 * being cached, or if a reconnection is triggered during construction.
+	 * Concurrent processes won't be to use it yet due to @cfid->time being
+	 * zero.
+	 */
+	cfid->has_lease = true;
+
 	return cfid;
 }
 
diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h
index 47c0404ba84a..4bc93131275e 100644
--- a/fs/smb/client/cached_dir.h
+++ b/fs/smb/client/cached_dir.h
@@ -79,10 +79,7 @@ extern int open_cached_dir_by_dentry(struct cifs_tcon *tcon,
 				     struct dentry *dentry,
 				     struct cached_fid **cfid);
 extern void close_cached_dir(struct cached_fid *cfid);
-extern void drop_cached_dir_by_name(const unsigned int xid,
-				    struct cifs_tcon *tcon,
-				    const char *name,
-				    struct cifs_sb_info *cifs_sb);
+extern void drop_cached_dir_by_name(struct cached_fids *cfids, const char *name);
 extern void close_all_cached_dirs(struct cifs_sb_info *cifs_sb);
 extern void invalidate_all_cached_dirs(struct cached_fids *cfids);
 extern bool cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16]);
diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c
index e80bf55765b6..9344a86f6d46 100644
--- a/fs/smb/client/inode.c
+++ b/fs/smb/client/inode.c
@@ -2615,7 +2615,7 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir,
 			 * ->i_nlink and then mark it as delete pending.
 			 */
 			if (S_ISDIR(inode->i_mode)) {
-				drop_cached_dir_by_name(xid, tcon, to_name, cifs_sb);
+				drop_cached_dir_by_name(tcon->cfids, to_name);
 				spin_lock(&inode->i_lock);
 				i_size_write(inode, 0);
 				clear_nlink(inode);
diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c
index 7cadc8ca4f55..f462845dd167 100644
--- a/fs/smb/client/smb2inode.c
+++ b/fs/smb/client/smb2inode.c
@@ -1162,7 +1162,7 @@ smb2_rmdir(const unsigned int xid, struct cifs_tcon *tcon, const char *name,
 {
 	struct cifs_open_parms oparms;
 
-	drop_cached_dir_by_name(xid, tcon, name, cifs_sb);
+	drop_cached_dir_by_name(tcon->cfids, name);
 	oparms = CIFS_OPARMS(cifs_sb, tcon, name, DELETE,
 			     FILE_OPEN, CREATE_NOT_FILE, ACL_NO_MODE);
 	return smb2_compound_op(xid, tcon, cifs_sb,
@@ -1240,7 +1240,7 @@ int smb2_rename_path(const unsigned int xid,
 	struct cifsFileInfo *cfile;
 	__u32 co = file_create_options(source_dentry);
 
-	drop_cached_dir_by_name(xid, tcon, from_name, cifs_sb);
+	drop_cached_dir_by_name(tcon->cfids, from_name);
 	cifs_get_writable_path(tcon, from_name, FIND_WR_WITH_DELETE, &cfile);
 
 	int rc = smb2_set_path_attr(xid, tcon, from_name, to_name, cifs_sb,
@@ -1522,7 +1522,7 @@ int smb2_rename_pending_delete(const char *full_path,
 		goto out;
 	}
 
-	drop_cached_dir_by_name(xid, tcon, full_path, cifs_sb);
+	drop_cached_dir_by_name(tcon->cfids, full_path);
 	oparms = CIFS_OPARMS(cifs_sb, tcon, full_path,
 			     DELETE | FILE_WRITE_ATTRIBUTES,
 			     FILE_OPEN, co, ACL_NO_MODE);
-- 
2.49.0


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

* [PATCH 09/20] smb: client: enhance cached dir lookups
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
                   ` (7 preceding siblings ...)
  2025-09-29 13:27 ` [PATCH 08/20] smb: client: split find_or_create_cached_dir() Enzo Matsumiya
@ 2025-09-29 13:27 ` Enzo Matsumiya
  2025-09-29 13:27 ` [PATCH 10/20] smb: client: refactor dropping cached dirs Enzo Matsumiya
                   ` (11 subsequent siblings)
  20 siblings, 0 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:27 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

Enable cfid lookups to be matched with the 3 modes (path, dentry,
and lease key) currently used in a single function.

Caller exposed function (find_cached_dir()) checks if cfid is
mid-creation in open_cached_dir() and retries the lookup, avoiding
opening the same path again.

Changes:
- expose find_cached_dir()
- add CFID_LOOKUP_* modes
- remove @lookup_only arg from open_cached_dir(), replace, in calllers,
  with find_cached_dir() where it was true
- remove open_cached_dir_by_dentry(), replace with find_cached_dir()
- use find_cached_dir() in cached_dir_lease_break()

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cached_dir.c | 200 ++++++++++++++++++++++---------------
 fs/smb/client/cached_dir.h |  17 ++--
 fs/smb/client/inode.c      |  11 +-
 fs/smb/client/readdir.c    |   4 +-
 fs/smb/client/smb2inode.c  |  10 +-
 fs/smb/client/smb2ops.c    |  15 ++-
 6 files changed, 144 insertions(+), 113 deletions(-)

diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index 92898880d20f..37a9bff26da7 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -54,27 +54,63 @@ static inline void drop_cfid(struct cached_fid *cfid)
 	}
 }
 
-static struct cached_fid *find_cached_dir(struct cached_fids *cfids, const char *path)
+/*
+ * Find a cached dir based on @key and @mode (raw lookup).
+ * The only validation done here is if cfid is not going down (last_access_time != 1).
+ *
+ * If @wait_open is true, keep retrying until cfid transitions from 'opening' to valid/invalid.
+ *
+ * Callers must handle any other validation as needed.
+ * Returned cfid, if found, has a ref taken, regardless of state.
+ */
+static struct cached_fid *find_cfid(struct cached_fids *cfids, const void *key, int mode,
+				    bool wait_open)
 {
-	struct cached_fid *cfid;
+	struct cached_fid *cfid, *found;
+	bool match;
 
+	if (!cfids || !key)
+		return NULL;
+
+retry_find:
+	found = NULL;
+
+	spin_lock(&cfids->cfid_list_lock);
 	list_for_each_entry(cfid, &cfids->entries, entry) {
-		if (!strcmp(cfid->path, path)) {
-			/*
-			 * If it doesn't have a lease it is either not yet
-			 * fully cached or it may be in the process of
-			 * being deleted due to a lease break.
-			 */
-			if (!cfid_is_valid(cfid))
-				return NULL;
+		/* don't even bother checking if it's going away */
+		if (cfid->last_access_time == 1)
+			continue;
 
-			cfid->last_access_time = jiffies;
+		if (mode == CFID_LOOKUP_PATH)
+			match = !strcmp(cfid->path, (char *)key);
+
+		if (mode == CFID_LOOKUP_DENTRY)
+			match = (cfid->dentry == key);
+
+		if (mode == CFID_LOOKUP_LEASEKEY)
+			match = !memcmp(cfid->fid.lease_key, (u8 *)key, SMB2_LEASE_KEY_SIZE);
+
+		if (!match)
+			continue;
+
+		/* only get a ref here if not waiting for open */
+		if (!wait_open)
 			kref_get(&cfid->refcount);
-			return cfid;
-		}
+		found = cfid;
+		break;
 	}
+	spin_unlock(&cfids->cfid_list_lock);
+
+	if (wait_open && found) {
+		/* cfid is being opened in open_cached_dir(), retry lookup */
+		if (found->has_lease && !found->time && !found->last_access_time)
+			goto retry_find;
 
-	return NULL;
+		/* we didn't get a ref above, so get one now */
+		kref_get(&found->refcount);
+	}
+
+	return found;
 }
 
 static struct dentry *
@@ -133,14 +169,38 @@ static const char *path_no_prefix(struct cifs_sb_info *cifs_sb,
 	return path + len;
 }
 
+/*
+ * Find a cached dir based on @key and @mode (caller exposed).
+ * This function will retry lookup if cfid found is in opening state.
+ *
+ * Returns valid cfid (with updated last_access_time) or NULL.
+ */
+struct cached_fid *find_cached_dir(struct cached_fids *cfids, const void *key, int mode)
+{
+	struct cached_fid *cfid;
+
+	if (!cfids || !key)
+		return NULL;
+
+	cfid = find_cfid(cfids, key, mode, true);
+	if (cfid) {
+		if (cfid_is_valid(cfid)) {
+			cfid->last_access_time = jiffies;
+		} else {
+			kref_put(&cfid->refcount, smb2_close_cached_fid);
+			cfid = NULL;
+		}
+	}
+
+	return cfid;
+}
+
 /*
  * Open the and cache a directory handle.
  * If error then *cfid is not initialized.
  */
-int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
-		    const char *path,
-		    struct cifs_sb_info *cifs_sb,
-		    bool lookup_only, struct cached_fid **ret_cfid)
+int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
+		    struct cifs_sb_info *cifs_sb, struct cached_fid **ret_cfid)
 {
 	struct cifs_ses *ses;
 	struct TCP_Server_Info *server;
@@ -156,7 +216,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
 	__le16 *utf16_path = NULL;
 	u8 oplock = SMB2_OPLOCK_LEVEL_II;
 	struct cifs_fid *pfid;
-	struct dentry *dentry = NULL;
+	struct dentry *dentry;
 	struct cached_fid *cfid;
 	struct cached_fids *cfids;
 	const char *npath;
@@ -178,6 +238,9 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
 	/* reinitialize for possible replay */
 	flags = 0;
 	oplock = SMB2_OPLOCK_LEVEL_II;
+	dentry = NULL;
+	cfid = NULL;
+	*ret_cfid = NULL;
 	server = cifs_pick_channel(ses);
 
 	if (!server->ops->new_lease_key)
@@ -187,27 +250,25 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
 	if (!utf16_path)
 		return -ENOMEM;
 
-	spin_lock(&cfids->cfid_list_lock);
-	if (cfids->num_entries >= tcon->max_cached_dirs) {
-		spin_unlock(&cfids->cfid_list_lock);
-		kfree(utf16_path);
-		return -ENOENT;
+	/* find_cached_dir() already checks has_lease and time, so no need to check here */
+	cfid = find_cached_dir(cfids, path, CFID_LOOKUP_PATH);
+	if (cfid) {
+		rc = 0;
+		goto out;
 	}
 
-	/* find_cached_dir() already checks if has_lease and time, so no need to check here */
-	cfid = find_cached_dir(cfids, path);
-	if (cfid || lookup_only) {
-		*ret_cfid = cfid;
+	spin_lock(&cfids->cfid_list_lock);
+	if (cfids->num_entries >= tcon->max_cached_dirs) {
 		spin_unlock(&cfids->cfid_list_lock);
-		kfree(utf16_path);
-		return cfid ? 0 : -ENOENT;
+		rc = -ENOENT;
+		goto out;
 	}
 
 	cfid = init_cached_dir(path);
 	if (!cfid) {
 		spin_unlock(&cfids->cfid_list_lock);
-		kfree(utf16_path);
-		return -ENOMEM;
+		rc = -ENOMEM;
+		goto out;
 	}
 
 	cfid->cfids = cfids;
@@ -393,8 +454,10 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
 		rc = -ENOENT;
 
 	if (rc) {
-		drop_cfid(cfid);
-		kref_put(&cfid->refcount, smb2_close_cached_fid);
+		if (cfid) {
+			drop_cfid(cfid);
+			kref_put(&cfid->refcount, smb2_close_cached_fid);
+		}
 	} else {
 		*ret_cfid = cfid;
 		atomic_inc(&tcon->num_remote_opens);
@@ -408,32 +471,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
 	return rc;
 }
 
-int open_cached_dir_by_dentry(struct cifs_tcon *tcon,
-			      struct dentry *dentry,
-			      struct cached_fid **ret_cfid)
-{
-	struct cached_fid *cfid;
-	struct cached_fids *cfids = tcon->cfids;
-
-	if (cfids == NULL)
-		return -EOPNOTSUPP;
-
-	spin_lock(&cfids->cfid_list_lock);
-	list_for_each_entry(cfid, &cfids->entries, entry) {
-		if (cfid_is_valid(cfid) && cfid->dentry == dentry) {
-			cifs_dbg(FYI, "found a cached file handle by dentry\n");
-			kref_get(&cfid->refcount);
-			*ret_cfid = cfid;
-			spin_unlock(&cfids->cfid_list_lock);
-			return 0;
-		}
-	}
-	spin_unlock(&cfids->cfid_list_lock);
-	return -ENOENT;
-}
-
-static void
-smb2_close_cached_fid(struct kref *ref)
+static void smb2_close_cached_fid(struct kref *ref)
 {
 	struct cached_fid *cfid = container_of(ref, struct cached_fid, refcount);
 	struct cached_dirent *de, *q;
@@ -475,7 +513,7 @@ void drop_cached_dir_by_name(struct cached_fids *cfids, const char *name)
 	if (!cfids)
 		return;
 
-	cfid = find_cached_dir(cfids, name);
+	cfid = find_cached_dir(cfids, name, CFID_LOOKUP_PATH);
 	if (!cfid) {
 		cifs_dbg(FYI, "no cached dir found for rmdir(%s)\n", name);
 		return;
@@ -555,35 +593,31 @@ bool cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16])
 {
 	struct cached_fids *cfids = tcon->cfids;
 	struct cached_fid *cfid;
-	bool found = false;
 
 	if (cfids == NULL)
 		return false;
 
-	spin_lock(&cfids->cfid_list_lock);
-	list_for_each_entry(cfid, &cfids->entries, entry) {
-		if (cfid->has_lease &&
-		    !memcmp(lease_key,
-			    cfid->fid.lease_key,
-			    SMB2_LEASE_KEY_SIZE)) {
-			/*
-			 * We found a lease, invalidate cfid and schedule immediate cleanup on
-			 * laundromat.
-			 * No need to take a ref here, as we still hold our initial one.
-			 */
-			invalidate_cfid(cfid);
-			cfid->has_lease = false;
-			found = true;
-			break;
-		}
-	}
-	spin_unlock(&cfids->cfid_list_lock);
+	/*
+	 * Raw lookup here as we _must_ find our lease, no matter cfid state.
+	 * Also, this lease break might be coming from the SMB2 open in open_cached_dir(), so no
+	 * need to wait for it to finish.
+	 */
+	cfid = find_cfid(cfids, lease_key, CFID_LOOKUP_LEASEKEY, false);
+	if (cfid) {
+		/* found a lease, invalidate cfid and schedule immediate cleanup on laundromat */
+		spin_lock(&cfids->cfid_list_lock);
+		invalidate_cfid(cfid);
+		cfid->has_lease = false;
+		spin_unlock(&cfids->cfid_list_lock);
 
-	/* avoid unnecessary scheduling */
-	if (found)
+		/* put lookup ref */
+		kref_put(&cfid->refcount, smb2_close_cached_fid);
 		mod_delayed_work(cfid_put_wq, &cfids->laundromat_work, 0);
 
-	return found;
+		return true;
+	}
+
+	return false;
 }
 
 static struct cached_fid *init_cached_dir(const char *path)
diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h
index 4bc93131275e..afb9af227219 100644
--- a/fs/smb/client/cached_dir.h
+++ b/fs/smb/client/cached_dir.h
@@ -59,6 +59,13 @@ struct cached_fids {
 	struct delayed_work laundromat_work;
 };
 
+/* Lookup modes for find_cached_dir() */
+enum {
+	CFID_LOOKUP_PATH,
+	CFID_LOOKUP_DENTRY,
+	CFID_LOOKUP_LEASEKEY,
+};
+
 static inline bool cfid_expired(const struct cached_fid *cfid)
 {
 	return (cfid->last_access_time &&
@@ -71,13 +78,9 @@ static inline bool cfid_is_valid(const struct cached_fid *cfid)
 }
 
 extern struct cached_fids *init_cached_dirs(void);
-extern int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
-			   const char *path,
-			   struct cifs_sb_info *cifs_sb,
-			   bool lookup_only, struct cached_fid **cfid);
-extern int open_cached_dir_by_dentry(struct cifs_tcon *tcon,
-				     struct dentry *dentry,
-				     struct cached_fid **cfid);
+extern struct cached_fid *find_cached_dir(struct cached_fids *cfids, const void *key, int mode);
+extern int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
+			   struct cifs_sb_info *cifs_sb, struct cached_fid **cfid);
 extern void close_cached_dir(struct cached_fid *cfid);
 extern void drop_cached_dir_by_name(struct cached_fids *cfids, const char *name);
 extern void close_all_cached_dirs(struct cifs_sb_info *cifs_sb);
diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c
index 9344a86f6d46..df236c844611 100644
--- a/fs/smb/client/inode.c
+++ b/fs/smb/client/inode.c
@@ -2676,7 +2676,7 @@ cifs_dentry_needs_reval(struct dentry *dentry)
 	struct cifsInodeInfo *cifs_i = CIFS_I(inode);
 	struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
 	struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
-	struct cached_fid *cfid = NULL;
+	struct cached_fid *cfid;
 
 	if (test_bit(CIFS_INO_DELETE_PENDING, &cifs_i->flags))
 		return false;
@@ -2689,13 +2689,12 @@ cifs_dentry_needs_reval(struct dentry *dentry)
 	if (!lookupCacheEnabled)
 		return true;
 
-	if (!open_cached_dir_by_dentry(tcon, dentry->d_parent, &cfid)) {
-		if (cfid->time && cifs_i->time > cfid->time) {
-			close_cached_dir(cfid);
-			return false;
-		}
+	cfid = find_cached_dir(tcon->cfids, dentry->d_parent, CFID_LOOKUP_DENTRY);
+	if (cfid) {
 		close_cached_dir(cfid);
+		return false;
 	}
+
 	/*
 	 * depending on inode type, check if attribute caching disabled for
 	 * files or directories
diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c
index 4e5460206397..cc6762d950d2 100644
--- a/fs/smb/client/readdir.c
+++ b/fs/smb/client/readdir.c
@@ -1065,7 +1065,7 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
 		tcon = tlink_tcon(cifsFile->tlink);
 	}
 
-	rc = open_cached_dir(xid, tcon, full_path, cifs_sb, false, &cfid);
+	rc = open_cached_dir(xid, tcon, full_path, cifs_sb, &cfid);
 	cifs_put_tlink(tlink);
 	if (rc)
 		goto cache_not_found;
@@ -1136,7 +1136,7 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
 	tcon = tlink_tcon(cifsFile->tlink);
 	rc = find_cifs_entry(xid, tcon, ctx->pos, file, full_path,
 			     &current_entry, &num_to_fill);
-	open_cached_dir(xid, tcon, full_path, cifs_sb, false, &cfid);
+	open_cached_dir(xid, tcon, full_path, cifs_sb, &cfid);
 	if (rc) {
 		cifs_dbg(FYI, "fce error %d\n", rc);
 		goto rddir2_exit;
diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c
index f462845dd167..c76fe1dec390 100644
--- a/fs/smb/client/smb2inode.c
+++ b/fs/smb/client/smb2inode.c
@@ -964,12 +964,10 @@ int smb2_query_path_info(const unsigned int xid,
 	 * is fast enough (always using the compounded version).
 	 */
 	if (!tcon->posix_extensions) {
-		if (*full_path) {
-			rc = -ENOENT;
-		} else {
-			rc = open_cached_dir(xid, tcon, full_path,
-					     cifs_sb, false, &cfid);
-		}
+		rc = -ENOENT;
+		if (!*full_path)
+			rc = open_cached_dir(xid, tcon, full_path, cifs_sb, &cfid);
+
 		/* If it is a root and its handle is cached then use it */
 		if (!rc) {
 			if (cfid->file_all_info_is_valid) {
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index e586f3f4b5c9..39e6dc13d2da 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -882,7 +882,7 @@ smb3_qfs_tcon(const unsigned int xid, struct cifs_tcon *tcon,
 		.fid = &fid,
 	};
 
-	rc = open_cached_dir(xid, tcon, "", cifs_sb, false, &cfid);
+	rc = open_cached_dir(xid, tcon, "", cifs_sb, &cfid);
 	if (rc == 0)
 		memcpy(&fid, &cfid->fid, sizeof(struct cifs_fid));
 	else
@@ -952,13 +952,10 @@ smb2_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon,
 	bool islink;
 	int rc, rc2;
 
-	rc = open_cached_dir(xid, tcon, full_path, cifs_sb, true, &cfid);
-	if (!rc) {
-		if (cfid->has_lease) {
-			close_cached_dir(cfid);
-			return 0;
-		}
+	cfid = find_cached_dir(tcon->cfids, full_path, CFID_LOOKUP_PATH);
+	if (cfid) {
 		close_cached_dir(cfid);
+		return 0;
 	}
 
 	utf16_path = cifs_convert_path_to_utf16(full_path, cifs_sb);
@@ -2747,8 +2744,8 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon,
 	 * We can only call this for things we know are directories.
 	 */
 	if (!strcmp(path, ""))
-		open_cached_dir(xid, tcon, path, cifs_sb, false,
-				&cfid); /* cfid null if open dir failed */
+		/* cfid null if open dir failed */
+		open_cached_dir(xid, tcon, path, cifs_sb, &cfid);
 
 	rqst[0].rq_iov = vars->open_iov;
 	rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE;
-- 
2.49.0


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

* [PATCH 10/20] smb: client: refactor dropping cached dirs
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
                   ` (8 preceding siblings ...)
  2025-09-29 13:27 ` [PATCH 09/20] smb: client: enhance cached dir lookups Enzo Matsumiya
@ 2025-09-29 13:27 ` Enzo Matsumiya
  2025-09-29 13:27 ` [PATCH 11/20] smb: client: simplify cached_fid state checking Enzo Matsumiya
                   ` (10 subsequent siblings)
  20 siblings, 0 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:27 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

- s/drop_cached_dir_by_name/drop_cached_dir/
  make it a generic find + invalidate function to replace
  drop_cached_dir_by_name() and cached_dir_lease_break()

We now funnel any cleanup to laundromat, so we can make the release
callback free only dirents, path, and cfid itself.

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cached_dir.c | 78 +++++++++++---------------------------
 fs/smb/client/cached_dir.h |  3 +-
 fs/smb/client/inode.c      |  2 +-
 fs/smb/client/smb2inode.c  |  7 ++--
 fs/smb/client/smb2misc.c   |  2 +-
 5 files changed, 29 insertions(+), 63 deletions(-)

diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index 37a9bff26da7..84ea2653cdb9 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -476,22 +476,6 @@ static void smb2_close_cached_fid(struct kref *ref)
 	struct cached_fid *cfid = container_of(ref, struct cached_fid, refcount);
 	struct cached_dirent *de, *q;
 
-	/*
-	 * There's no way a valid cfid can reach here.
-	 *
-	 * This is because we hould our own ref, and whenever we put it, we invalidate the cfid.
-	 *
-	 * So even if an external caller puts the last ref, cfid will already have been invalidated
-	 * by then by one of the invalidations that can happen concurrently, e.g. lease break,
-	 * invalidate_all_cached_dirs().
-	 *
-	 * So this check is mostly for precaution, but since we can still take the correct action
-	 * (just list_del()) if it's the case, do so.
-	 */
-	if (WARN_ON(cfid_is_valid(cfid)))
-		/* remaining invalidation done by drop_cfid() below */
-		list_del(&cfid->entry);
-
 	drop_cfid(cfid);
 
 	/* Delete all cached dirent names */
@@ -506,23 +490,36 @@ static void smb2_close_cached_fid(struct kref *ref)
 	kfree(cfid);
 }
 
-void drop_cached_dir_by_name(struct cached_fids *cfids, const char *name)
+bool drop_cached_dir(struct cached_fids *cfids, const void *key, int mode)
 {
 	struct cached_fid *cfid;
 
-	if (!cfids)
-		return;
+	if (!cfids || !key)
+		return false;
 
-	cfid = find_cached_dir(cfids, name, CFID_LOOKUP_PATH);
-	if (!cfid) {
-		cifs_dbg(FYI, "no cached dir found for rmdir(%s)\n", name);
-		return;
-	}
+	/*
+	 * Raw lookup here as we _must_ find any matching cfid, no matter its state.
+	 * Also, we might be racing with the SMB2 open in open_cached_dir(), so no need to wait
+	 * for it to finish.
+	 */
+	cfid = find_cfid(cfids, key, mode, false);
+	if (!cfid)
+		return false;
 
-	drop_cfid(cfid);
+	if (mode != CFID_LOOKUP_LEASEKEY) {
+		drop_cfid(cfid);
+	} else {
+		/* we're locked in smb2_is_valid_lease_break(), so can't dput/close here */
+		spin_lock(&cfids->cfid_list_lock);
+		invalidate_cfid(cfid);
+		spin_unlock(&cfids->cfid_list_lock);
+	}
 
 	/* put lookup ref */
 	kref_put(&cfid->refcount, smb2_close_cached_fid);
+	mod_delayed_work(cfid_put_wq, &cfids->laundromat_work, 0);
+
+	return true;
 }
 
 void close_cached_dir(struct cached_fid *cfid)
@@ -589,37 +586,6 @@ void invalidate_all_cached_dirs(struct cached_fids *cfids)
 	flush_delayed_work(&cfids->laundromat_work);
 }
 
-bool cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16])
-{
-	struct cached_fids *cfids = tcon->cfids;
-	struct cached_fid *cfid;
-
-	if (cfids == NULL)
-		return false;
-
-	/*
-	 * Raw lookup here as we _must_ find our lease, no matter cfid state.
-	 * Also, this lease break might be coming from the SMB2 open in open_cached_dir(), so no
-	 * need to wait for it to finish.
-	 */
-	cfid = find_cfid(cfids, lease_key, CFID_LOOKUP_LEASEKEY, false);
-	if (cfid) {
-		/* found a lease, invalidate cfid and schedule immediate cleanup on laundromat */
-		spin_lock(&cfids->cfid_list_lock);
-		invalidate_cfid(cfid);
-		cfid->has_lease = false;
-		spin_unlock(&cfids->cfid_list_lock);
-
-		/* put lookup ref */
-		kref_put(&cfid->refcount, smb2_close_cached_fid);
-		mod_delayed_work(cfid_put_wq, &cfids->laundromat_work, 0);
-
-		return true;
-	}
-
-	return false;
-}
-
 static struct cached_fid *init_cached_dir(const char *path)
 {
 	struct cached_fid *cfid;
diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h
index afb9af227219..bed5ba68b07f 100644
--- a/fs/smb/client/cached_dir.h
+++ b/fs/smb/client/cached_dir.h
@@ -82,8 +82,7 @@ extern struct cached_fid *find_cached_dir(struct cached_fids *cfids, const void
 extern int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 			   struct cifs_sb_info *cifs_sb, struct cached_fid **cfid);
 extern void close_cached_dir(struct cached_fid *cfid);
-extern void drop_cached_dir_by_name(struct cached_fids *cfids, const char *name);
+extern bool drop_cached_dir(struct cached_fids *cfids, const void *key, int mode);
 extern void close_all_cached_dirs(struct cifs_sb_info *cifs_sb);
 extern void invalidate_all_cached_dirs(struct cached_fids *cfids);
-extern bool cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16]);
 #endif			/* _CACHED_DIR_H */
diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c
index df236c844611..f2eff1138ed0 100644
--- a/fs/smb/client/inode.c
+++ b/fs/smb/client/inode.c
@@ -2615,7 +2615,7 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir,
 			 * ->i_nlink and then mark it as delete pending.
 			 */
 			if (S_ISDIR(inode->i_mode)) {
-				drop_cached_dir_by_name(tcon->cfids, to_name);
+				drop_cached_dir(tcon->cfids, to_name, CFID_LOOKUP_PATH);
 				spin_lock(&inode->i_lock);
 				i_size_write(inode, 0);
 				clear_nlink(inode);
diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c
index c76fe1dec390..62d6adf50ad1 100644
--- a/fs/smb/client/smb2inode.c
+++ b/fs/smb/client/smb2inode.c
@@ -1160,7 +1160,7 @@ smb2_rmdir(const unsigned int xid, struct cifs_tcon *tcon, const char *name,
 {
 	struct cifs_open_parms oparms;
 
-	drop_cached_dir_by_name(tcon->cfids, name);
+	drop_cached_dir(tcon->cfids, name, CFID_LOOKUP_PATH);
 	oparms = CIFS_OPARMS(cifs_sb, tcon, name, DELETE,
 			     FILE_OPEN, CREATE_NOT_FILE, ACL_NO_MODE);
 	return smb2_compound_op(xid, tcon, cifs_sb,
@@ -1238,7 +1238,7 @@ int smb2_rename_path(const unsigned int xid,
 	struct cifsFileInfo *cfile;
 	__u32 co = file_create_options(source_dentry);
 
-	drop_cached_dir_by_name(tcon->cfids, from_name);
+	drop_cached_dir(tcon->cfids, from_name, CFID_LOOKUP_PATH);
 	cifs_get_writable_path(tcon, from_name, FIND_WR_WITH_DELETE, &cfile);
 
 	int rc = smb2_set_path_attr(xid, tcon, from_name, to_name, cifs_sb,
@@ -1520,7 +1520,8 @@ int smb2_rename_pending_delete(const char *full_path,
 		goto out;
 	}
 
-	drop_cached_dir_by_name(tcon->cfids, full_path);
+	drop_cached_dir(tcon->cfids, full_path, CFID_LOOKUP_PATH);
+
 	oparms = CIFS_OPARMS(cifs_sb, tcon, full_path,
 			     DELETE | FILE_WRITE_ATTRIBUTES,
 			     FILE_OPEN, co, ACL_NO_MODE);
diff --git a/fs/smb/client/smb2misc.c b/fs/smb/client/smb2misc.c
index 89d933b4a8bc..71d987f76a12 100644
--- a/fs/smb/client/smb2misc.c
+++ b/fs/smb/client/smb2misc.c
@@ -660,7 +660,7 @@ smb2_is_valid_lease_break(char *buffer, struct TCP_Server_Info *server)
 			}
 			spin_unlock(&tcon->open_file_lock);
 
-			if (cached_dir_lease_break(tcon, rsp->LeaseKey)) {
+			if (drop_cached_dir(tcon->cfids, rsp->LeaseKey, CFID_LOOKUP_LEASEKEY)) {
 				spin_unlock(&cifs_tcp_ses_lock);
 				return true;
 			}
-- 
2.49.0


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

* [PATCH 11/20] smb: client: simplify cached_fid state checking
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
                   ` (9 preceding siblings ...)
  2025-09-29 13:27 ` [PATCH 10/20] smb: client: refactor dropping cached dirs Enzo Matsumiya
@ 2025-09-29 13:27 ` Enzo Matsumiya
  2025-09-29 13:27 ` [PATCH 12/20] smb: client: prevent lease breaks of cached parents when opening children Enzo Matsumiya
                   ` (9 subsequent siblings)
  20 siblings, 0 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:27 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

cached_fid validity (usable by callers) is already based on ->time != 0,
having other flags/booleans to check the same thing can be confusing and
make things unnecessarily complex.

This patch removes cached_fid booleans ->has_lease, ->is_open,
->file_all_info_is_valid.

Replace their semantics with already existing, simpler checks:

- ->is_open is replaced by checking if persistent_fid != 0
- ->has_lease is currently used as a "is valid" check, but we already
  validate it based on ->time anyway, so drop it
- ->file_all_info becomes a pointer and its presence becomes its
  validity

This patch also concretly defines the "is opening" semantic; it's
based on ->time == 0, which is used as "creation time", so the only
time it's 0 is between allocation and valid (opened/cached) or invalid,
and it also never transitions back to 0 again.
(->last_access_time follows the same, but it's already used for
expiration checks)

Other:
- add CFID_INVALID_TIME (value 1); this allows us to differentiate
  between opening (0), valid (jiffies), or invalid (1)
- rename time/last_access_time to ctime/atime to follow other common
  usage of such fields

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cached_dir.c | 72 +++++++++++++++++++-------------------
 fs/smb/client/cached_dir.h | 14 +++-----
 fs/smb/client/cifs_debug.c |  2 +-
 fs/smb/client/smb2inode.c  | 17 +++++----
 4 files changed, 52 insertions(+), 53 deletions(-)

diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index 84ea2653cdb9..ff71f2c06b72 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -12,6 +12,9 @@
 #include "smb2proto.h"
 #include "cached_dir.h"
 
+/* since jiffies can never be 1 here, use it as an invalid value to add meaning for our purposes */
+#define CFID_INVALID_TIME 1
+
 static struct cached_fid *init_cached_dir(const char *path);
 static void smb2_close_cached_fid(struct kref *ref);
 
@@ -24,29 +27,30 @@ static inline void invalidate_cfid(struct cached_fid *cfid)
 		cfid->cfids->num_entries--;
 
 	/* do not change other fields here! */
-	cfid->time = 0;
-	cfid->last_access_time = 1;
+	cfid->ctime = CFID_INVALID_TIME;
+	cfid->atime = CFID_INVALID_TIME;
 }
 
 static inline void drop_cfid(struct cached_fid *cfid)
 {
 	struct dentry *dentry = NULL;
+	u64 pfid = 0, vfid = 0;
 
 	spin_lock(&cfid->cfids->cfid_list_lock);
 	invalidate_cfid(cfid);
 
 	spin_lock(&cfid->fid_lock);
 	swap(cfid->dentry, dentry);
+	swap(cfid->fid.persistent_fid, pfid);
+	swap(cfid->fid.volatile_fid, vfid);
 	spin_unlock(&cfid->fid_lock);
 	spin_unlock(&cfid->cfids->cfid_list_lock);
 
 	dput(dentry);
 
-	if (cfid->is_open) {
-		int rc;
-
-		cfid->is_open = false;
-		rc = SMB2_close(0, cfid->tcon, cfid->fid.persistent_fid, cfid->fid.volatile_fid);
+	if (pfid) {
+		/* cfid->tcon is never set to NULL, so no need to check/swap it */
+		int rc = SMB2_close(0, cfid->tcon, pfid, vfid);
 
 		/* SMB2_close should handle -EBUSY or -EAGAIN */
 		if (rc)
@@ -56,7 +60,7 @@ static inline void drop_cfid(struct cached_fid *cfid)
 
 /*
  * Find a cached dir based on @key and @mode (raw lookup).
- * The only validation done here is if cfid is not going down (last_access_time != 1).
+ * The only validation done here is if cfid is going down (->ctime == CFID_INVALID_TIME).
  *
  * If @wait_open is true, keep retrying until cfid transitions from 'opening' to valid/invalid.
  *
@@ -78,7 +82,7 @@ static struct cached_fid *find_cfid(struct cached_fids *cfids, const void *key,
 	spin_lock(&cfids->cfid_list_lock);
 	list_for_each_entry(cfid, &cfids->entries, entry) {
 		/* don't even bother checking if it's going away */
-		if (cfid->last_access_time == 1)
+		if (cfid->ctime == CFID_INVALID_TIME)
 			continue;
 
 		if (mode == CFID_LOOKUP_PATH)
@@ -103,7 +107,7 @@ static struct cached_fid *find_cfid(struct cached_fids *cfids, const void *key,
 
 	if (wait_open && found) {
 		/* cfid is being opened in open_cached_dir(), retry lookup */
-		if (found->has_lease && !found->time && !found->last_access_time)
+		if (!found->ctime)
 			goto retry_find;
 
 		/* we didn't get a ref above, so get one now */
@@ -185,7 +189,7 @@ struct cached_fid *find_cached_dir(struct cached_fids *cfids, const void *key, i
 	cfid = find_cfid(cfids, key, mode, true);
 	if (cfid) {
 		if (cfid_is_valid(cfid)) {
-			cfid->last_access_time = jiffies;
+			cfid->atime = jiffies;
 		} else {
 			kref_put(&cfid->refcount, smb2_close_cached_fid);
 			cfid = NULL;
@@ -207,6 +211,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 	struct cifs_open_parms oparms;
 	struct smb2_create_rsp *o_rsp = NULL;
 	struct smb2_query_info_rsp *qi_rsp = NULL;
+	struct smb2_file_all_info info;
 	int resp_buftype[2];
 	struct smb_rqst rqst[2];
 	struct kvec rsp_iov[2];
@@ -241,6 +246,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 	dentry = NULL;
 	cfid = NULL;
 	*ret_cfid = NULL;
+	memset(&info, 0, sizeof(info));
 	server = cifs_pick_channel(ses);
 
 	if (!server->ops->new_lease_key)
@@ -250,7 +256,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 	if (!utf16_path)
 		return -ENOMEM;
 
-	/* find_cached_dir() already checks has_lease and time, so no need to check here */
+	/* find_cached_dir() already validates cfid if found, so no need to check here again */
 	cfid = find_cached_dir(cfids, path, CFID_LOOKUP_PATH);
 	if (cfid) {
 		rc = 0;
@@ -393,7 +399,6 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 		}
 		goto oshr_free;
 	}
-	cfid->is_open = true;
 
 	spin_lock(&cfids->cfid_list_lock);
 
@@ -430,19 +435,21 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 		spin_unlock(&cfids->cfid_list_lock);
 		goto oshr_free;
 	}
-	if (!smb2_validate_and_copy_iov(
-				le16_to_cpu(qi_rsp->OutputBufferOffset),
-				sizeof(struct smb2_file_all_info),
-				&rsp_iov[1], sizeof(struct smb2_file_all_info),
-				(char *)&cfid->file_all_info))
-		cfid->file_all_info_is_valid = true;
-
-	cfid->time = jiffies;
-	cfid->last_access_time = jiffies;
+	if (!smb2_validate_and_copy_iov(le16_to_cpu(qi_rsp->OutputBufferOffset),
+					sizeof(struct smb2_file_all_info), &rsp_iov[1],
+					sizeof(struct smb2_file_all_info), (char *)&info)) {
+		cfid->file_all_info = kmemdup(&info, sizeof(info), GFP_ATOMIC);
+		if (!cfid->file_all_info) {
+			rc = -ENOMEM;
+			goto out;
+		}
+	}
+
+	cfid->ctime = jiffies;
+	cfid->atime = jiffies;
 	spin_unlock(&cfids->cfid_list_lock);
 	/* At this point the directory handle is fully cached */
 	rc = 0;
-
 oshr_free:
 	SMB2_open_free(&rqst[0]);
 	SMB2_query_info_free(&rqst[1]);
@@ -485,6 +492,8 @@ static void smb2_close_cached_fid(struct kref *ref)
 		kfree(de);
 	}
 
+	kfree(cfid->file_all_info);
+	cfid->file_all_info = NULL;
 	kfree(cfid->path);
 	cfid->path = NULL;
 	kfree(cfid);
@@ -538,9 +547,10 @@ static void invalidate_all_cfids(struct cached_fids *cfids, bool closed)
 	spin_lock(&cfids->cfid_list_lock);
 	list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {
 		invalidate_cfid(cfid);
-		cfid->has_lease = false;
-		if (closed)
-			cfid->is_open = false;
+		if (closed) {
+			cfid->fid.persistent_fid = 0;
+			cfid->fid.volatile_fid = 0;
+		}
 	}
 	spin_unlock(&cfids->cfid_list_lock);
 
@@ -611,16 +621,6 @@ static struct cached_fid *init_cached_dir(const char *path)
 	/* this is caller/lease ref */
 	kref_get(&cfid->refcount);
 
-	/*
-	 * Set @cfid->has_lease to true during construction so that the lease
-	 * reference can be put in cached_dir_lease_break() due to a potential
-	 * lease break right after the request is sent or while @cfid is still
-	 * being cached, or if a reconnection is triggered during construction.
-	 * Concurrent processes won't be to use it yet due to @cfid->time being
-	 * zero.
-	 */
-	cfid->has_lease = true;
-
 	return cfid;
 }
 
diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h
index bed5ba68b07f..c45151446049 100644
--- a/fs/smb/client/cached_dir.h
+++ b/fs/smb/client/cached_dir.h
@@ -33,17 +33,14 @@ struct cached_fid {
 	struct list_head entry;
 	struct cached_fids *cfids;
 	const char *path;
-	bool has_lease:1;
-	bool is_open:1;
-	bool file_all_info_is_valid:1;
-	unsigned long time; /* jiffies of when lease was taken */
-	unsigned long last_access_time; /* jiffies of when last accessed */
+	unsigned long ctime; /* (jiffies) creation time, when cfid was created (cached) */
+	unsigned long atime; /* (jiffies) access time, when it was last used */
 	struct kref refcount;
 	struct cifs_fid fid;
 	spinlock_t fid_lock;
 	struct cifs_tcon *tcon;
 	struct dentry *dentry;
-	struct smb2_file_all_info file_all_info;
+	struct smb2_file_all_info *file_all_info;
 	struct cached_dirents dirents;
 };
 
@@ -68,13 +65,12 @@ enum {
 
 static inline bool cfid_expired(const struct cached_fid *cfid)
 {
-	return (cfid->last_access_time &&
-		time_is_before_jiffies(cfid->last_access_time + HZ * dir_cache_timeout));
+	return (cfid->atime && time_is_before_jiffies(cfid->atime + HZ * dir_cache_timeout));
 }
 
 static inline bool cfid_is_valid(const struct cached_fid *cfid)
 {
-	return (cfid->has_lease && cfid->time && !cfid_expired(cfid));
+	return (cfid->fid.persistent_fid && cfid->ctime && !cfid_expired(cfid));
 }
 
 extern struct cached_fids *init_cached_dirs(void);
diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c
index 2337cf795db3..bb27b9c97724 100644
--- a/fs/smb/client/cifs_debug.c
+++ b/fs/smb/client/cifs_debug.c
@@ -314,7 +314,7 @@ static int cifs_debug_dirs_proc_show(struct seq_file *m, void *v)
 						ses->Suid,
 						cfid->fid.persistent_fid,
 						cfid->path);
-					if (cfid->file_all_info_is_valid)
+					if (cfid->file_all_info)
 						seq_printf(m, "\tvalid file info");
 					if (cfid->dirents.is_valid)
 						seq_printf(m, ", valid dirents");
diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c
index 62d6adf50ad1..8ccdd1a3ba2c 100644
--- a/fs/smb/client/smb2inode.c
+++ b/fs/smb/client/smb2inode.c
@@ -970,14 +970,17 @@ int smb2_query_path_info(const unsigned int xid,
 
 		/* If it is a root and its handle is cached then use it */
 		if (!rc) {
-			if (cfid->file_all_info_is_valid) {
-				memcpy(&data->fi, &cfid->file_all_info,
-				       sizeof(data->fi));
+			if (cfid->file_all_info) {
+				memcpy(&data->fi, cfid->file_all_info, sizeof(data->fi));
 			} else {
-				rc = SMB2_query_info(xid, tcon,
-						     cfid->fid.persistent_fid,
-						     cfid->fid.volatile_fid,
-						     &data->fi);
+				rc = SMB2_query_info(xid, tcon, cfid->fid.persistent_fid,
+						     cfid->fid.volatile_fid, &data->fi);
+				if (!rc) {
+					cfid->file_all_info = kmemdup(&data->fi, sizeof(data->fi),
+								      GFP_KERNEL);
+					if (!cfid->file_all_info)
+						rc = -ENOMEM;
+				}
 			}
 			close_cached_dir(cfid);
 			return rc;
-- 
2.49.0


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

* [PATCH 12/20] smb: client: prevent lease breaks of cached parents when opening children
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
                   ` (10 preceding siblings ...)
  2025-09-29 13:27 ` [PATCH 11/20] smb: client: simplify cached_fid state checking Enzo Matsumiya
@ 2025-09-29 13:27 ` Enzo Matsumiya
  2025-09-29 14:23   ` Steve French
  2025-09-29 17:17   ` Enzo Matsumiya
  2025-09-29 13:27 ` [PATCH 13/20] smb: client: actually use cached dirs on readdir Enzo Matsumiya
                   ` (8 subsequent siblings)
  20 siblings, 2 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:27 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

In SMB2_open_init(), when opening (not creating/deleting) a path, lookup
for a cached parent and set ParentLeaseKey in lease context if found.

Other:
- set oparms->cifs_sb in open_cached_dir() as we need it in
  add_parent_lease_key(); use CIFS_OPARMS() too

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cached_dir.c | 42 ++++---------------
 fs/smb/client/dir.c        | 26 +++---------
 fs/smb/client/smb2inode.c  |  2 +
 fs/smb/client/smb2pdu.c    | 86 ++++++++++++++++++++++++++++++++------
 4 files changed, 88 insertions(+), 68 deletions(-)

diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index ff71f2c06b72..9dd74268b2d8 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -226,7 +226,6 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 	struct cached_fids *cfids;
 	const char *npath;
 	int retries = 0, cur_sleep = 1;
-	__le32 lease_flags = 0;
 
 	if (cifs_sb->root == NULL)
 		return -ENOENT;
@@ -236,9 +235,9 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 
 	ses = tcon->ses;
 	cfids = tcon->cfids;
-
 	if (!cfids)
 		return -EOPNOTSUPP;
+
 replay_again:
 	/* reinitialize for possible replay */
 	flags = 0;
@@ -306,24 +305,6 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 			rc = -ENOENT;
 			goto out;
 		}
-		if (dentry->d_parent && server->dialect >= SMB30_PROT_ID) {
-			struct cached_fid *parent_cfid;
-
-			spin_lock(&cfids->cfid_list_lock);
-			list_for_each_entry(parent_cfid, &cfids->entries, entry) {
-				if (parent_cfid->dentry == dentry->d_parent) {
-					if (!cfid_is_valid(parent_cfid))
-						break;
-
-					cifs_dbg(FYI, "found a parent cached file handle\n");
-					lease_flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
-					memcpy(pfid->parent_lease_key, parent_cfid->fid.lease_key,
-					       SMB2_LEASE_KEY_SIZE);
-					break;
-				}
-			}
-			spin_unlock(&cfids->cfid_list_lock);
-		}
 	}
 	cfid->dentry = dentry;
 	cfid->tcon = tcon;
@@ -350,20 +331,13 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 	rqst[0].rq_iov = open_iov;
 	rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE;
 
-	oparms = (struct cifs_open_parms) {
-		.tcon = tcon,
-		.path = path,
-		.create_options = cifs_create_options(cifs_sb, CREATE_NOT_FILE),
-		.desired_access =  FILE_READ_DATA | FILE_READ_ATTRIBUTES |
-				   FILE_READ_EA,
-		.disposition = FILE_OPEN,
-		.fid = pfid,
-		.lease_flags = lease_flags,
-		.replay = !!(retries),
-	};
-
-	rc = SMB2_open_init(tcon, server,
-			    &rqst[0], &oplock, &oparms, utf16_path);
+	oparms = CIFS_OPARMS(cifs_sb, tcon, path,
+			     FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA, FILE_OPEN,
+			     cifs_create_options(cifs_sb, CREATE_NOT_FILE), 0);
+	oparms.fid = pfid;
+	oparms.replay = !!retries;
+
+	rc = SMB2_open_init(tcon, server, &rqst[0], &oplock, &oparms, utf16_path);
 	if (rc)
 		goto oshr_free;
 	smb2_set_next_command(tcon, &rqst[0]);
diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c
index e5372c2c799d..b60af27668bb 100644
--- a/fs/smb/client/dir.c
+++ b/fs/smb/client/dir.c
@@ -189,10 +189,9 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
 	struct inode *newinode = NULL;
 	int disposition;
 	struct TCP_Server_Info *server = tcon->ses->server;
+	struct cached_fid *parent_cfid;
 	struct cifs_open_parms oparms;
-	struct cached_fid *parent_cfid = NULL;
 	int rdwr_for_fscache = 0;
-	__le32 lease_flags = 0;
 
 	*oplock = 0;
 	if (tcon->ses->server->oplocks)
@@ -314,25 +313,11 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
 	if (!tcon->unix_ext && (mode & S_IWUGO) == 0)
 		create_options |= CREATE_OPTION_READONLY;
 
-
 retry_open:
-	if (tcon->cfids && direntry->d_parent && server->dialect >= SMB30_PROT_ID) {
-		parent_cfid = NULL;
-		spin_lock(&tcon->cfids->cfid_list_lock);
-		list_for_each_entry(parent_cfid, &tcon->cfids->entries, entry) {
-			if (parent_cfid->dentry == direntry->d_parent) {
-				if (!cfid_is_valid(parent_cfid))
-					break;
-
-				cifs_dbg(FYI, "found a parent cached file handle\n");
-				lease_flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
-				memcpy(fid->parent_lease_key, parent_cfid->fid.lease_key,
-				       SMB2_LEASE_KEY_SIZE);
-				parent_cfid->dirents.is_valid = false;
-				break;
-			}
-		}
-		spin_unlock(&tcon->cfids->cfid_list_lock);
+	parent_cfid = find_cached_dir(tcon->cfids, direntry->d_parent, CFID_LOOKUP_DENTRY);
+	if (parent_cfid) {
+		parent_cfid->dirents.is_valid = false;
+		close_cached_dir(parent_cfid);
 	}
 
 	oparms = (struct cifs_open_parms) {
@@ -343,7 +328,6 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
 		.disposition = disposition,
 		.path = full_path,
 		.fid = fid,
-		.lease_flags = lease_flags,
 		.mode = mode,
 	};
 	rc = server->ops->open(xid, &oparms, oplock, buf);
diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c
index 8ccdd1a3ba2c..6d643b8b9547 100644
--- a/fs/smb/client/smb2inode.c
+++ b/fs/smb/client/smb2inode.c
@@ -1120,6 +1120,8 @@ smb2_mkdir(const unsigned int xid, struct inode *parent_inode, umode_t mode,
 {
 	struct cifs_open_parms oparms;
 
+	drop_cached_dir(tcon->cfids, name, CFID_LOOKUP_PATH);
+
 	oparms = CIFS_OPARMS(cifs_sb, tcon, name, FILE_WRITE_ATTRIBUTES,
 			     FILE_CREATE, CREATE_NOT_FILE, mode);
 	return smb2_compound_op(xid, tcon, cifs_sb,
diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index 07ba61583114..2474ac18b85e 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -2419,7 +2419,8 @@ add_lease_context(struct TCP_Server_Info *server,
 	if (iov[num].iov_base == NULL)
 		return -ENOMEM;
 	iov[num].iov_len = server->vals->create_lease_size;
-	req->RequestedOplockLevel = SMB2_OPLOCK_LEVEL_LEASE;
+	/* keep the requested oplock level in case of just setting ParentLeaseKey */
+	req->RequestedOplockLevel = *oplock;
 	*num_iovec = num + 1;
 	return 0;
 }
@@ -3001,6 +3002,50 @@ int smb311_posix_mkdir(const unsigned int xid, struct inode *inode,
 	return rc;
 }
 
+/*
+ * When opening a path, set ParentLeaseKey in @oparms if its parent is cached.
+ * We only have RH caching for dirs, so skip this on mkdir, unlink, rmdir.
+ *
+ * Ref: MS-SMB2 3.3.5.9 and MS-FSA 2.1.5.1
+ *
+ * Return: 0 if ParentLeaseKey was set in @oparms, -errno otherwise.
+ */
+static int check_cached_parent(struct cached_fids *cfids, struct cifs_open_parms *oparms)
+{
+	struct cached_fid *cfid;
+	const char *parent_path, *path;
+
+	if (!cfids || !oparms || !oparms->cifs_sb || !*oparms->path)
+		return -EINVAL;
+
+	if ((oparms->disposition == FILE_CREATE && oparms->create_options == CREATE_NOT_FILE) ||
+	    oparms->desired_access == DELETE)
+		return -EOPNOTSUPP;
+
+	path = oparms->path;
+	parent_path = strrchr(path, CIFS_DIR_SEP(oparms->cifs_sb));
+	if (!parent_path)
+		return -ENOENT;
+
+	parent_path = kstrndup(path, parent_path - path, GFP_KERNEL);
+	if (!parent_path)
+		return -ENOMEM;
+
+	cfid = find_cached_dir(cfids, parent_path, CFID_LOOKUP_PATH);
+	kfree(parent_path);
+
+	if (!cfid)
+		return -ENOENT;
+
+	cifs_dbg(FYI, "%s: found cached parent for path: %s\n", __func__, oparms->path);
+
+	memcpy(oparms->fid->parent_lease_key, cfid->fid.lease_key, SMB2_LEASE_KEY_SIZE);
+	oparms->lease_flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
+	close_cached_dir(cfid);
+
+	return 0;
+}
+
 int
 SMB2_open_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server,
 	       struct smb_rqst *rqst, __u8 *oplock,
@@ -3077,20 +3122,35 @@ SMB2_open_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server,
 	iov[1].iov_len = uni_path_len;
 	iov[1].iov_base = path;
 
-	if ((!server->oplocks) || (tcon->no_lease))
+	if (!server->oplocks || tcon->no_lease)
 		*oplock = SMB2_OPLOCK_LEVEL_NONE;
 
-	if (!(server->capabilities & SMB2_GLOBAL_CAP_LEASING) ||
-	    *oplock == SMB2_OPLOCK_LEVEL_NONE)
-		req->RequestedOplockLevel = *oplock;
-	else if (!(server->capabilities & SMB2_GLOBAL_CAP_DIRECTORY_LEASING) &&
-		  (oparms->create_options & CREATE_NOT_FILE))
-		req->RequestedOplockLevel = *oplock; /* no srv lease support */
-	else {
-		rc = add_lease_context(server, req, iov, &n_iov,
-				       oparms->fid->lease_key, oplock,
-				       oparms->fid->parent_lease_key,
-				       oparms->lease_flags);
+	req->RequestedOplockLevel = *oplock;
+
+	/*
+	 * MS-SMB2 "Product Behavior" says Windows only checks/sets ParentLeaseKey when a lease is
+	 * requested for the child/target.
+	 * Practically speaking, adding the lease context with ParentLeaseKey set, even with oplock
+	 * none, works fine.
+	 * As a precaution, however, only set it for oplocks != none.
+	 */
+	if ((server->capabilities & SMB2_GLOBAL_CAP_LEASING) &&
+	    *oplock != SMB2_OPLOCK_LEVEL_NONE) {
+		rc = -EOPNOTSUPP;
+		if (server->capabilities & SMB2_GLOBAL_CAP_DIRECTORY_LEASING)
+			rc = check_cached_parent(tcon->cfids, oparms);
+
+		/*
+		 * -ENOENT just means we couldn't find a cached parent, but we do have dir leasing,
+		 * so try requesting a level II oplock for the child path.
+		 */
+		if ((!rc || rc == -ENOENT) && *oplock == SMB2_OPLOCK_LEVEL_NONE)
+			*oplock = SMB2_OPLOCK_LEVEL_II;
+
+		if (*oplock != SMB2_OPLOCK_LEVEL_NONE)
+			rc = add_lease_context(server, req, iov, &n_iov, oparms->fid->lease_key,
+					       oplock, oparms->fid->parent_lease_key,
+					       oparms->lease_flags);
 		if (rc)
 			return rc;
 	}
-- 
2.49.0


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

* [PATCH 13/20] smb: client: actually use cached dirs on readdir
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
                   ` (11 preceding siblings ...)
  2025-09-29 13:27 ` [PATCH 12/20] smb: client: prevent lease breaks of cached parents when opening children Enzo Matsumiya
@ 2025-09-29 13:27 ` Enzo Matsumiya
  2025-10-03 17:26   ` Dan Carpenter
  2025-09-29 13:27 ` [PATCH 14/20] smb: client: wait for concurrent caching of dirents in cifs_readdir() Enzo Matsumiya
                   ` (7 subsequent siblings)
  20 siblings, 1 reply; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:27 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

Currently, even when we have a valid cached dir, cifs_readdir will not
make use of it for the Find request, and will reopen a separate handle
in query_dir_first.

Fix this by setting cifsFile->fid to cfid->fid and resetting search info
parameters.  Also add cifs_search_info->reset_scan to indicate
SMB2_query_directory_init to include the SMB2_RESTART_SCANS flag.

With this, we use query_dir_next directly instead of query_dir_first.

This patch also keeps the cfid reference all through cifs_readdir().
To prevent bogus/invalid usage of it, check if cfid is still valid after
each possible network call (i.e. where possible reconnects may have
happened).

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cifsglob.h  |  1 +
 fs/smb/client/readdir.c   | 71 +++++++++++++++++++++++----------------
 fs/smb/client/smb2ops.c   |  2 +-
 fs/smb/client/smb2pdu.c   | 11 +++---
 fs/smb/client/smb2proto.h |  2 +-
 5 files changed, 51 insertions(+), 36 deletions(-)

diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index 0fae95cf81c4..9a86efddc3c4 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -1434,6 +1434,7 @@ struct cifs_search_info {
 	bool emptyDir:1;
 	bool unicode:1;
 	bool smallBuf:1; /* so we know which buf_release function to call */
+	bool restart_scan;
 };
 
 #define ACL_NO_MODE	((umode_t)(-1))
diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c
index cc6762d950d2..0b2efd680fe6 100644
--- a/fs/smb/client/readdir.c
+++ b/fs/smb/client/readdir.c
@@ -344,7 +344,7 @@ cifs_std_info_to_fattr(struct cifs_fattr *fattr, FIND_FILE_STANDARD_INFO *info,
 
 static int
 _initiate_cifs_search(const unsigned int xid, struct file *file,
-		     const char *full_path)
+		     const char *full_path, struct cached_fid *cfid)
 {
 	__u16 search_flags;
 	int rc = 0;
@@ -374,7 +374,6 @@ _initiate_cifs_search(const unsigned int xid, struct file *file,
 	}
 
 	server = tcon->ses->server;
-
 	if (!server->ops->query_dir_first) {
 		rc = -ENOSYS;
 		goto error_exit;
@@ -382,6 +381,13 @@ _initiate_cifs_search(const unsigned int xid, struct file *file,
 
 	cifsFile->invalidHandle = true;
 	cifsFile->srch_inf.endOfSearch = false;
+	if (cfid) {
+		cifsFile->srch_inf.restart_scan = true;
+		cifsFile->srch_inf.entries_in_buffer = 0;
+		cifsFile->srch_inf.index_of_last_entry = 2;
+		cifsFile->fid.persistent_fid = cfid->fid.persistent_fid;
+		cifsFile->fid.volatile_fid = cfid->fid.volatile_fid;
+	}
 
 	cifs_dbg(FYI, "Full path: %s start at: %lld\n", full_path, file->f_pos);
 
@@ -406,12 +412,16 @@ _initiate_cifs_search(const unsigned int xid, struct file *file,
 	if (backup_cred(cifs_sb))
 		search_flags |= CIFS_SEARCH_BACKUP_SEARCH;
 
-	rc = server->ops->query_dir_first(xid, tcon, full_path, cifs_sb,
-					  &cifsFile->fid, search_flags,
-					  &cifsFile->srch_inf);
-
+	if (cfid)
+		rc = server->ops->query_dir_next(xid, tcon, &cifsFile->fid, 0, &cifsFile->srch_inf);
+	else
+		rc = server->ops->query_dir_first(xid, tcon, full_path, cifs_sb, &cifsFile->fid,
+						  search_flags, &cifsFile->srch_inf);
 	if (rc == 0) {
-		cifsFile->invalidHandle = false;
+		if (cfid)
+			cifsFile->srch_inf.restart_scan = false;
+		else
+			cifsFile->invalidHandle = false;
 	} else if ((rc == -EOPNOTSUPP) &&
 		   (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM)) {
 		cifs_autodisable_serverino(cifs_sb);
@@ -424,12 +434,12 @@ _initiate_cifs_search(const unsigned int xid, struct file *file,
 
 static int
 initiate_cifs_search(const unsigned int xid, struct file *file,
-		     const char *full_path)
+		     const char *full_path, struct cached_fid *cfid)
 {
 	int rc, retry_count = 0;
 
 	do {
-		rc = _initiate_cifs_search(xid, file, full_path);
+		rc = _initiate_cifs_search(xid, file, full_path, cfid);
 		/*
 		 * If we don't have enough credits to start reading the
 		 * directory just try again after short wait.
@@ -683,7 +693,7 @@ static int cifs_save_resume_key(const char *current_entry,
 static int
 find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos,
 		struct file *file, const char *full_path,
-		char **current_entry, int *num_to_ret)
+		char **current_entry, int *num_to_ret, struct cached_fid *cfid)
 {
 	__u16 search_flags;
 	int rc = 0;
@@ -739,7 +749,7 @@ find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos,
 			cfile->srch_inf.srch_entries_start = NULL;
 			cfile->srch_inf.last_entry = NULL;
 		}
-		rc = initiate_cifs_search(xid, file, full_path);
+		rc = initiate_cifs_search(xid, file, full_path, cfid);
 		if (rc) {
 			cifs_dbg(FYI, "error %d reinitiating a search on rewind\n",
 				 rc);
@@ -1028,7 +1038,6 @@ static int cifs_filldir(char *find_entry, struct file *file,
 			      &fattr, cfid, file);
 }
 
-
 int cifs_readdir(struct file *file, struct dir_context *ctx)
 {
 	int rc = 0;
@@ -1036,7 +1045,7 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
 	int i;
 	struct tcon_link *tlink = NULL;
 	struct cifs_tcon *tcon;
-	struct cifsFileInfo *cifsFile;
+	struct cifsFileInfo *cifsFile = NULL;
 	char *current_entry;
 	int num_to_fill = 0;
 	char *tmp_buf = NULL;
@@ -1095,23 +1104,22 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
 	}
 	mutex_unlock(&cfid->dirents.de_mutex);
 
-	/* Drop the cache while calling initiate_cifs_search and
-	 * find_cifs_entry in case there will be reconnects during
-	 * query_directory.
-	 */
-	close_cached_dir(cfid);
-	cfid = NULL;
-
- cache_not_found:
+	/* keep our cfid ref, but check if still valid after network calls */
+cache_not_found:
 	/*
 	 * Ensure FindFirst doesn't fail before doing filldir() for '.' and
 	 * '..'. Otherwise we won't be able to notify VFS in case of failure.
 	 */
 	if (file->private_data == NULL) {
-		rc = initiate_cifs_search(xid, file, full_path);
+		rc = initiate_cifs_search(xid, file, full_path, cfid);
 		cifs_dbg(FYI, "initiate cifs search rc %d\n", rc);
 		if (rc)
 			goto rddir2_exit;
+
+		if (tcon->status != TID_GOOD || (cfid && !cfid_is_valid(cfid))) {
+			rc = -ENOENT;
+			goto rddir2_exit;
+		}
 	}
 
 	if (!dir_emit_dots(file, ctx))
@@ -1128,15 +1136,17 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
 			rc = 0;
 			goto rddir2_exit;
 		}
-	} /* else {
-		cifsFile->invalidHandle = true;
-		tcon->ses->server->close(xid, tcon, &cifsFile->fid);
-	} */
+	}
 
 	tcon = tlink_tcon(cifsFile->tlink);
 	rc = find_cifs_entry(xid, tcon, ctx->pos, file, full_path,
-			     &current_entry, &num_to_fill);
-	open_cached_dir(xid, tcon, full_path, cifs_sb, &cfid);
+			     &current_entry, &num_to_fill, cfid);
+
+	if (tcon->status != TID_GOOD || (cfid && !cfid_is_valid(cfid))) {
+		rc = -ENOENT;
+		goto rddir2_exit;
+	}
+
 	if (rc) {
 		cifs_dbg(FYI, "fce error %d\n", rc);
 		goto rddir2_exit;
@@ -1204,8 +1214,11 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
 	kfree(tmp_buf);
 
 rddir2_exit:
-	if (cfid)
+	if (cfid) {
+		if (cifsFile)
+			cifsFile->invalidHandle = true;
 		close_cached_dir(cfid);
+	}
 	free_dentry_path(page);
 	free_xid(xid);
 	return rc;
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index 39e6dc13d2da..8842315d2526 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -2397,7 +2397,7 @@ smb2_query_dir_first(const unsigned int xid, struct cifs_tcon *tcon,
 	rc = SMB2_query_directory_init(xid, tcon, server,
 				       &rqst[1],
 				       COMPOUND_FID, COMPOUND_FID,
-				       0, srch_inf->info_level);
+				       0, srch_inf);
 	if (rc)
 		goto qdf_free;
 
diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index 2474ac18b85e..cc7251f4e3a6 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -5418,7 +5418,7 @@ int SMB2_query_directory_init(const unsigned int xid,
 			      struct TCP_Server_Info *server,
 			      struct smb_rqst *rqst,
 			      u64 persistent_fid, u64 volatile_fid,
-			      int index, int info_level)
+			      int index, struct cifs_search_info *search)
 {
 	struct smb2_query_directory_req *req;
 	unsigned char *bufptr;
@@ -5435,7 +5435,7 @@ int SMB2_query_directory_init(const unsigned int xid,
 	if (rc)
 		return rc;
 
-	switch (info_level) {
+	switch (search->info_level) {
 	case SMB_FIND_FILE_DIRECTORY_INFO:
 		req->FileInformationClass = FILE_DIRECTORY_INFORMATION;
 		break;
@@ -5449,14 +5449,15 @@ int SMB2_query_directory_init(const unsigned int xid,
 		req->FileInformationClass = FILE_FULL_DIRECTORY_INFORMATION;
 		break;
 	default:
-		cifs_tcon_dbg(VFS, "info level %u isn't supported\n",
-			info_level);
+		cifs_tcon_dbg(VFS, "info level %u isn't supported\n", search->info_level);
 		return -EINVAL;
 	}
 
 	req->FileIndex = cpu_to_le32(index);
 	req->PersistentFileId = persistent_fid;
 	req->VolatileFileId = volatile_fid;
+	if (search->restart_scan)
+		req->Flags |= SMB2_RESTART_SCANS;
 
 	len = 0x2;
 	bufptr = req->Buffer;
@@ -5603,7 +5604,7 @@ SMB2_query_directory(const unsigned int xid, struct cifs_tcon *tcon,
 	rc = SMB2_query_directory_init(xid, tcon, server,
 				       &rqst, persistent_fid,
 				       volatile_fid, index,
-				       srch_inf->info_level);
+				       srch_inf);
 	if (rc)
 		goto qdir_exit;
 
diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h
index b3f1398c9f79..ac6db1f18b5b 100644
--- a/fs/smb/client/smb2proto.h
+++ b/fs/smb/client/smb2proto.h
@@ -231,7 +231,7 @@ extern int SMB2_query_directory_init(unsigned int xid, struct cifs_tcon *tcon,
 				     struct TCP_Server_Info *server,
 				     struct smb_rqst *rqst,
 				     u64 persistent_fid, u64 volatile_fid,
-				     int index, int info_level);
+				     int index, struct cifs_search_info *search);
 extern void SMB2_query_directory_free(struct smb_rqst *rqst);
 extern int SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon,
 			u64 persistent_fid, u64 volatile_fid, u32 pid,
-- 
2.49.0


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

* [PATCH 14/20] smb: client: wait for concurrent caching of dirents in cifs_readdir()
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
                   ` (12 preceding siblings ...)
  2025-09-29 13:27 ` [PATCH 13/20] smb: client: actually use cached dirs on readdir Enzo Matsumiya
@ 2025-09-29 13:27 ` Enzo Matsumiya
  2025-09-29 13:28 ` [PATCH 15/20] smb: client: remove cached_dirent->fattr Enzo Matsumiya
                   ` (6 subsequent siblings)
  20 siblings, 0 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:27 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

The file struct passed down to cifs_readdir() is a stack variable, which
means it makes no sense to keep/track it across a cached dir lifetime.

Instead, use it to track concurrent accesses to the same cached path,
and wait for the previous one to finish filling/emitting.

Without this patch, virtually every 'ls' will issue a Find request,
even when we have both directory and dirents cached and valid.

With this patch, the chances of cache hits increases a lot, so on
highly concurrent scenarios, the amount of network calls are
drastically reduced.

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cached_dir.c |  3 ++
 fs/smb/client/readdir.c    | 66 +++++++++++++++++++++-----------------
 2 files changed, 40 insertions(+), 29 deletions(-)

diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index 9dd74268b2d8..ad455c067cba 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -595,6 +595,9 @@ static struct cached_fid *init_cached_dir(const char *path)
 	/* this is caller/lease ref */
 	kref_get(&cfid->refcount);
 
+	/* initial cached dirents position */
+	cfid->dirents.pos = 2;
+
 	return cfid;
 }
 
diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c
index 0b2efd680fe6..903919345df1 100644
--- a/fs/smb/client/readdir.c
+++ b/fs/smb/client/readdir.c
@@ -860,22 +860,16 @@ static bool emit_cached_dirents(struct cached_dirents *cde,
 	return true;
 }
 
-static void update_cached_dirents_count(struct cached_dirents *cde,
-					struct file *file)
+static void update_cached_dirents_count(struct cached_dirents *cde)
 {
-	if (cde->file != file)
-		return;
 	if (cde->is_valid || cde->is_failed)
 		return;
 
 	cde->pos++;
 }
 
-static void finished_cached_dirents_count(struct cached_dirents *cde,
-					struct dir_context *ctx, struct file *file)
+static void finished_cached_dirents_count(struct cached_dirents *cde, struct dir_context *ctx)
 {
-	if (cde->file != file)
-		return;
 	if (cde->is_valid || cde->is_failed)
 		return;
 	if (ctx->pos != cde->pos)
@@ -884,16 +878,11 @@ static void finished_cached_dirents_count(struct cached_dirents *cde,
 	cde->is_valid = 1;
 }
 
-static void add_cached_dirent(struct cached_dirents *cde,
-			      struct dir_context *ctx,
-			      const char *name, int namelen,
-			      struct cifs_fattr *fattr,
-				  struct file *file)
+static void add_cached_dirent(struct cached_dirents *cde, struct dir_context *ctx,
+			      const char *name, int namelen, struct cifs_fattr *fattr)
 {
 	struct cached_dirent *de;
 
-	if (cde->file != file)
-		return;
 	if (cde->is_valid || cde->is_failed)
 		return;
 	if (ctx->pos != cde->pos) {
@@ -934,8 +923,7 @@ static bool cifs_dir_emit(struct dir_context *ctx,
 
 	if (cfid) {
 		mutex_lock(&cfid->dirents.de_mutex);
-		add_cached_dirent(&cfid->dirents, ctx, name, namelen,
-				  fattr, file);
+		add_cached_dirent(&cfid->dirents, ctx, name, namelen, fattr);
 		mutex_unlock(&cfid->dirents.de_mutex);
 	}
 
@@ -1076,19 +1064,13 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
 
 	rc = open_cached_dir(xid, tcon, full_path, cifs_sb, &cfid);
 	cifs_put_tlink(tlink);
-	if (rc)
+retry_cached:
+	if (!cfid || !cfid_is_valid(cfid)) {
+		rc = -ENOENT;
 		goto cache_not_found;
+	}
 
 	mutex_lock(&cfid->dirents.de_mutex);
-	/*
-	 * If this was reading from the start of the directory
-	 * we need to initialize scanning and storing the
-	 * directory content.
-	 */
-	if (ctx->pos == 0 && cfid->dirents.file == NULL) {
-		cfid->dirents.file = file;
-		cfid->dirents.pos = 2;
-	}
 	/*
 	 * If we already have the entire directory cached then
 	 * we can just serve the cache.
@@ -1102,10 +1084,32 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
 		mutex_unlock(&cfid->dirents.de_mutex);
 		goto rddir2_exit;
 	}
+
+	if (cfid->dirents.is_failed) {
+		rc = -ENOENT;
+		mutex_unlock(&cfid->dirents.de_mutex);
+		goto cache_not_found;
+	}
+
+	if (!cfid->dirents.file)
+		cfid->dirents.file = file;
+	else if (cfid->dirents.file != file)
+		rc = -EAGAIN;
 	mutex_unlock(&cfid->dirents.de_mutex);
 
+	/* someone else is filling up the dirents for this cfid, wait for them to finish */
+	if (rc == -EAGAIN) {
+		rc = 0;
+		goto retry_cached;
+	}
+
 	/* keep our cfid ref, but check if still valid after network calls */
 cache_not_found:
+	if (rc && cfid) {
+		close_cached_dir(cfid);
+		cfid = NULL;
+	}
+
 	/*
 	 * Ensure FindFirst doesn't fail before doing filldir() for '.' and
 	 * '..'. Otherwise we won't be able to notify VFS in case of failure.
@@ -1155,7 +1159,7 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
 	} else {
 		if (cfid) {
 			mutex_lock(&cfid->dirents.de_mutex);
-			finished_cached_dirents_count(&cfid->dirents, ctx, file);
+			finished_cached_dirents_count(&cfid->dirents, ctx);
 			mutex_unlock(&cfid->dirents.de_mutex);
 		}
 		cifs_dbg(FYI, "Could not find entry\n");
@@ -1196,7 +1200,7 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
 		ctx->pos++;
 		if (cfid) {
 			mutex_lock(&cfid->dirents.de_mutex);
-			update_cached_dirents_count(&cfid->dirents, file);
+			update_cached_dirents_count(&cfid->dirents);
 			mutex_unlock(&cfid->dirents.de_mutex);
 		}
 
@@ -1215,8 +1219,12 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
 
 rddir2_exit:
 	if (cfid) {
+		if (rc || cfid->dirents.is_failed || !cifsFile || cifsFile->srch_inf.endOfSearch)
+			cfid->dirents.file = NULL;
+
 		if (cifsFile)
 			cifsFile->invalidHandle = true;
+
 		close_cached_dir(cfid);
 	}
 	free_dentry_path(page);
-- 
2.49.0


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

* [PATCH 15/20] smb: client: remove cached_dirent->fattr
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
                   ` (13 preceding siblings ...)
  2025-09-29 13:27 ` [PATCH 14/20] smb: client: wait for concurrent caching of dirents in cifs_readdir() Enzo Matsumiya
@ 2025-09-29 13:28 ` Enzo Matsumiya
  2025-09-29 13:28 ` [PATCH 16/20] smb: client: add is_dir argument to query_path_info Enzo Matsumiya
                   ` (5 subsequent siblings)
  20 siblings, 0 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:28 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

Replace with ->unique_id and ->dtype -- the only fields used from
cifs_fattr.

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cached_dir.h | 6 ++++--
 fs/smb/client/readdir.c    | 9 ++++-----
 2 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h
index c45151446049..d5ad10a35ed7 100644
--- a/fs/smb/client/cached_dir.h
+++ b/fs/smb/client/cached_dir.h
@@ -8,13 +8,15 @@
 #ifndef _CACHED_DIR_H
 #define _CACHED_DIR_H
 
-
 struct cached_dirent {
 	struct list_head entry;
 	char *name;
 	int namelen;
 	loff_t pos;
-	struct cifs_fattr fattr;
+
+	/* filled from cifs_fattr */
+	u64 unique_id;
+	unsigned int dtype;
 };
 
 struct cached_dirents {
diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c
index 903919345df1..4b6e2632e8ed 100644
--- a/fs/smb/client/readdir.c
+++ b/fs/smb/client/readdir.c
@@ -850,9 +850,7 @@ static bool emit_cached_dirents(struct cached_dirents *cde,
 		 * initial scan.
 		 */
 		ctx->pos = dirent->pos;
-		rc = dir_emit(ctx, dirent->name, dirent->namelen,
-			      dirent->fattr.cf_uniqueid,
-			      dirent->fattr.cf_dtype);
+		rc = dir_emit(ctx, dirent->name, dirent->namelen, dirent->unique_id, dirent->dtype);
 		if (!rc)
 			return rc;
 		ctx->pos++;
@@ -901,9 +899,10 @@ static void add_cached_dirent(struct cached_dirents *cde, struct dir_context *ct
 		cde->is_failed = 1;
 		return;
 	}
-	de->pos = ctx->pos;
 
-	memcpy(&de->fattr, fattr, sizeof(struct cifs_fattr));
+	de->pos = ctx->pos;
+	de->unique_id = fattr->cf_uniqueid;
+	de->dtype = fattr->cf_dtype;
 
 	list_add_tail(&de->entry, &cde->entries);
 }
-- 
2.49.0


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

* [PATCH 16/20] smb: client: add is_dir argument to query_path_info
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
                   ` (14 preceding siblings ...)
  2025-09-29 13:28 ` [PATCH 15/20] smb: client: remove cached_dirent->fattr Enzo Matsumiya
@ 2025-09-29 13:28 ` Enzo Matsumiya
  2025-10-03 17:20   ` Dan Carpenter
  2025-09-29 13:28 ` [PATCH 17/20] smb: client: use cached dir on queryfs/smb2_compound_op Enzo Matsumiya
                   ` (4 subsequent siblings)
  20 siblings, 1 reply; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:28 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

When we have an inode on upper levels, pass is_dir down to
smb2_query_path_info() so we can lookup for a cached dir there.

Since we now have a possible recursive lookup in open_cached_dir() e.g.:

cifs_readdir
  open_cached_dir
    lookup_noperm_positive_unlocked
    ...
      cifs_d_revalidate
      ...
        smb2_query_path_info
	  find_cached_dir

the cfid must be added to the entries list only after dentry lookup, so
we don't hang on an infinite recursion while waiting for itself to open.

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cached_dir.c | 12 ++++++++----
 fs/smb/client/cifsglob.h   |  2 +-
 fs/smb/client/inode.c      |  9 +++++++--
 fs/smb/client/smb1ops.c    |  4 ++--
 fs/smb/client/smb2inode.c  | 12 ++++++++----
 fs/smb/client/smb2proto.h  |  2 +-
 6 files changed, 27 insertions(+), 14 deletions(-)

diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index ad455c067cba..c9e3e71e3f1f 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -277,12 +277,9 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 	}
 
 	cfid->cfids = cfids;
-	cfids->num_entries++;
-	list_add(&cfid->entry, &cfids->entries);
+	cfid->tcon = tcon;
 	spin_unlock(&cfids->cfid_list_lock);
 
-	pfid = &cfid->fid;
-
 	/*
 	 * Skip any prefix paths in @path as lookup_noperm_positive_unlocked() ends up
 	 * calling ->lookup() which already adds those through
@@ -310,6 +307,13 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 	cfid->tcon = tcon;
 	dentry = NULL;
 
+	spin_lock(&cfids->cfid_list_lock);
+	cfids->num_entries++;
+	list_add(&cfid->entry, &cfids->entries);
+	spin_unlock(&cfids->cfid_list_lock);
+
+	pfid = &cfid->fid;
+
 	/*
 	 * We do not hold the lock for the open because in case
 	 * SMB2_open needs to reconnect.
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index 9a86efddc3c4..08c8131c8018 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -401,7 +401,7 @@ struct smb_version_operations {
 			       struct cifs_tcon *tcon,
 			       struct cifs_sb_info *cifs_sb,
 			       const char *full_path,
-			       struct cifs_open_info_data *data);
+			       struct cifs_open_info_data *data, bool is_dir);
 	/* query file data from the server */
 	int (*query_file_info)(const unsigned int xid, struct cifs_tcon *tcon,
 			       struct cifsFileInfo *cfile, struct cifs_open_info_data *data);
diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c
index f2eff1138ed0..35c5557d5ea1 100644
--- a/fs/smb/client/inode.c
+++ b/fs/smb/client/inode.c
@@ -1283,8 +1283,13 @@ static int cifs_get_fattr(struct cifs_open_info_data *data,
 	 */
 
 	if (!data) {
+		bool is_dir = false;
+
+		if (inode && *inode)
+			is_dir = S_ISDIR((*inode)->i_mode);
+
 		rc = server->ops->query_path_info(xid, tcon, cifs_sb,
-						  full_path, &tmp_data);
+						  full_path, &tmp_data, is_dir);
 		data = &tmp_data;
 	}
 
@@ -1470,7 +1475,7 @@ static int smb311_posix_get_fattr(struct cifs_open_info_data *data,
 	 */
 	if (!data) {
 		rc = server->ops->query_path_info(xid, tcon, cifs_sb,
-						  full_path, &tmp_data);
+						  full_path, &tmp_data, false);
 		data = &tmp_data;
 	}
 
diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c
index a02d41d1ce4a..d964bc9c2823 100644
--- a/fs/smb/client/smb1ops.c
+++ b/fs/smb/client/smb1ops.c
@@ -543,7 +543,7 @@ static int cifs_query_path_info(const unsigned int xid,
 				struct cifs_tcon *tcon,
 				struct cifs_sb_info *cifs_sb,
 				const char *full_path,
-				struct cifs_open_info_data *data)
+				struct cifs_open_info_data *data, bool is_dir)
 {
 	int rc = -EOPNOTSUPP;
 	FILE_ALL_INFO fi = {};
@@ -934,7 +934,7 @@ smb_set_file_info(struct inode *inode, const char *full_path,
 	if (!(tcon->ses->capabilities & CAP_NT_SMBS) &&
 	    (!buf->CreationTime || !buf->LastAccessTime ||
 	     !buf->LastWriteTime || !buf->ChangeTime)) {
-		rc = cifs_query_path_info(xid, tcon, cifs_sb, full_path, &query_data);
+		rc = cifs_query_path_info(xid, tcon, cifs_sb, full_path, &query_data, false);
 		if (rc) {
 			if (open_file) {
 				cifsFileInfo_put(open_file);
diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c
index 6d643b8b9547..280aa033b06a 100644
--- a/fs/smb/client/smb2inode.c
+++ b/fs/smb/client/smb2inode.c
@@ -940,10 +940,9 @@ int smb2_query_path_info(const unsigned int xid,
 			 struct cifs_tcon *tcon,
 			 struct cifs_sb_info *cifs_sb,
 			 const char *full_path,
-			 struct cifs_open_info_data *data)
+			 struct cifs_open_info_data *data, bool is_dir)
 {
 	struct kvec in_iov[3], out_iov[5] = {};
-	struct cached_fid *cfid = NULL;
 	struct cifs_open_parms oparms;
 	struct cifsFileInfo *cfile;
 	__u32 create_options = 0;
@@ -964,12 +963,17 @@ int smb2_query_path_info(const unsigned int xid,
 	 * is fast enough (always using the compounded version).
 	 */
 	if (!tcon->posix_extensions) {
+		struct cached_fid *cfid = NULL;
+
 		rc = -ENOENT;
 		if (!*full_path)
 			rc = open_cached_dir(xid, tcon, full_path, cifs_sb, &cfid);
+		else if (is_dir)
+			cfid = find_cached_dir(tcon->cfids, full_path, CFID_LOOKUP_PATH);
+
+		if (cfid) {
+			rc = 0;
 
-		/* If it is a root and its handle is cached then use it */
-		if (!rc) {
 			if (cfid->file_all_info) {
 				memcpy(&data->fi, cfid->file_all_info, sizeof(data->fi));
 			} else {
diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h
index ac6db1f18b5b..3e3faa7cf633 100644
--- a/fs/smb/client/smb2proto.h
+++ b/fs/smb/client/smb2proto.h
@@ -72,7 +72,7 @@ int smb2_query_path_info(const unsigned int xid,
 			 struct cifs_tcon *tcon,
 			 struct cifs_sb_info *cifs_sb,
 			 const char *full_path,
-			 struct cifs_open_info_data *data);
+			 struct cifs_open_info_data *data, bool is_dir);
 extern int smb2_set_path_size(const unsigned int xid, struct cifs_tcon *tcon,
 			      const char *full_path, __u64 size,
 			      struct cifs_sb_info *cifs_sb, bool set_alloc,
-- 
2.49.0


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

* [PATCH 17/20] smb: client: use cached dir on queryfs/smb2_compound_op
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
                   ` (15 preceding siblings ...)
  2025-09-29 13:28 ` [PATCH 16/20] smb: client: add is_dir argument to query_path_info Enzo Matsumiya
@ 2025-09-29 13:28 ` Enzo Matsumiya
  2025-09-29 14:26   ` Steve French
  2025-09-29 13:28 ` [PATCH 18/20] smb: client: fix dentry revalidation of cached root Enzo Matsumiya
                   ` (3 subsequent siblings)
  20 siblings, 1 reply; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:28 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

A dentry is passed to cifs_statfs(), so pass down d_is_dir() to
smb2_queryfs() so we can cache/reuse this dir.

Other:
- make smb2_compound_op a static function, as it's not used anywhere
  else

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cifsfs.c    |  2 +-
 fs/smb/client/cifsglob.h  |  2 +-
 fs/smb/client/smb1ops.c   |  2 +-
 fs/smb/client/smb2ops.c   | 32 +++++++++++++++++---------------
 fs/smb/client/smb2proto.h |  6 ------
 5 files changed, 20 insertions(+), 24 deletions(-)

diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c
index e1848276bab4..a2ecc5649860 100644
--- a/fs/smb/client/cifsfs.c
+++ b/fs/smb/client/cifsfs.c
@@ -339,7 +339,7 @@ cifs_statfs(struct dentry *dentry, struct kstatfs *buf)
 	buf->f_ffree = 0;	/* unlimited */
 
 	if (server->ops->queryfs)
-		rc = server->ops->queryfs(xid, tcon, full_path, cifs_sb, buf);
+		rc = server->ops->queryfs(xid, tcon, full_path, cifs_sb, buf, d_is_dir(dentry));
 
 statfs_out:
 	free_dentry_path(page);
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index 08c8131c8018..dddac55abd6f 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -519,7 +519,7 @@ struct smb_version_operations {
 			__u16 net_fid, struct cifsInodeInfo *cifs_inode);
 	/* query remote filesystem */
 	int (*queryfs)(const unsigned int, struct cifs_tcon *,
-		       const char *, struct cifs_sb_info *, struct kstatfs *);
+		       const char *, struct cifs_sb_info *, struct kstatfs *, bool);
 	/* send mandatory brlock to the server */
 	int (*mand_lock)(const unsigned int, struct cifsFileInfo *, __u64,
 			 __u64, __u32, int, int, bool);
diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c
index d964bc9c2823..9fa1ff9ea70d 100644
--- a/fs/smb/client/smb1ops.c
+++ b/fs/smb/client/smb1ops.c
@@ -1105,7 +1105,7 @@ cifs_oplock_response(struct cifs_tcon *tcon, __u64 persistent_fid,
 
 static int
 cifs_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
-	     const char *path, struct cifs_sb_info *cifs_sb, struct kstatfs *buf)
+	     const char *path, struct cifs_sb_info *cifs_sb, struct kstatfs *buf, bool is_dir)
 {
 	int rc = -EOPNOTSUPP;
 
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index 8842315d2526..f691271463b5 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -1110,6 +1110,11 @@ move_smb2_ea_to_cifs(char *dst, size_t dst_size,
 	return (ssize_t)rc;
 }
 
+static int smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon,
+				    const char *path, u32 desired_access, u32 class, u32 type,
+				    u32 output_len, struct kvec *rsp, int *buftype,
+				    struct cifs_sb_info *cifs_sb, bool is_dir);
+
 static ssize_t
 smb2_query_eas(const unsigned int xid, struct cifs_tcon *tcon,
 	       const unsigned char *path, const unsigned char *ea_name,
@@ -1129,7 +1134,7 @@ smb2_query_eas(const unsigned int xid, struct cifs_tcon *tcon,
 				      CIFSMaxBufSize -
 				      MAX_SMB2_CREATE_RESPONSE_SIZE -
 				      MAX_SMB2_CLOSE_RESPONSE_SIZE,
-				      &rsp_iov, &buftype, cifs_sb);
+				      &rsp_iov, &buftype, cifs_sb, false);
 	if (rc) {
 		/*
 		 * If ea_name is NULL (listxattr) and there are no EAs,
@@ -1231,7 +1236,7 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon,
 				      CIFSMaxBufSize -
 				      MAX_SMB2_CREATE_RESPONSE_SIZE -
 				      MAX_SMB2_CLOSE_RESPONSE_SIZE,
-				      &rsp_iov[1], &resp_buftype[1], cifs_sb);
+				      &rsp_iov[1], &resp_buftype[1], cifs_sb, false);
 			if (rc == 0) {
 				rsp = (struct smb2_query_info_rsp *)rsp_iov[1].iov_base;
 				used_len = le32_to_cpu(rsp->OutputBufferLength);
@@ -2694,12 +2699,10 @@ bool smb2_should_replay(struct cifs_tcon *tcon,
  * Passes the query info response back to the caller on success.
  * Caller need to free this with free_rsp_buf().
  */
-int
-smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon,
-			 const char *path, u32 desired_access,
-			 u32 class, u32 type, u32 output_len,
-			 struct kvec *rsp, int *buftype,
-			 struct cifs_sb_info *cifs_sb)
+static int smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon,
+				    const char *path, u32 desired_access, u32 class, u32 type,
+				    u32 output_len, struct kvec *rsp, int *buftype,
+				    struct cifs_sb_info *cifs_sb, bool is_dir)
 {
 	struct smb2_compound_vars *vars;
 	struct cifs_ses *ses = tcon->ses;
@@ -2741,9 +2744,9 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon,
 	rsp_iov = vars->rsp_iov;
 
 	/*
-	 * We can only call this for things we know are directories.
+	 * We can only open + cache paths we know are directories.
 	 */
-	if (!strcmp(path, ""))
+	if (is_dir)
 		/* cfid null if open dir failed */
 		open_cached_dir(xid, tcon, path, cifs_sb, &cfid);
 
@@ -2852,7 +2855,7 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon,
 
 static int
 smb2_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
-	     const char *path, struct cifs_sb_info *cifs_sb, struct kstatfs *buf)
+	     const char *path, struct cifs_sb_info *cifs_sb, struct kstatfs *buf, bool is_dir)
 {
 	struct smb2_query_info_rsp *rsp;
 	struct smb2_fs_full_size_info *info = NULL;
@@ -2860,13 +2863,12 @@ smb2_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
 	int buftype = CIFS_NO_BUFFER;
 	int rc;
 
-
 	rc = smb2_query_info_compound(xid, tcon, path,
 				      FILE_READ_ATTRIBUTES,
 				      FS_FULL_SIZE_INFORMATION,
 				      SMB2_O_INFO_FILESYSTEM,
 				      sizeof(struct smb2_fs_full_size_info),
-				      &rsp_iov, &buftype, cifs_sb);
+				      &rsp_iov, &buftype, cifs_sb, is_dir);
 	if (rc)
 		goto qfs_exit;
 
@@ -2889,7 +2891,7 @@ smb2_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
 
 static int
 smb311_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
-	       const char *path, struct cifs_sb_info *cifs_sb, struct kstatfs *buf)
+	       const char *path, struct cifs_sb_info *cifs_sb, struct kstatfs *buf, bool is_dir)
 {
 	int rc;
 	__le16 *utf16_path = NULL;
@@ -2898,7 +2900,7 @@ smb311_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
 	struct cifs_fid fid;
 
 	if (!tcon->posix_extensions)
-		return smb2_queryfs(xid, tcon, path, cifs_sb, buf);
+		return smb2_queryfs(xid, tcon, path, cifs_sb, buf, is_dir);
 
 	oparms = (struct cifs_open_parms) {
 		.tcon = tcon,
diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h
index 3e3faa7cf633..99326810a159 100644
--- a/fs/smb/client/smb2proto.h
+++ b/fs/smb/client/smb2proto.h
@@ -299,12 +299,6 @@ extern int smb311_crypto_shash_allocate(struct TCP_Server_Info *server);
 extern int smb311_update_preauth_hash(struct cifs_ses *ses,
 				      struct TCP_Server_Info *server,
 				      struct kvec *iov, int nvec);
-extern int smb2_query_info_compound(const unsigned int xid,
-				    struct cifs_tcon *tcon,
-				    const char *path, u32 desired_access,
-				    u32 class, u32 type, u32 output_len,
-				    struct kvec *rsp, int *buftype,
-				    struct cifs_sb_info *cifs_sb);
 /* query path info from the server using SMB311 POSIX extensions*/
 int smb311_posix_query_path_info(const unsigned int xid,
 				 struct cifs_tcon *tcon,
-- 
2.49.0


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

* [PATCH 18/20] smb: client: fix dentry revalidation of cached root
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
                   ` (16 preceding siblings ...)
  2025-09-29 13:28 ` [PATCH 17/20] smb: client: use cached dir on queryfs/smb2_compound_op Enzo Matsumiya
@ 2025-09-29 13:28 ` Enzo Matsumiya
  2025-09-29 13:28 ` [PATCH 19/20] smb: client: rework cached dirs synchronization Enzo Matsumiya
                   ` (2 subsequent siblings)
  20 siblings, 0 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:28 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

Don't check root dir dentry in cifs_dentry_needs_reval() as its inode
was created before the cfid, so the time check will fail and trigger an
unnecessary revalidation.

Also account for dir_cache_timeout in time comparison, because if we
have a cached dir, we have a lease for it, and, thus, may assume its
children are (still) valid.  To confirm that, let the ac*max checks
go through for granular results.

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/inode.c | 26 ++++++++++++++++++++++----
 1 file changed, 22 insertions(+), 4 deletions(-)

diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c
index 35c5557d5ea1..4b817fd528f7 100644
--- a/fs/smb/client/inode.c
+++ b/fs/smb/client/inode.c
@@ -2694,10 +2694,28 @@ cifs_dentry_needs_reval(struct dentry *dentry)
 	if (!lookupCacheEnabled)
 		return true;
 
-	cfid = find_cached_dir(tcon->cfids, dentry->d_parent, CFID_LOOKUP_DENTRY);
-	if (cfid) {
-		close_cached_dir(cfid);
-		return false;
+	if (!IS_ROOT(dentry)) {
+		cfid = find_cached_dir(tcon->cfids, dentry->d_parent, CFID_LOOKUP_DENTRY);
+		if (cfid) {
+			/*
+			 * We hold a lease for the cached parent.
+			 * So as long as this child is within cached dir lifetime, we don't need to
+			 * revalidate it.
+			 *
+			 * Since cfid expiration is based on access time, use it for comparison
+			 * instead of creation time.
+			 */
+			if (time_before(cifs_i->time, cfid->atime - dir_cache_timeout * HZ)) {
+				close_cached_dir(cfid);
+				return true;
+			}
+
+			/*
+			 * From cached dir perspective, we're done -- attr caching (ac*max) may
+			 * have different requirements, so let the checks go through.
+			 */
+			close_cached_dir(cfid);
+		}
 	}
 
 	/*
-- 
2.49.0


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

* [PATCH 19/20] smb: client: rework cached dirs synchronization
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
                   ` (17 preceding siblings ...)
  2025-09-29 13:28 ` [PATCH 18/20] smb: client: fix dentry revalidation of cached root Enzo Matsumiya
@ 2025-09-29 13:28 ` Enzo Matsumiya
  2025-09-30 19:02   ` kernel test robot
  2025-09-29 13:28 ` [PATCH 20/20] smb: client: cleanup open_cached_dir() Enzo Matsumiya
  2025-09-29 14:05 ` [PATCH 00/20] smb: client: cached dir fixes and improvements Steve French
  20 siblings, 1 reply; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:28 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

This patch adds usage of RCU and seqlocks for cached dir (list and
entries).

Traversing the list under RCU allows faster lookups (no locks) and also
guarantees that entries being read are not gone (i.e. prevents UAF).

seqlocks provides atomicity/consistency when reading entries, allowing
callers to re-check cfid in case of write-side invalidation.

Combined with refcounting, this new approach provides safety for
callers and flexibility for future enhancements.

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cached_dir.c | 269 ++++++++++++++++++++++++-------------
 fs/smb/client/cached_dir.h |  19 ++-
 fs/smb/client/cifs_debug.c |   5 +-
 3 files changed, 193 insertions(+), 100 deletions(-)

diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index c9e3e71e3f1f..6d9a06ede476 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -16,12 +16,12 @@
 #define CFID_INVALID_TIME 1
 
 static struct cached_fid *init_cached_dir(const char *path);
-static void smb2_close_cached_fid(struct kref *ref);
+static void cfid_release_ref(struct kref *ref);
 
 static inline void invalidate_cfid(struct cached_fid *cfid)
 {
 	/* callers must hold the list lock and do any list operations (del/move) themselves */
-	lockdep_assert_held(&cfid->cfids->cfid_list_lock);
+	lockdep_assert_held(&cfid->cfids->entries_seqlock.lock);
 
 	if (cfid_is_valid(cfid))
 		cfid->cfids->num_entries--;
@@ -31,20 +31,19 @@ static inline void invalidate_cfid(struct cached_fid *cfid)
 	cfid->atime = CFID_INVALID_TIME;
 }
 
-static inline void drop_cfid(struct cached_fid *cfid)
+static inline void __drop_cfid(struct cached_fid *cfid)
 {
 	struct dentry *dentry = NULL;
 	u64 pfid = 0, vfid = 0;
 
-	spin_lock(&cfid->cfids->cfid_list_lock);
+	write_seqlock(&cfid->cfids->entries_seqlock);
+	write_seqlock(&cfid->seqlock);
 	invalidate_cfid(cfid);
-
-	spin_lock(&cfid->fid_lock);
 	swap(cfid->dentry, dentry);
 	swap(cfid->fid.persistent_fid, pfid);
 	swap(cfid->fid.volatile_fid, vfid);
-	spin_unlock(&cfid->fid_lock);
-	spin_unlock(&cfid->cfids->cfid_list_lock);
+	write_sequnlock(&cfid->seqlock);
+	write_sequnlock(&cfid->cfids->entries_seqlock);
 
 	dput(dentry);
 
@@ -58,11 +57,18 @@ static inline void drop_cfid(struct cached_fid *cfid)
 	}
 }
 
+static inline void drop_cfid(struct cached_fid *cfid)
+{
+	__drop_cfid(cfid);
+	kref_put(&cfid->refcount, cfid_release_ref);
+}
+
 /*
  * Find a cached dir based on @key and @mode (raw lookup).
  * The only validation done here is if cfid is going down (->ctime == CFID_INVALID_TIME).
  *
  * If @wait_open is true, keep retrying until cfid transitions from 'opening' to valid/invalid.
+ * Will also keep retrying on list seqcount invalidations.
  *
  * Callers must handle any other validation as needed.
  * Returned cfid, if found, has a ref taken, regardless of state.
@@ -70,51 +76,94 @@ static inline void drop_cfid(struct cached_fid *cfid)
 static struct cached_fid *find_cfid(struct cached_fids *cfids, const void *key, int mode,
 				    bool wait_open)
 {
-	struct cached_fid *cfid, *found;
-	bool match;
+	struct cached_fid *cfid;
+	unsigned int lseq = 0;
+	int ret;
 
 	if (!cfids || !key)
 		return NULL;
 
 retry_find:
-	found = NULL;
+	ret = -ENOENT;
 
-	spin_lock(&cfids->cfid_list_lock);
-	list_for_each_entry(cfid, &cfids->entries, entry) {
+	rcu_read_lock();
+	lseq = read_seqbegin(&cfids->entries_seqlock);
+	list_for_each_entry_rcu(cfid, &cfids->entries, entry) {
+		if (need_seqretry(&cfids->entries_seqlock, lseq)) {
+			ret = -ECHILD;
+			break;
+		}
+
+		ret = -ENOENT;
 		/* don't even bother checking if it's going away */
 		if (cfid->ctime == CFID_INVALID_TIME)
 			continue;
 
 		if (mode == CFID_LOOKUP_PATH)
-			match = !strcmp(cfid->path, (char *)key);
+			ret = strcmp(cfid->path, (char *)key);
 
 		if (mode == CFID_LOOKUP_DENTRY)
-			match = (cfid->dentry == key);
+			ret = (cfid->dentry != key);
 
 		if (mode == CFID_LOOKUP_LEASEKEY)
-			match = !memcmp(cfid->fid.lease_key, (u8 *)key, SMB2_LEASE_KEY_SIZE);
+			ret = memcmp(cfid->fid.lease_key, (u8 *)key, SMB2_LEASE_KEY_SIZE);
 
-		if (!match)
+		if (ret) {
+			ret = -ENOENT;
 			continue;
+		}
+
+		if (wait_open && !cfid->ctime) {
+			unsigned int cseq = read_seqbegin(&cfid->seqlock);
+
+			if (!cfid->ctime)
+				ret = -ECHILD;
+			else if (!cfid_is_valid(cfid))
+				ret = -EINVAL;
+
+			if (read_seqretry(&cfid->seqlock, cseq) && !ret)
+				ret = -ECHILD;
 
-		/* only get a ref here if not waiting for open */
-		if (!wait_open)
-			kref_get(&cfid->refcount);
-		found = cfid;
+			if (ret)
+				break;
+		}
+
+		kref_get(&cfid->refcount);
 		break;
 	}
-	spin_unlock(&cfids->cfid_list_lock);
 
-	if (wait_open && found) {
-		/* cfid is being opened in open_cached_dir(), retry lookup */
-		if (!found->ctime)
-			goto retry_find;
+	if (read_seqretry(&cfids->entries_seqlock, lseq)) {
+		if (wait_open) {
+			if (ret == -ENOENT) {
+				ret = -ECHILD;
+
+				/*
+				 * Not found but caller requested wait for open.
+				 * The list seqcount invalidation might have been our open, retry
+				 * only once more (in case it wasn't).
+				 */
+				wait_open = false;
+			}
+		}
 
-		/* we didn't get a ref above, so get one now */
-		kref_get(&found->refcount);
+		if (!ret)
+			ret = -EUCLEAN;
 	}
+	rcu_read_unlock();
 
-	return found;
+	if (!ret)
+		return cfid;
+
+	if (ret == -EUCLEAN) {
+		kref_put(&cfid->refcount, cfid_release_ref);
+		ret = -ECHILD;
+	}
+
+	if (ret == -ECHILD)
+		goto retry_find;
+
+	/* -EINVAL or -ENOENT */
+	return NULL;
 }
 
 static struct dentry *
@@ -191,7 +240,7 @@ struct cached_fid *find_cached_dir(struct cached_fids *cfids, const void *key, i
 		if (cfid_is_valid(cfid)) {
 			cfid->atime = jiffies;
 		} else {
-			kref_put(&cfid->refcount, smb2_close_cached_fid);
+			kref_put(&cfid->refcount, cfid_release_ref);
 			cfid = NULL;
 		}
 	}
@@ -222,7 +271,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 	u8 oplock = SMB2_OPLOCK_LEVEL_II;
 	struct cifs_fid *pfid;
 	struct dentry *dentry;
-	struct cached_fid *cfid;
+	struct cached_fid __rcu *cfid;
 	struct cached_fids *cfids;
 	const char *npath;
 	int retries = 0, cur_sleep = 1;
@@ -251,34 +300,35 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 	if (!server->ops->new_lease_key)
 		return -EIO;
 
-	utf16_path = cifs_convert_path_to_utf16(path, cifs_sb);
-	if (!utf16_path)
-		return -ENOMEM;
-
 	/* find_cached_dir() already validates cfid if found, so no need to check here again */
 	cfid = find_cached_dir(cfids, path, CFID_LOOKUP_PATH);
 	if (cfid) {
-		rc = 0;
-		goto out;
+		*ret_cfid = cfid;
+		return 0;
 	}
 
-	spin_lock(&cfids->cfid_list_lock);
+	utf16_path = cifs_convert_path_to_utf16(path, cifs_sb);
+	if (!utf16_path)
+		return -ENOMEM;
+
+	read_seqlock_excl(&cfids->entries_seqlock);
 	if (cfids->num_entries >= tcon->max_cached_dirs) {
-		spin_unlock(&cfids->cfid_list_lock);
+		read_sequnlock_excl(&cfids->entries_seqlock);
 		rc = -ENOENT;
 		goto out;
 	}
+	read_sequnlock_excl(&cfids->entries_seqlock);
 
+	/* no ned to lock cfid or entries yet */
 	cfid = init_cached_dir(path);
 	if (!cfid) {
-		spin_unlock(&cfids->cfid_list_lock);
 		rc = -ENOMEM;
 		goto out;
 	}
 
 	cfid->cfids = cfids;
 	cfid->tcon = tcon;
-	spin_unlock(&cfids->cfid_list_lock);
+	pfid = &cfid->fid;
 
 	/*
 	 * Skip any prefix paths in @path as lookup_noperm_positive_unlocked() ends up
@@ -303,16 +353,11 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 			goto out;
 		}
 	}
-	cfid->dentry = dentry;
-	cfid->tcon = tcon;
-	dentry = NULL;
 
-	spin_lock(&cfids->cfid_list_lock);
+	write_seqlock(&cfids->entries_seqlock);
 	cfids->num_entries++;
-	list_add(&cfid->entry, &cfids->entries);
-	spin_unlock(&cfids->cfid_list_lock);
-
-	pfid = &cfid->fid;
+	list_add_rcu(&cfid->entry, &cfids->entries);
+	write_sequnlock(&cfids->entries_seqlock);
 
 	/*
 	 * We do not hold the lock for the open because in case
@@ -378,8 +423,6 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 		goto oshr_free;
 	}
 
-	spin_lock(&cfids->cfid_list_lock);
-
 	o_rsp = (struct smb2_create_rsp *)rsp_iov[0].iov_base;
 	oparms.fid->persistent_fid = o_rsp->PersistentFileId;
 	oparms.fid->volatile_fid = o_rsp->VolatileFileId;
@@ -389,30 +432,23 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 
 
 	if (o_rsp->OplockLevel != SMB2_OPLOCK_LEVEL_LEASE) {
-		spin_unlock(&cfids->cfid_list_lock);
 		rc = -EINVAL;
 		goto oshr_free;
 	}
 
-	rc = smb2_parse_contexts(server, rsp_iov,
-				 &oparms.fid->epoch,
-				 oparms.fid->lease_key,
+	rc = smb2_parse_contexts(server, rsp_iov, &oparms.fid->epoch, oparms.fid->lease_key,
 				 &oplock, NULL, NULL);
-	if (rc) {
-		spin_unlock(&cfids->cfid_list_lock);
+	if (rc)
 		goto oshr_free;
-	}
 
 	rc = -EINVAL;
-	if (!(oplock & SMB2_LEASE_READ_CACHING_HE)) {
-		spin_unlock(&cfids->cfid_list_lock);
+	if (!(oplock & SMB2_LEASE_READ_CACHING_HE))
 		goto oshr_free;
-	}
+
 	qi_rsp = (struct smb2_query_info_rsp *)rsp_iov[1].iov_base;
-	if (le32_to_cpu(qi_rsp->OutputBufferLength) < sizeof(struct smb2_file_all_info)) {
-		spin_unlock(&cfids->cfid_list_lock);
+	if (le32_to_cpu(qi_rsp->OutputBufferLength) < sizeof(struct smb2_file_all_info))
 		goto oshr_free;
-	}
+
 	if (!smb2_validate_and_copy_iov(le16_to_cpu(qi_rsp->OutputBufferOffset),
 					sizeof(struct smb2_file_all_info), &rsp_iov[1],
 					sizeof(struct smb2_file_all_info), (char *)&info)) {
@@ -423,10 +459,6 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 		}
 	}
 
-	cfid->ctime = jiffies;
-	cfid->atime = jiffies;
-	spin_unlock(&cfids->cfid_list_lock);
-	/* At this point the directory handle is fully cached */
 	rc = 0;
 oshr_free:
 	SMB2_open_free(&rqst[0]);
@@ -434,16 +466,23 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 	free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base);
 	free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base);
 out:
-	/* cfid invalidated in the mean time, drop it below */
-	if (!rc && !cfid_is_valid(cfid))
+	/* cfid only becomes fully valid below, so can't use cfid_is_valid() here */
+	if (!rc && cfid->ctime == CFID_INVALID_TIME)
 		rc = -ENOENT;
 
 	if (rc) {
-		if (cfid) {
+		dput(dentry);
+
+		if (cfid)
 			drop_cfid(cfid);
-			kref_put(&cfid->refcount, smb2_close_cached_fid);
-		}
 	} else {
+		/* seqlocked-write will inform concurrent lookups of opening -> open transition */
+		write_seqlock(&cfid->seqlock);
+		cfid->dentry = dentry;
+		cfid->ctime = jiffies;
+		cfid->atime = jiffies;
+		write_sequnlock(&cfid->seqlock);
+
 		*ret_cfid = cfid;
 		atomic_inc(&tcon->num_remote_opens);
 	}
@@ -456,13 +495,11 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 	return rc;
 }
 
-static void smb2_close_cached_fid(struct kref *ref)
+static void cfid_rcu_free(struct rcu_head *rcu)
 {
-	struct cached_fid *cfid = container_of(ref, struct cached_fid, refcount);
+	struct cached_fid *cfid = container_of(rcu, struct cached_fid, rcu);
 	struct cached_dirent *de, *q;
 
-	drop_cfid(cfid);
-
 	/* Delete all cached dirent names */
 	list_for_each_entry_safe(de, q, &cfid->dirents.entries, entry) {
 		list_del(&de->entry);
@@ -477,6 +514,14 @@ static void smb2_close_cached_fid(struct kref *ref)
 	kfree(cfid);
 }
 
+static void cfid_release_ref(struct kref *ref)
+{
+	struct cached_fid *cfid = container_of(ref, struct cached_fid, refcount);
+
+	__drop_cfid(cfid);
+	call_rcu_hurry(&cfid->rcu, cfid_rcu_free);
+}
+
 bool drop_cached_dir(struct cached_fids *cfids, const void *key, int mode)
 {
 	struct cached_fid *cfid;
@@ -497,13 +542,16 @@ bool drop_cached_dir(struct cached_fids *cfids, const void *key, int mode)
 		drop_cfid(cfid);
 	} else {
 		/* we're locked in smb2_is_valid_lease_break(), so can't dput/close here */
-		spin_lock(&cfids->cfid_list_lock);
+		write_seqlock(&cfids->entries_seqlock);
+		write_seqlock(&cfid->seqlock);
 		invalidate_cfid(cfid);
-		spin_unlock(&cfids->cfid_list_lock);
+		write_sequnlock(&cfid->seqlock);
+		write_sequnlock(&cfids->entries_seqlock);
+
+		/* put lookup ref */
+		kref_put(&cfid->refcount, cfid_release_ref);
 	}
 
-	/* put lookup ref */
-	kref_put(&cfid->refcount, smb2_close_cached_fid);
 	mod_delayed_work(cfid_put_wq, &cfids->laundromat_work, 0);
 
 	return true;
@@ -511,26 +559,28 @@ bool drop_cached_dir(struct cached_fids *cfids, const void *key, int mode)
 
 void close_cached_dir(struct cached_fid *cfid)
 {
-	kref_put(&cfid->refcount, smb2_close_cached_fid);
+	kref_put(&cfid->refcount, cfid_release_ref);
 }
 
 static void invalidate_all_cfids(struct cached_fids *cfids, bool closed)
 {
-	struct cached_fid *cfid, *q;
+	struct cached_fid *cfid;
 
 	if (!cfids)
 		return;
 
 	/* mark all the cfids as closed and invalidate them for laundromat cleanup */
-	spin_lock(&cfids->cfid_list_lock);
-	list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {
+	write_seqlock(&cfids->entries_seqlock);
+	list_for_each_entry(cfid, &cfids->entries, entry) {
+		write_seqlock(&cfid->seqlock);
 		invalidate_cfid(cfid);
 		if (closed) {
 			cfid->fid.persistent_fid = 0;
 			cfid->fid.volatile_fid = 0;
 		}
+		write_sequnlock(&cfid->seqlock);
 	}
-	spin_unlock(&cfids->cfid_list_lock);
+	write_sequnlock(&cfids->entries_seqlock);
 
 	/* run laundromat unconditionally now as there might have been previously queued work */
 	mod_delayed_work(cfid_put_wq, &cfids->laundromat_work, 0);
@@ -570,6 +620,8 @@ void invalidate_all_cached_dirs(struct cached_fids *cfids)
 	if (!cfids)
 		return;
 
+	synchronize_rcu();
+
 	invalidate_all_cfids(cfids, true);
 	flush_delayed_work(&cfids->laundromat_work);
 }
@@ -591,7 +643,7 @@ static struct cached_fid *init_cached_dir(const char *path)
 	INIT_LIST_HEAD(&cfid->entry);
 	INIT_LIST_HEAD(&cfid->dirents.entries);
 	mutex_init(&cfid->dirents.de_mutex);
-	spin_lock_init(&cfid->fid_lock);
+	seqlock_init(&cfid->seqlock);
 
 	/* this is our ref */
 	kref_init(&cfid->refcount);
@@ -607,20 +659,51 @@ static struct cached_fid *init_cached_dir(const char *path)
 
 static void cfids_laundromat_worker(struct work_struct *work)
 {
-	struct cached_fids *cfids;
+	unsigned int lseq, cseq, done = 1;
 	struct cached_fid *cfid, *q;
+	struct cached_fids *cfids;
 	LIST_HEAD(entry);
 
 	cfids = container_of(work, struct cached_fids, laundromat_work.work);
 
-	spin_lock(&cfids->cfid_list_lock);
+	rcu_read_lock();
+	/* RCU-seqcounted read first so we avoid unnecessary seqcount invalidations */
+	lseq = read_seqbegin(&cfids->entries_seqlock);
+	list_for_each_entry_rcu(cfid, &cfids->entries, entry) {
+		if (read_seqretry(&cfids->entries_seqlock, lseq)) {
+			done = 0;
+			break;
+		}
+
+		cseq = read_seqbegin(&cfid->seqlock);
+		if (cfid_expired(cfid))
+			done = 0;
+
+		if (read_seqretry(&cfid->seqlock, cseq) || !done) {
+			done = 0;
+			break;
+		}
+	}
+
+	/* no list invalidations and no invalidated/expired entries, requeue */
+	if (!read_seqretry(&cfids->entries_seqlock, lseq) && done) {
+		rcu_read_unlock();
+		goto requeue;
+	}
+	rcu_read_unlock();
+
+	/* now we know something was invalidated in the above read, do a write-locked run */
+	write_seqlock(&cfids->entries_seqlock);
 	list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {
+		write_seqlock(&cfid->seqlock);
 		if (cfid_expired(cfid)) {
+			//raw_write_seqcount_barrier(&cfids->entries_seqlock.seqcount);
 			invalidate_cfid(cfid);
 			list_move(&cfid->entry, &entry);
 		}
+		write_sequnlock(&cfid->seqlock);
 	}
-	spin_unlock(&cfids->cfid_list_lock);
+	write_sequnlock(&cfids->entries_seqlock);
 
 	list_for_each_entry_safe(cfid, q, &entry, entry) {
 		list_del(&cfid->entry);
@@ -636,9 +719,8 @@ static void cfids_laundromat_worker(struct work_struct *work)
 		 * No risk for a double list_del() here because cfid is only on this list now.
 		 */
 		drop_cfid(cfid);
-		kref_put(&cfid->refcount, smb2_close_cached_fid);
 	}
-
+requeue:
 	queue_delayed_work(cfid_put_wq, &cfids->laundromat_work, dir_cache_timeout * HZ);
 }
 
@@ -649,7 +731,8 @@ struct cached_fids *init_cached_dirs(void)
 	cfids = kzalloc(sizeof(*cfids), GFP_KERNEL);
 	if (!cfids)
 		return NULL;
-	spin_lock_init(&cfids->cfid_list_lock);
+
+	seqlock_init(&cfids->entries_seqlock);
 	INIT_LIST_HEAD(&cfids->entries);
 
 	INIT_DELAYED_WORK(&cfids->laundromat_work, cfids_laundromat_worker);
diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h
index d5ad10a35ed7..77c292399978 100644
--- a/fs/smb/client/cached_dir.h
+++ b/fs/smb/client/cached_dir.h
@@ -8,6 +8,8 @@
 #ifndef _CACHED_DIR_H
 #define _CACHED_DIR_H
 
+#include <linux/seqlock.h>
+
 struct cached_dirent {
 	struct list_head entry;
 	char *name;
@@ -33,13 +35,19 @@ struct cached_dirents {
 
 struct cached_fid {
 	struct list_head entry;
+	struct rcu_head rcu;
+	/*
+	 * ->seqlock must be used:
+	 * - write-locked when updating
+	 * - rcu_read_lock() + seqcounted on reads
+	 */
+	seqlock_t seqlock;
 	struct cached_fids *cfids;
 	const char *path;
 	unsigned long ctime; /* (jiffies) creation time, when cfid was created (cached) */
 	unsigned long atime; /* (jiffies) access time, when it was last used */
 	struct kref refcount;
 	struct cifs_fid fid;
-	spinlock_t fid_lock;
 	struct cifs_tcon *tcon;
 	struct dentry *dentry;
 	struct smb2_file_all_info *file_all_info;
@@ -48,11 +56,12 @@ struct cached_fid {
 
 /* default MAX_CACHED_FIDS is 16 */
 struct cached_fids {
-	/* Must be held when:
-	 * - accessing the cfids->entries list
-	 * - accessing cfids->num_entries
+	/*
+	 * ->entries_seqlock must be used when accessing ->entries or ->num_entries:
+	 * - write-locked when updating
+	 * - rcu_read_lock() + seqcounted on reads
 	 */
-	spinlock_t cfid_list_lock;
+	seqlock_t entries_seqlock;
 	int num_entries;
 	struct list_head entries;
 	struct delayed_work laundromat_work;
diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c
index bb27b9c97724..72e63db75403 100644
--- a/fs/smb/client/cifs_debug.c
+++ b/fs/smb/client/cifs_debug.c
@@ -306,7 +306,8 @@ static int cifs_debug_dirs_proc_show(struct seq_file *m, void *v)
 				cfids = tcon->cfids;
 				if (!cfids)
 					continue;
-				spin_lock(&cfids->cfid_list_lock); /* check lock ordering */
+
+				read_seqlock_excl(&cfids->entries_seqlock);
 				seq_printf(m, "Num entries: %d\n", cfids->num_entries);
 				list_for_each_entry(cfid, &cfids->entries, entry) {
 					seq_printf(m, "0x%x 0x%llx 0x%llx     %s",
@@ -320,7 +321,7 @@ static int cifs_debug_dirs_proc_show(struct seq_file *m, void *v)
 						seq_printf(m, ", valid dirents");
 					seq_printf(m, "\n");
 				}
-				spin_unlock(&cfids->cfid_list_lock);
+				read_sequnlock_excl(&cfids->entries_seqlock);
 			}
 		}
 	}
-- 
2.49.0


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

* [PATCH 20/20] smb: client: cleanup open_cached_dir()
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
                   ` (18 preceding siblings ...)
  2025-09-29 13:28 ` [PATCH 19/20] smb: client: rework cached dirs synchronization Enzo Matsumiya
@ 2025-09-29 13:28 ` Enzo Matsumiya
  2025-09-29 14:05 ` [PATCH 00/20] smb: client: cached dir fixes and improvements Steve French
  20 siblings, 0 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 13:28 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

A bit of refactoring, and merge path_no_prefix() into path_to_dentry().

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 fs/smb/client/cached_dir.c | 133 ++++++++++++++++---------------------
 1 file changed, 56 insertions(+), 77 deletions(-)

diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index 6d9a06ede476..45be551aad5d 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -166,13 +166,33 @@ static struct cached_fid *find_cfid(struct cached_fids *cfids, const void *key,
 	return NULL;
 }
 
-static struct dentry *
-path_to_dentry(struct cifs_sb_info *cifs_sb, const char *path)
+/*
+ * Skip any prefix paths in @path as lookup_noperm_positive_unlocked() ends up calling ->lookup()
+ * which already adds those through build_path_from_dentry().
+ *
+ * Also, this should be called before sending a network request as we might reconnect and
+ * potentially end up having a different prefix path (e.g. after DFS failover).
+ *
+ * Callers must dput() returned dentry if !IS_ERR().
+ */
+static struct dentry *path_to_dentry(struct cifs_sb_info *cifs_sb, const char *path)
 {
 	struct dentry *dentry;
 	const char *s, *p;
 	char sep;
 
+	if (!*path)
+		return dget(cifs_sb->root);
+
+	if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_USE_PREFIX_PATH) && cifs_sb->prepath) {
+		size_t len = strlen(cifs_sb->prepath) + 1;
+
+		if (unlikely(len > strlen(path)))
+			return ERR_PTR(-EINVAL);
+
+		path += len;
+	}
+
 	sep = CIFS_DIR_SEP(cifs_sb);
 	dentry = dget(cifs_sb->root);
 	s = path;
@@ -197,29 +217,12 @@ path_to_dentry(struct cifs_sb_info *cifs_sb, const char *path)
 		while (*s && *s != sep)
 			s++;
 
-		child = lookup_noperm_positive_unlocked(&QSTR_LEN(p, s - p),
-							dentry);
+		child = lookup_noperm_positive_unlocked(&QSTR_LEN(p, s - p), dentry);
 		dput(dentry);
 		dentry = child;
 	} while (!IS_ERR(dentry));
-	return dentry;
-}
-
-static const char *path_no_prefix(struct cifs_sb_info *cifs_sb,
-				  const char *path)
-{
-	size_t len = 0;
-
-	if (!*path)
-		return path;
 
-	if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_USE_PREFIX_PATH) &&
-	    cifs_sb->prepath) {
-		len = strlen(cifs_sb->prepath) + 1;
-		if (unlikely(len > strlen(path)))
-			return ERR_PTR(-EINVAL);
-	}
-	return path + len;
+	return dentry;
 }
 
 /*
@@ -255,31 +258,29 @@ struct cached_fid *find_cached_dir(struct cached_fids *cfids, const void *key, i
 int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 		    struct cifs_sb_info *cifs_sb, struct cached_fid **ret_cfid)
 {
-	struct cifs_ses *ses;
+	int rc, flags = 0, retries = 0, cur_sleep = 1;
+	struct kvec open_iov[SMB2_CREATE_IOV_SIZE];
+	struct smb2_query_info_rsp *qi_rsp = NULL;
 	struct TCP_Server_Info *server;
 	struct cifs_open_parms oparms;
-	struct smb2_create_rsp *o_rsp = NULL;
-	struct smb2_query_info_rsp *qi_rsp = NULL;
 	struct smb2_file_all_info info;
-	int resp_buftype[2];
+	struct smb2_create_rsp *o_rsp = NULL;
+	struct cached_fid __rcu *cfid;
+	struct cached_fids *cfids;
 	struct smb_rqst rqst[2];
 	struct kvec rsp_iov[2];
-	struct kvec open_iov[SMB2_CREATE_IOV_SIZE];
+	struct cifs_fid *pfid;
+	struct dentry *dentry;
 	struct kvec qi_iov[1];
-	int rc, flags = 0;
+	struct cifs_ses *ses;
+	int resp_buftype[2];
 	__le16 *utf16_path = NULL;
 	u8 oplock = SMB2_OPLOCK_LEVEL_II;
-	struct cifs_fid *pfid;
-	struct dentry *dentry;
-	struct cached_fid __rcu *cfid;
-	struct cached_fids *cfids;
-	const char *npath;
-	int retries = 0, cur_sleep = 1;
 
-	if (cifs_sb->root == NULL)
+	if (!cifs_sb->root)
 		return -ENOENT;
 
-	if (tcon == NULL)
+	if (!tcon)
 		return -EOPNOTSUPP;
 
 	ses = tcon->ses;
@@ -307,10 +308,6 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 		return 0;
 	}
 
-	utf16_path = cifs_convert_path_to_utf16(path, cifs_sb);
-	if (!utf16_path)
-		return -ENOMEM;
-
 	read_seqlock_excl(&cfids->entries_seqlock);
 	if (cfids->num_entries >= tcon->max_cached_dirs) {
 		read_sequnlock_excl(&cfids->entries_seqlock);
@@ -330,30 +327,13 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 	cfid->tcon = tcon;
 	pfid = &cfid->fid;
 
-	/*
-	 * Skip any prefix paths in @path as lookup_noperm_positive_unlocked() ends up
-	 * calling ->lookup() which already adds those through
-	 * build_path_from_dentry().  Also, do it earlier as we might reconnect
-	 * below when trying to send compounded request and then potentially
-	 * having a different prefix path (e.g. after DFS failover).
-	 */
-	npath = path_no_prefix(cifs_sb, path);
-	if (IS_ERR(npath)) {
-		rc = PTR_ERR(npath);
+	dentry = path_to_dentry(cifs_sb, path);
+	if (IS_ERR(dentry)) {
+		rc = PTR_ERR(dentry);
+		dentry = NULL;
 		goto out;
 	}
 
-	if (!npath[0]) {
-		dentry = dget(cifs_sb->root);
-	} else {
-		dentry = path_to_dentry(cifs_sb, npath);
-		if (IS_ERR(dentry)) {
-			dentry = NULL;
-			rc = -ENOENT;
-			goto out;
-		}
-	}
-
 	write_seqlock(&cfids->entries_seqlock);
 	cfids->num_entries++;
 	list_add_rcu(&cfid->entry, &cfids->entries);
@@ -386,21 +366,26 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 	oparms.fid = pfid;
 	oparms.replay = !!retries;
 
+	utf16_path = cifs_convert_path_to_utf16(path, cifs_sb);
+	if (!utf16_path) {
+		rc = -ENOMEM;
+		goto oshr_free;
+	}
+
 	rc = SMB2_open_init(tcon, server, &rqst[0], &oplock, &oparms, utf16_path);
+	kfree(utf16_path);
+
 	if (rc)
 		goto oshr_free;
-	smb2_set_next_command(tcon, &rqst[0]);
 
+	smb2_set_next_command(tcon, &rqst[0]);
 	memset(&qi_iov, 0, sizeof(qi_iov));
 	rqst[1].rq_iov = qi_iov;
 	rqst[1].rq_nvec = 1;
 
-	rc = SMB2_query_info_init(tcon, server,
-				  &rqst[1], COMPOUND_FID,
-				  COMPOUND_FID, FILE_ALL_INFORMATION,
-				  SMB2_O_INFO_FILE, 0,
-				  sizeof(struct smb2_file_all_info) +
-				  PATH_MAX * 2, 0, NULL);
+	rc = SMB2_query_info_init(tcon, server, &rqst[1], COMPOUND_FID, COMPOUND_FID,
+				  FILE_ALL_INFORMATION, SMB2_O_INFO_FILE, 0,
+				  sizeof(struct smb2_file_all_info) + PATH_MAX * 2, 0, NULL);
 	if (rc)
 		goto oshr_free;
 
@@ -411,14 +396,11 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 		smb2_set_replay(server, &rqst[1]);
 	}
 
-	rc = compound_send_recv(xid, ses, server,
-				flags, 2, rqst,
-				resp_buftype, rsp_iov);
+	rc = compound_send_recv(xid, ses, server, flags, 2, rqst, resp_buftype, rsp_iov);
 	if (rc) {
 		if (rc == -EREMCHG) {
 			tcon->need_reconnect = true;
-			pr_warn_once("server share %s deleted\n",
-				     tcon->tree_name);
+			pr_warn_once("server share %s deleted\n", tcon->tree_name);
 		}
 		goto oshr_free;
 	}
@@ -430,7 +412,6 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 	oparms.fid->mid = le64_to_cpu(o_rsp->hdr.MessageId);
 #endif /* CIFS_DEBUG2 */
 
-
 	if (o_rsp->OplockLevel != SMB2_OPLOCK_LEVEL_LEASE) {
 		rc = -EINVAL;
 		goto oshr_free;
@@ -449,9 +430,8 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 	if (le32_to_cpu(qi_rsp->OutputBufferLength) < sizeof(struct smb2_file_all_info))
 		goto oshr_free;
 
-	if (!smb2_validate_and_copy_iov(le16_to_cpu(qi_rsp->OutputBufferOffset),
-					sizeof(struct smb2_file_all_info), &rsp_iov[1],
-					sizeof(struct smb2_file_all_info), (char *)&info)) {
+	if (!smb2_validate_and_copy_iov(le16_to_cpu(qi_rsp->OutputBufferOffset), sizeof(info),
+					&rsp_iov[1], sizeof(info), (char *)&info)) {
 		cfid->file_all_info = kmemdup(&info, sizeof(info), GFP_ATOMIC);
 		if (!cfid->file_all_info) {
 			rc = -ENOMEM;
@@ -486,7 +466,6 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
 		*ret_cfid = cfid;
 		atomic_inc(&tcon->num_remote_opens);
 	}
-	kfree(utf16_path);
 
 	if (is_replayable_error(rc) &&
 	    smb2_should_replay(tcon, &retries, &cur_sleep))
-- 
2.49.0


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

* Re: [PATCH 00/20] smb: client: cached dir fixes and improvements
  2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
                   ` (19 preceding siblings ...)
  2025-09-29 13:28 ` [PATCH 20/20] smb: client: cleanup open_cached_dir() Enzo Matsumiya
@ 2025-09-29 14:05 ` Steve French
  2025-09-29 14:44   ` Enzo Matsumiya
  20 siblings, 1 reply; 29+ messages in thread
From: Steve French @ 2025-09-29 14:05 UTC (permalink / raw)
  To: Enzo Matsumiya
  Cc: linux-cifs, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

Looks promising (although wish could be done in smaller stages, easier
to test and review).

Do you have any reproducers for the problems it fixes?   I have been
worried about tests like generic/011 and generic/013 that fail about
10-20% of the time (probably due to deferred close issues/races) - and
hoping that we can get more consistent tests ot repro deferred close
issues so we don't see them in the future.

On Mon, Sep 29, 2025 at 8:28 AM Enzo Matsumiya <ematsumiya@suse.de> wrote:
>
> Hi,
>
> This patch series aims to refactor cached dir related code in order to
> improve performance, improve code maintenance/readability, and of course
> fix several, existing and potential, bugs.
>
> Please note that the below only makes sense to the whole series applied.
>
> Semantic fixes:
> - cfid->has_lease vs cfid->is_open: when opening a cached dir, we get a fid
>   (is_open) and a lease (has_lease), however, has_lease is used differently
>   throughout the code, meaning, most of the time, that the cfid is 'usable'
>   (fix in patch 11)
> - refcounting also follows has_lease, up to a point, when we need to
>   'steal' the reference, then we might have a cfid with 2 refs but
>   has_lease == false (fix in patches 1-5)
> - cfid lookup: currently done with open_cached_dir() with @lookup_only arg,
>   but that is not visibly good-looking and also highly inflexible (because
>   it only works for paths (char *).
>
>
> Technical fixes:
> - due to the many "Dentry still in use" bugs, cleaning up a cfid has become
>   too complex -- there are 3 workers to do that asynchronously, and the
>   release callback itself.  Complexity aside, this still has bugs because
>   open_cached_dir() design doesn't account for any concurrent invalidation,
>   leading sometimes to double opens/closes, sometimes straight UAF/deadlock
>   bugs (examples upon request).
>   (fix in patches 1-11)
> - locking: the list lock is not used consistently; sometimes protecting only
>   the list, sometimes protecting only a cfid, sometimes both.
>   cfid->fid_lock only protects ->dentry, nothing else.  This leads to
>   inconsistent data being read when a concurrent invalidation occurs, e.g.
>   cached_dir_lease_break() (sets ->time = 0) vs cifs_dentry_needs_reval()
>   (reads ->time unlocked)
>   * also, open_cached_dir() always assume it has >1 refs, but such
>     assumption is proven wrong when SMB2_open_init() triggers
>     smb2_reconnect(), and kref_put() is ran locked in the rc != 0 case,
>     leading to a deadlock because the extra ref has been dropped async
>   (both fixed in patch 19 and others)
>
> Improvements:
> Having all above fixes and changes allows a cleaner code with a simpler
> design:
> - code readability is improved (cf. whole series)
> - usage of cached dirs in places that weren't making use of it (cf. patches
>   12-18)
> - patch 19 (locking) not only fixes the synchronization problems, but RCU +
>   seqcounting allows faster lookups (read-mostly) while also allowing
>   consistent reads and stability for callers (prevents UAF)
> - because a directory is always a parent, bake-in support for when opening
>   a path, ParentLeaseKey can be set for any target child (cf. patch 12)
>
>
> Cheers,
>
> Enzo Matsumiya (20):
>   smb: client: remove cfids_invalidation_worker
>   smb: client: remove cached_dir_offload_close/close_work
>   smb: client: remove cached_dir_put_work/put_work
>   smb: client: remove cached_fids->dying list
>   smb: client: remove cached_fid->on_list
>   smb: client: merge {close,invalidate}_all_cached_dirs()
>   smb: client: merge free_cached_dir in release callback
>   smb: client: split find_or_create_cached_dir()
>   smb: client: enhance cached dir lookups
>   smb: client: refactor dropping cached dirs
>   smb: client: simplify cached_fid state checking
>   smb: client: prevent lease breaks of cached parents when opening
>     children
>   smb: client: actually use cached dirs on readdir
>   smb: client: wait for concurrent caching of dirents in cifs_readdir()
>   smb: client: remove cached_dirent->fattr
>   smb: client: add is_dir argument to query_path_info
>   smb: client: use cached dir on queryfs/smb2_compound_op
>   smb: client: fix dentry revalidation of cached root
>   smb: client: rework cached dirs synchronization
>   smb: client: cleanup open_cached_dir()
>
>  fs/smb/client/cached_dir.c | 946 ++++++++++++++++---------------------
>  fs/smb/client/cached_dir.h |  74 +--
>  fs/smb/client/cifs_debug.c |   7 +-
>  fs/smb/client/cifsfs.c     |   2 +-
>  fs/smb/client/cifsglob.h   |   5 +-
>  fs/smb/client/dir.c        |  27 +-
>  fs/smb/client/file.c       |   2 +-
>  fs/smb/client/inode.c      |  38 +-
>  fs/smb/client/misc.c       |   9 +-
>  fs/smb/client/readdir.c    | 146 +++---
>  fs/smb/client/smb1ops.c    |   6 +-
>  fs/smb/client/smb2inode.c  |  48 +-
>  fs/smb/client/smb2misc.c   |   2 +-
>  fs/smb/client/smb2ops.c    |  49 +-
>  fs/smb/client/smb2pdu.c    |  99 +++-
>  fs/smb/client/smb2proto.h  |  10 +-
>  16 files changed, 733 insertions(+), 737 deletions(-)
>
> --
> 2.49.0
>


-- 
Thanks,

Steve

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

* Re: [PATCH 12/20] smb: client: prevent lease breaks of cached parents when opening children
  2025-09-29 13:27 ` [PATCH 12/20] smb: client: prevent lease breaks of cached parents when opening children Enzo Matsumiya
@ 2025-09-29 14:23   ` Steve French
  2025-09-29 17:17   ` Enzo Matsumiya
  1 sibling, 0 replies; 29+ messages in thread
From: Steve French @ 2025-09-29 14:23 UTC (permalink / raw)
  To: Enzo Matsumiya
  Cc: linux-cifs, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

This looks potentially very useful but would help to have some simple
repro example

On Mon, Sep 29, 2025 at 8:29 AM Enzo Matsumiya <ematsumiya@suse.de> wrote:
>
> In SMB2_open_init(), when opening (not creating/deleting) a path, lookup
> for a cached parent and set ParentLeaseKey in lease context if found.
>
> Other:
> - set oparms->cifs_sb in open_cached_dir() as we need it in
>   add_parent_lease_key(); use CIFS_OPARMS() too
>
> Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
> ---
>  fs/smb/client/cached_dir.c | 42 ++++---------------
>  fs/smb/client/dir.c        | 26 +++---------
>  fs/smb/client/smb2inode.c  |  2 +
>  fs/smb/client/smb2pdu.c    | 86 ++++++++++++++++++++++++++++++++------
>  4 files changed, 88 insertions(+), 68 deletions(-)
>
> diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
> index ff71f2c06b72..9dd74268b2d8 100644
> --- a/fs/smb/client/cached_dir.c
> +++ b/fs/smb/client/cached_dir.c
> @@ -226,7 +226,6 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
>         struct cached_fids *cfids;
>         const char *npath;
>         int retries = 0, cur_sleep = 1;
> -       __le32 lease_flags = 0;
>
>         if (cifs_sb->root == NULL)
>                 return -ENOENT;
> @@ -236,9 +235,9 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
>
>         ses = tcon->ses;
>         cfids = tcon->cfids;
> -
>         if (!cfids)
>                 return -EOPNOTSUPP;
> +
>  replay_again:
>         /* reinitialize for possible replay */
>         flags = 0;
> @@ -306,24 +305,6 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
>                         rc = -ENOENT;
>                         goto out;
>                 }
> -               if (dentry->d_parent && server->dialect >= SMB30_PROT_ID) {
> -                       struct cached_fid *parent_cfid;
> -
> -                       spin_lock(&cfids->cfid_list_lock);
> -                       list_for_each_entry(parent_cfid, &cfids->entries, entry) {
> -                               if (parent_cfid->dentry == dentry->d_parent) {
> -                                       if (!cfid_is_valid(parent_cfid))
> -                                               break;
> -
> -                                       cifs_dbg(FYI, "found a parent cached file handle\n");
> -                                       lease_flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
> -                                       memcpy(pfid->parent_lease_key, parent_cfid->fid.lease_key,
> -                                              SMB2_LEASE_KEY_SIZE);
> -                                       break;
> -                               }
> -                       }
> -                       spin_unlock(&cfids->cfid_list_lock);
> -               }
>         }
>         cfid->dentry = dentry;
>         cfid->tcon = tcon;
> @@ -350,20 +331,13 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
>         rqst[0].rq_iov = open_iov;
>         rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE;
>
> -       oparms = (struct cifs_open_parms) {
> -               .tcon = tcon,
> -               .path = path,
> -               .create_options = cifs_create_options(cifs_sb, CREATE_NOT_FILE),
> -               .desired_access =  FILE_READ_DATA | FILE_READ_ATTRIBUTES |
> -                                  FILE_READ_EA,
> -               .disposition = FILE_OPEN,
> -               .fid = pfid,
> -               .lease_flags = lease_flags,
> -               .replay = !!(retries),
> -       };
> -
> -       rc = SMB2_open_init(tcon, server,
> -                           &rqst[0], &oplock, &oparms, utf16_path);
> +       oparms = CIFS_OPARMS(cifs_sb, tcon, path,
> +                            FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA, FILE_OPEN,
> +                            cifs_create_options(cifs_sb, CREATE_NOT_FILE), 0);
> +       oparms.fid = pfid;
> +       oparms.replay = !!retries;
> +
> +       rc = SMB2_open_init(tcon, server, &rqst[0], &oplock, &oparms, utf16_path);
>         if (rc)
>                 goto oshr_free;
>         smb2_set_next_command(tcon, &rqst[0]);
> diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c
> index e5372c2c799d..b60af27668bb 100644
> --- a/fs/smb/client/dir.c
> +++ b/fs/smb/client/dir.c
> @@ -189,10 +189,9 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
>         struct inode *newinode = NULL;
>         int disposition;
>         struct TCP_Server_Info *server = tcon->ses->server;
> +       struct cached_fid *parent_cfid;
>         struct cifs_open_parms oparms;
> -       struct cached_fid *parent_cfid = NULL;
>         int rdwr_for_fscache = 0;
> -       __le32 lease_flags = 0;
>
>         *oplock = 0;
>         if (tcon->ses->server->oplocks)
> @@ -314,25 +313,11 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
>         if (!tcon->unix_ext && (mode & S_IWUGO) == 0)
>                 create_options |= CREATE_OPTION_READONLY;
>
> -
>  retry_open:
> -       if (tcon->cfids && direntry->d_parent && server->dialect >= SMB30_PROT_ID) {
> -               parent_cfid = NULL;
> -               spin_lock(&tcon->cfids->cfid_list_lock);
> -               list_for_each_entry(parent_cfid, &tcon->cfids->entries, entry) {
> -                       if (parent_cfid->dentry == direntry->d_parent) {
> -                               if (!cfid_is_valid(parent_cfid))
> -                                       break;
> -
> -                               cifs_dbg(FYI, "found a parent cached file handle\n");
> -                               lease_flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
> -                               memcpy(fid->parent_lease_key, parent_cfid->fid.lease_key,
> -                                      SMB2_LEASE_KEY_SIZE);
> -                               parent_cfid->dirents.is_valid = false;
> -                               break;
> -                       }
> -               }
> -               spin_unlock(&tcon->cfids->cfid_list_lock);
> +       parent_cfid = find_cached_dir(tcon->cfids, direntry->d_parent, CFID_LOOKUP_DENTRY);
> +       if (parent_cfid) {
> +               parent_cfid->dirents.is_valid = false;
> +               close_cached_dir(parent_cfid);
>         }
>
>         oparms = (struct cifs_open_parms) {
> @@ -343,7 +328,6 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
>                 .disposition = disposition,
>                 .path = full_path,
>                 .fid = fid,
> -               .lease_flags = lease_flags,
>                 .mode = mode,
>         };
>         rc = server->ops->open(xid, &oparms, oplock, buf);
> diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c
> index 8ccdd1a3ba2c..6d643b8b9547 100644
> --- a/fs/smb/client/smb2inode.c
> +++ b/fs/smb/client/smb2inode.c
> @@ -1120,6 +1120,8 @@ smb2_mkdir(const unsigned int xid, struct inode *parent_inode, umode_t mode,
>  {
>         struct cifs_open_parms oparms;
>
> +       drop_cached_dir(tcon->cfids, name, CFID_LOOKUP_PATH);
> +
>         oparms = CIFS_OPARMS(cifs_sb, tcon, name, FILE_WRITE_ATTRIBUTES,
>                              FILE_CREATE, CREATE_NOT_FILE, mode);
>         return smb2_compound_op(xid, tcon, cifs_sb,
> diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
> index 07ba61583114..2474ac18b85e 100644
> --- a/fs/smb/client/smb2pdu.c
> +++ b/fs/smb/client/smb2pdu.c
> @@ -2419,7 +2419,8 @@ add_lease_context(struct TCP_Server_Info *server,
>         if (iov[num].iov_base == NULL)
>                 return -ENOMEM;
>         iov[num].iov_len = server->vals->create_lease_size;
> -       req->RequestedOplockLevel = SMB2_OPLOCK_LEVEL_LEASE;
> +       /* keep the requested oplock level in case of just setting ParentLeaseKey */
> +       req->RequestedOplockLevel = *oplock;
>         *num_iovec = num + 1;
>         return 0;
>  }
> @@ -3001,6 +3002,50 @@ int smb311_posix_mkdir(const unsigned int xid, struct inode *inode,
>         return rc;
>  }
>
> +/*
> + * When opening a path, set ParentLeaseKey in @oparms if its parent is cached.
> + * We only have RH caching for dirs, so skip this on mkdir, unlink, rmdir.
> + *
> + * Ref: MS-SMB2 3.3.5.9 and MS-FSA 2.1.5.1
> + *
> + * Return: 0 if ParentLeaseKey was set in @oparms, -errno otherwise.
> + */
> +static int check_cached_parent(struct cached_fids *cfids, struct cifs_open_parms *oparms)
> +{
> +       struct cached_fid *cfid;
> +       const char *parent_path, *path;
> +
> +       if (!cfids || !oparms || !oparms->cifs_sb || !*oparms->path)
> +               return -EINVAL;
> +
> +       if ((oparms->disposition == FILE_CREATE && oparms->create_options == CREATE_NOT_FILE) ||
> +           oparms->desired_access == DELETE)
> +               return -EOPNOTSUPP;
> +
> +       path = oparms->path;
> +       parent_path = strrchr(path, CIFS_DIR_SEP(oparms->cifs_sb));
> +       if (!parent_path)
> +               return -ENOENT;
> +
> +       parent_path = kstrndup(path, parent_path - path, GFP_KERNEL);
> +       if (!parent_path)
> +               return -ENOMEM;
> +
> +       cfid = find_cached_dir(cfids, parent_path, CFID_LOOKUP_PATH);
> +       kfree(parent_path);
> +
> +       if (!cfid)
> +               return -ENOENT;
> +
> +       cifs_dbg(FYI, "%s: found cached parent for path: %s\n", __func__, oparms->path);
> +
> +       memcpy(oparms->fid->parent_lease_key, cfid->fid.lease_key, SMB2_LEASE_KEY_SIZE);
> +       oparms->lease_flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
> +       close_cached_dir(cfid);
> +
> +       return 0;
> +}
> +
>  int
>  SMB2_open_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server,
>                struct smb_rqst *rqst, __u8 *oplock,
> @@ -3077,20 +3122,35 @@ SMB2_open_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server,
>         iov[1].iov_len = uni_path_len;
>         iov[1].iov_base = path;
>
> -       if ((!server->oplocks) || (tcon->no_lease))
> +       if (!server->oplocks || tcon->no_lease)
>                 *oplock = SMB2_OPLOCK_LEVEL_NONE;
>
> -       if (!(server->capabilities & SMB2_GLOBAL_CAP_LEASING) ||
> -           *oplock == SMB2_OPLOCK_LEVEL_NONE)
> -               req->RequestedOplockLevel = *oplock;
> -       else if (!(server->capabilities & SMB2_GLOBAL_CAP_DIRECTORY_LEASING) &&
> -                 (oparms->create_options & CREATE_NOT_FILE))
> -               req->RequestedOplockLevel = *oplock; /* no srv lease support */
> -       else {
> -               rc = add_lease_context(server, req, iov, &n_iov,
> -                                      oparms->fid->lease_key, oplock,
> -                                      oparms->fid->parent_lease_key,
> -                                      oparms->lease_flags);
> +       req->RequestedOplockLevel = *oplock;
> +
> +       /*
> +        * MS-SMB2 "Product Behavior" says Windows only checks/sets ParentLeaseKey when a lease is
> +        * requested for the child/target.
> +        * Practically speaking, adding the lease context with ParentLeaseKey set, even with oplock
> +        * none, works fine.
> +        * As a precaution, however, only set it for oplocks != none.
> +        */
> +       if ((server->capabilities & SMB2_GLOBAL_CAP_LEASING) &&
> +           *oplock != SMB2_OPLOCK_LEVEL_NONE) {
> +               rc = -EOPNOTSUPP;
> +               if (server->capabilities & SMB2_GLOBAL_CAP_DIRECTORY_LEASING)
> +                       rc = check_cached_parent(tcon->cfids, oparms);
> +
> +               /*
> +                * -ENOENT just means we couldn't find a cached parent, but we do have dir leasing,
> +                * so try requesting a level II oplock for the child path.
> +                */
> +               if ((!rc || rc == -ENOENT) && *oplock == SMB2_OPLOCK_LEVEL_NONE)
> +                       *oplock = SMB2_OPLOCK_LEVEL_II;
> +
> +               if (*oplock != SMB2_OPLOCK_LEVEL_NONE)
> +                       rc = add_lease_context(server, req, iov, &n_iov, oparms->fid->lease_key,
> +                                              oplock, oparms->fid->parent_lease_key,
> +                                              oparms->lease_flags);
>                 if (rc)
>                         return rc;
>         }
> --
> 2.49.0
>


-- 
Thanks,

Steve

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

* Re: [PATCH 17/20] smb: client: use cached dir on queryfs/smb2_compound_op
  2025-09-29 13:28 ` [PATCH 17/20] smb: client: use cached dir on queryfs/smb2_compound_op Enzo Matsumiya
@ 2025-09-29 14:26   ` Steve French
  0 siblings, 0 replies; 29+ messages in thread
From: Steve French @ 2025-09-29 14:26 UTC (permalink / raw)
  To: Enzo Matsumiya
  Cc: linux-cifs, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

Would be helpful to show simple command example where this reduces ops

On Mon, Sep 29, 2025 at 8:29 AM Enzo Matsumiya <ematsumiya@suse.de> wrote:
>
> A dentry is passed to cifs_statfs(), so pass down d_is_dir() to
> smb2_queryfs() so we can cache/reuse this dir.
>
> Other:
> - make smb2_compound_op a static function, as it's not used anywhere
>   else
>
> Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
> ---
>  fs/smb/client/cifsfs.c    |  2 +-
>  fs/smb/client/cifsglob.h  |  2 +-
>  fs/smb/client/smb1ops.c   |  2 +-
>  fs/smb/client/smb2ops.c   | 32 +++++++++++++++++---------------
>  fs/smb/client/smb2proto.h |  6 ------
>  5 files changed, 20 insertions(+), 24 deletions(-)
>
> diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c
> index e1848276bab4..a2ecc5649860 100644
> --- a/fs/smb/client/cifsfs.c
> +++ b/fs/smb/client/cifsfs.c
> @@ -339,7 +339,7 @@ cifs_statfs(struct dentry *dentry, struct kstatfs *buf)
>         buf->f_ffree = 0;       /* unlimited */
>
>         if (server->ops->queryfs)
> -               rc = server->ops->queryfs(xid, tcon, full_path, cifs_sb, buf);
> +               rc = server->ops->queryfs(xid, tcon, full_path, cifs_sb, buf, d_is_dir(dentry));
>
>  statfs_out:
>         free_dentry_path(page);
> diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
> index 08c8131c8018..dddac55abd6f 100644
> --- a/fs/smb/client/cifsglob.h
> +++ b/fs/smb/client/cifsglob.h
> @@ -519,7 +519,7 @@ struct smb_version_operations {
>                         __u16 net_fid, struct cifsInodeInfo *cifs_inode);
>         /* query remote filesystem */
>         int (*queryfs)(const unsigned int, struct cifs_tcon *,
> -                      const char *, struct cifs_sb_info *, struct kstatfs *);
> +                      const char *, struct cifs_sb_info *, struct kstatfs *, bool);
>         /* send mandatory brlock to the server */
>         int (*mand_lock)(const unsigned int, struct cifsFileInfo *, __u64,
>                          __u64, __u32, int, int, bool);
> diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c
> index d964bc9c2823..9fa1ff9ea70d 100644
> --- a/fs/smb/client/smb1ops.c
> +++ b/fs/smb/client/smb1ops.c
> @@ -1105,7 +1105,7 @@ cifs_oplock_response(struct cifs_tcon *tcon, __u64 persistent_fid,
>
>  static int
>  cifs_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
> -            const char *path, struct cifs_sb_info *cifs_sb, struct kstatfs *buf)
> +            const char *path, struct cifs_sb_info *cifs_sb, struct kstatfs *buf, bool is_dir)
>  {
>         int rc = -EOPNOTSUPP;
>
> diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
> index 8842315d2526..f691271463b5 100644
> --- a/fs/smb/client/smb2ops.c
> +++ b/fs/smb/client/smb2ops.c
> @@ -1110,6 +1110,11 @@ move_smb2_ea_to_cifs(char *dst, size_t dst_size,
>         return (ssize_t)rc;
>  }
>
> +static int smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon,
> +                                   const char *path, u32 desired_access, u32 class, u32 type,
> +                                   u32 output_len, struct kvec *rsp, int *buftype,
> +                                   struct cifs_sb_info *cifs_sb, bool is_dir);
> +
>  static ssize_t
>  smb2_query_eas(const unsigned int xid, struct cifs_tcon *tcon,
>                const unsigned char *path, const unsigned char *ea_name,
> @@ -1129,7 +1134,7 @@ smb2_query_eas(const unsigned int xid, struct cifs_tcon *tcon,
>                                       CIFSMaxBufSize -
>                                       MAX_SMB2_CREATE_RESPONSE_SIZE -
>                                       MAX_SMB2_CLOSE_RESPONSE_SIZE,
> -                                     &rsp_iov, &buftype, cifs_sb);
> +                                     &rsp_iov, &buftype, cifs_sb, false);
>         if (rc) {
>                 /*
>                  * If ea_name is NULL (listxattr) and there are no EAs,
> @@ -1231,7 +1236,7 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon,
>                                       CIFSMaxBufSize -
>                                       MAX_SMB2_CREATE_RESPONSE_SIZE -
>                                       MAX_SMB2_CLOSE_RESPONSE_SIZE,
> -                                     &rsp_iov[1], &resp_buftype[1], cifs_sb);
> +                                     &rsp_iov[1], &resp_buftype[1], cifs_sb, false);
>                         if (rc == 0) {
>                                 rsp = (struct smb2_query_info_rsp *)rsp_iov[1].iov_base;
>                                 used_len = le32_to_cpu(rsp->OutputBufferLength);
> @@ -2694,12 +2699,10 @@ bool smb2_should_replay(struct cifs_tcon *tcon,
>   * Passes the query info response back to the caller on success.
>   * Caller need to free this with free_rsp_buf().
>   */
> -int
> -smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon,
> -                        const char *path, u32 desired_access,
> -                        u32 class, u32 type, u32 output_len,
> -                        struct kvec *rsp, int *buftype,
> -                        struct cifs_sb_info *cifs_sb)
> +static int smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon,
> +                                   const char *path, u32 desired_access, u32 class, u32 type,
> +                                   u32 output_len, struct kvec *rsp, int *buftype,
> +                                   struct cifs_sb_info *cifs_sb, bool is_dir)
>  {
>         struct smb2_compound_vars *vars;
>         struct cifs_ses *ses = tcon->ses;
> @@ -2741,9 +2744,9 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon,
>         rsp_iov = vars->rsp_iov;
>
>         /*
> -        * We can only call this for things we know are directories.
> +        * We can only open + cache paths we know are directories.
>          */
> -       if (!strcmp(path, ""))
> +       if (is_dir)
>                 /* cfid null if open dir failed */
>                 open_cached_dir(xid, tcon, path, cifs_sb, &cfid);
>
> @@ -2852,7 +2855,7 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon,
>
>  static int
>  smb2_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
> -            const char *path, struct cifs_sb_info *cifs_sb, struct kstatfs *buf)
> +            const char *path, struct cifs_sb_info *cifs_sb, struct kstatfs *buf, bool is_dir)
>  {
>         struct smb2_query_info_rsp *rsp;
>         struct smb2_fs_full_size_info *info = NULL;
> @@ -2860,13 +2863,12 @@ smb2_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
>         int buftype = CIFS_NO_BUFFER;
>         int rc;
>
> -
>         rc = smb2_query_info_compound(xid, tcon, path,
>                                       FILE_READ_ATTRIBUTES,
>                                       FS_FULL_SIZE_INFORMATION,
>                                       SMB2_O_INFO_FILESYSTEM,
>                                       sizeof(struct smb2_fs_full_size_info),
> -                                     &rsp_iov, &buftype, cifs_sb);
> +                                     &rsp_iov, &buftype, cifs_sb, is_dir);
>         if (rc)
>                 goto qfs_exit;
>
> @@ -2889,7 +2891,7 @@ smb2_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
>
>  static int
>  smb311_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
> -              const char *path, struct cifs_sb_info *cifs_sb, struct kstatfs *buf)
> +              const char *path, struct cifs_sb_info *cifs_sb, struct kstatfs *buf, bool is_dir)
>  {
>         int rc;
>         __le16 *utf16_path = NULL;
> @@ -2898,7 +2900,7 @@ smb311_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
>         struct cifs_fid fid;
>
>         if (!tcon->posix_extensions)
> -               return smb2_queryfs(xid, tcon, path, cifs_sb, buf);
> +               return smb2_queryfs(xid, tcon, path, cifs_sb, buf, is_dir);
>
>         oparms = (struct cifs_open_parms) {
>                 .tcon = tcon,
> diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h
> index 3e3faa7cf633..99326810a159 100644
> --- a/fs/smb/client/smb2proto.h
> +++ b/fs/smb/client/smb2proto.h
> @@ -299,12 +299,6 @@ extern int smb311_crypto_shash_allocate(struct TCP_Server_Info *server);
>  extern int smb311_update_preauth_hash(struct cifs_ses *ses,
>                                       struct TCP_Server_Info *server,
>                                       struct kvec *iov, int nvec);
> -extern int smb2_query_info_compound(const unsigned int xid,
> -                                   struct cifs_tcon *tcon,
> -                                   const char *path, u32 desired_access,
> -                                   u32 class, u32 type, u32 output_len,
> -                                   struct kvec *rsp, int *buftype,
> -                                   struct cifs_sb_info *cifs_sb);
>  /* query path info from the server using SMB311 POSIX extensions*/
>  int smb311_posix_query_path_info(const unsigned int xid,
>                                  struct cifs_tcon *tcon,
> --
> 2.49.0
>


-- 
Thanks,

Steve

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

* Re: [PATCH 00/20] smb: client: cached dir fixes and improvements
  2025-09-29 14:05 ` [PATCH 00/20] smb: client: cached dir fixes and improvements Steve French
@ 2025-09-29 14:44   ` Enzo Matsumiya
  0 siblings, 0 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 14:44 UTC (permalink / raw)
  To: Steve French
  Cc: linux-cifs, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

On 09/29, Steve French wrote:
>Looks promising (although wish could be done in smaller stages, easier
>to test and review).

The patch I sent you privately a while ago contained most of these in a
single patch, and it would be definitely not fun to review.

Also, I keep seeing reviews saying to "split patch into smaller bits for
easier review", so which one is it?

>Do you have any reproducers for the problems it fixes?

Since the issues covered are mostly caused by races, it's hard to come
up with a reproducer, let alone a reliable one.

I do however can say that we have customer bugs with the following
backtraces, that inspired all these changes.

Deadlock in open_cached_dir() when smb2_reconnect() is triggered:

> Kernel panic - not syncing: softlockup: hung tasks
> ...
> RIP: 0010:native_queued_spin_lock_slowpath+0x2d/0x2c0
> ...
>  _raw_spin_lock+0x25/0x30
>  smb2_close_cached_fid+0x17/0xc0
>  open_cached_dir+0x8ff/0xb60
>  ? smb2_query_info_compound+0x332/0x5c0
>  smb2_query_info_compound+0x332/0x5c0
>  ? step_into+0x10d/0x690
>  ? path_lookupat+0x98/0x140
>  ? filename_lookup+0x115/0x1c0
>  smb2_queryfs+0x6a/0xf0
>  cifs_statfs+0x9f/0x2b0
>  statfs_by_dentry+0x67/0x90
>  vfs_statfs+0x16/0xd0
>  user_statfs+0x54/0xa0
>  __do_sys_statfs+0x20/0x50
>  do_syscall_64+0x5b/0x80


Refcount underflow (UAF) in smb2_query_info_compound():

> refcount_t: underflow; use-after-free.
> WARNING: CPU: 1 PID: 11224 at ../lib/refcount.c:28 refcount_warn_saturate+0x9c/0x110
> ...
> RIP: 0010:refcount_warn_saturate+0x9c/0x110
> ...
> Call Trace:
>  <TASK>
>  smb2_query_info_compound+0x29c/0x5c0
>  ? step_into+0x10d/0x690
>  ? __legitimize_path+0x28/0x60
>  smb2_queryfs+0x6a/0xf0
>  smb311_queryfs+0x12d/0x140
>  ? kmem_cache_alloc+0x18a/0x340
>  ? getname_flags+0x46/0x1e0
>  cifs_statfs+0x9f/0x2b0
>  statfs_by_dentry+0x67/0x90
>  vfs_statfs+0x16/0xd0
>  user_statfs+0x54/0xa0
>  __do_sys_statfs+0x20/0x50
>  do_syscall_64+0x58/0x80

While developing the patches, I used xfstests generic/241 (dbench 4
clients) which makes heavy usage of cached dirs.

If you printk cfid fields at the end of open_cached_dir() when
rc == 0, you should see that it sometimes has been invalidated, and a
now-closed fid is returned to the caller.

If you mix in random interruptions (ctrl-c or network drops) you should
end up with similar backtraces as above.

>I have been
>worried about tests like generic/011 and generic/013 that fail about
>10-20% of the time (probably due to deferred close issues/races) - and
>hoping that we can get more consistent tests ot repro deferred close
>issues so we don't see them in the future.

Yes, that problem also shows up in generic/241 -- even though running
dbench standalone is fine, the xfstest unit does a final cleanup of the
test dir and leads to ENOTEMPTY in the end.

I spent the last 1-2 months debugging this, trying to find a way to fix
it in this very series, but failed.


FWIW, improvement-wise, the pcap for that test, with the series
applied, shows ~5% less opens and ~14% less lease breaks.

Cheers,

Enzo

>On Mon, Sep 29, 2025 at 8:28 AM Enzo Matsumiya <ematsumiya@suse.de> wrote:
>>
>> Hi,
>>
>> This patch series aims to refactor cached dir related code in order to
>> improve performance, improve code maintenance/readability, and of course
>> fix several, existing and potential, bugs.
>>
>> Please note that the below only makes sense to the whole series applied.
>>
>> Semantic fixes:
>> - cfid->has_lease vs cfid->is_open: when opening a cached dir, we get a fid
>>   (is_open) and a lease (has_lease), however, has_lease is used differently
>>   throughout the code, meaning, most of the time, that the cfid is 'usable'
>>   (fix in patch 11)
>> - refcounting also follows has_lease, up to a point, when we need to
>>   'steal' the reference, then we might have a cfid with 2 refs but
>>   has_lease == false (fix in patches 1-5)
>> - cfid lookup: currently done with open_cached_dir() with @lookup_only arg,
>>   but that is not visibly good-looking and also highly inflexible (because
>>   it only works for paths (char *).
>>
>>
>> Technical fixes:
>> - due to the many "Dentry still in use" bugs, cleaning up a cfid has become
>>   too complex -- there are 3 workers to do that asynchronously, and the
>>   release callback itself.  Complexity aside, this still has bugs because
>>   open_cached_dir() design doesn't account for any concurrent invalidation,
>>   leading sometimes to double opens/closes, sometimes straight UAF/deadlock
>>   bugs (examples upon request).
>>   (fix in patches 1-11)
>> - locking: the list lock is not used consistently; sometimes protecting only
>>   the list, sometimes protecting only a cfid, sometimes both.
>>   cfid->fid_lock only protects ->dentry, nothing else.  This leads to
>>   inconsistent data being read when a concurrent invalidation occurs, e.g.
>>   cached_dir_lease_break() (sets ->time = 0) vs cifs_dentry_needs_reval()
>>   (reads ->time unlocked)
>>   * also, open_cached_dir() always assume it has >1 refs, but such
>>     assumption is proven wrong when SMB2_open_init() triggers
>>     smb2_reconnect(), and kref_put() is ran locked in the rc != 0 case,
>>     leading to a deadlock because the extra ref has been dropped async
>>   (both fixed in patch 19 and others)
>>
>> Improvements:
>> Having all above fixes and changes allows a cleaner code with a simpler
>> design:
>> - code readability is improved (cf. whole series)
>> - usage of cached dirs in places that weren't making use of it (cf. patches
>>   12-18)
>> - patch 19 (locking) not only fixes the synchronization problems, but RCU +
>>   seqcounting allows faster lookups (read-mostly) while also allowing
>>   consistent reads and stability for callers (prevents UAF)
>> - because a directory is always a parent, bake-in support for when opening
>>   a path, ParentLeaseKey can be set for any target child (cf. patch 12)
>>
>>
>> Cheers,
>>
>> Enzo Matsumiya (20):
>>   smb: client: remove cfids_invalidation_worker
>>   smb: client: remove cached_dir_offload_close/close_work
>>   smb: client: remove cached_dir_put_work/put_work
>>   smb: client: remove cached_fids->dying list
>>   smb: client: remove cached_fid->on_list
>>   smb: client: merge {close,invalidate}_all_cached_dirs()
>>   smb: client: merge free_cached_dir in release callback
>>   smb: client: split find_or_create_cached_dir()
>>   smb: client: enhance cached dir lookups
>>   smb: client: refactor dropping cached dirs
>>   smb: client: simplify cached_fid state checking
>>   smb: client: prevent lease breaks of cached parents when opening
>>     children
>>   smb: client: actually use cached dirs on readdir
>>   smb: client: wait for concurrent caching of dirents in cifs_readdir()
>>   smb: client: remove cached_dirent->fattr
>>   smb: client: add is_dir argument to query_path_info
>>   smb: client: use cached dir on queryfs/smb2_compound_op
>>   smb: client: fix dentry revalidation of cached root
>>   smb: client: rework cached dirs synchronization
>>   smb: client: cleanup open_cached_dir()
>>
>>  fs/smb/client/cached_dir.c | 946 ++++++++++++++++---------------------
>>  fs/smb/client/cached_dir.h |  74 +--
>>  fs/smb/client/cifs_debug.c |   7 +-
>>  fs/smb/client/cifsfs.c     |   2 +-
>>  fs/smb/client/cifsglob.h   |   5 +-
>>  fs/smb/client/dir.c        |  27 +-
>>  fs/smb/client/file.c       |   2 +-
>>  fs/smb/client/inode.c      |  38 +-
>>  fs/smb/client/misc.c       |   9 +-
>>  fs/smb/client/readdir.c    | 146 +++---
>>  fs/smb/client/smb1ops.c    |   6 +-
>>  fs/smb/client/smb2inode.c  |  48 +-
>>  fs/smb/client/smb2misc.c   |   2 +-
>>  fs/smb/client/smb2ops.c    |  49 +-
>>  fs/smb/client/smb2pdu.c    |  99 +++-
>>  fs/smb/client/smb2proto.h  |  10 +-
>>  16 files changed, 733 insertions(+), 737 deletions(-)
>>
>> --
>> 2.49.0
>>
>
>
>-- 
>Thanks,
>
>Steve

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

* Re: [PATCH 12/20] smb: client: prevent lease breaks of cached parents when opening children
  2025-09-29 13:27 ` [PATCH 12/20] smb: client: prevent lease breaks of cached parents when opening children Enzo Matsumiya
  2025-09-29 14:23   ` Steve French
@ 2025-09-29 17:17   ` Enzo Matsumiya
  1 sibling, 0 replies; 29+ messages in thread
From: Enzo Matsumiya @ 2025-09-29 17:17 UTC (permalink / raw)
  To: linux-cifs
  Cc: smfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	henrique.carvalho

On 09/29, Enzo Matsumiya wrote:
>In SMB2_open_init(), when opening (not creating/deleting) a path, lookup
>for a cached parent and set ParentLeaseKey in lease context if found.
>
>Other:
>- set oparms->cifs_sb in open_cached_dir() as we need it in
>  add_parent_lease_key(); use CIFS_OPARMS() too

This patch is from a rebase gone wrong.

- the function above is now called check_cached_parent() instead of
   add_parent_lease_key()
- the conditions to add ParentLeaseKey needs to be refreshed because of
   the above
- it should've already include Bharath's fix in cifs_do_create()
   (setting dirents.is_failed to true)

I'll fix these in V2.


Enzo

>Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
>---
> fs/smb/client/cached_dir.c | 42 ++++---------------
> fs/smb/client/dir.c        | 26 +++---------
> fs/smb/client/smb2inode.c  |  2 +
> fs/smb/client/smb2pdu.c    | 86 ++++++++++++++++++++++++++++++++------
> 4 files changed, 88 insertions(+), 68 deletions(-)
>
>diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
>index ff71f2c06b72..9dd74268b2d8 100644
>--- a/fs/smb/client/cached_dir.c
>+++ b/fs/smb/client/cached_dir.c
>@@ -226,7 +226,6 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
> 	struct cached_fids *cfids;
> 	const char *npath;
> 	int retries = 0, cur_sleep = 1;
>-	__le32 lease_flags = 0;
>
> 	if (cifs_sb->root == NULL)
> 		return -ENOENT;
>@@ -236,9 +235,9 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
>
> 	ses = tcon->ses;
> 	cfids = tcon->cfids;
>-
> 	if (!cfids)
> 		return -EOPNOTSUPP;
>+
> replay_again:
> 	/* reinitialize for possible replay */
> 	flags = 0;
>@@ -306,24 +305,6 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
> 			rc = -ENOENT;
> 			goto out;
> 		}
>-		if (dentry->d_parent && server->dialect >= SMB30_PROT_ID) {
>-			struct cached_fid *parent_cfid;
>-
>-			spin_lock(&cfids->cfid_list_lock);
>-			list_for_each_entry(parent_cfid, &cfids->entries, entry) {
>-				if (parent_cfid->dentry == dentry->d_parent) {
>-					if (!cfid_is_valid(parent_cfid))
>-						break;
>-
>-					cifs_dbg(FYI, "found a parent cached file handle\n");
>-					lease_flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
>-					memcpy(pfid->parent_lease_key, parent_cfid->fid.lease_key,
>-					       SMB2_LEASE_KEY_SIZE);
>-					break;
>-				}
>-			}
>-			spin_unlock(&cfids->cfid_list_lock);
>-		}
> 	}
> 	cfid->dentry = dentry;
> 	cfid->tcon = tcon;
>@@ -350,20 +331,13 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
> 	rqst[0].rq_iov = open_iov;
> 	rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE;
>
>-	oparms = (struct cifs_open_parms) {
>-		.tcon = tcon,
>-		.path = path,
>-		.create_options = cifs_create_options(cifs_sb, CREATE_NOT_FILE),
>-		.desired_access =  FILE_READ_DATA | FILE_READ_ATTRIBUTES |
>-				   FILE_READ_EA,
>-		.disposition = FILE_OPEN,
>-		.fid = pfid,
>-		.lease_flags = lease_flags,
>-		.replay = !!(retries),
>-	};
>-
>-	rc = SMB2_open_init(tcon, server,
>-			    &rqst[0], &oplock, &oparms, utf16_path);
>+	oparms = CIFS_OPARMS(cifs_sb, tcon, path,
>+			     FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA, FILE_OPEN,
>+			     cifs_create_options(cifs_sb, CREATE_NOT_FILE), 0);
>+	oparms.fid = pfid;
>+	oparms.replay = !!retries;
>+
>+	rc = SMB2_open_init(tcon, server, &rqst[0], &oplock, &oparms, utf16_path);
> 	if (rc)
> 		goto oshr_free;
> 	smb2_set_next_command(tcon, &rqst[0]);
>diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c
>index e5372c2c799d..b60af27668bb 100644
>--- a/fs/smb/client/dir.c
>+++ b/fs/smb/client/dir.c
>@@ -189,10 +189,9 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
> 	struct inode *newinode = NULL;
> 	int disposition;
> 	struct TCP_Server_Info *server = tcon->ses->server;
>+	struct cached_fid *parent_cfid;
> 	struct cifs_open_parms oparms;
>-	struct cached_fid *parent_cfid = NULL;
> 	int rdwr_for_fscache = 0;
>-	__le32 lease_flags = 0;
>
> 	*oplock = 0;
> 	if (tcon->ses->server->oplocks)
>@@ -314,25 +313,11 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
> 	if (!tcon->unix_ext && (mode & S_IWUGO) == 0)
> 		create_options |= CREATE_OPTION_READONLY;
>
>-
> retry_open:
>-	if (tcon->cfids && direntry->d_parent && server->dialect >= SMB30_PROT_ID) {
>-		parent_cfid = NULL;
>-		spin_lock(&tcon->cfids->cfid_list_lock);
>-		list_for_each_entry(parent_cfid, &tcon->cfids->entries, entry) {
>-			if (parent_cfid->dentry == direntry->d_parent) {
>-				if (!cfid_is_valid(parent_cfid))
>-					break;
>-
>-				cifs_dbg(FYI, "found a parent cached file handle\n");
>-				lease_flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
>-				memcpy(fid->parent_lease_key, parent_cfid->fid.lease_key,
>-				       SMB2_LEASE_KEY_SIZE);
>-				parent_cfid->dirents.is_valid = false;
>-				break;
>-			}
>-		}
>-		spin_unlock(&tcon->cfids->cfid_list_lock);
>+	parent_cfid = find_cached_dir(tcon->cfids, direntry->d_parent, CFID_LOOKUP_DENTRY);
>+	if (parent_cfid) {
>+		parent_cfid->dirents.is_valid = false;
>+		close_cached_dir(parent_cfid);
> 	}
>
> 	oparms = (struct cifs_open_parms) {
>@@ -343,7 +328,6 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
> 		.disposition = disposition,
> 		.path = full_path,
> 		.fid = fid,
>-		.lease_flags = lease_flags,
> 		.mode = mode,
> 	};
> 	rc = server->ops->open(xid, &oparms, oplock, buf);
>diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c
>index 8ccdd1a3ba2c..6d643b8b9547 100644
>--- a/fs/smb/client/smb2inode.c
>+++ b/fs/smb/client/smb2inode.c
>@@ -1120,6 +1120,8 @@ smb2_mkdir(const unsigned int xid, struct inode *parent_inode, umode_t mode,
> {
> 	struct cifs_open_parms oparms;
>
>+	drop_cached_dir(tcon->cfids, name, CFID_LOOKUP_PATH);
>+
> 	oparms = CIFS_OPARMS(cifs_sb, tcon, name, FILE_WRITE_ATTRIBUTES,
> 			     FILE_CREATE, CREATE_NOT_FILE, mode);
> 	return smb2_compound_op(xid, tcon, cifs_sb,
>diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
>index 07ba61583114..2474ac18b85e 100644
>--- a/fs/smb/client/smb2pdu.c
>+++ b/fs/smb/client/smb2pdu.c
>@@ -2419,7 +2419,8 @@ add_lease_context(struct TCP_Server_Info *server,
> 	if (iov[num].iov_base == NULL)
> 		return -ENOMEM;
> 	iov[num].iov_len = server->vals->create_lease_size;
>-	req->RequestedOplockLevel = SMB2_OPLOCK_LEVEL_LEASE;
>+	/* keep the requested oplock level in case of just setting ParentLeaseKey */
>+	req->RequestedOplockLevel = *oplock;
> 	*num_iovec = num + 1;
> 	return 0;
> }
>@@ -3001,6 +3002,50 @@ int smb311_posix_mkdir(const unsigned int xid, struct inode *inode,
> 	return rc;
> }
>
>+/*
>+ * When opening a path, set ParentLeaseKey in @oparms if its parent is cached.
>+ * We only have RH caching for dirs, so skip this on mkdir, unlink, rmdir.
>+ *
>+ * Ref: MS-SMB2 3.3.5.9 and MS-FSA 2.1.5.1
>+ *
>+ * Return: 0 if ParentLeaseKey was set in @oparms, -errno otherwise.
>+ */
>+static int check_cached_parent(struct cached_fids *cfids, struct cifs_open_parms *oparms)
>+{
>+	struct cached_fid *cfid;
>+	const char *parent_path, *path;
>+
>+	if (!cfids || !oparms || !oparms->cifs_sb || !*oparms->path)
>+		return -EINVAL;
>+
>+	if ((oparms->disposition == FILE_CREATE && oparms->create_options == CREATE_NOT_FILE) ||
>+	    oparms->desired_access == DELETE)
>+		return -EOPNOTSUPP;
>+
>+	path = oparms->path;
>+	parent_path = strrchr(path, CIFS_DIR_SEP(oparms->cifs_sb));
>+	if (!parent_path)
>+		return -ENOENT;
>+
>+	parent_path = kstrndup(path, parent_path - path, GFP_KERNEL);
>+	if (!parent_path)
>+		return -ENOMEM;
>+
>+	cfid = find_cached_dir(cfids, parent_path, CFID_LOOKUP_PATH);
>+	kfree(parent_path);
>+
>+	if (!cfid)
>+		return -ENOENT;
>+
>+	cifs_dbg(FYI, "%s: found cached parent for path: %s\n", __func__, oparms->path);
>+
>+	memcpy(oparms->fid->parent_lease_key, cfid->fid.lease_key, SMB2_LEASE_KEY_SIZE);
>+	oparms->lease_flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
>+	close_cached_dir(cfid);
>+
>+	return 0;
>+}
>+
> int
> SMB2_open_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server,
> 	       struct smb_rqst *rqst, __u8 *oplock,
>@@ -3077,20 +3122,35 @@ SMB2_open_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server,
> 	iov[1].iov_len = uni_path_len;
> 	iov[1].iov_base = path;
>
>-	if ((!server->oplocks) || (tcon->no_lease))
>+	if (!server->oplocks || tcon->no_lease)
> 		*oplock = SMB2_OPLOCK_LEVEL_NONE;
>
>-	if (!(server->capabilities & SMB2_GLOBAL_CAP_LEASING) ||
>-	    *oplock == SMB2_OPLOCK_LEVEL_NONE)
>-		req->RequestedOplockLevel = *oplock;
>-	else if (!(server->capabilities & SMB2_GLOBAL_CAP_DIRECTORY_LEASING) &&
>-		  (oparms->create_options & CREATE_NOT_FILE))
>-		req->RequestedOplockLevel = *oplock; /* no srv lease support */
>-	else {
>-		rc = add_lease_context(server, req, iov, &n_iov,
>-				       oparms->fid->lease_key, oplock,
>-				       oparms->fid->parent_lease_key,
>-				       oparms->lease_flags);
>+	req->RequestedOplockLevel = *oplock;
>+
>+	/*
>+	 * MS-SMB2 "Product Behavior" says Windows only checks/sets ParentLeaseKey when a lease is
>+	 * requested for the child/target.
>+	 * Practically speaking, adding the lease context with ParentLeaseKey set, even with oplock
>+	 * none, works fine.
>+	 * As a precaution, however, only set it for oplocks != none.
>+	 */
>+	if ((server->capabilities & SMB2_GLOBAL_CAP_LEASING) &&
>+	    *oplock != SMB2_OPLOCK_LEVEL_NONE) {
>+		rc = -EOPNOTSUPP;
>+		if (server->capabilities & SMB2_GLOBAL_CAP_DIRECTORY_LEASING)
>+			rc = check_cached_parent(tcon->cfids, oparms);
>+
>+		/*
>+		 * -ENOENT just means we couldn't find a cached parent, but we do have dir leasing,
>+		 * so try requesting a level II oplock for the child path.
>+		 */
>+		if ((!rc || rc == -ENOENT) && *oplock == SMB2_OPLOCK_LEVEL_NONE)
>+			*oplock = SMB2_OPLOCK_LEVEL_II;
>+
>+		if (*oplock != SMB2_OPLOCK_LEVEL_NONE)
>+			rc = add_lease_context(server, req, iov, &n_iov, oparms->fid->lease_key,
>+					       oplock, oparms->fid->parent_lease_key,
>+					       oparms->lease_flags);
> 		if (rc)
> 			return rc;
> 	}
>-- 
>2.49.0
>
>

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

* Re: [PATCH 19/20] smb: client: rework cached dirs synchronization
  2025-09-29 13:28 ` [PATCH 19/20] smb: client: rework cached dirs synchronization Enzo Matsumiya
@ 2025-09-30 19:02   ` kernel test robot
  0 siblings, 0 replies; 29+ messages in thread
From: kernel test robot @ 2025-09-30 19:02 UTC (permalink / raw)
  To: Enzo Matsumiya, linux-cifs
  Cc: oe-kbuild-all, smfrench, pc, ronniesahlberg, sprasad, tom,
	bharathsm, henrique.carvalho

Hi Enzo,

kernel test robot noticed the following build warnings:

[auto build test WARNING on v6.17]
[also build test WARNING on linus/master]
[cannot apply to cifs/for-next next-20250929]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Enzo-Matsumiya/smb-client-remove-cfids_invalidation_worker/20250929-213155
base:   v6.17
patch link:    https://lore.kernel.org/r/20250929132805.220558-20-ematsumiya%40suse.de
patch subject: [PATCH 19/20] smb: client: rework cached dirs synchronization
config: i386-randconfig-061-20250930 (https://download.01.org/0day-ci/archive/20251001/202510010208.20c25Gzw-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251001/202510010208.20c25Gzw-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202510010208.20c25Gzw-lkp@intel.com/

sparse warnings: (new ones prefixed by >>)
>> fs/smb/client/cached_dir.c:304:14: sparse: sparse: incorrect type in assignment (different address spaces) @@     expected struct cached_fid [noderef] __rcu *[assigned] cfid @@     got struct cached_fid * @@
   fs/smb/client/cached_dir.c:304:14: sparse:     expected struct cached_fid [noderef] __rcu *[assigned] cfid
   fs/smb/client/cached_dir.c:304:14: sparse:     got struct cached_fid *
>> fs/smb/client/cached_dir.c:306:27: sparse: sparse: incorrect type in assignment (different address spaces) @@     expected struct cached_fid * @@     got struct cached_fid [noderef] __rcu *[assigned] cfid @@
   fs/smb/client/cached_dir.c:306:27: sparse:     expected struct cached_fid *
   fs/smb/client/cached_dir.c:306:27: sparse:     got struct cached_fid [noderef] __rcu *[assigned] cfid
   fs/smb/client/cached_dir.c:323:14: sparse: sparse: incorrect type in assignment (different address spaces) @@     expected struct cached_fid [noderef] __rcu *[assigned] cfid @@     got struct cached_fid * @@
   fs/smb/client/cached_dir.c:323:14: sparse:     expected struct cached_fid [noderef] __rcu *[assigned] cfid
   fs/smb/client/cached_dir.c:323:14: sparse:     got struct cached_fid *
>> fs/smb/client/cached_dir.c:331:14: sparse: sparse: incorrect type in assignment (different address spaces) @@     expected struct cifs_fid *pfid @@     got struct cifs_fid [noderef] __rcu * @@
   fs/smb/client/cached_dir.c:331:14: sparse:     expected struct cifs_fid *pfid
   fs/smb/client/cached_dir.c:331:14: sparse:     got struct cifs_fid [noderef] __rcu *
>> fs/smb/client/cached_dir.c:359:23: sparse: sparse: incorrect type in argument 1 (different address spaces) @@     expected struct list_head *new @@     got struct list_head [noderef] __rcu * @@
   fs/smb/client/cached_dir.c:359:23: sparse:     expected struct list_head *new
   fs/smb/client/cached_dir.c:359:23: sparse:     got struct list_head [noderef] __rcu *
>> fs/smb/client/cached_dir.c:477:35: sparse: sparse: incorrect type in argument 1 (different address spaces) @@     expected struct cached_fid *cfid @@     got struct cached_fid [noderef] __rcu *[assigned] cfid @@
   fs/smb/client/cached_dir.c:477:35: sparse:     expected struct cached_fid *cfid
   fs/smb/client/cached_dir.c:477:35: sparse:     got struct cached_fid [noderef] __rcu *[assigned] cfid
>> fs/smb/client/cached_dir.c:480:32: sparse: sparse: incorrect type in argument 1 (different address spaces) @@     expected struct seqlock_t [usertype] *sl @@     got struct seqlock_t [noderef] __rcu * @@
   fs/smb/client/cached_dir.c:480:32: sparse:     expected struct seqlock_t [usertype] *sl
   fs/smb/client/cached_dir.c:480:32: sparse:     got struct seqlock_t [noderef] __rcu *
   fs/smb/client/cached_dir.c:484:34: sparse: sparse: incorrect type in argument 1 (different address spaces) @@     expected struct seqlock_t [usertype] *sl @@     got struct seqlock_t [noderef] __rcu * @@
   fs/smb/client/cached_dir.c:484:34: sparse:     expected struct seqlock_t [usertype] *sl
   fs/smb/client/cached_dir.c:484:34: sparse:     got struct seqlock_t [noderef] __rcu *
   fs/smb/client/cached_dir.c:486:27: sparse: sparse: incorrect type in assignment (different address spaces) @@     expected struct cached_fid * @@     got struct cached_fid [noderef] __rcu *[assigned] cfid @@
   fs/smb/client/cached_dir.c:486:27: sparse:     expected struct cached_fid *
   fs/smb/client/cached_dir.c:486:27: sparse:     got struct cached_fid [noderef] __rcu *[assigned] cfid
>> fs/smb/client/cached_dir.c:329:9: sparse: sparse: dereference of noderef expression
   fs/smb/client/cached_dir.c:330:9: sparse: sparse: dereference of noderef expression
   fs/smb/client/cached_dir.c:455:17: sparse: sparse: dereference of noderef expression
   fs/smb/client/cached_dir.c:456:22: sparse: sparse: dereference of noderef expression
   fs/smb/client/cached_dir.c:470:20: sparse: sparse: dereference of noderef expression
   fs/smb/client/cached_dir.c:481:17: sparse: sparse: dereference of noderef expression
   fs/smb/client/cached_dir.c:482:17: sparse: sparse: dereference of noderef expression
   fs/smb/client/cached_dir.c:483:17: sparse: sparse: dereference of noderef expression

vim +304 fs/smb/client/cached_dir.c

7d24f0ff5dad1f fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  250  
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  251  /*
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  252   * Open the and cache a directory handle.
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  253   * If error then *cfid is not initialized.
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  254   */
7d24f0ff5dad1f fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  255  int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path,
7d24f0ff5dad1f fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  256  		    struct cifs_sb_info *cifs_sb, struct cached_fid **ret_cfid)
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  257  {
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  258  	struct cifs_ses *ses;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  259  	struct TCP_Server_Info *server;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  260  	struct cifs_open_parms oparms;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  261  	struct smb2_create_rsp *o_rsp = NULL;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  262  	struct smb2_query_info_rsp *qi_rsp = NULL;
ff1e8e71b1ac5d fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  263  	struct smb2_file_all_info info;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  264  	int resp_buftype[2];
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  265  	struct smb_rqst rqst[2];
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  266  	struct kvec rsp_iov[2];
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  267  	struct kvec open_iov[SMB2_CREATE_IOV_SIZE];
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  268  	struct kvec qi_iov[1];
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  269  	int rc, flags = 0;
ebe98f1447bbcc fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-10-06  270  	__le16 *utf16_path = NULL;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  271  	u8 oplock = SMB2_OPLOCK_LEVEL_II;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  272  	struct cifs_fid *pfid;
7d24f0ff5dad1f fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  273  	struct dentry *dentry;
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  274  	struct cached_fid __rcu *cfid;
ebe98f1447bbcc fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-10-06  275  	struct cached_fids *cfids;
be4fde79812f02 fs/cifs/cached_dir.c       Paulo Alcantara   2023-03-24  276  	const char *npath;
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  277  	int retries = 0, cur_sleep = 1;
ebe98f1447bbcc fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-10-06  278  
f6e88838400d88 fs/smb/client/cached_dir.c Henrique Carvalho 2024-11-22  279  	if (cifs_sb->root == NULL)
f6e88838400d88 fs/smb/client/cached_dir.c Henrique Carvalho 2024-11-22  280  		return -ENOENT;
f6e88838400d88 fs/smb/client/cached_dir.c Henrique Carvalho 2024-11-22  281  
f6e88838400d88 fs/smb/client/cached_dir.c Henrique Carvalho 2024-11-22  282  	if (tcon == NULL)
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  283  		return -EOPNOTSUPP;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  284  
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  285  	ses = tcon->ses;
ebe98f1447bbcc fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-10-06  286  	cfids = tcon->cfids;
1d4a92c061c478 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  287  	if (!cfids)
f6e88838400d88 fs/smb/client/cached_dir.c Henrique Carvalho 2024-11-22  288  		return -EOPNOTSUPP;
e28638bb28ed69 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  289  
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  290  replay_again:
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  291  	/* reinitialize for possible replay */
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  292  	flags = 0;
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  293  	oplock = SMB2_OPLOCK_LEVEL_II;
7d24f0ff5dad1f fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  294  	dentry = NULL;
7d24f0ff5dad1f fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  295  	cfid = NULL;
7d24f0ff5dad1f fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  296  	*ret_cfid = NULL;
ff1e8e71b1ac5d fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  297  	memset(&info, 0, sizeof(info));
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  298  	server = cifs_pick_channel(ses);
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  299  
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  300  	if (!server->ops->new_lease_key)
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  301  		return -EIO;
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  302  
ff1e8e71b1ac5d fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  303  	/* find_cached_dir() already validates cfid if found, so no need to check here again */
7d24f0ff5dad1f fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29 @304  	cfid = find_cached_dir(cfids, path, CFID_LOOKUP_PATH);
7d24f0ff5dad1f fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  305  	if (cfid) {
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29 @306  		*ret_cfid = cfid;
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  307  		return 0;
ebe98f1447bbcc fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-10-06  308  	}
1d4a92c061c478 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  309  
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  310  	utf16_path = cifs_convert_path_to_utf16(path, cifs_sb);
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  311  	if (!utf16_path)
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  312  		return -ENOMEM;
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  313  
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  314  	read_seqlock_excl(&cfids->entries_seqlock);
7d24f0ff5dad1f fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  315  	if (cfids->num_entries >= tcon->max_cached_dirs) {
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  316  		read_sequnlock_excl(&cfids->entries_seqlock);
7d24f0ff5dad1f fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  317  		rc = -ENOENT;
7d24f0ff5dad1f fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  318  		goto out;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  319  	}
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  320  	read_sequnlock_excl(&cfids->entries_seqlock);
1d4a92c061c478 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  321  
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  322  	/* no ned to lock cfid or entries yet */
1d4a92c061c478 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29 @323  	cfid = init_cached_dir(path);
1d4a92c061c478 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  324  	if (!cfid) {
7d24f0ff5dad1f fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  325  		rc = -ENOMEM;
7d24f0ff5dad1f fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  326  		goto out;
1d4a92c061c478 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  327  	}
1d4a92c061c478 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  328  
1d4a92c061c478 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29 @329  	cfid->cfids = cfids;
65e58ef1dafb0c fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  330  	cfid->tcon = tcon;
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29 @331  	pfid = &cfid->fid;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  332  
be4fde79812f02 fs/cifs/cached_dir.c       Paulo Alcantara   2023-03-24  333  	/*
fa6fe07d153636 fs/smb/client/cached_dir.c NeilBrown         2025-03-19  334  	 * Skip any prefix paths in @path as lookup_noperm_positive_unlocked() ends up
be4fde79812f02 fs/cifs/cached_dir.c       Paulo Alcantara   2023-03-24  335  	 * calling ->lookup() which already adds those through
be4fde79812f02 fs/cifs/cached_dir.c       Paulo Alcantara   2023-03-24  336  	 * build_path_from_dentry().  Also, do it earlier as we might reconnect
be4fde79812f02 fs/cifs/cached_dir.c       Paulo Alcantara   2023-03-24  337  	 * below when trying to send compounded request and then potentially
be4fde79812f02 fs/cifs/cached_dir.c       Paulo Alcantara   2023-03-24  338  	 * having a different prefix path (e.g. after DFS failover).
be4fde79812f02 fs/cifs/cached_dir.c       Paulo Alcantara   2023-03-24  339  	 */
be4fde79812f02 fs/cifs/cached_dir.c       Paulo Alcantara   2023-03-24  340  	npath = path_no_prefix(cifs_sb, path);
be4fde79812f02 fs/cifs/cached_dir.c       Paulo Alcantara   2023-03-24  341  	if (IS_ERR(npath)) {
be4fde79812f02 fs/cifs/cached_dir.c       Paulo Alcantara   2023-03-24  342  		rc = PTR_ERR(npath);
5c86919455c1ed fs/smb/client/cached_dir.c Paulo Alcantara   2023-10-30  343  		goto out;
be4fde79812f02 fs/cifs/cached_dir.c       Paulo Alcantara   2023-03-24  344  	}
be4fde79812f02 fs/cifs/cached_dir.c       Paulo Alcantara   2023-03-24  345  
5c86919455c1ed fs/smb/client/cached_dir.c Paulo Alcantara   2023-10-30  346  	if (!npath[0]) {
5c86919455c1ed fs/smb/client/cached_dir.c Paulo Alcantara   2023-10-30  347  		dentry = dget(cifs_sb->root);
5c86919455c1ed fs/smb/client/cached_dir.c Paulo Alcantara   2023-10-30  348  	} else {
5c86919455c1ed fs/smb/client/cached_dir.c Paulo Alcantara   2023-10-30  349  		dentry = path_to_dentry(cifs_sb, npath);
5c86919455c1ed fs/smb/client/cached_dir.c Paulo Alcantara   2023-10-30  350  		if (IS_ERR(dentry)) {
a43689b1b41ccc fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  351  			dentry = NULL;
5c86919455c1ed fs/smb/client/cached_dir.c Paulo Alcantara   2023-10-30  352  			rc = -ENOENT;
5c86919455c1ed fs/smb/client/cached_dir.c Paulo Alcantara   2023-10-30  353  			goto out;
5c86919455c1ed fs/smb/client/cached_dir.c Paulo Alcantara   2023-10-30  354  		}
5c86919455c1ed fs/smb/client/cached_dir.c Paulo Alcantara   2023-10-30  355  	}
5c86919455c1ed fs/smb/client/cached_dir.c Paulo Alcantara   2023-10-30  356  
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  357  	write_seqlock(&cfids->entries_seqlock);
65e58ef1dafb0c fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  358  	cfids->num_entries++;
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29 @359  	list_add_rcu(&cfid->entry, &cfids->entries);
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  360  	write_sequnlock(&cfids->entries_seqlock);
65e58ef1dafb0c fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  361  
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  362  	/*
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  363  	 * We do not hold the lock for the open because in case
ebe98f1447bbcc fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-10-06  364  	 * SMB2_open needs to reconnect.
ebe98f1447bbcc fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-10-06  365  	 * This is safe because no other thread will be able to get a ref
ebe98f1447bbcc fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-10-06  366  	 * to the cfid until we have finished opening the file and (possibly)
ebe98f1447bbcc fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-10-06  367  	 * acquired a lease.
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  368  	 */
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  369  	if (smb3_encryption_required(tcon))
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  370  		flags |= CIFS_TRANSFORM_REQ;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  371  
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  372  	server->ops->new_lease_key(pfid);
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  373  
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  374  	memset(rqst, 0, sizeof(rqst));
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  375  	resp_buftype[0] = resp_buftype[1] = CIFS_NO_BUFFER;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  376  	memset(rsp_iov, 0, sizeof(rsp_iov));
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  377  
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  378  	/* Open */
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  379  	memset(&open_iov, 0, sizeof(open_iov));
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  380  	rqst[0].rq_iov = open_iov;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  381  	rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  382  
e28638bb28ed69 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  383  	oparms = CIFS_OPARMS(cifs_sb, tcon, path,
e28638bb28ed69 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  384  			     FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA, FILE_OPEN,
e28638bb28ed69 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  385  			     cifs_create_options(cifs_sb, CREATE_NOT_FILE), 0);
e28638bb28ed69 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  386  	oparms.fid = pfid;
e28638bb28ed69 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  387  	oparms.replay = !!retries;
e28638bb28ed69 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  388  
e28638bb28ed69 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  389  	rc = SMB2_open_init(tcon, server, &rqst[0], &oplock, &oparms, utf16_path);
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  390  	if (rc)
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  391  		goto oshr_free;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  392  	smb2_set_next_command(tcon, &rqst[0]);
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  393  
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  394  	memset(&qi_iov, 0, sizeof(qi_iov));
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  395  	rqst[1].rq_iov = qi_iov;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  396  	rqst[1].rq_nvec = 1;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  397  
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  398  	rc = SMB2_query_info_init(tcon, server,
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  399  				  &rqst[1], COMPOUND_FID,
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  400  				  COMPOUND_FID, FILE_ALL_INFORMATION,
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  401  				  SMB2_O_INFO_FILE, 0,
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  402  				  sizeof(struct smb2_file_all_info) +
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  403  				  PATH_MAX * 2, 0, NULL);
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  404  	if (rc)
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  405  		goto oshr_free;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  406  
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  407  	smb2_set_related(&rqst[1]);
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  408  
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  409  	if (retries) {
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  410  		smb2_set_replay(server, &rqst[0]);
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  411  		smb2_set_replay(server, &rqst[1]);
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  412  	}
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  413  
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  414  	rc = compound_send_recv(xid, ses, server,
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  415  				flags, 2, rqst,
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  416  				resp_buftype, rsp_iov);
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  417  	if (rc) {
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  418  		if (rc == -EREMCHG) {
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  419  			tcon->need_reconnect = true;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  420  			pr_warn_once("server share %s deleted\n",
68e14569d7e5a1 fs/cifs/cached_dir.c       Steve French      2022-09-21  421  				     tcon->tree_name);
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  422  		}
ebe98f1447bbcc fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-10-06  423  		goto oshr_free;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  424  	}
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  425  
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  426  	o_rsp = (struct smb2_create_rsp *)rsp_iov[0].iov_base;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  427  	oparms.fid->persistent_fid = o_rsp->PersistentFileId;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  428  	oparms.fid->volatile_fid = o_rsp->VolatileFileId;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  429  #ifdef CONFIG_CIFS_DEBUG2
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  430  	oparms.fid->mid = le64_to_cpu(o_rsp->hdr.MessageId);
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  431  #endif /* CIFS_DEBUG2 */
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  432  
af1689a9b7701d fs/smb/client/cached_dir.c Paulo Alcantara   2023-12-11  433  
5c86919455c1ed fs/smb/client/cached_dir.c Paulo Alcantara   2023-10-30  434  	if (o_rsp->OplockLevel != SMB2_OPLOCK_LEVEL_LEASE) {
af1689a9b7701d fs/smb/client/cached_dir.c Paulo Alcantara   2023-12-11  435  		rc = -EINVAL;
ebe98f1447bbcc fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-10-06  436  		goto oshr_free;
5c86919455c1ed fs/smb/client/cached_dir.c Paulo Alcantara   2023-10-30  437  	}
ebe98f1447bbcc fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-10-06  438  
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  439  	rc = smb2_parse_contexts(server, rsp_iov, &oparms.fid->epoch, oparms.fid->lease_key,
af1689a9b7701d fs/smb/client/cached_dir.c Paulo Alcantara   2023-12-11  440  				 &oplock, NULL, NULL);
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  441  	if (rc)
af1689a9b7701d fs/smb/client/cached_dir.c Paulo Alcantara   2023-12-11  442  		goto oshr_free;
af1689a9b7701d fs/smb/client/cached_dir.c Paulo Alcantara   2023-12-11  443  
af1689a9b7701d fs/smb/client/cached_dir.c Paulo Alcantara   2023-12-11  444  	rc = -EINVAL;
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  445  	if (!(oplock & SMB2_LEASE_READ_CACHING_HE))
66d45ca1350a3b fs/cifs/cached_dir.c       Ronnie Sahlberg   2023-02-17  446  		goto oshr_free;
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  447  
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  448  	qi_rsp = (struct smb2_query_info_rsp *)rsp_iov[1].iov_base;
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  449  	if (le32_to_cpu(qi_rsp->OutputBufferLength) < sizeof(struct smb2_file_all_info))
ebe98f1447bbcc fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-10-06  450  		goto oshr_free;
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  451  
ff1e8e71b1ac5d fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  452  	if (!smb2_validate_and_copy_iov(le16_to_cpu(qi_rsp->OutputBufferOffset),
ff1e8e71b1ac5d fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  453  					sizeof(struct smb2_file_all_info), &rsp_iov[1],
ff1e8e71b1ac5d fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  454  					sizeof(struct smb2_file_all_info), (char *)&info)) {
ff1e8e71b1ac5d fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  455  		cfid->file_all_info = kmemdup(&info, sizeof(info), GFP_ATOMIC);
ff1e8e71b1ac5d fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  456  		if (!cfid->file_all_info) {
ff1e8e71b1ac5d fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  457  			rc = -ENOMEM;
ff1e8e71b1ac5d fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  458  			goto out;
ff1e8e71b1ac5d fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  459  		}
ff1e8e71b1ac5d fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  460  	}
e4029e072673d8 fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-10-12  461  
5c86919455c1ed fs/smb/client/cached_dir.c Paulo Alcantara   2023-10-30  462  	rc = 0;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  463  oshr_free:
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  464  	SMB2_open_free(&rqst[0]);
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  465  	SMB2_query_info_free(&rqst[1]);
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  466  	free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base);
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  467  	free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base);
a9685b409a03b7 fs/smb/client/cached_dir.c Paul Aurich       2024-11-18  468  out:
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  469  	/* cfid only becomes fully valid below, so can't use cfid_is_valid() here */
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  470  	if (!rc && cfid->ctime == CFID_INVALID_TIME)
a43689b1b41ccc fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  471  		rc = -ENOENT;
a9685b409a03b7 fs/smb/client/cached_dir.c Paul Aurich       2024-11-18  472  
a43689b1b41ccc fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  473  	if (rc) {
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  474  		dput(dentry);
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  475  
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  476  		if (cfid)
a43689b1b41ccc fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29 @477  			drop_cfid(cfid);
5c86919455c1ed fs/smb/client/cached_dir.c Paulo Alcantara   2023-10-30  478  	} else {
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  479  		/* seqlocked-write will inform concurrent lookups of opening -> open transition */
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29 @480  		write_seqlock(&cfid->seqlock);
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  481  		cfid->dentry = dentry;
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  482  		cfid->ctime = jiffies;
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  483  		cfid->atime = jiffies;
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  484  		write_sequnlock(&cfid->seqlock);
9faca5c38f7815 fs/smb/client/cached_dir.c Enzo Matsumiya    2025-09-29  485  
a63ec83c462b5b fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-11  486  		*ret_cfid = cfid;
66d45ca1350a3b fs/cifs/cached_dir.c       Ronnie Sahlberg   2023-02-17  487  		atomic_inc(&tcon->num_remote_opens);
66d45ca1350a3b fs/cifs/cached_dir.c       Ronnie Sahlberg   2023-02-17  488  	}
5c86919455c1ed fs/smb/client/cached_dir.c Paulo Alcantara   2023-10-30  489  	kfree(utf16_path);
64cc377b7628b8 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  490  
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  491  	if (is_replayable_error(rc) &&
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  492  	    smb2_should_replay(tcon, &retries, &cur_sleep))
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  493  		goto replay_again;
4f1fffa2376922 fs/smb/client/cached_dir.c Shyam Prasad N    2024-01-21  494  
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  495  	return rc;
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  496  }
05b98fd2da6bdf fs/cifs/cached_dir.c       Ronnie Sahlberg   2022-08-10  497  

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

* Re: [PATCH 16/20] smb: client: add is_dir argument to query_path_info
  2025-09-29 13:28 ` [PATCH 16/20] smb: client: add is_dir argument to query_path_info Enzo Matsumiya
@ 2025-10-03 17:20   ` Dan Carpenter
  0 siblings, 0 replies; 29+ messages in thread
From: Dan Carpenter @ 2025-10-03 17:20 UTC (permalink / raw)
  To: oe-kbuild, Enzo Matsumiya, linux-cifs
  Cc: lkp, oe-kbuild-all, smfrench, pc, ronniesahlberg, sprasad, tom,
	bharathsm, henrique.carvalho

Hi Enzo,

kernel test robot noticed the following build warnings:

https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Enzo-Matsumiya/smb-client-remove-cfids_invalidation_worker/20250929-213155
base:   v6.17
patch link:    https://lore.kernel.org/r/20250929132805.220558-17-ematsumiya%40suse.de
patch subject: [PATCH 16/20] smb: client: add is_dir argument to query_path_info
config: i386-randconfig-141-20251003 (https://download.01.org/0day-ci/archive/20251003/202510032329.NN83GCga-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Reported-by: Dan Carpenter <dan.carpenter@linaro.org>
| Closes: https://lore.kernel.org/r/202510032329.NN83GCga-lkp@intel.com/

New smatch warnings:
fs/smb/client/inode.c:1313 cifs_get_fattr() error: we previously assumed 'inode' could be null (see line 1288)

vim +/inode +1313 fs/smb/client/inode.c

a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1259  static int cifs_get_fattr(struct cifs_open_info_data *data,
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1260  			  struct super_block *sb, int xid,
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1261  			  const struct cifs_fid *fid,
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1262  			  struct cifs_fattr *fattr,
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1263  			  struct inode **inode,
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1264  			  const char *full_path)
^1da177e4c3f41 fs/cifs/inode.c       Linus Torvalds                    2005-04-16  1265  {
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1266  	struct cifs_open_info_data tmp_data = {};
1208ef1f76540b fs/cifs/inode.c       Pavel Shilovsky                   2012-05-27  1267  	struct cifs_tcon *tcon;
1208ef1f76540b fs/cifs/inode.c       Pavel Shilovsky                   2012-05-27  1268  	struct TCP_Server_Info *server;
7ffec372458d16 fs/cifs/inode.c       Jeff Layton                       2010-09-29  1269  	struct tcon_link *tlink;
^1da177e4c3f41 fs/cifs/inode.c       Linus Torvalds                    2005-04-16  1270  	struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1271  	void *smb1_backup_rsp_buf = NULL;
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1272  	int rc = 0;
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1273  	int tmprc = 0;
^1da177e4c3f41 fs/cifs/inode.c       Linus Torvalds                    2005-04-16  1274  
7ffec372458d16 fs/cifs/inode.c       Jeff Layton                       2010-09-29  1275  	tlink = cifs_sb_tlink(cifs_sb);
7ffec372458d16 fs/cifs/inode.c       Jeff Layton                       2010-09-29  1276  	if (IS_ERR(tlink))
7ffec372458d16 fs/cifs/inode.c       Jeff Layton                       2010-09-29  1277  		return PTR_ERR(tlink);
1208ef1f76540b fs/cifs/inode.c       Pavel Shilovsky                   2012-05-27  1278  	tcon = tlink_tcon(tlink);
1208ef1f76540b fs/cifs/inode.c       Pavel Shilovsky                   2012-05-27  1279  	server = tcon->ses->server;
7ffec372458d16 fs/cifs/inode.c       Jeff Layton                       2010-09-29  1280  
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1281  	/*
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1282  	 * 1. Fetch file metadata if not provided (data)
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1283  	 */
^1da177e4c3f41 fs/cifs/inode.c       Linus Torvalds                    2005-04-16  1284  
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1285  	if (!data) {
65e58ef1dafb0c fs/smb/client/inode.c Enzo Matsumiya                    2025-09-29  1286  		bool is_dir = false;
65e58ef1dafb0c fs/smb/client/inode.c Enzo Matsumiya                    2025-09-29  1287  
65e58ef1dafb0c fs/smb/client/inode.c Enzo Matsumiya                    2025-09-29 @1288  		if (inode && *inode)

The check implies that "inode" can be NULL.  Pretty sure this
check can be removed unless something changed out of tree.

65e58ef1dafb0c fs/smb/client/inode.c Enzo Matsumiya                    2025-09-29  1289  			is_dir = S_ISDIR((*inode)->i_mode);
65e58ef1dafb0c fs/smb/client/inode.c Enzo Matsumiya                    2025-09-29  1290  
8b4e285d8ce3c6 fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1291  		rc = server->ops->query_path_info(xid, tcon, cifs_sb,
65e58ef1dafb0c fs/smb/client/inode.c Enzo Matsumiya                    2025-09-29  1292  						  full_path, &tmp_data, is_dir);
76894f3e2f7117 fs/cifs/inode.c       Paulo Alcantara                   2022-10-03  1293  		data = &tmp_data;
^1da177e4c3f41 fs/cifs/inode.c       Linus Torvalds                    2005-04-16  1294  	}
0b8f18e358384a fs/cifs/inode.c       Jeff Layton                       2009-07-09  1295  
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1296  	/*
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1297  	 * 2. Convert it to internal cifs metadata (fattr)
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1298  	 */
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1299  
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1300  	switch (rc) {
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1301  	case 0:
2e4564b31b645f fs/cifs/inode.c       Steve French                      2020-10-22  1302  		/*
2e4564b31b645f fs/cifs/inode.c       Steve French                      2020-10-22  1303  		 * If the file is a reparse point, it is more complicated
2e4564b31b645f fs/cifs/inode.c       Steve French                      2020-10-22  1304  		 * since we have to check if its reparse tag matches a known
2e4564b31b645f fs/cifs/inode.c       Steve French                      2020-10-22  1305  		 * special file type e.g. symlink or fifo or char etc.
2e4564b31b645f fs/cifs/inode.c       Steve French                      2020-10-22  1306  		 */
5f71ebc4129449 fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1307  		if (cifs_open_data_reparse(data)) {
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1308  			rc = reparse_info_to_fattr(data, sb, xid, tcon,
858e74876c5cbf fs/smb/client/inode.c Paulo Alcantara                   2024-01-19  1309  						   full_path, fattr);
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1310  		} else {
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1311  			cifs_open_info_to_fattr(fattr, data, sb);
76894f3e2f7117 fs/cifs/inode.c       Paulo Alcantara                   2022-10-03  1312  		}
ec4535b2a1d709 fs/smb/client/inode.c Paulo Alcantara                   2024-04-08 @1313  		if (!rc && *inode &&
                                                                                                                   ^^^^^^

The rest of the function assumes inode is a valid pointer.

regards,
dan carpenter

ec4535b2a1d709 fs/smb/client/inode.c Paulo Alcantara                   2024-04-08  1314  		    (fattr->cf_flags & CIFS_FATTR_DELETE_PENDING))
fc20c523211a38 fs/smb/client/inode.c Meetakshi Setiya                  2024-03-14  1315  			cifs_mark_open_handles_for_deleted_file(*inode, full_path);
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1316  		break;
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1317  	case -EREMOTE:
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1318  		/* DFS link, no metadata available on this server */
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1319  		cifs_create_junction_fattr(fattr, sb);
b9a3260f25ab5d fs/cifs/inode.c       Steve French                      2008-05-20  1320  		rc = 0;
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1321  		break;
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1322  	case -EACCES:
fb157ed226d225 fs/cifs/inode.c       Steve French                      2022-08-01  1323  #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
1e77a8c204c9d1 fs/cifs/inode.c       Steve French                      2018-10-19  1324  		/*
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1325  		 * perm errors, try again with backup flags if possible
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1326  		 *
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1327  		 * For SMB2 and later the backup intent flag
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1328  		 * is already sent if needed on open and there
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1329  		 * is no path based FindFirst operation to use
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1330  		 * to retry with
1e77a8c204c9d1 fs/cifs/inode.c       Steve French                      2018-10-19  1331  		 */
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1332  		if (backup_cred(cifs_sb) && is_smb1_server(server)) {
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1333  			/* for easier reading */
76894f3e2f7117 fs/cifs/inode.c       Paulo Alcantara                   2022-10-03  1334  			FILE_ALL_INFO *fi;
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1335  			FILE_DIRECTORY_INFO *fdi;
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1336  			SEARCH_ID_FULL_DIR_INFO *si;
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1337  
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1338  			rc = cifs_backup_query_path_info(xid, tcon, sb,
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1339  							 full_path,
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1340  							 &smb1_backup_rsp_buf,
76894f3e2f7117 fs/cifs/inode.c       Paulo Alcantara                   2022-10-03  1341  							 &fi);
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1342  			if (rc)
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1343  				goto out;
1e77a8c204c9d1 fs/cifs/inode.c       Steve French                      2018-10-19  1344  
76894f3e2f7117 fs/cifs/inode.c       Paulo Alcantara                   2022-10-03  1345  			move_cifs_info_to_smb2(&data->fi, fi);
76894f3e2f7117 fs/cifs/inode.c       Paulo Alcantara                   2022-10-03  1346  			fdi = (FILE_DIRECTORY_INFO *)fi;
76894f3e2f7117 fs/cifs/inode.c       Paulo Alcantara                   2022-10-03  1347  			si = (SEARCH_ID_FULL_DIR_INFO *)fi;
c052e2b423f3ea fs/cifs/inode.c       Shirish Pargaonkar                2012-09-28  1348  
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1349  			cifs_dir_info_to_fattr(fattr, fdi, cifs_sb);
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1350  			fattr->cf_uniqueid = le64_to_cpu(si->UniqueId);
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1351  			/* uniqueid set, skip get inum step */
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1352  			goto handle_mnt_opt;
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1353  		} else {
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1354  			/* nothing we can do, bail out */
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1355  			goto out;
c052e2b423f3ea fs/cifs/inode.c       Shirish Pargaonkar                2012-09-28  1356  		}
fb157ed226d225 fs/cifs/inode.c       Steve French                      2022-08-01  1357  #else
fb157ed226d225 fs/cifs/inode.c       Steve French                      2022-08-01  1358  		goto out;
fb157ed226d225 fs/cifs/inode.c       Steve French                      2022-08-01  1359  #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1360  		break;
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1361  	default:
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1362  		cifs_dbg(FYI, "%s: unhandled err rc %d\n", __func__, rc);
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1363  		goto out;
132ac7b77cc95a fs/cifs/inode.c       Jeff Layton                       2009-02-10  1364  	}
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1365  
a108471b5730b5 fs/cifs/inode.c       Ross Lagerwall                    2015-12-02  1366  	/*
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1367  	 * 3. Get or update inode number (fattr->cf_uniqueid)
a108471b5730b5 fs/cifs/inode.c       Ross Lagerwall                    2015-12-02  1368  	 */
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1369  
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1370  	cifs_set_fattr_ino(xid, tcon, sb, inode, full_path, data, fattr);
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1371  
7ea884c77e5c97 fs/cifs/inode.c       Steve French                      2018-03-31  1372  	/*
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1373  	 * 4. Tweak fattr based on mount options
7ea884c77e5c97 fs/cifs/inode.c       Steve French                      2018-03-31  1374  	 */
fb157ed226d225 fs/cifs/inode.c       Steve French                      2022-08-01  1375  #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1376  handle_mnt_opt:
fb157ed226d225 fs/cifs/inode.c       Steve French                      2022-08-01  1377  #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
0b8f18e358384a fs/cifs/inode.c       Jeff Layton                       2009-07-09  1378  	/* query for SFU type info if supported and needed */
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1379  	if ((fattr->cf_cifsattrs & ATTR_SYSTEM) &&
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1380  	    (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UNX_EMUL)) {
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1381  		tmprc = cifs_sfu_type(fattr, full_path, cifs_sb, xid);
0b8f18e358384a fs/cifs/inode.c       Jeff Layton                       2009-07-09  1382  		if (tmprc)
f96637be081141 fs/cifs/inode.c       Joe Perches                       2013-05-04  1383  			cifs_dbg(FYI, "cifs_sfu_type failed: %d\n", tmprc);
^1da177e4c3f41 fs/cifs/inode.c       Linus Torvalds                    2005-04-16  1384  	}
^1da177e4c3f41 fs/cifs/inode.c       Linus Torvalds                    2005-04-16  1385  
953f868138dbf4 fs/cifs/inode.c       Steve French                      2007-10-31  1386  	/* fill in 0777 bits from ACL */
e2f8fbfb8d09c0 fs/cifs/inode.c       Steve French                      2019-07-19  1387  	if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MODE_FROM_SID) {
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1388  		rc = cifs_acl_to_fattr(cifs_sb, fattr, *inode,
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1389  				       true, full_path, fid);
01ec372cef1e5a fs/cifs/inode.c       Ronnie Sahlberg                   2020-09-03  1390  		if (rc == -EREMOTE)
01ec372cef1e5a fs/cifs/inode.c       Ronnie Sahlberg                   2020-09-03  1391  			rc = 0;
e2f8fbfb8d09c0 fs/cifs/inode.c       Steve French                      2019-07-19  1392  		if (rc) {
e2f8fbfb8d09c0 fs/cifs/inode.c       Steve French                      2019-07-19  1393  			cifs_dbg(FYI, "%s: Get mode from SID failed. rc=%d\n",
e2f8fbfb8d09c0 fs/cifs/inode.c       Steve French                      2019-07-19  1394  				 __func__, rc);
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1395  			goto out;
e2f8fbfb8d09c0 fs/cifs/inode.c       Steve French                      2019-07-19  1396  		}
e2f8fbfb8d09c0 fs/cifs/inode.c       Steve French                      2019-07-19  1397  	} else if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_CIFS_ACL) {
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1398  		rc = cifs_acl_to_fattr(cifs_sb, fattr, *inode,
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1399  				       false, full_path, fid);
01ec372cef1e5a fs/cifs/inode.c       Ronnie Sahlberg                   2020-09-03  1400  		if (rc == -EREMOTE)
01ec372cef1e5a fs/cifs/inode.c       Ronnie Sahlberg                   2020-09-03  1401  			rc = 0;
68464b88cc0a73 fs/cifs/inode.c       Dan Carpenter via samba-technical 2019-11-26  1402  		if (rc) {
f96637be081141 fs/cifs/inode.c       Joe Perches                       2013-05-04  1403  			cifs_dbg(FYI, "%s: Getting ACL failed with error: %d\n",
78415d2d306bfe fs/cifs/inode.c       Shirish Pargaonkar                2010-11-27  1404  				 __func__, rc);
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1405  			goto out;
78415d2d306bfe fs/cifs/inode.c       Shirish Pargaonkar                2010-11-27  1406  		}
2f3017e7cc7515 fs/smb/client/inode.c Steve French                      2024-09-21  1407  	} else if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UNX_EMUL)
0b8f18e358384a fs/cifs/inode.c       Jeff Layton                       2009-07-09  1408  		/* fill in remaining high mode bits e.g. SUID, VTX */
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1409  		cifs_sfu_mode(fattr, full_path, cifs_sb, xid);
2f3017e7cc7515 fs/smb/client/inode.c Steve French                      2024-09-21  1410  	else if (!(tcon->posix_extensions))
2f3017e7cc7515 fs/smb/client/inode.c Steve French                      2024-09-21  1411  		/* clear write bits if ATTR_READONLY is set */
2f3017e7cc7515 fs/smb/client/inode.c Steve French                      2024-09-21  1412  		if (fattr->cf_cifsattrs & ATTR_READONLY)
2f3017e7cc7515 fs/smb/client/inode.c Steve French                      2024-09-21  1413  			fattr->cf_mode &= ~(S_IWUGO);
2f3017e7cc7515 fs/smb/client/inode.c Steve French                      2024-09-21  1414  
b9a3260f25ab5d fs/cifs/inode.c       Steve French                      2008-05-20  1415  
1b12b9c15b4371 fs/cifs/inode.c       Stefan Metzmacher                 2010-08-05  1416  	/* check for Minshall+French symlinks */
1b12b9c15b4371 fs/cifs/inode.c       Stefan Metzmacher                 2010-08-05  1417  	if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MF_SYMLINKS) {
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1418  		tmprc = check_mf_symlink(xid, tcon, cifs_sb, fattr, full_path);
cb084b1a9be347 fs/cifs/inode.c       Sachin Prabhu                     2013-11-25  1419  		cifs_dbg(FYI, "check_mf_symlink: %d\n", tmprc);
1b12b9c15b4371 fs/cifs/inode.c       Stefan Metzmacher                 2010-08-05  1420  	}
1b12b9c15b4371 fs/cifs/inode.c       Stefan Metzmacher                 2010-08-05  1421  
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1422  out:
b8f7442bc46e48 fs/cifs/inode.c       Aurelien Aptel                    2019-11-18  1423  	cifs_buf_release(smb1_backup_rsp_buf);
7ffec372458d16 fs/cifs/inode.c       Jeff Layton                       2010-09-29  1424  	cifs_put_tlink(tlink);
76894f3e2f7117 fs/cifs/inode.c       Paulo Alcantara                   2022-10-03  1425  	cifs_free_open_info(&tmp_data);
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1426  	return rc;
a18280e7fdea1f fs/smb/client/inode.c Paulo Alcantara                   2023-08-17  1427  }

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki


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

* Re: [PATCH 13/20] smb: client: actually use cached dirs on readdir
  2025-09-29 13:27 ` [PATCH 13/20] smb: client: actually use cached dirs on readdir Enzo Matsumiya
@ 2025-10-03 17:26   ` Dan Carpenter
  0 siblings, 0 replies; 29+ messages in thread
From: Dan Carpenter @ 2025-10-03 17:26 UTC (permalink / raw)
  To: oe-kbuild, Enzo Matsumiya, linux-cifs
  Cc: lkp, oe-kbuild-all, smfrench, pc, ronniesahlberg, sprasad, tom,
	bharathsm, henrique.carvalho

Hi Enzo,

kernel test robot noticed the following build warnings:

https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Enzo-Matsumiya/smb-client-remove-cfids_invalidation_worker/20250929-213155
base:   v6.17
patch link:    https://lore.kernel.org/r/20250929132805.220558-14-ematsumiya%40suse.de
patch subject: [PATCH 13/20] smb: client: actually use cached dirs on readdir
config: i386-randconfig-141-20251003 (https://download.01.org/0day-ci/archive/20251003/202510032035.eEAqpgDl-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Reported-by: Dan Carpenter <dan.carpenter@linaro.org>
| Closes: https://lore.kernel.org/r/202510032035.eEAqpgDl-lkp@intel.com/

smatch warnings:
fs/smb/client/readdir.c:1119 cifs_readdir() error: uninitialized symbol 'tcon'.

vim +/tcon +1119 fs/smb/client/readdir.c

be4ccdcc2575ae fs/cifs/readdir.c       Al Viro          2013-05-22  1041  int cifs_readdir(struct file *file, struct dir_context *ctx)
^1da177e4c3f41 fs/cifs/readdir.c       Linus Torvalds   2005-04-16  1042  {
^1da177e4c3f41 fs/cifs/readdir.c       Linus Torvalds   2005-04-16  1043  	int rc = 0;
6d5786a34d98bf fs/cifs/readdir.c       Pavel Shilovsky  2012-06-20  1044  	unsigned int xid;
6d5786a34d98bf fs/cifs/readdir.c       Pavel Shilovsky  2012-06-20  1045  	int i;
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1046  	struct tcon_link *tlink = NULL;
92fc65a74a2be1 fs/cifs/readdir.c       Pavel Shilovsky  2012-09-18  1047  	struct cifs_tcon *tcon;
ec217637e7f16d fs/smb/client/readdir.c Enzo Matsumiya   2025-09-29  1048  	struct cifsFileInfo *cifsFile = NULL;
^1da177e4c3f41 fs/cifs/readdir.c       Linus Torvalds   2005-04-16  1049  	char *current_entry;
^1da177e4c3f41 fs/cifs/readdir.c       Linus Torvalds   2005-04-16  1050  	int num_to_fill = 0;
^1da177e4c3f41 fs/cifs/readdir.c       Linus Torvalds   2005-04-16  1051  	char *tmp_buf = NULL;
^1da177e4c3f41 fs/cifs/readdir.c       Linus Torvalds   2005-04-16  1052  	char *end_of_smb;
18295796a30cad fs/cifs/readdir.c       Jeff Layton      2009-04-30  1053  	unsigned int max_len;
f6a9bc336b600e fs/cifs/readdir.c       Al Viro          2021-03-05  1054  	const char *full_path;
f6a9bc336b600e fs/cifs/readdir.c       Al Viro          2021-03-05  1055  	void *page = alloc_dentry_path();
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1056  	struct cached_fid *cfid = NULL;
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1057  	struct cifs_sb_info *cifs_sb = CIFS_FILE_SB(file);
^1da177e4c3f41 fs/cifs/readdir.c       Linus Torvalds   2005-04-16  1058  
6d5786a34d98bf fs/cifs/readdir.c       Pavel Shilovsky  2012-06-20  1059  	xid = get_xid();
^1da177e4c3f41 fs/cifs/readdir.c       Linus Torvalds   2005-04-16  1060  
f6a9bc336b600e fs/cifs/readdir.c       Al Viro          2021-03-05  1061  	full_path = build_path_from_dentry(file_dentry(file), page);
f6a9bc336b600e fs/cifs/readdir.c       Al Viro          2021-03-05  1062  	if (IS_ERR(full_path)) {
f6a9bc336b600e fs/cifs/readdir.c       Al Viro          2021-03-05  1063  		rc = PTR_ERR(full_path);
d1542cf6165e52 fs/cifs/readdir.c       Ronnie Sahlberg  2020-10-05  1064  		goto rddir2_exit;
d1542cf6165e52 fs/cifs/readdir.c       Ronnie Sahlberg  2020-10-05  1065  	}
d1542cf6165e52 fs/cifs/readdir.c       Ronnie Sahlberg  2020-10-05  1066  
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1067  	if (file->private_data == NULL) {
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1068  		tlink = cifs_sb_tlink(cifs_sb);
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1069  		if (IS_ERR(tlink))
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1070  			goto cache_not_found;

tcon is not initialized yet.  private_data is NULL.

d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1071  		tcon = tlink_tcon(tlink);
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1072  	} else {
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1073  		cifsFile = file->private_data;
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1074  		tcon = tlink_tcon(cifsFile->tlink);
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1075  	}
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1076  
7d24f0ff5dad1f fs/smb/client/readdir.c Enzo Matsumiya   2025-09-29  1077  	rc = open_cached_dir(xid, tcon, full_path, cifs_sb, &cfid);
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1078  	cifs_put_tlink(tlink);
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1079  	if (rc)
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1080  		goto cache_not_found;
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1081  
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1082  	mutex_lock(&cfid->dirents.de_mutex);
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1083  	/*
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1084  	 * If this was reading from the start of the directory
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1085  	 * we need to initialize scanning and storing the
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1086  	 * directory content.
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1087  	 */
72dd7961a4bb4f fs/smb/client/readdir.c Bharath SM       2025-06-11  1088  	if (ctx->pos == 0 && cfid->dirents.file == NULL) {
72dd7961a4bb4f fs/smb/client/readdir.c Bharath SM       2025-06-11  1089  		cfid->dirents.file = file;
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1090  		cfid->dirents.pos = 2;
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1091  	}
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1092  	/*
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1093  	 * If we already have the entire directory cached then
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1094  	 * we can just serve the cache.
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1095  	 */
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1096  	if (cfid->dirents.is_valid) {
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1097  		if (!dir_emit_dots(file, ctx)) {
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1098  			mutex_unlock(&cfid->dirents.de_mutex);
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1099  			goto rddir2_exit;
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1100  		}
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1101  		emit_cached_dirents(&cfid->dirents, ctx);
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1102  		mutex_unlock(&cfid->dirents.de_mutex);
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1103  		goto rddir2_exit;
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1104  	}
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1105  	mutex_unlock(&cfid->dirents.de_mutex);
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1106  
ec217637e7f16d fs/smb/client/readdir.c Enzo Matsumiya   2025-09-29  1107  	/* keep our cfid ref, but check if still valid after network calls */
d87c48ce4d8951 fs/cifs/readdir.c       Ronnie Sahlberg  2022-05-10  1108  cache_not_found:
6221ddd0f5e2dd fs/cifs/readdir.c       Suresh Jayaraman 2010-10-01  1109  	/*
6221ddd0f5e2dd fs/cifs/readdir.c       Suresh Jayaraman 2010-10-01  1110  	 * Ensure FindFirst doesn't fail before doing filldir() for '.' and
6221ddd0f5e2dd fs/cifs/readdir.c       Suresh Jayaraman 2010-10-01  1111  	 * '..'. Otherwise we won't be able to notify VFS in case of failure.
6221ddd0f5e2dd fs/cifs/readdir.c       Suresh Jayaraman 2010-10-01  1112  	 */
6221ddd0f5e2dd fs/cifs/readdir.c       Suresh Jayaraman 2010-10-01  1113  	if (file->private_data == NULL) {
ec217637e7f16d fs/smb/client/readdir.c Enzo Matsumiya   2025-09-29  1114  		rc = initiate_cifs_search(xid, file, full_path, cfid);
f96637be081141 fs/cifs/readdir.c       Joe Perches      2013-05-04  1115  		cifs_dbg(FYI, "initiate cifs search rc %d\n", rc);
6221ddd0f5e2dd fs/cifs/readdir.c       Suresh Jayaraman 2010-10-01  1116  		if (rc)
6221ddd0f5e2dd fs/cifs/readdir.c       Suresh Jayaraman 2010-10-01  1117  			goto rddir2_exit;
ec217637e7f16d fs/smb/client/readdir.c Enzo Matsumiya   2025-09-29  1118  
ec217637e7f16d fs/smb/client/readdir.c Enzo Matsumiya   2025-09-29 @1119  		if (tcon->status != TID_GOOD || (cfid && !cfid_is_valid(cfid))) {
                                                                                            ^^^^^^^^^^^^
Uninitialized?

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki


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

end of thread, other threads:[~2025-10-03 17:26 UTC | newest]

Thread overview: 29+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-29 13:27 [PATCH 00/20] smb: client: cached dir fixes and improvements Enzo Matsumiya
2025-09-29 13:27 ` [PATCH 01/20] smb: client: remove cfids_invalidation_worker Enzo Matsumiya
2025-09-29 13:27 ` [PATCH 02/20] smb: client: remove cached_dir_offload_close/close_work Enzo Matsumiya
2025-09-29 13:27 ` [PATCH 03/20] smb: client: remove cached_dir_put_work/put_work Enzo Matsumiya
2025-09-29 13:27 ` [PATCH 04/20] smb: client: remove cached_fids->dying list Enzo Matsumiya
2025-09-29 13:27 ` [PATCH 05/20] smb: client: remove cached_fid->on_list Enzo Matsumiya
2025-09-29 13:27 ` [PATCH 06/20] smb: client: merge {close,invalidate}_all_cached_dirs() Enzo Matsumiya
2025-09-29 13:27 ` [PATCH 07/20] smb: client: merge free_cached_dir in release callback Enzo Matsumiya
2025-09-29 13:27 ` [PATCH 08/20] smb: client: split find_or_create_cached_dir() Enzo Matsumiya
2025-09-29 13:27 ` [PATCH 09/20] smb: client: enhance cached dir lookups Enzo Matsumiya
2025-09-29 13:27 ` [PATCH 10/20] smb: client: refactor dropping cached dirs Enzo Matsumiya
2025-09-29 13:27 ` [PATCH 11/20] smb: client: simplify cached_fid state checking Enzo Matsumiya
2025-09-29 13:27 ` [PATCH 12/20] smb: client: prevent lease breaks of cached parents when opening children Enzo Matsumiya
2025-09-29 14:23   ` Steve French
2025-09-29 17:17   ` Enzo Matsumiya
2025-09-29 13:27 ` [PATCH 13/20] smb: client: actually use cached dirs on readdir Enzo Matsumiya
2025-10-03 17:26   ` Dan Carpenter
2025-09-29 13:27 ` [PATCH 14/20] smb: client: wait for concurrent caching of dirents in cifs_readdir() Enzo Matsumiya
2025-09-29 13:28 ` [PATCH 15/20] smb: client: remove cached_dirent->fattr Enzo Matsumiya
2025-09-29 13:28 ` [PATCH 16/20] smb: client: add is_dir argument to query_path_info Enzo Matsumiya
2025-10-03 17:20   ` Dan Carpenter
2025-09-29 13:28 ` [PATCH 17/20] smb: client: use cached dir on queryfs/smb2_compound_op Enzo Matsumiya
2025-09-29 14:26   ` Steve French
2025-09-29 13:28 ` [PATCH 18/20] smb: client: fix dentry revalidation of cached root Enzo Matsumiya
2025-09-29 13:28 ` [PATCH 19/20] smb: client: rework cached dirs synchronization Enzo Matsumiya
2025-09-30 19:02   ` kernel test robot
2025-09-29 13:28 ` [PATCH 20/20] smb: client: cleanup open_cached_dir() Enzo Matsumiya
2025-09-29 14:05 ` [PATCH 00/20] smb: client: cached dir fixes and improvements Steve French
2025-09-29 14:44   ` Enzo Matsumiya

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox