diff options
| author | Enzo Matsumiya <ematsumiya@suse.de> | 2025-03-12 09:40:11 -0300 |
|---|---|---|
| committer | Enzo Matsumiya <ematsumiya@suse.de> | 2025-03-18 12:24:48 -0300 |
| commit | de4c67c30cdc6cd8814e790486f044a102ed0f92 (patch) | |
| tree | e729fec75a7305a829fe5d093953ffa725904d65 | |
| parent | a8d12f5d67d9d0fd58daad72e3aa4cc46213b4f7 (diff) | |
| download | linux-de4c67c30cdc6cd8814e790486f044a102ed0f92.tar.gz linux-de4c67c30cdc6cd8814e790486f044a102ed0f92.tar.bz2 linux-de4c67c30cdc6cd8814e790486f044a102ed0f92.zip | |
smb: client: cached dir rewrite
TODO
Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
| -rw-r--r-- | fs/smb/client/cached_dir.c | 1259 | ||||
| -rw-r--r-- | fs/smb/client/cached_dir.h | 146 | ||||
| -rw-r--r-- | fs/smb/client/cifs_debug.c | 26 | ||||
| -rw-r--r-- | fs/smb/client/cifsfs.c | 2 | ||||
| -rw-r--r-- | fs/smb/client/cifsglob.h | 3 | ||||
| -rw-r--r-- | fs/smb/client/connect.c | 3 | ||||
| -rw-r--r-- | fs/smb/client/file.c | 3 | ||||
| -rw-r--r-- | fs/smb/client/inode.c | 13 | ||||
| -rw-r--r-- | fs/smb/client/misc.c | 14 | ||||
| -rw-r--r-- | fs/smb/client/readdir.c | 189 | ||||
| -rw-r--r-- | fs/smb/client/smb2inode.c | 39 | ||||
| -rw-r--r-- | fs/smb/client/smb2misc.c | 7 | ||||
| -rw-r--r-- | fs/smb/client/smb2ops.c | 91 | ||||
| -rw-r--r-- | fs/smb/client/smb2pdu.c | 2 |
14 files changed, 927 insertions, 870 deletions
diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c index fe738623cf1b..ed8bd00dee97 100644 --- a/fs/smb/client/cached_dir.c +++ b/fs/smb/client/cached_dir.c @@ -1,94 +1,66 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Functions to handle the cached directory entries + * Functions to handle cached directories. * - * Copyright (c) 2022, Ronnie Sahlberg <lsahlber@redhat.com> + * Author: Enzo Matsumiya <ematsumiya@suse.de> + * Copyright (c) 2025, SUSE GmbH */ - +#include <linux/seqlock.h> #include <linux/namei.h> -#include "cifsglob.h" +#include "cached_dir.h" #include "cifsproto.h" -#include "cifs_debug.h" #include "smb2proto.h" -#include "cached_dir.h" +#include "smb2pdu.h" +#include "cifs_debug.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 void cfids_laundromat_worker(struct work_struct *work); +#define CDIR_DEBUG -struct cached_dir_dentry { - struct list_head entry; - struct dentry *dentry; -}; +#ifdef CDIR_DEBUG +#define cdir_dbg(fmt, ...) pr_err(fmt, __VA_ARGS__) +#else +#define cdir_dbg(fmt, ...) +#endif -static struct cached_fid *find_or_create_cached_dir(struct cached_fids *cfids, - const char *path, - bool lookup_only, - __u32 max_cached_dirs) -{ - struct cached_fid *cfid; - - 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->time || !cfid->has_lease) { - spin_unlock(&cfids->cfid_list_lock); - return NULL; - } - kref_get(&cfid->refcount); - spin_unlock(&cfids->cfid_list_lock); - return cfid; - } - } - if (lookup_only) { - spin_unlock(&cfids->cfid_list_lock); - return NULL; - } - if (cfids->num_entries >= max_cached_dirs) { - spin_unlock(&cfids->cfid_list_lock); - return NULL; - } - cfid = init_cached_dir(path); - if (cfid == NULL) { - spin_unlock(&cfids->cfid_list_lock); - return NULL; - } - 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 - * 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; +//static void cdir_release(struct kref *ref); +//static inline void cdir_invalidate(struct cached_dir_internal *cdir); +static void cdir_remote_close(struct cached_dirs *cdirs, unsigned long long pfid, unsigned long long vfid); + +/* + * Utils + */ + +/* XXX: ok to do this? cdir::path is for reference/search purposes only afterall */ +static inline const char *root_path(const char *path) +{ + if (!*path) + return "\\"; - spin_unlock(&cfids->cfid_list_lock); - return cfid; + return path; } -static struct dentry * -path_to_dentry(struct cifs_sb_info *cifs_sb, const char *path) +static struct dentry *path_to_dentry(struct cifs_sb_info *cifs_sb, const char *path) { - struct dentry *dentry; - const char *s, *p; - char sep; + struct dentry *dentry = dget(cifs_sb->root); + const char *p, *s; + char sep = CIFS_DIR_SEP(cifs_sb); - sep = CIFS_DIR_SEP(cifs_sb); - dentry = dget(cifs_sb->root); - s = path; + if (!*path) + return dentry; + + /* + * Skip any prefix paths in @path as lookup_positive_unlocked() ends up calling ->lookup() + * which already adds those through build_path_from_dentry(). + */ + 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 = path + len; + } + + s = path; do { struct inode *dir = d_inode(dentry); struct dentry *child; @@ -102,9 +74,11 @@ path_to_dentry(struct cifs_sb_info *cifs_sb, const char *path) /* skip separators */ while (*s == sep) s++; + if (!*s) break; p = s++; + /* next separator */ while (*s && *s != sep) s++; @@ -113,173 +87,397 @@ path_to_dentry(struct cifs_sb_info *cifs_sb, const char *path) 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) +static void cdir_entry_free_all(struct cached_dir_entries *cde) { - size_t len = 0; + struct cached_dir_entry *de, *q; - if (!*path) - return path; + //mutex_lock(&cde->lock); + list_for_each_entry_safe(de, q, &cde->list, head) { + list_del(&de->head); + kfree(de->name); + kfree(de); + } + //mutex_unlock(&cde->lock); - 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); + cde->ctx = ERR_PTR(-ENOENT); +} + +/* + * Internal ops. + */ + +/* + * This function frees the whole cdir. + * + * cdir_remote_close() and dput() must have been handled by callers, e.g. save cdir::fid and/or + * cdir::dentry BEFORE calling this function and act accordingly. + */ +static void cdir_free(struct cached_dir *cdir) +{ + if (!cdir) + return; + + kfree_const(cdir->path); + kfree(cdir->lease_key); + kfree(cdir->info); + + cdir_entry_free_all(&cdir->dir_entries); +} + +static bool cdir_expired_unlocked(struct cached_dir *cdir, unsigned long t) +{ + if (!t) + return time_is_before_jiffies(cdir->time + dir_cache_timeout * HZ); + + return time_before(cdir->time, t); +} + +static bool cdir_is_valid_unlocked(struct cached_dir *cdir) +{ + return !cdir_expired_unlocked(cdir, 0); +} + +static bool cdir_is_valid(struct cached_dir *cdir, unsigned long t) +{ + unsigned int seq = read_seqbegin(&cdir->seq); + bool valid; + + do { + valid = cdir_expired_unlocked(cdir, t); + } while (read_seqretry(&cdir->seq, seq)); + + return valid; +} + +static bool cdir_match_unlocked(struct cached_dir *cdir, const void *key, int mode) +{ + switch (mode) { + case CDIR_FIND_PATH: + return (cdir->path && !strcmp(cdir->path, key)); + case CDIR_FIND_DENTRY: + return (cdir->dentry && cdir->dentry == key); + case CDIR_FIND_LEASEKEY: + return (cdir->lease_key && !memcmp(cdir->lease_key, key, 16)); + case CDIR_FIND_CDE: + return (cdir->dir_entries.ctx == key); + } + + return false; +} + +static bool cdir_match(struct cached_dir *cdir, const void *key, int mode) +{ + unsigned int seq = read_seqbegin(&cdir->seq); + bool valid = false; + + do { + valid = (cdir_match_unlocked(cdir, key, mode) && + cdir_is_valid_unlocked(cdir)); + } while (read_seqretry(&cdir->seq, seq)); + + return valid; +} + +/* + * Mark a cdir as expired (i.e. invalid). + * + * Since this can be called from several different contexts, we don't free/close/put anything else + * here. + */ +static inline void cdir_invalidate_unlocked(struct cached_dir *cdir) +{ + if (!cdir) + return; + + cdir->time = 0; +} + +static struct cached_dir *cdir_alloc(void) +{ + struct cached_dir *cdir = kzalloc(sizeof(*cdir), GFP_KERNEL); + + if (!cdir) + return NULL; + + /* This is our internal ref, cdir stays on the list until it expires/leasebreak/drop. */ + INIT_LIST_HEAD(&cdir->head); + seqlock_init(&cdir->seq); + + INIT_LIST_HEAD(&cdir->dir_entries.list); + mutex_init(&cdir->dir_entries.lock); + //cdir->dir_entries.valid = false; + + return cdir; +} + +/* + * Management ops for tcon::cdirs. + */ + +/* + * Add an entry with the provided data to cdirs::entries list. + */ +static void cdirs_add_entry(struct cached_dirs *cdirs, struct cached_dir *cdir) +{ + write_seqlock(&cdirs->seq); + list_add(&cdir->head, &cdirs->entries); + atomic_inc(&cdirs->count); + write_sequnlock(&cdirs->seq); +} + +static struct cached_dir *cdirs_init_entry(struct cached_dirs *cdirs, const char *path, + struct dentry *dentry, struct cifs_fid *fid, + struct smb2_file_all_info *info) +{ + struct cached_dir *cdir = cdir_alloc(); + + if (!cdir) + return ERR_PTR(-ENOMEM); + + if (IS_ERR(dentry)) + return ERR_CAST(dentry); + + cdir->dentry = dentry; + cdir->path = kstrdup_const(path, GFP_KERNEL); + if (!cdir->path) { + kfree(cdir); + return ERR_PTR(-ENOMEM); + } + + cdir->pfid = fid->persistent_fid; + cdir->vfid = fid->volatile_fid; + cdir->lease_key = kmemdup(fid->lease_key, 16, GFP_KERNEL); + if (!cdir->lease_key) { + kfree(cdir->path); + kfree(cdir); + return ERR_PTR(-ENOMEM); + } + cdir->info = info; + + cdir->time = jiffies; + + return cdir; +} + +struct need_close { + struct cached_dir *cdir; + struct dentry *dentry; + unsigned long long pfid; + unsigned long long vfid; +}; + +static void cdir_del_entry(struct cached_dir *cdir, struct need_close *close) +{ + cdir_invalidate_unlocked(cdir); + + close->cdir = cdir; + close->dentry = NULL; + close->pfid = COMPOUND_FID; + close->vfid = COMPOUND_FID; + + list_del(&cdir->head); + + swap(cdir->dentry, close->dentry); + + /* mark cdir as "closed" */ + swap(cdir->pfid, close->pfid); + swap(cdir->vfid, close->vfid); +} + +/* + * Search for a matching, and valid, cdir by @key (specified by @mode) in @list. + * + * Caller must hold any locks necessary, and do refcount accordingly. + * + * Return: + * - valid cdir if found + * - NULL in any other case + */ +static struct cached_dir *cdirs_find_fast(struct cached_dirs *cdirs, const void *key, int mode) +{ + struct cached_dir *cdir; + + if (!key) + return NULL; + + list_for_each_entry(cdir, &cdirs->entries, head) + if (cdir_match_unlocked(cdir, key, mode) && cdir_is_valid_unlocked(cdir)) + return cdir; + + return NULL; +} + +/* + * Try a fast lookup first, if can't find an entry, fallback to slow search. + * + * Fast: seqcount protected only, check valid. + * Slow: exclusive seqlock protected, retry on -ECHILD. + */ +static struct cached_dir *cdirs_find_entry(struct cached_dirs *cdirs, const void *key, int mode) +{ + struct cached_dir *cdir; + unsigned int seq; + bool slow = false; + int retries = 3; + + seq = read_seqbegin(&cdirs->seq); +find_again: + cdir = cdirs_find_fast(cdirs, key, mode); + + if (!slow && !read_seqretry(&cdirs->seq, seq)) + return cdir; + + /* if the list was modified, but didn't affect our cdir, ok to return it */ + if (cdir && cdir_match(cdir, key, mode)) { + if (slow) + read_sequnlock_excl(&cdirs->seq); + return cdir; } - return path + len; + + /* slow path */ + if (!slow) { + slow = true; + read_seqlock_excl(&cdirs->seq); + if (retries-- > 0) + goto find_again; + } + + read_sequnlock_excl(&cdirs->seq); + + return NULL; +} + +static void cdir_cleanup_sync(struct cached_dirs *cdirs, bool all) +{ + struct cached_dir *cdir, *q; + struct need_close close[16] = {}; + int i = 0; +#if 0 + need_close = kcalloc(16, sizeof(*need_close), GFP_KERNEL); + if (WARN_ON(!need_close)) + return; +#endif + write_seqlock(&cdirs->seq); + if (all) + atomic_set(&cdirs->count, INT_MAX); + + list_for_each_entry_safe(cdir, q, &cdirs->entries, head) { + if (!cdir_is_valid_unlocked(cdir) || all) { + write_seqlock(&cdir->seq); + cdir_del_entry(cdir, &close[i++]); + cdir_free(cdir); + write_sequnlock(&cdir->seq); + kfree(cdir); + + atomic_dec(&cdirs->count); + } + } + write_sequnlock(&cdirs->seq); + + i = 0; + while (i < 16) { + if (close[i].dentry) + dput(close[i].dentry); + + cdir_remote_close(cdirs, close[i].pfid, close[i].vfid); + i++; + } + +} + +static void cdir_cleanup(struct work_struct *work) +{ + struct cached_dirs *cdirs = container_of(work, struct cached_dirs, cleanup_work.work); + + cdir_cleanup_sync(cdirs, false); + queue_delayed_work(cfid_put_wq, &cdirs->cleanup_work, dir_cache_timeout * HZ); } /* - * Open the and cache a directory handle. - * If error then *cfid is not initialized. + * Remote ops. */ -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) +static void cdir_remote_close(struct cached_dirs *cdirs, unsigned long long pfid, unsigned long long vfid) +{ + if (pfid != COMPOUND_FID) { + SMB2_close(get_xid(), cdirs->tcon, pfid, vfid); + atomic_dec(&cdirs->tcon->num_remote_opens); + } +} + +static int cdir_remote_open(struct cifs_tcon *tcon, const char *path, struct cifs_sb_info *cifs_sb, + struct cifs_fid *fid, struct smb2_file_all_info *info) { - struct cifs_ses *ses; + struct smb2_query_info_rsp *qi_rsp = NULL; + struct smb2_file_all_info _info = {}; 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; - int resp_buftype[2]; + struct cifs_ses *ses = tcon->ses; struct smb_rqst rqst[2]; struct kvec rsp_iov[2]; struct kvec open_iov[SMB2_CREATE_IOV_SIZE]; struct kvec qi_iov[1]; - int rc, flags = 0; + unsigned int xid = get_xid(); + bool got_info = false; + int resp_buftype[2]; __le16 *utf16_path = NULL; + int rc, flags = 0; u8 oplock = SMB2_OPLOCK_LEVEL_II; - struct cifs_fid *pfid; - struct dentry *dentry = NULL; - struct cached_fid *cfid; - struct cached_fids *cfids; - const char *npath; - int retries = 0, cur_sleep = 1; - - if (cifs_sb->root == NULL) - return -ENOENT; - - if (tcon == NULL) - return -EOPNOTSUPP; - - ses = tcon->ses; - cfids = tcon->cfids; - - if (cfids == NULL) - return -EOPNOTSUPP; + int retries = 0, sleep = 0; replay_again: - /* reinitialize for possible replay */ + memset(fid, 0, sizeof(*fid)); + flags = 0; oplock = SMB2_OPLOCK_LEVEL_II; server = cifs_pick_channel(ses); - if (!server->ops->new_lease_key) return -EIO; + server->ops->new_lease_key(fid); + utf16_path = cifs_convert_path_to_utf16(path, cifs_sb); if (!utf16_path) return -ENOMEM; - cfid = find_or_create_cached_dir(cfids, path, lookup_only, tcon->max_cached_dirs); - if (cfid == NULL) { - 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. - */ - spin_lock(&cfids->cfid_list_lock); - if (cfid->has_lease && cfid->time) { - spin_unlock(&cfids->cfid_list_lock); - *ret_cfid = cfid; - kfree(utf16_path); - return 0; - } - spin_unlock(&cfids->cfid_list_lock); - - /* - * Skip any prefix paths in @path as lookup_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); - goto out; - } - - if (!npath[0]) { - dentry = dget(cifs_sb->root); - } else { - dentry = path_to_dentry(cifs_sb, npath); - if (IS_ERR(dentry)) { - rc = -ENOENT; - goto out; - } - } - cfid->dentry = dentry; - cfid->tcon = tcon; - - /* - * We do not hold the lock for the open because in case - * SMB2_open needs to reconnect. - * This is safe because no other thread will be able to get a ref - * to the cfid until we have finished opening the file and (possibly) - * acquired a lease. - */ if (smb3_encryption_required(tcon)) flags |= CIFS_TRANSFORM_REQ; - pfid = &cfid->fid; - server->ops->new_lease_key(pfid); - memset(rqst, 0, sizeof(rqst)); - resp_buftype[0] = resp_buftype[1] = CIFS_NO_BUFFER; memset(rsp_iov, 0, sizeof(rsp_iov)); + memset(&open_iov, 0, sizeof(open_iov)); + memset(&qi_iov, 0, sizeof(qi_iov)); /* Open */ - memset(&open_iov, 0, sizeof(open_iov)); + resp_buftype[0] = resp_buftype[1] = CIFS_NO_BUFFER; rqst[0].rq_iov = open_iov; rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE; + rqst[1].rq_iov = qi_iov; + rqst[1].rq_nvec = 1; 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, + .desired_access = FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA, .disposition = FILE_OPEN, - .fid = pfid, - .replay = !!(retries), + .fid = fid, + .replay = !!retries, }; - rc = SMB2_open_init(tcon, server, - &rqst[0], &oplock, &oparms, utf16_path); + rc = SMB2_open_init(tcon, server, &rqst[0], &oplock, &oparms, utf16_path); if (rc) goto oshr_free; - 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; + smb2_set_next_command(tcon, &rqst[0]); - 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; @@ -290,20 +488,15 @@ replay_again: 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; } - cfid->is_open = true; - - spin_lock(&cfids->cfid_list_lock); o_rsp = (struct smb2_create_rsp *)rsp_iov[0].iov_base; oparms.fid->persistent_fid = o_rsp->PersistentFileId; @@ -312,492 +505,414 @@ replay_again: oparms.fid->mid = le64_to_cpu(o_rsp->hdr.MessageId); #endif /* CIFS_DEBUG2 */ - 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 *)&cfid->file_all_info)) - cfid->file_all_info_is_valid = true; - - cfid->time = jiffies; - spin_unlock(&cfids->cfid_list_lock); - /* At this point the directory handle is fully cached */ - rc = 0; + 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)) + got_info = true; + + rc = 0; oshr_free: SMB2_open_free(&rqst[0]); SMB2_query_info_free(&rqst[1]); 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); - - kref_put(&cfid->refcount, smb2_close_cached_fid); - } else { - *ret_cfid = cfid; - atomic_inc(&tcon->num_remote_opens); - } kfree(utf16_path); - if (is_replayable_error(rc) && - smb2_should_replay(tcon, &retries, &cur_sleep)) + if (rc && is_replayable_error(rc) && smb2_should_replay(tcon, &retries, &sleep)) goto replay_again; + if (!rc) { + atomic_inc(&tcon->num_remote_opens); + if (got_info) + memcpy(info, &_info, sizeof(_info)); + } else if (rc == -EINVAL) { + rc = SMB2_close(xid, tcon, fid->persistent_fid, fid->volatile_fid); + } + return rc; } -int open_cached_dir_by_dentry(struct cifs_tcon *tcon, - struct dentry *dentry, - struct cached_fid **ret_cfid) +/* Do a remote open + query info and add a cached_dir entry on success. */ +static struct cached_dir *cdir_open(struct cached_dirs *cdirs, const char *path, + struct cifs_sb_info *cifs_sb) { - struct cached_fid *cfid; - struct cached_fids *cfids = tcon->cfids; + struct cached_dir *cdir = NULL; + struct smb2_file_all_info *info, dummy = {}; + struct cifs_fid fid = {}; + unsigned int seq; + int ret; - if (cfids == NULL) - return -EOPNOTSUPP; + seq = read_seqbegin(&cdirs->seq); + do { + ret = atomic_read(&cdirs->count) >= 16; /* MAX_CACHED_FIDS */ + } while (read_seqretry(&cdirs->seq, seq)); + + if (ret) + return ERR_PTR(-EMFILE); + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return ERR_PTR(-ENOMEM); - spin_lock(&cfids->cfid_list_lock); - list_for_each_entry(cfid, &cfids->entries, entry) { - if (dentry && 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; + ret = cdir_remote_open(cdirs->tcon, path, cifs_sb, &fid, info); + if (!ret) { + if (memcmp(info, &dummy, sizeof(dummy))) { + kfree(info); + info = NULL; } + + cdir = cdirs_init_entry(cdirs, root_path(path), path_to_dentry(cifs_sb, path), + &fid, info); + if (!IS_ERR(cdir)) + cdirs_add_entry(cdirs, cdir); + else { + kfree(info); + } + } else { + kfree(info); } - spin_unlock(&cfids->cfid_list_lock); - return -ENOENT; + + return cdir; } -static void -smb2_close_cached_fid(struct kref *ref) +/* + * Public ops (used by callers). + */ + +struct cached_dir *cached_dir_get_path(struct cached_dirs *cdirs, const char *path) { - struct cached_fid *cfid = container_of(ref, struct cached_fid, - refcount); - int rc; + return cdirs_find_entry(cdirs, root_path(path), CDIR_FIND_PATH); +} - 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); +struct cached_dir *cached_dir_get_dentry(struct cached_dirs *cdirs, const struct dentry *dentry) +{ + return cdirs_find_entry(cdirs, dentry, CDIR_FIND_DENTRY); +} - dput(cfid->dentry); - cfid->dentry = NULL; +struct cached_dir *cached_dir_get_leasekey(struct cached_dirs *cdirs, const u8 *lease_key) +{ + return cdirs_find_entry(cdirs, lease_key, CDIR_FIND_LEASEKEY); +} - 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_dir *cached_dir_get_open(struct cached_dirs *cdirs, const char *path, + struct cifs_sb_info *cifs_sb) +{ + struct cached_dir *cdir; + + if (!cifs_sb->root || !cdirs) + return NULL; + + cdir = cached_dir_get_path(cdirs, path); + if (cdir) + return cdir; + + cdir = cdir_open(cdirs, path, cifs_sb); + if (IS_ERR(cdir)) + return NULL; - free_cached_dir(cfid); + return cdir; } -void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon, - const char *name, struct cifs_sb_info *cifs_sb) +void cached_dir_put(struct cached_dirs *cdirs, struct cached_dir *cdir) { - struct cached_fid *cfid = NULL; - int rc; + unsigned int seq; - rc = open_cached_dir(xid, tcon, name, cifs_sb, true, &cfid); - if (rc) { - cifs_dbg(FYI, "no cached dir found for rmdir(%s)\n", name); + if (!cdir) + return; + + seq = read_seqbegin(&cdir->seq); + if (cdir_is_valid_unlocked(cdir)) + if (!read_seqretry(&cdir->seq, seq)) + return; + + /* similarly, this must also not happen */ + if (WARN_ON(!cdir->path || !cdir->dentry || !cdir->lease_key)) { + mod_delayed_work(cfid_put_wq, &cdirs->cleanup_work, 0); 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); + + queue_delayed_work(cfid_put_wq, &cdirs->cleanup_work, dir_cache_timeout * HZ); } +static void __cdir_drop(struct cached_dirs *cdirs, struct cached_dir *cdir) +{ + write_seqlock(&cdir->seq); + cdir_invalidate_unlocked(cdir); + write_sequnlock(&cdir->seq); + + mod_delayed_work(cfid_put_wq, &cdirs->cleanup_work, 0); +} -void close_cached_dir(struct cached_fid *cfid) +static void cdir_drop_by(struct cached_dirs *cdirs, const void *key, int mode) { - kref_put(&cfid->refcount, smb2_close_cached_fid); + struct cached_dir *cdir = cdirs_find_entry(cdirs, key, mode); + + /* Already gone? */ + if (!cdir) + return; + + __cdir_drop(cdirs, cdir); } -/* - * Called from cifs_kill_sb when we unmount a share - */ -void close_all_cached_dirs(struct cifs_sb_info *cifs_sb) +void cached_dir_drop(struct cached_dirs *cdirs, struct cached_dir *cdir) { - 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); + if (!cdir) + return; - spin_lock(&cifs_sb->tlink_tree_lock); - for (node = rb_first(root); node; node = rb_next(node)) { - tlink = rb_entry(node, struct tcon_link, tl_rbnode); - 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) { - tmp_list = kmalloc(sizeof(*tmp_list), GFP_ATOMIC); - if (tmp_list == NULL) - break; - 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); - } - spin_unlock(&cifs_sb->tlink_tree_lock); + __cdir_drop(cdirs, cdir); +} - list_for_each_entry_safe(tmp_list, q, &entry, entry) { - list_del(&tmp_list->entry); - dput(tmp_list->dentry); - kfree(tmp_list); - } +/* return true if cdir::time + `dir_cache_timeout' seconds is < jiffies */ +bool cached_dir_expired(struct cached_dir *cdir) +{ + return cdir_is_valid(cdir, 0); +} - /* Flush any pending work that will drop dentries */ - flush_workqueue(cfid_put_wq); +/* return true if cdir::time is older than @time */ +bool cached_dir_is_older(struct cached_dir *cdir, unsigned long time) +{ + return cdir_is_valid(cdir, time); } /* - * Invalidate all cached dirs when a TCON has been reset - * due to a session loss. + * Invalidate or drop a cdir that got a lease break. */ -void invalidate_all_cached_dir |
