ASoC: rockchip: add support for rk3228 spdif
[firefly-linux-kernel-4.4.55.git] / fs / cifs / smb2pdu.c
index 61276929d139f64d1309481e69f2dfce2c8f1fe5..6cb2603f8a5c3685159eef3c549e6d558613d61d 100644 (file)
@@ -103,7 +103,21 @@ smb2_hdr_assemble(struct smb2_hdr *hdr, __le16 smb2_cmd /* command */ ,
        hdr->ProtocolId[3] = 'B';
        hdr->StructureSize = cpu_to_le16(64);
        hdr->Command = smb2_cmd;
-       hdr->CreditRequest = cpu_to_le16(2); /* BB make this dynamic */
+       if (tcon && tcon->ses && tcon->ses->server) {
+               struct TCP_Server_Info *server = tcon->ses->server;
+
+               spin_lock(&server->req_lock);
+               /* Request up to 2 credits but don't go over the limit. */
+               if (server->credits >= SMB2_MAX_CREDITS_AVAILABLE)
+                       hdr->CreditRequest = cpu_to_le16(0);
+               else
+                       hdr->CreditRequest = cpu_to_le16(
+                               min_t(int, SMB2_MAX_CREDITS_AVAILABLE -
+                                               server->credits, 2));
+               spin_unlock(&server->req_lock);
+       } else {
+               hdr->CreditRequest = cpu_to_le16(2);
+       }
        hdr->ProcessId = cpu_to_le32((__u16)current->tgid);
 
        if (!tcon)
@@ -264,7 +278,7 @@ out:
        case SMB2_CHANGE_NOTIFY:
        case SMB2_QUERY_INFO:
        case SMB2_SET_INFO:
-               return -EAGAIN;
+               rc = -EAGAIN;
        }
        unload_nls(nls_codepage);
        return rc;
@@ -591,8 +605,9 @@ SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses,
        u16 blob_length = 0;
        struct key *spnego_key = NULL;
        char *security_blob = NULL;
-       char *ntlmssp_blob = NULL;
+       unsigned char *ntlmssp_blob = NULL;
        bool use_spnego = false; /* else use raw ntlmssp */
+       u64 previous_session = ses->Suid;
 
        cifs_dbg(FYI, "Session Setup\n");
 
@@ -630,6 +645,10 @@ ssetup_ntlmssp_authenticate:
                return rc;
 
        req->hdr.SessionId = 0; /* First session, not a reauthenticate */
+
+       /* if reconnect, we need to send previous sess id, otherwise it is 0 */
+       req->PreviousSessionId = previous_session;
+
        req->Flags = 0; /* MBZ */
        /* to enable echos and oplocks */
        req->hdr.CreditRequest = cpu_to_le16(3);
@@ -716,13 +735,7 @@ ssetup_ntlmssp_authenticate:
                iov[1].iov_len = blob_length;
        } else if (phase == NtLmAuthenticate) {
                req->hdr.SessionId = ses->Suid;
-               ntlmssp_blob = kzalloc(sizeof(struct _NEGOTIATE_MESSAGE) + 500,
-                                      GFP_KERNEL);
-               if (ntlmssp_blob == NULL) {
-                       rc = -ENOMEM;
-                       goto ssetup_exit;
-               }
-               rc = build_ntlmssp_auth_blob(ntlmssp_blob, &blob_length, ses,
+               rc = build_ntlmssp_auth_blob(&ntlmssp_blob, &blob_length, ses,
                                             nls_cp);
                if (rc) {
                        cifs_dbg(FYI, "build_ntlmssp_auth_blob failed %d\n",
@@ -919,9 +932,6 @@ SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree,
        else
                return -EIO;
 
-       if (tcon && tcon->bad_network_name)
-               return -ENOENT;
-
        if ((tcon && tcon->seal) &&
            ((ses->server->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION) == 0)) {
                cifs_dbg(VFS, "encryption requested but no server support");
@@ -939,6 +949,10 @@ SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree,
                return -EINVAL;
        }
 
+       /* SMB2 TREE_CONNECT request must be called with TreeId == 0 */
+       if (tcon)
+               tcon->tid = 0;
+
        rc = small_smb2_init(SMB2_TREE_CONNECT, tcon, (void **) &req);
        if (rc) {
                kfree(unc_path);
@@ -1019,8 +1033,6 @@ tcon_exit:
 tcon_error_exit:
        if (rsp->hdr.Status == STATUS_BAD_NETWORK_NAME) {
                cifs_dbg(VFS, "BAD_NETWORK_NAME: %s\n", tree);
-               if (tcon)
-                       tcon->bad_network_name = true;
        }
        goto tcon_exit;
 }
@@ -1109,21 +1121,25 @@ parse_lease_state(struct TCP_Server_Info *server, struct smb2_create_rsp *rsp,
 {
        char *data_offset;
        struct create_context *cc;
-       unsigned int next = 0;
+       unsigned int next;
+       unsigned int remaining;
        char *name;
 
        data_offset = (char *)rsp + 4 + le32_to_cpu(rsp->CreateContextsOffset);
+       remaining = le32_to_cpu(rsp->CreateContextsLength);
        cc = (struct create_context *)data_offset;
-       do {
-               cc = (struct create_context *)((char *)cc + next);
+       while (remaining >= sizeof(struct create_context)) {
                name = le16_to_cpu(cc->NameOffset) + (char *)cc;
-               if (le16_to_cpu(cc->NameLength) != 4 ||
-                   strncmp(name, "RqLs", 4)) {
-                       next = le32_to_cpu(cc->Next);
-                       continue;
-               }
-               return server->ops->parse_lease_buf(cc, epoch);
-       } while (next != 0);
+               if (le16_to_cpu(cc->NameLength) == 4 &&
+                   strncmp(name, "RqLs", 4) == 0)
+                       return server->ops->parse_lease_buf(cc, epoch);
+
+               next = le32_to_cpu(cc->Next);
+               if (!next)
+                       break;
+               remaining -= next;
+               cc = (struct create_context *)((char *)cc + next);
+       }
 
        return 0;
 }
@@ -1151,13 +1167,130 @@ add_lease_context(struct TCP_Server_Info *server, struct kvec *iov,
        return 0;
 }
 
+static struct create_durable_v2 *
+create_durable_v2_buf(struct cifs_fid *pfid)
+{
+       struct create_durable_v2 *buf;
+
+       buf = kzalloc(sizeof(struct create_durable_v2), GFP_KERNEL);
+       if (!buf)
+               return NULL;
+
+       buf->ccontext.DataOffset = cpu_to_le16(offsetof
+                                       (struct create_durable_v2, dcontext));
+       buf->ccontext.DataLength = cpu_to_le32(sizeof(struct durable_context_v2));
+       buf->ccontext.NameOffset = cpu_to_le16(offsetof
+                               (struct create_durable_v2, Name));
+       buf->ccontext.NameLength = cpu_to_le16(4);
+
+       buf->dcontext.Timeout = 0; /* Should this be configurable by workload */
+       buf->dcontext.Flags = cpu_to_le32(SMB2_DHANDLE_FLAG_PERSISTENT);
+       generate_random_uuid(buf->dcontext.CreateGuid);
+       memcpy(pfid->create_guid, buf->dcontext.CreateGuid, 16);
+
+       /* SMB2_CREATE_DURABLE_HANDLE_REQUEST is "DH2Q" */
+       buf->Name[0] = 'D';
+       buf->Name[1] = 'H';
+       buf->Name[2] = '2';
+       buf->Name[3] = 'Q';
+       return buf;
+}
+
+static struct create_durable_handle_reconnect_v2 *
+create_reconnect_durable_v2_buf(struct cifs_fid *fid)
+{
+       struct create_durable_handle_reconnect_v2 *buf;
+
+       buf = kzalloc(sizeof(struct create_durable_handle_reconnect_v2),
+                       GFP_KERNEL);
+       if (!buf)
+               return NULL;
+
+       buf->ccontext.DataOffset =
+               cpu_to_le16(offsetof(struct create_durable_handle_reconnect_v2,
+                                    dcontext));
+       buf->ccontext.DataLength =
+               cpu_to_le32(sizeof(struct durable_reconnect_context_v2));
+       buf->ccontext.NameOffset =
+               cpu_to_le16(offsetof(struct create_durable_handle_reconnect_v2,
+                           Name));
+       buf->ccontext.NameLength = cpu_to_le16(4);
+
+       buf->dcontext.Fid.PersistentFileId = fid->persistent_fid;
+       buf->dcontext.Fid.VolatileFileId = fid->volatile_fid;
+       buf->dcontext.Flags = cpu_to_le32(SMB2_DHANDLE_FLAG_PERSISTENT);
+       memcpy(buf->dcontext.CreateGuid, fid->create_guid, 16);
+
+       /* SMB2_CREATE_DURABLE_HANDLE_RECONNECT_V2 is "DH2C" */
+       buf->Name[0] = 'D';
+       buf->Name[1] = 'H';
+       buf->Name[2] = '2';
+       buf->Name[3] = 'C';
+       return buf;
+}
+
 static int
-add_durable_context(struct kvec *iov, unsigned int *num_iovec,
+add_durable_v2_context(struct kvec *iov, unsigned int *num_iovec,
                    struct cifs_open_parms *oparms)
 {
        struct smb2_create_req *req = iov[0].iov_base;
        unsigned int num = *num_iovec;
 
+       iov[num].iov_base = create_durable_v2_buf(oparms->fid);
+       if (iov[num].iov_base == NULL)
+               return -ENOMEM;
+       iov[num].iov_len = sizeof(struct create_durable_v2);
+       if (!req->CreateContextsOffset)
+               req->CreateContextsOffset =
+                       cpu_to_le32(sizeof(struct smb2_create_req) - 4 +
+                                                               iov[1].iov_len);
+       le32_add_cpu(&req->CreateContextsLength, sizeof(struct create_durable_v2));
+       inc_rfc1001_len(&req->hdr, sizeof(struct create_durable_v2));
+       *num_iovec = num + 1;
+       return 0;
+}
+
+static int
+add_durable_reconnect_v2_context(struct kvec *iov, unsigned int *num_iovec,
+                   struct cifs_open_parms *oparms)
+{
+       struct smb2_create_req *req = iov[0].iov_base;
+       unsigned int num = *num_iovec;
+
+       /* indicate that we don't need to relock the file */
+       oparms->reconnect = false;
+
+       iov[num].iov_base = create_reconnect_durable_v2_buf(oparms->fid);
+       if (iov[num].iov_base == NULL)
+               return -ENOMEM;
+       iov[num].iov_len = sizeof(struct create_durable_handle_reconnect_v2);
+       if (!req->CreateContextsOffset)
+               req->CreateContextsOffset =
+                       cpu_to_le32(sizeof(struct smb2_create_req) - 4 +
+                                                               iov[1].iov_len);
+       le32_add_cpu(&req->CreateContextsLength,
+                       sizeof(struct create_durable_handle_reconnect_v2));
+       inc_rfc1001_len(&req->hdr,
+                       sizeof(struct create_durable_handle_reconnect_v2));
+       *num_iovec = num + 1;
+       return 0;
+}
+
+static int
+add_durable_context(struct kvec *iov, unsigned int *num_iovec,
+                   struct cifs_open_parms *oparms, bool use_persistent)
+{
+       struct smb2_create_req *req = iov[0].iov_base;
+       unsigned int num = *num_iovec;
+
+       if (use_persistent) {
+               if (oparms->reconnect)
+                       return add_durable_reconnect_v2_context(iov, num_iovec,
+                                                               oparms);
+               else
+                       return add_durable_v2_context(iov, num_iovec, oparms);
+       }
+
        if (oparms->reconnect) {
                iov[num].iov_base = create_reconnect_durable_buf(oparms->fid);
                /* indicate that we don't need to relock the file */
@@ -1275,7 +1408,9 @@ SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path,
                        ccontext->Next =
                                cpu_to_le32(server->vals->create_lease_size);
                }
-               rc = add_durable_context(iov, &num_iovecs, oparms);
+
+               rc = add_durable_context(iov, &num_iovecs, oparms,
+                                       tcon->use_persistent);
                if (rc) {
                        cifs_small_buf_release(req);
                        kfree(copy_path);
@@ -1686,6 +1821,54 @@ smb2_echo_callback(struct mid_q_entry *mid)
        add_credits(server, credits_received, CIFS_ECHO_OP);
 }
 
+void smb2_reconnect_server(struct work_struct *work)
+{
+       struct TCP_Server_Info *server = container_of(work,
+                                       struct TCP_Server_Info, reconnect.work);
+       struct cifs_ses *ses;
+       struct cifs_tcon *tcon, *tcon2;
+       struct list_head tmp_list;
+       int tcon_exist = false;
+
+       /* Prevent simultaneous reconnects that can corrupt tcon->rlist list */
+       mutex_lock(&server->reconnect_mutex);
+
+       INIT_LIST_HEAD(&tmp_list);
+       cifs_dbg(FYI, "Need negotiate, reconnecting tcons\n");
+
+       spin_lock(&cifs_tcp_ses_lock);
+       list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
+               list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
+                       if (tcon->need_reconnect) {
+                               tcon->tc_count++;
+                               list_add_tail(&tcon->rlist, &tmp_list);
+                               tcon_exist = true;
+                       }
+               }
+       }
+       /*
+        * Get the reference to server struct to be sure that the last call of
+        * cifs_put_tcon() in the loop below won't release the server pointer.
+        */
+       if (tcon_exist)
+               server->srv_count++;
+
+       spin_unlock(&cifs_tcp_ses_lock);
+
+       list_for_each_entry_safe(tcon, tcon2, &tmp_list, rlist) {
+               smb2_reconnect(SMB2_ECHO, tcon);
+               list_del_init(&tcon->rlist);
+               cifs_put_tcon(tcon);
+       }
+
+       cifs_dbg(FYI, "Reconnecting tcons finished\n");
+       mutex_unlock(&server->reconnect_mutex);
+
+       /* now we can safely release srv struct */
+       if (tcon_exist)
+               cifs_put_tcp_session(server, 1);
+}
+
 int
 SMB2_echo(struct TCP_Server_Info *server)
 {
@@ -1697,6 +1880,12 @@ SMB2_echo(struct TCP_Server_Info *server)
 
        cifs_dbg(FYI, "In echo request\n");
 
+       if (server->tcpStatus == CifsNeedNegotiate) {
+               /* No need to send echo on newly established connections */
+               queue_delayed_work(cifsiod_wq, &server->reconnect, 0);
+               return rc;
+       }
+
        rc = small_smb2_init(SMB2_ECHO, NULL, (void **)&req);
        if (rc)
                return rc;
@@ -1915,6 +2104,7 @@ smb2_async_readv(struct cifs_readdata *rdata)
        if (rdata->credits) {
                buf->CreditCharge = cpu_to_le16(DIV_ROUND_UP(rdata->bytes,
                                                SMB2_MAX_BUFFER_SIZE));
+               buf->CreditRequest = buf->CreditCharge;
                spin_lock(&server->req_lock);
                server->credits += rdata->credits -
                                                le16_to_cpu(buf->CreditCharge);
@@ -2101,6 +2291,7 @@ smb2_async_writev(struct cifs_writedata *wdata,
        if (wdata->credits) {
                req->hdr.CreditCharge = cpu_to_le16(DIV_ROUND_UP(wdata->bytes,
                                                    SMB2_MAX_BUFFER_SIZE));
+               req->hdr.CreditRequest = req->hdr.CreditCharge;
                spin_lock(&server->req_lock);
                server->credits += wdata->credits -
                                        le16_to_cpu(req->hdr.CreditCharge);
@@ -2453,6 +2644,22 @@ SMB2_rename(const unsigned int xid, struct cifs_tcon *tcon,
        return rc;
 }
 
+int
+SMB2_rmdir(const unsigned int xid, struct cifs_tcon *tcon,
+                 u64 persistent_fid, u64 volatile_fid)
+{
+       __u8 delete_pending = 1;
+       void *data;
+       unsigned int size;
+
+       data = &delete_pending;
+       size = 1; /* sizeof __u8 */
+
+       return send_set_info(xid, tcon, persistent_fid, volatile_fid,
+                       current->tgid, FILE_DISPOSITION_INFORMATION, 1, &data,
+                       &size);
+}
+
 int
 SMB2_set_hardlink(const unsigned int xid, struct cifs_tcon *tcon,
                  u64 persistent_fid, u64 volatile_fid, __le16 *target_file)