smbd/smb2_ioctl: validate dup_extent request lengths
[gd/samba-autobuild/.git] / source3 / smbd / smb2_ioctl_filesys.c
1 /*
2    Unix SMB/CIFS implementation.
3    Core SMB2 server
4
5    Copyright (C) Stefan Metzmacher 2009
6    Copyright (C) David Disseldorp 2013-2015
7
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 3 of the License, or
11    (at your option) any later version.
12
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include "includes.h"
23 #include "smbd/smbd.h"
24 #include "smbd/globals.h"
25 #include "../libcli/smb/smb_common.h"
26 #include "../libcli/security/security.h"
27 #include "../lib/util/tevent_ntstatus.h"
28 #include "rpc_server/srv_pipe_hnd.h"
29 #include "include/ntioctl.h"
30 #include "../librpc/ndr/libndr.h"
31 #include "librpc/gen_ndr/ndr_ioctl.h"
32 #include "smb2_ioctl_private.h"
33
34 /*
35  * XXX this may reduce dup_extents->byte_count so that it's less than the
36  * target file size.
37  */
38 static NTSTATUS fsctl_dup_extents_check_lengths(struct files_struct *src_fsp,
39                                                 struct files_struct *dst_fsp,
40                                 struct fsctl_dup_extents_to_file *dup_extents)
41 {
42         NTSTATUS status;
43
44         if ((dup_extents->source_off + dup_extents->byte_count
45                                                 < dup_extents->source_off)
46          || (dup_extents->target_off + dup_extents->byte_count
47                                                 < dup_extents->target_off)) {
48                 return NT_STATUS_INVALID_PARAMETER;     /* wrap */
49         }
50
51         status = vfs_stat_fsp(src_fsp);
52         if (!NT_STATUS_IS_OK(status)) {
53                 return status;
54         }
55
56         /*
57          * XXX vfs_btrfs and vfs_default have size checks in the copychunk
58          * handler, as this needs to be rechecked after the src has potentially
59          * been extended by a previous chunk in the compound copychunk req.
60          */
61         if (src_fsp->fsp_name->st.st_ex_size
62                         < dup_extents->source_off + dup_extents->byte_count) {
63                 DEBUG(2, ("dup_extents req exceeds src size\n"));
64                 return NT_STATUS_NOT_SUPPORTED;
65         }
66
67         status = vfs_stat_fsp(dst_fsp);
68         if (!NT_STATUS_IS_OK(status)) {
69                 return status;
70         }
71
72         if (dst_fsp->fsp_name->st.st_ex_size
73                         < dup_extents->target_off + dup_extents->byte_count) {
74
75                 if (dst_fsp->fsp_name->st.st_ex_size - dup_extents->target_off
76                                         > dst_fsp->fsp_name->st.st_ex_size) {
77                         return NT_STATUS_INVALID_PARAMETER;     /* wrap */
78                 }
79
80                 /*
81                  * this server behaviour is pretty hairy, but we need to match
82                  * Windows, so...
83                  */
84                 DEBUG(2, ("dup_extents req exceeds target size, capping\n"));
85                 dup_extents->byte_count = dst_fsp->fsp_name->st.st_ex_size
86                                                 - dup_extents->target_off;
87         }
88
89         return NT_STATUS_OK;
90 }
91
92 static NTSTATUS fsctl_dup_extents_check_overlap(struct files_struct *src_fsp,
93                                                 struct files_struct *dst_fsp,
94                                 struct fsctl_dup_extents_to_file *dup_extents)
95 {
96         uint64_t src_off_last;
97         uint64_t tgt_off_last;
98
99         if (!file_id_equal(&src_fsp->file_id, &dst_fsp->file_id)) {
100                 /* src and dest refer to different files */
101                 return NT_STATUS_OK;
102         }
103
104         if (dup_extents->byte_count == 0) {
105                 /* no range to overlap */
106                 return NT_STATUS_OK;
107         }
108
109         /*
110          * [MS-FSCC] 2.3.8 FSCTL_DUPLICATE_EXTENTS_TO_FILE Reply
111          * STATUS_NOT_SUPPORTED:
112          * The source and target destination ranges overlap on the same file.
113          */
114
115         src_off_last = dup_extents->source_off + dup_extents->byte_count - 1;
116         if ((dup_extents->target_off >= dup_extents->source_off)
117                                 && (dup_extents->target_off <= src_off_last)) {
118                 /*
119                  * src: |-----------|
120                  * tgt:       |-----------|
121                  */
122                 return NT_STATUS_NOT_SUPPORTED;
123         }
124
125
126         tgt_off_last = dup_extents->target_off + dup_extents->byte_count - 1;
127         if ((tgt_off_last >= dup_extents->source_off)
128                                         && (tgt_off_last <= src_off_last)) {
129                 /*
130                  * src:       |-----------|
131                  * tgt: |-----------|
132                  */
133                 return NT_STATUS_NOT_SUPPORTED;
134         }
135
136         return NT_STATUS_OK;
137 }
138
139 static NTSTATUS fsctl_dup_extents_check_sparse(struct files_struct *src_fsp,
140                                                struct files_struct *dst_fsp)
141 {
142         /*
143          * 2.3.8 FSCTL_DUPLICATE_EXTENTS_TO_FILE Reply...
144          * STATUS_NOT_SUPPORTED: Target file is sparse, while source
145          *                       is a non-sparse file.
146          *
147          * WS2016 has the following behaviour (MS are in the process of fixing
148          * the spec):
149          * STATUS_NOT_SUPPORTED is returned if the source is sparse, while the
150          * target is non-sparse. However, if target is sparse while the source
151          * is non-sparse, then FSCTL_DUPLICATE_EXTENTS_TO_FILE completes
152          * successfully.
153          */
154         if ((src_fsp->is_sparse) && (!dst_fsp->is_sparse)) {
155                 return NT_STATUS_NOT_SUPPORTED;
156         }
157
158         return NT_STATUS_OK;
159 }
160
161 struct fsctl_dup_extents_state {
162         struct tevent_context *ev;
163         struct connection_struct *conn;
164         struct fsctl_dup_extents_to_file dup_extents;
165 };
166
167 static void fsctl_dup_extents_vfs_done(struct tevent_req *subreq);
168
169 static struct tevent_req *fsctl_dup_extents_send(TALLOC_CTX *mem_ctx,
170                                                  struct tevent_context *ev,
171                                                  struct files_struct *dst_fsp,
172                                                  DATA_BLOB *in_input,
173                                                  struct smbd_smb2_request *smb2req)
174 {
175         struct tevent_req *req = NULL;
176         struct tevent_req *subreq = NULL;
177         struct fsctl_dup_extents_state *state = NULL;
178         uint64_t src_fid_persistent = 0;
179         uint64_t src_fid_volatile = 0;
180         struct files_struct *src_fsp = NULL;
181         int ndr_ret;
182         NTSTATUS status;
183
184         req = tevent_req_create(mem_ctx, &state,
185                                 struct fsctl_dup_extents_state);
186         if (req == NULL) {
187                 return NULL;
188         }
189         *state = (struct fsctl_dup_extents_state) {
190                 .conn = dst_fsp->conn,
191                 .ev = ev,
192         };
193
194         if (dst_fsp == NULL) {
195                 tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
196                 return tevent_req_post(req, ev);
197         }
198
199         if ((dst_fsp->conn->fs_capabilities
200                                 & FILE_SUPPORTS_BLOCK_REFCOUNTING) == 0) {
201                 DBG_INFO("FS does not advertise block refcounting support\n");
202                 tevent_req_nterror(req, NT_STATUS_INVALID_DEVICE_REQUEST);
203                 return tevent_req_post(req, ev);
204         }
205
206         ndr_ret = ndr_pull_struct_blob(in_input, state, &state->dup_extents,
207                        (ndr_pull_flags_fn_t)ndr_pull_fsctl_dup_extents_to_file);
208         if (ndr_ret != NDR_ERR_SUCCESS) {
209                 DBG_ERR("failed to unmarshall dup extents to file req\n");
210                 tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
211                 return tevent_req_post(req, ev);
212         }
213
214         src_fid_persistent = BVAL(state->dup_extents.source_fid, 0);
215         src_fid_volatile = BVAL(state->dup_extents.source_fid, 8);
216         src_fsp = file_fsp_get(smb2req, src_fid_persistent, src_fid_volatile);
217         if ((src_fsp == NULL)
218                       || (src_fsp->file_id.devid != dst_fsp->file_id.devid)) {
219                 /*
220                  * [MS-FSCC] 2.3.8 FSCTL_DUPLICATE_EXTENTS_TO_FILE Reply
221                  * STATUS_INVALID_PARAMETER:
222                  * The FileHandle parameter is either invalid or does not
223                  * represent a handle to an opened file on the same volume.
224                  *
225                  * Windows Server responds with NT_STATUS_INVALID_HANDLE instead
226                  * of STATUS_INVALID_PARAMETER here, despite the above spec.
227                  */
228                 DBG_ERR("invalid src_fsp for dup_extents\n");
229                 tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE);
230                 return tevent_req_post(req, ev);
231         }
232
233         status = fsctl_dup_extents_check_lengths(src_fsp, dst_fsp,
234                                                  &state->dup_extents);
235         if (!NT_STATUS_IS_OK(status)) {
236                 tevent_req_nterror(req, status);
237                 return tevent_req_post(req, ev);
238         }
239
240         if (state->dup_extents.byte_count == 0) {
241                 DBG_ERR("skipping zero length dup extents\n");
242                 tevent_req_done(req);
243                 return tevent_req_post(req, ev);
244         }
245
246         status = fsctl_dup_extents_check_overlap(src_fsp, dst_fsp,
247                                                  &state->dup_extents);
248         if (!NT_STATUS_IS_OK(status)) {
249                 tevent_req_nterror(req, status);
250                 return tevent_req_post(req, ev);
251         }
252
253         status = fsctl_dup_extents_check_sparse(src_fsp, dst_fsp);
254         if (!NT_STATUS_IS_OK(status)) {
255                 tevent_req_nterror(req, status);
256                 return tevent_req_post(req, ev);
257         }
258
259         subreq = SMB_VFS_COPY_CHUNK_SEND(dst_fsp->conn, state, ev,
260                                          src_fsp, state->dup_extents.source_off,
261                                          dst_fsp, state->dup_extents.target_off,
262                                          state->dup_extents.byte_count,
263                                          VFS_COPY_CHUNK_FL_MUST_CLONE);
264         if (tevent_req_nomem(subreq, req)) {
265                 return tevent_req_post(req, ev);
266         }
267
268         tevent_req_set_callback(subreq, fsctl_dup_extents_vfs_done, req);
269
270         return subreq;
271 }
272
273 static void fsctl_dup_extents_vfs_done(struct tevent_req *subreq)
274 {
275         struct tevent_req *req = tevent_req_callback_data(
276                 subreq, struct tevent_req);
277         struct fsctl_dup_extents_state *state = tevent_req_data(
278                 req, struct fsctl_dup_extents_state);
279         off_t nb_chunk;
280         NTSTATUS status;
281
282         status = SMB_VFS_COPY_CHUNK_RECV(state->conn, subreq, &nb_chunk);
283         TALLOC_FREE(subreq);
284         if (tevent_req_nterror(req, status)) {
285                 return;
286         }
287
288         if (nb_chunk != state->dup_extents.byte_count) {
289                 tevent_req_nterror(req, NT_STATUS_IO_DEVICE_ERROR);
290                 return;
291         }
292
293         tevent_req_done(req);
294 }
295
296 static NTSTATUS fsctl_dup_extents_recv(struct tevent_req *req)
297 {
298         return tevent_req_simple_recv_ntstatus(req);
299 }
300
301 static NTSTATUS fsctl_get_cmprn(TALLOC_CTX *mem_ctx,
302                                 struct tevent_context *ev,
303                                 struct files_struct *fsp,
304                                 size_t in_max_output,
305                                 DATA_BLOB *out_output)
306 {
307         struct compression_state cmpr_state;
308         enum ndr_err_code ndr_ret;
309         DATA_BLOB output;
310         NTSTATUS status;
311
312         if (fsp == NULL) {
313                 return NT_STATUS_FILE_CLOSED;
314         }
315
316         /* Windows doesn't check for SEC_FILE_READ_ATTRIBUTE permission here */
317
318         ZERO_STRUCT(cmpr_state);
319         if (fsp->conn->fs_capabilities & FILE_FILE_COMPRESSION) {
320                 status = SMB_VFS_GET_COMPRESSION(fsp->conn,
321                                                  mem_ctx,
322                                                  fsp,
323                                                  NULL,
324                                                  &cmpr_state.format);
325                 if (!NT_STATUS_IS_OK(status)) {
326                         return status;
327                 }
328         } else {
329                 /*
330                  * bso#12144: The underlying filesystem doesn't support
331                  * compression, so we should respond with "not-compressed"
332                  * (like WS2016 ReFS) instead of STATUS_NOT_SUPPORTED or
333                  * NT_STATUS_INVALID_DEVICE_REQUEST.
334                  */
335                 cmpr_state.format = COMPRESSION_FORMAT_NONE;
336         }
337
338         ndr_ret = ndr_push_struct_blob(&output, mem_ctx,
339                                        &cmpr_state,
340                         (ndr_push_flags_fn_t)ndr_push_compression_state);
341         if (ndr_ret != NDR_ERR_SUCCESS) {
342                 return NT_STATUS_INTERNAL_ERROR;
343         }
344
345         if (in_max_output < output.length) {
346                 DEBUG(1, ("max output %u too small for compression state %ld\n",
347                       (unsigned int)in_max_output, (long int)output.length));
348                 return NT_STATUS_INVALID_USER_BUFFER;
349         }
350         *out_output = output;
351
352         return NT_STATUS_OK;
353 }
354
355 static NTSTATUS fsctl_set_cmprn(TALLOC_CTX *mem_ctx,
356                                 struct tevent_context *ev,
357                                 struct files_struct *fsp,
358                                 DATA_BLOB *in_input)
359 {
360         struct compression_state cmpr_state;
361         enum ndr_err_code ndr_ret;
362         NTSTATUS status;
363
364         if (fsp == NULL) {
365                 return NT_STATUS_FILE_CLOSED;
366         }
367
368         /* WRITE_DATA permission is required, WRITE_ATTRIBUTES is not */
369         status = check_access_fsp(fsp, FILE_WRITE_DATA);
370         if (!NT_STATUS_IS_OK(status)) {
371                 return status;
372         }
373
374         ndr_ret = ndr_pull_struct_blob(in_input, mem_ctx, &cmpr_state,
375                         (ndr_pull_flags_fn_t)ndr_pull_compression_state);
376         if (ndr_ret != NDR_ERR_SUCCESS) {
377                 DEBUG(0, ("failed to unmarshall set compression req\n"));
378                 return NT_STATUS_INVALID_PARAMETER;
379         }
380
381         status = NT_STATUS_NOT_SUPPORTED;
382         if (fsp->conn->fs_capabilities & FILE_FILE_COMPRESSION) {
383                 status = SMB_VFS_SET_COMPRESSION(fsp->conn,
384                                                  mem_ctx,
385                                                  fsp,
386                                                  cmpr_state.format);
387         } else if (cmpr_state.format == COMPRESSION_FORMAT_NONE) {
388                 /*
389                  * bso#12144: The underlying filesystem doesn't support
390                  * compression. We should still accept set(FORMAT_NONE) requests
391                  * (like WS2016 ReFS).
392                  */
393                 status = NT_STATUS_OK;
394         }
395
396         return status;
397 }
398
399 static NTSTATUS fsctl_zero_data(TALLOC_CTX *mem_ctx,
400                                 struct tevent_context *ev,
401                                 struct files_struct *fsp,
402                                 DATA_BLOB *in_input)
403 {
404         struct file_zero_data_info zdata_info;
405         enum ndr_err_code ndr_ret;
406         struct lock_struct lck;
407         int mode;
408         uint64_t len;
409         int ret;
410         NTSTATUS status;
411
412         if (fsp == NULL) {
413                 return NT_STATUS_FILE_CLOSED;
414         }
415
416         /* WRITE_DATA permission is required */
417         status = check_access_fsp(fsp, FILE_WRITE_DATA);
418         if (!NT_STATUS_IS_OK(status)) {
419                 return status;
420         }
421
422         /* allow regardless of whether FS supports sparse or not */
423
424         ndr_ret = ndr_pull_struct_blob(in_input, mem_ctx, &zdata_info,
425                         (ndr_pull_flags_fn_t)ndr_pull_file_zero_data_info);
426         if (ndr_ret != NDR_ERR_SUCCESS) {
427                 DEBUG(0, ("failed to unmarshall zero data request\n"));
428                 return NT_STATUS_INVALID_PARAMETER;
429         }
430
431         if (zdata_info.beyond_final_zero < zdata_info.file_off) {
432                 DEBUG(0, ("invalid zero data params: off %lu, bfz, %lu\n",
433                           (unsigned long)zdata_info.file_off,
434                           (unsigned long)zdata_info.beyond_final_zero));
435                 return NT_STATUS_INVALID_PARAMETER;
436         }
437
438         /* convert strange "beyond final zero" param into length */
439         len = zdata_info.beyond_final_zero - zdata_info.file_off;
440
441         if (len == 0) {
442                 DEBUG(2, ("zero data called with zero length range\n"));
443                 return NT_STATUS_OK;
444         }
445
446         init_strict_lock_struct(fsp,
447                                 fsp->op->global->open_persistent_id,
448                                 zdata_info.file_off,
449                                 len,
450                                 WRITE_LOCK,
451                                 &lck);
452
453         if (!SMB_VFS_STRICT_LOCK(fsp->conn, fsp, &lck)) {
454                 DEBUG(2, ("failed to lock range for zero-data\n"));
455                 return NT_STATUS_FILE_LOCK_CONFLICT;
456         }
457
458         /*
459          * MS-FSCC <58> Section 2.3.67
460          * This FSCTL sets the range of bytes to zero (0) without extending the
461          * file size.
462          *
463          * The VFS_FALLOCATE_FL_KEEP_SIZE flag is used to satisfy this
464          * constraint.
465          */
466
467         mode = VFS_FALLOCATE_FL_PUNCH_HOLE | VFS_FALLOCATE_FL_KEEP_SIZE;
468         ret = SMB_VFS_FALLOCATE(fsp, mode, zdata_info.file_off, len);
469         if (ret == -1)  {
470                 status = map_nt_error_from_unix_common(errno);
471                 DEBUG(2, ("zero-data fallocate(0x%x) failed: %s\n", mode,
472                       strerror(errno)));
473                 SMB_VFS_STRICT_UNLOCK(fsp->conn, fsp, &lck);
474                 return status;
475         }
476
477         if (!fsp->is_sparse && lp_strict_allocate(SNUM(fsp->conn))) {
478                 /*
479                  * File marked non-sparse and "strict allocate" is enabled -
480                  * allocate the range that we just punched out.
481                  * In future FALLOC_FL_ZERO_RANGE could be used exclusively for
482                  * this, but it's currently only supported on XFS and ext4.
483                  *
484                  * The newly allocated range still won't be found by SEEK_DATA
485                  * for QAR, but stat.st_blocks will reflect it.
486                  */
487                 ret = SMB_VFS_FALLOCATE(fsp, VFS_FALLOCATE_FL_KEEP_SIZE,
488                                         zdata_info.file_off, len);
489                 if (ret == -1)  {
490                         status = map_nt_error_from_unix_common(errno);
491                         DEBUG(0, ("fallocate failed: %s\n", strerror(errno)));
492                         SMB_VFS_STRICT_UNLOCK(fsp->conn, fsp, &lck);
493                         return status;
494                 }
495         }
496
497         SMB_VFS_STRICT_UNLOCK(fsp->conn, fsp, &lck);
498         return NT_STATUS_OK;
499 }
500
501 static NTSTATUS fsctl_qar_buf_push(TALLOC_CTX *mem_ctx,
502                                    struct file_alloced_range_buf *qar_buf,
503                                    DATA_BLOB *qar_array_blob)
504 {
505         DATA_BLOB new_slot;
506         enum ndr_err_code ndr_ret;
507         bool ok;
508
509         ndr_ret = ndr_push_struct_blob(&new_slot, mem_ctx, qar_buf,
510                         (ndr_push_flags_fn_t)ndr_push_file_alloced_range_buf);
511         if (ndr_ret != NDR_ERR_SUCCESS) {
512                 DEBUG(0, ("failed to marshall QAR buf\n"));
513                 return NT_STATUS_INVALID_PARAMETER;
514         }
515
516         /* TODO should be able to avoid copy by pushing into prealloced buf */
517         ok = data_blob_append(mem_ctx, qar_array_blob, new_slot.data,
518                               new_slot.length);
519         data_blob_free(&new_slot);
520         if (!ok) {
521                 return NT_STATUS_NO_MEMORY;
522         }
523
524         return NT_STATUS_OK;
525 }
526
527 static NTSTATUS fsctl_qar_seek_fill(TALLOC_CTX *mem_ctx,
528                                     struct files_struct *fsp,
529                                     off_t curr_off,
530                                     off_t max_off,
531                                     DATA_BLOB *qar_array_blob)
532 {
533         NTSTATUS status = NT_STATUS_NOT_SUPPORTED;
534
535 #ifdef HAVE_LSEEK_HOLE_DATA
536         while (curr_off <= max_off) {
537                 off_t data_off;
538                 off_t hole_off;
539                 struct file_alloced_range_buf qar_buf;
540
541                 /* seek next data */
542                 data_off = SMB_VFS_LSEEK(fsp, curr_off, SEEK_DATA);
543                 if ((data_off == -1) && (errno == ENXIO)) {
544                         /* no data from curr_off to EOF */
545                         break;
546                 } else if (data_off == -1) {
547                         status = map_nt_error_from_unix_common(errno);
548                         DEBUG(1, ("lseek data failed: %s\n", strerror(errno)));
549                         return status;
550                 }
551
552                 if (data_off > max_off) {
553                         /* found something, but passed range of interest */
554                         break;
555                 }
556
557                 hole_off = SMB_VFS_LSEEK(fsp, data_off, SEEK_HOLE);
558                 if (hole_off == -1) {
559                         status = map_nt_error_from_unix_common(errno);
560                         DEBUG(1, ("lseek hole failed: %s\n", strerror(errno)));
561                         return status;
562                 }
563
564                 if (hole_off <= data_off) {
565                         DEBUG(1, ("lseek inconsistent: hole %lu at or before "
566                                   "data %lu\n", (unsigned long)hole_off,
567                                   (unsigned long)data_off));
568                         return NT_STATUS_INTERNAL_ERROR;
569                 }
570
571                 qar_buf.file_off = data_off;
572                 /* + 1 to convert maximum offset to length */
573                 qar_buf.len = MIN(hole_off, max_off + 1) - data_off;
574
575                 status = fsctl_qar_buf_push(mem_ctx, &qar_buf, qar_array_blob);
576                 if (!NT_STATUS_IS_OK(status)) {
577                         return NT_STATUS_NO_MEMORY;
578                 }
579
580                 curr_off = hole_off;
581         }
582         status = NT_STATUS_OK;
583 #endif
584
585         return status;
586 }
587
588 static NTSTATUS fsctl_qar(TALLOC_CTX *mem_ctx,
589                           struct tevent_context *ev,
590                           struct files_struct *fsp,
591                           DATA_BLOB *in_input,
592                           size_t in_max_output,
593                           DATA_BLOB *out_output)
594 {
595         struct fsctl_query_alloced_ranges_req qar_req;
596         struct fsctl_query_alloced_ranges_rsp qar_rsp;
597         DATA_BLOB qar_array_blob = data_blob_null;
598         uint64_t max_off;
599         enum ndr_err_code ndr_ret;
600         int ret;
601         NTSTATUS status;
602         SMB_STRUCT_STAT sbuf;
603
604         if (fsp == NULL) {
605                 return NT_STATUS_FILE_CLOSED;
606         }
607
608         /* READ_DATA permission is required */
609         status = check_access_fsp(fsp, FILE_READ_DATA);
610         if (!NT_STATUS_IS_OK(status)) {
611                 return status;
612         }
613
614         ndr_ret = ndr_pull_struct_blob(in_input, mem_ctx, &qar_req,
615                 (ndr_pull_flags_fn_t)ndr_pull_fsctl_query_alloced_ranges_req);
616         if (ndr_ret != NDR_ERR_SUCCESS) {
617                 DEBUG(0, ("failed to unmarshall QAR req\n"));
618                 return NT_STATUS_INVALID_PARAMETER;
619         }
620
621         /*
622          * XXX Windows Server 2008 & 2012 servers don't return lock-conflict
623          * for QAR requests over an exclusively locked range!
624          */
625
626         ret = SMB_VFS_FSTAT(fsp, &sbuf);
627         if (ret == -1) {
628                 status = map_nt_error_from_unix_common(errno);
629                 DEBUG(2, ("fstat failed: %s\n", strerror(errno)));
630                 return status;
631         }
632
633         if ((qar_req.buf.len == 0)
634          || (sbuf.st_ex_size == 0)
635          || (qar_req.buf.file_off >= sbuf.st_ex_size)) {
636                 /* zero length range or after EOF, no ranges to return */
637                 return NT_STATUS_OK;
638         }
639
640         /* check for integer overflow */
641         if (qar_req.buf.file_off + qar_req.buf.len < qar_req.buf.file_off) {
642                 return NT_STATUS_INVALID_PARAMETER;
643         }
644
645         /*
646          * Maximum offset is either the last valid offset _before_ EOF, or the
647          * last byte offset within the requested range. -1 converts length to
648          * offset, which is easier to work with for SEEK_DATA/SEEK_HOLE, E.g.:
649          *
650          * /off=0             /off=512K          /st_ex_size=1M
651          * |-------------------------------------|
652          * | File data                           |
653          * |-------------------------------------|
654          *                                                   QAR end\
655          *                    |=====================================|
656          *                    |    QAR off=512K, len=1M             |
657          *                    |=================^===================|
658          *                                   max_off=1M - 1
659          *             QAR end\
660          * |==================|
661          * |QAR off=0 len=512K|
662          * |==================|
663          *                   ^
664          *                max_off=512K - 1
665          */
666         max_off = MIN(sbuf.st_ex_size,
667                       qar_req.buf.file_off + qar_req.buf.len) - 1;
668
669         if (!fsp->is_sparse) {
670                 struct file_alloced_range_buf qar_buf;
671
672                 /* file is non-sparse, claim file_off->max_off is allocated */
673                 qar_buf.file_off = qar_req.buf.file_off;
674                 /* + 1 to convert maximum offset back to length */
675                 qar_buf.len = max_off - qar_req.buf.file_off + 1;
676
677                 status = fsctl_qar_buf_push(mem_ctx, &qar_buf, &qar_array_blob);
678         } else {
679                 status = fsctl_qar_seek_fill(mem_ctx, fsp, qar_req.buf.file_off,
680                                              max_off, &qar_array_blob);
681         }
682         if (!NT_STATUS_IS_OK(status)) {
683                 return status;
684         }
685
686         /* marshall response buffer. */
687         qar_rsp.far_buf_array = qar_array_blob;
688
689         ndr_ret = ndr_push_struct_blob(out_output, mem_ctx, &qar_rsp,
690                 (ndr_push_flags_fn_t)ndr_push_fsctl_query_alloced_ranges_rsp);
691         if (ndr_ret != NDR_ERR_SUCCESS) {
692                 DEBUG(0, ("failed to marshall QAR rsp\n"));
693                 return NT_STATUS_INVALID_PARAMETER;
694         }
695
696         if (out_output->length > in_max_output) {
697                 DEBUG(2, ("QAR output len %lu exceeds max %lu\n",
698                           (unsigned long)out_output->length,
699                           (unsigned long)in_max_output));
700                 data_blob_free(out_output);
701                 return NT_STATUS_BUFFER_TOO_SMALL;
702         }
703
704         return NT_STATUS_OK;
705 }
706
707 static void smb2_ioctl_filesys_dup_extents_done(struct tevent_req *subreq);
708
709 struct tevent_req *smb2_ioctl_filesys(uint32_t ctl_code,
710                                       struct tevent_context *ev,
711                                       struct tevent_req *req,
712                                       struct smbd_smb2_ioctl_state *state)
713 {
714         NTSTATUS status;
715
716         switch (ctl_code) {
717         case FSCTL_GET_COMPRESSION:
718                 status = fsctl_get_cmprn(state, ev, state->fsp,
719                                          state->in_max_output,
720                                          &state->out_output);
721                 if (!tevent_req_nterror(req, status)) {
722                         tevent_req_done(req);
723                 }
724                 return tevent_req_post(req, ev);
725                 break;
726         case FSCTL_SET_COMPRESSION:
727                 status = fsctl_set_cmprn(state, ev, state->fsp,
728                                          &state->in_input);
729                 if (!tevent_req_nterror(req, status)) {
730                         tevent_req_done(req);
731                 }
732                 return tevent_req_post(req, ev);
733                 break;
734         case FSCTL_SET_ZERO_DATA:
735                 status = fsctl_zero_data(state, ev, state->fsp,
736                                          &state->in_input);
737                 if (!tevent_req_nterror(req, status)) {
738                         tevent_req_done(req);
739                 }
740                 return tevent_req_post(req, ev);
741                 break;
742         case FSCTL_QUERY_ALLOCATED_RANGES:
743                 status = fsctl_qar(state, ev, state->fsp,
744                                    &state->in_input,
745                                    state->in_max_output,
746                                    &state->out_output);
747                 if (!tevent_req_nterror(req, status)) {
748                         tevent_req_done(req);
749                 }
750                 return tevent_req_post(req, ev);
751                 break;
752         case FSCTL_DUP_EXTENTS_TO_FILE: {
753                 struct tevent_req *subreq = NULL;
754
755                 subreq = fsctl_dup_extents_send(state, ev,
756                                                 state->fsp,
757                                                 &state->in_input,
758                                                 state->smb2req);
759                 if (tevent_req_nomem(subreq, req)) {
760                         return tevent_req_post(req, ev);
761                 }
762                 tevent_req_set_callback(subreq,
763                                         smb2_ioctl_filesys_dup_extents_done,
764                                         req);
765                 return req;
766                 break;
767         }
768         default: {
769                 uint8_t *out_data = NULL;
770                 uint32_t out_data_len = 0;
771
772                 if (state->fsp == NULL) {
773                         status = NT_STATUS_NOT_SUPPORTED;
774                 } else {
775                         status = SMB_VFS_FSCTL(state->fsp,
776                                                state,
777                                                ctl_code,
778                                                state->smbreq->flags2,
779                                                state->in_input.data,
780                                                state->in_input.length,
781                                                &out_data,
782                                                state->in_max_output,
783                                                &out_data_len);
784                         state->out_output = data_blob_const(out_data, out_data_len);
785                         if (NT_STATUS_IS_OK(status)) {
786                                 tevent_req_done(req);
787                                 return tevent_req_post(req, ev);
788                         }
789                 }
790
791                 if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
792                         if (IS_IPC(state->smbreq->conn)) {
793                                 status = NT_STATUS_FS_DRIVER_REQUIRED;
794                         } else {
795                                 status = NT_STATUS_INVALID_DEVICE_REQUEST;
796                         }
797                 }
798
799                 tevent_req_nterror(req, status);
800                 return tevent_req_post(req, ev);
801                 break;
802         }
803         }
804
805         tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
806         return tevent_req_post(req, ev);
807 }
808
809 static void smb2_ioctl_filesys_dup_extents_done(struct tevent_req *subreq)
810 {
811         struct tevent_req *req = tevent_req_callback_data(subreq,
812                                                           struct tevent_req);
813         NTSTATUS status;
814
815         status = fsctl_dup_extents_recv(subreq);
816         TALLOC_FREE(subreq);
817         if (!tevent_req_nterror(req, status)) {
818                 tevent_req_done(req);
819         }
820 }