diff options
Diffstat (limited to 'fs/cifs/smb2ops.c')
-rw-r--r-- | fs/cifs/smb2ops.c | 309 |
1 files changed, 230 insertions, 79 deletions
diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index c36ff0d1fe2a..a930c8965e5c 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -1382,6 +1382,26 @@ smb2_ioctl_query_info(const unsigned int xid, oparms.fid = &fid; oparms.reconnect = false; + /* + * FSCTL codes encode the special access they need in the fsctl code. + */ + if (qi.flags & PASSTHRU_FSCTL) { + switch (qi.info_type & FSCTL_DEVICE_ACCESS_MASK) { + case FSCTL_DEVICE_ACCESS_FILE_READ_WRITE_ACCESS: + oparms.desired_access = FILE_READ_DATA | FILE_WRITE_DATA | FILE_READ_ATTRIBUTES | SYNCHRONIZE; + break; + case FSCTL_DEVICE_ACCESS_FILE_ANY_ACCESS: + oparms.desired_access = GENERIC_ALL; + break; + case FSCTL_DEVICE_ACCESS_FILE_READ_ACCESS: + oparms.desired_access = GENERIC_READ; + break; + case FSCTL_DEVICE_ACCESS_FILE_WRITE_ACCESS: + oparms.desired_access = GENERIC_WRITE; + break; + } + } + rc = SMB2_open_init(tcon, &rqst[0], &oplock, &oparms, path); if (rc) goto iqinf_exit; @@ -1399,8 +1419,9 @@ smb2_ioctl_query_info(const unsigned int xid, rc = SMB2_ioctl_init(tcon, &rqst[1], COMPOUND_FID, COMPOUND_FID, - qi.info_type, true, NULL, - 0, CIFSMaxBufSize); + qi.info_type, true, buffer, + qi.output_buffer_length, + CIFSMaxBufSize); } } else if (qi.flags == PASSTHRU_QUERY_INFO) { memset(&qi_iov, 0, sizeof(qi_iov)); @@ -1441,12 +1462,19 @@ smb2_ioctl_query_info(const unsigned int xid, io_rsp = (struct smb2_ioctl_rsp *)rsp_iov[1].iov_base; if (le32_to_cpu(io_rsp->OutputCount) < qi.input_buffer_length) qi.input_buffer_length = le32_to_cpu(io_rsp->OutputCount); + if (qi.input_buffer_length > 0 && + le32_to_cpu(io_rsp->OutputOffset) + qi.input_buffer_length > rsp_iov[1].iov_len) { + rc = -EFAULT; + goto iqinf_exit; + } if (copy_to_user(&pqi->input_buffer_length, &qi.input_buffer_length, sizeof(qi.input_buffer_length))) { rc = -EFAULT; goto iqinf_exit; } - if (copy_to_user(pqi + 1, &io_rsp[1], qi.input_buffer_length)) { + if (copy_to_user((void __user *)pqi + sizeof(struct smb_query_info), + (const void *)io_rsp + le32_to_cpu(io_rsp->OutputOffset), + qi.input_buffer_length)) { rc = -EFAULT; goto iqinf_exit; } @@ -1821,6 +1849,14 @@ smb3_enum_snapshots(const unsigned int xid, struct cifs_tcon *tcon, u32 max_response_size; struct smb_snapshot_array snapshot_in; + /* + * On the first query to enumerate the list of snapshots available + * for this volume the buffer begins with 0 (number of snapshots + * which can be returned is zero since at that point we do not know + * how big the buffer needs to be). On the second query, + * it (ret_data_len) is set to number of snapshots so we can + * know to set the maximum response size larger (see below). + */ if (get_user(ret_data_len, (unsigned int __user *)ioc_buf)) return -EFAULT; @@ -2354,46 +2390,129 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses, static int smb2_query_symlink(const unsigned int xid, struct cifs_tcon *tcon, - const char *full_path, char **target_path, - struct cifs_sb_info *cifs_sb) + struct cifs_sb_info *cifs_sb, const char *full_path, + char **target_path, bool is_reparse_point) { int rc; - __le16 *utf16_path; + __le16 *utf16_path = NULL; __u8 oplock = SMB2_OPLOCK_LEVEL_NONE; struct cifs_open_parms oparms; struct cifs_fid fid; struct kvec err_iov = {NULL, 0}; struct smb2_err_rsp *err_buf = NULL; - int resp_buftype; struct smb2_symlink_err_rsp *symlink; unsigned int sub_len; unsigned int sub_offset; unsigned int print_len; unsigned int print_offset; + int flags = 0; + struct smb_rqst rqst[3]; + int resp_buftype[3]; + struct kvec rsp_iov[3]; + struct kvec open_iov[SMB2_CREATE_IOV_SIZE]; + struct kvec io_iov[SMB2_IOCTL_IOV_SIZE]; + struct kvec close_iov[1]; + struct smb2_create_rsp *create_rsp; + struct smb2_ioctl_rsp *ioctl_rsp; + char *ioctl_buf; + u32 plen; cifs_dbg(FYI, "%s: path: %s\n", __func__, full_path); + if (smb3_encryption_required(tcon)) + flags |= CIFS_TRANSFORM_REQ; + + memset(rqst, 0, sizeof(rqst)); + resp_buftype[0] = resp_buftype[1] = resp_buftype[2] = CIFS_NO_BUFFER; + memset(rsp_iov, 0, sizeof(rsp_iov)); + utf16_path = cifs_convert_path_to_utf16(full_path, cifs_sb); if (!utf16_path) return -ENOMEM; + /* Open */ + memset(&open_iov, 0, sizeof(open_iov)); + rqst[0].rq_iov = open_iov; + rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE; + + memset(&oparms, 0, sizeof(oparms)); oparms.tcon = tcon; oparms.desired_access = FILE_READ_ATTRIBUTES; oparms.disposition = FILE_OPEN; + if (backup_cred(cifs_sb)) oparms.create_options = CREATE_OPEN_BACKUP_INTENT; else oparms.create_options = 0; + if (is_reparse_point) + oparms.create_options = OPEN_REPARSE_POINT; + oparms.fid = &fid; oparms.reconnect = false; - rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, &err_iov, - &resp_buftype); - if (!rc) - SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid); + rc = SMB2_open_init(tcon, &rqst[0], &oplock, &oparms, utf16_path); + if (rc) + goto querty_exit; + smb2_set_next_command(tcon, &rqst[0]); + + + /* IOCTL */ + memset(&io_iov, 0, sizeof(io_iov)); + rqst[1].rq_iov = io_iov; + rqst[1].rq_nvec = SMB2_IOCTL_IOV_SIZE; + + rc = SMB2_ioctl_init(tcon, &rqst[1], fid.persistent_fid, + fid.volatile_fid, FSCTL_GET_REPARSE_POINT, + true /* is_fctl */, NULL, 0, CIFSMaxBufSize); + if (rc) + goto querty_exit; + + smb2_set_next_command(tcon, &rqst[1]); + smb2_set_related(&rqst[1]); + + + /* Close */ + memset(&close_iov, 0, sizeof(close_iov)); + rqst[2].rq_iov = close_iov; + rqst[2].rq_nvec = 1; + + rc = SMB2_close_init(tcon, &rqst[2], COMPOUND_FID, COMPOUND_FID); + if (rc) + goto querty_exit; + + smb2_set_related(&rqst[2]); + + rc = compound_send_recv(xid, tcon->ses, flags, 3, rqst, + resp_buftype, rsp_iov); + + create_rsp = rsp_iov[0].iov_base; + if (create_rsp && create_rsp->sync_hdr.Status) + err_iov = rsp_iov[0]; + ioctl_rsp = rsp_iov[1].iov_base; + + /* + * Open was successful and we got an ioctl response. + */ + if ((rc == 0) && (is_reparse_point)) { + /* See MS-FSCC 2.3.23 */ + + ioctl_buf = (char *)ioctl_rsp + le32_to_cpu(ioctl_rsp->OutputOffset); + plen = le32_to_cpu(ioctl_rsp->OutputCount); + + if (plen + le32_to_cpu(ioctl_rsp->OutputOffset) > + rsp_iov[1].iov_len) { + cifs_dbg(VFS, "srv returned invalid ioctl length: %d\n", plen); + rc = -EIO; + goto querty_exit; + } + + /* Do stuff with ioctl_buf/plen */ + goto querty_exit; + } + if (!rc || !err_iov.iov_base) { rc = -ENOENT; - goto free_path; + goto querty_exit; } err_buf = err_iov.iov_base; @@ -2433,9 +2552,14 @@ smb2_query_symlink(const unsigned int xid, struct cifs_tcon *tcon, cifs_dbg(FYI, "%s: target path: %s\n", __func__, *target_path); querty_exit: - free_rsp_buf(resp_buftype, err_buf); - free_path: + cifs_dbg(FYI, "query symlink rc %d\n", rc); kfree(utf16_path); + SMB2_open_free(&rqst[0]); + SMB2_ioctl_free(&rqst[1]); + SMB2_close_free(&rqst[2]); + free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base); + free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base); + free_rsp_buf(resp_buftype[2], rsp_iov[2].iov_base); return rc; } @@ -2612,16 +2736,8 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, struct cifsInodeInfo *cifsi; struct cifsFileInfo *cfile = file->private_data; struct file_zero_data_information fsctl_buf; - struct smb_rqst rqst[2]; - int resp_buftype[2]; - struct kvec rsp_iov[2]; - struct kvec io_iov[SMB2_IOCTL_IOV_SIZE]; - struct kvec si_iov[1]; - unsigned int size[1]; - void *data[1]; long rc; unsigned int xid; - int num = 0, flags = 0; __le64 eof; xid = get_xid(); @@ -2643,39 +2759,16 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, return rc; } - /* - * Must check if file sparse since fallocate -z (zero range) assumes - * non-sparse allocation - */ - if (!(cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE)) { - rc = -EOPNOTSUPP; - trace_smb3_zero_err(xid, cfile->fid.persistent_fid, tcon->tid, - ses->Suid, offset, len, rc); - free_xid(xid); - return rc; - } - cifs_dbg(FYI, "offset %lld len %lld", offset, len); fsctl_buf.FileOffset = cpu_to_le64(offset); fsctl_buf.BeyondFinalZero = cpu_to_le64(offset + len); - if (smb3_encryption_required(tcon)) - flags |= CIFS_TRANSFORM_REQ; - - memset(rqst, 0, sizeof(rqst)); - resp_buftype[0] = resp_buftype[1] = CIFS_NO_BUFFER; - memset(rsp_iov, 0, sizeof(rsp_iov)); - - - memset(&io_iov, 0, sizeof(io_iov)); - rqst[num].rq_iov = io_iov; - rqst[num].rq_nvec = SMB2_IOCTL_IOV_SIZE; - rc = SMB2_ioctl_init(tcon, &rqst[num++], cfile->fid.persistent_fid, - cfile->fid.volatile_fid, FSCTL_SET_ZERO_DATA, - true /* is_fctl */, (char *)&fsctl_buf, - sizeof(struct file_zero_data_information), - CIFSMaxBufSize); + rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid, + cfile->fid.volatile_fid, FSCTL_SET_ZERO_DATA, true, + (char *)&fsctl_buf, + sizeof(struct file_zero_data_information), + 0, NULL, NULL); if (rc) goto zero_range_exit; @@ -2683,33 +2776,12 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, * do we also need to change the size of the file? */ if (keep_size == false && i_size_read(inode) < offset + len) { - smb2_set_next_command(tcon, &rqst[0]); - - memset(&si_iov, 0, sizeof(si_iov)); - rqst[num].rq_iov = si_iov; - rqst[num].rq_nvec = 1; - eof = cpu_to_le64(offset + len); - size[0] = 8; /* sizeof __le64 */ - data[0] = &eof; - - rc = SMB2_set_info_init(tcon, &rqst[num++], - cfile->fid.persistent_fid, - cfile->fid.persistent_fid, - current->tgid, - FILE_END_OF_FILE_INFORMATION, - SMB2_O_INFO_FILE, 0, data, size); - smb2_set_related(&rqst[1]); + rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, + cfile->fid.volatile_fid, cfile->pid, &eof); } - rc = compound_send_recv(xid, ses, flags, num, rqst, - resp_buftype, rsp_iov); - zero_range_exit: - SMB2_ioctl_free(&rqst[0]); - SMB2_set_info_free(&rqst[1]); - free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base); - free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base); free_xid(xid); if (rc) trace_smb3_zero_err(xid, cfile->fid.persistent_fid, tcon->tid, @@ -2850,6 +2922,79 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, return rc; } +static int smb3_fiemap(struct cifs_tcon *tcon, + struct cifsFileInfo *cfile, + struct fiemap_extent_info *fei, u64 start, u64 len) +{ + unsigned int xid; + struct file_allocated_range_buffer in_data, *out_data; + u32 out_data_len; + int i, num, rc, flags, last_blob; + u64 next; + + if (fiemap_check_flags(fei, FIEMAP_FLAG_SYNC)) + return -EBADR; + + xid = get_xid(); + again: + in_data.file_offset = cpu_to_le64(start); + in_data.length = cpu_to_le64(len); + + rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid, + cfile->fid.volatile_fid, + FSCTL_QUERY_ALLOCATED_RANGES, true, + (char *)&in_data, sizeof(in_data), + 1024 * sizeof(struct file_allocated_range_buffer), + (char **)&out_data, &out_data_len); + if (rc == -E2BIG) { + last_blob = 0; + rc = 0; + } else + last_blob = 1; + if (rc) + goto out; + + if (out_data_len < sizeof(struct file_allocated_range_buffer)) { + rc = -EINVAL; + goto out; + } + if (out_data_len % sizeof(struct file_allocated_range_buffer)) { + rc = -EINVAL; + goto out; + } + + num = out_data_len / sizeof(struct file_allocated_range_buffer); + for (i = 0; i < num; i++) { + flags = 0; + if (i == num - 1 && last_blob) + flags |= FIEMAP_EXTENT_LAST; + + rc = fiemap_fill_next_extent(fei, + le64_to_cpu(out_data[i].file_offset), + le64_to_cpu(out_data[i].file_offset), + le64_to_cpu(out_data[i].length), + flags); + if (rc < 0) + goto out; + if (rc == 1) { + rc = 0; + goto out; + } + } + + if (!last_blob) { + next = le64_to_cpu(out_data[num - 1].file_offset) + + le64_to_cpu(out_data[num - 1].length); + len = len - (next - start); + start = next; + goto again; + } + + out: + free_xid(xid); + kfree(out_data); + return rc; +} static long smb3_fallocate(struct file *file, struct cifs_tcon *tcon, int mode, loff_t off, loff_t len) @@ -2917,26 +3062,28 @@ smb21_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock, unsigned int epoch, bool *purge_cache) { char message[5] = {0}; + unsigned int new_oplock = 0; oplock &= 0xFF; if (oplock == SMB2_OPLOCK_LEVEL_NOCHANGE) return; - cinode->oplock = 0; if (oplock & SMB2_LEASE_READ_CACHING_HE) { - cinode->oplock |= CIFS_CACHE_READ_FLG; + new_oplock |= CIFS_CACHE_READ_FLG; strcat(message, "R"); } if (oplock & SMB2_LEASE_HANDLE_CACHING_HE) { - cinode->oplock |= CIFS_CACHE_HANDLE_FLG; + new_oplock |= CIFS_CACHE_HANDLE_FLG; strcat(message, "H"); } if (oplock & SMB2_LEASE_WRITE_CACHING_HE) { - cinode->oplock |= CIFS_CACHE_WRITE_FLG; + new_oplock |= CIFS_CACHE_WRITE_FLG; strcat(message, "W"); } - if (!cinode->oplock) - strcat(message, "None"); + if (!new_oplock) + strncpy(message, "None", sizeof(message)); + + cinode->oplock = new_oplock; cifs_dbg(FYI, "%s Lease granted on inode %p\n", message, &cinode->vfs_inode); } @@ -4018,6 +4165,7 @@ struct smb_version_operations smb20_operations = { .next_header = smb2_next_header, .ioctl_query_info = smb2_ioctl_query_info, .make_node = smb2_make_node, + .fiemap = smb3_fiemap, }; struct smb_version_operations smb21_operations = { @@ -4117,6 +4265,7 @@ struct smb_version_operations smb21_operations = { .next_header = smb2_next_header, .ioctl_query_info = smb2_ioctl_query_info, .make_node = smb2_make_node, + .fiemap = smb3_fiemap, }; struct smb_version_operations smb30_operations = { @@ -4225,6 +4374,7 @@ struct smb_version_operations smb30_operations = { .next_header = smb2_next_header, .ioctl_query_info = smb2_ioctl_query_info, .make_node = smb2_make_node, + .fiemap = smb3_fiemap, }; struct smb_version_operations smb311_operations = { @@ -4334,6 +4484,7 @@ struct smb_version_operations smb311_operations = { .next_header = smb2_next_header, .ioctl_query_info = smb2_ioctl_query_info, .make_node = smb2_make_node, + .fiemap = smb3_fiemap, }; struct smb_version_values smb20_values = { |