From 773891ffd4d628d05c4e22f34541e4779ee7a076 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 8 Aug 2022 13:41:18 -0300 Subject: [PATCH 1/8] cifs: fix lock length calculation The lock length was wrongly set to 0 when fl_end == OFFSET_MAX, thus failing to lock the whole file when l_start=0 and l_len=0. This fixes test 2 from cthon04. Before patch: $ ./cthon04/lock/tlocklfs -t 2 /mnt Creating parent/child synchronization pipes. Test #1 - Test regions of an unlocked file. Parent: 1.1 - F_TEST [ 0, 1] PASSED. Parent: 1.2 - F_TEST [ 0, ENDING] PASSED. Parent: 1.3 - F_TEST [ 0,7fffffffffffffff] PASSED. Parent: 1.4 - F_TEST [ 1, 1] PASSED. Parent: 1.5 - F_TEST [ 1, ENDING] PASSED. Parent: 1.6 - F_TEST [ 1,7fffffffffffffff] PASSED. Parent: 1.7 - F_TEST [7fffffffffffffff, 1] PASSED. Parent: 1.8 - F_TEST [7fffffffffffffff, ENDING] PASSED. Parent: 1.9 - F_TEST [7fffffffffffffff,7fffffffffffffff] PASSED. Test #2 - Try to lock the whole file. Parent: 2.0 - F_TLOCK [ 0, ENDING] PASSED. Child: 2.1 - F_TEST [ 0, 1] FAILED! Child: **** Expected EACCES, returned success... Child: **** Probably implementation error. ** CHILD pass 1 results: 0/0 pass, 0/0 warn, 1/1 fail (pass/total). Parent: Child died ** PARENT pass 1 results: 10/10 pass, 0/0 warn, 0/0 fail (pass/total). After patch: $ ./cthon04/lock/tlocklfs -t 2 /mnt Creating parent/child synchronization pipes. Test #2 - Try to lock the whole file. Parent: 2.0 - F_TLOCK [ 0, ENDING] PASSED. Child: 2.1 - F_TEST [ 0, 1] PASSED. Child: 2.2 - F_TEST [ 0, ENDING] PASSED. Child: 2.3 - F_TEST [ 0,7fffffffffffffff] PASSED. Child: 2.4 - F_TEST [ 1, 1] PASSED. Child: 2.5 - F_TEST [ 1, ENDING] PASSED. Child: 2.6 - F_TEST [ 1,7fffffffffffffff] PASSED. Child: 2.7 - F_TEST [7fffffffffffffff, 1] PASSED. Child: 2.8 - F_TEST [7fffffffffffffff, ENDING] PASSED. Child: 2.9 - F_TEST [7fffffffffffffff,7fffffffffffffff] PASSED. Parent: 2.10 - F_ULOCK [ 0, ENDING] PASSED. ** PARENT pass 1 results: 2/2 pass, 0/0 warn, 0/0 fail (pass/total). ** CHILD pass 1 results: 9/9 pass, 0/0 warn, 0/0 fail (pass/total). Fixes: d80c69846ddf ("cifs: fix signed integer overflow when fl_end is OFFSET_MAX") Reported-by: Xiaoli Feng Cc: Signed-off-by: Paulo Alcantara (SUSE) Reviewed-by: Ronnie Sahlberg Signed-off-by: Steve French --- fs/cifs/cifsglob.h | 4 ++-- fs/cifs/file.c | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index 3070407cafa72..a93024eaf251b 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -2132,9 +2132,9 @@ static inline bool cifs_is_referral_server(struct cifs_tcon *tcon, return is_tcon_dfs(tcon) || (ref && (ref->flags & DFSREF_REFERRAL_SERVER)); } -static inline u64 cifs_flock_len(struct file_lock *fl) +static inline u64 cifs_flock_len(const struct file_lock *fl) { - return fl->fl_end == OFFSET_MAX ? 0 : fl->fl_end - fl->fl_start + 1; + return (u64)fl->fl_end - fl->fl_start + 1; } static inline size_t ntlmssp_workstation_name_size(const struct cifs_ses *ses) diff --git a/fs/cifs/file.c b/fs/cifs/file.c index 85f2abcb27954..09975bd7d8605 100644 --- a/fs/cifs/file.c +++ b/fs/cifs/file.c @@ -1936,9 +1936,9 @@ int cifs_lock(struct file *file, int cmd, struct file_lock *flock) rc = -EACCES; xid = get_xid(); - cifs_dbg(FYI, "Lock parm: 0x%x flockflags: 0x%x flocktype: 0x%x start: %lld end: %lld\n", - cmd, flock->fl_flags, flock->fl_type, - flock->fl_start, flock->fl_end); + cifs_dbg(FYI, "%s: %pD2 cmd=0x%x type=0x%x flags=0x%x r=%lld:%lld\n", __func__, file, cmd, + flock->fl_flags, flock->fl_type, (long long)flock->fl_start, + (long long)flock->fl_end); cfile = (struct cifsFileInfo *)file->private_data; tcon = tlink_tcon(cfile->tlink); From cd04345598b7c191d41574bc9da3fe435dc65605 Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 8 Aug 2022 15:33:51 +0100 Subject: [PATCH 2/8] cifs: Remove {cifs,nfs}_fscache_release_page() Remove {cifs,nfs}_fscache_release_page() from fs/cifs/fscache.h. This functionality got built directly into cifs_release_folio() and will hopefully be replaced with netfs_release_folio() at some point. The "nfs_" version is a copy and paste error and should've been altered to read "cifs_". That can also be removed. Reported-by: Matthew Wilcox Signed-off-by: David Howells Reviewed-by: Jeff Layton cc: Steve French cc: linux-cifs@vger.kernel.org cc: samba-technical@lists.samba.org cc: linux-fsdevel@vger.kernel.org Signed-off-by: Steve French --- fs/cifs/fscache.h | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/fs/cifs/fscache.h b/fs/cifs/fscache.h index aa3b941a55557..67b601041f0a3 100644 --- a/fs/cifs/fscache.h +++ b/fs/cifs/fscache.h @@ -108,17 +108,6 @@ static inline void cifs_readpage_to_fscache(struct inode *inode, __cifs_readpage_to_fscache(inode, page); } -static inline int cifs_fscache_release_page(struct page *page, gfp_t gfp) -{ - if (PageFsCache(page)) { - if (current_is_kswapd() || !(gfp & __GFP_FS)) - return false; - wait_on_page_fscache(page); - fscache_note_page_release(cifs_inode_cookie(page->mapping->host)); - } - return true; -} - #else /* CONFIG_CIFS_FSCACHE */ static inline void cifs_fscache_fill_coherency(struct inode *inode, @@ -154,11 +143,6 @@ cifs_readpage_from_fscache(struct inode *inode, struct page *page) static inline void cifs_readpage_to_fscache(struct inode *inode, struct page *page) {} -static inline int nfs_fscache_release_page(struct page *page, gfp_t gfp) -{ - return true; /* May release page */ -} - #endif /* CONFIG_CIFS_FSCACHE */ #endif /* _CIFS_FSCACHE_H */ From 05b98fd2da6bdf241d3b9ba40582d60a84a89d70 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Wed, 10 Aug 2022 22:00:08 -0500 Subject: [PATCH 3/8] cifs: Move cached-dir functions into a separate file Also rename crfid to cfid to have consistent naming for this variable. This commit does not change any logic. Signed-off-by: Ronnie Sahlberg Reviewed-by: Paulo Alcantara (SUSE) Signed-off-by: Steve French --- fs/cifs/Makefile | 2 +- fs/cifs/cached_dir.c | 362 +++++++++++++++++++++++++++++++++++++++++++ fs/cifs/cached_dir.h | 26 ++++ fs/cifs/cifsfs.c | 20 +-- fs/cifs/cifsglob.h | 2 +- fs/cifs/cifsproto.h | 1 - fs/cifs/file.c | 9 +- fs/cifs/inode.c | 1 + fs/cifs/misc.c | 12 +- fs/cifs/readdir.c | 1 + fs/cifs/smb2inode.c | 5 +- fs/cifs/smb2misc.c | 11 +- fs/cifs/smb2ops.c | 297 +---------------------------------- fs/cifs/smb2pdu.c | 3 +- fs/cifs/smb2proto.h | 10 -- 15 files changed, 411 insertions(+), 351 deletions(-) create mode 100644 fs/cifs/cached_dir.c create mode 100644 fs/cifs/cached_dir.h diff --git a/fs/cifs/Makefile b/fs/cifs/Makefile index e882e912a5176..7c9785973f496 100644 --- a/fs/cifs/Makefile +++ b/fs/cifs/Makefile @@ -7,7 +7,7 @@ obj-$(CONFIG_CIFS) += cifs.o cifs-y := trace.o cifsfs.o cifs_debug.o connect.o dir.o file.o \ inode.o link.o misc.o netmisc.o smbencrypt.o transport.o \ - cifs_unicode.o nterr.o cifsencrypt.o \ + cached_dir.o cifs_unicode.o nterr.o cifsencrypt.o \ readdir.o ioctl.o sess.o export.o unc.o winucase.o \ smb2ops.o smb2maperror.o smb2transport.o \ smb2misc.o smb2pdu.o smb2inode.o smb2file.o cifsacl.o fs_context.o \ diff --git a/fs/cifs/cached_dir.c b/fs/cifs/cached_dir.c new file mode 100644 index 0000000000000..753b850b6f878 --- /dev/null +++ b/fs/cifs/cached_dir.c @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions to handle the cached directory entries + * + * Copyright (c) 2022, Ronnie Sahlberg + */ + +#include "cifsglob.h" +#include "cifsproto.h" +#include "cifs_debug.h" +#include "smb2proto.h" +#include "cached_dir.h" + +/* + * Open the and cache a directory handle. + * If error then *cfid is not initialized. + */ +int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, + const char *path, + struct cifs_sb_info *cifs_sb, + struct cached_fid **cfid) +{ + struct cifs_ses *ses; + struct TCP_Server_Info *server; + struct cifs_open_parms oparms; + struct smb2_create_rsp *o_rsp = NULL; + struct smb2_query_info_rsp *qi_rsp = NULL; + int resp_buftype[2]; + struct smb_rqst rqst[2]; + struct kvec rsp_iov[2]; + struct kvec open_iov[SMB2_CREATE_IOV_SIZE]; + struct kvec qi_iov[1]; + int rc, flags = 0; + __le16 utf16_path = 0; /* Null - since an open of top of share */ + u8 oplock = SMB2_OPLOCK_LEVEL_II; + struct cifs_fid *pfid; + struct dentry *dentry; + + if (tcon == NULL || tcon->nohandlecache || + is_smb1_server(tcon->ses->server)) + return -EOPNOTSUPP; + + ses = tcon->ses; + server = ses->server; + + if (cifs_sb->root == NULL) + return -ENOENT; + + if (strlen(path)) + return -ENOENT; + + dentry = cifs_sb->root; + + mutex_lock(&tcon->cfid.fid_mutex); + if (tcon->cfid.is_valid) { + cifs_dbg(FYI, "found a cached root file handle\n"); + *cfid = &tcon->cfid; + kref_get(&tcon->cfid.refcount); + mutex_unlock(&tcon->cfid.fid_mutex); + return 0; + } + + /* + * We do not hold the lock for the open because in case + * SMB2_open needs to reconnect, it will end up calling + * cifs_mark_open_files_invalid() which takes the lock again + * thus causing a deadlock + */ + + mutex_unlock(&tcon->cfid.fid_mutex); + + if (smb3_encryption_required(tcon)) + flags |= CIFS_TRANSFORM_REQ; + + if (!server->ops->new_lease_key) + return -EIO; + + pfid = tcon->cfid.fid; + server->ops->new_lease_key(pfid); + + memset(rqst, 0, sizeof(rqst)); + resp_buftype[0] = resp_buftype[1] = CIFS_NO_BUFFER; + memset(rsp_iov, 0, sizeof(rsp_iov)); + + /* Open */ + memset(&open_iov, 0, sizeof(open_iov)); + rqst[0].rq_iov = open_iov; + rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE; + + oparms.tcon = tcon; + oparms.create_options = cifs_create_options(cifs_sb, CREATE_NOT_FILE); + oparms.desired_access = FILE_READ_ATTRIBUTES; + oparms.disposition = FILE_OPEN; + oparms.fid = pfid; + oparms.reconnect = false; + + rc = SMB2_open_init(tcon, server, + &rqst[0], &oplock, &oparms, &utf16_path); + if (rc) + goto oshr_free; + smb2_set_next_command(tcon, &rqst[0]); + + memset(&qi_iov, 0, sizeof(qi_iov)); + rqst[1].rq_iov = qi_iov; + rqst[1].rq_nvec = 1; + + rc = SMB2_query_info_init(tcon, server, + &rqst[1], COMPOUND_FID, + COMPOUND_FID, FILE_ALL_INFORMATION, + SMB2_O_INFO_FILE, 0, + sizeof(struct smb2_file_all_info) + + PATH_MAX * 2, 0, NULL); + if (rc) + goto oshr_free; + + smb2_set_related(&rqst[1]); + + rc = compound_send_recv(xid, ses, server, + flags, 2, rqst, + resp_buftype, rsp_iov); + mutex_lock(&tcon->cfid.fid_mutex); + + /* + * Now we need to check again as the cached root might have + * been successfully re-opened from a concurrent process + */ + + if (tcon->cfid.is_valid) { + /* work was already done */ + + /* stash fids for close() later */ + struct cifs_fid fid = { + .persistent_fid = pfid->persistent_fid, + .volatile_fid = pfid->volatile_fid, + }; + + /* + * caller expects this func to set the fid in cfid to valid + * cached root, so increment the refcount. + */ + kref_get(&tcon->cfid.refcount); + + mutex_unlock(&tcon->cfid.fid_mutex); + + if (rc == 0) { + /* close extra handle outside of crit sec */ + SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid); + } + rc = 0; + goto oshr_free; + } + + /* Cached root is still invalid, continue normaly */ + + if (rc) { + if (rc == -EREMCHG) { + tcon->need_reconnect = true; + pr_warn_once("server share %s deleted\n", + tcon->treeName); + } + goto oshr_exit; + } + + atomic_inc(&tcon->num_remote_opens); + + o_rsp = (struct smb2_create_rsp *)rsp_iov[0].iov_base; + oparms.fid->persistent_fid = o_rsp->PersistentFileId; + oparms.fid->volatile_fid = o_rsp->VolatileFileId; +#ifdef CONFIG_CIFS_DEBUG2 + oparms.fid->mid = le64_to_cpu(o_rsp->hdr.MessageId); +#endif /* CIFS_DEBUG2 */ + + tcon->cfid.tcon = tcon; + tcon->cfid.is_valid = true; + tcon->cfid.dentry = dentry; + dget(dentry); + kref_init(&tcon->cfid.refcount); + + /* BB TBD check to see if oplock level check can be removed below */ + if (o_rsp->OplockLevel == SMB2_OPLOCK_LEVEL_LEASE) { + /* + * See commit 2f94a3125b87. Increment the refcount when we + * get a lease for root, release it if lease break occurs + */ + kref_get(&tcon->cfid.refcount); + tcon->cfid.has_lease = true; + smb2_parse_contexts(server, o_rsp, + &oparms.fid->epoch, + oparms.fid->lease_key, &oplock, + NULL, NULL); + } else + goto oshr_exit; + + qi_rsp = (struct smb2_query_info_rsp *)rsp_iov[1].iov_base; + if (le32_to_cpu(qi_rsp->OutputBufferLength) < sizeof(struct smb2_file_all_info)) + goto oshr_exit; + if (!smb2_validate_and_copy_iov( + le16_to_cpu(qi_rsp->OutputBufferOffset), + sizeof(struct smb2_file_all_info), + &rsp_iov[1], sizeof(struct smb2_file_all_info), + (char *)&tcon->cfid.file_all_info)) + tcon->cfid.file_all_info_is_valid = true; + tcon->cfid.time = jiffies; + + +oshr_exit: + mutex_unlock(&tcon->cfid.fid_mutex); +oshr_free: + SMB2_open_free(&rqst[0]); + SMB2_query_info_free(&rqst[1]); + free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base); + free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base); + if (rc == 0) + *cfid = &tcon->cfid; + + return rc; +} + +int open_cached_dir_by_dentry(struct cifs_tcon *tcon, + struct dentry *dentry, + struct cached_fid **cfid) +{ + mutex_lock(&tcon->cfid.fid_mutex); + if (tcon->cfid.dentry == dentry) { + cifs_dbg(FYI, "found a cached root file handle by dentry\n"); + *cfid = &tcon->cfid; + kref_get(&tcon->cfid.refcount); + mutex_unlock(&tcon->cfid.fid_mutex); + return 0; + } + mutex_unlock(&tcon->cfid.fid_mutex); + return -ENOENT; +} + +static void +smb2_close_cached_fid(struct kref *ref) +{ + struct cached_fid *cfid = container_of(ref, struct cached_fid, + refcount); + struct cached_dirent *dirent, *q; + + if (cfid->is_valid) { + cifs_dbg(FYI, "clear cached root file handle\n"); + SMB2_close(0, cfid->tcon, cfid->fid->persistent_fid, + cfid->fid->volatile_fid); + } + + /* + * We only check validity above to send SMB2_close, + * but we still need to invalidate these entries + * when this function is called + */ + cfid->is_valid = false; + cfid->file_all_info_is_valid = false; + cfid->has_lease = false; + if (cfid->dentry) { + dput(cfid->dentry); + cfid->dentry = NULL; + } + /* + * Delete all cached dirent names + */ + mutex_lock(&cfid->dirents.de_mutex); + list_for_each_entry_safe(dirent, q, &cfid->dirents.entries, entry) { + list_del(&dirent->entry); + kfree(dirent->name); + kfree(dirent); + } + cfid->dirents.is_valid = 0; + cfid->dirents.is_failed = 0; + cfid->dirents.ctx = NULL; + cfid->dirents.pos = 0; + mutex_unlock(&cfid->dirents.de_mutex); + +} + +void close_cached_dir(struct cached_fid *cfid) +{ + mutex_lock(&cfid->fid_mutex); + kref_put(&cfid->refcount, smb2_close_cached_fid); + mutex_unlock(&cfid->fid_mutex); +} + +void close_cached_dir_lease_locked(struct cached_fid *cfid) +{ + if (cfid->has_lease) { + cfid->has_lease = false; + kref_put(&cfid->refcount, smb2_close_cached_fid); + } +} + +void close_cached_dir_lease(struct cached_fid *cfid) +{ + mutex_lock(&cfid->fid_mutex); + close_cached_dir_lease_locked(cfid); + mutex_unlock(&cfid->fid_mutex); +} + +/* + * Called from cifs_kill_sb when we unmount a share + */ +void close_all_cached_dirs(struct cifs_sb_info *cifs_sb) +{ + struct rb_root *root = &cifs_sb->tlink_tree; + struct rb_node *node; + struct cached_fid *cfid; + struct cifs_tcon *tcon; + struct tcon_link *tlink; + + for (node = rb_first(root); node; node = rb_next(node)) { + tlink = rb_entry(node, struct tcon_link, tl_rbnode); + tcon = tlink_tcon(tlink); + if (IS_ERR(tcon)) + continue; + cfid = &tcon->cfid; + mutex_lock(&cfid->fid_mutex); + if (cfid->dentry) { + dput(cfid->dentry); + cfid->dentry = NULL; + } + mutex_unlock(&cfid->fid_mutex); + } +} + +/* + * Invalidate and close all cached dirs when a TCON has been reset + * due to a session loss. + */ +void invalidate_all_cached_dirs(struct cifs_tcon *tcon) +{ + mutex_lock(&tcon->cfid.fid_mutex); + tcon->cfid.is_valid = false; + /* cached handle is not valid, so SMB2_CLOSE won't be sent below */ + close_cached_dir_lease_locked(&tcon->cfid); + memset(tcon->cfid.fid, 0, sizeof(struct cifs_fid)); + mutex_unlock(&tcon->cfid.fid_mutex); +} + +static void +smb2_cached_lease_break(struct work_struct *work) +{ + struct cached_fid *cfid = container_of(work, + struct cached_fid, lease_break); + + close_cached_dir_lease(cfid); +} + +int cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16]) +{ + if (tcon->cfid.is_valid && + !memcmp(lease_key, + tcon->cfid.fid->lease_key, + SMB2_LEASE_KEY_SIZE)) { + tcon->cfid.time = 0; + INIT_WORK(&tcon->cfid.lease_break, + smb2_cached_lease_break); + queue_work(cifsiod_wq, + &tcon->cfid.lease_break); + return true; + } + return false; +} diff --git a/fs/cifs/cached_dir.h b/fs/cifs/cached_dir.h new file mode 100644 index 0000000000000..51c6b968f8b67 --- /dev/null +++ b/fs/cifs/cached_dir.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Functions to handle the cached directory entries + * + * Copyright (c) 2022, Ronnie Sahlberg + */ + +#ifndef _CACHED_DIR_H +#define _CACHED_DIR_H + + +extern int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, + const char *path, + struct cifs_sb_info *cifs_sb, + struct cached_fid **cfid); +extern int open_cached_dir_by_dentry(struct cifs_tcon *tcon, + struct dentry *dentry, + struct cached_fid **cfid); +extern void close_cached_dir(struct cached_fid *cfid); +extern void close_cached_dir_lease(struct cached_fid *cfid); +extern void close_cached_dir_lease_locked(struct cached_fid *cfid); +extern void close_all_cached_dirs(struct cifs_sb_info *cifs_sb); +extern void invalidate_all_cached_dirs(struct cifs_tcon *tcon); +extern int cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16]); + +#endif /* _CACHED_DIR_H */ diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index 8849f08521103..945fb083cea7a 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -46,6 +46,7 @@ #include "netlink.h" #endif #include "fs_context.h" +#include "cached_dir.h" /* * DOS dates from 1980/1/1 through 2107/12/31 @@ -283,30 +284,13 @@ cifs_read_super(struct super_block *sb) static void cifs_kill_sb(struct super_block *sb) { struct cifs_sb_info *cifs_sb = CIFS_SB(sb); - struct cifs_tcon *tcon; - struct cached_fid *cfid; - struct rb_root *root = &cifs_sb->tlink_tree; - struct rb_node *node; - struct tcon_link *tlink; /* * We ned to release all dentries for the cached directories * before we kill the sb. */ if (cifs_sb->root) { - for (node = rb_first(root); node; node = rb_next(node)) { - tlink = rb_entry(node, struct tcon_link, tl_rbnode); - tcon = tlink_tcon(tlink); - if (IS_ERR(tcon)) - continue; - cfid = &tcon->crfid; - mutex_lock(&cfid->fid_mutex); - if (cfid->dentry) { - dput(cfid->dentry); - cfid->dentry = NULL; - } - mutex_unlock(&cfid->fid_mutex); - } + close_all_cached_dirs(cifs_sb); /* finally release root dentry */ dput(cifs_sb->root); diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index a93024eaf251b..8b82f13d11e09 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -1257,7 +1257,7 @@ struct cifs_tcon { struct fscache_volume *fscache; /* cookie for share */ #endif struct list_head pending_opens; /* list of incomplete opens */ - struct cached_fid crfid; /* Cached root fid */ + struct cached_fid cfid; /* Cached root fid */ /* BB add field for back pointer to sb struct(s)? */ #ifdef CONFIG_CIFS_DFS_UPCALL struct list_head ulist; /* cache update list */ diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index daaadffa2b88f..87a77a684339f 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -597,7 +597,6 @@ enum securityEnum cifs_select_sectype(struct TCP_Server_Info *, struct cifs_aio_ctx *cifs_aio_ctx_alloc(void); void cifs_aio_ctx_release(struct kref *refcount); int setup_aio_ctx_iter(struct cifs_aio_ctx *ctx, struct iov_iter *iter, int rw); -void smb2_cached_lease_break(struct work_struct *work); int cifs_alloc_hash(const char *name, struct crypto_shash **shash, struct sdesc **sdesc); diff --git a/fs/cifs/file.c b/fs/cifs/file.c index 09975bd7d8605..42f2639a1a66a 100644 --- a/fs/cifs/file.c +++ b/fs/cifs/file.c @@ -34,6 +34,7 @@ #include "smbdirect.h" #include "fs_context.h" #include "cifs_ioctl.h" +#include "cached_dir.h" /* * Mark as invalid, all open files on tree connections since they @@ -64,13 +65,7 @@ cifs_mark_open_files_invalid(struct cifs_tcon *tcon) } spin_unlock(&tcon->open_file_lock); - mutex_lock(&tcon->crfid.fid_mutex); - tcon->crfid.is_valid = false; - /* cached handle is not valid, so SMB2_CLOSE won't be sent below */ - close_cached_dir_lease_locked(&tcon->crfid); - memset(tcon->crfid.fid, 0, sizeof(struct cifs_fid)); - mutex_unlock(&tcon->crfid.fid_mutex); - + invalidate_all_cached_dirs(tcon); spin_lock(&tcon->tc_lock); if (tcon->status == TID_IN_FILES_INVALIDATE) tcon->status = TID_NEED_TCON; diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c index eeeaba3dec053..bac08c20f559b 100644 --- a/fs/cifs/inode.c +++ b/fs/cifs/inode.c @@ -25,6 +25,7 @@ #include "fscache.h" #include "fs_context.h" #include "cifs_ioctl.h" +#include "cached_dir.h" static void cifs_set_ops(struct inode *inode) { diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c index 7a906067db047..ea0405cfaee31 100644 --- a/fs/cifs/misc.c +++ b/fs/cifs/misc.c @@ -116,13 +116,13 @@ tconInfoAlloc(void) ret_buf = kzalloc(sizeof(*ret_buf), GFP_KERNEL); if (!ret_buf) return NULL; - ret_buf->crfid.fid = kzalloc(sizeof(*ret_buf->crfid.fid), GFP_KERNEL); - if (!ret_buf->crfid.fid) { + ret_buf->cfid.fid = kzalloc(sizeof(*ret_buf->cfid.fid), GFP_KERNEL); + if (!ret_buf->cfid.fid) { kfree(ret_buf); return NULL; } - INIT_LIST_HEAD(&ret_buf->crfid.dirents.entries); - mutex_init(&ret_buf->crfid.dirents.de_mutex); + INIT_LIST_HEAD(&ret_buf->cfid.dirents.entries); + mutex_init(&ret_buf->cfid.dirents.de_mutex); atomic_inc(&tconInfoAllocCount); ret_buf->status = TID_NEW; @@ -131,7 +131,7 @@ tconInfoAlloc(void) INIT_LIST_HEAD(&ret_buf->openFileList); INIT_LIST_HEAD(&ret_buf->tcon_list); spin_lock_init(&ret_buf->open_file_lock); - mutex_init(&ret_buf->crfid.fid_mutex); + mutex_init(&ret_buf->cfid.fid_mutex); spin_lock_init(&ret_buf->stat_lock); atomic_set(&ret_buf->num_local_opens, 0); atomic_set(&ret_buf->num_remote_opens, 0); @@ -149,7 +149,7 @@ tconInfoFree(struct cifs_tcon *buf_to_free) atomic_dec(&tconInfoAllocCount); kfree(buf_to_free->nativeFileSystem); kfree_sensitive(buf_to_free->password); - kfree(buf_to_free->crfid.fid); + kfree(buf_to_free->cfid.fid); kfree(buf_to_free); } diff --git a/fs/cifs/readdir.c b/fs/cifs/readdir.c index 384cabdf47caa..a06072ae6c7e2 100644 --- a/fs/cifs/readdir.c +++ b/fs/cifs/readdir.c @@ -21,6 +21,7 @@ #include "cifsfs.h" #include "smb2proto.h" #include "fs_context.h" +#include "cached_dir.h" /* * To be safe - for UCS to UTF-8 with strings loaded with the rare long diff --git a/fs/cifs/smb2inode.c b/fs/cifs/smb2inode.c index 8571a459c7101..f6f9fc3f2e2da 100644 --- a/fs/cifs/smb2inode.c +++ b/fs/cifs/smb2inode.c @@ -23,6 +23,7 @@ #include "smb2glob.h" #include "smb2pdu.h" #include "smb2proto.h" +#include "cached_dir.h" static void free_set_inf_compound(struct smb_rqst *rqst) @@ -518,9 +519,9 @@ smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon, rc = open_cached_dir(xid, tcon, full_path, cifs_sb, &cfid); /* If it is a root and its handle is cached then use it */ if (!rc) { - if (tcon->crfid.file_all_info_is_valid) { + if (tcon->cfid.file_all_info_is_valid) { move_smb2_info_to_cifs(data, - &tcon->crfid.file_all_info); + &tcon->cfid.file_all_info); } else { rc = SMB2_query_info(xid, tcon, cfid->fid->persistent_fid, diff --git a/fs/cifs/smb2misc.c b/fs/cifs/smb2misc.c index 818cc4dee0e2e..6a6ec6efb45a9 100644 --- a/fs/cifs/smb2misc.c +++ b/fs/cifs/smb2misc.c @@ -16,6 +16,7 @@ #include "smb2status.h" #include "smb2glob.h" #include "nterr.h" +#include "cached_dir.h" static int check_smb2_hdr(struct smb2_hdr *shdr, __u64 mid) @@ -648,15 +649,7 @@ smb2_is_valid_lease_break(char *buffer) } spin_unlock(&tcon->open_file_lock); - if (tcon->crfid.is_valid && - !memcmp(rsp->LeaseKey, - tcon->crfid.fid->lease_key, - SMB2_LEASE_KEY_SIZE)) { - tcon->crfid.time = 0; - INIT_WORK(&tcon->crfid.lease_break, - smb2_cached_lease_break); - queue_work(cifsiod_wq, - &tcon->crfid.lease_break); + if (cached_dir_lease_break(tcon, rsp->LeaseKey)) { spin_unlock(&cifs_tcp_ses_lock); return true; } diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index c0039dc0715ae..8cb1eed1dd63c 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -27,6 +27,7 @@ #include "smbdirect.h" #include "fscache.h" #include "fs_context.h" +#include "cached_dir.h" /* Change credits for different ops and return the total number of credits */ static int @@ -701,300 +702,6 @@ SMB3_request_interfaces(const unsigned int xid, struct cifs_tcon *tcon) return rc; } -static void -smb2_close_cached_fid(struct kref *ref) -{ - struct cached_fid *cfid = container_of(ref, struct cached_fid, - refcount); - struct cached_dirent *dirent, *q; - - if (cfid->is_valid) { - cifs_dbg(FYI, "clear cached root file handle\n"); - SMB2_close(0, cfid->tcon, cfid->fid->persistent_fid, - cfid->fid->volatile_fid); - } - - /* - * We only check validity above to send SMB2_close, - * but we still need to invalidate these entries - * when this function is called - */ - cfid->is_valid = false; - cfid->file_all_info_is_valid = false; - cfid->has_lease = false; - if (cfid->dentry) { - dput(cfid->dentry); - cfid->dentry = NULL; - } - /* - * Delete all cached dirent names - */ - mutex_lock(&cfid->dirents.de_mutex); - list_for_each_entry_safe(dirent, q, &cfid->dirents.entries, entry) { - list_del(&dirent->entry); - kfree(dirent->name); - kfree(dirent); - } - cfid->dirents.is_valid = 0; - cfid->dirents.is_failed = 0; - cfid->dirents.ctx = NULL; - cfid->dirents.pos = 0; - mutex_unlock(&cfid->dirents.de_mutex); - -} - -void close_cached_dir(struct cached_fid *cfid) -{ - mutex_lock(&cfid->fid_mutex); - kref_put(&cfid->refcount, smb2_close_cached_fid); - mutex_unlock(&cfid->fid_mutex); -} - -void close_cached_dir_lease_locked(struct cached_fid *cfid) -{ - if (cfid->has_lease) { - cfid->has_lease = false; - kref_put(&cfid->refcount, smb2_close_cached_fid); - } -} - -void close_cached_dir_lease(struct cached_fid *cfid) -{ - mutex_lock(&cfid->fid_mutex); - close_cached_dir_lease_locked(cfid); - mutex_unlock(&cfid->fid_mutex); -} - -void -smb2_cached_lease_break(struct work_struct *work) -{ - struct cached_fid *cfid = container_of(work, - struct cached_fid, lease_break); - - close_cached_dir_lease(cfid); -} - -/* - * Open the and cache a directory handle. - * Only supported for the root handle. - * If error then *cfid is not initialized. - */ -int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, - const char *path, - struct cifs_sb_info *cifs_sb, - struct cached_fid **cfid) -{ - struct cifs_ses *ses; - struct TCP_Server_Info *server; - struct cifs_open_parms oparms; - struct smb2_create_rsp *o_rsp = NULL; - struct smb2_query_info_rsp *qi_rsp = NULL; - int resp_buftype[2]; - struct smb_rqst rqst[2]; - struct kvec rsp_iov[2]; - struct kvec open_iov[SMB2_CREATE_IOV_SIZE]; - struct kvec qi_iov[1]; - int rc, flags = 0; - __le16 utf16_path = 0; /* Null - since an open of top of share */ - u8 oplock = SMB2_OPLOCK_LEVEL_II; - struct cifs_fid *pfid; - struct dentry *dentry; - - if (tcon == NULL || tcon->nohandlecache || - is_smb1_server(tcon->ses->server)) - return -ENOTSUPP; - - ses = tcon->ses; - server = ses->server; - - if (cifs_sb->root == NULL) - return -ENOENT; - - if (strlen(path)) - return -ENOENT; - - dentry = cifs_sb->root; - - mutex_lock(&tcon->crfid.fid_mutex); - if (tcon->crfid.is_valid) { - cifs_dbg(FYI, "found a cached root file handle\n"); - *cfid = &tcon->crfid; - kref_get(&tcon->crfid.refcount); - mutex_unlock(&tcon->crfid.fid_mutex); - return 0; - } - - /* - * We do not hold the lock for the open because in case - * SMB2_open needs to reconnect, it will end up calling - * cifs_mark_open_files_invalid() which takes the lock again - * thus causing a deadlock - */ - - mutex_unlock(&tcon->crfid.fid_mutex); - - if (smb3_encryption_required(tcon)) - flags |= CIFS_TRANSFORM_REQ; - - if (!server->ops->new_lease_key) - return -EIO; - - pfid = tcon->crfid.fid; - server->ops->new_lease_key(pfid); - - memset(rqst, 0, sizeof(rqst)); - resp_buftype[0] = resp_buftype[1] = CIFS_NO_BUFFER; - memset(rsp_iov, 0, sizeof(rsp_iov)); - - /* Open */ - memset(&open_iov, 0, sizeof(open_iov)); - rqst[0].rq_iov = open_iov; - rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE; - - oparms.tcon = tcon; - oparms.create_options = cifs_create_options(cifs_sb, CREATE_NOT_FILE); - oparms.desired_access = FILE_READ_ATTRIBUTES; - oparms.disposition = FILE_OPEN; - oparms.fid = pfid; - oparms.reconnect = false; - - rc = SMB2_open_init(tcon, server, - &rqst[0], &oplock, &oparms, &utf16_path); - if (rc) - goto oshr_free; - smb2_set_next_command(tcon, &rqst[0]); - - memset(&qi_iov, 0, sizeof(qi_iov)); - rqst[1].rq_iov = qi_iov; - rqst[1].rq_nvec = 1; - - rc = SMB2_query_info_init(tcon, server, - &rqst[1], COMPOUND_FID, - COMPOUND_FID, FILE_ALL_INFORMATION, - SMB2_O_INFO_FILE, 0, - sizeof(struct smb2_file_all_info) + - PATH_MAX * 2, 0, NULL); - if (rc) - goto oshr_free; - - smb2_set_related(&rqst[1]); - - rc = compound_send_recv(xid, ses, server, - flags, 2, rqst, - resp_buftype, rsp_iov); - mutex_lock(&tcon->crfid.fid_mutex); - - /* - * Now we need to check again as the cached root might have - * been successfully re-opened from a concurrent process - */ - - if (tcon->crfid.is_valid) { - /* work was already done */ - - /* stash fids for close() later */ - struct cifs_fid fid = { - .persistent_fid = pfid->persistent_fid, - .volatile_fid = pfid->volatile_fid, - }; - - /* - * caller expects this func to set the fid in crfid to valid - * cached root, so increment the refcount. - */ - kref_get(&tcon->crfid.refcount); - - mutex_unlock(&tcon->crfid.fid_mutex); - - if (rc == 0) { - /* close extra handle outside of crit sec */ - SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid); - } - rc = 0; - goto oshr_free; - } - - /* Cached root is still invalid, continue normaly */ - - if (rc) { - if (rc == -EREMCHG) { - tcon->need_reconnect = true; - pr_warn_once("server share %s deleted\n", - tcon->treeName); - } - goto oshr_exit; - } - - atomic_inc(&tcon->num_remote_opens); - - o_rsp = (struct smb2_create_rsp *)rsp_iov[0].iov_base; - oparms.fid->persistent_fid = o_rsp->PersistentFileId; - oparms.fid->volatile_fid = o_rsp->VolatileFileId; -#ifdef CONFIG_CIFS_DEBUG2 - oparms.fid->mid = le64_to_cpu(o_rsp->hdr.MessageId); -#endif /* CIFS_DEBUG2 */ - - tcon->crfid.tcon = tcon; - tcon->crfid.is_valid = true; - tcon->crfid.dentry = dentry; - dget(dentry); - kref_init(&tcon->crfid.refcount); - - /* BB TBD check to see if oplock level check can be removed below */ - if (o_rsp->OplockLevel == SMB2_OPLOCK_LEVEL_LEASE) { - /* - * See commit 2f94a3125b87. Increment the refcount when we - * get a lease for root, release it if lease break occurs - */ - kref_get(&tcon->crfid.refcount); - tcon->crfid.has_lease = true; - smb2_parse_contexts(server, o_rsp, - &oparms.fid->epoch, - oparms.fid->lease_key, &oplock, - NULL, NULL); - } else - goto oshr_exit; - - qi_rsp = (struct smb2_query_info_rsp *)rsp_iov[1].iov_base; - if (le32_to_cpu(qi_rsp->OutputBufferLength) < sizeof(struct smb2_file_all_info)) - goto oshr_exit; - if (!smb2_validate_and_copy_iov( - le16_to_cpu(qi_rsp->OutputBufferOffset), - sizeof(struct smb2_file_all_info), - &rsp_iov[1], sizeof(struct smb2_file_all_info), - (char *)&tcon->crfid.file_all_info)) - tcon->crfid.file_all_info_is_valid = true; - tcon->crfid.time = jiffies; - - -oshr_exit: - mutex_unlock(&tcon->crfid.fid_mutex); -oshr_free: - SMB2_open_free(&rqst[0]); - SMB2_query_info_free(&rqst[1]); - free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base); - free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base); - if (rc == 0) - *cfid = &tcon->crfid; - return rc; -} - -int open_cached_dir_by_dentry(struct cifs_tcon *tcon, - struct dentry *dentry, - struct cached_fid **cfid) -{ - mutex_lock(&tcon->crfid.fid_mutex); - if (tcon->crfid.dentry == dentry) { - cifs_dbg(FYI, "found a cached root file handle by dentry\n"); - *cfid = &tcon->crfid; - kref_get(&tcon->crfid.refcount); - mutex_unlock(&tcon->crfid.fid_mutex); - return 0; - } - mutex_unlock(&tcon->crfid.fid_mutex); - return -ENOENT; -} - static void smb3_qfs_tcon(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb) @@ -1077,7 +784,7 @@ smb2_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_open_parms oparms; struct cifs_fid fid; - if ((*full_path == 0) && tcon->crfid.is_valid) + if ((*full_path == 0) && tcon->cfid.is_valid) return 0; utf16_path = cifs_convert_path_to_utf16(full_path, cifs_sb); diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index 590a1d4ac140c..7c200b9382674 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -39,6 +39,7 @@ #ifdef CONFIG_CIFS_DFS_UPCALL #include "dfs_cache.h" #endif +#include "cached_dir.h" /* * The following table defines the expected "StructureSize" of SMB2 requests @@ -1978,7 +1979,7 @@ SMB2_tdis(const unsigned int xid, struct cifs_tcon *tcon) } spin_unlock(&ses->chan_lock); - close_cached_dir_lease(&tcon->crfid); + close_cached_dir_lease(&tcon->cfid); rc = smb2_plain_req_init(SMB2_TREE_DISCONNECT, tcon, ses->server, (void **) &req, diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h index a69f1eed1cfe5..51c5bf4a338ae 100644 --- a/fs/cifs/smb2proto.h +++ b/fs/cifs/smb2proto.h @@ -54,16 +54,6 @@ extern bool smb2_is_valid_oplock_break(char *buffer, extern int smb3_handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid); -extern int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, - const char *path, - struct cifs_sb_info *cifs_sb, - struct cached_fid **cfid); -extern int open_cached_dir_by_dentry(struct cifs_tcon *tcon, - struct dentry *dentry, - struct cached_fid **cfid); -extern void close_cached_dir(struct cached_fid *cfid); -extern void close_cached_dir_lease(struct cached_fid *cfid); -extern void close_cached_dir_lease_locked(struct cached_fid *cfid); extern void move_smb2_info_to_cifs(FILE_ALL_INFO *dst, struct smb2_file_all_info *src); extern int smb2_query_reparse_tag(const unsigned int xid, struct cifs_tcon *tcon, From dcb45fd7f501f86481473bde14fa0a9069ad5cdc Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Tue, 9 Aug 2022 12:11:49 +1000 Subject: [PATCH 4/8] cifs: Do not use tcon->cfid directly, use the cfid we get from open_cached_dir They are the same right now but tcon-> will later point to a different type of struct containing a list of cfids. Signed-off-by: Ronnie Sahlberg Reviewed-by: Paulo Alcantara (SUSE) Signed-off-by: Steve French --- fs/cifs/smb2inode.c | 4 ++-- fs/cifs/smb2pdu.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/cifs/smb2inode.c b/fs/cifs/smb2inode.c index f6f9fc3f2e2da..09f01f70e0204 100644 --- a/fs/cifs/smb2inode.c +++ b/fs/cifs/smb2inode.c @@ -519,9 +519,9 @@ smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon, rc = open_cached_dir(xid, tcon, full_path, cifs_sb, &cfid); /* If it is a root and its handle is cached then use it */ if (!rc) { - if (tcon->cfid.file_all_info_is_valid) { + if (cfid->file_all_info_is_valid) { move_smb2_info_to_cifs(data, - &tcon->cfid.file_all_info); + &cfid->file_all_info); } else { rc = SMB2_query_info(xid, tcon, cfid->fid->persistent_fid, diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index 7c200b9382674..9b31ea946d454 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -1979,7 +1979,7 @@ SMB2_tdis(const unsigned int xid, struct cifs_tcon *tcon) } spin_unlock(&ses->chan_lock); - close_cached_dir_lease(&tcon->cfid); + invalidate_all_cached_dirs(tcon); rc = smb2_plain_req_init(SMB2_TREE_DISCONNECT, tcon, ses->server, (void **) &req, From 5efdd9122eff772eae2feae9f0fc0ec02d4846a3 Mon Sep 17 00:00:00 2001 From: Steve French Date: Thu, 11 Aug 2022 00:53:00 -0500 Subject: [PATCH 5/8] smb3: allow deferred close timeout to be configurable Deferred close can be a very useful feature for allowing caching data for read, and for minimizing the number of reopens needed for a file that is repeatedly opened and close but there are workloads where its default (1 second, similar to actimeo/acregmax) is much too small. Allow the user to configure the amount of time we can defer sending the final smb3 close when we have a handle lease on the file (rather than forcing it to depend on value of actimeo which is often unrelated, and less safe). Adds new mount parameter "closetimeo=" which is the maximum number of seconds we can wait before sending an SMB3 close when we have a handle lease for it. Default value also is set to slightly larger at 5 seconds (although some other clients use larger default this should still help). Suggested-by: Bharath SM Reviewed-by: Bharath SM Reviewed-by: Shyam Prasad N Reviewed-by: Paulo Alcantara (SUSE) Signed-off-by: Steve French --- fs/cifs/cifsfs.c | 1 + fs/cifs/connect.c | 2 ++ fs/cifs/file.c | 4 ++-- fs/cifs/fs_context.c | 9 +++++++++ fs/cifs/fs_context.h | 8 ++++++++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index 945fb083cea7a..f54d8bf2732a5 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -693,6 +693,7 @@ cifs_show_options(struct seq_file *s, struct dentry *root) seq_printf(s, ",acdirmax=%lu", cifs_sb->ctx->acdirmax / HZ); seq_printf(s, ",acregmax=%lu", cifs_sb->ctx->acregmax / HZ); } + seq_printf(s, ",closetimeo=%lu", cifs_sb->ctx->closetimeo / HZ); if (tcon->ses->chan_max > 1) seq_printf(s, ",multichannel,max_channels=%zu", diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 7f205a9a2de4b..9111c025bcb8e 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -2681,6 +2681,8 @@ compare_mount_options(struct super_block *sb, struct cifs_mnt_data *mnt_data) return 0; if (old->ctx->acdirmax != new->ctx->acdirmax) return 0; + if (old->ctx->closetimeo != new->ctx->closetimeo) + return 0; return 1; } diff --git a/fs/cifs/file.c b/fs/cifs/file.c index 42f2639a1a66a..2c5eae7d31f42 100644 --- a/fs/cifs/file.c +++ b/fs/cifs/file.c @@ -964,12 +964,12 @@ int cifs_close(struct inode *inode, struct file *file) * So, Increase the ref count to avoid use-after-free. */ if (!mod_delayed_work(deferredclose_wq, - &cfile->deferred, cifs_sb->ctx->acregmax)) + &cfile->deferred, cifs_sb->ctx->closetimeo)) cifsFileInfo_get(cfile); } else { /* Deferred close for files */ queue_delayed_work(deferredclose_wq, - &cfile->deferred, cifs_sb->ctx->acregmax); + &cfile->deferred, cifs_sb->ctx->closetimeo); cfile->deferred_close_scheduled = true; spin_unlock(&cinode->deferred_lock); return 0; diff --git a/fs/cifs/fs_context.c b/fs/cifs/fs_context.c index 8dc0d923ef6a9..0e13dec86b252 100644 --- a/fs/cifs/fs_context.c +++ b/fs/cifs/fs_context.c @@ -147,6 +147,7 @@ const struct fs_parameter_spec smb3_fs_parameters[] = { fsparam_u32("actimeo", Opt_actimeo), fsparam_u32("acdirmax", Opt_acdirmax), fsparam_u32("acregmax", Opt_acregmax), + fsparam_u32("closetimeo", Opt_closetimeo), fsparam_u32("echo_interval", Opt_echo_interval), fsparam_u32("max_credits", Opt_max_credits), fsparam_u32("handletimeout", Opt_handletimeout), @@ -1074,6 +1075,13 @@ static int smb3_fs_context_parse_param(struct fs_context *fc, } ctx->acdirmax = ctx->acregmax = HZ * result.uint_32; break; + case Opt_closetimeo: + ctx->closetimeo = HZ * result.uint_32; + if (ctx->closetimeo > SMB3_MAX_DCLOSETIMEO) { + cifs_errorf(fc, "closetimeo too large\n"); + goto cifs_parse_mount_err; + } + break; case Opt_echo_interval: ctx->echo_interval = result.uint_32; break; @@ -1521,6 +1529,7 @@ int smb3_init_fs_context(struct fs_context *fc) ctx->acregmax = CIFS_DEF_ACTIMEO; ctx->acdirmax = CIFS_DEF_ACTIMEO; + ctx->closetimeo = SMB3_DEF_DCLOSETIMEO; /* Most clients set timeout to 0, allows server to use its default */ ctx->handle_timeout = 0; /* See MS-SMB2 spec section 2.2.14.2.12 */ diff --git a/fs/cifs/fs_context.h b/fs/cifs/fs_context.h index 5f093cb7e9b98..bbaee4c2281f8 100644 --- a/fs/cifs/fs_context.h +++ b/fs/cifs/fs_context.h @@ -125,6 +125,7 @@ enum cifs_param { Opt_actimeo, Opt_acdirmax, Opt_acregmax, + Opt_closetimeo, Opt_echo_interval, Opt_max_credits, Opt_snapshot, @@ -247,6 +248,8 @@ struct smb3_fs_context { /* attribute cache timemout for files and directories in jiffies */ unsigned long acregmax; unsigned long acdirmax; + /* timeout for deferred close of files in jiffies */ + unsigned long closetimeo; struct smb_version_operations *ops; struct smb_version_values *vals; char *prepath; @@ -279,4 +282,9 @@ static inline struct smb3_fs_context *smb3_fc2context(const struct fs_context *f extern int smb3_fs_context_dup(struct smb3_fs_context *new_ctx, struct smb3_fs_context *ctx); extern void smb3_update_mnt_flags(struct cifs_sb_info *cifs_sb); +/* + * max deferred close timeout (jiffies) - 2^30 + */ +#define SMB3_MAX_DCLOSETIMEO (1 << 30) +#define SMB3_DEF_DCLOSETIMEO (5 * HZ) /* Can increase later, other clients use larger */ #endif From 9e31678fb403eae0f4fe37c6374be098835c73cd Mon Sep 17 00:00:00 2001 From: Bharath SM Date: Thu, 11 Aug 2022 19:46:11 +0000 Subject: [PATCH 6/8] SMB3: fix lease break timeout when multiple deferred close handles for the same file. Solution is to send lease break ack immediately even in case of deferred close handles to avoid lease break request timing out and let deferred closed handle gets closed as scheduled. Later patches could optimize cases where we then close some of these handles sooner for the cases where lease break is to 'none' Cc: stable@kernel.org Signed-off-by: Bharath SM Signed-off-by: Steve French --- fs/cifs/file.c | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/fs/cifs/file.c b/fs/cifs/file.c index 2c5eae7d31f42..c00f71884b82b 100644 --- a/fs/cifs/file.c +++ b/fs/cifs/file.c @@ -5061,8 +5061,6 @@ void cifs_oplock_break(struct work_struct *work) struct TCP_Server_Info *server = tcon->ses->server; int rc = 0; bool purge_cache = false; - bool is_deferred = false; - struct cifs_deferred_close *dclose; wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS, TASK_UNINTERRUPTIBLE); @@ -5098,22 +5096,6 @@ void cifs_oplock_break(struct work_struct *work) cifs_dbg(VFS, "Push locks rc = %d\n", rc); oplock_break_ack: - /* - * When oplock break is received and there are no active - * file handles but cached, then schedule deferred close immediately. - * So, new open will not use cached handle. - */ - spin_lock(&CIFS_I(inode)->deferred_lock); - is_deferred = cifs_is_deferred_close(cfile, &dclose); - spin_unlock(&CIFS_I(inode)->deferred_lock); - if (is_deferred && - cfile->deferred_close_scheduled && - delayed_work_pending(&cfile->deferred)) { - if (cancel_delayed_work(&cfile->deferred)) { - _cifsFileInfo_put(cfile, false, false); - goto oplock_break_done; - } - } /* * releasing stale oplock after recent reconnect of smb session using * a now incorrect file handle is not a data integrity issue but do @@ -5125,7 +5107,7 @@ void cifs_oplock_break(struct work_struct *work) cinode); cifs_dbg(FYI, "Oplock release rc = %d\n", rc); } -oplock_break_done: + _cifsFileInfo_put(cfile, false /* do not wait for ourself */, false); cifs_done_oplock_break(cinode); } From a63ec83c462b5b1439f71ace751e8985dfb3fcab Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Thu, 11 Aug 2022 19:04:29 -0500 Subject: [PATCH 7/8] cifs: Add constructor/destructors for tcon->cfid and move the structure definitions into cached_dir.h Reviewed-by: Paulo Alcantara (SUSE) Signed-off-by: Ronnie Sahlberg Signed-off-by: Steve French --- fs/cifs/cached_dir.c | 110 ++++++++++++++++++++++++++----------------- fs/cifs/cached_dir.h | 38 +++++++++++++++ fs/cifs/cifsglob.h | 38 +-------------- fs/cifs/misc.c | 20 ++++---- fs/cifs/smb2inode.c | 4 +- fs/cifs/smb2ops.c | 8 ++-- 6 files changed, 121 insertions(+), 97 deletions(-) diff --git a/fs/cifs/cached_dir.c b/fs/cifs/cached_dir.c index 753b850b6f878..78e8deb82a0a1 100644 --- a/fs/cifs/cached_dir.c +++ b/fs/cifs/cached_dir.c @@ -18,7 +18,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path, struct cifs_sb_info *cifs_sb, - struct cached_fid **cfid) + struct cached_fid **ret_cfid) { struct cifs_ses *ses; struct TCP_Server_Info *server; @@ -35,6 +35,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, u8 oplock = SMB2_OPLOCK_LEVEL_II; struct cifs_fid *pfid; struct dentry *dentry; + struct cached_fid *cfid; if (tcon == NULL || tcon->nohandlecache || is_smb1_server(tcon->ses->server)) @@ -51,12 +52,13 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, dentry = cifs_sb->root; - mutex_lock(&tcon->cfid.fid_mutex); - if (tcon->cfid.is_valid) { + cfid = tcon->cfid; + mutex_lock(&cfid->fid_mutex); + if (cfid->is_valid) { cifs_dbg(FYI, "found a cached root file handle\n"); - *cfid = &tcon->cfid; - kref_get(&tcon->cfid.refcount); - mutex_unlock(&tcon->cfid.fid_mutex); + *ret_cfid = cfid; + kref_get(&cfid->refcount); + mutex_unlock(&cfid->fid_mutex); return 0; } @@ -67,7 +69,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, * thus causing a deadlock */ - mutex_unlock(&tcon->cfid.fid_mutex); + mutex_unlock(&cfid->fid_mutex); if (smb3_encryption_required(tcon)) flags |= CIFS_TRANSFORM_REQ; @@ -75,7 +77,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, if (!server->ops->new_lease_key) return -EIO; - pfid = tcon->cfid.fid; + pfid = &cfid->fid; server->ops->new_lease_key(pfid); memset(rqst, 0, sizeof(rqst)); @@ -118,14 +120,14 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, rc = compound_send_recv(xid, ses, server, flags, 2, rqst, resp_buftype, rsp_iov); - mutex_lock(&tcon->cfid.fid_mutex); + mutex_lock(&cfid->fid_mutex); /* * Now we need to check again as the cached root might have * been successfully re-opened from a concurrent process */ - if (tcon->cfid.is_valid) { + if (cfid->is_valid) { /* work was already done */ /* stash fids for close() later */ @@ -138,9 +140,9 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, * caller expects this func to set the fid in cfid to valid * cached root, so increment the refcount. */ - kref_get(&tcon->cfid.refcount); + kref_get(&cfid->refcount); - mutex_unlock(&tcon->cfid.fid_mutex); + mutex_unlock(&cfid->fid_mutex); if (rc == 0) { /* close extra handle outside of crit sec */ @@ -170,11 +172,11 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, oparms.fid->mid = le64_to_cpu(o_rsp->hdr.MessageId); #endif /* CIFS_DEBUG2 */ - tcon->cfid.tcon = tcon; - tcon->cfid.is_valid = true; - tcon->cfid.dentry = dentry; + cfid->tcon = tcon; + cfid->is_valid = true; + cfid->dentry = dentry; dget(dentry); - kref_init(&tcon->cfid.refcount); + kref_init(&cfid->refcount); /* BB TBD check to see if oplock level check can be removed below */ if (o_rsp->OplockLevel == SMB2_OPLOCK_LEVEL_LEASE) { @@ -182,8 +184,8 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, * See commit 2f94a3125b87. Increment the refcount when we * get a lease for root, release it if lease break occurs */ - kref_get(&tcon->cfid.refcount); - tcon->cfid.has_lease = true; + kref_get(&cfid->refcount); + cfid->has_lease = true; smb2_parse_contexts(server, o_rsp, &oparms.fid->epoch, oparms.fid->lease_key, &oplock, @@ -198,37 +200,41 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, le16_to_cpu(qi_rsp->OutputBufferOffset), sizeof(struct smb2_file_all_info), &rsp_iov[1], sizeof(struct smb2_file_all_info), - (char *)&tcon->cfid.file_all_info)) - tcon->cfid.file_all_info_is_valid = true; - tcon->cfid.time = jiffies; + (char *)&cfid->file_all_info)) + cfid->file_all_info_is_valid = true; + cfid->time = jiffies; oshr_exit: - mutex_unlock(&tcon->cfid.fid_mutex); + mutex_unlock(&cfid->fid_mutex); oshr_free: SMB2_open_free(&rqst[0]); SMB2_query_info_free(&rqst[1]); free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base); free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base); if (rc == 0) - *cfid = &tcon->cfid; + *ret_cfid = cfid; return rc; } int open_cached_dir_by_dentry(struct cifs_tcon *tcon, struct dentry *dentry, - struct cached_fid **cfid) + struct cached_fid **ret_cfid) { - mutex_lock(&tcon->cfid.fid_mutex); - if (tcon->cfid.dentry == dentry) { + struct cached_fid *cfid; + + cfid = tcon->cfid; + + mutex_lock(&cfid->fid_mutex); + if (cfid->dentry == dentry) { cifs_dbg(FYI, "found a cached root file handle by dentry\n"); - *cfid = &tcon->cfid; - kref_get(&tcon->cfid.refcount); - mutex_unlock(&tcon->cfid.fid_mutex); + *ret_cfid = cfid; + kref_get(&cfid->refcount); + mutex_unlock(&cfid->fid_mutex); return 0; } - mutex_unlock(&tcon->cfid.fid_mutex); + mutex_unlock(&cfid->fid_mutex); return -ENOENT; } @@ -241,8 +247,8 @@ smb2_close_cached_fid(struct kref *ref) if (cfid->is_valid) { cifs_dbg(FYI, "clear cached root file handle\n"); - SMB2_close(0, cfid->tcon, cfid->fid->persistent_fid, - cfid->fid->volatile_fid); + SMB2_close(0, cfid->tcon, cfid->fid.persistent_fid, + cfid->fid.volatile_fid); } /* @@ -312,7 +318,7 @@ void close_all_cached_dirs(struct cifs_sb_info *cifs_sb) tcon = tlink_tcon(tlink); if (IS_ERR(tcon)) continue; - cfid = &tcon->cfid; + cfid = tcon->cfid; mutex_lock(&cfid->fid_mutex); if (cfid->dentry) { dput(cfid->dentry); @@ -328,12 +334,12 @@ void close_all_cached_dirs(struct cifs_sb_info *cifs_sb) */ void invalidate_all_cached_dirs(struct cifs_tcon *tcon) { - mutex_lock(&tcon->cfid.fid_mutex); - tcon->cfid.is_valid = false; + mutex_lock(&tcon->cfid->fid_mutex); + tcon->cfid->is_valid = false; /* cached handle is not valid, so SMB2_CLOSE won't be sent below */ - close_cached_dir_lease_locked(&tcon->cfid); - memset(tcon->cfid.fid, 0, sizeof(struct cifs_fid)); - mutex_unlock(&tcon->cfid.fid_mutex); + close_cached_dir_lease_locked(tcon->cfid); + memset(&tcon->cfid->fid, 0, sizeof(struct cifs_fid)); + mutex_unlock(&tcon->cfid->fid_mutex); } static void @@ -347,16 +353,34 @@ smb2_cached_lease_break(struct work_struct *work) int cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16]) { - if (tcon->cfid.is_valid && + if (tcon->cfid->is_valid && !memcmp(lease_key, - tcon->cfid.fid->lease_key, + tcon->cfid->fid.lease_key, SMB2_LEASE_KEY_SIZE)) { - tcon->cfid.time = 0; - INIT_WORK(&tcon->cfid.lease_break, + tcon->cfid->time = 0; + INIT_WORK(&tcon->cfid->lease_break, smb2_cached_lease_break); queue_work(cifsiod_wq, - &tcon->cfid.lease_break); + &tcon->cfid->lease_break); return true; } return false; } + +struct cached_fid *init_cached_dir(void) +{ + struct cached_fid *cfid; + + cfid = kzalloc(sizeof(*cfid), GFP_KERNEL); + if (!cfid) + return NULL; + INIT_LIST_HEAD(&cfid->dirents.entries); + mutex_init(&cfid->dirents.de_mutex); + mutex_init(&cfid->fid_mutex); + return cfid; +} + +void free_cached_dir(struct cifs_tcon *tcon) +{ + kfree(tcon->cfid); +} diff --git a/fs/cifs/cached_dir.h b/fs/cifs/cached_dir.h index 51c6b968f8b67..89c0343d7e266 100644 --- a/fs/cifs/cached_dir.h +++ b/fs/cifs/cached_dir.h @@ -9,6 +9,44 @@ #define _CACHED_DIR_H +struct cached_dirent { + struct list_head entry; + char *name; + int namelen; + loff_t pos; + + struct cifs_fattr fattr; +}; + +struct cached_dirents { + bool is_valid:1; + bool is_failed:1; + struct dir_context *ctx; /* + * Only used to make sure we only take entries + * from a single context. Never dereferenced. + */ + struct mutex de_mutex; + int pos; /* Expected ctx->pos */ + struct list_head entries; +}; + +struct cached_fid { + bool is_valid:1; /* Do we have a useable root fid */ + bool file_all_info_is_valid:1; + bool has_lease:1; + unsigned long time; /* jiffies of when lease was taken */ + struct kref refcount; + struct cifs_fid fid; + struct mutex fid_mutex; + struct cifs_tcon *tcon; + struct dentry *dentry; + struct work_struct lease_break; + struct smb2_file_all_info file_all_info; + struct cached_dirents dirents; +}; + +extern struct cached_fid *init_cached_dir(void); +extern void free_cached_dir(struct cifs_tcon *tcon); 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/cifs/cifsglob.h b/fs/cifs/cifsglob.h index 8b82f13d11e09..bc0ee2d4b47b2 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -1128,42 +1128,6 @@ struct cifs_fattr { u32 cf_cifstag; }; -struct cached_dirent { - struct list_head entry; - char *name; - int namelen; - loff_t pos; - - struct cifs_fattr fattr; -}; - -struct cached_dirents { - bool is_valid:1; - bool is_failed:1; - struct dir_context *ctx; /* - * Only used to make sure we only take entries - * from a single context. Never dereferenced. - */ - struct mutex de_mutex; - int pos; /* Expected ctx->pos */ - struct list_head entries; -}; - -struct cached_fid { - bool is_valid:1; /* Do we have a useable root fid */ - bool file_all_info_is_valid:1; - bool has_lease:1; - unsigned long time; /* jiffies of when lease was taken */ - struct kref refcount; - struct cifs_fid *fid; - struct mutex fid_mutex; - struct cifs_tcon *tcon; - struct dentry *dentry; - struct work_struct lease_break; - struct smb2_file_all_info file_all_info; - struct cached_dirents dirents; -}; - /* * there is one of these for each connection to a resource on a particular * session @@ -1257,7 +1221,7 @@ struct cifs_tcon { struct fscache_volume *fscache; /* cookie for share */ #endif struct list_head pending_opens; /* list of incomplete opens */ - struct cached_fid cfid; /* Cached root fid */ + struct cached_fid *cfid; /* Cached root fid */ /* BB add field for back pointer to sb struct(s)? */ #ifdef CONFIG_CIFS_DFS_UPCALL struct list_head ulist; /* cache update list */ diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c index ea0405cfaee31..3f4f3d1a378f1 100644 --- a/fs/cifs/misc.c +++ b/fs/cifs/misc.c @@ -23,6 +23,7 @@ #include "dns_resolve.h" #endif #include "fs_context.h" +#include "cached_dir.h" extern mempool_t *cifs_sm_req_poolp; extern mempool_t *cifs_req_poolp; @@ -116,13 +117,11 @@ tconInfoAlloc(void) ret_buf = kzalloc(sizeof(*ret_buf), GFP_KERNEL); if (!ret_buf) return NULL; - ret_buf->cfid.fid = kzalloc(sizeof(*ret_buf->cfid.fid), GFP_KERNEL); - if (!ret_buf->cfid.fid) { + ret_buf->cfid = init_cached_dir(); + if (!ret_buf->cfid) { kfree(ret_buf); return NULL; } - INIT_LIST_HEAD(&ret_buf->cfid.dirents.entries); - mutex_init(&ret_buf->cfid.dirents.de_mutex); atomic_inc(&tconInfoAllocCount); ret_buf->status = TID_NEW; @@ -131,7 +130,6 @@ tconInfoAlloc(void) INIT_LIST_HEAD(&ret_buf->openFileList); INIT_LIST_HEAD(&ret_buf->tcon_list); spin_lock_init(&ret_buf->open_file_lock); - mutex_init(&ret_buf->cfid.fid_mutex); spin_lock_init(&ret_buf->stat_lock); atomic_set(&ret_buf->num_local_opens, 0); atomic_set(&ret_buf->num_remote_opens, 0); @@ -140,17 +138,17 @@ tconInfoAlloc(void) } void -tconInfoFree(struct cifs_tcon *buf_to_free) +tconInfoFree(struct cifs_tcon *tcon) { - if (buf_to_free == NULL) { + if (tcon == NULL) { cifs_dbg(FYI, "Null buffer passed to tconInfoFree\n"); return; } + free_cached_dir(tcon); atomic_dec(&tconInfoAllocCount); - kfree(buf_to_free->nativeFileSystem); - kfree_sensitive(buf_to_free->password); - kfree(buf_to_free->cfid.fid); - kfree(buf_to_free); + kfree(tcon->nativeFileSystem); + kfree_sensitive(tcon->password); + kfree(tcon); } struct smb_hdr * diff --git a/fs/cifs/smb2inode.c b/fs/cifs/smb2inode.c index 09f01f70e0204..9696184a09e39 100644 --- a/fs/cifs/smb2inode.c +++ b/fs/cifs/smb2inode.c @@ -524,8 +524,8 @@ smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon, &cfid->file_all_info); } else { rc = SMB2_query_info(xid, tcon, - cfid->fid->persistent_fid, - cfid->fid->volatile_fid, smb2_data); + cfid->fid.persistent_fid, + cfid->fid.volatile_fid, smb2_data); if (!rc) move_smb2_info_to_cifs(data, smb2_data); } diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index 8cb1eed1dd63c..6507761a8040c 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -722,7 +722,7 @@ smb3_qfs_tcon(const unsigned int xid, struct cifs_tcon *tcon, rc = open_cached_dir(xid, tcon, "", cifs_sb, &cfid); if (rc == 0) - memcpy(&fid, cfid->fid, sizeof(struct cifs_fid)); + memcpy(&fid, &cfid->fid, sizeof(struct cifs_fid)); else rc = SMB2_open(xid, &oparms, &srch_path, &oplock, NULL, NULL, NULL, NULL); @@ -784,7 +784,7 @@ smb2_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_open_parms oparms; struct cifs_fid fid; - if ((*full_path == 0) && tcon->cfid.is_valid) + if ((*full_path == 0) && tcon->cfid->is_valid) return 0; utf16_path = cifs_convert_path_to_utf16(full_path, cifs_sb); @@ -2457,8 +2457,8 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon, if (cfid) { rc = SMB2_query_info_init(tcon, server, &rqst[1], - cfid->fid->persistent_fid, - cfid->fid->volatile_fid, + cfid->fid.persistent_fid, + cfid->fid.volatile_fid, class, type, 0, output_len, 0, NULL); From 7eb59a98701d3113671b513593bb489cc76f58d2 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Thu, 11 Aug 2022 19:51:18 -0500 Subject: [PATCH 8/8] cifs: Do not access tcon->cfids->cfid directly from is_path_accessible cfids will soon keep a list of cached fids so we should not access this directly from outside of cached_dir.c Reviewed-by: Paulo Alcantara (SUSE) Signed-off-by: Ronnie Sahlberg Signed-off-by: Steve French --- fs/cifs/cached_dir.c | 10 ++++++---- fs/cifs/cached_dir.h | 2 +- fs/cifs/readdir.c | 4 ++-- fs/cifs/smb2inode.c | 2 +- fs/cifs/smb2ops.c | 19 +++++++++++++++---- 5 files changed, 25 insertions(+), 12 deletions(-) diff --git a/fs/cifs/cached_dir.c b/fs/cifs/cached_dir.c index 78e8deb82a0a1..b401339f6e738 100644 --- a/fs/cifs/cached_dir.c +++ b/fs/cifs/cached_dir.c @@ -16,9 +16,9 @@ * If error then *cfid is not initialized. */ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, - const char *path, - struct cifs_sb_info *cifs_sb, - struct cached_fid **ret_cfid) + const char *path, + struct cifs_sb_info *cifs_sb, + bool lookup_only, struct cached_fid **ret_cfid) { struct cifs_ses *ses; struct TCP_Server_Info *server; @@ -68,9 +68,11 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, * cifs_mark_open_files_invalid() which takes the lock again * thus causing a deadlock */ - mutex_unlock(&cfid->fid_mutex); + if (lookup_only) + return -ENOENT; + if (smb3_encryption_required(tcon)) flags |= CIFS_TRANSFORM_REQ; diff --git a/fs/cifs/cached_dir.h b/fs/cifs/cached_dir.h index 89c0343d7e266..bd262dc8b179a 100644 --- a/fs/cifs/cached_dir.h +++ b/fs/cifs/cached_dir.h @@ -50,7 +50,7 @@ extern void free_cached_dir(struct cifs_tcon *tcon); extern int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *path, struct cifs_sb_info *cifs_sb, - struct cached_fid **cfid); + bool lookup_only, struct cached_fid **cfid); extern int open_cached_dir_by_dentry(struct cifs_tcon *tcon, struct dentry *dentry, struct cached_fid **cfid); diff --git a/fs/cifs/readdir.c b/fs/cifs/readdir.c index a06072ae6c7e2..2eece8a07c112 100644 --- a/fs/cifs/readdir.c +++ b/fs/cifs/readdir.c @@ -1072,7 +1072,7 @@ 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); + rc = open_cached_dir(xid, tcon, full_path, cifs_sb, false, &cfid); cifs_put_tlink(tlink); if (rc) goto cache_not_found; @@ -1143,7 +1143,7 @@ int cifs_readdir(struct file *file, struct dir_context *ctx) 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); + open_cached_dir(xid, tcon, full_path, cifs_sb, false, &cfid); if (rc) { cifs_dbg(FYI, "fce error %d\n", rc); goto rddir2_exit; diff --git a/fs/cifs/smb2inode.c b/fs/cifs/smb2inode.c index 9696184a09e39..b83f59051b26f 100644 --- a/fs/cifs/smb2inode.c +++ b/fs/cifs/smb2inode.c @@ -516,7 +516,7 @@ smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon, if (strcmp(full_path, "")) rc = -ENOENT; else - rc = open_cached_dir(xid, tcon, full_path, cifs_sb, &cfid); + rc = open_cached_dir(xid, tcon, full_path, cifs_sb, false, &cfid); /* If it is a root and its handle is cached then use it */ if (!rc) { if (cfid->file_all_info_is_valid) { diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index 6507761a8040c..f406af5968877 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -720,7 +720,7 @@ smb3_qfs_tcon(const unsigned int xid, struct cifs_tcon *tcon, oparms.fid = &fid; oparms.reconnect = false; - rc = open_cached_dir(xid, tcon, "", cifs_sb, &cfid); + rc = open_cached_dir(xid, tcon, "", cifs_sb, false, &cfid); if (rc == 0) memcpy(&fid, &cfid->fid, sizeof(struct cifs_fid)); else @@ -783,9 +783,16 @@ smb2_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon, __u8 oplock = SMB2_OPLOCK_LEVEL_NONE; struct cifs_open_parms oparms; struct cifs_fid fid; + struct cached_fid *cfid; - if ((*full_path == 0) && tcon->cfid->is_valid) - return 0; + rc = open_cached_dir(xid, tcon, full_path, cifs_sb, true, &cfid); + if (!rc) { + if (cfid->is_valid) { + close_cached_dir(cfid); + return 0; + } + close_cached_dir(cfid); + } utf16_path = cifs_convert_path_to_utf16(full_path, cifs_sb); if (!utf16_path) @@ -2430,8 +2437,12 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon, resp_buftype[0] = resp_buftype[1] = resp_buftype[2] = CIFS_NO_BUFFER; memset(rsp_iov, 0, sizeof(rsp_iov)); + /* + * We can only call this for things we know are directories. + */ if (!strcmp(path, "")) - open_cached_dir(xid, tcon, path, cifs_sb, &cfid); /* cfid null if open dir failed */ + open_cached_dir(xid, tcon, path, cifs_sb, false, + &cfid); /* cfid null if open dir failed */ memset(&open_iov, 0, sizeof(open_iov)); rqst[0].rq_iov = open_iov;