From 29531c55928aedc6f860555737cba99689a75d2c Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Thu, 19 Feb 2015 16:36:05 +0100 Subject: smbd/ioctl: add FSCTL_QUERY_ALLOCATED_RANGES support This change implements support for FSCTL_QUERY_ALLOCATED_RANGES using the SEEK_HOLE/SEEK_DATA functionality of lseek(). Files marked non-sparse are always reported by the ioctl as fully allocated, regardless of any potential "strict allocate = no" savings. Signed-off-by: David Disseldorp Reviewed-by: Jeremy Allison --- source3/smbd/smb2_ioctl_filesys.c | 216 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) diff --git a/source3/smbd/smb2_ioctl_filesys.c b/source3/smbd/smb2_ioctl_filesys.c index 63ec19661e..187deaf118 100644 --- a/source3/smbd/smb2_ioctl_filesys.c +++ b/source3/smbd/smb2_ioctl_filesys.c @@ -225,6 +225,212 @@ static NTSTATUS fsctl_zero_data(TALLOC_CTX *mem_ctx, return NT_STATUS_OK; } +static NTSTATUS fsctl_qar_buf_push(TALLOC_CTX *mem_ctx, + struct file_alloced_range_buf *qar_buf, + DATA_BLOB *qar_array_blob) +{ + DATA_BLOB new_slot; + enum ndr_err_code ndr_ret; + bool ok; + + ndr_ret = ndr_push_struct_blob(&new_slot, mem_ctx, qar_buf, + (ndr_push_flags_fn_t)ndr_push_file_alloced_range_buf); + if (ndr_ret != NDR_ERR_SUCCESS) { + DEBUG(0, ("failed to marshall QAR buf\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + /* TODO should be able to avoid copy by pushing into prealloced buf */ + ok = data_blob_append(mem_ctx, qar_array_blob, new_slot.data, + new_slot.length); + data_blob_free(&new_slot); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +static NTSTATUS fsctl_qar_seek_fill(TALLOC_CTX *mem_ctx, + struct files_struct *fsp, + off_t curr_off, + off_t max_off, + DATA_BLOB *qar_array_blob) +{ + NTSTATUS status = NT_STATUS_NOT_SUPPORTED; + +#ifdef HAVE_LSEEK_HOLE_DATA + while (curr_off <= max_off) { + off_t data_off; + off_t hole_off; + struct file_alloced_range_buf qar_buf; + + /* seek next data */ + data_off = SMB_VFS_LSEEK(fsp, curr_off, SEEK_DATA); + if ((data_off == -1) && (errno == ENXIO)) { + /* no data from curr_off to EOF */ + break; + } else if (data_off == -1) { + status = map_nt_error_from_unix_common(errno); + DEBUG(1, ("lseek data failed: %s\n", strerror(errno))); + return status; + } + + if (data_off > max_off) { + /* found something, but passed range of interest */ + break; + } + + hole_off = SMB_VFS_LSEEK(fsp, data_off, SEEK_HOLE); + if (hole_off == -1) { + status = map_nt_error_from_unix_common(errno); + DEBUG(1, ("lseek hole failed: %s\n", strerror(errno))); + return status; + } + + if (hole_off <= data_off) { + DEBUG(1, ("lseek inconsistent: hole %lu at or before " + "data %lu\n", (unsigned long)hole_off, + (unsigned long)data_off)); + return NT_STATUS_INTERNAL_ERROR; + } + + qar_buf.file_off = data_off; + /* + 1 to convert maximum offset to length */ + qar_buf.len = MIN(hole_off, max_off + 1) - data_off; + + status = fsctl_qar_buf_push(mem_ctx, &qar_buf, qar_array_blob); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_NO_MEMORY; + } + + curr_off = hole_off; + } + status = NT_STATUS_OK; +#endif + + return status; +} + +static NTSTATUS fsctl_qar(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + DATA_BLOB *in_input, + size_t in_max_output, + DATA_BLOB *out_output) +{ + struct fsctl_query_alloced_ranges_req qar_req; + struct fsctl_query_alloced_ranges_rsp qar_rsp; + DATA_BLOB qar_array_blob = data_blob_null; + uint64_t max_off; + enum ndr_err_code ndr_ret; + int ret; + NTSTATUS status; + SMB_STRUCT_STAT sbuf; + + if (fsp == NULL) { + return NT_STATUS_FILE_CLOSED; + } + + /* READ_DATA permission is required */ + status = check_access(fsp->conn, fsp, NULL, FILE_READ_DATA); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ndr_ret = ndr_pull_struct_blob(in_input, mem_ctx, &qar_req, + (ndr_pull_flags_fn_t)ndr_pull_fsctl_query_alloced_ranges_req); + if (ndr_ret != NDR_ERR_SUCCESS) { + DEBUG(0, ("failed to unmarshall QAR req\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + /* + * XXX Windows Server 2008 & 2012 servers don't return lock-conflict + * for QAR requests over an exclusively locked range! + */ + + ret = SMB_VFS_FSTAT(fsp, &sbuf); + if (ret == -1) { + status = map_nt_error_from_unix_common(errno); + DEBUG(2, ("fstat failed: %s\n", strerror(errno))); + return status; + } + + if ((qar_req.buf.len == 0) + || (sbuf.st_ex_size == 0) + || (qar_req.buf.file_off >= sbuf.st_ex_size)) { + /* zero length range or after EOF, no ranges to return */ + return NT_STATUS_OK; + } + + /* check for integer overflow */ + if (qar_req.buf.file_off + qar_req.buf.len < qar_req.buf.file_off) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* + * Maximum offset is either the last valid offset _before_ EOF, or the + * last byte offset within the requested range. -1 converts length to + * offset, which is easier to work with for SEEK_DATA/SEEK_HOLE, E.g.: + * + * /off=0 /off=512K /st_ex_size=1M + * |-------------------------------------| + * | File data | + * |-------------------------------------| + * QAR end\ + * |=====================================| + * | QAR off=512K, len=1M | + * |=================^===================| + * max_off=1M - 1 + * QAR end\ + * |==================| + * |QAR off=0 len=512K| + * |==================| + * ^ + * max_off=512K - 1 + */ + max_off = MIN(sbuf.st_ex_size, + qar_req.buf.file_off + qar_req.buf.len) - 1; + + if (!fsp->is_sparse) { + struct file_alloced_range_buf qar_buf; + + /* file is non-sparse, claim file_off->max_off is allocated */ + qar_buf.file_off = qar_req.buf.file_off; + /* + 1 to convert maximum offset back to length */ + qar_buf.len = max_off - qar_req.buf.file_off + 1; + + status = fsctl_qar_buf_push(mem_ctx, &qar_buf, &qar_array_blob); + } else { + status = fsctl_qar_seek_fill(mem_ctx, fsp, qar_req.buf.file_off, + max_off, &qar_array_blob); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* marshall response buffer. */ + qar_rsp.far_buf_array = qar_array_blob; + + ndr_ret = ndr_push_struct_blob(out_output, mem_ctx, &qar_rsp, + (ndr_push_flags_fn_t)ndr_push_fsctl_query_alloced_ranges_rsp); + if (ndr_ret != NDR_ERR_SUCCESS) { + DEBUG(0, ("failed to marshall QAR rsp\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (out_output->length > in_max_output) { + DEBUG(2, ("QAR output len %lu exceeds max %lu\n", + (unsigned long)out_output->length, + (unsigned long)in_max_output)); + data_blob_free(out_output); + return NT_STATUS_BUFFER_TOO_SMALL; + } + + return NT_STATUS_OK; +} + struct tevent_req *smb2_ioctl_filesys(uint32_t ctl_code, struct tevent_context *ev, struct tevent_req *req, @@ -258,6 +464,16 @@ struct tevent_req *smb2_ioctl_filesys(uint32_t ctl_code, } return tevent_req_post(req, ev); break; + case FSCTL_QUERY_ALLOCATED_RANGES: + status = fsctl_qar(state, ev, state->fsp, + &state->in_input, + state->in_max_output, + &state->out_output); + if (!tevent_req_nterror(req, status)) { + tevent_req_done(req); + } + return tevent_req_post(req, ev); + break; default: { uint8_t *out_data = NULL; uint32_t out_data_len = 0; -- cgit