summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Disseldorp <ddiss@samba.org>2015-02-19 16:36:05 +0100
committerJeremy Allison <jra@samba.org>2015-03-09 21:27:07 +0100
commit29531c55928aedc6f860555737cba99689a75d2c (patch)
tree59fb047fedc34a0ba226885a9610c00505bb0020
parent76fff2befe6613416e80f891ccb1521609e19169 (diff)
downloadsamba-29531c55928aedc6f860555737cba99689a75d2c.tar.gz
samba-29531c55928aedc6f860555737cba99689a75d2c.tar.xz
samba-29531c55928aedc6f860555737cba99689a75d2c.zip
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 <ddiss@samba.org> Reviewed-by: Jeremy Allison <jra@samba.org>
-rw-r--r--source3/smbd/smb2_ioctl_filesys.c216
1 files changed, 216 insertions, 0 deletions
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;