summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEnzo Matsumiya <ematsumiya@suse.de>2025-03-12 09:40:11 -0300
committerEnzo Matsumiya <ematsumiya@suse.de>2025-03-18 12:24:48 -0300
commitde4c67c30cdc6cd8814e790486f044a102ed0f92 (patch)
treee729fec75a7305a829fe5d093953ffa725904d65
parenta8d12f5d67d9d0fd58daad72e3aa4cc46213b4f7 (diff)
downloadlinux-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.c1259
-rw-r--r--fs/smb/client/cached_dir.h146
-rw-r--r--fs/smb/client/cifs_debug.c26
-rw-r--r--fs/smb/client/cifsfs.c2
-rw-r--r--fs/smb/client/cifsglob.h3
-rw-r--r--fs/smb/client/connect.c3
-rw-r--r--fs/smb/client/file.c3
-rw-r--r--fs/smb/client/inode.c13
-rw-r--r--fs/smb/client/misc.c14
-rw-r--r--fs/smb/client/readdir.c189
-rw-r--r--fs/smb/client/smb2inode.c39
-rw-r--r--fs/smb/client/smb2misc.c7
-rw-r--r--fs/smb/client/smb2ops.c91
-rw-r--r--fs/smb/client/smb2pdu.c2
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