diff options
| -rw-r--r-- | fs/smb/client/cached_dir.c | 245 | ||||
| -rw-r--r-- | fs/smb/client/cached_dir.h | 7 |
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; |
