diff options
Diffstat (limited to 'fs/smb/client/readdir.c')
-rw-r--r-- | fs/smb/client/readdir.c | 260 |
1 files changed, 154 insertions, 106 deletions
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; |