diff options
| author | Enzo Matsumiya <ematsumiya@suse.de> | 2025-09-12 14:00:22 -0300 |
|---|---|---|
| committer | Enzo Matsumiya <ematsumiya@suse.de> | 2025-09-12 14:48:30 -0300 |
| commit | 255a29cedaef5eb1923a898a088688c3dca61400 (patch) | |
| tree | 94789dbf40df410fdd49fe20ca7532dc52750dcf | |
| parent | 3d02bd42bd531fe86d7ec9dd46d4ee301d5c9f17 (diff) | |
| download | linux-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.c | 197 | ||||
| -rw-r--r-- | fs/smb/client/cached_dir.h | 17 | ||||
| -rw-r--r-- | fs/smb/client/inode.c | 11 | ||||
| -rw-r--r-- | fs/smb/client/readdir.c | 4 | ||||
| -rw-r--r-- | fs/smb/client/smb2inode.c | 10 | ||||
| -rw-r--r-- | fs/smb/client/smb2ops.c | 15 |
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, ¤t_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; |
