summaryrefslogtreecommitdiff
path: root/fs/smb/client/readdir.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/smb/client/readdir.c')
-rw-r--r--fs/smb/client/readdir.c260
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,
- &current_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, &current_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;