From 47178c7722ac528ea08aa82c3ef9ffa178962d7a Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Fri, 4 Mar 2022 10:31:49 +1000 Subject: [PATCH 1/5] cifs: fix handlecache and multiuser In multiuser each individual user has their own tcon structure for the share and thus their own handle for a cached directory. When we umount such a share we much make sure to release the pinned down dentry for each such tcon and not just the master tcon. Otherwise we will get nasty warnings on umount that dentries are still in use: [ 3459.590047] BUG: Dentry 00000000115c6f41{i=12000000019d95,n=/} still in use\ (2) [unmount of cifs cifs] ... [ 3459.590492] Call Trace: [ 3459.590500] d_walk+0x61/0x2a0 [ 3459.590518] ? shrink_lock_dentry.part.0+0xe0/0xe0 [ 3459.590526] shrink_dcache_for_umount+0x49/0x110 [ 3459.590535] generic_shutdown_super+0x1a/0x110 [ 3459.590542] kill_anon_super+0x14/0x30 [ 3459.590549] cifs_kill_sb+0xf5/0x104 [cifs] [ 3459.590773] deactivate_locked_super+0x36/0xa0 [ 3459.590782] cleanup_mnt+0x131/0x190 [ 3459.590789] task_work_run+0x5c/0x90 [ 3459.590798] exit_to_user_mode_loop+0x151/0x160 [ 3459.590809] exit_to_user_mode_prepare+0x83/0xd0 [ 3459.590818] syscall_exit_to_user_mode+0x12/0x30 [ 3459.590828] do_syscall_64+0x48/0x90 [ 3459.590833] entry_SYSCALL_64_after_hwframe+0x44/0xae Signed-off-by: Ronnie Sahlberg Acked-by: Paulo Alcantara (SUSE) Cc: stable@vger.kernel.org Signed-off-by: Steve French --- fs/cifs/cifsfs.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index 082c21478686..a61bb5d683c6 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -254,6 +254,9 @@ 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 @@ -263,17 +266,21 @@ static void cifs_kill_sb(struct super_block *sb) dput(cifs_sb->root); cifs_sb->root = NULL; } - tcon = cifs_sb_master_tcon(cifs_sb); - if (tcon) { + spin_lock(&cifs_sb->tlink_tree_lock); + node = rb_first(root); + while (node != NULL) { + tlink = rb_entry(node, struct tcon_link, tl_rbnode); + tcon = tlink_tcon(tlink); cfid = &tcon->crfid; mutex_lock(&cfid->fid_mutex); if (cfid->dentry) { - dput(cfid->dentry); cfid->dentry = NULL; } mutex_unlock(&cfid->fid_mutex); + node = rb_next(node); } + spin_unlock(&cifs_sb->tlink_tree_lock); kill_anon_super(sb); cifs_umount(cifs_sb); From 84330d41efb12bc227899e54dbdbe7d9590cb2b7 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Wed, 23 Feb 2022 11:14:16 +1000 Subject: [PATCH 2/5] cifs: truncate the inode and mapping when we simulate fcollapse RHBZ:1997367 When we collapse a range in smb3_collapse_range() we must make sure we update the inode size and pagecache accordingly. If not, both inode size and pagecahce may be stale until it is refreshed. This can be demonstrated for the inode size by running : xfs_io -i -f -c "truncate 320k" -c "fcollapse 64k 128k" -c "fiemap -v" \ /mnt/testfile where we can see the result of stale data in the fiemap output. The third line of the output is wrong, all this data should be truncated. EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS 0: [0..127]: hole 128 1: [128..383]: 128..383 256 0x1 2: [384..639]: hole 256 And the correct output, when the inode size has been updated correctly should look like this: EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS 0: [0..127]: hole 128 1: [128..383]: 128..383 256 0x1 Reported-by: Xiaoli Feng Reported-by: kernel test robot Cc: stable@vger.kernel.org Signed-off-by: Ronnie Sahlberg Signed-off-by: Steve French --- fs/cifs/smb2ops.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index af5d0830bc8a..891b11576e55 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -25,6 +25,7 @@ #include "smb2glob.h" #include "cifs_ioctl.h" #include "smbdirect.h" +#include "fscache.h" #include "fs_context.h" /* Change credits for different ops and return the total number of credits */ @@ -3887,29 +3888,38 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon, { int rc; unsigned int xid; + struct inode *inode; struct cifsFileInfo *cfile = file->private_data; + struct cifsInodeInfo *cifsi; __le64 eof; xid = get_xid(); - if (off >= i_size_read(file->f_inode) || - off + len >= i_size_read(file->f_inode)) { + inode = d_inode(cfile->dentry); + cifsi = CIFS_I(inode); + + if (off >= i_size_read(inode) || + off + len >= i_size_read(inode)) { rc = -EINVAL; goto out; } rc = smb2_copychunk_range(xid, cfile, cfile, off + len, - i_size_read(file->f_inode) - off - len, off); + i_size_read(inode) - off - len, off); if (rc < 0) goto out; - eof = cpu_to_le64(i_size_read(file->f_inode) - len); + eof = cpu_to_le64(i_size_read(inode) - len); rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, cfile->fid.volatile_fid, cfile->pid, &eof); if (rc < 0) goto out; rc = 0; + + cifsi->server_eof = i_size_read(inode) - len; + truncate_setsize(inode, cifsi->server_eof); + fscache_resize_cookie(cifs_inode_cookie(inode), cifsi->server_eof); out: free_xid(xid); return rc; From 06a466565d54a1a42168f9033a062a3f5c40e73b Mon Sep 17 00:00:00 2001 From: Rohith Surabattula Date: Mon, 7 Mar 2022 18:37:22 +0000 Subject: [PATCH 3/5] Adjust cifssb maximum read size When session gets reconnected during mount then read size in super block fs context gets set to zero and after negotiate, rsize is not modified which results in incorrect read with requested bytes as zero. Fixes intermittent failure of xfstest generic/240 Note that stable requires a different version of this patch which will be sent to the stable mailing list. Signed-off-by: Rohith Surabattula Acked-by: Paulo Alcantara (SUSE) Signed-off-by: Steve French --- fs/cifs/cifsfs.c | 3 +++ fs/cifs/file.c | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index a61bb5d683c6..677c02aa8731 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -210,6 +210,9 @@ cifs_read_super(struct super_block *sb) if (rc) goto out_no_root; /* tune readahead according to rsize if readahead size not set on mount */ + if (cifs_sb->ctx->rsize == 0) + cifs_sb->ctx->rsize = + tcon->ses->server->ops->negotiate_rsize(tcon, cifs_sb->ctx); if (cifs_sb->ctx->rasize) sb->s_bdi->ra_pages = cifs_sb->ctx->rasize / PAGE_SIZE; else diff --git a/fs/cifs/file.c b/fs/cifs/file.c index e7af802dcfa6..a2723f7cb5e9 100644 --- a/fs/cifs/file.c +++ b/fs/cifs/file.c @@ -3740,6 +3740,11 @@ cifs_send_async_read(loff_t offset, size_t len, struct cifsFileInfo *open_file, break; } + if (cifs_sb->ctx->rsize == 0) + cifs_sb->ctx->rsize = + server->ops->negotiate_rsize(tlink_tcon(open_file->tlink), + cifs_sb->ctx); + rc = server->ops->wait_mtu_credits(server, cifs_sb->ctx->rsize, &rsize, credits); if (rc) @@ -4474,6 +4479,11 @@ static void cifs_readahead(struct readahead_control *ractl) } } + if (cifs_sb->ctx->rsize == 0) + cifs_sb->ctx->rsize = + server->ops->negotiate_rsize(tlink_tcon(open_file->tlink), + cifs_sb->ctx); + rc = server->ops->wait_mtu_credits(server, cifs_sb->ctx->rsize, &rsize, credits); if (rc) From 9a14b65d590105d393b63f5320e1594edda7c672 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Tue, 15 Mar 2022 13:44:04 +1000 Subject: [PATCH 4/5] cifs: we do not need a spinlock around the tree access during umount Remove the spinlock around the tree traversal as we are calling possibly sleeping functions. We do not need a spinlock here as there will be no modifications to this tree at this point. This prevents warnings like this to occur in dmesg: [ 653.774996] BUG: sleeping function called from invalid context at kernel/loc\ king/mutex.c:280 [ 653.775088] in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 1827, nam\ e: umount [ 653.775152] preempt_count: 1, expected: 0 [ 653.775191] CPU: 0 PID: 1827 Comm: umount Tainted: G W OE 5.17.0\ -rc7-00006-g4eb628dd74df #135 [ 653.775195] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.14.0-\ 1.fc33 04/01/2014 [ 653.775197] Call Trace: [ 653.775199] [ 653.775202] dump_stack_lvl+0x34/0x44 [ 653.775209] __might_resched.cold+0x13f/0x172 [ 653.775213] mutex_lock+0x75/0xf0 [ 653.775217] ? __mutex_lock_slowpath+0x10/0x10 [ 653.775220] ? _raw_write_lock_irq+0xd0/0xd0 [ 653.775224] ? dput+0x6b/0x360 [ 653.775228] cifs_kill_sb+0xff/0x1d0 [cifs] [ 653.775285] deactivate_locked_super+0x85/0x130 [ 653.775289] cleanup_mnt+0x32c/0x4d0 [ 653.775292] ? path_umount+0x228/0x380 [ 653.775296] task_work_run+0xd8/0x180 [ 653.775301] exit_to_user_mode_loop+0x152/0x160 [ 653.775306] exit_to_user_mode_prepare+0x89/0xd0 [ 653.775315] syscall_exit_to_user_mode+0x12/0x30 [ 653.775322] do_syscall_64+0x48/0x90 [ 653.775326] entry_SYSCALL_64_after_hwframe+0x44/0xae Fixes: 187af6e98b44e5d8f25e1d41a92db138eb54416f ("cifs: fix handlecache and multiuser") Reported-by: kernel test robot Cc: stable@vger.kernel.org Signed-off-by: Ronnie Sahlberg Signed-off-by: Steve French --- fs/cifs/cifsfs.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index 677c02aa8731..6e5246122ee2 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -269,7 +269,6 @@ static void cifs_kill_sb(struct super_block *sb) dput(cifs_sb->root); cifs_sb->root = NULL; } - spin_lock(&cifs_sb->tlink_tree_lock); node = rb_first(root); while (node != NULL) { tlink = rb_entry(node, struct tcon_link, tl_rbnode); @@ -283,7 +282,6 @@ static void cifs_kill_sb(struct super_block *sb) mutex_unlock(&cfid->fid_mutex); node = rb_next(node); } - spin_unlock(&cifs_sb->tlink_tree_lock); kill_anon_super(sb); cifs_umount(cifs_sb); From dca65818c80cf06e0f08ba2cf94060a5236e73c2 Mon Sep 17 00:00:00 2001 From: Shyam Prasad N Date: Tue, 15 Feb 2022 13:55:40 +0000 Subject: [PATCH 5/5] cifs: use a different reconnect helper for non-cifsd threads The cifs_demultiplexer_thread should only call cifs_reconnect. If any other thread wants to trigger a reconnect, they can do so by updating the server tcpStatus to CifsNeedReconnect. The last patch attempted to use the same helper function for both types of threads, but that causes other issues with lock dependencies. This patch creates a new helper for non-cifsd threads, that will indicate to cifsd that the server needs reconnect. Fixes: 2a05137a0575 ("cifs: mark sessions for reconnection in helper function") Signed-off-by: Shyam Prasad N Signed-off-by: Steve French --- fs/cifs/cifs_swn.c | 6 +++--- fs/cifs/cifsproto.h | 3 +++ fs/cifs/connect.c | 42 +++++++++++++++++++++++++++++++++++++++++- fs/cifs/dfs_cache.c | 2 +- fs/cifs/smb1ops.c | 2 +- fs/cifs/transport.c | 2 +- 6 files changed, 50 insertions(+), 7 deletions(-) diff --git a/fs/cifs/cifs_swn.c b/fs/cifs/cifs_swn.c index cdce1609c5c2..180c234c2f46 100644 --- a/fs/cifs/cifs_swn.c +++ b/fs/cifs/cifs_swn.c @@ -396,11 +396,11 @@ static int cifs_swn_resource_state_changed(struct cifs_swn_reg *swnreg, const ch switch (state) { case CIFS_SWN_RESOURCE_STATE_UNAVAILABLE: cifs_dbg(FYI, "%s: resource name '%s' become unavailable\n", __func__, name); - cifs_mark_tcp_ses_conns_for_reconnect(swnreg->tcon->ses->server, true); + cifs_signal_cifsd_for_reconnect(swnreg->tcon->ses->server, true); break; case CIFS_SWN_RESOURCE_STATE_AVAILABLE: cifs_dbg(FYI, "%s: resource name '%s' become available\n", __func__, name); - cifs_mark_tcp_ses_conns_for_reconnect(swnreg->tcon->ses->server, true); + cifs_signal_cifsd_for_reconnect(swnreg->tcon->ses->server, true); break; case CIFS_SWN_RESOURCE_STATE_UNKNOWN: cifs_dbg(FYI, "%s: resource name '%s' changed to unknown state\n", __func__, name); @@ -498,7 +498,7 @@ static int cifs_swn_reconnect(struct cifs_tcon *tcon, struct sockaddr_storage *a goto unlock; } - cifs_mark_tcp_ses_conns_for_reconnect(tcon->ses->server, false); + cifs_signal_cifsd_for_reconnect(tcon->ses->server, false); unlock: mutex_unlock(&tcon->ses->server->srv_mutex); diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index d3701295402d..0df3b24a0bf4 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -132,6 +132,9 @@ extern int SendReceiveBlockingLock(const unsigned int xid, struct smb_hdr *out_buf, int *bytes_returned); void +cifs_signal_cifsd_for_reconnect(struct TCP_Server_Info *server, + bool all_channels); +void cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server, bool mark_smb_session); extern int cifs_reconnect(struct TCP_Server_Info *server, diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index d3020abfe404..9964c3634322 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -162,11 +162,51 @@ static void cifs_resolve_server(struct work_struct *work) mutex_unlock(&server->srv_mutex); } +/* + * Update the tcpStatus for the server. + * This is used to signal the cifsd thread to call cifs_reconnect + * ONLY cifsd thread should call cifs_reconnect. For any other + * thread, use this function + * + * @server: the tcp ses for which reconnect is needed + * @all_channels: if this needs to be done for all channels + */ +void +cifs_signal_cifsd_for_reconnect(struct TCP_Server_Info *server, + bool all_channels) +{ + struct TCP_Server_Info *pserver; + struct cifs_ses *ses; + int i; + + /* If server is a channel, select the primary channel */ + pserver = CIFS_SERVER_IS_CHAN(server) ? server->primary_server : server; + + spin_lock(&cifs_tcp_ses_lock); + if (!all_channels) { + pserver->tcpStatus = CifsNeedReconnect; + spin_unlock(&cifs_tcp_ses_lock); + return; + } + + list_for_each_entry(ses, &pserver->smb_ses_list, smb_ses_list) { + spin_lock(&ses->chan_lock); + for (i = 0; i < ses->chan_count; i++) + ses->chans[i].server->tcpStatus = CifsNeedReconnect; + spin_unlock(&ses->chan_lock); + } + spin_unlock(&cifs_tcp_ses_lock); +} + /* * Mark all sessions and tcons for reconnect. + * IMPORTANT: make sure that this gets called only from + * cifsd thread. For any other thread, use + * cifs_signal_cifsd_for_reconnect * + * @server: the tcp ses for which reconnect is needed * @server needs to be previously set to CifsNeedReconnect. - * + * @mark_smb_session: whether even sessions need to be marked */ void cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server, diff --git a/fs/cifs/dfs_cache.c b/fs/cifs/dfs_cache.c index 831f42458bf6..30e040da4f09 100644 --- a/fs/cifs/dfs_cache.c +++ b/fs/cifs/dfs_cache.c @@ -1355,7 +1355,7 @@ static void mark_for_reconnect_if_needed(struct cifs_tcon *tcon, struct dfs_cach } cifs_dbg(FYI, "%s: no cached or matched targets. mark dfs share for reconnect.\n", __func__); - cifs_mark_tcp_ses_conns_for_reconnect(tcon->ses->server, true); + cifs_signal_cifsd_for_reconnect(tcon->ses->server, true); } /* Refresh dfs referral of tcon and mark it for reconnect if needed */ diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c index b2fb7bd11936..c71c9a44bef4 100644 --- a/fs/cifs/smb1ops.c +++ b/fs/cifs/smb1ops.c @@ -228,7 +228,7 @@ cifs_get_next_mid(struct TCP_Server_Info *server) spin_unlock(&GlobalMid_Lock); if (reconnect) { - cifs_mark_tcp_ses_conns_for_reconnect(server, false); + cifs_signal_cifsd_for_reconnect(server, false); } return mid; diff --git a/fs/cifs/transport.c b/fs/cifs/transport.c index a4c3e027cca2..eeb1a699bd6f 100644 --- a/fs/cifs/transport.c +++ b/fs/cifs/transport.c @@ -430,7 +430,7 @@ unmask: * be taken as the remainder of this one. We need to kill the * socket so the server throws away the partial SMB */ - cifs_mark_tcp_ses_conns_for_reconnect(server, false); + cifs_signal_cifsd_for_reconnect(server, false); trace_smb3_partial_send_reconnect(server->CurrentMid, server->conn_id, server->hostname); }