diff options
Diffstat (limited to 'fs/cifs/misc.c')
| -rw-r--r-- | fs/cifs/misc.c | 737 |
1 files changed, 312 insertions, 425 deletions
diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c index 2a42d9fedbb..3b0c62e622d 100644 --- a/fs/cifs/misc.c +++ b/fs/cifs/misc.c @@ -29,10 +29,12 @@ #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; -extern struct task_struct *oplockThread; /* 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, @@ -41,7 +43,7 @@ extern struct task_struct *oplockThread; since the cifs fs was mounted */ unsigned int -_GetXid(void) +_get_xid(void) { unsigned int xid; @@ -52,14 +54,14 @@ _GetXid(void) if (GlobalTotalActiveXid > GlobalMaxActiveXid) GlobalMaxActiveXid = GlobalTotalActiveXid; if (GlobalTotalActiveXid > 65000) - cFYI(1, ("warning: more than 65000 requests active")); + cifs_dbg(FYI, "warning: more than 65000 requests active\n"); xid = GlobalCurrentXid++; spin_unlock(&GlobalMid_Lock); return xid; } void -_FreeXid(unsigned int xid) +_free_xid(unsigned int xid) { spin_lock(&GlobalMid_Lock); /* if (GlobalTotalActiveXid == 0) @@ -68,76 +70,76 @@ _FreeXid(unsigned int xid) spin_unlock(&GlobalMid_Lock); } -struct cifsSesInfo * +struct cifs_ses * sesInfoAlloc(void) { - struct cifsSesInfo *ret_buf; + struct cifs_ses *ret_buf; - ret_buf = kzalloc(sizeof(struct cifsSesInfo), GFP_KERNEL); + ret_buf = kzalloc(sizeof(struct cifs_ses), GFP_KERNEL); if (ret_buf) { - write_lock(&GlobalSMBSeslock); atomic_inc(&sesInfoAllocCount); ret_buf->status = CifsNew; - list_add(&ret_buf->cifsSessionList, &GlobalSMBSessionList); - init_MUTEX(&ret_buf->sesSem); - write_unlock(&GlobalSMBSeslock); + ++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 cifsSesInfo *buf_to_free) +sesInfoFree(struct cifs_ses *buf_to_free) { if (buf_to_free == NULL) { - cFYI(1, ("Null buffer passed to sesInfoFree")); + cifs_dbg(FYI, "Null buffer passed to sesInfoFree\n"); return; } - write_lock(&GlobalSMBSeslock); atomic_dec(&sesInfoAllocCount); - list_del(&buf_to_free->cifsSessionList); - write_unlock(&GlobalSMBSeslock); kfree(buf_to_free->serverOS); kfree(buf_to_free->serverDomain); kfree(buf_to_free->serverNOS); - kfree(buf_to_free->password); + 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 cifsTconInfo * +struct cifs_tcon * tconInfoAlloc(void) { - struct cifsTconInfo *ret_buf; - ret_buf = kzalloc(sizeof(struct cifsTconInfo), GFP_KERNEL); + struct cifs_tcon *ret_buf; + ret_buf = kzalloc(sizeof(struct cifs_tcon), GFP_KERNEL); if (ret_buf) { - write_lock(&GlobalSMBSeslock); atomic_inc(&tconInfoAllocCount); - list_add(&ret_buf->cifsConnectionList, - &GlobalTreeConnectionList); ret_buf->tidStatus = CifsNew; + ++ret_buf->tc_count; INIT_LIST_HEAD(&ret_buf->openFileList); - init_MUTEX(&ret_buf->tconSem); + INIT_LIST_HEAD(&ret_buf->tcon_list); #ifdef CONFIG_CIFS_STATS spin_lock_init(&ret_buf->stat_lock); #endif - write_unlock(&GlobalSMBSeslock); } return ret_buf; } void -tconInfoFree(struct cifsTconInfo *buf_to_free) +tconInfoFree(struct cifs_tcon *buf_to_free) { if (buf_to_free == NULL) { - cFYI(1, ("Null buffer passed to tconInfoFree")); + cifs_dbg(FYI, "Null buffer passed to tconInfoFree\n"); return; } - write_lock(&GlobalSMBSeslock); atomic_dec(&tconInfoAllocCount); - list_del(&buf_to_free->cifsConnectionList); - write_unlock(&GlobalSMBSeslock); 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); } @@ -145,18 +147,27 @@ struct smb_hdr * cifs_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 = (struct smb_hdr *) mempool_alloc(cifs_req_poolp, - GFP_KERNEL | GFP_NOFS); + 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 */ if (ret_buf) { - memset(ret_buf, 0, sizeof(struct smb_hdr) + 3); + memset(ret_buf, 0, buf_size + 3); atomic_inc(&bufAllocCount); #ifdef CONFIG_CIFS_STATS2 atomic_inc(&totBufAllocCount); @@ -170,7 +181,7 @@ void cifs_buf_release(void *buf_to_free) { if (buf_to_free == NULL) { - /* cFYI(1, ("Null buffer passed to cifs_buf_release"));*/ + /* cifs_dbg(FYI, "Null buffer passed to cifs_buf_release\n");*/ return; } mempool_free(buf_to_free, cifs_req_poolp); @@ -188,8 +199,7 @@ cifs_small_buf_get(void) 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 = (struct smb_hdr *) mempool_alloc(cifs_sm_req_poolp, - GFP_KERNEL | GFP_NOFS); + ret_buf = mempool_alloc(cifs_sm_req_poolp, GFP_NOFS); if (ret_buf) { /* No need to clear memory here, cleared in header assemble */ /* memset(ret_buf, 0, sizeof(struct smb_hdr) + 27);*/ @@ -207,7 +217,7 @@ cifs_small_buf_release(void *buf_to_free) { if (buf_to_free == NULL) { - cFYI(1, ("Null buffer passed to cifs_small_buf_release")); + cifs_dbg(FYI, "Null buffer passed to cifs_small_buf_release\n"); return; } mempool_free(buf_to_free, cifs_sm_req_poolp); @@ -216,93 +226,21 @@ cifs_small_buf_release(void *buf_to_free) return; } -/* - Find a free multiplex id (SMB mid). Otherwise there could be - mid collisions which might cause problems, demultiplexing the - wrong response to this request. Multiplex ids could collide if - one of a series requests takes much longer than the others, or - if a very large number of long lived requests (byte range - locks or FindNotify requests) are pending. No more than - 64K-1 requests can be outstanding at one time. If no - mids are available, return zero. A future optimization - could make the combination of mids and uid the key we use - to demultiplex on (rather than mid alone). - In addition to the above check, the cifs demultiplex - code already used the command code as a secondary - check of the frame and if signing is negotiated the - response would be discarded if the mid were the same - but the signature was wrong. Since the mid is not put in the - pending queue until later (when it is about to be dispatched) - we do have to limit the number of outstanding requests - to somewhat less than 64K-1 although it is hard to imagine - so many threads being in the vfs at one time. -*/ -__u16 GetNextMid(struct TCP_Server_Info *server) -{ - __u16 mid = 0; - __u16 last_mid; - int collision; - - if (server == NULL) - return mid; - - spin_lock(&GlobalMid_Lock); - last_mid = server->CurrentMid; /* we do not want to loop forever */ - server->CurrentMid++; - /* This nested loop looks more expensive than it is. - In practice the list of pending requests is short, - fewer than 50, and the mids are likely to be unique - on the first pass through the loop unless some request - takes longer than the 64 thousand requests before it - (and it would also have to have been a request that - did not time out) */ - while (server->CurrentMid != last_mid) { - struct list_head *tmp; - struct mid_q_entry *mid_entry; - - collision = 0; - if (server->CurrentMid == 0) - server->CurrentMid++; - - list_for_each(tmp, &server->pending_mid_q) { - mid_entry = list_entry(tmp, struct mid_q_entry, qhead); - - if ((mid_entry->mid == server->CurrentMid) && - (mid_entry->midState == MID_REQUEST_SUBMITTED)) { - /* This mid is in use, try a different one */ - collision = 1; - break; - } - } - if (collision == 0) { - mid = server->CurrentMid; - break; - } - server->CurrentMid++; - } - spin_unlock(&GlobalMid_Lock); - return mid; -} - /* 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 cifsTconInfo *treeCon, int word_count + const struct cifs_tcon *treeCon, int word_count /* length of fixed section (word count) in two byte units */) { - struct list_head *temp_item; - struct cifsSesInfo *ses; char *temp = (char *) buffer; memset(temp, 0, 256); /* bigger than MAX_CIFS_HDR_SIZE */ - buffer->smb_buf_length = + 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 */ ; - /* Note that this is the only network field that has to be converted - to big endian and it is done just before we send it */ + 2 /* for bcc field itself */) ; buffer->Protocol[0] = 0xFF; buffer->Protocol[1] = 'S'; @@ -313,8 +251,6 @@ header_assemble(struct smb_hdr *buffer, char smb_command /* command */ , buffer->Flags2 = SMBFLG2_KNOWS_LONG_NAMES; buffer->Pid = cpu_to_le16((__u16)current->tgid); buffer->PidHigh = cpu_to_le16((__u16)(current->tgid >> 16)); - spin_lock(&GlobalMid_Lock); - spin_unlock(&GlobalMid_Lock); if (treeCon) { buffer->Tid = treeCon->tid; if (treeCon->ses) { @@ -325,60 +261,14 @@ header_assemble(struct smb_hdr *buffer, char smb_command /* command */ , /* Uid is not converted */ buffer->Uid = treeCon->ses->Suid; - buffer->Mid = GetNextMid(treeCon->ses->server); - if (multiuser_mount != 0) { - /* For the multiuser case, there are few obvious technically */ - /* possible mechanisms to match the local linux user (uid) */ - /* to a valid remote smb user (smb_uid): */ - /* 1) Query Winbind (or other local pam/nss daemon */ - /* for userid/password/logon_domain or credential */ - /* 2) Query Winbind for uid to sid to username mapping */ - /* and see if we have a matching password for existing*/ - /* session for that user perhas getting password by */ - /* adding a new pam_cifs module that stores passwords */ - /* so that the cifs vfs can get at that for all logged*/ - /* on users */ - /* 3) (Which is the mechanism we have chosen) */ - /* Search through sessions to the same server for a */ - /* a match on the uid that was passed in on mount */ - /* with the current processes uid (or euid?) and use */ - /* that smb uid. If no existing smb session for */ - /* that uid found, use the default smb session ie */ - /* the smb session for the volume mounted which is */ - /* the same as would be used if the multiuser mount */ - /* flag were disabled. */ - - /* BB Add support for establishing new tCon and SMB Session */ - /* with userid/password pairs found on the smb session */ - /* for other target tcp/ip addresses BB */ - if (current->fsuid != treeCon->ses->linux_uid) { - cFYI(1, ("Multiuser mode and UID " - "did not match tcon uid")); - read_lock(&GlobalSMBSeslock); - list_for_each(temp_item, &GlobalSMBSessionList) { - ses = list_entry(temp_item, struct cifsSesInfo, cifsSessionList); - if (ses->linux_uid == current->fsuid) { - if (ses->server == treeCon->ses->server) { - cFYI(1, ("found matching uid substitute right smb_uid")); - buffer->Uid = ses->Suid; - break; - } else { - /* BB eventually call cifs_setup_session here */ - cFYI(1, ("local UID found but no smb sess with this server exists")); - } - } - } - read_unlock(&GlobalSMBSeslock); - } - } + 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->secMode & - (SECMODE_SIGN_REQUIRED | SECMODE_SIGN_ENABLED)) + if (treeCon->ses->server->sign) buffer->Flags2 |= SMBFLG2_SECURITY_SIGNATURE; } @@ -388,47 +278,46 @@ header_assemble(struct smb_hdr *buffer, char smb_command /* command */ , } static int -checkSMBhdr(struct smb_hdr *smb, __u16 mid) +check_smb_hdr(struct smb_hdr *smb) { - /* Make sure that this really is an SMB, that it is a response, - and that the message ids match */ - if ((*(__le32 *) smb->Protocol == cpu_to_le32(0x424d53ff)) && - (mid == smb->Mid)) { - if (smb->Flags & SMBFLG_RESPONSE) - return 0; - else { - /* only one valid case where server sends us request */ - if (smb->Command == SMB_COM_LOCKING_ANDX) - return 0; - else - cERROR(1, ("Received Request not response")); - } - } else { /* bad signature or mid */ - if (*(__le32 *) smb->Protocol != cpu_to_le32(0x424d53ff)) - cERROR(1, - ("Bad protocol string signature header %x", - *(unsigned int *) smb->Protocol)); - if (mid != smb->Mid) - cERROR(1, ("Mids do not match")); + /* 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; } - cERROR(1, ("bad smb detected. The Mid=%d", smb->Mid)); + + /* 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(struct smb_hdr *smb, __u16 mid, unsigned int length) +checkSMB(char *buf, unsigned int total_read) { - __u32 len = smb->smb_buf_length; + struct smb_hdr *smb = (struct smb_hdr *)buf; + __u32 rfclen = be32_to_cpu(smb->smb_buf_length); __u32 clc_len; /* calculated length */ - cFYI(0, ("checkSMB Length: 0x%x, smb_buf_length: 0x%x", length, len)); + cifs_dbg(FYI, "checkSMB Length: 0x%x, smb_buf_length: 0x%x\n", + total_read, rfclen); - if (length < 2 + sizeof(struct smb_hdr)) { - if ((length >= sizeof(struct smb_hdr) - 1) + /* 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 ((length == sizeof(struct smb_hdr) + 1) && + } 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 */ @@ -444,95 +333,95 @@ checkSMB(struct smb_hdr *smb, __u16 mid, unsigned int length) tmp[sizeof(struct smb_hdr)+1] = 0; return 0; } - cERROR(1, ("rcvd invalid byte count (bcc)")); + cifs_dbg(VFS, "rcvd invalid byte count (bcc)\n"); } else { - cERROR(1, ("Length less than smb header size")); + cifs_dbg(VFS, "Length less than smb header size\n"); } - return 1; - } - if (len > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4) { - cERROR(1, ("smb length greater than MaxBufSize, mid=%d", - smb->Mid)); - return 1; + return -EIO; } - if (checkSMBhdr(smb, mid)) - return 1; - clc_len = smbCalcSize_LE(smb); + /* otherwise, there is enough to get to the BCC */ + if (check_smb_hdr(smb)) + return -EIO; + clc_len = smbCalcSize(smb); - if (4 + len != length) { - cERROR(1, ("Length read does not match RFC1001 length %d", - len)); - return 1; + if (4 + rfclen != total_read) { + cifs_dbg(VFS, "Length read does not match RFC1001 length %d\n", + rfclen); + return -EIO; } - if (4 + len != clc_len) { + if (4 + rfclen != clc_len) { + __u16 mid = get_mid(smb); /* check if bcc wrapped around for large read responses */ - if ((len > 64 * 1024) && (len > clc_len)) { + if ((rfclen > 64 * 1024) && (rfclen > clc_len)) { /* check if lengths match mod 64K */ - if (((4 + len) & 0xFFFF) == (clc_len & 0xFFFF)) + if (((4 + rfclen) & 0xFFFF) == (clc_len & 0xFFFF)) return 0; /* bcc wrapped */ } - cFYI(1, ("Calculated size %d vs length %d mismatch for mid %d", - clc_len, 4 + len, smb->Mid)); - /* Windows XP can return a few bytes too much, presumably - an illegal pad, at the end of byte range lock responses - so we allow for that three byte pad, as long as actual - received length is as long or longer than calculated length */ - /* We have now had to extend this more, since there is a - case in which it needs to be bigger still to handle a - malformed response to transact2 findfirst from WinXP when - access denied is returned and thus bcc and wct are zero - but server says length is 0x21 bytes too long as if the server - forget to reset the smb rfc1001 length when it reset the - wct and bcc to minimum size and drop the t2 parms and data */ - if ((4+len > clc_len) && (len <= clc_len + 512)) - return 0; - else { - cERROR(1, ("RFC1001 size %d bigger than SMB for Mid=%d", - len, smb->Mid)); - return 1; + 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; } -int -is_valid_oplock_break(struct smb_hdr *buf, struct TCP_Server_Info *srv) + +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; - struct list_head *tmp1; - struct cifsTconInfo *tcon; + struct list_head *tmp, *tmp1, *tmp2; + struct cifs_ses *ses; + struct cifs_tcon *tcon; + struct cifsInodeInfo *pCifsInode; struct cifsFileInfo *netfile; - cFYI(1, ("Checking for oplock break or dnotify response")); + 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 (pSMBr->ByteCount > sizeof(struct file_notify_information)) { + 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); - cFYI(1, ("dnotify on %s Action: 0x%x", - pnotify->FileName, - pnotify->Action)); /* BB removeme BB */ + 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; + return true; } if (pSMBr->hdr.Status.CifsError) { - cFYI(1, ("notify err 0x%d", - pSMBr->hdr.Status.CifsError)); - return TRUE; + cifs_dbg(FYI, "notify err 0x%d\n", + pSMBr->hdr.Status.CifsError); + return true; } - return FALSE; + return false; } if (pSMB->hdr.Command != SMB_COM_LOCKING_ANDX) - return FALSE; + 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 @@ -540,75 +429,88 @@ is_valid_oplock_break(struct smb_hdr *buf, struct TCP_Server_Info *srv) large dirty files cached on the client */ if ((NT_STATUS_INVALID_HANDLE) == le32_to_cpu(pSMB->hdr.Status.CifsError)) { - cFYI(1, ("invalid handle on oplock break")); - return TRUE; + cifs_dbg(FYI, "invalid handle on oplock break\n"); + return true; } else if (ERRbadfid == le16_to_cpu(pSMB->hdr.Status.DosError.Error)) { - return TRUE; + return true; } else { - return FALSE; /* on valid oplock brk we get "request" */ + return false; /* on valid oplock brk we get "request" */ } } if (pSMB->hdr.WordCount != 8) - return FALSE; + return false; - cFYI(1, ("oplock type 0x%d level 0x%d", - pSMB->LockType, pSMB->OplockLevel)); + cifs_dbg(FYI, "oplock type 0x%d level 0x%d\n", + pSMB->LockType, pSMB->OplockLevel); if (!(pSMB->LockType & LOCKING_ANDX_OPLOCK_RELEASE)) - return FALSE; + return false; /* look up tcon based on tid & uid */ - read_lock(&GlobalSMBSeslock); - list_for_each(tmp, &GlobalTreeConnectionList) { - tcon = list_entry(tmp, struct cifsTconInfo, cifsConnectionList); - if ((tcon->tid == buf->Tid) && (srv == tcon->ses->server)) { - cifs_stats_inc(&tcon->num_oplock_brks); - list_for_each(tmp1, &tcon->openFileList) { - netfile = list_entry(tmp1, struct cifsFileInfo, + 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(&cifs_file_list_lock); + list_for_each(tmp2, &tcon->openFileList) { + netfile = list_entry(tmp2, struct cifsFileInfo, tlist); - if (pSMB->Fid == netfile->netfid) { - struct cifsInodeInfo *pCifsInode; - read_unlock(&GlobalSMBSeslock); - cFYI(1, - ("file id match, oplock break")); - pCifsInode = - CIFS_I(netfile->pInode); - pCifsInode->clientCanCacheAll = FALSE; - if (pSMB->OplockLevel == 0) - pCifsInode->clientCanCacheRead - = FALSE; - pCifsInode->oplockPending = TRUE; - AllocOplockQEntry(netfile->pInode, - netfile->netfid, - tcon); - cFYI(1, - ("about to wake up oplock thread")); - if (oplockThread) - wake_up_process(oplockThread); - return TRUE; - } + if (pSMB->Fid != netfile->fid.netfid) + continue; + + cifs_dbg(FYI, "file id match, oplock break\n"); + pCifsInode = CIFS_I(netfile->dentry->d_inode); + + 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(cifsiod_wq, + &netfile->oplock_break); + netfile->oplock_break_cancelled = false; + + spin_unlock(&cifs_file_list_lock); + spin_unlock(&cifs_tcp_ses_lock); + return true; } - read_unlock(&GlobalSMBSeslock); - cFYI(1, ("No matching file for oplock break")); - return TRUE; + spin_unlock(&cifs_file_list_lock); + spin_unlock(&cifs_tcp_ses_lock); + cifs_dbg(FYI, "No matching file for oplock break\n"); + return true; } } - read_unlock(&GlobalSMBSeslock); - cFYI(1, ("Can not process oplock break for non-existent connection")); - 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(struct smb_hdr *smb_buf, int smb_buf_length) +dump_smb(void *buf, int smb_buf_length) { int i, j; char debug_line[17]; - unsigned char *buffer; + unsigned char *buffer = buf; if (traceSMB == 0) return; - buffer = (unsigned char *) smb_buf; for (i = 0, j = 0; i < smb_buf_length; i++, j++) { if (i % 8 == 0) { /* have reached the beginning of line */ @@ -637,144 +539,129 @@ dump_smb(struct smb_hdr *smb_buf, int smb_buf_length) return; } -/* Windows maps these to the user defined 16 bit Unicode range since they are - reserved symbols (along with \ and /), otherwise illegal to store - in filenames in NTFS */ -#define UNI_ASTERIK (__u16) ('*' + 0xF000) -#define UNI_QUESTION (__u16) ('?' + 0xF000) -#define UNI_COLON (__u16) (':' + 0xF000) -#define UNI_GRTRTHAN (__u16) ('>' + 0xF000) -#define UNI_LESSTHAN (__u16) ('<' + 0xF000) -#define UNI_PIPE (__u16) ('|' + 0xF000) -#define UNI_SLASH (__u16) ('\\' + 0xF000) - -/* Convert 16 bit Unicode pathname from wire format to string in current code - page. Conversion may involve remapping up the seven characters that are - only legal in POSIX-like OS (if they are present in the string). Path - names are little endian 16 bit Unicode on the wire */ -int -cifs_convertUCSpath(char *target, const __le16 *source, int maxlen, - const struct nls_table *cp) +void +cifs_autodisable_serverino(struct cifs_sb_info *cifs_sb) { - int i, j, len; - __u16 src_char; - - for (i = 0, j = 0; i < maxlen; i++) { - src_char = le16_to_cpu(source[i]); - switch (src_char) { - case 0: - goto cUCS_out; /* BB check this BB */ - case UNI_COLON: - target[j] = ':'; - break; - case UNI_ASTERIK: - target[j] = '*'; - break; - case UNI_QUESTION: - target[j] = '?'; - break; - /* BB We can not handle remapping slash until - all the calls to build_path_from_dentry - are modified, as they use slash as separator BB */ - /* case UNI_SLASH: - target[j] = '\\'; - break;*/ - case UNI_PIPE: - target[j] = '|'; - break; - case UNI_GRTRTHAN: - target[j] = '>'; - break; - case UNI_LESSTHAN: - target[j] = '<'; - break; - default: - len = cp->uni2char(src_char, &target[j], - NLS_MAX_CHARSET_SIZE); - if (len > 0) { - j += len; - continue; - } else { - target[j] = '?'; - } - } - j++; - /* make sure we do not overrun callers allocated temp buffer */ - if (j >= (2 * NAME_MAX)) - break; + 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); } -cUCS_out: - target[j] = 0; - return j; } -/* Convert 16 bit Unicode pathname to wire format from string in current code - page. Conversion may involve remapping up the seven characters that are - only legal in POSIX-like OS (if they are present in the string). Path - names are little endian 16 bit Unicode on the wire */ -int -cifsConvertToUCS(__le16 *target, const char *source, int maxlen, - const struct nls_table *cp, int mapChars) +void cifs_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock) { - int i, j, charlen; - int len_remaining = maxlen; - char src_char; - __u16 temp; - - if (!mapChars) - return cifs_strtoUCS(target, source, PATH_MAX, cp); - - for (i = 0, j = 0; i < maxlen; j++) { - src_char = source[i]; - switch (src_char) { - case 0: - target[j] = 0; - goto ctoUCS_out; - case ':': - target[j] = cpu_to_le16(UNI_COLON); - break; - case '*': - target[j] = cpu_to_le16(UNI_ASTERIK); - break; - case '?': - target[j] = cpu_to_le16(UNI_QUESTION); - break; - case '<': - target[j] = cpu_to_le16(UNI_LESSTHAN); - break; - case '>': - target[j] = cpu_to_le16(UNI_GRTRTHAN); - break; - case '|': - target[j] = cpu_to_le16(UNI_PIPE); - break; - /* BB We can not handle remapping slash until - all the calls to build_path_from_dentry - are modified, as they use slash as separator BB */ - /* case '\\': - target[j] = cpu_to_le16(UNI_SLASH); - break;*/ - default: - charlen = cp->char2uni(source+i, - len_remaining, &temp); - /* if no match, use question mark, which - at least in some cases servers as wild card */ - if (charlen < 1) { - target[j] = cpu_to_le16(0x003f); - charlen = 1; - } else - target[j] = cpu_to_le16(temp); - len_remaining -= charlen; - /* character may take more than one byte in the - the source string, but will take exactly two - bytes in the target string */ - i += charlen; - continue; + 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; +} + +static int +cifs_oplock_break_wait(void *unused) +{ + schedule(); + return signal_pending(current) ? -ERESTARTSYS : 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, + cifs_oplock_break_wait, 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); } - i++; /* move to next char in source string */ - len_remaining--; + spin_unlock(&cinode->writers_lock); + goto start; } + spin_unlock(&cinode->writers_lock); + return 0; +} -ctoUCS_out: - return i; +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(&cifs_file_list_lock); + list_del(&open->olist); + spin_unlock(&cifs_file_list_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(&cifs_file_list_lock); + cifs_add_pending_open_locked(fid, tlink, open); + spin_unlock(&cifs_file_list_lock); } |
