summaryrefslogtreecommitdiff
path: root/fs/cifs/misc.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /fs/cifs/misc.c
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'fs/cifs/misc.c')
-rw-r--r--fs/cifs/misc.c516
1 files changed, 516 insertions, 0 deletions
diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c
new file mode 100644
index 000000000000..7b38d3059a83
--- /dev/null
+++ b/fs/cifs/misc.c
@@ -0,0 +1,516 @@
+/*
+ * fs/cifs/misc.c
+ *
+ * Copyright (C) International Business Machines Corp., 2002,2004
+ * 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 "cifspdu.h"
+#include "cifsglob.h"
+#include "cifsproto.h"
+#include "cifs_debug.h"
+#include "smberr.h"
+#include "nterr.h"
+
+extern mempool_t *cifs_sm_req_poolp;
+extern mempool_t *cifs_req_poolp;
+extern struct task_struct * oplockThread;
+
+static __u16 GlobalMid; /* multiplex id - rotating counter */
+
+/* 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
+_GetXid(void)
+{
+ unsigned int xid;
+
+ spin_lock(&GlobalMid_Lock);
+ GlobalTotalActiveXid++;
+ if (GlobalTotalActiveXid > GlobalMaxActiveXid)
+ GlobalMaxActiveXid = GlobalTotalActiveXid; /* keep high water mark for number of simultaneous vfs ops in our filesystem */
+ xid = GlobalCurrentXid++;
+ spin_unlock(&GlobalMid_Lock);
+ return xid;
+}
+
+void
+_FreeXid(unsigned int xid)
+{
+ spin_lock(&GlobalMid_Lock);
+ /* if(GlobalTotalActiveXid == 0)
+ BUG(); */
+ GlobalTotalActiveXid--;
+ spin_unlock(&GlobalMid_Lock);
+}
+
+struct cifsSesInfo *
+sesInfoAlloc(void)
+{
+ struct cifsSesInfo *ret_buf;
+
+ ret_buf =
+ (struct cifsSesInfo *) kmalloc(sizeof (struct cifsSesInfo),
+ GFP_KERNEL);
+ if (ret_buf) {
+ memset(ret_buf, 0, sizeof (struct cifsSesInfo));
+ write_lock(&GlobalSMBSeslock);
+ atomic_inc(&sesInfoAllocCount);
+ ret_buf->status = CifsNew;
+ list_add(&ret_buf->cifsSessionList, &GlobalSMBSessionList);
+ init_MUTEX(&ret_buf->sesSem);
+ write_unlock(&GlobalSMBSeslock);
+ }
+ return ret_buf;
+}
+
+void
+sesInfoFree(struct cifsSesInfo *buf_to_free)
+{
+ if (buf_to_free == NULL) {
+ cFYI(1, ("Null buffer passed to sesInfoFree"));
+ return;
+ }
+
+ write_lock(&GlobalSMBSeslock);
+ atomic_dec(&sesInfoAllocCount);
+ list_del(&buf_to_free->cifsSessionList);
+ write_unlock(&GlobalSMBSeslock);
+ if (buf_to_free->serverOS)
+ kfree(buf_to_free->serverOS);
+ if (buf_to_free->serverDomain)
+ kfree(buf_to_free->serverDomain);
+ if (buf_to_free->serverNOS)
+ kfree(buf_to_free->serverNOS);
+ if (buf_to_free->password)
+ kfree(buf_to_free->password);
+ kfree(buf_to_free);
+}
+
+struct cifsTconInfo *
+tconInfoAlloc(void)
+{
+ struct cifsTconInfo *ret_buf;
+ ret_buf =
+ (struct cifsTconInfo *) kmalloc(sizeof (struct cifsTconInfo),
+ GFP_KERNEL);
+ if (ret_buf) {
+ memset(ret_buf, 0, sizeof (struct cifsTconInfo));
+ write_lock(&GlobalSMBSeslock);
+ atomic_inc(&tconInfoAllocCount);
+ list_add(&ret_buf->cifsConnectionList,
+ &GlobalTreeConnectionList);
+ ret_buf->tidStatus = CifsNew;
+ INIT_LIST_HEAD(&ret_buf->openFileList);
+ init_MUTEX(&ret_buf->tconSem);
+#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)
+{
+ if (buf_to_free == NULL) {
+ cFYI(1, ("Null buffer passed to tconInfoFree"));
+ return;
+ }
+ write_lock(&GlobalSMBSeslock);
+ atomic_dec(&tconInfoAllocCount);
+ list_del(&buf_to_free->cifsConnectionList);
+ write_unlock(&GlobalSMBSeslock);
+ if (buf_to_free->nativeFileSystem)
+ kfree(buf_to_free->nativeFileSystem);
+ kfree(buf_to_free);
+}
+
+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, SLAB_KERNEL | SLAB_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);
+ atomic_inc(&bufAllocCount);
+ }
+
+ return ret_buf;
+}
+
+void
+cifs_buf_release(void *buf_to_free)
+{
+
+ if (buf_to_free == NULL) {
+ /* cFYI(1, ("Null buffer passed to cifs_buf_release"));*/
+ 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 =
+ (struct smb_hdr *) mempool_alloc(cifs_sm_req_poolp, SLAB_KERNEL | SLAB_NOFS);
+ if (ret_buf) {
+ /* No need to clear memory here, cleared in header assemble */
+ /* memset(ret_buf, 0, sizeof(struct smb_hdr) + 27);*/
+ atomic_inc(&smBufAllocCount);
+ }
+ return ret_buf;
+}
+
+void
+cifs_small_buf_release(void *buf_to_free)
+{
+
+ if (buf_to_free == NULL) {
+ cFYI(1, ("Null buffer passed to cifs_small_buf_release"));
+ return;
+ }
+ mempool_free(buf_to_free,cifs_sm_req_poolp);
+
+ atomic_dec(&smBufAllocCount);
+ return;
+}
+
+void
+header_assemble(struct smb_hdr *buffer, char smb_command /* command */ ,
+ const struct cifsTconInfo *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,MAX_CIFS_HDR_SIZE);
+
+ buffer->smb_buf_length =
+ (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 */
+
+ 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));
+ spin_lock(&GlobalMid_Lock);
+ GlobalMid++;
+ buffer->Mid = GlobalMid;
+ spin_unlock(&GlobalMid_Lock);
+ 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;
+ }
+
+ buffer->Uid = treeCon->ses->Suid; /* always in LE format */
+ 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->uid != 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->uid) {
+ 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 smb sess with this server does not exist"));
+ }
+ }
+ }
+ read_unlock(&GlobalSMBSeslock);
+ }
+ }
+ }
+ if (treeCon->Flags & SMB_SHARE_IS_IN_DFS)
+ buffer->Flags2 |= SMBFLG2_DFS;
+ if((treeCon->ses) && (treeCon->ses->server))
+ if(treeCon->ses->server->secMode &
+ (SECMODE_SIGN_REQUIRED | SECMODE_SIGN_ENABLED))
+ buffer->Flags2 |= SMBFLG2_SECURITY_SIGNATURE;
+ }
+
+/* endian conversion of flags is now done just before sending */
+ buffer->WordCount = (char) word_count;
+ return;
+}
+
+int
+checkSMBhdr(struct smb_hdr *smb, __u16 mid)
+{
+ /* 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, ("Rcvd 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"));
+ }
+ cERROR(1, ("bad smb detected. The Mid=%d", smb->Mid));
+ return 1;
+}
+
+int
+checkSMB(struct smb_hdr *smb, __u16 mid, int length)
+{
+ __u32 len = be32_to_cpu(smb->smb_buf_length);
+ cFYI(0,
+ ("Entering checkSMB with Length: %x, smb_buf_length: %x ",
+ length, len));
+ if (((unsigned int)length < 2 + sizeof (struct smb_hdr)) ||
+ (len > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4)) {
+ if ((unsigned int)length < 2 + sizeof (struct smb_hdr)) {
+ if (((unsigned int)length >=
+ sizeof (struct smb_hdr) - 1)
+ && (smb->Status.CifsError != 0)) {
+ smb->WordCount = 0;
+ return 0; /* some error cases do not return wct and bcc */
+ } else {
+ cERROR(1, ("Length less than smb header size"));
+ }
+
+ }
+ if (len > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4)
+ cERROR(1,
+ ("smb_buf_length greater than MaxBufSize"));
+ cERROR(1,
+ ("bad smb detected. Illegal length. The mid=%d",
+ smb->Mid));
+ return 1;
+ }
+
+ if (checkSMBhdr(smb, mid))
+ return 1;
+
+ if ((4 + len != smbCalcSize(smb))
+ || (4 + len != (unsigned int)length)) {
+ return 0;
+ } else {
+ cERROR(1, ("smbCalcSize %x ", smbCalcSize(smb)));
+ cERROR(1,
+ ("bad smb size detected. The Mid=%d", smb->Mid));
+ return 1;
+ }
+}
+int
+is_valid_oplock_break(struct smb_hdr *buf)
+{
+ struct smb_com_lock_req * pSMB = (struct smb_com_lock_req *)buf;
+ struct list_head *tmp;
+ struct list_head *tmp1;
+ struct cifsTconInfo *tcon;
+ struct cifsFileInfo *netfile;
+
+ cFYI(1,("Checking for oplock break or dnotify response"));
+ 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)) {
+ data_offset = le32_to_cpu(pSMBr->DataOffset);
+
+ pnotify = (struct file_notify_information *)((char *)&pSMBr->hdr.Protocol
+ + data_offset);
+ cFYI(1,("dnotify on %s with action: 0x%x",pnotify->FileName,
+ pnotify->Action)); /* BB removeme BB */
+ /* cifs_dump_mem("Received notify Data is: ",buf,sizeof(struct smb_hdr)+60); */
+ return TRUE;
+ }
+ if(pSMBr->hdr.Status.CifsError) {
+ cFYI(1,("notify err 0x%d",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)) {
+ cFYI(1,("invalid handle on oplock break"));
+ 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;
+
+ cFYI(1,(" oplock type 0x%d level 0x%d",pSMB->LockType,pSMB->OplockLevel));
+ if(!(pSMB->LockType & LOCKING_ANDX_OPLOCK_RELEASE))
+ 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) {
+#ifdef CONFIG_CIFS_STATS
+ atomic_inc(&tcon->num_oplock_brks);
+#endif
+ list_for_each(tmp1,&tcon->openFileList){
+ netfile = list_entry(tmp1,struct cifsFileInfo,tlist);
+ if(pSMB->Fid == netfile->netfid) {
+ struct cifsInodeInfo *pCifsInode;
+ read_unlock(&GlobalSMBSeslock);
+ cFYI(1,("Matching file id, processing 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 thd"));
+ wake_up_process(oplockThread);
+ return TRUE;
+ }
+ }
+ read_unlock(&GlobalSMBSeslock);
+ cFYI(1,("No matching file for oplock break on connection"));
+ return TRUE;
+ }
+ }
+ read_unlock(&GlobalSMBSeslock);
+ cFYI(1,("Can not process oplock break for non-existent connection"));
+ return TRUE;
+}
+
+void
+dump_smb(struct smb_hdr *smb_buf, int smb_buf_length)
+{
+ int i, j;
+ char debug_line[17];
+ unsigned char *buffer;
+
+ if (traceSMB == 0)
+ return;
+
+ buffer = (unsigned char *) smb_buf;
+ for (i = 0, j = 0; i < smb_buf_length; i++, j++) {
+ if (i % 8 == 0) { /* we have reached the beginning of line */
+ printk(KERN_DEBUG "| ");
+ j = 0;
+ }
+ printk("%0#4x ", buffer[i]);
+ debug_line[2 * j] = ' ';
+ if (isprint(buffer[i]))
+ debug_line[1 + (2 * j)] = buffer[i];
+ else
+ debug_line[1 + (2 * j)] = '_';
+
+ if (i % 8 == 7) { /* we have reached end of line, time to print ascii */
+ debug_line[16] = 0;
+ printk(" | %s\n", debug_line);
+ }
+ }
+ for (; j < 8; j++) {
+ printk(" ");
+ debug_line[2 * j] = ' ';
+ debug_line[1 + (2 * j)] = ' ';
+ }
+ printk( " | %s\n", debug_line);
+ return;
+}