| /* |
| * fs/cifs/cifssmb.c |
| * |
| * Copyright (C) International Business Machines Corp., 2002,2010 |
| * Author(s): Steve French (sfrench@us.ibm.com) |
| * |
| * Contains the routines for constructing the SMB PDUs themselves |
| * |
| * 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 |
| */ |
| |
| /* SMB/CIFS PDU handling routines here - except for leftovers in connect.c */ |
| /* These are mostly routines that operate on a pathname, or on a tree id */ |
| /* (mounted volume), but there are eight handle based routines which must be */ |
| /* treated slightly differently for reconnection purposes since we never */ |
| /* want to reuse a stale file handle and only the caller knows the file info */ |
| |
| #include <linux/fs.h> |
| #include <linux/kernel.h> |
| #include <linux/vfs.h> |
| #include <linux/slab.h> |
| #include <linux/posix_acl_xattr.h> |
| #include <linux/pagemap.h> |
| #include <linux/swap.h> |
| #include <linux/task_io_accounting_ops.h> |
| #include <linux/uaccess.h> |
| #include "cifspdu.h" |
| #include "cifsglob.h" |
| #include "cifsacl.h" |
| #include "cifsproto.h" |
| #include "cifs_unicode.h" |
| #include "cifs_debug.h" |
| #include "fscache.h" |
| |
| #ifdef CONFIG_CIFS_POSIX |
| static struct { |
| int index; |
| char *name; |
| } protocols[] = { |
| #ifdef CONFIG_CIFS_WEAK_PW_HASH |
| {LANMAN_PROT, "\2LM1.2X002"}, |
| {LANMAN2_PROT, "\2LANMAN2.1"}, |
| #endif /* weak password hashing for legacy clients */ |
| {CIFS_PROT, "\2NT LM 0.12"}, |
| {POSIX_PROT, "\2POSIX 2"}, |
| {BAD_PROT, "\2"} |
| }; |
| #else |
| static struct { |
| int index; |
| char *name; |
| } protocols[] = { |
| #ifdef CONFIG_CIFS_WEAK_PW_HASH |
| {LANMAN_PROT, "\2LM1.2X002"}, |
| {LANMAN2_PROT, "\2LANMAN2.1"}, |
| #endif /* weak password hashing for legacy clients */ |
| {CIFS_PROT, "\2NT LM 0.12"}, |
| {BAD_PROT, "\2"} |
| }; |
| #endif |
| |
| /* define the number of elements in the cifs dialect array */ |
| #ifdef CONFIG_CIFS_POSIX |
| #ifdef CONFIG_CIFS_WEAK_PW_HASH |
| #define CIFS_NUM_PROT 4 |
| #else |
| #define CIFS_NUM_PROT 2 |
| #endif /* CIFS_WEAK_PW_HASH */ |
| #else /* not posix */ |
| #ifdef CONFIG_CIFS_WEAK_PW_HASH |
| #define CIFS_NUM_PROT 3 |
| #else |
| #define CIFS_NUM_PROT 1 |
| #endif /* CONFIG_CIFS_WEAK_PW_HASH */ |
| #endif /* CIFS_POSIX */ |
| |
| /* |
| * Mark as invalid, all open files on tree connections since they |
| * were closed when session to server was lost. |
| */ |
| void |
| cifs_mark_open_files_invalid(struct cifs_tcon *tcon) |
| { |
| struct cifsFileInfo *open_file = NULL; |
| struct list_head *tmp; |
| struct list_head *tmp1; |
| |
| /* list all files open on tree connection and mark them invalid */ |
| spin_lock(&tcon->open_file_lock); |
| list_for_each_safe(tmp, tmp1, &tcon->openFileList) { |
| open_file = list_entry(tmp, struct cifsFileInfo, tlist); |
| open_file->invalidHandle = true; |
| open_file->oplock_break_cancelled = true; |
| } |
| spin_unlock(&tcon->open_file_lock); |
| /* |
| * BB Add call to invalidate_inodes(sb) for all superblocks mounted |
| * to this tcon. |
| */ |
| } |
| |
| /* reconnect the socket, tcon, and smb session if needed */ |
| static int |
| cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command) |
| { |
| int rc; |
| struct cifs_ses *ses; |
| struct TCP_Server_Info *server; |
| struct nls_table *nls_codepage; |
| |
| /* |
| * SMBs NegProt, SessSetup, uLogoff do not have tcon yet so check for |
| * tcp and smb session status done differently for those three - in the |
| * calling routine |
| */ |
| if (!tcon) |
| return 0; |
| |
| ses = tcon->ses; |
| server = ses->server; |
| |
| /* |
| * only tree disconnect, open, and write, (and ulogoff which does not |
| * have tcon) are allowed as we start force umount |
| */ |
| if (tcon->tidStatus == CifsExiting) { |
| if (smb_command != SMB_COM_WRITE_ANDX && |
| smb_command != SMB_COM_OPEN_ANDX && |
| smb_command != SMB_COM_TREE_DISCONNECT) { |
| cifs_dbg(FYI, "can not send cmd %d while umounting\n", |
| smb_command); |
| return -ENODEV; |
| } |
| } |
| |
| /* |
| * Give demultiplex thread up to 10 seconds to reconnect, should be |
| * greater than cifs socket timeout which is 7 seconds |
| */ |
| while (server->tcpStatus == CifsNeedReconnect) { |
| wait_event_interruptible_timeout(server->response_q, |
| (server->tcpStatus != CifsNeedReconnect), 10 * HZ); |
| |
| /* are we still trying to reconnect? */ |
| if (server->tcpStatus != CifsNeedReconnect) |
| break; |
| |
| /* |
| * on "soft" mounts we wait once. Hard mounts keep |
| * retrying until process is killed or server comes |
| * back on-line |
| */ |
| if (!tcon->retry) { |
| cifs_dbg(FYI, "gave up waiting on reconnect in smb_init\n"); |
| return -EHOSTDOWN; |
| } |
| } |
| |
| if (!ses->need_reconnect && !tcon->need_reconnect) |
| return 0; |
| |
| nls_codepage = load_nls_default(); |
| |
| /* |
| * need to prevent multiple threads trying to simultaneously |
| * reconnect the same SMB session |
| */ |
| mutex_lock(&ses->session_mutex); |
| |
| /* |
| * Recheck after acquire mutex. If another thread is negotiating |
| * and the server never sends an answer the socket will be closed |
| * and tcpStatus set to reconnect. |
| */ |
| if (server->tcpStatus == CifsNeedReconnect) { |
| rc = -EHOSTDOWN; |
| mutex_unlock(&ses->session_mutex); |
| goto out; |
| } |
| |
| rc = cifs_negotiate_protocol(0, ses); |
| if (rc == 0 && ses->need_reconnect) |
| rc = cifs_setup_session(0, ses, nls_codepage); |
| |
| /* do we need to reconnect tcon? */ |
| if (rc || !tcon->need_reconnect) { |
| mutex_unlock(&ses->session_mutex); |
| goto out; |
| } |
| |
| cifs_mark_open_files_invalid(tcon); |
| rc = CIFSTCon(0, ses, tcon->treeName, tcon, nls_codepage); |
| mutex_unlock(&ses->session_mutex); |
| cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc); |
| |
| if (rc) |
| goto out; |
| |
| atomic_inc(&tconInfoReconnectCount); |
| |
| /* tell server Unix caps we support */ |
| if (ses->capabilities & CAP_UNIX) |
| reset_cifs_unix_caps(0, tcon, NULL, NULL); |
| |
| /* |
| * Removed call to reopen open files here. It is safer (and faster) to |
| * reopen files one at a time as needed in read and write. |
| * |
| * FIXME: what about file locks? don't we need to reclaim them ASAP? |
| */ |
| |
| out: |
| /* |
| * Check if handle based operation so we know whether we can continue |
| * or not without returning to caller to reset file handle |
| */ |
| switch (smb_command) { |
| case SMB_COM_READ_ANDX: |
| case SMB_COM_WRITE_ANDX: |
| case SMB_COM_CLOSE: |
| case SMB_COM_FIND_CLOSE2: |
| case SMB_COM_LOCKING_ANDX: |
| rc = -EAGAIN; |
| } |
| |
| unload_nls(nls_codepage); |
| return rc; |
| } |
| |
| /* Allocate and return pointer to an SMB request buffer, and set basic |
| SMB information in the SMB header. If the return code is zero, this |
| function must have filled in request_buf pointer */ |
| static int |
| small_smb_init(int smb_command, int wct, struct cifs_tcon *tcon, |
| void **request_buf) |
| { |
| int rc; |
| |
| rc = cifs_reconnect_tcon(tcon, smb_command); |
| if (rc) |
| return rc; |
| |
| *request_buf = cifs_small_buf_get(); |
| if (*request_buf == NULL) { |
| /* BB should we add a retry in here if not a writepage? */ |
| return -ENOMEM; |
| } |
| |
| header_assemble((struct smb_hdr *) *request_buf, smb_command, |
| tcon, wct); |
| |
| if (tcon != NULL) |
| cifs_stats_inc(&tcon->num_smbs_sent); |
| |
| return 0; |
| } |
| |
| int |
| small_smb_init_no_tc(const int smb_command, const int wct, |
| struct cifs_ses *ses, void **request_buf) |
| { |
| int rc; |
| struct smb_hdr *buffer; |
| |
| rc = small_smb_init(smb_command, wct, NULL, request_buf); |
| if (rc) |
| return rc; |
| |
| buffer = (struct smb_hdr *)*request_buf; |
| buffer->Mid = get_next_mid(ses->server); |
| if (ses->capabilities & CAP_UNICODE) |
| buffer->Flags2 |= SMBFLG2_UNICODE; |
| if (ses->capabilities & CAP_STATUS32) |
| buffer->Flags2 |= SMBFLG2_ERR_STATUS; |
| |
| /* uid, tid can stay at zero as set in header assemble */ |
| |
| /* BB add support for turning on the signing when |
| this function is used after 1st of session setup requests */ |
| |
| return rc; |
| } |
| |
| /* If the return code is zero, this function must fill in request_buf pointer */ |
| static int |
| __smb_init(int smb_command, int wct, struct cifs_tcon *tcon, |
| void **request_buf, void **response_buf) |
| { |
| *request_buf = cifs_buf_get(); |
| if (*request_buf == NULL) { |
| /* BB should we add a retry in here if not a writepage? */ |
| return -ENOMEM; |
| } |
| /* Although the original thought was we needed the response buf for */ |
| /* potential retries of smb operations it turns out we can determine */ |
| /* from the mid flags when the request buffer can be resent without */ |
| /* having to use a second distinct buffer for the response */ |
| if (response_buf) |
| *response_buf = *request_buf; |
| |
| header_assemble((struct smb_hdr *) *request_buf, smb_command, tcon, |
| wct); |
| |
| if (tcon != NULL) |
| cifs_stats_inc(&tcon->num_smbs_sent); |
| |
| return 0; |
| } |
| |
| /* If the return code is zero, this function must fill in request_buf pointer */ |
| static int |
| smb_init(int smb_command, int wct, struct cifs_tcon *tcon, |
| void **request_buf, void **response_buf) |
| { |
| int rc; |
| |
| rc = cifs_reconnect_tcon(tcon, smb_command); |
| if (rc) |
| return rc; |
| |
| return __smb_init(smb_command, wct, tcon, request_buf, response_buf); |
| } |
| |
| static int |
| smb_init_no_reconnect(int smb_command, int wct, struct cifs_tcon *tcon, |
| void **request_buf, void **response_buf) |
| { |
| if (tcon->ses->need_reconnect || tcon->need_reconnect) |
| return -EHOSTDOWN; |
| |
| return __smb_init(smb_command, wct, tcon, request_buf, response_buf); |
| } |
| |
| static int validate_t2(struct smb_t2_rsp *pSMB) |
| { |
| unsigned int total_size; |
| |
| /* check for plausible wct */ |
| if (pSMB->hdr.WordCount < 10) |
| goto vt2_err; |
| |
| /* check for parm and data offset going beyond end of smb */ |
| if (get_unaligned_le16(&pSMB->t2_rsp.ParameterOffset) > 1024 || |
| get_unaligned_le16(&pSMB->t2_rsp.DataOffset) > 1024) |
| goto vt2_err; |
| |
| total_size = get_unaligned_le16(&pSMB->t2_rsp.ParameterCount); |
| if (total_size >= 512) |
| goto vt2_err; |
| |
| /* check that bcc is at least as big as parms + data, and that it is |
| * less than negotiated smb buffer |
| */ |
| total_size += get_unaligned_le16(&pSMB->t2_rsp.DataCount); |
| if (total_size > get_bcc(&pSMB->hdr) || |
| total_size >= CIFSMaxBufSize + MAX_CIFS_HDR_SIZE) |
| goto vt2_err; |
| |
| return 0; |
| vt2_err: |
| cifs_dump_mem("Invalid transact2 SMB: ", (char *)pSMB, |
| sizeof(struct smb_t2_rsp) + 16); |
| return -EINVAL; |
| } |
| |
| static int |
| decode_ext_sec_blob(struct cifs_ses *ses, NEGOTIATE_RSP *pSMBr) |
| { |
| int rc = 0; |
| u16 count; |
| char *guid = pSMBr->u.extended_response.GUID; |
| struct TCP_Server_Info *server = ses->server; |
| |
| count = get_bcc(&pSMBr->hdr); |
| if (count < SMB1_CLIENT_GUID_SIZE) |
| return -EIO; |
| |
| spin_lock(&cifs_tcp_ses_lock); |
| if (server->srv_count > 1) { |
| spin_unlock(&cifs_tcp_ses_lock); |
| if (memcmp(server->server_GUID, guid, SMB1_CLIENT_GUID_SIZE) != 0) { |
| cifs_dbg(FYI, "server UID changed\n"); |
| memcpy(server->server_GUID, guid, SMB1_CLIENT_GUID_SIZE); |
| } |
| } else { |
| spin_unlock(&cifs_tcp_ses_lock); |
| memcpy(server->server_GUID, guid, SMB1_CLIENT_GUID_SIZE); |
| } |
| |
| if (count == SMB1_CLIENT_GUID_SIZE) { |
| server->sec_ntlmssp = true; |
| } else { |
| count -= SMB1_CLIENT_GUID_SIZE; |
| rc = decode_negTokenInit( |
| pSMBr->u.extended_response.SecurityBlob, count, server); |
| if (rc != 1) |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| int |
| cifs_enable_signing(struct TCP_Server_Info *server, bool mnt_sign_required) |
| { |
| bool srv_sign_required = server->sec_mode & server->vals->signing_required; |
| bool srv_sign_enabled = server->sec_mode & server->vals->signing_enabled; |
| bool mnt_sign_enabled = global_secflags & CIFSSEC_MAY_SIGN; |
| |
| /* |
| * Is signing required by mnt options? If not then check |
| * global_secflags to see if it is there. |
| */ |
| if (!mnt_sign_required) |
| mnt_sign_required = ((global_secflags & CIFSSEC_MUST_SIGN) == |
| CIFSSEC_MUST_SIGN); |
| |
| /* |
| * If signing is required then it's automatically enabled too, |
| * otherwise, check to see if the secflags allow it. |
| */ |
| mnt_sign_enabled = mnt_sign_required ? mnt_sign_required : |
| (global_secflags & CIFSSEC_MAY_SIGN); |
| |
| /* If server requires signing, does client allow it? */ |
| if (srv_sign_required) { |
| if (!mnt_sign_enabled) { |
| cifs_dbg(VFS, "Server requires signing, but it's disabled in SecurityFlags!"); |
| return -ENOTSUPP; |
| } |
| server->sign = true; |
| } |
| |
| /* If client requires signing, does server allow it? */ |
| if (mnt_sign_required) { |
| if (!srv_sign_enabled) { |
| cifs_dbg(VFS, "Server does not support signing!"); |
| return -ENOTSUPP; |
| } |
| server->sign = true; |
| } |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_CIFS_WEAK_PW_HASH |
| static int |
| decode_lanman_negprot_rsp(struct TCP_Server_Info *server, NEGOTIATE_RSP *pSMBr) |
| { |
| __s16 tmp; |
| struct lanman_neg_rsp *rsp = (struct lanman_neg_rsp *)pSMBr; |
| |
| if (server->dialect != LANMAN_PROT && server->dialect != LANMAN2_PROT) |
| return -EOPNOTSUPP; |
| |
| server->sec_mode = le16_to_cpu(rsp->SecurityMode); |
| server->maxReq = min_t(unsigned int, |
| le16_to_cpu(rsp->MaxMpxCount), |
| cifs_max_pending); |
| set_credits(server, server->maxReq); |
| server->maxBuf = le16_to_cpu(rsp->MaxBufSize); |
| /* even though we do not use raw we might as well set this |
| accurately, in case we ever find a need for it */ |
| if ((le16_to_cpu(rsp->RawMode) & RAW_ENABLE) == RAW_ENABLE) { |
| server->max_rw = 0xFF00; |
| server->capabilities = CAP_MPX_MODE | CAP_RAW_MODE; |
| } else { |
| server->max_rw = 0;/* do not need to use raw anyway */ |
| server->capabilities = CAP_MPX_MODE; |
| } |
| tmp = (__s16)le16_to_cpu(rsp->ServerTimeZone); |
| if (tmp == -1) { |
| /* OS/2 often does not set timezone therefore |
| * we must use server time to calc time zone. |
| * Could deviate slightly from the right zone. |
| * Smallest defined timezone difference is 15 minutes |
| * (i.e. Nepal). Rounding up/down is done to match |
| * this requirement. |
| */ |
| int val, seconds, remain, result; |
| struct timespec ts; |
| unsigned long utc = ktime_get_real_seconds(); |
| ts = cnvrtDosUnixTm(rsp->SrvTime.Date, |
| rsp->SrvTime.Time, 0); |
| cifs_dbg(FYI, "SrvTime %d sec since 1970 (utc: %d) diff: %d\n", |
| (int)ts.tv_sec, (int)utc, |
| (int)(utc - ts.tv_sec)); |
| val = (int)(utc - ts.tv_sec); |
| seconds = abs(val); |
| result = (seconds / MIN_TZ_ADJ) * MIN_TZ_ADJ; |
| remain = seconds % MIN_TZ_ADJ; |
| if (remain >= (MIN_TZ_ADJ / 2)) |
| result += MIN_TZ_ADJ; |
| if (val < 0) |
| result = -result; |
| server->timeAdj = result; |
| } else { |
| server->timeAdj = (int)tmp; |
| server->timeAdj *= 60; /* also in seconds */ |
| } |
| cifs_dbg(FYI, "server->timeAdj: %d seconds\n", server->timeAdj); |
| |
| |
| /* BB get server time for time conversions and add |
| code to use it and timezone since this is not UTC */ |
| |
| if (rsp->EncryptionKeyLength == |
| cpu_to_le16(CIFS_CRYPTO_KEY_SIZE)) { |
| memcpy(server->cryptkey, rsp->EncryptionKey, |
| CIFS_CRYPTO_KEY_SIZE); |
| } else if (server->sec_mode & SECMODE_PW_ENCRYPT) { |
| return -EIO; /* need cryptkey unless plain text */ |
| } |
| |
| cifs_dbg(FYI, "LANMAN negotiated\n"); |
| return 0; |
| } |
| #else |
| static inline int |
| decode_lanman_negprot_rsp(struct TCP_Server_Info *server, NEGOTIATE_RSP *pSMBr) |
| { |
| cifs_dbg(VFS, "mount failed, cifs module not built with CIFS_WEAK_PW_HASH support\n"); |
| return -EOPNOTSUPP; |
| } |
| #endif |
| |
| static bool |
| should_set_ext_sec_flag(enum securityEnum sectype) |
| { |
| switch (sectype) { |
| case RawNTLMSSP: |
| case Kerberos: |
| return true; |
| case Unspecified: |
| if (global_secflags & |
| (CIFSSEC_MAY_KRB5 | CIFSSEC_MAY_NTLMSSP)) |
| return true; |
| /* Fallthrough */ |
| default: |
| return false; |
| } |
| } |
| |
| int |
| CIFSSMBNegotiate(const unsigned int xid, struct cifs_ses *ses) |
| { |
| NEGOTIATE_REQ *pSMB; |
| NEGOTIATE_RSP *pSMBr; |
| int rc = 0; |
| int bytes_returned; |
| int i; |
| struct TCP_Server_Info *server = ses->server; |
| u16 count; |
| |
| if (!server) { |
| WARN(1, "%s: server is NULL!\n", __func__); |
| return -EIO; |
| } |
| |
| rc = smb_init(SMB_COM_NEGOTIATE, 0, NULL /* no tcon yet */ , |
| (void **) &pSMB, (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| pSMB->hdr.Mid = get_next_mid(server); |
| pSMB->hdr.Flags2 |= (SMBFLG2_UNICODE | SMBFLG2_ERR_STATUS); |
| |
| if (should_set_ext_sec_flag(ses->sectype)) { |
| cifs_dbg(FYI, "Requesting extended security."); |
| pSMB->hdr.Flags2 |= SMBFLG2_EXT_SEC; |
| } |
| |
| count = 0; |
| for (i = 0; i < CIFS_NUM_PROT; i++) { |
| strncpy(pSMB->DialectsArray+count, protocols[i].name, 16); |
| count += strlen(protocols[i].name) + 1; |
| /* null at end of source and target buffers anyway */ |
| } |
| inc_rfc1001_len(pSMB, count); |
| pSMB->ByteCount = cpu_to_le16(count); |
| |
| rc = SendReceive(xid, ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| if (rc != 0) |
| goto neg_err_exit; |
| |
| server->dialect = le16_to_cpu(pSMBr->DialectIndex); |
| cifs_dbg(FYI, "Dialect: %d\n", server->dialect); |
| /* Check wct = 1 error case */ |
| if ((pSMBr->hdr.WordCount < 13) || (server->dialect == BAD_PROT)) { |
| /* core returns wct = 1, but we do not ask for core - otherwise |
| small wct just comes when dialect index is -1 indicating we |
| could not negotiate a common dialect */ |
| rc = -EOPNOTSUPP; |
| goto neg_err_exit; |
| } else if (pSMBr->hdr.WordCount == 13) { |
| server->negflavor = CIFS_NEGFLAVOR_LANMAN; |
| rc = decode_lanman_negprot_rsp(server, pSMBr); |
| goto signing_check; |
| } else if (pSMBr->hdr.WordCount != 17) { |
| /* unknown wct */ |
| rc = -EOPNOTSUPP; |
| goto neg_err_exit; |
| } |
| /* else wct == 17, NTLM or better */ |
| |
| server->sec_mode = pSMBr->SecurityMode; |
| if ((server->sec_mode & SECMODE_USER) == 0) |
| cifs_dbg(FYI, "share mode security\n"); |
| |
| /* one byte, so no need to convert this or EncryptionKeyLen from |
| little endian */ |
| server->maxReq = min_t(unsigned int, le16_to_cpu(pSMBr->MaxMpxCount), |
| cifs_max_pending); |
| set_credits(server, server->maxReq); |
| /* probably no need to store and check maxvcs */ |
| server->maxBuf = le32_to_cpu(pSMBr->MaxBufferSize); |
| server->max_rw = le32_to_cpu(pSMBr->MaxRawSize); |
| cifs_dbg(NOISY, "Max buf = %d\n", ses->server->maxBuf); |
| server->capabilities = le32_to_cpu(pSMBr->Capabilities); |
| server->timeAdj = (int)(__s16)le16_to_cpu(pSMBr->ServerTimeZone); |
| server->timeAdj *= 60; |
| |
| if (pSMBr->EncryptionKeyLength == CIFS_CRYPTO_KEY_SIZE) { |
| server->negflavor = CIFS_NEGFLAVOR_UNENCAP; |
| memcpy(ses->server->cryptkey, pSMBr->u.EncryptionKey, |
| CIFS_CRYPTO_KEY_SIZE); |
| } else if (pSMBr->hdr.Flags2 & SMBFLG2_EXT_SEC || |
| server->capabilities & CAP_EXTENDED_SECURITY) { |
| server->negflavor = CIFS_NEGFLAVOR_EXTENDED; |
| rc = decode_ext_sec_blob(ses, pSMBr); |
| } else if (server->sec_mode & SECMODE_PW_ENCRYPT) { |
| rc = -EIO; /* no crypt key only if plain text pwd */ |
| } else { |
| server->negflavor = CIFS_NEGFLAVOR_UNENCAP; |
| server->capabilities &= ~CAP_EXTENDED_SECURITY; |
| } |
| |
| signing_check: |
| if (!rc) |
| rc = cifs_enable_signing(server, ses->sign); |
| neg_err_exit: |
| cifs_buf_release(pSMB); |
| |
| cifs_dbg(FYI, "negprot rc %d\n", rc); |
| return rc; |
| } |
| |
| int |
| CIFSSMBTDis(const unsigned int xid, struct cifs_tcon *tcon) |
| { |
| struct smb_hdr *smb_buffer; |
| int rc = 0; |
| |
| cifs_dbg(FYI, "In tree disconnect\n"); |
| |
| /* BB: do we need to check this? These should never be NULL. */ |
| if ((tcon->ses == NULL) || (tcon->ses->server == NULL)) |
| return -EIO; |
| |
| /* |
| * No need to return error on this operation if tid invalidated and |
| * closed on server already e.g. due to tcp session crashing. Also, |
| * the tcon is no longer on the list, so no need to take lock before |
| * checking this. |
| */ |
| if ((tcon->need_reconnect) || (tcon->ses->need_reconnect)) |
| return 0; |
| |
| rc = small_smb_init(SMB_COM_TREE_DISCONNECT, 0, tcon, |
| (void **)&smb_buffer); |
| if (rc) |
| return rc; |
| |
| rc = SendReceiveNoRsp(xid, tcon->ses, (char *)smb_buffer, 0); |
| cifs_small_buf_release(smb_buffer); |
| if (rc) |
| cifs_dbg(FYI, "Tree disconnect failed %d\n", rc); |
| |
| /* No need to return error on this operation if tid invalidated and |
| closed on server already e.g. due to tcp session crashing */ |
| if (rc == -EAGAIN) |
| rc = 0; |
| |
| return rc; |
| } |
| |
| /* |
| * This is a no-op for now. We're not really interested in the reply, but |
| * rather in the fact that the server sent one and that server->lstrp |
| * gets updated. |
| * |
| * FIXME: maybe we should consider checking that the reply matches request? |
| */ |
| static void |
| cifs_echo_callback(struct mid_q_entry *mid) |
| { |
| struct TCP_Server_Info *server = mid->callback_data; |
| |
| DeleteMidQEntry(mid); |
| add_credits(server, 1, CIFS_ECHO_OP); |
| } |
| |
| int |
| CIFSSMBEcho(struct TCP_Server_Info *server) |
| { |
| ECHO_REQ *smb; |
| int rc = 0; |
| struct kvec iov[2]; |
| struct smb_rqst rqst = { .rq_iov = iov, |
| .rq_nvec = 2 }; |
| |
| cifs_dbg(FYI, "In echo request\n"); |
| |
| rc = small_smb_init(SMB_COM_ECHO, 0, NULL, (void **)&smb); |
| if (rc) |
| return rc; |
| |
| if (server->capabilities & CAP_UNICODE) |
| smb->hdr.Flags2 |= SMBFLG2_UNICODE; |
| |
| /* set up echo request */ |
| smb->hdr.Tid = 0xffff; |
| smb->hdr.WordCount = 1; |
| put_unaligned_le16(1, &smb->EchoCount); |
| put_bcc(1, &smb->hdr); |
| smb->Data[0] = 'a'; |
| inc_rfc1001_len(smb, 3); |
| |
| iov[0].iov_len = 4; |
| iov[0].iov_base = smb; |
| iov[1].iov_len = get_rfc1002_length(smb); |
| iov[1].iov_base = (char *)smb + 4; |
| |
| rc = cifs_call_async(server, &rqst, NULL, cifs_echo_callback, NULL, |
| server, CIFS_ASYNC_OP | CIFS_ECHO_OP); |
| if (rc) |
| cifs_dbg(FYI, "Echo request failed: %d\n", rc); |
| |
| cifs_small_buf_release(smb); |
| |
| return rc; |
| } |
| |
| int |
| CIFSSMBLogoff(const unsigned int xid, struct cifs_ses *ses) |
| { |
| LOGOFF_ANDX_REQ *pSMB; |
| int rc = 0; |
| |
| cifs_dbg(FYI, "In SMBLogoff for session disconnect\n"); |
| |
| /* |
| * BB: do we need to check validity of ses and server? They should |
| * always be valid since we have an active reference. If not, that |
| * should probably be a BUG() |
| */ |
| if (!ses || !ses->server) |
| return -EIO; |
| |
| mutex_lock(&ses->session_mutex); |
| if (ses->need_reconnect) |
| goto session_already_dead; /* no need to send SMBlogoff if uid |
| already closed due to reconnect */ |
| rc = small_smb_init(SMB_COM_LOGOFF_ANDX, 2, NULL, (void **)&pSMB); |
| if (rc) { |
| mutex_unlock(&ses->session_mutex); |
| return rc; |
| } |
| |
| pSMB->hdr.Mid = get_next_mid(ses->server); |
| |
| if (ses->server->sign) |
| pSMB->hdr.Flags2 |= SMBFLG2_SECURITY_SIGNATURE; |
| |
| pSMB->hdr.Uid = ses->Suid; |
| |
| pSMB->AndXCommand = 0xFF; |
| rc = SendReceiveNoRsp(xid, ses, (char *) pSMB, 0); |
| cifs_small_buf_release(pSMB); |
| session_already_dead: |
| mutex_unlock(&ses->session_mutex); |
| |
| /* if session dead then we do not need to do ulogoff, |
| since server closed smb session, no sense reporting |
| error */ |
| if (rc == -EAGAIN) |
| rc = 0; |
| return rc; |
| } |
| |
| int |
| CIFSPOSIXDelFile(const unsigned int xid, struct cifs_tcon *tcon, |
| const char *fileName, __u16 type, |
| const struct nls_table *nls_codepage, int remap) |
| { |
| TRANSACTION2_SPI_REQ *pSMB = NULL; |
| TRANSACTION2_SPI_RSP *pSMBr = NULL; |
| struct unlink_psx_rq *pRqD; |
| int name_len; |
| int rc = 0; |
| int bytes_returned = 0; |
| __u16 params, param_offset, offset, byte_count; |
| |
| cifs_dbg(FYI, "In POSIX delete\n"); |
| PsxDelete: |
| rc = smb_init(SMB_COM_TRANSACTION2, 15, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = |
| cifsConvertToUTF16((__le16 *) pSMB->FileName, fileName, |
| PATH_MAX, nls_codepage, remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| } else { /* BB add path length overrun check */ |
| name_len = strnlen(fileName, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->FileName, fileName, name_len); |
| } |
| |
| params = 6 + name_len; |
| pSMB->MaxParameterCount = cpu_to_le16(2); |
| pSMB->MaxDataCount = 0; /* BB double check this with jra */ |
| pSMB->MaxSetupCount = 0; |
| pSMB->Reserved = 0; |
| pSMB->Flags = 0; |
| pSMB->Timeout = 0; |
| pSMB->Reserved2 = 0; |
| param_offset = offsetof(struct smb_com_transaction2_spi_req, |
| InformationLevel) - 4; |
| offset = param_offset + params; |
| |
| /* Setup pointer to Request Data (inode type) */ |
| pRqD = (struct unlink_psx_rq *)(((char *)&pSMB->hdr.Protocol) + offset); |
| pRqD->type = cpu_to_le16(type); |
| pSMB->ParameterOffset = cpu_to_le16(param_offset); |
| pSMB->DataOffset = cpu_to_le16(offset); |
| pSMB->SetupCount = 1; |
| pSMB->Reserved3 = 0; |
| pSMB->SubCommand = cpu_to_le16(TRANS2_SET_PATH_INFORMATION); |
| byte_count = 3 /* pad */ + params + sizeof(struct unlink_psx_rq); |
| |
| pSMB->DataCount = cpu_to_le16(sizeof(struct unlink_psx_rq)); |
| pSMB->TotalDataCount = cpu_to_le16(sizeof(struct unlink_psx_rq)); |
| pSMB->ParameterCount = cpu_to_le16(params); |
| pSMB->TotalParameterCount = pSMB->ParameterCount; |
| pSMB->InformationLevel = cpu_to_le16(SMB_POSIX_UNLINK); |
| pSMB->Reserved4 = 0; |
| inc_rfc1001_len(pSMB, byte_count); |
| pSMB->ByteCount = cpu_to_le16(byte_count); |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| if (rc) |
| cifs_dbg(FYI, "Posix delete returned %d\n", rc); |
| cifs_buf_release(pSMB); |
| |
| cifs_stats_inc(&tcon->stats.cifs_stats.num_deletes); |
| |
| if (rc == -EAGAIN) |
| goto PsxDelete; |
| |
| return rc; |
| } |
| |
| int |
| CIFSSMBDelFile(const unsigned int xid, struct cifs_tcon *tcon, const char *name, |
| struct cifs_sb_info *cifs_sb) |
| { |
| DELETE_FILE_REQ *pSMB = NULL; |
| DELETE_FILE_RSP *pSMBr = NULL; |
| int rc = 0; |
| int bytes_returned; |
| int name_len; |
| int remap = cifs_remap(cifs_sb); |
| |
| DelFileRetry: |
| rc = smb_init(SMB_COM_DELETE, 1, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = cifsConvertToUTF16((__le16 *) pSMB->fileName, name, |
| PATH_MAX, cifs_sb->local_nls, |
| remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| } else { /* BB improve check for buffer overruns BB */ |
| name_len = strnlen(name, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->fileName, name, name_len); |
| } |
| pSMB->SearchAttributes = |
| cpu_to_le16(ATTR_READONLY | ATTR_HIDDEN | ATTR_SYSTEM); |
| pSMB->BufferFormat = 0x04; |
| inc_rfc1001_len(pSMB, name_len + 1); |
| pSMB->ByteCount = cpu_to_le16(name_len + 1); |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| cifs_stats_inc(&tcon->stats.cifs_stats.num_deletes); |
| if (rc) |
| cifs_dbg(FYI, "Error in RMFile = %d\n", rc); |
| |
| cifs_buf_release(pSMB); |
| if (rc == -EAGAIN) |
| goto DelFileRetry; |
| |
| return rc; |
| } |
| |
| int |
| CIFSSMBRmDir(const unsigned int xid, struct cifs_tcon *tcon, const char *name, |
| struct cifs_sb_info *cifs_sb) |
| { |
| DELETE_DIRECTORY_REQ *pSMB = NULL; |
| DELETE_DIRECTORY_RSP *pSMBr = NULL; |
| int rc = 0; |
| int bytes_returned; |
| int name_len; |
| int remap = cifs_remap(cifs_sb); |
| |
| cifs_dbg(FYI, "In CIFSSMBRmDir\n"); |
| RmDirRetry: |
| rc = smb_init(SMB_COM_DELETE_DIRECTORY, 0, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = cifsConvertToUTF16((__le16 *) pSMB->DirName, name, |
| PATH_MAX, cifs_sb->local_nls, |
| remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| } else { /* BB improve check for buffer overruns BB */ |
| name_len = strnlen(name, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->DirName, name, name_len); |
| } |
| |
| pSMB->BufferFormat = 0x04; |
| inc_rfc1001_len(pSMB, name_len + 1); |
| pSMB->ByteCount = cpu_to_le16(name_len + 1); |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| cifs_stats_inc(&tcon->stats.cifs_stats.num_rmdirs); |
| if (rc) |
| cifs_dbg(FYI, "Error in RMDir = %d\n", rc); |
| |
| cifs_buf_release(pSMB); |
| if (rc == -EAGAIN) |
| goto RmDirRetry; |
| return rc; |
| } |
| |
| int |
| CIFSSMBMkDir(const unsigned int xid, struct cifs_tcon *tcon, const char *name, |
| struct cifs_sb_info *cifs_sb) |
| { |
| int rc = 0; |
| CREATE_DIRECTORY_REQ *pSMB = NULL; |
| CREATE_DIRECTORY_RSP *pSMBr = NULL; |
| int bytes_returned; |
| int name_len; |
| int remap = cifs_remap(cifs_sb); |
| |
| cifs_dbg(FYI, "In CIFSSMBMkDir\n"); |
| MkDirRetry: |
| rc = smb_init(SMB_COM_CREATE_DIRECTORY, 0, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = cifsConvertToUTF16((__le16 *) pSMB->DirName, name, |
| PATH_MAX, cifs_sb->local_nls, |
| remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| } else { /* BB improve check for buffer overruns BB */ |
| name_len = strnlen(name, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->DirName, name, name_len); |
| } |
| |
| pSMB->BufferFormat = 0x04; |
| inc_rfc1001_len(pSMB, name_len + 1); |
| pSMB->ByteCount = cpu_to_le16(name_len + 1); |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| cifs_stats_inc(&tcon->stats.cifs_stats.num_mkdirs); |
| if (rc) |
| cifs_dbg(FYI, "Error in Mkdir = %d\n", rc); |
| |
| cifs_buf_release(pSMB); |
| if (rc == -EAGAIN) |
| goto MkDirRetry; |
| return rc; |
| } |
| |
| int |
| CIFSPOSIXCreate(const unsigned int xid, struct cifs_tcon *tcon, |
| __u32 posix_flags, __u64 mode, __u16 *netfid, |
| FILE_UNIX_BASIC_INFO *pRetData, __u32 *pOplock, |
| const char *name, const struct nls_table *nls_codepage, |
| int remap) |
| { |
| TRANSACTION2_SPI_REQ *pSMB = NULL; |
| TRANSACTION2_SPI_RSP *pSMBr = NULL; |
| int name_len; |
| int rc = 0; |
| int bytes_returned = 0; |
| __u16 params, param_offset, offset, byte_count, count; |
| OPEN_PSX_REQ *pdata; |
| OPEN_PSX_RSP *psx_rsp; |
| |
| cifs_dbg(FYI, "In POSIX Create\n"); |
| PsxCreat: |
| rc = smb_init(SMB_COM_TRANSACTION2, 15, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = |
| cifsConvertToUTF16((__le16 *) pSMB->FileName, name, |
| PATH_MAX, nls_codepage, remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| } else { /* BB improve the check for buffer overruns BB */ |
| name_len = strnlen(name, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->FileName, name, name_len); |
| } |
| |
| params = 6 + name_len; |
| count = sizeof(OPEN_PSX_REQ); |
| pSMB->MaxParameterCount = cpu_to_le16(2); |
| pSMB->MaxDataCount = cpu_to_le16(1000); /* large enough */ |
| pSMB->MaxSetupCount = 0; |
| pSMB->Reserved = 0; |
| pSMB->Flags = 0; |
| pSMB->Timeout = 0; |
| pSMB->Reserved2 = 0; |
| param_offset = offsetof(struct smb_com_transaction2_spi_req, |
| InformationLevel) - 4; |
| offset = param_offset + params; |
| pdata = (OPEN_PSX_REQ *)(((char *)&pSMB->hdr.Protocol) + offset); |
| pdata->Level = cpu_to_le16(SMB_QUERY_FILE_UNIX_BASIC); |
| pdata->Permissions = cpu_to_le64(mode); |
| pdata->PosixOpenFlags = cpu_to_le32(posix_flags); |
| pdata->OpenFlags = cpu_to_le32(*pOplock); |
| pSMB->ParameterOffset = cpu_to_le16(param_offset); |
| pSMB->DataOffset = cpu_to_le16(offset); |
| pSMB->SetupCount = 1; |
| pSMB->Reserved3 = 0; |
| pSMB->SubCommand = cpu_to_le16(TRANS2_SET_PATH_INFORMATION); |
| byte_count = 3 /* pad */ + params + count; |
| |
| pSMB->DataCount = cpu_to_le16(count); |
| pSMB->ParameterCount = cpu_to_le16(params); |
| pSMB->TotalDataCount = pSMB->DataCount; |
| pSMB->TotalParameterCount = pSMB->ParameterCount; |
| pSMB->InformationLevel = cpu_to_le16(SMB_POSIX_OPEN); |
| pSMB->Reserved4 = 0; |
| inc_rfc1001_len(pSMB, byte_count); |
| pSMB->ByteCount = cpu_to_le16(byte_count); |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| if (rc) { |
| cifs_dbg(FYI, "Posix create returned %d\n", rc); |
| goto psx_create_err; |
| } |
| |
| cifs_dbg(FYI, "copying inode info\n"); |
| rc = validate_t2((struct smb_t2_rsp *)pSMBr); |
| |
| if (rc || get_bcc(&pSMBr->hdr) < sizeof(OPEN_PSX_RSP)) { |
| rc = -EIO; /* bad smb */ |
| goto psx_create_err; |
| } |
| |
| /* copy return information to pRetData */ |
| psx_rsp = (OPEN_PSX_RSP *)((char *) &pSMBr->hdr.Protocol |
| + le16_to_cpu(pSMBr->t2.DataOffset)); |
| |
| *pOplock = le16_to_cpu(psx_rsp->OplockFlags); |
| if (netfid) |
| *netfid = psx_rsp->Fid; /* cifs fid stays in le */ |
| /* Let caller know file was created so we can set the mode. */ |
| /* Do we care about the CreateAction in any other cases? */ |
| if (cpu_to_le32(FILE_CREATE) == psx_rsp->CreateAction) |
| *pOplock |= CIFS_CREATE_ACTION; |
| /* check to make sure response data is there */ |
| if (psx_rsp->ReturnedLevel != cpu_to_le16(SMB_QUERY_FILE_UNIX_BASIC)) { |
| pRetData->Type = cpu_to_le32(-1); /* unknown */ |
| cifs_dbg(NOISY, "unknown type\n"); |
| } else { |
| if (get_bcc(&pSMBr->hdr) < sizeof(OPEN_PSX_RSP) |
| + sizeof(FILE_UNIX_BASIC_INFO)) { |
| cifs_dbg(VFS, "Open response data too small\n"); |
| pRetData->Type = cpu_to_le32(-1); |
| goto psx_create_err; |
| } |
| memcpy((char *) pRetData, |
| (char *)psx_rsp + sizeof(OPEN_PSX_RSP), |
| sizeof(FILE_UNIX_BASIC_INFO)); |
| } |
| |
| psx_create_err: |
| cifs_buf_release(pSMB); |
| |
| if (posix_flags & SMB_O_DIRECTORY) |
| cifs_stats_inc(&tcon->stats.cifs_stats.num_posixmkdirs); |
| else |
| cifs_stats_inc(&tcon->stats.cifs_stats.num_posixopens); |
| |
| if (rc == -EAGAIN) |
| goto PsxCreat; |
| |
| return rc; |
| } |
| |
| static __u16 convert_disposition(int disposition) |
| { |
| __u16 ofun = 0; |
| |
| switch (disposition) { |
| case FILE_SUPERSEDE: |
| ofun = SMBOPEN_OCREATE | SMBOPEN_OTRUNC; |
| break; |
| case FILE_OPEN: |
| ofun = SMBOPEN_OAPPEND; |
| break; |
| case FILE_CREATE: |
| ofun = SMBOPEN_OCREATE; |
| break; |
| case FILE_OPEN_IF: |
| ofun = SMBOPEN_OCREATE | SMBOPEN_OAPPEND; |
| break; |
| case FILE_OVERWRITE: |
| ofun = SMBOPEN_OTRUNC; |
| break; |
| case FILE_OVERWRITE_IF: |
| ofun = SMBOPEN_OCREATE | SMBOPEN_OTRUNC; |
| break; |
| default: |
| cifs_dbg(FYI, "unknown disposition %d\n", disposition); |
| ofun = SMBOPEN_OAPPEND; /* regular open */ |
| } |
| return ofun; |
| } |
| |
| static int |
| access_flags_to_smbopen_mode(const int access_flags) |
| { |
| int masked_flags = access_flags & (GENERIC_READ | GENERIC_WRITE); |
| |
| if (masked_flags == GENERIC_READ) |
| return SMBOPEN_READ; |
| else if (masked_flags == GENERIC_WRITE) |
| return SMBOPEN_WRITE; |
| |
| /* just go for read/write */ |
| return SMBOPEN_READWRITE; |
| } |
| |
| int |
| SMBLegacyOpen(const unsigned int xid, struct cifs_tcon *tcon, |
| const char *fileName, const int openDisposition, |
| const int access_flags, const int create_options, __u16 *netfid, |
| int *pOplock, FILE_ALL_INFO *pfile_info, |
| const struct nls_table *nls_codepage, int remap) |
| { |
| int rc = -EACCES; |
| OPENX_REQ *pSMB = NULL; |
| OPENX_RSP *pSMBr = NULL; |
| int bytes_returned; |
| int name_len; |
| __u16 count; |
| |
| OldOpenRetry: |
| rc = smb_init(SMB_COM_OPEN_ANDX, 15, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| pSMB->AndXCommand = 0xFF; /* none */ |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| count = 1; /* account for one byte pad to word boundary */ |
| name_len = |
| cifsConvertToUTF16((__le16 *) (pSMB->fileName + 1), |
| fileName, PATH_MAX, nls_codepage, remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| } else { /* BB improve check for buffer overruns BB */ |
| count = 0; /* no pad */ |
| name_len = strnlen(fileName, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->fileName, fileName, name_len); |
| } |
| if (*pOplock & REQ_OPLOCK) |
| pSMB->OpenFlags = cpu_to_le16(REQ_OPLOCK); |
| else if (*pOplock & REQ_BATCHOPLOCK) |
| pSMB->OpenFlags = cpu_to_le16(REQ_BATCHOPLOCK); |
| |
| pSMB->OpenFlags |= cpu_to_le16(REQ_MORE_INFO); |
| pSMB->Mode = cpu_to_le16(access_flags_to_smbopen_mode(access_flags)); |
| pSMB->Mode |= cpu_to_le16(0x40); /* deny none */ |
| /* set file as system file if special file such |
| as fifo and server expecting SFU style and |
| no Unix extensions */ |
| |
| if (create_options & CREATE_OPTION_SPECIAL) |
| pSMB->FileAttributes = cpu_to_le16(ATTR_SYSTEM); |
| else /* BB FIXME BB */ |
| pSMB->FileAttributes = cpu_to_le16(0/*ATTR_NORMAL*/); |
| |
| if (create_options & CREATE_OPTION_READONLY) |
| pSMB->FileAttributes |= cpu_to_le16(ATTR_READONLY); |
| |
| /* BB FIXME BB */ |
| /* pSMB->CreateOptions = cpu_to_le32(create_options & |
| CREATE_OPTIONS_MASK); */ |
| /* BB FIXME END BB */ |
| |
| pSMB->Sattr = cpu_to_le16(ATTR_HIDDEN | ATTR_SYSTEM | ATTR_DIRECTORY); |
| pSMB->OpenFunction = cpu_to_le16(convert_disposition(openDisposition)); |
| count += name_len; |
| inc_rfc1001_len(pSMB, count); |
| |
| pSMB->ByteCount = cpu_to_le16(count); |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *)pSMBr, &bytes_returned, 0); |
| cifs_stats_inc(&tcon->stats.cifs_stats.num_opens); |
| if (rc) { |
| cifs_dbg(FYI, "Error in Open = %d\n", rc); |
| } else { |
| /* BB verify if wct == 15 */ |
| |
| /* *pOplock = pSMBr->OplockLevel; */ /* BB take from action field*/ |
| |
| *netfid = pSMBr->Fid; /* cifs fid stays in le */ |
| /* Let caller know file was created so we can set the mode. */ |
| /* Do we care about the CreateAction in any other cases? */ |
| /* BB FIXME BB */ |
| /* if (cpu_to_le32(FILE_CREATE) == pSMBr->CreateAction) |
| *pOplock |= CIFS_CREATE_ACTION; */ |
| /* BB FIXME END */ |
| |
| if (pfile_info) { |
| pfile_info->CreationTime = 0; /* BB convert CreateTime*/ |
| pfile_info->LastAccessTime = 0; /* BB fixme */ |
| pfile_info->LastWriteTime = 0; /* BB fixme */ |
| pfile_info->ChangeTime = 0; /* BB fixme */ |
| pfile_info->Attributes = |
| cpu_to_le32(le16_to_cpu(pSMBr->FileAttributes)); |
| /* the file_info buf is endian converted by caller */ |
| pfile_info->AllocationSize = |
| cpu_to_le64(le32_to_cpu(pSMBr->EndOfFile)); |
| pfile_info->EndOfFile = pfile_info->AllocationSize; |
| pfile_info->NumberOfLinks = cpu_to_le32(1); |
| pfile_info->DeletePending = 0; |
| } |
| } |
| |
| cifs_buf_release(pSMB); |
| if (rc == -EAGAIN) |
| goto OldOpenRetry; |
| return rc; |
| } |
| |
| int |
| CIFS_open(const unsigned int xid, struct cifs_open_parms *oparms, int *oplock, |
| FILE_ALL_INFO *buf) |
| { |
| int rc = -EACCES; |
| OPEN_REQ *req = NULL; |
| OPEN_RSP *rsp = NULL; |
| int bytes_returned; |
| int name_len; |
| __u16 count; |
| struct cifs_sb_info *cifs_sb = oparms->cifs_sb; |
| struct cifs_tcon *tcon = oparms->tcon; |
| int remap = cifs_remap(cifs_sb); |
| const struct nls_table *nls = cifs_sb->local_nls; |
| int create_options = oparms->create_options; |
| int desired_access = oparms->desired_access; |
| int disposition = oparms->disposition; |
| const char *path = oparms->path; |
| |
| openRetry: |
| rc = smb_init(SMB_COM_NT_CREATE_ANDX, 24, tcon, (void **)&req, |
| (void **)&rsp); |
| if (rc) |
| return rc; |
| |
| /* no commands go after this */ |
| req->AndXCommand = 0xFF; |
| |
| if (req->hdr.Flags2 & SMBFLG2_UNICODE) { |
| /* account for one byte pad to word boundary */ |
| count = 1; |
| name_len = cifsConvertToUTF16((__le16 *)(req->fileName + 1), |
| path, PATH_MAX, nls, remap); |
| /* trailing null */ |
| name_len++; |
| name_len *= 2; |
| req->NameLength = cpu_to_le16(name_len); |
| } else { |
| /* BB improve check for buffer overruns BB */ |
| /* no pad */ |
| count = 0; |
| name_len = strnlen(path, PATH_MAX); |
| /* trailing null */ |
| name_len++; |
| req->NameLength = cpu_to_le16(name_len); |
| strncpy(req->fileName, path, name_len); |
| } |
| |
| if (*oplock & REQ_OPLOCK) |
| req->OpenFlags = cpu_to_le32(REQ_OPLOCK); |
| else if (*oplock & REQ_BATCHOPLOCK) |
| req->OpenFlags = cpu_to_le32(REQ_BATCHOPLOCK); |
| |
| req->DesiredAccess = cpu_to_le32(desired_access); |
| req->AllocationSize = 0; |
| |
| /* |
| * Set file as system file if special file such as fifo and server |
| * expecting SFU style and no Unix extensions. |
| */ |
| if (create_options & CREATE_OPTION_SPECIAL) |
| req->FileAttributes = cpu_to_le32(ATTR_SYSTEM); |
| else |
| req->FileAttributes = cpu_to_le32(ATTR_NORMAL); |
| |
| /* |
| * XP does not handle ATTR_POSIX_SEMANTICS but it helps speed up case |
| * sensitive checks for other servers such as Samba. |
| */ |
| if (tcon->ses->capabilities & CAP_UNIX) |
| req->FileAttributes |= cpu_to_le32(ATTR_POSIX_SEMANTICS); |
| |
| if (create_options & CREATE_OPTION_READONLY) |
| req->FileAttributes |= cpu_to_le32(ATTR_READONLY); |
| |
| req->ShareAccess = cpu_to_le32(FILE_SHARE_ALL); |
| req->CreateDisposition = cpu_to_le32(disposition); |
| req->CreateOptions = cpu_to_le32(create_options & CREATE_OPTIONS_MASK); |
| |
| /* BB Expirement with various impersonation levels and verify */ |
| req->ImpersonationLevel = cpu_to_le32(SECURITY_IMPERSONATION); |
| req->SecurityFlags = SECURITY_CONTEXT_TRACKING|SECURITY_EFFECTIVE_ONLY; |
| |
| count += name_len; |
| inc_rfc1001_len(req, count); |
| |
| req->ByteCount = cpu_to_le16(count); |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *)req, |
| (struct smb_hdr *)rsp, &bytes_returned, 0); |
| cifs_stats_inc(&tcon->stats.cifs_stats.num_opens); |
| if (rc) { |
| cifs_dbg(FYI, "Error in Open = %d\n", rc); |
| cifs_buf_release(req); |
| if (rc == -EAGAIN) |
| goto openRetry; |
| return rc; |
| } |
| |
| /* 1 byte no need to le_to_cpu */ |
| *oplock = rsp->OplockLevel; |
| /* cifs fid stays in le */ |
| oparms->fid->netfid = rsp->Fid; |
| |
| /* Let caller know file was created so we can set the mode. */ |
| /* Do we care about the CreateAction in any other cases? */ |
| if (cpu_to_le32(FILE_CREATE) == rsp->CreateAction) |
| *oplock |= CIFS_CREATE_ACTION; |
| |
| if (buf) { |
| /* copy from CreationTime to Attributes */ |
| memcpy((char *)buf, (char *)&rsp->CreationTime, 36); |
| /* the file_info buf is endian converted by caller */ |
| buf->AllocationSize = rsp->AllocationSize; |
| buf->EndOfFile = rsp->EndOfFile; |
| buf->NumberOfLinks = cpu_to_le32(1); |
| buf->DeletePending = 0; |
| } |
| |
| cifs_buf_release(req); |
| return rc; |
| } |
| |
| /* |
| * Discard any remaining data in the current SMB. To do this, we borrow the |
| * current bigbuf. |
| */ |
| int |
| cifs_discard_remaining_data(struct TCP_Server_Info *server) |
| { |
| unsigned int rfclen = get_rfc1002_length(server->smallbuf); |
| int remaining = rfclen + 4 - server->total_read; |
| |
| while (remaining > 0) { |
| int length; |
| |
| length = cifs_read_from_socket(server, server->bigbuf, |
| min_t(unsigned int, remaining, |
| CIFSMaxBufSize + MAX_HEADER_SIZE(server))); |
| if (length < 0) |
| return length; |
| server->total_read += length; |
| remaining -= length; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| cifs_readv_discard(struct TCP_Server_Info *server, struct mid_q_entry *mid) |
| { |
| int length; |
| struct cifs_readdata *rdata = mid->callback_data; |
| |
| length = cifs_discard_remaining_data(server); |
| dequeue_mid(mid, rdata->result); |
| mid->resp_buf = server->smallbuf; |
| server->smallbuf = NULL; |
| return length; |
| } |
| |
| int |
| cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid) |
| { |
| int length, len; |
| unsigned int data_offset, data_len; |
| struct cifs_readdata *rdata = mid->callback_data; |
| char *buf = server->smallbuf; |
| unsigned int buflen = get_rfc1002_length(buf) + 4; |
| |
| cifs_dbg(FYI, "%s: mid=%llu offset=%llu bytes=%u\n", |
| __func__, mid->mid, rdata->offset, rdata->bytes); |
| |
| /* |
| * read the rest of READ_RSP header (sans Data array), or whatever we |
| * can if there's not enough data. At this point, we've read down to |
| * the Mid. |
| */ |
| len = min_t(unsigned int, buflen, server->vals->read_rsp_size) - |
| HEADER_SIZE(server) + 1; |
| |
| length = cifs_read_from_socket(server, |
| buf + HEADER_SIZE(server) - 1, len); |
| if (length < 0) |
| return length; |
| server->total_read += length; |
| |
| if (server->ops->is_session_expired && |
| server->ops->is_session_expired(buf)) { |
| cifs_reconnect(server); |
| wake_up(&server->response_q); |
| return -1; |
| } |
| |
| if (server->ops->is_status_pending && |
| server->ops->is_status_pending(buf, server, 0)) { |
| cifs_discard_remaining_data(server); |
| return -1; |
| } |
| |
| /* Was the SMB read successful? */ |
| rdata->result = server->ops->map_error(buf, false); |
| if (rdata->result != 0) { |
| cifs_dbg(FYI, "%s: server returned error %d\n", |
| __func__, rdata->result); |
| return cifs_readv_discard(server, mid); |
| } |
| |
| /* Is there enough to get to the rest of the READ_RSP header? */ |
| if (server->total_read < server->vals->read_rsp_size) { |
| cifs_dbg(FYI, "%s: server returned short header. got=%u expected=%zu\n", |
| __func__, server->total_read, |
| server->vals->read_rsp_size); |
| rdata->result = -EIO; |
| return cifs_readv_discard(server, mid); |
| } |
| |
| data_offset = server->ops->read_data_offset(buf) + 4; |
| if (data_offset < server->total_read) { |
| /* |
| * win2k8 sometimes sends an offset of 0 when the read |
| * is beyond the EOF. Treat it as if the data starts just after |
| * the header. |
| */ |
| cifs_dbg(FYI, "%s: data offset (%u) inside read response header\n", |
| __func__, data_offset); |
| data_offset = server->total_read; |
| } else if (data_offset > MAX_CIFS_SMALL_BUFFER_SIZE) { |
| /* data_offset is beyond the end of smallbuf */ |
| cifs_dbg(FYI, "%s: data offset (%u) beyond end of smallbuf\n", |
| __func__, data_offset); |
| rdata->result = -EIO; |
| return cifs_readv_discard(server, mid); |
| } |
| |
| cifs_dbg(FYI, "%s: total_read=%u data_offset=%u\n", |
| __func__, server->total_read, data_offset); |
| |
| len = data_offset - server->total_read; |
| if (len > 0) { |
| /* read any junk before data into the rest of smallbuf */ |
| length = cifs_read_from_socket(server, |
| buf + server->total_read, len); |
| if (length < 0) |
| return length; |
| server->total_read += length; |
| } |
| |
| /* set up first iov for signature check */ |
| rdata->iov[0].iov_base = buf; |
| rdata->iov[0].iov_len = 4; |
| rdata->iov[1].iov_base = buf + 4; |
| rdata->iov[1].iov_len = server->total_read - 4; |
| cifs_dbg(FYI, "0: iov_base=%p iov_len=%u\n", |
| rdata->iov[0].iov_base, server->total_read); |
| |
| /* how much data is in the response? */ |
| data_len = server->ops->read_data_length(buf); |
| if (data_offset + data_len > buflen) { |
| /* data_len is corrupt -- discard frame */ |
| rdata->result = -EIO; |
| return cifs_readv_discard(server, mid); |
| } |
| |
| length = rdata->read_into_pages(server, rdata, data_len); |
| if (length < 0) |
| return length; |
| |
| server->total_read += length; |
| |
| cifs_dbg(FYI, "total_read=%u buflen=%u remaining=%u\n", |
| server->total_read, buflen, data_len); |
| |
| /* discard anything left over */ |
| if (server->total_read < buflen) |
| return cifs_readv_discard(server, mid); |
| |
| dequeue_mid(mid, false); |
| mid->resp_buf = server->smallbuf; |
| server->smallbuf = NULL; |
| return length; |
| } |
| |
| static void |
| cifs_readv_callback(struct mid_q_entry *mid) |
| { |
| struct cifs_readdata *rdata = mid->callback_data; |
| struct cifs_tcon *tcon = tlink_tcon(rdata->cfile->tlink); |
| struct TCP_Server_Info *server = tcon->ses->server; |
| struct smb_rqst rqst = { .rq_iov = rdata->iov, |
| .rq_nvec = 2, |
| .rq_pages = rdata->pages, |
| .rq_npages = rdata->nr_pages, |
| .rq_pagesz = rdata->pagesz, |
| .rq_tailsz = rdata->tailsz }; |
| |
| cifs_dbg(FYI, "%s: mid=%llu state=%d result=%d bytes=%u\n", |
| __func__, mid->mid, mid->mid_state, rdata->result, |
| rdata->bytes); |
| |
| switch (mid->mid_state) { |
| case MID_RESPONSE_RECEIVED: |
| /* result already set, check signature */ |
| if (server->sign) { |
| int rc = 0; |
| |
| rc = cifs_verify_signature(&rqst, server, |
| mid->sequence_number); |
| if (rc) |
| cifs_dbg(VFS, "SMB signature verification returned error = %d\n", |
| rc); |
| } |
| /* FIXME: should this be counted toward the initiating task? */ |
| task_io_account_read(rdata->got_bytes); |
| cifs_stats_bytes_read(tcon, rdata->got_bytes); |
| break; |
| case MID_REQUEST_SUBMITTED: |
| case MID_RETRY_NEEDED: |
| rdata->result = -EAGAIN; |
| if (server->sign && rdata->got_bytes) |
| /* reset bytes number since we can not check a sign */ |
| rdata->got_bytes = 0; |
| /* FIXME: should this be counted toward the initiating task? */ |
| task_io_account_read(rdata->got_bytes); |
| cifs_stats_bytes_read(tcon, rdata->got_bytes); |
| break; |
| default: |
| rdata->result = -EIO; |
| } |
| |
| queue_work(cifsiod_wq, &rdata->work); |
| DeleteMidQEntry(mid); |
| add_credits(server, 1, 0); |
| } |
| |
| /* cifs_async_readv - send an async write, and set up mid to handle result */ |
| int |
| cifs_async_readv(struct cifs_readdata *rdata) |
| { |
| int rc; |
| READ_REQ *smb = NULL; |
| int wct; |
| struct cifs_tcon *tcon = tlink_tcon(rdata->cfile->tlink); |
| struct smb_rqst rqst = { .rq_iov = rdata->iov, |
| .rq_nvec = 2 }; |
| |
| cifs_dbg(FYI, "%s: offset=%llu bytes=%u\n", |
| __func__, rdata->offset, rdata->bytes); |
| |
| if (tcon->ses->capabilities & CAP_LARGE_FILES) |
| wct = 12; |
| else { |
| wct = 10; /* old style read */ |
| if ((rdata->offset >> 32) > 0) { |
| /* can not handle this big offset for old */ |
| return -EIO; |
| } |
| } |
| |
| rc = small_smb_init(SMB_COM_READ_ANDX, wct, tcon, (void **)&smb); |
| if (rc) |
| return rc; |
| |
| smb->hdr.Pid = cpu_to_le16((__u16)rdata->pid); |
| smb->hdr.PidHigh = cpu_to_le16((__u16)(rdata->pid >> 16)); |
| |
| smb->AndXCommand = 0xFF; /* none */ |
| smb->Fid = rdata->cfile->fid.netfid; |
| smb->OffsetLow = cpu_to_le32(rdata->offset & 0xFFFFFFFF); |
| if (wct == 12) |
| smb->OffsetHigh = cpu_to_le32(rdata->offset >> 32); |
| smb->Remaining = 0; |
| smb->MaxCount = cpu_to_le16(rdata->bytes & 0xFFFF); |
| smb->MaxCountHigh = cpu_to_le32(rdata->bytes >> 16); |
| if (wct == 12) |
| smb->ByteCount = 0; |
| else { |
| /* old style read */ |
| struct smb_com_readx_req *smbr = |
| (struct smb_com_readx_req *)smb; |
| smbr->ByteCount = 0; |
| } |
| |
| /* 4 for RFC1001 length + 1 for BCC */ |
| rdata->iov[0].iov_base = smb; |
| rdata->iov[0].iov_len = 4; |
| rdata->iov[1].iov_base = (char *)smb + 4; |
| rdata->iov[1].iov_len = get_rfc1002_length(smb); |
| |
| kref_get(&rdata->refcount); |
| rc = cifs_call_async(tcon->ses->server, &rqst, cifs_readv_receive, |
| cifs_readv_callback, NULL, rdata, 0); |
| |
| if (rc == 0) |
| cifs_stats_inc(&tcon->stats.cifs_stats.num_reads); |
| else |
| kref_put(&rdata->refcount, cifs_readdata_release); |
| |
| cifs_small_buf_release(smb); |
| return rc; |
| } |
| |
| int |
| CIFSSMBRead(const unsigned int xid, struct cifs_io_parms *io_parms, |
| unsigned int *nbytes, char **buf, int *pbuf_type) |
| { |
| int rc = -EACCES; |
| READ_REQ *pSMB = NULL; |
| READ_RSP *pSMBr = NULL; |
| char *pReadData = NULL; |
| int wct; |
| int resp_buf_type = 0; |
| struct kvec iov[1]; |
| struct kvec rsp_iov; |
| __u32 pid = io_parms->pid; |
| __u16 netfid = io_parms->netfid; |
| __u64 offset = io_parms->offset; |
| struct cifs_tcon *tcon = io_parms->tcon; |
| unsigned int count = io_parms->length; |
| |
| cifs_dbg(FYI, "Reading %d bytes on fid %d\n", count, netfid); |
| if (tcon->ses->capabilities & CAP_LARGE_FILES) |
| wct = 12; |
| else { |
| wct = 10; /* old style read */ |
| if ((offset >> 32) > 0) { |
| /* can not handle this big offset for old */ |
| return -EIO; |
| } |
| } |
| |
| *nbytes = 0; |
| rc = small_smb_init(SMB_COM_READ_ANDX, wct, tcon, (void **) &pSMB); |
| if (rc) |
| return rc; |
| |
| pSMB->hdr.Pid = cpu_to_le16((__u16)pid); |
| pSMB->hdr.PidHigh = cpu_to_le16((__u16)(pid >> 16)); |
| |
| /* tcon and ses pointer are checked in smb_init */ |
| if (tcon->ses->server == NULL) |
| return -ECONNABORTED; |
| |
| pSMB->AndXCommand = 0xFF; /* none */ |
| pSMB->Fid = netfid; |
| pSMB->OffsetLow = cpu_to_le32(offset & 0xFFFFFFFF); |
| if (wct == 12) |
| pSMB->OffsetHigh = cpu_to_le32(offset >> 32); |
| |
| pSMB->Remaining = 0; |
| pSMB->MaxCount = cpu_to_le16(count & 0xFFFF); |
| pSMB->MaxCountHigh = cpu_to_le32(count >> 16); |
| if (wct == 12) |
| pSMB->ByteCount = 0; /* no need to do le conversion since 0 */ |
| else { |
| /* old style read */ |
| struct smb_com_readx_req *pSMBW = |
| (struct smb_com_readx_req *)pSMB; |
| pSMBW->ByteCount = 0; |
| } |
| |
| iov[0].iov_base = (char *)pSMB; |
| iov[0].iov_len = be32_to_cpu(pSMB->hdr.smb_buf_length) + 4; |
| rc = SendReceive2(xid, tcon->ses, iov, 1, &resp_buf_type, |
| CIFS_LOG_ERROR, &rsp_iov); |
| cifs_small_buf_release(pSMB); |
| cifs_stats_inc(&tcon->stats.cifs_stats.num_reads); |
| pSMBr = (READ_RSP *)rsp_iov.iov_base; |
| if (rc) { |
| cifs_dbg(VFS, "Send error in read = %d\n", rc); |
| } else { |
| int data_length = le16_to_cpu(pSMBr->DataLengthHigh); |
| data_length = data_length << 16; |
| data_length += le16_to_cpu(pSMBr->DataLength); |
| *nbytes = data_length; |
| |
| /*check that DataLength would not go beyond end of SMB */ |
| if ((data_length > CIFSMaxBufSize) |
| || (data_length > count)) { |
| cifs_dbg(FYI, "bad length %d for count %d\n", |
| data_length, count); |
| rc = -EIO; |
| *nbytes = 0; |
| } else { |
| pReadData = (char *) (&pSMBr->hdr.Protocol) + |
| le16_to_cpu(pSMBr->DataOffset); |
| /* if (rc = copy_to_user(buf, pReadData, data_length)) { |
| cifs_dbg(VFS, "Faulting on read rc = %d\n",rc); |
| rc = -EFAULT; |
| }*/ /* can not use copy_to_user when using page cache*/ |
| if (*buf) |
| memcpy(*buf, pReadData, data_length); |
| } |
| } |
| |
| if (*buf) { |
| free_rsp_buf(resp_buf_type, rsp_iov.iov_base); |
| } else if (resp_buf_type != CIFS_NO_BUFFER) { |
| /* return buffer to caller to free */ |
| *buf = rsp_iov.iov_base; |
| if (resp_buf_type == CIFS_SMALL_BUFFER) |
| *pbuf_type = CIFS_SMALL_BUFFER; |
| else if (resp_buf_type == CIFS_LARGE_BUFFER) |
| *pbuf_type = CIFS_LARGE_BUFFER; |
| } /* else no valid buffer on return - leave as null */ |
| |
| /* Note: On -EAGAIN error only caller can retry on handle based calls |
| since file handle passed in no longer valid */ |
| return rc; |
| } |
| |
| |
| int |
| CIFSSMBWrite(const unsigned int xid, struct cifs_io_parms *io_parms, |
| unsigned int *nbytes, const char *buf) |
| { |
| int rc = -EACCES; |
| WRITE_REQ *pSMB = NULL; |
| WRITE_RSP *pSMBr = NULL; |
| int bytes_returned, wct; |
| __u32 bytes_sent; |
| __u16 byte_count; |
| __u32 pid = io_parms->pid; |
| __u16 netfid = io_parms->netfid; |
| __u64 offset = io_parms->offset; |
| struct cifs_tcon *tcon = io_parms->tcon; |
| unsigned int count = io_parms->length; |
| |
| *nbytes = 0; |
| |
| /* cifs_dbg(FYI, "write at %lld %d bytes\n", offset, count);*/ |
| if (tcon->ses == NULL) |
| return -ECONNABORTED; |
| |
| if (tcon->ses->capabilities & CAP_LARGE_FILES) |
| wct = 14; |
| else { |
| wct = 12; |
| if ((offset >> 32) > 0) { |
| /* can not handle big offset for old srv */ |
| return -EIO; |
| } |
| } |
| |
| rc = smb_init(SMB_COM_WRITE_ANDX, wct, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| pSMB->hdr.Pid = cpu_to_le16((__u16)pid); |
| pSMB->hdr.PidHigh = cpu_to_le16((__u16)(pid >> 16)); |
| |
| /* tcon and ses pointer are checked in smb_init */ |
| if (tcon->ses->server == NULL) |
| return -ECONNABORTED; |
| |
| pSMB->AndXCommand = 0xFF; /* none */ |
| pSMB->Fid = netfid; |
| pSMB->OffsetLow = cpu_to_le32(offset & 0xFFFFFFFF); |
| if (wct == 14) |
| pSMB->OffsetHigh = cpu_to_le32(offset >> 32); |
| |
| pSMB->Reserved = 0xFFFFFFFF; |
| pSMB->WriteMode = 0; |
| pSMB->Remaining = 0; |
| |
| /* Can increase buffer size if buffer is big enough in some cases ie we |
| can send more if LARGE_WRITE_X capability returned by the server and if |
| our buffer is big enough or if we convert to iovecs on socket writes |
| and eliminate the copy to the CIFS buffer */ |
| if (tcon->ses->capabilities & CAP_LARGE_WRITE_X) { |
| bytes_sent = min_t(const unsigned int, CIFSMaxBufSize, count); |
| } else { |
| bytes_sent = (tcon->ses->server->maxBuf - MAX_CIFS_HDR_SIZE) |
| & ~0xFF; |
| } |
| |
| if (bytes_sent > count) |
| bytes_sent = count; |
| pSMB->DataOffset = |
| cpu_to_le16(offsetof(struct smb_com_write_req, Data) - 4); |
| if (buf) |
| memcpy(pSMB->Data, buf, bytes_sent); |
| else if (count != 0) { |
| /* No buffer */ |
| cifs_buf_release(pSMB); |
| return -EINVAL; |
| } /* else setting file size with write of zero bytes */ |
| if (wct == 14) |
| byte_count = bytes_sent + 1; /* pad */ |
| else /* wct == 12 */ |
| byte_count = bytes_sent + 5; /* bigger pad, smaller smb hdr */ |
| |
| pSMB->DataLengthLow = cpu_to_le16(bytes_sent & 0xFFFF); |
| pSMB->DataLengthHigh = cpu_to_le16(bytes_sent >> 16); |
| inc_rfc1001_len(pSMB, byte_count); |
| |
| if (wct == 14) |
| pSMB->ByteCount = cpu_to_le16(byte_count); |
| else { /* old style write has byte count 4 bytes earlier |
| so 4 bytes pad */ |
| struct smb_com_writex_req *pSMBW = |
| (struct smb_com_writex_req *)pSMB; |
| pSMBW->ByteCount = cpu_to_le16(byte_count); |
| } |
| |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| cifs_stats_inc(&tcon->stats.cifs_stats.num_writes); |
| if (rc) { |
| cifs_dbg(FYI, "Send error in write = %d\n", rc); |
| } else { |
| *nbytes = le16_to_cpu(pSMBr->CountHigh); |
| *nbytes = (*nbytes) << 16; |
| *nbytes += le16_to_cpu(pSMBr->Count); |
| |
| /* |
| * Mask off high 16 bits when bytes written as returned by the |
| * server is greater than bytes requested by the client. Some |
| * OS/2 servers are known to set incorrect CountHigh values. |
| */ |
| if (*nbytes > count) |
| *nbytes &= 0xFFFF; |
| } |
| |
| cifs_buf_release(pSMB); |
| |
| /* Note: On -EAGAIN error only caller can retry on handle based calls |
| since file handle passed in no longer valid */ |
| |
| return rc; |
| } |
| |
| void |
| cifs_writedata_release(struct kref *refcount) |
| { |
| struct cifs_writedata *wdata = container_of(refcount, |
| struct cifs_writedata, refcount); |
| |
| if (wdata->cfile) |
| cifsFileInfo_put(wdata->cfile); |
| |
| kfree(wdata); |
| } |
| |
| /* |
| * Write failed with a retryable error. Resend the write request. It's also |
| * possible that the page was redirtied so re-clean the page. |
| */ |
| static void |
| cifs_writev_requeue(struct cifs_writedata *wdata) |
| { |
| int i, rc = 0; |
| struct inode *inode = d_inode(wdata->cfile->dentry); |
| struct TCP_Server_Info *server; |
| unsigned int rest_len; |
| |
| server = tlink_tcon(wdata->cfile->tlink)->ses->server; |
| i = 0; |
| rest_len = wdata->bytes; |
| do { |
| struct cifs_writedata *wdata2; |
| unsigned int j, nr_pages, wsize, tailsz, cur_len; |
| |
| wsize = server->ops->wp_retry_size(inode); |
| if (wsize < rest_len) { |
| nr_pages = wsize / PAGE_SIZE; |
| if (!nr_pages) { |
| rc = -ENOTSUPP; |
| break; |
| } |
| cur_len = nr_pages * PAGE_SIZE; |
| tailsz = PAGE_SIZE; |
| } else { |
| nr_pages = DIV_ROUND_UP(rest_len, PAGE_SIZE); |
| cur_len = rest_len; |
| tailsz = rest_len - (nr_pages - 1) * PAGE_SIZE; |
| } |
| |
| wdata2 = cifs_writedata_alloc(nr_pages, cifs_writev_complete); |
| if (!wdata2) { |
| rc = -ENOMEM; |
| break; |
| } |
| |
| for (j = 0; j < nr_pages; j++) { |
| wdata2->pages[j] = wdata->pages[i + j]; |
| lock_page(wdata2->pages[j]); |
| clear_page_dirty_for_io(wdata2->pages[j]); |
| } |
| |
| wdata2->sync_mode = wdata->sync_mode; |
| wdata2->nr_pages = nr_pages; |
| wdata2->offset = page_offset(wdata2->pages[0]); |
| wdata2->pagesz = PAGE_SIZE; |
| wdata2->tailsz = tailsz; |
| wdata2->bytes = cur_len; |
| |
| wdata2->cfile = find_writable_file(CIFS_I(inode), false); |
| if (!wdata2->cfile) { |
| cifs_dbg(VFS, "No writable handles for inode\n"); |
| rc = -EBADF; |
| break; |
| } |
| wdata2->pid = wdata2->cfile->pid; |
| rc = server->ops->async_writev(wdata2, cifs_writedata_release); |
| |
| for (j = 0; j < nr_pages; j++) { |
| unlock_page(wdata2->pages[j]); |
| if (rc != 0 && rc != -EAGAIN) { |
| SetPageError(wdata2->pages[j]); |
| end_page_writeback(wdata2->pages[j]); |
| put_page(wdata2->pages[j]); |
| } |
| } |
| |
| if (rc) { |
| kref_put(&wdata2->refcount, cifs_writedata_release); |
| if (rc == -EAGAIN) |
| continue; |
| break; |
| } |
| |
| rest_len -= cur_len; |
| i += nr_pages; |
| } while (i < wdata->nr_pages); |
| |
| mapping_set_error(inode->i_mapping, rc); |
| kref_put(&wdata->refcount, cifs_writedata_release); |
| } |
| |
| void |
| cifs_writev_complete(struct work_struct *work) |
| { |
| struct cifs_writedata *wdata = container_of(work, |
| struct cifs_writedata, work); |
| struct inode *inode = d_inode(wdata->cfile->dentry); |
| int i = 0; |
| |
| if (wdata->result == 0) { |
| spin_lock(&inode->i_lock); |
| cifs_update_eof(CIFS_I(inode), wdata->offset, wdata->bytes); |
| spin_unlock(&inode->i_lock); |
| cifs_stats_bytes_written(tlink_tcon(wdata->cfile->tlink), |
| wdata->bytes); |
| } else if (wdata->sync_mode == WB_SYNC_ALL && wdata->result == -EAGAIN) |
| return cifs_writev_requeue(wdata); |
| |
| for (i = 0; i < wdata->nr_pages; i++) { |
| struct page *page = wdata->pages[i]; |
| if (wdata->result == -EAGAIN) |
| __set_page_dirty_nobuffers(page); |
| else if (wdata->result < 0) |
| SetPageError(page); |
| end_page_writeback(page); |
| put_page(page); |
| } |
| if (wdata->result != -EAGAIN) |
| mapping_set_error(inode->i_mapping, wdata->result); |
| kref_put(&wdata->refcount, cifs_writedata_release); |
| } |
| |
| struct cifs_writedata * |
| cifs_writedata_alloc(unsigned int nr_pages, work_func_t complete) |
| { |
| struct cifs_writedata *wdata; |
| |
| /* writedata + number of page pointers */ |
| wdata = kzalloc(sizeof(*wdata) + |
| sizeof(struct page *) * nr_pages, GFP_NOFS); |
| if (wdata != NULL) { |
| kref_init(&wdata->refcount); |
| INIT_LIST_HEAD(&wdata->list); |
| init_completion(&wdata->done); |
| INIT_WORK(&wdata->work, complete); |
| } |
| return wdata; |
| } |
| |
| /* |
| * Check the mid_state and signature on received buffer (if any), and queue the |
| * workqueue completion task. |
| */ |
| static void |
| cifs_writev_callback(struct mid_q_entry *mid) |
| { |
| struct cifs_writedata *wdata = mid->callback_data; |
| struct cifs_tcon *tcon = tlink_tcon(wdata->cfile->tlink); |
| unsigned int written; |
| WRITE_RSP *smb = (WRITE_RSP *)mid->resp_buf; |
| |
| switch (mid->mid_state) { |
| case MID_RESPONSE_RECEIVED: |
| wdata->result = cifs_check_receive(mid, tcon->ses->server, 0); |
| if (wdata->result != 0) |
| break; |
| |
| written = le16_to_cpu(smb->CountHigh); |
| written <<= 16; |
| written += le16_to_cpu(smb->Count); |
| /* |
| * Mask off high 16 bits when bytes written as returned |
| * by the server is greater than bytes requested by the |
| * client. OS/2 servers are known to set incorrect |
| * CountHigh values. |
| */ |
| if (written > wdata->bytes) |
| written &= 0xFFFF; |
| |
| if (written < wdata->bytes) |
| wdata->result = -ENOSPC; |
| else |
| wdata->bytes = written; |
| break; |
| case MID_REQUEST_SUBMITTED: |
| case MID_RETRY_NEEDED: |
| wdata->result = -EAGAIN; |
| break; |
| default: |
| wdata->result = -EIO; |
| break; |
| } |
| |
| queue_work(cifsiod_wq, &wdata->work); |
| DeleteMidQEntry(mid); |
| add_credits(tcon->ses->server, 1, 0); |
| } |
| |
| /* cifs_async_writev - send an async write, and set up mid to handle result */ |
| int |
| cifs_async_writev(struct cifs_writedata *wdata, |
| void (*release)(struct kref *kref)) |
| { |
| int rc = -EACCES; |
| WRITE_REQ *smb = NULL; |
| int wct; |
| struct cifs_tcon *tcon = tlink_tcon(wdata->cfile->tlink); |
| struct kvec iov[2]; |
| struct smb_rqst rqst = { }; |
| |
| if (tcon->ses->capabilities & CAP_LARGE_FILES) { |
| wct = 14; |
| } else { |
| wct = 12; |
| if (wdata->offset >> 32 > 0) { |
| /* can not handle big offset for old srv */ |
| return -EIO; |
| } |
| } |
| |
| rc = small_smb_init(SMB_COM_WRITE_ANDX, wct, tcon, (void **)&smb); |
| if (rc) |
| goto async_writev_out; |
| |
| smb->hdr.Pid = cpu_to_le16((__u16)wdata->pid); |
| smb->hdr.PidHigh = cpu_to_le16((__u16)(wdata->pid >> 16)); |
| |
| smb->AndXCommand = 0xFF; /* none */ |
| smb->Fid = wdata->cfile->fid.netfid; |
| smb->OffsetLow = cpu_to_le32(wdata->offset & 0xFFFFFFFF); |
| if (wct == 14) |
| smb->OffsetHigh = cpu_to_le32(wdata->offset >> 32); |
| smb->Reserved = 0xFFFFFFFF; |
| smb->WriteMode = 0; |
| smb->Remaining = 0; |
| |
| smb->DataOffset = |
| cpu_to_le16(offsetof(struct smb_com_write_req, Data) - 4); |
| |
| /* 4 for RFC1001 length + 1 for BCC */ |
| iov[0].iov_len = 4; |
| iov[0].iov_base = smb; |
| iov[1].iov_len = get_rfc1002_length(smb) + 1; |
| iov[1].iov_base = (char *)smb + 4; |
| |
| rqst.rq_iov = iov; |
| rqst.rq_nvec = 2; |
| rqst.rq_pages = wdata->pages; |
| rqst.rq_npages = wdata->nr_pages; |
| rqst.rq_pagesz = wdata->pagesz; |
| rqst.rq_tailsz = wdata->tailsz; |
| |
| cifs_dbg(FYI, "async write at %llu %u bytes\n", |
| wdata->offset, wdata->bytes); |
| |
| smb->DataLengthLow = cpu_to_le16(wdata->bytes & 0xFFFF); |
| smb->DataLengthHigh = cpu_to_le16(wdata->bytes >> 16); |
| |
| if (wct == 14) { |
| inc_rfc1001_len(&smb->hdr, wdata->bytes + 1); |
| put_bcc(wdata->bytes + 1, &smb->hdr); |
| } else { |
| /* wct == 12 */ |
| struct smb_com_writex_req *smbw = |
| (struct smb_com_writex_req *)smb; |
| inc_rfc1001_len(&smbw->hdr, wdata->bytes + 5); |
| put_bcc(wdata->bytes + 5, &smbw->hdr); |
| iov[1].iov_len += 4; /* pad bigger by four bytes */ |
| } |
| |
| kref_get(&wdata->refcount); |
| rc = cifs_call_async(tcon->ses->server, &rqst, NULL, |
| cifs_writev_callback, NULL, wdata, 0); |
| |
| if (rc == 0) |
| cifs_stats_inc(&tcon->stats.cifs_stats.num_writes); |
| else |
| kref_put(&wdata->refcount, release); |
| |
| async_writev_out: |
| cifs_small_buf_release(smb); |
| return rc; |
| } |
| |
| int |
| CIFSSMBWrite2(const unsigned int xid, struct cifs_io_parms *io_parms, |
| unsigned int *nbytes, struct kvec *iov, int n_vec) |
| { |
| int rc = -EACCES; |
| WRITE_REQ *pSMB = NULL; |
| int wct; |
| int smb_hdr_len; |
| int resp_buf_type = 0; |
| __u32 pid = io_parms->pid; |
| __u16 netfid = io_parms->netfid; |
| __u64 offset = io_parms->offset; |
| struct cifs_tcon *tcon = io_parms->tcon; |
| unsigned int count = io_parms->length; |
| struct kvec rsp_iov; |
| |
| *nbytes = 0; |
| |
| cifs_dbg(FYI, "write2 at %lld %d bytes\n", (long long)offset, count); |
| |
| if (tcon->ses->capabilities & CAP_LARGE_FILES) { |
| wct = 14; |
| } else { |
| wct = 12; |
| if ((offset >> 32) > 0) { |
| /* can not handle big offset for old srv */ |
| return -EIO; |
| } |
| } |
| rc = small_smb_init(SMB_COM_WRITE_ANDX, wct, tcon, (void **) &pSMB); |
| if (rc) |
| return rc; |
| |
| pSMB->hdr.Pid = cpu_to_le16((__u16)pid); |
| pSMB->hdr.PidHigh = cpu_to_le16((__u16)(pid >> 16)); |
| |
| /* tcon and ses pointer are checked in smb_init */ |
| if (tcon->ses->server == NULL) |
| return -ECONNABORTED; |
| |
| pSMB->AndXCommand = 0xFF; /* none */ |
| pSMB->Fid = netfid; |
| pSMB->OffsetLow = cpu_to_le32(offset & 0xFFFFFFFF); |
| if (wct == 14) |
| pSMB->OffsetHigh = cpu_to_le32(offset >> 32); |
| pSMB->Reserved = 0xFFFFFFFF; |
| pSMB->WriteMode = 0; |
| pSMB->Remaining = 0; |
| |
| pSMB->DataOffset = |
| cpu_to_le16(offsetof(struct smb_com_write_req, Data) - 4); |
| |
| pSMB->DataLengthLow = cpu_to_le16(count & 0xFFFF); |
| pSMB->DataLengthHigh = cpu_to_le16(count >> 16); |
| /* header + 1 byte pad */ |
| smb_hdr_len = be32_to_cpu(pSMB->hdr.smb_buf_length) + 1; |
| if (wct == 14) |
| inc_rfc1001_len(pSMB, count + 1); |
| else /* wct == 12 */ |
| inc_rfc1001_len(pSMB, count + 5); /* smb data starts later */ |
| if (wct == 14) |
| pSMB->ByteCount = cpu_to_le16(count + 1); |
| else /* wct == 12 */ /* bigger pad, smaller smb hdr, keep offset ok */ { |
| struct smb_com_writex_req *pSMBW = |
| (struct smb_com_writex_req *)pSMB; |
| pSMBW->ByteCount = cpu_to_le16(count + 5); |
| } |
| iov[0].iov_base = pSMB; |
| if (wct == 14) |
| iov[0].iov_len = smb_hdr_len + 4; |
| else /* wct == 12 pad bigger by four bytes */ |
| iov[0].iov_len = smb_hdr_len + 8; |
| |
| rc = SendReceive2(xid, tcon->ses, iov, n_vec + 1, &resp_buf_type, 0, |
| &rsp_iov); |
| cifs_small_buf_release(pSMB); |
| cifs_stats_inc(&tcon->stats.cifs_stats.num_writes); |
| if (rc) { |
| cifs_dbg(FYI, "Send error Write2 = %d\n", rc); |
| } else if (resp_buf_type == 0) { |
| /* presumably this can not happen, but best to be safe */ |
| rc = -EIO; |
| } else { |
| WRITE_RSP *pSMBr = (WRITE_RSP *)rsp_iov.iov_base; |
| *nbytes = le16_to_cpu(pSMBr->CountHigh); |
| *nbytes = (*nbytes) << 16; |
| *nbytes += le16_to_cpu(pSMBr->Count); |
| |
| /* |
| * Mask off high 16 bits when bytes written as returned by the |
| * server is greater than bytes requested by the client. OS/2 |
| * servers are known to set incorrect CountHigh values. |
| */ |
| if (*nbytes > count) |
| *nbytes &= 0xFFFF; |
| } |
| |
| free_rsp_buf(resp_buf_type, rsp_iov.iov_base); |
| |
| /* Note: On -EAGAIN error only caller can retry on handle based calls |
| since file handle passed in no longer valid */ |
| |
| return rc; |
| } |
| |
| int cifs_lockv(const unsigned int xid, struct cifs_tcon *tcon, |
| const __u16 netfid, const __u8 lock_type, const __u32 num_unlock, |
| const __u32 num_lock, LOCKING_ANDX_RANGE *buf) |
| { |
| int rc = 0; |
| LOCK_REQ *pSMB = NULL; |
| struct kvec iov[2]; |
| struct kvec rsp_iov; |
| int resp_buf_type; |
| __u16 count; |
| |
| cifs_dbg(FYI, "cifs_lockv num lock %d num unlock %d\n", |
| num_lock, num_unlock); |
| |
| rc = small_smb_init(SMB_COM_LOCKING_ANDX, 8, tcon, (void **) &pSMB); |
| if (rc) |
| return rc; |
| |
| pSMB->Timeout = 0; |
| pSMB->NumberOfLocks = cpu_to_le16(num_lock); |
| pSMB->NumberOfUnlocks = cpu_to_le16(num_unlock); |
| pSMB->LockType = lock_type; |
| pSMB->AndXCommand = 0xFF; /* none */ |
| pSMB->Fid = netfid; /* netfid stays le */ |
| |
| count = (num_unlock + num_lock) * sizeof(LOCKING_ANDX_RANGE); |
| inc_rfc1001_len(pSMB, count); |
| pSMB->ByteCount = cpu_to_le16(count); |
| |
| iov[0].iov_base = (char *)pSMB; |
| iov[0].iov_len = be32_to_cpu(pSMB->hdr.smb_buf_length) + 4 - |
| (num_unlock + num_lock) * sizeof(LOCKING_ANDX_RANGE); |
| iov[1].iov_base = (char *)buf; |
| iov[1].iov_len = (num_unlock + num_lock) * sizeof(LOCKING_ANDX_RANGE); |
| |
| cifs_stats_inc(&tcon->stats.cifs_stats.num_locks); |
| rc = SendReceive2(xid, tcon->ses, iov, 2, &resp_buf_type, CIFS_NO_RESP, |
| &rsp_iov); |
| cifs_small_buf_release(pSMB); |
| if (rc) |
| cifs_dbg(FYI, "Send error in cifs_lockv = %d\n", rc); |
| |
| return rc; |
| } |
| |
| int |
| CIFSSMBLock(const unsigned int xid, struct cifs_tcon *tcon, |
| const __u16 smb_file_id, const __u32 netpid, const __u64 len, |
| const __u64 offset, const __u32 numUnlock, |
| const __u32 numLock, const __u8 lockType, |
| const bool waitFlag, const __u8 oplock_level) |
| { |
| int rc = 0; |
| LOCK_REQ *pSMB = NULL; |
| /* LOCK_RSP *pSMBr = NULL; */ /* No response data other than rc to parse */ |
| int bytes_returned; |
| int flags = 0; |
| __u16 count; |
| |
| cifs_dbg(FYI, "CIFSSMBLock timeout %d numLock %d\n", |
| (int)waitFlag, numLock); |
| rc = small_smb_init(SMB_COM_LOCKING_ANDX, 8, tcon, (void **) &pSMB); |
| |
| if (rc) |
| return rc; |
| |
| if (lockType == LOCKING_ANDX_OPLOCK_RELEASE) { |
| /* no response expected */ |
| flags = CIFS_ASYNC_OP | CIFS_OBREAK_OP; |
| pSMB->Timeout = 0; |
| } else if (waitFlag) { |
| flags = CIFS_BLOCKING_OP; /* blocking operation, no timeout */ |
| pSMB->Timeout = cpu_to_le32(-1);/* blocking - do not time out */ |
| } else { |
| pSMB->Timeout = 0; |
| } |
| |
| pSMB->NumberOfLocks = cpu_to_le16(numLock); |
| pSMB->NumberOfUnlocks = cpu_to_le16(numUnlock); |
| pSMB->LockType = lockType; |
| pSMB->OplockLevel = oplock_level; |
| pSMB->AndXCommand = 0xFF; /* none */ |
| pSMB->Fid = smb_file_id; /* netfid stays le */ |
| |
| if ((numLock != 0) || (numUnlock != 0)) { |
| pSMB->Locks[0].Pid = cpu_to_le16(netpid); |
| /* BB where to store pid high? */ |
| pSMB->Locks[0].LengthLow = cpu_to_le32((u32)len); |
| pSMB->Locks[0].LengthHigh = cpu_to_le32((u32)(len>>32)); |
| pSMB->Locks[0].OffsetLow = cpu_to_le32((u32)offset); |
| pSMB->Locks[0].OffsetHigh = cpu_to_le32((u32)(offset>>32)); |
| count = sizeof(LOCKING_ANDX_RANGE); |
| } else { |
| /* oplock break */ |
| count = 0; |
| } |
| inc_rfc1001_len(pSMB, count); |
| pSMB->ByteCount = cpu_to_le16(count); |
| |
| if (waitFlag) |
| rc = SendReceiveBlockingLock(xid, tcon, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMB, &bytes_returned); |
| else |
| rc = SendReceiveNoRsp(xid, tcon->ses, (char *)pSMB, flags); |
| cifs_small_buf_release(pSMB); |
| cifs_stats_inc(&tcon->stats.cifs_stats.num_locks); |
| if (rc) |
| cifs_dbg(FYI, "Send error in Lock = %d\n", rc); |
| |
| /* Note: On -EAGAIN error only caller can retry on handle based calls |
| since file handle passed in no longer valid */ |
| return rc; |
| } |
| |
| int |
| CIFSSMBPosixLock(const unsigned int xid, struct cifs_tcon *tcon, |
| const __u16 smb_file_id, const __u32 netpid, |
| const loff_t start_offset, const __u64 len, |
| struct file_lock *pLockData, const __u16 lock_type, |
| const bool waitFlag) |
| { |
| struct smb_com_transaction2_sfi_req *pSMB = NULL; |
| struct smb_com_transaction2_sfi_rsp *pSMBr = NULL; |
| struct cifs_posix_lock *parm_data; |
| int rc = 0; |
| int timeout = 0; |
| int bytes_returned = 0; |
| int resp_buf_type = 0; |
| __u16 params, param_offset, offset, byte_count, count; |
| struct kvec iov[1]; |
| struct kvec rsp_iov; |
| |
| cifs_dbg(FYI, "Posix Lock\n"); |
| |
| rc = small_smb_init(SMB_COM_TRANSACTION2, 15, tcon, (void **) &pSMB); |
| |
| if (rc) |
| return rc; |
| |
| pSMBr = (struct smb_com_transaction2_sfi_rsp *)pSMB; |
| |
| params = 6; |
| pSMB->MaxSetupCount = 0; |
| pSMB->Reserved = 0; |
| pSMB->Flags = 0; |
| pSMB->Reserved2 = 0; |
| param_offset = offsetof(struct smb_com_transaction2_sfi_req, Fid) - 4; |
| offset = param_offset + params; |
| |
| count = sizeof(struct cifs_posix_lock); |
| pSMB->MaxParameterCount = cpu_to_le16(2); |
| pSMB->MaxDataCount = cpu_to_le16(1000); /* BB find max SMB from sess */ |
| pSMB->SetupCount = 1; |
| pSMB->Reserved3 = 0; |
| if (pLockData) |
| pSMB->SubCommand = cpu_to_le16(TRANS2_QUERY_FILE_INFORMATION); |
| else |
| pSMB->SubCommand = cpu_to_le16(TRANS2_SET_FILE_INFORMATION); |
| byte_count = 3 /* pad */ + params + count; |
| pSMB->DataCount = cpu_to_le16(count); |
| pSMB->ParameterCount = cpu_to_le16(params); |
| pSMB->TotalDataCount = pSMB->DataCount; |
| pSMB->TotalParameterCount = pSMB->ParameterCount; |
| pSMB->ParameterOffset = cpu_to_le16(param_offset); |
| parm_data = (struct cifs_posix_lock *) |
| (((char *) &pSMB->hdr.Protocol) + offset); |
| |
| parm_data->lock_type = cpu_to_le16(lock_type); |
| if (waitFlag) { |
| timeout = CIFS_BLOCKING_OP; /* blocking operation, no timeout */ |
| parm_data->lock_flags = cpu_to_le16(1); |
| pSMB->Timeout = cpu_to_le32(-1); |
| } else |
| pSMB->Timeout = 0; |
| |
| parm_data->pid = cpu_to_le32(netpid); |
| parm_data->start = cpu_to_le64(start_offset); |
| parm_data->length = cpu_to_le64(len); /* normalize negative numbers */ |
| |
| pSMB->DataOffset = cpu_to_le16(offset); |
| pSMB->Fid = smb_file_id; |
| pSMB->InformationLevel = cpu_to_le16(SMB_SET_POSIX_LOCK); |
| pSMB->Reserved4 = 0; |
| inc_rfc1001_len(pSMB, byte_count); |
| pSMB->ByteCount = cpu_to_le16(byte_count); |
| if (waitFlag) { |
| rc = SendReceiveBlockingLock(xid, tcon, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned); |
| } else { |
| iov[0].iov_base = (char *)pSMB; |
| iov[0].iov_len = be32_to_cpu(pSMB->hdr.smb_buf_length) + 4; |
| rc = SendReceive2(xid, tcon->ses, iov, 1 /* num iovecs */, |
| &resp_buf_type, timeout, &rsp_iov); |
| pSMBr = (struct smb_com_transaction2_sfi_rsp *)rsp_iov.iov_base; |
| } |
| cifs_small_buf_release(pSMB); |
| |
| if (rc) { |
| cifs_dbg(FYI, "Send error in Posix Lock = %d\n", rc); |
| } else if (pLockData) { |
| /* lock structure can be returned on get */ |
| __u16 data_offset; |
| __u16 data_count; |
| rc = validate_t2((struct smb_t2_rsp *)pSMBr); |
| |
| if (rc || get_bcc(&pSMBr->hdr) < sizeof(*parm_data)) { |
| rc = -EIO; /* bad smb */ |
| goto plk_err_exit; |
| } |
| data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| data_count = le16_to_cpu(pSMBr->t2.DataCount); |
| if (data_count < sizeof(struct cifs_posix_lock)) { |
| rc = -EIO; |
| goto plk_err_exit; |
| } |
| parm_data = (struct cifs_posix_lock *) |
| ((char *)&pSMBr->hdr.Protocol + data_offset); |
| if (parm_data->lock_type == cpu_to_le16(CIFS_UNLCK)) |
| pLockData->fl_type = F_UNLCK; |
| else { |
| if (parm_data->lock_type == |
| cpu_to_le16(CIFS_RDLCK)) |
| pLockData->fl_type = F_RDLCK; |
| else if (parm_data->lock_type == |
| cpu_to_le16(CIFS_WRLCK)) |
| pLockData->fl_type = F_WRLCK; |
| |
| pLockData->fl_start = le64_to_cpu(parm_data->start); |
| pLockData->fl_end = pLockData->fl_start + |
| le64_to_cpu(parm_data->length) - 1; |
| pLockData->fl_pid = -le32_to_cpu(parm_data->pid); |
| } |
| } |
| |
| plk_err_exit: |
| free_rsp_buf(resp_buf_type, rsp_iov.iov_base); |
| |
| /* Note: On -EAGAIN error only caller can retry on handle based calls |
| since file handle passed in no longer valid */ |
| |
| return rc; |
| } |
| |
| |
| int |
| CIFSSMBClose(const unsigned int xid, struct cifs_tcon *tcon, int smb_file_id) |
| { |
| int rc = 0; |
| CLOSE_REQ *pSMB = NULL; |
| cifs_dbg(FYI, "In CIFSSMBClose\n"); |
| |
| /* do not retry on dead session on close */ |
| rc = small_smb_init(SMB_COM_CLOSE, 3, tcon, (void **) &pSMB); |
| if (rc == -EAGAIN) |
| return 0; |
| if (rc) |
| return rc; |
| |
| pSMB->FileID = (__u16) smb_file_id; |
| pSMB->LastWriteTime = 0xFFFFFFFF; |
| pSMB->ByteCount = 0; |
| rc = SendReceiveNoRsp(xid, tcon->ses, (char *) pSMB, 0); |
| cifs_small_buf_release(pSMB); |
| cifs_stats_inc(&tcon->stats.cifs_stats.num_closes); |
| if (rc) { |
| if (rc != -EINTR) { |
| /* EINTR is expected when user ctl-c to kill app */ |
| cifs_dbg(VFS, "Send error in Close = %d\n", rc); |
| } |
| } |
| |
| /* Since session is dead, file will be closed on server already */ |
| if (rc == -EAGAIN) |
| rc = 0; |
| |
| return rc; |
| } |
| |
| int |
| CIFSSMBFlush(const unsigned int xid, struct cifs_tcon *tcon, int smb_file_id) |
| { |
| int rc = 0; |
| FLUSH_REQ *pSMB = NULL; |
| cifs_dbg(FYI, "In CIFSSMBFlush\n"); |
| |
| rc = small_smb_init(SMB_COM_FLUSH, 1, tcon, (void **) &pSMB); |
| if (rc) |
| return rc; |
| |
| pSMB->FileID = (__u16) smb_file_id; |
| pSMB->ByteCount = 0; |
| rc = SendReceiveNoRsp(xid, tcon->ses, (char *) pSMB, 0); |
| cifs_small_buf_release(pSMB); |
| cifs_stats_inc(&tcon->stats.cifs_stats.num_flushes); |
| if (rc) |
| cifs_dbg(VFS, "Send error in Flush = %d\n", rc); |
| |
| return rc; |
| } |
| |
| int |
| CIFSSMBRename(const unsigned int xid, struct cifs_tcon *tcon, |
| const char *from_name, const char *to_name, |
| struct cifs_sb_info *cifs_sb) |
| { |
| int rc = 0; |
| RENAME_REQ *pSMB = NULL; |
| RENAME_RSP *pSMBr = NULL; |
| int bytes_returned; |
| int name_len, name_len2; |
| __u16 count; |
| int remap = cifs_remap(cifs_sb); |
| |
| cifs_dbg(FYI, "In CIFSSMBRename\n"); |
| renameRetry: |
| rc = smb_init(SMB_COM_RENAME, 1, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| pSMB->BufferFormat = 0x04; |
| pSMB->SearchAttributes = |
| cpu_to_le16(ATTR_READONLY | ATTR_HIDDEN | ATTR_SYSTEM | |
| ATTR_DIRECTORY); |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = cifsConvertToUTF16((__le16 *) pSMB->OldFileName, |
| from_name, PATH_MAX, |
| cifs_sb->local_nls, remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| pSMB->OldFileName[name_len] = 0x04; /* pad */ |
| /* protocol requires ASCII signature byte on Unicode string */ |
| pSMB->OldFileName[name_len + 1] = 0x00; |
| name_len2 = |
| cifsConvertToUTF16((__le16 *)&pSMB->OldFileName[name_len+2], |
| to_name, PATH_MAX, cifs_sb->local_nls, |
| remap); |
| name_len2 += 1 /* trailing null */ + 1 /* Signature word */ ; |
| name_len2 *= 2; /* convert to bytes */ |
| } else { /* BB improve the check for buffer overruns BB */ |
| name_len = strnlen(from_name, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->OldFileName, from_name, name_len); |
| name_len2 = strnlen(to_name, PATH_MAX); |
| name_len2++; /* trailing null */ |
| pSMB->OldFileName[name_len] = 0x04; /* 2nd buffer format */ |
| strncpy(&pSMB->OldFileName[name_len + 1], to_name, name_len2); |
| name_len2++; /* trailing null */ |
| name_len2++; /* signature byte */ |
| } |
| |
| count = 1 /* 1st signature byte */ + name_len + name_len2; |
| inc_rfc1001_len(pSMB, count); |
| pSMB->ByteCount = cpu_to_le16(count); |
| |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| cifs_stats_inc(&tcon->stats.cifs_stats.num_renames); |
| if (rc) |
| cifs_dbg(FYI, "Send error in rename = %d\n", rc); |
| |
| cifs_buf_release(pSMB); |
| |
| if (rc == -EAGAIN) |
| goto renameRetry; |
| |
| return rc; |
| } |
| |
| int CIFSSMBRenameOpenFile(const unsigned int xid, struct cifs_tcon *pTcon, |
| int netfid, const char *target_name, |
| const struct nls_table *nls_codepage, int remap) |
| { |
| struct smb_com_transaction2_sfi_req *pSMB = NULL; |
| struct smb_com_transaction2_sfi_rsp *pSMBr = NULL; |
| struct set_file_rename *rename_info; |
| char *data_offset; |
| char dummy_string[30]; |
| int rc = 0; |
| int bytes_returned = 0; |
| int len_of_str; |
| __u16 params, param_offset, offset, count, byte_count; |
| |
| cifs_dbg(FYI, "Rename to File by handle\n"); |
| rc = smb_init(SMB_COM_TRANSACTION2, 15, pTcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| params = 6; |
| pSMB->MaxSetupCount = 0; |
| pSMB->Reserved = 0; |
| pSMB->Flags = 0; |
| pSMB->Timeout = 0; |
| pSMB->Reserved2 = 0; |
| param_offset = offsetof(struct smb_com_transaction2_sfi_req, Fid) - 4; |
| offset = param_offset + params; |
| |
| data_offset = (char *) (&pSMB->hdr.Protocol) + offset; |
| rename_info = (struct set_file_rename *) data_offset; |
| pSMB->MaxParameterCount = cpu_to_le16(2); |
| pSMB->MaxDataCount = cpu_to_le16(1000); /* BB find max SMB from sess */ |
| pSMB->SetupCount = 1; |
| pSMB->Reserved3 = 0; |
| pSMB->SubCommand = cpu_to_le16(TRANS2_SET_FILE_INFORMATION); |
| byte_count = 3 /* pad */ + params; |
| pSMB->ParameterCount = cpu_to_le16(params); |
| pSMB->TotalParameterCount = pSMB->ParameterCount; |
| pSMB->ParameterOffset = cpu_to_le16(param_offset); |
| pSMB->DataOffset = cpu_to_le16(offset); |
| /* construct random name ".cifs_tmp<inodenum><mid>" */ |
| rename_info->overwrite = cpu_to_le32(1); |
| rename_info->root_fid = 0; |
| /* unicode only call */ |
| if (target_name == NULL) { |
| sprintf(dummy_string, "cifs%x", pSMB->hdr.Mid); |
| len_of_str = |
| cifsConvertToUTF16((__le16 *)rename_info->target_name, |
| dummy_string, 24, nls_codepage, remap); |
| } else { |
| len_of_str = |
| cifsConvertToUTF16((__le16 *)rename_info->target_name, |
| target_name, PATH_MAX, nls_codepage, |
| remap); |
| } |
| rename_info->target_name_len = cpu_to_le32(2 * len_of_str); |
| count = 12 /* sizeof(struct set_file_rename) */ + (2 * len_of_str); |
| byte_count += count; |
| pSMB->DataCount = cpu_to_le16(count); |
| pSMB->TotalDataCount = pSMB->DataCount; |
| pSMB->Fid = netfid; |
| pSMB->InformationLevel = |
| cpu_to_le16(SMB_SET_FILE_RENAME_INFORMATION); |
| pSMB->Reserved4 = 0; |
| inc_rfc1001_len(pSMB, byte_count); |
| pSMB->ByteCount = cpu_to_le16(byte_count); |
| rc = SendReceive(xid, pTcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| cifs_stats_inc(&pTcon->stats.cifs_stats.num_t2renames); |
| if (rc) |
| cifs_dbg(FYI, "Send error in Rename (by file handle) = %d\n", |
| rc); |
| |
| cifs_buf_release(pSMB); |
| |
| /* Note: On -EAGAIN error only caller can retry on handle based calls |
| since file handle passed in no longer valid */ |
| |
| return rc; |
| } |
| |
| int |
| CIFSSMBCopy(const unsigned int xid, struct cifs_tcon *tcon, |
| const char *fromName, const __u16 target_tid, const char *toName, |
| const int flags, const struct nls_table *nls_codepage, int remap) |
| { |
| int rc = 0; |
| COPY_REQ *pSMB = NULL; |
| COPY_RSP *pSMBr = NULL; |
| int bytes_returned; |
| int name_len, name_len2; |
| __u16 count; |
| |
| cifs_dbg(FYI, "In CIFSSMBCopy\n"); |
| copyRetry: |
| rc = smb_init(SMB_COM_COPY, 1, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| pSMB->BufferFormat = 0x04; |
| pSMB->Tid2 = target_tid; |
| |
| pSMB->Flags = cpu_to_le16(flags & COPY_TREE); |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = cifsConvertToUTF16((__le16 *) pSMB->OldFileName, |
| fromName, PATH_MAX, nls_codepage, |
| remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| pSMB->OldFileName[name_len] = 0x04; /* pad */ |
| /* protocol requires ASCII signature byte on Unicode string */ |
| pSMB->OldFileName[name_len + 1] = 0x00; |
| name_len2 = |
| cifsConvertToUTF16((__le16 *)&pSMB->OldFileName[name_len+2], |
| toName, PATH_MAX, nls_codepage, remap); |
| name_len2 += 1 /* trailing null */ + 1 /* Signature word */ ; |
| name_len2 *= 2; /* convert to bytes */ |
| } else { /* BB improve the check for buffer overruns BB */ |
| name_len = strnlen(fromName, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->OldFileName, fromName, name_len); |
| name_len2 = strnlen(toName, PATH_MAX); |
| name_len2++; /* trailing null */ |
| pSMB->OldFileName[name_len] = 0x04; /* 2nd buffer format */ |
| strncpy(&pSMB->OldFileName[name_len + 1], toName, name_len2); |
| name_len2++; /* trailing null */ |
| name_len2++; /* signature byte */ |
| } |
| |
| count = 1 /* 1st signature byte */ + name_len + name_len2; |
| inc_rfc1001_len(pSMB, count); |
| pSMB->ByteCount = cpu_to_le16(count); |
| |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| if (rc) { |
| cifs_dbg(FYI, "Send error in copy = %d with %d files copied\n", |
| rc, le16_to_cpu(pSMBr->CopyCount)); |
| } |
| cifs_buf_release(pSMB); |
| |
| if (rc == -EAGAIN) |
| goto copyRetry; |
| |
| return rc; |
| } |
| |
| int |
| CIFSUnixCreateSymLink(const unsigned int xid, struct cifs_tcon *tcon, |
| const char *fromName, const char *toName, |
| const struct nls_table *nls_codepage, int remap) |
| { |
| TRANSACTION2_SPI_REQ *pSMB = NULL; |
| TRANSACTION2_SPI_RSP *pSMBr = NULL; |
| char *data_offset; |
| int name_len; |
| int name_len_target; |
| int rc = 0; |
| int bytes_returned = 0; |
| __u16 params, param_offset, offset, byte_count; |
| |
| cifs_dbg(FYI, "In Symlink Unix style\n"); |
| createSymLinkRetry: |
| rc = smb_init(SMB_COM_TRANSACTION2, 15, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = |
| cifsConvertToUTF16((__le16 *) pSMB->FileName, fromName, |
| /* find define for this maxpathcomponent */ |
| PATH_MAX, nls_codepage, remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| |
| } else { /* BB improve the check for buffer overruns BB */ |
| name_len = strnlen(fromName, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->FileName, fromName, name_len); |
| } |
| params = 6 + name_len; |
| pSMB->MaxSetupCount = 0; |
| pSMB->Reserved = 0; |
| pSMB->Flags = 0; |
| pSMB->Timeout = 0; |