diff options
author | Enzo Matsumiya <ematsumiya@suse.de> | 2025-07-08 15:54:31 -0300 |
---|---|---|
committer | Enzo Matsumiya <ematsumiya@suse.de> | 2025-07-09 03:00:14 -0300 |
commit | a70b71978ca38fcd839cf708cae288c442c14141 (patch) | |
tree | 0b0c893fe653e634e3d69291a7a4336c5c0fc1ba | |
parent | e835f5ff89e9106fe8c8d1fe8ea3917cea67c016 (diff) | |
download | linux-plk.tar.gz linux-plk.tar.bz2 linux-plk.zip |
smb: client: properly invalidate and reuse cached direntsplk
By properly invalidating cache dir entries allows to determine when
we must or not do a remote open of the dir, reducing the number of
network calls while the cached dir is still valid.
Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
-rw-r--r-- | fs/smb/client/cached_dir.c | 22 | ||||
-rw-r--r-- | fs/smb/client/cached_dir.h | 5 | ||||
-rw-r--r-- | fs/smb/client/cifspdu.h | 1 | ||||
-rw-r--r-- | fs/smb/client/readdir.c | 260 | ||||
-rw-r--r-- | fs/smb/client/smb2inode.c | 7 | ||||
-rw-r--r-- | fs/smb/client/smb2ops.c | 9 |
6 files changed, 191 insertions, 113 deletions
diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c index d28855a1edc1..0ee6eb959f1b 100644 --- a/fs/smb/client/cached_dir.c +++ b/fs/smb/client/cached_dir.c @@ -134,6 +134,28 @@ struct cached_fid *lookup_cached_dir(struct cached_fids *cfids, const void *key, return cfid; } +void invalidate_dirents(struct cached_fid *cfid) +{ + struct cached_dirents *cde; + struct cached_dirent *de, *q; + + if (!cfid) + return; + + cde = &cfid->dirents; + mutex_lock(&cde->de_mutex); + list_for_each_entry_safe(de, q, &cde->entries, entry) { + list_del(&de->entry); + kfree(de->name); + kfree(de); + } + + cde->is_valid = false; + cde->is_failed = false; + cde->pos = 2; + mutex_unlock(&cde->de_mutex); +} + /* * Open the and cache a directory handle. * If error then *cfid is not initialized. diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h index 4390860fc45c..81bdab01c91f 100644 --- a/fs/smb/client/cached_dir.h +++ b/fs/smb/client/cached_dir.h @@ -21,10 +21,6 @@ struct cached_dirent { struct cached_dirents { bool is_valid:1; bool is_failed:1; - struct file *file; /* - * Used to associate the cache with a single - * open file instance. - */ struct mutex de_mutex; loff_t pos; /* Expected ctx->pos */ struct list_head entries; @@ -76,6 +72,7 @@ extern struct cached_fid *init_cached_dir(struct cached_fids *cfids, const char extern int add_cached_dir(struct cifs_tcon *tcon, const char *path, struct cached_fid *cfid); extern struct cached_fid *lookup_cached_dir(struct cached_fids *cfids, const void *key, int type); +extern void invalidate_dirents(struct cached_fid *cfid); extern int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path, struct cifs_sb_info *cifs_sb, diff --git a/fs/smb/client/cifspdu.h b/fs/smb/client/cifspdu.h index d9cf7db0ac35..341dd015edf7 100644 --- a/fs/smb/client/cifspdu.h +++ b/fs/smb/client/cifspdu.h @@ -1789,6 +1789,7 @@ struct smb_t2_qfi_rsp { #define CIFS_SEARCH_RETURN_RESUME 0x0004 #define CIFS_SEARCH_CONTINUE_FROM_LAST 0x0008 #define CIFS_SEARCH_BACKUP_SEARCH 0x0010 +#define CIFS_SEARCH_CACHED_FID 0x0020 /* * Size of the resume key on FINDFIRST and FINDNEXT calls diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c index cc6762d950d2..414938923faf 100644 --- a/fs/smb/client/readdir.c +++ b/fs/smb/client/readdir.c @@ -344,7 +344,7 @@ cifs_std_info_to_fattr(struct cifs_fattr *fattr, FIND_FILE_STANDARD_INFO *info, static int _initiate_cifs_search(const unsigned int xid, struct file *file, - const char *full_path) + const char *full_path, struct cifs_fid *fid) { __u16 search_flags; int rc = 0; @@ -369,6 +369,7 @@ _initiate_cifs_search(const unsigned int xid, struct file *file, cifsFile->tlink = cifs_get_tlink(tlink); tcon = tlink_tcon(tlink); } else { + cifsFile = kzalloc(sizeof(struct cifsFileInfo), GFP_KERNEL); cifsFile = file->private_data; tcon = tlink_tcon(cifsFile->tlink); } @@ -406,10 +407,13 @@ ffirst_retry: if (backup_cred(cifs_sb)) search_flags |= CIFS_SEARCH_BACKUP_SEARCH; - rc = server->ops->query_dir_first(xid, tcon, full_path, cifs_sb, - &cifsFile->fid, search_flags, - &cifsFile->srch_inf); + pr_err("%s: %s query\n", __func__, fid ? "cached" : "remote"); + if (!fid) + fid = &cifsFile->fid; + else + search_flags |= CIFS_SEARCH_CACHED_FID; + rc = server->ops->query_dir_first(xid, tcon, full_path, cifs_sb, fid, search_flags, &cifsFile->srch_inf); if (rc == 0) { cifsFile->invalidHandle = false; } else if ((rc == -EOPNOTSUPP) && @@ -424,12 +428,12 @@ error_exit: static int initiate_cifs_search(const unsigned int xid, struct file *file, - const char *full_path) + const char *full_path, struct cifs_fid *fid) { int rc, retry_count = 0; do { - rc = _initiate_cifs_search(xid, file, full_path); + rc = _initiate_cifs_search(xid, file, full_path, fid); /* * If we don't have enough credits to start reading the * directory just try again after short wait. @@ -683,9 +687,9 @@ static int cifs_save_resume_key(const char *current_entry, static int find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos, struct file *file, const char *full_path, - char **current_entry, int *num_to_ret) + char **current_entry, int *num_to_ret, struct cifs_fid *fid) { - __u16 search_flags; + __u16 search_flags = 0; int rc = 0; int pos_in_buf = 0; loff_t first_entry_in_buffer; @@ -713,6 +717,9 @@ find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos, * to start two entries earlier. */ + if (fid) + search_flags = CIFS_SEARCH_CACHED_FID; + dump_cifs_file_struct(file, "In fce "); if (((index_to_find < cfile->srch_inf.index_of_last_entry) && is_dir_changed(file)) || (index_to_find < first_entry_in_buffer)) { @@ -739,7 +746,7 @@ find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos, cfile->srch_inf.srch_entries_start = NULL; cfile->srch_inf.last_entry = NULL; } - rc = initiate_cifs_search(xid, file, full_path); + rc = initiate_cifs_search(xid, file, full_path, fid); if (rc) { cifs_dbg(FYI, "error %d reinitiating a search on rewind\n", rc); @@ -750,16 +757,17 @@ find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos, cifs_save_resume_key(cfile->srch_inf.last_entry, cfile); } - search_flags = CIFS_SEARCH_CLOSE_AT_END | CIFS_SEARCH_RETURN_RESUME; + search_flags |= CIFS_SEARCH_CLOSE_AT_END | CIFS_SEARCH_RETURN_RESUME; if (backup_cred(cifs_sb)) search_flags |= CIFS_SEARCH_BACKUP_SEARCH; + if (!fid) + fid = &cfile->fid; + while ((index_to_find >= cfile->srch_inf.index_of_last_entry) && (rc == 0) && !cfile->srch_inf.endOfSearch) { cifs_dbg(FYI, "calling findnext2\n"); - rc = server->ops->query_dir_next(xid, tcon, &cfile->fid, - search_flags, - &cfile->srch_inf); + rc = server->ops->query_dir_next(xid, tcon, fid, search_flags, &cfile->srch_inf); if (rc) return -ENOENT; /* FindFirst/Next set last_entry to NULL on malformed reply */ @@ -814,59 +822,70 @@ find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos, return rc; } -static bool emit_cached_dirents(struct cached_dirents *cde, - struct dir_context *ctx) +/* + * Return: false if caller should re-open/revalidate entries, true otherwise. + */ +static bool emit_cached_dirents(struct cached_fid *cfid, struct dir_context *ctx, struct file *file) { + struct cached_dirents *cde = &cfid->dirents; struct cached_dirent *dirent; - bool rc; + bool ret = true; + + mutex_lock(&cde->de_mutex); + /* + * If this was reading from the start of the directory we need to initialize scanning and storing the + * directory content. + */ + if (ctx->pos == 0) + cde->pos = 2; + + /* If we already have the entire directory cached then we can just serve the cache. */ + if (!cfid->dirents.is_valid) { + ret = false; + goto out; + } + + if (!dir_emit_dots(file, ctx)) + goto out; list_for_each_entry(dirent, &cde->entries, entry) { - /* - * Skip all early entries prior to the current lseek() - * position. - */ + /* Skip all early entries prior to the current lseek() position. */ if (ctx->pos > dirent->pos) continue; + /* - * We recorded the current ->pos value for the dirent - * when we stored it in the cache. - * However, this sequence of ->pos values may have holes - * in it, for example dot-dirs returned from the server - * are suppressed. - * Handle this by forcing ctx->pos to be the same as the - * ->pos of the current dirent we emit from the cache. - * This means that when we emit these entries from the cache - * we now emit them with the same ->pos value as in the - * initial scan. + * We recorded the current ->pos value for the dirent when we stored it in the cache. + * + * However, this sequence of ->pos values may have holes in it, for example dot-dirs returned from the + * server are suppressed. + * + * Handle this by forcing ctx->pos to be the same as the ->pos of the current dirent we emit from the + * cache. This means that when we emit these entries from the cache we now emit them with the same + * ->pos value as in the initial scan. */ ctx->pos = dirent->pos; - rc = dir_emit(ctx, dirent->name, dirent->namelen, - dirent->fattr.cf_uniqueid, - dirent->fattr.cf_dtype); - if (!rc) - return rc; + ret = dir_emit(ctx, dirent->name, dirent->namelen, dirent->fattr.cf_uniqueid, dirent->fattr.cf_dtype); + if (!ret) + goto out; + ctx->pos++; } - return true; +out: + mutex_unlock(&cde->de_mutex); + return ret; } -static void update_cached_dirents_count(struct cached_dirents *cde, - struct file *file) +static void update_cached_dirents_count(struct cached_dirents *cde) { - if (cde->file != file) - return; - if (cde->is_valid || cde->is_failed) + if (cde->is_failed) return; cde->pos++; } -static void finished_cached_dirents_count(struct cached_dirents *cde, - struct dir_context *ctx, struct file *file) +static void finished_cached_dirents_count(struct cached_dirents *cde, struct dir_context *ctx) { - if (cde->file != file) - return; - if (cde->is_valid || cde->is_failed) + if (cde->is_failed) return; if (ctx->pos != cde->pos) return; @@ -877,14 +896,11 @@ static void finished_cached_dirents_count(struct cached_dirents *cde, static void add_cached_dirent(struct cached_dirents *cde, struct dir_context *ctx, const char *name, int namelen, - struct cifs_fattr *fattr, - struct file *file) + struct cifs_fattr *fattr) { struct cached_dirent *de; - if (cde->file != file) - return; - if (cde->is_valid || cde->is_failed) + if (cde->is_failed) return; if (ctx->pos != cde->pos) { cde->is_failed = 1; @@ -924,8 +940,7 @@ static bool cifs_dir_emit(struct dir_context *ctx, if (cfid) { mutex_lock(&cfid->dirents.de_mutex); - add_cached_dirent(&cfid->dirents, ctx, name, namelen, - fattr, file); + add_cached_dirent(&cfid->dirents, ctx, name, namelen, fattr); mutex_unlock(&cfid->dirents.de_mutex); } @@ -1033,10 +1048,10 @@ int cifs_readdir(struct file *file, struct dir_context *ctx) { int rc = 0; unsigned int xid; - int i; + int i, retries = 0; struct tcon_link *tlink = NULL; struct cifs_tcon *tcon; - struct cifsFileInfo *cifsFile; + struct cifsFileInfo *cifsFile = NULL; char *current_entry; int num_to_fill = 0; char *tmp_buf = NULL; @@ -1045,17 +1060,23 @@ int cifs_readdir(struct file *file, struct dir_context *ctx) const char *full_path; void *page = alloc_dentry_path(); struct cached_fid *cfid = NULL; + struct cifs_fid *fid = NULL; struct cifs_sb_info *cifs_sb = CIFS_FILE_SB(file); + u16 nrecs = 0; + bool can_emit_cached = true; + struct dentry *dentry = file_dentry(file); xid = get_xid(); - full_path = build_path_from_dentry(file_dentry(file), page); + pr_err("-------------------------------\n"); + + full_path = build_path_from_dentry(dentry, page); if (IS_ERR(full_path)) { rc = PTR_ERR(full_path); goto rddir2_exit; } - if (file->private_data == NULL) { + if (!file->private_data) { tlink = cifs_sb_tlink(cifs_sb); if (IS_ERR(tlink)) goto cache_not_found; @@ -1065,50 +1086,35 @@ 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, &cfid); + /* Use server->channel_sequence_num to track reconnects (see down below). */ + nrecs = tcon->ses->server->channel_sequence_num; + cfid = lookup_cached_dir(tcon->cfids, full_path, CFID_LOOKUP_PATH); + cifs_put_tlink(tlink); - if (rc) - goto cache_not_found; - mutex_lock(&cfid->dirents.de_mutex); - /* - * If this was reading from the start of the directory - * we need to initialize scanning and storing the - * directory content. - */ - if (ctx->pos == 0 && cfid->dirents.file == NULL) { - cfid->dirents.file = file; - cfid->dirents.pos = 2; + if (!cfid) { + pr_err("%s: cached dir not found for path='%s'\n", __func__, full_path); + goto cache_not_found; } - /* - * If we already have the entire directory cached then - * we can just serve the cache. - */ - if (cfid->dirents.is_valid) { - if (!dir_emit_dots(file, ctx)) { - mutex_unlock(&cfid->dirents.de_mutex); - goto rddir2_exit; - } - emit_cached_dirents(&cfid->dirents, ctx); - mutex_unlock(&cfid->dirents.de_mutex); + + pr_err("%s: cached dir found for path='%s'\n", __func__, full_path); + if (emit_cached_dirents(cfid, ctx, file)) { + pr_err("%s: emitting cached dir entries\n", __func__); goto rddir2_exit; } - mutex_unlock(&cfid->dirents.de_mutex); - /* Drop the cache while calling initiate_cifs_search and - * find_cifs_entry in case there will be reconnects during - * query_directory. - */ - close_cached_dir(cfid); - cfid = NULL; + pr_err("%s: revalidating cached dir entries\n", __func__); + fid = &cfid->fid; +cache_not_found: + can_emit_cached = false; - cache_not_found: /* * Ensure FindFirst doesn't fail before doing filldir() for '.' and * '..'. Otherwise we won't be able to notify VFS in case of failure. */ if (file->private_data == NULL) { - rc = initiate_cifs_search(xid, file, full_path); + rc = initiate_cifs_search(xid, file, full_path, fid); + pr_err("%s: initate cifs search rc=%d\n", __func__, rc); cifs_dbg(FYI, "initiate cifs search rc %d\n", rc); if (rc) goto rddir2_exit; @@ -1117,10 +1123,11 @@ int cifs_readdir(struct file *file, struct dir_context *ctx) if (!dir_emit_dots(file, ctx)) goto rddir2_exit; - /* 1) If search is active, - is in current search buffer? - if it before then restart search - if after then keep searching till find it */ + /* + * If search is active, is in current search buffer? + * - if before, then restart search + * - if after, then keep searching till find it + */ cifsFile = file->private_data; if (cifsFile->srch_inf.endOfSearch) { if (cifsFile->srch_inf.emptyDir) { @@ -1128,29 +1135,63 @@ int cifs_readdir(struct file *file, struct dir_context *ctx) rc = 0; goto rddir2_exit; } - } /* else { - cifsFile->invalidHandle = true; - tcon->ses->server->close(xid, tcon, &cifsFile->fid); - } */ + } 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, &cfid); + rc = find_cifs_entry(xid, tcon, ctx->pos, file, full_path, ¤t_entry, &num_to_fill, fid); + pr_err("%s: find cifs entry rc=%d\n", __func__, rc); if (rc) { cifs_dbg(FYI, "fce error %d\n", rc); goto rddir2_exit; - } else if (current_entry != NULL) { - cifs_dbg(FYI, "entry %lld found\n", ctx->pos); - } else { + } + + /* + * This means there was a reconnect during FindFirst/FindNext, so we can't trust our cached dir instance + * anymore. Since we held a ref to it, let it go and re-open it. + * + * If the dir was cached meanwhile, open_cached_dir() will find it through it's internal lookup. + */ + if (nrecs != tcon->ses->server->channel_sequence_num) { + close_cached_dir(cfid); + cfid = NULL; + fid = NULL; + + /* prevent a reconnect loop */ + if (++retries > 3) + goto rddir2_exit; + + rc = open_cached_dir(xid, tcon, full_path, cifs_sb, &cfid); + /* If we just opened+cached this dir, go through cache_not_found path again. */ + if (!rc) { + nrecs = tcon->ses->server->channel_sequence_num; + fid = &cfid->fid; + goto cache_not_found; + } + + goto rddir2_exit; + } + + if (!current_entry) { if (cfid) { mutex_lock(&cfid->dirents.de_mutex); - finished_cached_dirents_count(&cfid->dirents, ctx, file); + finished_cached_dirents_count(&cfid->dirents, ctx); mutex_unlock(&cfid->dirents.de_mutex); + +#if 0 + if (can_emit_cached) { + pr_err("%s: emitting cached\n", __func__); + emit_cached_dirents(cfid, ctx, file); + } +#endif + + goto rddir2_exit; } cifs_dbg(FYI, "Could not find entry\n"); goto rddir2_exit; } + + cifs_dbg(FYI, "entry %lld found\n", ctx->pos); + pr_err("%s: entry %lld found\n", __func__, ctx->pos); cifs_dbg(FYI, "loop through %d times filling dir for net buf %p\n", num_to_fill, cifsFile->srch_inf.ntwrk_buf_start); max_len = tcon->ses->server->ops->calc_smb_size( @@ -1186,7 +1227,7 @@ int cifs_readdir(struct file *file, struct dir_context *ctx) ctx->pos++; if (cfid) { mutex_lock(&cfid->dirents.de_mutex); - update_cached_dirents_count(&cfid->dirents, file); + update_cached_dirents_count(&cfid->dirents); mutex_unlock(&cfid->dirents.de_mutex); } @@ -1204,8 +1245,15 @@ int cifs_readdir(struct file *file, struct dir_context *ctx) kfree(tmp_buf); rddir2_exit: - if (cfid) + if (cfid) { + if (!rc) + cfid->dirents.is_valid = true; + + if (cifsFile && can_emit_cached) + cifsFile->invalidHandle = true; close_cached_dir(cfid); + } + free_dentry_path(page); free_xid(xid); return rc; diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index cd3901e8649e..d9f3fe7830a0 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -265,6 +265,9 @@ replay_again: if (rc) goto finished; + if (num_cmds > 0 && cmds[0] == SMB2_OP_MKDIR && oplock == SMB2_OPLOCK_LEVEL_LEASE && oparms->fid) + server->ops->new_lease_key(oparms->fid); + smb2_set_next_command(tcon, &rqst[num_rqst]); after_open: num_rqst++; @@ -1222,9 +1225,7 @@ mkdir_compound: } out: if (parent_cfid) { - mutex_lock(&parent_cfid->dirents.de_mutex); - parent_cfid->dirents.is_valid = false; - mutex_unlock(&parent_cfid->dirents.de_mutex); + invalidate_dirents(parent_cfid); close_cached_dir(parent_cfid); } diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 02543b0d4473..28e79d2da652 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -2339,6 +2339,14 @@ smb2_query_dir_first(const unsigned int xid, struct cifs_tcon *tcon, struct TCP_Server_Info *server; int retries = 0, cur_sleep = 1; + pr_err("%s: %s first query\n", __func__, (search_flags & CIFS_SEARCH_CACHED_FID) ? "cached" : "remote"); + if (search_flags & CIFS_SEARCH_CACHED_FID) { + srch_inf->entries_in_buffer = 0; + srch_inf->index_of_last_entry = 2; + + return SMB2_query_directory(xid, tcon, fid->persistent_fid, fid->volatile_fid, 0, srch_inf); + } + replay_again: /* reinitialize for possible replay */ flags = 0; @@ -2463,6 +2471,7 @@ smb2_query_dir_next(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_fid *fid, __u16 search_flags, struct cifs_search_info *srch_inf) { + pr_err("%s: %s next query\n", __func__, (search_flags & CIFS_SEARCH_CACHED_FID) ? "cached" : "remote"); return SMB2_query_directory(xid, tcon, fid->persistent_fid, fid->volatile_fid, 0, srch_inf); } |