summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEnzo Matsumiya <ematsumiya@suse.de>2025-09-12 14:00:22 -0300
committerEnzo Matsumiya <ematsumiya@suse.de>2025-09-12 14:48:30 -0300
commit255a29cedaef5eb1923a898a088688c3dca61400 (patch)
tree94789dbf40df410fdd49fe20ca7532dc52750dcf
parent3d02bd42bd531fe86d7ec9dd46d4ee301d5c9f17 (diff)
downloadlinux-255a29cedaef5eb1923a898a088688c3dca61400.tar.gz
linux-255a29cedaef5eb1923a898a088688c3dca61400.tar.bz2
linux-255a29cedaef5eb1923a898a088688c3dca61400.zip
smb: client: enhance cached dir lookups
Add find_cfid() to allow looking up the 3 modes (path, dentry, and lease key) used to find a cached dir. 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>
-rw-r--r--fs/smb/client/cached_dir.c197
-rw-r--r--fs/smb/client/cached_dir.h17
-rw-r--r--fs/smb/client/inode.c11
-rw-r--r--fs/smb/client/readdir.c4
-rw-r--r--fs/smb/client/smb2inode.c10
-rw-r--r--fs/smb/client/smb2ops.c15
6 files changed, 136 insertions, 118 deletions
diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index 7b240e8d83f4..ecd33c75bc80 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -37,27 +37,46 @@ static inline void invalidate_cfid(struct cached_fid *cfid, bool force_expire)
cfid->last_access_time = 1;
}
-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).
+ *
+ * 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)
{
- struct cached_fid *cfid;
+ struct cached_fid *cfid, *found = NULL;
+ bool match;
+
+ if (!cfids || !key)
+ return 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;
- kref_get(&cfid->refcount);
- return cfid;
- }
+ 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;
+
+ kref_get(&cfid->refcount);
+ found = cfid;
+ break;
}
+ spin_unlock(&cfids->cfid_list_lock);
- return NULL;
+ return found;
}
static struct dentry *
@@ -117,13 +136,46 @@ static const char *path_no_prefix(struct cifs_sb_info *cifs_sb,
}
/*
+ * 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;
+
+retry_find:
+ cfid = find_cfid(cfids, key, mode);
+ if (cfid) {
+ if (cfid_is_valid(cfid)) {
+ cfid->last_access_time = jiffies;
+ } else {
+ bool retry;
+
+ /* cfid is being opened in open_cached_dir() */
+ retry = (cfid->has_lease && !cfid->time && !cfid->last_access_time);
+ kref_put(&cfid->refcount, smb2_close_cached_fid);
+
+ if (retry)
+ goto retry_find;
+
+ 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;
@@ -139,7 +191,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;
@@ -161,6 +213,9 @@ replay_again:
/* 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)
@@ -170,27 +225,25 @@ replay_again:
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;
@@ -377,15 +430,17 @@ out:
rc = -ENOENT;
if (rc) {
- spin_lock(&cfids->cfid_list_lock);
- /* we don't know what caused the invalidation, so don't force cleanup */
- invalidate_cfid(cfid, false);
- spin_unlock(&cfids->cfid_list_lock);
+ if (cfid) {
+ spin_lock(&cfids->cfid_list_lock);
+ /* we don't know what caused the invalidation, so don't force cleanup */
+ invalidate_cfid(cfid, false);
+ spin_unlock(&cfids->cfid_list_lock);
- if (!cfid->dentry)
- dput(dentry);
+ dput(cfid->dentry);
+ cfid->dentry = NULL;
- kref_put(&cfid->refcount, smb2_close_cached_fid);
+ kref_put(&cfid->refcount, smb2_close_cached_fid);
+ }
} else {
*ret_cfid = cfid;
atomic_inc(&tcon->num_remote_opens);
@@ -399,32 +454,7 @@ out:
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_fids *cfids = cfid->cfids;
@@ -461,7 +491,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;
@@ -543,34 +573,25 @@ 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)
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, true);
- cfid->has_lease = false;
- found = true;
- break;
- }
- }
- spin_unlock(&cfids->cfid_list_lock);
+ /* use raw lookup here as we _must_ find our lease, no matter cfid state */
+ cfid = find_cfid(cfids, lease_key, CFID_LOOKUP_LEASEKEY);
+ if (cfid) {
+ /* found a lease, invalidate cfid and schedule immediate cleanup on laundromat */
+ invalidate_cfid(cfid, true);
+ cfid->has_lease = false;
- 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 59682fd30377..3c74def05726 100644
--- a/fs/smb/client/cached_dir.h
+++ b/fs/smb/client/cached_dir.h
@@ -59,14 +59,17 @@ struct cached_fids {
struct delayed_work laundromat_work;
};
+/* Lookup modes for find_cached_dir() */
+enum {
+ CFID_LOOKUP_PATH,
+ CFID_LOOKUP_DENTRY,
+ CFID_LOOKUP_LEASEKEY,
+};
+
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 @@ replay_again:
* 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;