diff options
Diffstat (limited to 'fs/cifs/smb2pdu.c')
-rw-r--r-- | fs/cifs/smb2pdu.c | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index a1314f99b4cc..21b3a652e192 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -45,6 +45,7 @@ #include "ntlmssp.h" #include "smb2status.h" #include "smb2glob.h" +#include "cifspdu.h" /* * The following table defines the expected "StructureSize" of SMB2 requests @@ -1603,6 +1604,173 @@ SMB2_write(const unsigned int xid, struct cifs_io_parms *io_parms, return rc; } +static unsigned int +num_entries(char *bufstart, char *end_of_buf, char **lastentry, size_t size) +{ + int len; + unsigned int entrycount = 0; + unsigned int next_offset = 0; + FILE_DIRECTORY_INFO *entryptr; + + if (bufstart == NULL) + return 0; + + entryptr = (FILE_DIRECTORY_INFO *)bufstart; + + while (1) { + entryptr = (FILE_DIRECTORY_INFO *) + ((char *)entryptr + next_offset); + + if ((char *)entryptr + size > end_of_buf) { + cERROR(1, "malformed search entry would overflow"); + break; + } + + len = le32_to_cpu(entryptr->FileNameLength); + if ((char *)entryptr + len + size > end_of_buf) { + cERROR(1, "directory entry name would overflow frame " + "end of buf %p", end_of_buf); + break; + } + + *lastentry = (char *)entryptr; + entrycount++; + + next_offset = le32_to_cpu(entryptr->NextEntryOffset); + if (!next_offset) + break; + } + + return entrycount; +} + +/* + * Readdir/FindFirst + */ +int +SMB2_query_directory(const unsigned int xid, struct cifs_tcon *tcon, + u64 persistent_fid, u64 volatile_fid, int index, + struct cifs_search_info *srch_inf) +{ + struct smb2_query_directory_req *req; + struct smb2_query_directory_rsp *rsp = NULL; + struct kvec iov[2]; + int rc = 0; + int len; + int resp_buftype; + unsigned char *bufptr; + struct TCP_Server_Info *server; + struct cifs_ses *ses = tcon->ses; + __le16 asteriks = cpu_to_le16('*'); + char *end_of_smb; + unsigned int output_size = CIFSMaxBufSize; + size_t info_buf_size; + + if (ses && (ses->server)) + server = ses->server; + else + return -EIO; + + rc = small_smb2_init(SMB2_QUERY_DIRECTORY, tcon, (void **) &req); + if (rc) + return rc; + + switch (srch_inf->info_level) { + case SMB_FIND_FILE_DIRECTORY_INFO: + req->FileInformationClass = FILE_DIRECTORY_INFORMATION; + info_buf_size = sizeof(FILE_DIRECTORY_INFO) - 1; + break; + case SMB_FIND_FILE_ID_FULL_DIR_INFO: + req->FileInformationClass = FILEID_FULL_DIRECTORY_INFORMATION; + info_buf_size = sizeof(SEARCH_ID_FULL_DIR_INFO) - 1; + break; + default: + cERROR(1, "info level %u isn't supported", + srch_inf->info_level); + rc = -EINVAL; + goto qdir_exit; + } + + req->FileIndex = cpu_to_le32(index); + req->PersistentFileId = persistent_fid; + req->VolatileFileId = volatile_fid; + + len = 0x2; + bufptr = req->Buffer; + memcpy(bufptr, &asteriks, len); + + req->FileNameOffset = + cpu_to_le16(sizeof(struct smb2_query_directory_req) - 1 - 4); + req->FileNameLength = cpu_to_le16(len); + /* + * BB could be 30 bytes or so longer if we used SMB2 specific + * buffer lengths, but this is safe and close enough. + */ + output_size = min_t(unsigned int, output_size, server->maxBuf); + output_size = min_t(unsigned int, output_size, 2 << 15); + req->OutputBufferLength = cpu_to_le32(output_size); + + iov[0].iov_base = (char *)req; + /* 4 for RFC1001 length and 1 for Buffer */ + iov[0].iov_len = get_rfc1002_length(req) + 4 - 1; + + iov[1].iov_base = (char *)(req->Buffer); + iov[1].iov_len = len; + + inc_rfc1001_len(req, len - 1 /* Buffer */); + + rc = SendReceive2(xid, ses, iov, 2, &resp_buftype, 0); + if (rc) { + cifs_stats_fail_inc(tcon, SMB2_QUERY_DIRECTORY_HE); + goto qdir_exit; + } + rsp = (struct smb2_query_directory_rsp *)iov[0].iov_base; + + rc = validate_buf(le16_to_cpu(rsp->OutputBufferOffset), + le32_to_cpu(rsp->OutputBufferLength), &rsp->hdr, + info_buf_size); + if (rc) + goto qdir_exit; + + srch_inf->unicode = true; + + if (srch_inf->ntwrk_buf_start) { + if (srch_inf->smallBuf) + cifs_small_buf_release(srch_inf->ntwrk_buf_start); + else + cifs_buf_release(srch_inf->ntwrk_buf_start); + } + srch_inf->ntwrk_buf_start = (char *)rsp; + srch_inf->srch_entries_start = srch_inf->last_entry = 4 /* rfclen */ + + (char *)&rsp->hdr + le16_to_cpu(rsp->OutputBufferOffset); + /* 4 for rfc1002 length field */ + end_of_smb = get_rfc1002_length(rsp) + 4 + (char *)&rsp->hdr; + srch_inf->entries_in_buffer = + num_entries(srch_inf->srch_entries_start, end_of_smb, + &srch_inf->last_entry, info_buf_size); + srch_inf->index_of_last_entry += srch_inf->entries_in_buffer; + cFYI(1, "num entries %d last_index %lld srch start %p srch end %p", + srch_inf->entries_in_buffer, srch_inf->index_of_last_entry, + srch_inf->srch_entries_start, srch_inf->last_entry); + if (resp_buftype == CIFS_LARGE_BUFFER) + srch_inf->smallBuf = false; + else if (resp_buftype == CIFS_SMALL_BUFFER) + srch_inf->smallBuf = true; + else + cERROR(1, "illegal search buffer type"); + + if (rsp->hdr.Status == STATUS_NO_MORE_FILES) + srch_inf->endOfSearch = 1; + else + srch_inf->endOfSearch = 0; + + return rc; + +qdir_exit: + free_rsp_buf(resp_buftype, rsp); + return rc; +} + static int send_set_info(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, u64 volatile_fid, u32 pid, int info_class, |