Merge branch 'dmaengine' of git://git.linaro.org/people/rmk/linux-arm
[cascardo/linux.git] / fs / cifs / smb1ops.c
index 6dec38f..c40356d 100644 (file)
@@ -101,7 +101,8 @@ cifs_find_mid(struct TCP_Server_Info *server, char *buffer)
 }
 
 static void
-cifs_add_credits(struct TCP_Server_Info *server, const unsigned int add)
+cifs_add_credits(struct TCP_Server_Info *server, const unsigned int add,
+                const int optype)
 {
        spin_lock(&server->req_lock);
        server->credits += add;
@@ -120,11 +121,17 @@ cifs_set_credits(struct TCP_Server_Info *server, const int val)
 }
 
 static int *
-cifs_get_credits_field(struct TCP_Server_Info *server)
+cifs_get_credits_field(struct TCP_Server_Info *server, const int optype)
 {
        return &server->credits;
 }
 
+static unsigned int
+cifs_get_credits(struct mid_q_entry *mid)
+{
+       return 1;
+}
+
 /*
  * Find a free multiplex id (SMB mid). Otherwise there could be
  * mid collisions which might cause problems, demultiplexing the
@@ -213,14 +220,382 @@ cifs_get_next_mid(struct TCP_Server_Info *server)
        return mid;
 }
 
+/*
+       return codes:
+               0       not a transact2, or all data present
+               >0      transact2 with that much data missing
+               -EINVAL invalid transact2
+ */
+static int
+check2ndT2(char *buf)
+{
+       struct smb_hdr *pSMB = (struct smb_hdr *)buf;
+       struct smb_t2_rsp *pSMBt;
+       int remaining;
+       __u16 total_data_size, data_in_this_rsp;
+
+       if (pSMB->Command != SMB_COM_TRANSACTION2)
+               return 0;
+
+       /* check for plausible wct, bcc and t2 data and parm sizes */
+       /* check for parm and data offset going beyond end of smb */
+       if (pSMB->WordCount != 10) { /* coalesce_t2 depends on this */
+               cFYI(1, "invalid transact2 word count");
+               return -EINVAL;
+       }
+
+       pSMBt = (struct smb_t2_rsp *)pSMB;
+
+       total_data_size = get_unaligned_le16(&pSMBt->t2_rsp.TotalDataCount);
+       data_in_this_rsp = get_unaligned_le16(&pSMBt->t2_rsp.DataCount);
+
+       if (total_data_size == data_in_this_rsp)
+               return 0;
+       else if (total_data_size < data_in_this_rsp) {
+               cFYI(1, "total data %d smaller than data in frame %d",
+                       total_data_size, data_in_this_rsp);
+               return -EINVAL;
+       }
+
+       remaining = total_data_size - data_in_this_rsp;
+
+       cFYI(1, "missing %d bytes from transact2, check next response",
+               remaining);
+       if (total_data_size > CIFSMaxBufSize) {
+               cERROR(1, "TotalDataSize %d is over maximum buffer %d",
+                       total_data_size, CIFSMaxBufSize);
+               return -EINVAL;
+       }
+       return remaining;
+}
+
+static int
+coalesce_t2(char *second_buf, struct smb_hdr *target_hdr)
+{
+       struct smb_t2_rsp *pSMBs = (struct smb_t2_rsp *)second_buf;
+       struct smb_t2_rsp *pSMBt  = (struct smb_t2_rsp *)target_hdr;
+       char *data_area_of_tgt;
+       char *data_area_of_src;
+       int remaining;
+       unsigned int byte_count, total_in_tgt;
+       __u16 tgt_total_cnt, src_total_cnt, total_in_src;
+
+       src_total_cnt = get_unaligned_le16(&pSMBs->t2_rsp.TotalDataCount);
+       tgt_total_cnt = get_unaligned_le16(&pSMBt->t2_rsp.TotalDataCount);
+
+       if (tgt_total_cnt != src_total_cnt)
+               cFYI(1, "total data count of primary and secondary t2 differ "
+                       "source=%hu target=%hu", src_total_cnt, tgt_total_cnt);
+
+       total_in_tgt = get_unaligned_le16(&pSMBt->t2_rsp.DataCount);
+
+       remaining = tgt_total_cnt - total_in_tgt;
+
+       if (remaining < 0) {
+               cFYI(1, "Server sent too much data. tgt_total_cnt=%hu "
+                       "total_in_tgt=%hu", tgt_total_cnt, total_in_tgt);
+               return -EPROTO;
+       }
+
+       if (remaining == 0) {
+               /* nothing to do, ignore */
+               cFYI(1, "no more data remains");
+               return 0;
+       }
+
+       total_in_src = get_unaligned_le16(&pSMBs->t2_rsp.DataCount);
+       if (remaining < total_in_src)
+               cFYI(1, "transact2 2nd response contains too much data");
+
+       /* find end of first SMB data area */
+       data_area_of_tgt = (char *)&pSMBt->hdr.Protocol +
+                               get_unaligned_le16(&pSMBt->t2_rsp.DataOffset);
+
+       /* validate target area */
+       data_area_of_src = (char *)&pSMBs->hdr.Protocol +
+                               get_unaligned_le16(&pSMBs->t2_rsp.DataOffset);
+
+       data_area_of_tgt += total_in_tgt;
+
+       total_in_tgt += total_in_src;
+       /* is the result too big for the field? */
+       if (total_in_tgt > USHRT_MAX) {
+               cFYI(1, "coalesced DataCount too large (%u)", total_in_tgt);
+               return -EPROTO;
+       }
+       put_unaligned_le16(total_in_tgt, &pSMBt->t2_rsp.DataCount);
+
+       /* fix up the BCC */
+       byte_count = get_bcc(target_hdr);
+       byte_count += total_in_src;
+       /* is the result too big for the field? */
+       if (byte_count > USHRT_MAX) {
+               cFYI(1, "coalesced BCC too large (%u)", byte_count);
+               return -EPROTO;
+       }
+       put_bcc(byte_count, target_hdr);
+
+       byte_count = be32_to_cpu(target_hdr->smb_buf_length);
+       byte_count += total_in_src;
+       /* don't allow buffer to overflow */
+       if (byte_count > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4) {
+               cFYI(1, "coalesced BCC exceeds buffer size (%u)", byte_count);
+               return -ENOBUFS;
+       }
+       target_hdr->smb_buf_length = cpu_to_be32(byte_count);
+
+       /* copy second buffer into end of first buffer */
+       memcpy(data_area_of_tgt, data_area_of_src, total_in_src);
+
+       if (remaining != total_in_src) {
+               /* more responses to go */
+               cFYI(1, "waiting for more secondary responses");
+               return 1;
+       }
+
+       /* we are done */
+       cFYI(1, "found the last secondary response");
+       return 0;
+}
+
+static bool
+cifs_check_trans2(struct mid_q_entry *mid, struct TCP_Server_Info *server,
+                 char *buf, int malformed)
+{
+       if (malformed)
+               return false;
+       if (check2ndT2(buf) <= 0)
+               return false;
+       mid->multiRsp = true;
+       if (mid->resp_buf) {
+               /* merge response - fix up 1st*/
+               malformed = coalesce_t2(buf, mid->resp_buf);
+               if (malformed > 0)
+                       return true;
+               /* All parts received or packet is malformed. */
+               mid->multiEnd = true;
+               dequeue_mid(mid, malformed);
+               return true;
+       }
+       if (!server->large_buf) {
+               /*FIXME: switch to already allocated largebuf?*/
+               cERROR(1, "1st trans2 resp needs bigbuf");
+       } else {
+               /* Have first buffer */
+               mid->resp_buf = buf;
+               mid->large_buf = true;
+               server->bigbuf = NULL;
+       }
+       return true;
+}
+
+static bool
+cifs_need_neg(struct TCP_Server_Info *server)
+{
+       return server->maxBuf == 0;
+}
+
+static int
+cifs_negotiate(const unsigned int xid, struct cifs_ses *ses)
+{
+       int rc;
+       rc = CIFSSMBNegotiate(xid, ses);
+       if (rc == -EAGAIN) {
+               /* retry only once on 1st time connection */
+               set_credits(ses->server, 1);
+               rc = CIFSSMBNegotiate(xid, ses);
+               if (rc == -EAGAIN)
+                       rc = -EHOSTDOWN;
+       }
+       return rc;
+}
+
+static void
+cifs_qfs_tcon(const unsigned int xid, struct cifs_tcon *tcon)
+{
+       CIFSSMBQFSDeviceInfo(xid, tcon);
+       CIFSSMBQFSAttributeInfo(xid, tcon);
+}
+
+static int
+cifs_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon,
+                       struct cifs_sb_info *cifs_sb, const char *full_path)
+{
+       int rc;
+       FILE_ALL_INFO *file_info;
+
+       file_info = kmalloc(sizeof(FILE_ALL_INFO), GFP_KERNEL);
+       if (file_info == NULL)
+               return -ENOMEM;
+
+       rc = CIFSSMBQPathInfo(xid, tcon, full_path, file_info,
+                             0 /* not legacy */, cifs_sb->local_nls,
+                             cifs_sb->mnt_cifs_flags &
+                               CIFS_MOUNT_MAP_SPECIAL_CHR);
+
+       if (rc == -EOPNOTSUPP || rc == -EINVAL)
+               rc = SMBQueryInformation(xid, tcon, full_path, file_info,
+                               cifs_sb->local_nls, cifs_sb->mnt_cifs_flags &
+                                 CIFS_MOUNT_MAP_SPECIAL_CHR);
+       kfree(file_info);
+       return rc;
+}
+
+static int
+cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
+                    struct cifs_sb_info *cifs_sb, const char *full_path,
+                    FILE_ALL_INFO *data, bool *adjustTZ)
+{
+       int rc;
+
+       /* could do find first instead but this returns more info */
+       rc = CIFSSMBQPathInfo(xid, tcon, full_path, data, 0 /* not legacy */,
+                             cifs_sb->local_nls, cifs_sb->mnt_cifs_flags &
+                                               CIFS_MOUNT_MAP_SPECIAL_CHR);
+       /*
+        * BB optimize code so we do not make the above call when server claims
+        * no NT SMB support and the above call failed at least once - set flag
+        * in tcon or mount.
+        */
+       if ((rc == -EOPNOTSUPP) || (rc == -EINVAL)) {
+               rc = SMBQueryInformation(xid, tcon, full_path, data,
+                                        cifs_sb->local_nls,
+                                        cifs_sb->mnt_cifs_flags &
+                                               CIFS_MOUNT_MAP_SPECIAL_CHR);
+               *adjustTZ = true;
+       }
+       return rc;
+}
+
+static int
+cifs_get_srv_inum(const unsigned int xid, struct cifs_tcon *tcon,
+                 struct cifs_sb_info *cifs_sb, const char *full_path,
+                 u64 *uniqueid, FILE_ALL_INFO *data)
+{
+       /*
+        * We can not use the IndexNumber field by default from Windows or
+        * Samba (in ALL_INFO buf) but we can request it explicitly. The SNIA
+        * CIFS spec claims that this value is unique within the scope of a
+        * share, and the windows docs hint that it's actually unique
+        * per-machine.
+        *
+        * There may be higher info levels that work but are there Windows
+        * server or network appliances for which IndexNumber field is not
+        * guaranteed unique?
+        */
+       return CIFSGetSrvInodeNumber(xid, tcon, full_path, uniqueid,
+                                    cifs_sb->local_nls,
+                                    cifs_sb->mnt_cifs_flags &
+                                               CIFS_MOUNT_MAP_SPECIAL_CHR);
+}
+
+static char *
+cifs_build_path_to_root(struct smb_vol *vol, struct cifs_sb_info *cifs_sb,
+                       struct cifs_tcon *tcon)
+{
+       int pplen = vol->prepath ? strlen(vol->prepath) : 0;
+       int dfsplen;
+       char *full_path = NULL;
+
+       /* if no prefix path, simply set path to the root of share to "" */
+       if (pplen == 0) {
+               full_path = kzalloc(1, GFP_KERNEL);
+               return full_path;
+       }
+
+       if (tcon->Flags & SMB_SHARE_IS_IN_DFS)
+               dfsplen = strnlen(tcon->treeName, MAX_TREE_SIZE + 1);
+       else
+               dfsplen = 0;
+
+       full_path = kmalloc(dfsplen + pplen + 1, GFP_KERNEL);
+       if (full_path == NULL)
+               return full_path;
+
+       if (dfsplen)
+               strncpy(full_path, tcon->treeName, dfsplen);
+       strncpy(full_path + dfsplen, vol->prepath, pplen);
+       convert_delimiter(full_path, CIFS_DIR_SEP(cifs_sb));
+       full_path[dfsplen + pplen] = 0; /* add trailing null */
+       return full_path;
+}
+
+static void
+cifs_clear_stats(struct cifs_tcon *tcon)
+{
+#ifdef CONFIG_CIFS_STATS
+       atomic_set(&tcon->stats.cifs_stats.num_writes, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_reads, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_flushes, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_oplock_brks, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_opens, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_posixopens, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_posixmkdirs, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_closes, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_deletes, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_mkdirs, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_rmdirs, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_renames, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_t2renames, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_ffirst, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_fnext, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_fclose, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_hardlinks, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_symlinks, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_locks, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_acl_get, 0);
+       atomic_set(&tcon->stats.cifs_stats.num_acl_set, 0);
+#endif
+}
+
+static void
+cifs_print_stats(struct seq_file *m, struct cifs_tcon *tcon)
+{
+#ifdef CONFIG_CIFS_STATS
+       seq_printf(m, " Oplocks breaks: %d",
+                  atomic_read(&tcon->stats.cifs_stats.num_oplock_brks));
+       seq_printf(m, "\nReads:  %d Bytes: %llu",
+                  atomic_read(&tcon->stats.cifs_stats.num_reads),
+                  (long long)(tcon->bytes_read));
+       seq_printf(m, "\nWrites: %d Bytes: %llu",
+                  atomic_read(&tcon->stats.cifs_stats.num_writes),
+                  (long long)(tcon->bytes_written));
+       seq_printf(m, "\nFlushes: %d",
+                  atomic_read(&tcon->stats.cifs_stats.num_flushes));
+       seq_printf(m, "\nLocks: %d HardLinks: %d Symlinks: %d",
+                  atomic_read(&tcon->stats.cifs_stats.num_locks),
+                  atomic_read(&tcon->stats.cifs_stats.num_hardlinks),
+                  atomic_read(&tcon->stats.cifs_stats.num_symlinks));
+       seq_printf(m, "\nOpens: %d Closes: %d Deletes: %d",
+                  atomic_read(&tcon->stats.cifs_stats.num_opens),
+                  atomic_read(&tcon->stats.cifs_stats.num_closes),
+                  atomic_read(&tcon->stats.cifs_stats.num_deletes));
+       seq_printf(m, "\nPosix Opens: %d Posix Mkdirs: %d",
+                  atomic_read(&tcon->stats.cifs_stats.num_posixopens),
+                  atomic_read(&tcon->stats.cifs_stats.num_posixmkdirs));
+       seq_printf(m, "\nMkdirs: %d Rmdirs: %d",
+                  atomic_read(&tcon->stats.cifs_stats.num_mkdirs),
+                  atomic_read(&tcon->stats.cifs_stats.num_rmdirs));
+       seq_printf(m, "\nRenames: %d T2 Renames %d",
+                  atomic_read(&tcon->stats.cifs_stats.num_renames),
+                  atomic_read(&tcon->stats.cifs_stats.num_t2renames));
+       seq_printf(m, "\nFindFirst: %d FNext %d FClose %d",
+                  atomic_read(&tcon->stats.cifs_stats.num_ffirst),
+                  atomic_read(&tcon->stats.cifs_stats.num_fnext),
+                  atomic_read(&tcon->stats.cifs_stats.num_fclose));
+#endif
+}
+
 struct smb_version_operations smb1_operations = {
        .send_cancel = send_nt_cancel,
        .compare_fids = cifs_compare_fids,
        .setup_request = cifs_setup_request,
+       .setup_async_request = cifs_setup_async_request,
        .check_receive = cifs_check_receive,
        .add_credits = cifs_add_credits,
        .set_credits = cifs_set_credits,
        .get_credits_field = cifs_get_credits_field,
+       .get_credits = cifs_get_credits,
        .get_next_mid = cifs_get_next_mid,
        .read_data_offset = cifs_read_data_offset,
        .read_data_length = cifs_read_data_length,
@@ -228,7 +603,23 @@ struct smb_version_operations smb1_operations = {
        .find_mid = cifs_find_mid,
        .check_message = checkSMB,
        .dump_detail = cifs_dump_detail,
+       .clear_stats = cifs_clear_stats,
+       .print_stats = cifs_print_stats,
        .is_oplock_break = is_valid_oplock_break,
+       .check_trans2 = cifs_check_trans2,
+       .need_neg = cifs_need_neg,
+       .negotiate = cifs_negotiate,
+       .sess_setup = CIFS_SessSetup,
+       .logoff = CIFSSMBLogoff,
+       .tree_connect = CIFSTCon,
+       .tree_disconnect = CIFSSMBTDis,
+       .get_dfs_refer = CIFSGetDFSRefer,
+       .qfs_tcon = cifs_qfs_tcon,
+       .is_path_accessible = cifs_is_path_accessible,
+       .query_path_info = cifs_query_path_info,
+       .get_srv_inum = cifs_get_srv_inum,
+       .build_path_to_root = cifs_build_path_to_root,
+       .echo = CIFSSMBEcho,
 };
 
 struct smb_version_values smb1_values = {
@@ -240,4 +631,8 @@ struct smb_version_values smb1_values = {
        .header_size = sizeof(struct smb_hdr),
        .max_header_size = MAX_CIFS_HDR_SIZE,
        .read_rsp_size = sizeof(READ_RSP),
+       .lock_cmd = cpu_to_le16(SMB_COM_LOCKING_ANDX),
+       .cap_unix = CAP_UNIX,
+       .cap_nt_find = CAP_NT_SMBS | CAP_NT_FIND,
+       .cap_large_files = CAP_LARGE_FILES,
 };