![Rabin Vincent](/assets/img/avatar_default.png)
When the final cifsFileInfo_put() is called from cifsiod and an oplock break work is queued, lockdep complains loudly: ============================================= [ INFO: possible recursive locking detected ] 4.11.0+ #21 Not tainted --------------------------------------------- kworker/0:2/78 is trying to acquire lock: ("cifsiod"){++++.+}, at: flush_work+0x215/0x350 but task is already holding lock: ("cifsiod"){++++.+}, at: process_one_work+0x255/0x8e0 other info that might help us debug this: Possible unsafe locking scenario: CPU0 ---- lock("cifsiod"); lock("cifsiod"); *** DEADLOCK *** May be due to missing lock nesting notation 2 locks held by kworker/0:2/78: #0: ("cifsiod"){++++.+}, at: process_one_work+0x255/0x8e0 #1: ((&wdata->work)){+.+...}, at: process_one_work+0x255/0x8e0 stack backtrace: CPU: 0 PID: 78 Comm: kworker/0:2 Not tainted 4.11.0+ #21 Workqueue: cifsiod cifs_writev_complete Call Trace: dump_stack+0x85/0xc2 __lock_acquire+0x17dd/0x2260 ? match_held_lock+0x20/0x2b0 ? trace_hardirqs_off_caller+0x86/0x130 ? mark_lock+0xa6/0x920 lock_acquire+0xcc/0x260 ? lock_acquire+0xcc/0x260 ? flush_work+0x215/0x350 flush_work+0x236/0x350 ? flush_work+0x215/0x350 ? destroy_worker+0x170/0x170 __cancel_work_timer+0x17d/0x210 ? ___preempt_schedule+0x16/0x18 cancel_work_sync+0x10/0x20 cifsFileInfo_put+0x338/0x7f0 cifs_writedata_release+0x2a/0x40 ? cifs_writedata_release+0x2a/0x40 cifs_writev_complete+0x29d/0x850 ? preempt_count_sub+0x18/0xd0 process_one_work+0x304/0x8e0 worker_thread+0x9b/0x6a0 kthread+0x1b2/0x200 ? process_one_work+0x8e0/0x8e0 ? kthread_create_on_node+0x40/0x40 ret_from_fork+0x31/0x40 This is a real warning. Since the oplock is queued on the same workqueue this can deadlock if there is only one worker thread active for the workqueue (which will be the case during memory pressure when the rescuer thread is handling it). Furthermore, there is at least one other kind of hang possible due to the oplock break handling if there is only worker. (This can be reproduced without introducing memory pressure by having passing 1 for the max_active parameter of cifsiod.) cifs_oplock_break() can wait indefintely in the filemap_fdatawait() while the cifs_writev_complete() work is blocked: sysrq: SysRq : Show Blocked State task PC stack pid father kworker/0:1 D 0 16 2 0x00000000 Workqueue: cifsiod cifs_oplock_break Call Trace: __schedule+0x562/0xf40 ? mark_held_locks+0x4a/0xb0 schedule+0x57/0xe0 io_schedule+0x21/0x50 wait_on_page_bit+0x143/0x190 ? add_to_page_cache_lru+0x150/0x150 __filemap_fdatawait_range+0x134/0x190 ? do_writepages+0x51/0x70 filemap_fdatawait_range+0x14/0x30 filemap_fdatawait+0x3b/0x40 cifs_oplock_break+0x651/0x710 ? preempt_count_sub+0x18/0xd0 process_one_work+0x304/0x8e0 worker_thread+0x9b/0x6a0 kthread+0x1b2/0x200 ? process_one_work+0x8e0/0x8e0 ? kthread_create_on_node+0x40/0x40 ret_from_fork+0x31/0x40 dd D 0 683 171 0x00000000 Call Trace: __schedule+0x562/0xf40 ? mark_held_locks+0x29/0xb0 schedule+0x57/0xe0 io_schedule+0x21/0x50 wait_on_page_bit+0x143/0x190 ? add_to_page_cache_lru+0x150/0x150 __filemap_fdatawait_range+0x134/0x190 ? do_writepages+0x51/0x70 filemap_fdatawait_range+0x14/0x30 filemap_fdatawait+0x3b/0x40 filemap_write_and_wait+0x4e/0x70 cifs_flush+0x6a/0xb0 filp_close+0x52/0xa0 __close_fd+0xdc/0x150 SyS_close+0x33/0x60 entry_SYSCALL_64_fastpath+0x1f/0xbe Showing all locks held in the system: 2 locks held by kworker/0:1/16: #0: ("cifsiod"){.+.+.+}, at: process_one_work+0x255/0x8e0 #1: ((&cfile->oplock_break)){+.+.+.}, at: process_one_work+0x255/0x8e0 Showing busy workqueues and worker pools: workqueue cifsiod: flags=0xc pwq 0: cpus=0 node=0 flags=0x0 nice=0 active=1/1 in-flight: 16:cifs_oplock_break delayed: cifs_writev_complete, cifs_echo_request pool 0: cpus=0 node=0 flags=0x0 nice=0 hung=0s workers=3 idle: 750 3 Fix these problems by creating a a new workqueue (with a rescuer) for the oplock break work. Signed-off-by: Rabin Vincent <rabinv@axis.com> Signed-off-by: Steve French <smfrench@gmail.com> CC: Stable <stable@vger.kernel.org>
864 lines
23 KiB
C
864 lines
23 KiB
C
/*
|
|
* fs/cifs/misc.c
|
|
*
|
|
* Copyright (C) International Business Machines Corp., 2002,2008
|
|
* Author(s): Steve French (sfrench@us.ibm.com)
|
|
*
|
|
* This library is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published
|
|
* by the Free Software Foundation; either version 2.1 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
|
* the GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/mempool.h>
|
|
#include <linux/vmalloc.h>
|
|
#include "cifspdu.h"
|
|
#include "cifsglob.h"
|
|
#include "cifsproto.h"
|
|
#include "cifs_debug.h"
|
|
#include "smberr.h"
|
|
#include "nterr.h"
|
|
#include "cifs_unicode.h"
|
|
#ifdef CONFIG_CIFS_SMB2
|
|
#include "smb2pdu.h"
|
|
#endif
|
|
|
|
extern mempool_t *cifs_sm_req_poolp;
|
|
extern mempool_t *cifs_req_poolp;
|
|
|
|
/* The xid serves as a useful identifier for each incoming vfs request,
|
|
in a similar way to the mid which is useful to track each sent smb,
|
|
and CurrentXid can also provide a running counter (although it
|
|
will eventually wrap past zero) of the total vfs operations handled
|
|
since the cifs fs was mounted */
|
|
|
|
unsigned int
|
|
_get_xid(void)
|
|
{
|
|
unsigned int xid;
|
|
|
|
spin_lock(&GlobalMid_Lock);
|
|
GlobalTotalActiveXid++;
|
|
|
|
/* keep high water mark for number of simultaneous ops in filesystem */
|
|
if (GlobalTotalActiveXid > GlobalMaxActiveXid)
|
|
GlobalMaxActiveXid = GlobalTotalActiveXid;
|
|
if (GlobalTotalActiveXid > 65000)
|
|
cifs_dbg(FYI, "warning: more than 65000 requests active\n");
|
|
xid = GlobalCurrentXid++;
|
|
spin_unlock(&GlobalMid_Lock);
|
|
return xid;
|
|
}
|
|
|
|
void
|
|
_free_xid(unsigned int xid)
|
|
{
|
|
spin_lock(&GlobalMid_Lock);
|
|
/* if (GlobalTotalActiveXid == 0)
|
|
BUG(); */
|
|
GlobalTotalActiveXid--;
|
|
spin_unlock(&GlobalMid_Lock);
|
|
}
|
|
|
|
struct cifs_ses *
|
|
sesInfoAlloc(void)
|
|
{
|
|
struct cifs_ses *ret_buf;
|
|
|
|
ret_buf = kzalloc(sizeof(struct cifs_ses), GFP_KERNEL);
|
|
if (ret_buf) {
|
|
atomic_inc(&sesInfoAllocCount);
|
|
ret_buf->status = CifsNew;
|
|
++ret_buf->ses_count;
|
|
INIT_LIST_HEAD(&ret_buf->smb_ses_list);
|
|
INIT_LIST_HEAD(&ret_buf->tcon_list);
|
|
mutex_init(&ret_buf->session_mutex);
|
|
}
|
|
return ret_buf;
|
|
}
|
|
|
|
void
|
|
sesInfoFree(struct cifs_ses *buf_to_free)
|
|
{
|
|
if (buf_to_free == NULL) {
|
|
cifs_dbg(FYI, "Null buffer passed to sesInfoFree\n");
|
|
return;
|
|
}
|
|
|
|
atomic_dec(&sesInfoAllocCount);
|
|
kfree(buf_to_free->serverOS);
|
|
kfree(buf_to_free->serverDomain);
|
|
kfree(buf_to_free->serverNOS);
|
|
if (buf_to_free->password) {
|
|
memset(buf_to_free->password, 0, strlen(buf_to_free->password));
|
|
kfree(buf_to_free->password);
|
|
}
|
|
kfree(buf_to_free->user_name);
|
|
kfree(buf_to_free->domainName);
|
|
kfree(buf_to_free->auth_key.response);
|
|
kfree(buf_to_free);
|
|
}
|
|
|
|
struct cifs_tcon *
|
|
tconInfoAlloc(void)
|
|
{
|
|
struct cifs_tcon *ret_buf;
|
|
ret_buf = kzalloc(sizeof(struct cifs_tcon), GFP_KERNEL);
|
|
if (ret_buf) {
|
|
atomic_inc(&tconInfoAllocCount);
|
|
ret_buf->tidStatus = CifsNew;
|
|
++ret_buf->tc_count;
|
|
INIT_LIST_HEAD(&ret_buf->openFileList);
|
|
INIT_LIST_HEAD(&ret_buf->tcon_list);
|
|
spin_lock_init(&ret_buf->open_file_lock);
|
|
#ifdef CONFIG_CIFS_STATS
|
|
spin_lock_init(&ret_buf->stat_lock);
|
|
#endif
|
|
}
|
|
return ret_buf;
|
|
}
|
|
|
|
void
|
|
tconInfoFree(struct cifs_tcon *buf_to_free)
|
|
{
|
|
if (buf_to_free == NULL) {
|
|
cifs_dbg(FYI, "Null buffer passed to tconInfoFree\n");
|
|
return;
|
|
}
|
|
atomic_dec(&tconInfoAllocCount);
|
|
kfree(buf_to_free->nativeFileSystem);
|
|
if (buf_to_free->password) {
|
|
memset(buf_to_free->password, 0, strlen(buf_to_free->password));
|
|
kfree(buf_to_free->password);
|
|
}
|
|
kfree(buf_to_free);
|
|
}
|
|
|
|
struct smb_hdr *
|
|
cifs_buf_get(void)
|
|
{
|
|
struct smb_hdr *ret_buf = NULL;
|
|
size_t buf_size = sizeof(struct smb_hdr);
|
|
|
|
#ifdef CONFIG_CIFS_SMB2
|
|
/*
|
|
* SMB2 header is bigger than CIFS one - no problems to clean some
|
|
* more bytes for CIFS.
|
|
*/
|
|
buf_size = sizeof(struct smb2_hdr);
|
|
#endif
|
|
/*
|
|
* We could use negotiated size instead of max_msgsize -
|
|
* but it may be more efficient to always alloc same size
|
|
* albeit slightly larger than necessary and maxbuffersize
|
|
* defaults to this and can not be bigger.
|
|
*/
|
|
ret_buf = mempool_alloc(cifs_req_poolp, GFP_NOFS);
|
|
|
|
/* clear the first few header bytes */
|
|
/* for most paths, more is cleared in header_assemble */
|
|
memset(ret_buf, 0, buf_size + 3);
|
|
atomic_inc(&bufAllocCount);
|
|
#ifdef CONFIG_CIFS_STATS2
|
|
atomic_inc(&totBufAllocCount);
|
|
#endif /* CONFIG_CIFS_STATS2 */
|
|
|
|
return ret_buf;
|
|
}
|
|
|
|
void
|
|
cifs_buf_release(void *buf_to_free)
|
|
{
|
|
if (buf_to_free == NULL) {
|
|
/* cifs_dbg(FYI, "Null buffer passed to cifs_buf_release\n");*/
|
|
return;
|
|
}
|
|
mempool_free(buf_to_free, cifs_req_poolp);
|
|
|
|
atomic_dec(&bufAllocCount);
|
|
return;
|
|
}
|
|
|
|
struct smb_hdr *
|
|
cifs_small_buf_get(void)
|
|
{
|
|
struct smb_hdr *ret_buf = NULL;
|
|
|
|
/* We could use negotiated size instead of max_msgsize -
|
|
but it may be more efficient to always alloc same size
|
|
albeit slightly larger than necessary and maxbuffersize
|
|
defaults to this and can not be bigger */
|
|
ret_buf = mempool_alloc(cifs_sm_req_poolp, GFP_NOFS);
|
|
/* No need to clear memory here, cleared in header assemble */
|
|
/* memset(ret_buf, 0, sizeof(struct smb_hdr) + 27);*/
|
|
atomic_inc(&smBufAllocCount);
|
|
#ifdef CONFIG_CIFS_STATS2
|
|
atomic_inc(&totSmBufAllocCount);
|
|
#endif /* CONFIG_CIFS_STATS2 */
|
|
|
|
return ret_buf;
|
|
}
|
|
|
|
void
|
|
cifs_small_buf_release(void *buf_to_free)
|
|
{
|
|
|
|
if (buf_to_free == NULL) {
|
|
cifs_dbg(FYI, "Null buffer passed to cifs_small_buf_release\n");
|
|
return;
|
|
}
|
|
mempool_free(buf_to_free, cifs_sm_req_poolp);
|
|
|
|
atomic_dec(&smBufAllocCount);
|
|
return;
|
|
}
|
|
|
|
void
|
|
free_rsp_buf(int resp_buftype, void *rsp)
|
|
{
|
|
if (resp_buftype == CIFS_SMALL_BUFFER)
|
|
cifs_small_buf_release(rsp);
|
|
else if (resp_buftype == CIFS_LARGE_BUFFER)
|
|
cifs_buf_release(rsp);
|
|
}
|
|
|
|
/* NB: MID can not be set if treeCon not passed in, in that
|
|
case it is responsbility of caller to set the mid */
|
|
void
|
|
header_assemble(struct smb_hdr *buffer, char smb_command /* command */ ,
|
|
const struct cifs_tcon *treeCon, int word_count
|
|
/* length of fixed section (word count) in two byte units */)
|
|
{
|
|
char *temp = (char *) buffer;
|
|
|
|
memset(temp, 0, 256); /* bigger than MAX_CIFS_HDR_SIZE */
|
|
|
|
buffer->smb_buf_length = cpu_to_be32(
|
|
(2 * word_count) + sizeof(struct smb_hdr) -
|
|
4 /* RFC 1001 length field does not count */ +
|
|
2 /* for bcc field itself */) ;
|
|
|
|
buffer->Protocol[0] = 0xFF;
|
|
buffer->Protocol[1] = 'S';
|
|
buffer->Protocol[2] = 'M';
|
|
buffer->Protocol[3] = 'B';
|
|
buffer->Command = smb_command;
|
|
buffer->Flags = 0x00; /* case sensitive */
|
|
buffer->Flags2 = SMBFLG2_KNOWS_LONG_NAMES;
|
|
buffer->Pid = cpu_to_le16((__u16)current->tgid);
|
|
buffer->PidHigh = cpu_to_le16((__u16)(current->tgid >> 16));
|
|
if (treeCon) {
|
|
buffer->Tid = treeCon->tid;
|
|
if (treeCon->ses) {
|
|
if (treeCon->ses->capabilities & CAP_UNICODE)
|
|
buffer->Flags2 |= SMBFLG2_UNICODE;
|
|
if (treeCon->ses->capabilities & CAP_STATUS32)
|
|
buffer->Flags2 |= SMBFLG2_ERR_STATUS;
|
|
|
|
/* Uid is not converted */
|
|
buffer->Uid = treeCon->ses->Suid;
|
|
buffer->Mid = get_next_mid(treeCon->ses->server);
|
|
}
|
|
if (treeCon->Flags & SMB_SHARE_IS_IN_DFS)
|
|
buffer->Flags2 |= SMBFLG2_DFS;
|
|
if (treeCon->nocase)
|
|
buffer->Flags |= SMBFLG_CASELESS;
|
|
if ((treeCon->ses) && (treeCon->ses->server))
|
|
if (treeCon->ses->server->sign)
|
|
buffer->Flags2 |= SMBFLG2_SECURITY_SIGNATURE;
|
|
}
|
|
|
|
/* endian conversion of flags is now done just before sending */
|
|
buffer->WordCount = (char) word_count;
|
|
return;
|
|
}
|
|
|
|
static int
|
|
check_smb_hdr(struct smb_hdr *smb)
|
|
{
|
|
/* does it have the right SMB "signature" ? */
|
|
if (*(__le32 *) smb->Protocol != cpu_to_le32(0x424d53ff)) {
|
|
cifs_dbg(VFS, "Bad protocol string signature header 0x%x\n",
|
|
*(unsigned int *)smb->Protocol);
|
|
return 1;
|
|
}
|
|
|
|
/* if it's a response then accept */
|
|
if (smb->Flags & SMBFLG_RESPONSE)
|
|
return 0;
|
|
|
|
/* only one valid case where server sends us request */
|
|
if (smb->Command == SMB_COM_LOCKING_ANDX)
|
|
return 0;
|
|
|
|
cifs_dbg(VFS, "Server sent request, not response. mid=%u\n",
|
|
get_mid(smb));
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
checkSMB(char *buf, unsigned int total_read, struct TCP_Server_Info *server)
|
|
{
|
|
struct smb_hdr *smb = (struct smb_hdr *)buf;
|
|
__u32 rfclen = be32_to_cpu(smb->smb_buf_length);
|
|
__u32 clc_len; /* calculated length */
|
|
cifs_dbg(FYI, "checkSMB Length: 0x%x, smb_buf_length: 0x%x\n",
|
|
total_read, rfclen);
|
|
|
|
/* is this frame too small to even get to a BCC? */
|
|
if (total_read < 2 + sizeof(struct smb_hdr)) {
|
|
if ((total_read >= sizeof(struct smb_hdr) - 1)
|
|
&& (smb->Status.CifsError != 0)) {
|
|
/* it's an error return */
|
|
smb->WordCount = 0;
|
|
/* some error cases do not return wct and bcc */
|
|
return 0;
|
|
} else if ((total_read == sizeof(struct smb_hdr) + 1) &&
|
|
(smb->WordCount == 0)) {
|
|
char *tmp = (char *)smb;
|
|
/* Need to work around a bug in two servers here */
|
|
/* First, check if the part of bcc they sent was zero */
|
|
if (tmp[sizeof(struct smb_hdr)] == 0) {
|
|
/* some servers return only half of bcc
|
|
* on simple responses (wct, bcc both zero)
|
|
* in particular have seen this on
|
|
* ulogoffX and FindClose. This leaves
|
|
* one byte of bcc potentially unitialized
|
|
*/
|
|
/* zero rest of bcc */
|
|
tmp[sizeof(struct smb_hdr)+1] = 0;
|
|
return 0;
|
|
}
|
|
cifs_dbg(VFS, "rcvd invalid byte count (bcc)\n");
|
|
} else {
|
|
cifs_dbg(VFS, "Length less than smb header size\n");
|
|
}
|
|
return -EIO;
|
|
}
|
|
|
|
/* otherwise, there is enough to get to the BCC */
|
|
if (check_smb_hdr(smb))
|
|
return -EIO;
|
|
clc_len = smbCalcSize(smb);
|
|
|
|
if (4 + rfclen != total_read) {
|
|
cifs_dbg(VFS, "Length read does not match RFC1001 length %d\n",
|
|
rfclen);
|
|
return -EIO;
|
|
}
|
|
|
|
if (4 + rfclen != clc_len) {
|
|
__u16 mid = get_mid(smb);
|
|
/* check if bcc wrapped around for large read responses */
|
|
if ((rfclen > 64 * 1024) && (rfclen > clc_len)) {
|
|
/* check if lengths match mod 64K */
|
|
if (((4 + rfclen) & 0xFFFF) == (clc_len & 0xFFFF))
|
|
return 0; /* bcc wrapped */
|
|
}
|
|
cifs_dbg(FYI, "Calculated size %u vs length %u mismatch for mid=%u\n",
|
|
clc_len, 4 + rfclen, mid);
|
|
|
|
if (4 + rfclen < clc_len) {
|
|
cifs_dbg(VFS, "RFC1001 size %u smaller than SMB for mid=%u\n",
|
|
rfclen, mid);
|
|
return -EIO;
|
|
} else if (rfclen > clc_len + 512) {
|
|
/*
|
|
* Some servers (Windows XP in particular) send more
|
|
* data than the lengths in the SMB packet would
|
|
* indicate on certain calls (byte range locks and
|
|
* trans2 find first calls in particular). While the
|
|
* client can handle such a frame by ignoring the
|
|
* trailing data, we choose limit the amount of extra
|
|
* data to 512 bytes.
|
|
*/
|
|
cifs_dbg(VFS, "RFC1001 size %u more than 512 bytes larger than SMB for mid=%u\n",
|
|
rfclen, mid);
|
|
return -EIO;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv)
|
|
{
|
|
struct smb_hdr *buf = (struct smb_hdr *)buffer;
|
|
struct smb_com_lock_req *pSMB = (struct smb_com_lock_req *)buf;
|
|
struct list_head *tmp, *tmp1, *tmp2;
|
|
struct cifs_ses *ses;
|
|
struct cifs_tcon *tcon;
|
|
struct cifsInodeInfo *pCifsInode;
|
|
struct cifsFileInfo *netfile;
|
|
|
|
cifs_dbg(FYI, "Checking for oplock break or dnotify response\n");
|
|
if ((pSMB->hdr.Command == SMB_COM_NT_TRANSACT) &&
|
|
(pSMB->hdr.Flags & SMBFLG_RESPONSE)) {
|
|
struct smb_com_transaction_change_notify_rsp *pSMBr =
|
|
(struct smb_com_transaction_change_notify_rsp *)buf;
|
|
struct file_notify_information *pnotify;
|
|
__u32 data_offset = 0;
|
|
if (get_bcc(buf) > sizeof(struct file_notify_information)) {
|
|
data_offset = le32_to_cpu(pSMBr->DataOffset);
|
|
|
|
pnotify = (struct file_notify_information *)
|
|
((char *)&pSMBr->hdr.Protocol + data_offset);
|
|
cifs_dbg(FYI, "dnotify on %s Action: 0x%x\n",
|
|
pnotify->FileName, pnotify->Action);
|
|
/* cifs_dump_mem("Rcvd notify Data: ",buf,
|
|
sizeof(struct smb_hdr)+60); */
|
|
return true;
|
|
}
|
|
if (pSMBr->hdr.Status.CifsError) {
|
|
cifs_dbg(FYI, "notify err 0x%x\n",
|
|
pSMBr->hdr.Status.CifsError);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
if (pSMB->hdr.Command != SMB_COM_LOCKING_ANDX)
|
|
return false;
|
|
if (pSMB->hdr.Flags & SMBFLG_RESPONSE) {
|
|
/* no sense logging error on invalid handle on oplock
|
|
break - harmless race between close request and oplock
|
|
break response is expected from time to time writing out
|
|
large dirty files cached on the client */
|
|
if ((NT_STATUS_INVALID_HANDLE) ==
|
|
le32_to_cpu(pSMB->hdr.Status.CifsError)) {
|
|
cifs_dbg(FYI, "invalid handle on oplock break\n");
|
|
return true;
|
|
} else if (ERRbadfid ==
|
|
le16_to_cpu(pSMB->hdr.Status.DosError.Error)) {
|
|
return true;
|
|
} else {
|
|
return false; /* on valid oplock brk we get "request" */
|
|
}
|
|
}
|
|
if (pSMB->hdr.WordCount != 8)
|
|
return false;
|
|
|
|
cifs_dbg(FYI, "oplock type 0x%x level 0x%x\n",
|
|
pSMB->LockType, pSMB->OplockLevel);
|
|
if (!(pSMB->LockType & LOCKING_ANDX_OPLOCK_RELEASE))
|
|
return false;
|
|
|
|
/* look up tcon based on tid & uid */
|
|
spin_lock(&cifs_tcp_ses_lock);
|
|
list_for_each(tmp, &srv->smb_ses_list) {
|
|
ses = list_entry(tmp, struct cifs_ses, smb_ses_list);
|
|
list_for_each(tmp1, &ses->tcon_list) {
|
|
tcon = list_entry(tmp1, struct cifs_tcon, tcon_list);
|
|
if (tcon->tid != buf->Tid)
|
|
continue;
|
|
|
|
cifs_stats_inc(&tcon->stats.cifs_stats.num_oplock_brks);
|
|
spin_lock(&tcon->open_file_lock);
|
|
list_for_each(tmp2, &tcon->openFileList) {
|
|
netfile = list_entry(tmp2, struct cifsFileInfo,
|
|
tlist);
|
|
if (pSMB->Fid != netfile->fid.netfid)
|
|
continue;
|
|
|
|
cifs_dbg(FYI, "file id match, oplock break\n");
|
|
pCifsInode = CIFS_I(d_inode(netfile->dentry));
|
|
|
|
set_bit(CIFS_INODE_PENDING_OPLOCK_BREAK,
|
|
&pCifsInode->flags);
|
|
|
|
/*
|
|
* Set flag if the server downgrades the oplock
|
|
* to L2 else clear.
|
|
*/
|
|
if (pSMB->OplockLevel)
|
|
set_bit(
|
|
CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
|
|
&pCifsInode->flags);
|
|
else
|
|
clear_bit(
|
|
CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
|
|
&pCifsInode->flags);
|
|
|
|
queue_work(cifsoplockd_wq,
|
|
&netfile->oplock_break);
|
|
netfile->oplock_break_cancelled = false;
|
|
|
|
spin_unlock(&tcon->open_file_lock);
|
|
spin_unlock(&cifs_tcp_ses_lock);
|
|
return true;
|
|
}
|
|
spin_unlock(&tcon->open_file_lock);
|
|
spin_unlock(&cifs_tcp_ses_lock);
|
|
cifs_dbg(FYI, "No matching file for oplock break\n");
|
|
return true;
|
|
}
|
|
}
|
|
spin_unlock(&cifs_tcp_ses_lock);
|
|
cifs_dbg(FYI, "Can not process oplock break for non-existent connection\n");
|
|
return true;
|
|
}
|
|
|
|
void
|
|
dump_smb(void *buf, int smb_buf_length)
|
|
{
|
|
if (traceSMB == 0)
|
|
return;
|
|
|
|
print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_NONE, 8, 2, buf,
|
|
smb_buf_length, true);
|
|
}
|
|
|
|
void
|
|
cifs_autodisable_serverino(struct cifs_sb_info *cifs_sb)
|
|
{
|
|
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM) {
|
|
cifs_sb->mnt_cifs_flags &= ~CIFS_MOUNT_SERVER_INUM;
|
|
cifs_dbg(VFS, "Autodisabling the use of server inode numbers on %s. This server doesn't seem to support them properly. Hardlinks will not be recognized on this mount. Consider mounting with the \"noserverino\" option to silence this message.\n",
|
|
cifs_sb_master_tcon(cifs_sb)->treeName);
|
|
}
|
|
}
|
|
|
|
void cifs_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock)
|
|
{
|
|
oplock &= 0xF;
|
|
|
|
if (oplock == OPLOCK_EXCLUSIVE) {
|
|
cinode->oplock = CIFS_CACHE_WRITE_FLG | CIFS_CACHE_READ_FLG;
|
|
cifs_dbg(FYI, "Exclusive Oplock granted on inode %p\n",
|
|
&cinode->vfs_inode);
|
|
} else if (oplock == OPLOCK_READ) {
|
|
cinode->oplock = CIFS_CACHE_READ_FLG;
|
|
cifs_dbg(FYI, "Level II Oplock granted on inode %p\n",
|
|
&cinode->vfs_inode);
|
|
} else
|
|
cinode->oplock = 0;
|
|
}
|
|
|
|
/*
|
|
* We wait for oplock breaks to be processed before we attempt to perform
|
|
* writes.
|
|
*/
|
|
int cifs_get_writer(struct cifsInodeInfo *cinode)
|
|
{
|
|
int rc;
|
|
|
|
start:
|
|
rc = wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_OPLOCK_BREAK,
|
|
TASK_KILLABLE);
|
|
if (rc)
|
|
return rc;
|
|
|
|
spin_lock(&cinode->writers_lock);
|
|
if (!cinode->writers)
|
|
set_bit(CIFS_INODE_PENDING_WRITERS, &cinode->flags);
|
|
cinode->writers++;
|
|
/* Check to see if we have started servicing an oplock break */
|
|
if (test_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, &cinode->flags)) {
|
|
cinode->writers--;
|
|
if (cinode->writers == 0) {
|
|
clear_bit(CIFS_INODE_PENDING_WRITERS, &cinode->flags);
|
|
wake_up_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS);
|
|
}
|
|
spin_unlock(&cinode->writers_lock);
|
|
goto start;
|
|
}
|
|
spin_unlock(&cinode->writers_lock);
|
|
return 0;
|
|
}
|
|
|
|
void cifs_put_writer(struct cifsInodeInfo *cinode)
|
|
{
|
|
spin_lock(&cinode->writers_lock);
|
|
cinode->writers--;
|
|
if (cinode->writers == 0) {
|
|
clear_bit(CIFS_INODE_PENDING_WRITERS, &cinode->flags);
|
|
wake_up_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS);
|
|
}
|
|
spin_unlock(&cinode->writers_lock);
|
|
}
|
|
|
|
void cifs_done_oplock_break(struct cifsInodeInfo *cinode)
|
|
{
|
|
clear_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, &cinode->flags);
|
|
wake_up_bit(&cinode->flags, CIFS_INODE_PENDING_OPLOCK_BREAK);
|
|
}
|
|
|
|
bool
|
|
backup_cred(struct cifs_sb_info *cifs_sb)
|
|
{
|
|
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_CIFS_BACKUPUID) {
|
|
if (uid_eq(cifs_sb->mnt_backupuid, current_fsuid()))
|
|
return true;
|
|
}
|
|
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_CIFS_BACKUPGID) {
|
|
if (in_group_p(cifs_sb->mnt_backupgid))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
cifs_del_pending_open(struct cifs_pending_open *open)
|
|
{
|
|
spin_lock(&tlink_tcon(open->tlink)->open_file_lock);
|
|
list_del(&open->olist);
|
|
spin_unlock(&tlink_tcon(open->tlink)->open_file_lock);
|
|
}
|
|
|
|
void
|
|
cifs_add_pending_open_locked(struct cifs_fid *fid, struct tcon_link *tlink,
|
|
struct cifs_pending_open *open)
|
|
{
|
|
#ifdef CONFIG_CIFS_SMB2
|
|
memcpy(open->lease_key, fid->lease_key, SMB2_LEASE_KEY_SIZE);
|
|
#endif
|
|
open->oplock = CIFS_OPLOCK_NO_CHANGE;
|
|
open->tlink = tlink;
|
|
fid->pending_open = open;
|
|
list_add_tail(&open->olist, &tlink_tcon(tlink)->pending_opens);
|
|
}
|
|
|
|
void
|
|
cifs_add_pending_open(struct cifs_fid *fid, struct tcon_link *tlink,
|
|
struct cifs_pending_open *open)
|
|
{
|
|
spin_lock(&tlink_tcon(tlink)->open_file_lock);
|
|
cifs_add_pending_open_locked(fid, tlink, open);
|
|
spin_unlock(&tlink_tcon(open->tlink)->open_file_lock);
|
|
}
|
|
|
|
/* parses DFS refferal V3 structure
|
|
* caller is responsible for freeing target_nodes
|
|
* returns:
|
|
* - on success - 0
|
|
* - on failure - errno
|
|
*/
|
|
int
|
|
parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
|
|
unsigned int *num_of_nodes,
|
|
struct dfs_info3_param **target_nodes,
|
|
const struct nls_table *nls_codepage, int remap,
|
|
const char *searchName, bool is_unicode)
|
|
{
|
|
int i, rc = 0;
|
|
char *data_end;
|
|
struct dfs_referral_level_3 *ref;
|
|
|
|
*num_of_nodes = le16_to_cpu(rsp->NumberOfReferrals);
|
|
|
|
if (*num_of_nodes < 1) {
|
|
cifs_dbg(VFS, "num_referrals: must be at least > 0, but we get num_referrals = %d\n",
|
|
*num_of_nodes);
|
|
rc = -EINVAL;
|
|
goto parse_DFS_referrals_exit;
|
|
}
|
|
|
|
ref = (struct dfs_referral_level_3 *) &(rsp->referrals);
|
|
if (ref->VersionNumber != cpu_to_le16(3)) {
|
|
cifs_dbg(VFS, "Referrals of V%d version are not supported, should be V3\n",
|
|
le16_to_cpu(ref->VersionNumber));
|
|
rc = -EINVAL;
|
|
goto parse_DFS_referrals_exit;
|
|
}
|
|
|
|
/* get the upper boundary of the resp buffer */
|
|
data_end = (char *)rsp + rsp_size;
|
|
|
|
cifs_dbg(FYI, "num_referrals: %d dfs flags: 0x%x ...\n",
|
|
*num_of_nodes, le32_to_cpu(rsp->DFSFlags));
|
|
|
|
*target_nodes = kcalloc(*num_of_nodes, sizeof(struct dfs_info3_param),
|
|
GFP_KERNEL);
|
|
if (*target_nodes == NULL) {
|
|
rc = -ENOMEM;
|
|
goto parse_DFS_referrals_exit;
|
|
}
|
|
|
|
/* collect necessary data from referrals */
|
|
for (i = 0; i < *num_of_nodes; i++) {
|
|
char *temp;
|
|
int max_len;
|
|
struct dfs_info3_param *node = (*target_nodes)+i;
|
|
|
|
node->flags = le32_to_cpu(rsp->DFSFlags);
|
|
if (is_unicode) {
|
|
__le16 *tmp = kmalloc(strlen(searchName)*2 + 2,
|
|
GFP_KERNEL);
|
|
if (tmp == NULL) {
|
|
rc = -ENOMEM;
|
|
goto parse_DFS_referrals_exit;
|
|
}
|
|
cifsConvertToUTF16((__le16 *) tmp, searchName,
|
|
PATH_MAX, nls_codepage, remap);
|
|
node->path_consumed = cifs_utf16_bytes(tmp,
|
|
le16_to_cpu(rsp->PathConsumed),
|
|
nls_codepage);
|
|
kfree(tmp);
|
|
} else
|
|
node->path_consumed = le16_to_cpu(rsp->PathConsumed);
|
|
|
|
node->server_type = le16_to_cpu(ref->ServerType);
|
|
node->ref_flag = le16_to_cpu(ref->ReferralEntryFlags);
|
|
|
|
/* copy DfsPath */
|
|
temp = (char *)ref + le16_to_cpu(ref->DfsPathOffset);
|
|
max_len = data_end - temp;
|
|
node->path_name = cifs_strndup_from_utf16(temp, max_len,
|
|
is_unicode, nls_codepage);
|
|
if (!node->path_name) {
|
|
rc = -ENOMEM;
|
|
goto parse_DFS_referrals_exit;
|
|
}
|
|
|
|
/* copy link target UNC */
|
|
temp = (char *)ref + le16_to_cpu(ref->NetworkAddressOffset);
|
|
max_len = data_end - temp;
|
|
node->node_name = cifs_strndup_from_utf16(temp, max_len,
|
|
is_unicode, nls_codepage);
|
|
if (!node->node_name) {
|
|
rc = -ENOMEM;
|
|
goto parse_DFS_referrals_exit;
|
|
}
|
|
|
|
ref++;
|
|
}
|
|
|
|
parse_DFS_referrals_exit:
|
|
if (rc) {
|
|
free_dfs_info_array(*target_nodes, *num_of_nodes);
|
|
*target_nodes = NULL;
|
|
*num_of_nodes = 0;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
struct cifs_aio_ctx *
|
|
cifs_aio_ctx_alloc(void)
|
|
{
|
|
struct cifs_aio_ctx *ctx;
|
|
|
|
ctx = kzalloc(sizeof(struct cifs_aio_ctx), GFP_KERNEL);
|
|
if (!ctx)
|
|
return NULL;
|
|
|
|
INIT_LIST_HEAD(&ctx->list);
|
|
mutex_init(&ctx->aio_mutex);
|
|
init_completion(&ctx->done);
|
|
kref_init(&ctx->refcount);
|
|
return ctx;
|
|
}
|
|
|
|
void
|
|
cifs_aio_ctx_release(struct kref *refcount)
|
|
{
|
|
struct cifs_aio_ctx *ctx = container_of(refcount,
|
|
struct cifs_aio_ctx, refcount);
|
|
|
|
cifsFileInfo_put(ctx->cfile);
|
|
kvfree(ctx->bv);
|
|
kfree(ctx);
|
|
}
|
|
|
|
#define CIFS_AIO_KMALLOC_LIMIT (1024 * 1024)
|
|
|
|
int
|
|
setup_aio_ctx_iter(struct cifs_aio_ctx *ctx, struct iov_iter *iter, int rw)
|
|
{
|
|
ssize_t rc;
|
|
unsigned int cur_npages;
|
|
unsigned int npages = 0;
|
|
unsigned int i;
|
|
size_t len;
|
|
size_t count = iov_iter_count(iter);
|
|
unsigned int saved_len;
|
|
size_t start;
|
|
unsigned int max_pages = iov_iter_npages(iter, INT_MAX);
|
|
struct page **pages = NULL;
|
|
struct bio_vec *bv = NULL;
|
|
|
|
if (iter->type & ITER_KVEC) {
|
|
memcpy(&ctx->iter, iter, sizeof(struct iov_iter));
|
|
ctx->len = count;
|
|
iov_iter_advance(iter, count);
|
|
return 0;
|
|
}
|
|
|
|
if (max_pages * sizeof(struct bio_vec) <= CIFS_AIO_KMALLOC_LIMIT)
|
|
bv = kmalloc_array(max_pages, sizeof(struct bio_vec),
|
|
GFP_KERNEL);
|
|
|
|
if (!bv) {
|
|
bv = vmalloc(max_pages * sizeof(struct bio_vec));
|
|
if (!bv)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (max_pages * sizeof(struct page *) <= CIFS_AIO_KMALLOC_LIMIT)
|
|
pages = kmalloc_array(max_pages, sizeof(struct page *),
|
|
GFP_KERNEL);
|
|
|
|
if (!pages) {
|
|
pages = vmalloc(max_pages * sizeof(struct page *));
|
|
if (!bv) {
|
|
kvfree(bv);
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
saved_len = count;
|
|
|
|
while (count && npages < max_pages) {
|
|
rc = iov_iter_get_pages(iter, pages, count, max_pages, &start);
|
|
if (rc < 0) {
|
|
cifs_dbg(VFS, "couldn't get user pages (rc=%zd)\n", rc);
|
|
break;
|
|
}
|
|
|
|
if (rc > count) {
|
|
cifs_dbg(VFS, "get pages rc=%zd more than %zu\n", rc,
|
|
count);
|
|
break;
|
|
}
|
|
|
|
iov_iter_advance(iter, rc);
|
|
count -= rc;
|
|
rc += start;
|
|
cur_npages = DIV_ROUND_UP(rc, PAGE_SIZE);
|
|
|
|
if (npages + cur_npages > max_pages) {
|
|
cifs_dbg(VFS, "out of vec array capacity (%u vs %u)\n",
|
|
npages + cur_npages, max_pages);
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < cur_npages; i++) {
|
|
len = rc > PAGE_SIZE ? PAGE_SIZE : rc;
|
|
bv[npages + i].bv_page = pages[i];
|
|
bv[npages + i].bv_offset = start;
|
|
bv[npages + i].bv_len = len - start;
|
|
rc -= len;
|
|
start = 0;
|
|
}
|
|
|
|
npages += cur_npages;
|
|
}
|
|
|
|
kvfree(pages);
|
|
ctx->bv = bv;
|
|
ctx->len = saved_len - count;
|
|
ctx->npages = npages;
|
|
iov_iter_bvec(&ctx->iter, ITER_BVEC | rw, ctx->bv, npages, ctx->len);
|
|
return 0;
|
|
}
|