summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEnzo Matsumiya <ematsumiya@suse.de>2025-04-04 18:28:09 -0300
committerEnzo Matsumiya <ematsumiya@suse.de>2025-04-04 18:28:09 -0300
commit54ba61a34441320e5ce133f5110e9077950f4d2c (patch)
treecc1f35512130920604a8bb7b4fb7a4e92f53562c
parentd3fef38b24a36c51d0774fd9b87da4393dd16cc2 (diff)
downloadlinux-cdir_v2.tar.gz
linux-cdir_v2.tar.bz2
linux-cdir_v2.zip
smb: client: cdir with lockrefcdir_v2
Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
-rw-r--r--fs/smb/client/cached_dir.c245
-rw-r--r--fs/smb/client/cached_dir.h7
2 files changed, 117 insertions, 135 deletions
diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index a6ca99d62bc8..bc8a7fca8fa0 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -116,147 +116,113 @@ static void cdir_free(struct cached_dir *cdir)
WRITE_ONCE(cdir->entries.valid, false);
}
-static inline bool cdir_expired(struct cached_dir *cdir, void *arg)
+static inline bool cdir_expired(struct cached_dir *cdir, unsigned long time)
{
- unsigned long time, t = READ_ONCE(cdir->time);
+ unsigned long t = READ_ONCE(cdir->time);
if (!t)
return true;
- time = (unsigned long)arg;
if (!time)
return time_is_before_jiffies(t + dir_cache_timeout * HZ);
return time_after(t, time);
}
-static inline bool cdir_is_expired(struct cached_dir *cdir, void *arg)
+static inline bool cdir_is_expired(struct cached_dir *cdir)
{
- return cdir_expired(cdir, arg);
+ return cdir_expired(cdir, 0);
}
-static inline bool cdir_has_lease(struct cached_dir *cdir, void __maybe_unused *arg)
+static inline bool cdir_has_lease(struct cached_dir *cdir)
{
return (!!READ_ONCE(cdir->lease_key));
}
-static inline bool cdir_is_open(struct cached_dir *cdir, void __maybe_unused *arg)
+static inline bool cdir_is_open(struct cached_dir *cdir)
{
return (READ_ONCE(cdir->pfid) != COMPOUND_FID && READ_ONCE(cdir->vfid) != COMPOUND_FID);
}
-static inline bool cdir_is_valid(struct cached_dir *cdir, void *arg)
+static inline bool cdir_is_valid(struct cached_dir *cdir)
{
- return (cdir_is_open(cdir, NULL) && cdir_has_lease(cdir, NULL) && !cdir_is_expired(cdir, arg));
+ return (cdir_has_lease(cdir) && !cdir_is_expired(cdir));
}
-static inline bool cdir_in_use(struct cached_dir *cdir, void __maybe_unused *arg)
+static inline void cdir_invalidate_fast(struct cached_dir *cdir)
{
- return (kref_read(&cdir->ref) > 1);
-}
-
-static inline bool cdir_check(struct cached_dir *cdir, void *arg,
- bool (*fn)(struct cached_dir *cdir, void *arg))
-{
- bool ret;
- int seq = 0;
-
- if (!cdir || IS_ERR(cdir))
- return false;
-
- do {
- read_seqbegin_or_lock(&cdir->seq, &seq);
-
- ret = fn(cdir, arg);
- } while (need_seqretry(&cdir->seq, seq));
- done_seqretry(&cdir->seq, seq);
-
- return ret;
+ kfree(cdir->lease_key);
+ WRITE_ONCE(cdir->lease_key, NULL);
+ WRITE_ONCE(cdir->time, 0);
+ lockref_mark_dead(&cdir->lockref);
}
-#define cdir_check_expired(cdir, arg) cdir_check(cdir, arg, cdir_is_expired)
-#define cdir_check_lease(cdir) cdir_check(cdir, NULL, cdir_has_lease)
-#define cdir_check_valid(cdir) cdir_check(cdir, NULL, cdir_is_valid)
-#define cdir_check_use(cdir) cdir_check(cdir, NULL, cdir_in_use)
-
/*
- * Mark a cdir as invalid (i.e. no lease anymore).
- *
- * Since this can be called from several different contexts, don't free/close/put anything here.
+ * Since this can be called from several different contexts, don't free/close/dput anything here.
*
* Return: true if can be cleaned up now, false otherwise.
*/
-static inline bool cdir_invalidate_unlocked(struct cached_dir *cdir)
+static inline bool cdir_put_fast(struct cached_dir *cdir)
{
- bool ret = true;
-
- if (!cdir_has_lease(cdir, NULL)) {
- if (!cdir_is_expired(cdir, NULL))
- return false;
- return true;
- }
+ int ret;
/*
* This gives a grace period for explicitly invalidation calls for cdirs that are still in
* use to survive at least until the next cleanup run.
*/
- if (cdir_in_use(cdir, NULL)) {
- pr_err("%s: invalidating cdir=%p in use (%u), key=%*ph\n", __func__, cdir,
- kref_read(&cdir->ref), 16, cdir->lease_key);
- WRITE_ONCE(cdir->time, jiffies + dir_cache_timeout * HZ);
- ret = false;
- } else {
- pr_err("%s: invalidating unused cdir=%p, key=%*ph\n", __func__, cdir, 16, cdir->lease_key);
- WRITE_ONCE(cdir->time, 0);
- }
-
- kfree(cdir->lease_key);
- WRITE_ONCE(cdir->lease_key, NULL);
-
- /* catch unexpected kref_get/kref_put calls, which shouldn't happen for an invalid cdir */
- //refcount_set(&cdir->ref.refcount, 0);
+ ret = lockref_put_return(&cdir->lockref);
+ if (unlikely(ret < 0)) {
+ down_read(&cdir->sem);
+ if (cdir->lockref.count <= 0) {
+ up_read(&cdir->sem);
+ return true;
+ }
- return ret;
-}
+ // continue locked
+ goto locked;
+ }
-static inline bool cdir_invalidate(struct cached_dir *cdir)
-{
- bool ret;
+ /* not last ref, nothing else to do */
+ if (ret)
+ return false;
- write_seqlock(&cdir->seq);
- ret = cdir_invalidate_unlocked(cdir);
- write_sequnlock(&cdir->seq);
+ /* last ref, but not expired yet, keep it around? */
+ if (!cdir_is_expired(cdir))
+ return false;
- return ret;
-}
+ /* validate again, but locked this time, as it might have changed since we got here */
+ down_read(&cdir->sem);
+locked:
+ if (cdir->lockref.count > 0 || (cdir_has_lease(cdir) && !cdir_is_expired(cdir))) {
+ up_read(&cdir->sem);
+ return false;
+ }
+ up_read(&cdir->sem);
-static inline void cdir_invalidate_put(struct kref *kref)
-{
- struct cached_dir *cdir = container_of(kref, struct cached_dir, ref);
+ down_write(&cdir->sem);
+ cdir_invalidate_fast(cdir);
- cdir_invalidate_unlocked(cdir);
- WRITE_ONCE(cdir->time, 0);
+ /* cdir is ready to go (wsem locked) */
+ return true;
}
+/* Callers must call SMB2_close before this */
static inline bool cdir_put(struct cached_dir *cdir)
{
- bool ret = true;
+ if (!cdir_put_fast(cdir))
+ return false;
- if (!cdir)
- return true;
+ cdir_free(cdir);
+ // from cdir_put_fast
+ up_write(&cdir->sem);
- write_seqlock(&cdir->seq);
- ret = kref_put(&cdir->ref, cdir_invalidate_put);
- write_sequnlock(&cdir->seq);
+ kfree(cdir);
- return ret;
+ return true;
}
-static bool cdir_match_key(struct cached_dir *cdir, const void *key, int mode)
+static bool cdir_match(struct cached_dir *cdir, const void *key, int mode)
{
- if (!cdir || !key)
- return false;
-
switch (mode) {
case CDIR_FIND_PATH:
{
@@ -283,68 +249,83 @@ static bool cdir_match_key(struct cached_dir *cdir, const void *key, int mode)
return false;
}
-static int cdir_match_check(struct cached_dir *cdir, const void *key, int mode)
+/*
+ * Search for a matching cdir by @key by @mode in @cdirs::list.
+ *
+ * Return: cdir if found, NULL otherwise.
+ */
+static struct cached_dir *__cdir_lookup(struct cached_dirs *cdirs, const void *key, int mode)
{
- int ret = -ENOENT, seq = 0;
+ struct cached_dir *cdir;
- read_seqbegin_or_lock(&cdir->seq, &seq);
+ list_for_each_entry(cdir, &cdirs->list, head)
+ if (cdir_match(cdir, key, mode))
+ return cdir;
- if (cdir_match_key(cdir, key, mode)) {
- if (kref_get_unless_zero(&cdir->ref)) {
- if (cdir_is_valid(cdir, NULL))
- ret = 0;
- else
- ret = -EINVAL;
- }
- }
+ return ERR_PTR(-ENOENT);
+}
- if (need_seqretry(&cdir->seq, seq)) {
- if (!ret)
- kref_put(&cdir->ref, cdir_invalidate_put);
- ret = -ECHILD;
- }
+static struct cached_dir *cdir_get_fast(struct cached_dir *cdir)
+{
+ if (lockref_get_not_dead(&cdir->lockref))
+ return cdir;
+ return NULL;
+}
+
+static struct cached_dir *cdir_get(struct cached_dir *cdir)
+{
+ if (!cdir)
+ return NULL;
- done_seqretry(&cdir->seq, seq);
+ down_read(&cdir->sem);
+ if (lockref_get_not_dead(&cdir->lockref))
+ return cdir;
- return ret;
+ up_read(&cdir->sem);
+ return NULL;
}
-/*
- * Search for a matching cdir by @key by @mode in @cdirs::list.
- *
- * Try a fast lookup first, if can't find an entry, retry a slow search.
- *
- * Fast: seqcount protected iteration and matching.
- * Slow: read exclusive seqlock protected iteration and matching.
- *
- * Return: On success, valid matching cdir is returned, ERR_PTR(-errno) otherwise.
- */
-static struct cached_dir *cdir_find_entry(struct cached_dirs *cdirs, const void *key, int mode)
+static struct cached_dir *cdir_lookup_fast(struct cached_dirs *cdirs, const void *key, int mode)
{
struct cached_dir *cdir;
- long ret;
- int seq = 0;
+ int seq = read_seqcount_begin(&cdirs->seq);
+
+ cdir = __cdir_lookup(cdirs, key, mode);
+ if (!read_seqcount_retry(&cdirs->seq, seq))
+ return cdir;
+
+ return ERR_PTR(-ECHILD);
+}
- if (!cdirs || !key || !mode)
- return ERR_PTR(-EOPNOTSUPP);
+static struct cached_dir *cdir_lookup_slow(struct cached_dirs *cdirs, const void *key, int mode)
+{
+ struct cached_dir *cdir;
+ down_read(&cdirs->sem);
do {
- read_seqbegin_or_lock(&cdirs->seq, &seq);
+ cdir = cdir_lookup_fast(cdirs, key, mode);
+ if (cdir && !IS_ERR(cdir))
+ cdir = cdir_get(cdir);
+ } while (cdir != ERR_PTR(-ECHILD));
+ up_read(&cdirs->sem);
- ret = -ENOENT;
- list_for_each_entry(cdir, &cdirs->list, head) {
- ret = cdir_match_check(cdir, key, mode);
- if (ret != -ENOENT)
- break;
- }
- } while (need_seqretry(&cdirs->seq, seq) && ret != -ECHILD);
+ return cdir;
+}
- done_seqretry(&cdirs->seq, seq);
+static struct cached_dir *cdir_lookup(struct cached_dirs *cdirs, const void *key, int mode)
+{
+ struct cached_dir *cdir;
- if (!ret)
+ cdir = cdir_lookup_fast(cdirs, key, mode);
+ if (!IS_ERR(cdir))
+ return cdir_get(cdir);
+
+ // slow path
+ cdir = cdir_lookup_slow(cdirs, key, mode);
+ if (cdir && !IS_ERR(cdir))
return cdir;
- return ERR_PTR(ret);
+ return NULL;
}
static struct cached_dir *cdir_init_entry(const char *path, struct dentry *dentry,
@@ -361,7 +342,7 @@ static struct cached_dir *cdir_init_entry(const char *path, struct dentry *dentr
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&cdir->head);
- seqlock_init(&cdir->seq);
+ lockref_init(&cdir->lock);
kref_init(&cdir->ref);
mutex_init(&cdir->entries.lock);
diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h
index 4e5c9b86fc49..d971e664f07e 100644
--- a/fs/smb/client/cached_dir.h
+++ b/fs/smb/client/cached_dir.h
@@ -31,8 +31,8 @@ struct cached_dir_entries {
struct cached_dir {
/* Private/internal use only! */
struct list_head head;
- seqlock_t seq;
- struct kref ref;
+ struct rw_semaphore sem;
+ struct lockref lockref;
const char *path;
struct dentry *dentry;
@@ -50,7 +50,8 @@ struct cached_dir {
struct cached_dirs {
struct list_head list;
- seqlock_t seq;
+ struct rw_semaphore sem;
+ seqcount_t seq;
/* tcon backreference */
struct cifs_tcon *tcon;