smbd/ioctl: match WS2016 ReFS set compression behaviour
[bbaumbach/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 static NTSTATUS fsctl_get_cmprn(TALLOC_CTX *mem_ctx,
35                                 struct tevent_context *ev,
36                                 struct files_struct *fsp,
37                                 size_t in_max_output,
38                                 DATA_BLOB *out_output)
39 {
40         struct compression_state cmpr_state;
41         enum ndr_err_code ndr_ret;
42         DATA_BLOB output;
43         NTSTATUS status;
44
45         if (fsp == NULL) {
46                 return NT_STATUS_FILE_CLOSED;
47         }
48
49         /* Windows doesn't check for SEC_FILE_READ_ATTRIBUTE permission here */
50
51         ZERO_STRUCT(cmpr_state);
52         if (fsp->conn->fs_capabilities & FILE_FILE_COMPRESSION) {
53                 status = SMB_VFS_GET_COMPRESSION(fsp->conn,
54                                                  mem_ctx,
55                                                  fsp,
56                                                  NULL,
57                                                  &cmpr_state.format);
58                 if (!NT_STATUS_IS_OK(status)) {
59                         return status;
60                 }
61         } else {
62                 /*
63                  * bso#12144: The underlying filesystem doesn't support
64                  * compression, so we should respond with "not-compressed"
65                  * (like WS2016 ReFS) instead of STATUS_NOT_SUPPORTED or
66                  * NT_STATUS_INVALID_DEVICE_REQUEST.
67                  */
68                 cmpr_state.format = COMPRESSION_FORMAT_NONE;
69         }
70
71         ndr_ret = ndr_push_struct_blob(&output, mem_ctx,
72                                        &cmpr_state,
73                         (ndr_push_flags_fn_t)ndr_push_compression_state);
74         if (ndr_ret != NDR_ERR_SUCCESS) {
75                 return NT_STATUS_INTERNAL_ERROR;
76         }
77
78         if (in_max_output < output.length) {
79                 DEBUG(1, ("max output %u too small for compression state %ld\n",
80                       (unsigned int)in_max_output, (long int)output.length));
81                 return NT_STATUS_INVALID_USER_BUFFER;
82         }
83         *out_output = output;
84
85         return NT_STATUS_OK;
86 }
87
88 static NTSTATUS fsctl_set_cmprn(TALLOC_CTX *mem_ctx,
89                                 struct tevent_context *ev,
90                                 struct files_struct *fsp,
91                                 DATA_BLOB *in_input)
92 {
93         struct compression_state cmpr_state;
94         enum ndr_err_code ndr_ret;
95         NTSTATUS status;
96
97         if (fsp == NULL) {
98                 return NT_STATUS_FILE_CLOSED;
99         }
100
101         /* WRITE_DATA permission is required, WRITE_ATTRIBUTES is not */
102         status = check_access_fsp(fsp, FILE_WRITE_DATA);
103         if (!NT_STATUS_IS_OK(status)) {
104                 return status;
105         }
106
107         ndr_ret = ndr_pull_struct_blob(in_input, mem_ctx, &cmpr_state,
108                         (ndr_pull_flags_fn_t)ndr_pull_compression_state);
109         if (ndr_ret != NDR_ERR_SUCCESS) {
110                 DEBUG(0, ("failed to unmarshall set compression req\n"));
111                 return NT_STATUS_INVALID_PARAMETER;
112         }
113
114         status = NT_STATUS_NOT_SUPPORTED;
115         if (fsp->conn->fs_capabilities & FILE_FILE_COMPRESSION) {
116                 status = SMB_VFS_SET_COMPRESSION(fsp->conn,
117                                                  mem_ctx,
118                                                  fsp,
119                                                  cmpr_state.format);
120         } else if (cmpr_state.format == COMPRESSION_FORMAT_NONE) {
121                 /*
122                  * bso#12144: The underlying filesystem doesn't support
123                  * compression. We should still accept set(FORMAT_NONE) requests
124                  * (like WS2016 ReFS).
125                  */
126                 status = NT_STATUS_OK;
127         }
128
129         return status;
130 }
131
132 static NTSTATUS fsctl_zero_data(TALLOC_CTX *mem_ctx,
133                                 struct tevent_context *ev,
134                                 struct files_struct *fsp,
135                                 DATA_BLOB *in_input)
136 {
137         struct file_zero_data_info zdata_info;
138         enum ndr_err_code ndr_ret;
139         struct lock_struct lck;
140         int mode;
141         uint64_t len;
142         int ret;
143         NTSTATUS status;
144
145         if (fsp == NULL) {
146                 return NT_STATUS_FILE_CLOSED;
147         }
148
149         /* WRITE_DATA permission is required */
150         status = check_access_fsp(fsp, FILE_WRITE_DATA);
151         if (!NT_STATUS_IS_OK(status)) {
152                 return status;
153         }
154
155         /* allow regardless of whether FS supports sparse or not */
156
157         ndr_ret = ndr_pull_struct_blob(in_input, mem_ctx, &zdata_info,
158                         (ndr_pull_flags_fn_t)ndr_pull_file_zero_data_info);
159         if (ndr_ret != NDR_ERR_SUCCESS) {
160                 DEBUG(0, ("failed to unmarshall zero data request\n"));
161                 return NT_STATUS_INVALID_PARAMETER;
162         }
163
164         if (zdata_info.beyond_final_zero < zdata_info.file_off) {
165                 DEBUG(0, ("invalid zero data params: off %lu, bfz, %lu\n",
166                           (unsigned long)zdata_info.file_off,
167                           (unsigned long)zdata_info.beyond_final_zero));
168                 return NT_STATUS_INVALID_PARAMETER;
169         }
170
171         /* convert strange "beyond final zero" param into length */
172         len = zdata_info.beyond_final_zero - zdata_info.file_off;
173
174         if (len == 0) {
175                 DEBUG(2, ("zero data called with zero length range\n"));
176                 return NT_STATUS_OK;
177         }
178
179         init_strict_lock_struct(fsp,
180                                 fsp->op->global->open_persistent_id,
181                                 zdata_info.file_off,
182                                 len,
183                                 WRITE_LOCK,
184                                 &lck);
185
186         if (!SMB_VFS_STRICT_LOCK(fsp->conn, fsp, &lck)) {
187                 DEBUG(2, ("failed to lock range for zero-data\n"));
188                 return NT_STATUS_FILE_LOCK_CONFLICT;
189         }
190
191         /*
192          * MS-FSCC <58> Section 2.3.67
193          * This FSCTL sets the range of bytes to zero (0) without extending the
194          * file size.
195          *
196          * The VFS_FALLOCATE_FL_KEEP_SIZE flag is used to satisfy this
197          * constraint.
198          */
199
200         mode = VFS_FALLOCATE_FL_PUNCH_HOLE | VFS_FALLOCATE_FL_KEEP_SIZE;
201         ret = SMB_VFS_FALLOCATE(fsp, mode, zdata_info.file_off, len);
202         if (ret == -1)  {
203                 status = map_nt_error_from_unix_common(errno);
204                 DEBUG(2, ("zero-data fallocate(0x%x) failed: %s\n", mode,
205                       strerror(errno)));
206                 SMB_VFS_STRICT_UNLOCK(fsp->conn, fsp, &lck);
207                 return status;
208         }
209
210         if (!fsp->is_sparse && lp_strict_allocate(SNUM(fsp->conn))) {
211                 /*
212                  * File marked non-sparse and "strict allocate" is enabled -
213                  * allocate the range that we just punched out.
214                  * In future FALLOC_FL_ZERO_RANGE could be used exclusively for
215                  * this, but it's currently only supported on XFS and ext4.
216                  *
217                  * The newly allocated range still won't be found by SEEK_DATA
218                  * for QAR, but stat.st_blocks will reflect it.
219                  */
220                 ret = SMB_VFS_FALLOCATE(fsp, VFS_FALLOCATE_FL_KEEP_SIZE,
221                                         zdata_info.file_off, len);
222                 if (ret == -1)  {
223                         status = map_nt_error_from_unix_common(errno);
224                         DEBUG(0, ("fallocate failed: %s\n", strerror(errno)));
225                         SMB_VFS_STRICT_UNLOCK(fsp->conn, fsp, &lck);
226                         return status;
227                 }
228         }
229
230         SMB_VFS_STRICT_UNLOCK(fsp->conn, fsp, &lck);
231         return NT_STATUS_OK;
232 }
233
234 static NTSTATUS fsctl_qar_buf_push(TALLOC_CTX *mem_ctx,
235                                    struct file_alloced_range_buf *qar_buf,
236                                    DATA_BLOB *qar_array_blob)
237 {
238         DATA_BLOB new_slot;
239         enum ndr_err_code ndr_ret;
240         bool ok;
241
242         ndr_ret = ndr_push_struct_blob(&new_slot, mem_ctx, qar_buf,
243                         (ndr_push_flags_fn_t)ndr_push_file_alloced_range_buf);
244         if (ndr_ret != NDR_ERR_SUCCESS) {
245                 DEBUG(0, ("failed to marshall QAR buf\n"));
246                 return NT_STATUS_INVALID_PARAMETER;
247         }
248
249         /* TODO should be able to avoid copy by pushing into prealloced buf */
250         ok = data_blob_append(mem_ctx, qar_array_blob, new_slot.data,
251                               new_slot.length);
252         data_blob_free(&new_slot);
253         if (!ok) {
254                 return NT_STATUS_NO_MEMORY;
255         }
256
257         return NT_STATUS_OK;
258 }
259
260 static NTSTATUS fsctl_qar_seek_fill(TALLOC_CTX *mem_ctx,
261                                     struct files_struct *fsp,
262                                     off_t curr_off,
263                                     off_t max_off,
264                                     DATA_BLOB *qar_array_blob)
265 {
266         NTSTATUS status = NT_STATUS_NOT_SUPPORTED;
267
268 #ifdef HAVE_LSEEK_HOLE_DATA
269         while (curr_off <= max_off) {
270                 off_t data_off;
271                 off_t hole_off;
272                 struct file_alloced_range_buf qar_buf;
273
274                 /* seek next data */
275                 data_off = SMB_VFS_LSEEK(fsp, curr_off, SEEK_DATA);
276                 if ((data_off == -1) && (errno == ENXIO)) {
277                         /* no data from curr_off to EOF */
278                         break;
279                 } else if (data_off == -1) {
280                         status = map_nt_error_from_unix_common(errno);
281                         DEBUG(1, ("lseek data failed: %s\n", strerror(errno)));
282                         return status;
283                 }
284
285                 if (data_off > max_off) {
286                         /* found something, but passed range of interest */
287                         break;
288                 }
289
290                 hole_off = SMB_VFS_LSEEK(fsp, data_off, SEEK_HOLE);
291                 if (hole_off == -1) {
292                         status = map_nt_error_from_unix_common(errno);
293                         DEBUG(1, ("lseek hole failed: %s\n", strerror(errno)));
294                         return status;
295                 }
296
297                 if (hole_off <= data_off) {
298                         DEBUG(1, ("lseek inconsistent: hole %lu at or before "
299                                   "data %lu\n", (unsigned long)hole_off,
300                                   (unsigned long)data_off));
301                         return NT_STATUS_INTERNAL_ERROR;
302                 }
303
304                 qar_buf.file_off = data_off;
305                 /* + 1 to convert maximum offset to length */
306                 qar_buf.len = MIN(hole_off, max_off + 1) - data_off;
307
308                 status = fsctl_qar_buf_push(mem_ctx, &qar_buf, qar_array_blob);
309                 if (!NT_STATUS_IS_OK(status)) {
310                         return NT_STATUS_NO_MEMORY;
311                 }
312
313                 curr_off = hole_off;
314         }
315         status = NT_STATUS_OK;
316 #endif
317
318         return status;
319 }
320
321 static NTSTATUS fsctl_qar(TALLOC_CTX *mem_ctx,
322                           struct tevent_context *ev,
323                           struct files_struct *fsp,
324                           DATA_BLOB *in_input,
325                           size_t in_max_output,
326                           DATA_BLOB *out_output)
327 {
328         struct fsctl_query_alloced_ranges_req qar_req;
329         struct fsctl_query_alloced_ranges_rsp qar_rsp;
330         DATA_BLOB qar_array_blob = data_blob_null;
331         uint64_t max_off;
332         enum ndr_err_code ndr_ret;
333         int ret;
334         NTSTATUS status;
335         SMB_STRUCT_STAT sbuf;
336
337         if (fsp == NULL) {
338                 return NT_STATUS_FILE_CLOSED;
339         }
340
341         /* READ_DATA permission is required */
342         status = check_access_fsp(fsp, FILE_READ_DATA);
343         if (!NT_STATUS_IS_OK(status)) {
344                 return status;
345         }
346
347         ndr_ret = ndr_pull_struct_blob(in_input, mem_ctx, &qar_req,
348                 (ndr_pull_flags_fn_t)ndr_pull_fsctl_query_alloced_ranges_req);
349         if (ndr_ret != NDR_ERR_SUCCESS) {
350                 DEBUG(0, ("failed to unmarshall QAR req\n"));
351                 return NT_STATUS_INVALID_PARAMETER;
352         }
353
354         /*
355          * XXX Windows Server 2008 & 2012 servers don't return lock-conflict
356          * for QAR requests over an exclusively locked range!
357          */
358
359         ret = SMB_VFS_FSTAT(fsp, &sbuf);
360         if (ret == -1) {
361                 status = map_nt_error_from_unix_common(errno);
362                 DEBUG(2, ("fstat failed: %s\n", strerror(errno)));
363                 return status;
364         }
365
366         if ((qar_req.buf.len == 0)
367          || (sbuf.st_ex_size == 0)
368          || (qar_req.buf.file_off >= sbuf.st_ex_size)) {
369                 /* zero length range or after EOF, no ranges to return */
370                 return NT_STATUS_OK;
371         }
372
373         /* check for integer overflow */
374         if (qar_req.buf.file_off + qar_req.buf.len < qar_req.buf.file_off) {
375                 return NT_STATUS_INVALID_PARAMETER;
376         }
377
378         /*
379          * Maximum offset is either the last valid offset _before_ EOF, or the
380          * last byte offset within the requested range. -1 converts length to
381          * offset, which is easier to work with for SEEK_DATA/SEEK_HOLE, E.g.:
382          *
383          * /off=0             /off=512K          /st_ex_size=1M
384          * |-------------------------------------|
385          * | File data                           |
386          * |-------------------------------------|
387          *                                                   QAR end\
388          *                    |=====================================|
389          *                    |    QAR off=512K, len=1M             |
390          *                    |=================^===================|
391          *                                   max_off=1M - 1
392          *             QAR end\
393          * |==================|
394          * |QAR off=0 len=512K|
395          * |==================|
396          *                   ^
397          *                max_off=512K - 1
398          */
399         max_off = MIN(sbuf.st_ex_size,
400                       qar_req.buf.file_off + qar_req.buf.len) - 1;
401
402         if (!fsp->is_sparse) {
403                 struct file_alloced_range_buf qar_buf;
404
405                 /* file is non-sparse, claim file_off->max_off is allocated */
406                 qar_buf.file_off = qar_req.buf.file_off;
407                 /* + 1 to convert maximum offset back to length */
408                 qar_buf.len = max_off - qar_req.buf.file_off + 1;
409
410                 status = fsctl_qar_buf_push(mem_ctx, &qar_buf, &qar_array_blob);
411         } else {
412                 status = fsctl_qar_seek_fill(mem_ctx, fsp, qar_req.buf.file_off,
413                                              max_off, &qar_array_blob);
414         }
415         if (!NT_STATUS_IS_OK(status)) {
416                 return status;
417         }
418
419         /* marshall response buffer. */
420         qar_rsp.far_buf_array = qar_array_blob;
421
422         ndr_ret = ndr_push_struct_blob(out_output, mem_ctx, &qar_rsp,
423                 (ndr_push_flags_fn_t)ndr_push_fsctl_query_alloced_ranges_rsp);
424         if (ndr_ret != NDR_ERR_SUCCESS) {
425                 DEBUG(0, ("failed to marshall QAR rsp\n"));
426                 return NT_STATUS_INVALID_PARAMETER;
427         }
428
429         if (out_output->length > in_max_output) {
430                 DEBUG(2, ("QAR output len %lu exceeds max %lu\n",
431                           (unsigned long)out_output->length,
432                           (unsigned long)in_max_output));
433                 data_blob_free(out_output);
434                 return NT_STATUS_BUFFER_TOO_SMALL;
435         }
436
437         return NT_STATUS_OK;
438 }
439
440 struct tevent_req *smb2_ioctl_filesys(uint32_t ctl_code,
441                                       struct tevent_context *ev,
442                                       struct tevent_req *req,
443                                       struct smbd_smb2_ioctl_state *state)
444 {
445         NTSTATUS status;
446
447         switch (ctl_code) {
448         case FSCTL_GET_COMPRESSION:
449                 status = fsctl_get_cmprn(state, ev, state->fsp,
450                                          state->in_max_output,
451                                          &state->out_output);
452                 if (!tevent_req_nterror(req, status)) {
453                         tevent_req_done(req);
454                 }
455                 return tevent_req_post(req, ev);
456                 break;
457         case FSCTL_SET_COMPRESSION:
458                 status = fsctl_set_cmprn(state, ev, state->fsp,
459                                          &state->in_input);
460                 if (!tevent_req_nterror(req, status)) {
461                         tevent_req_done(req);
462                 }
463                 return tevent_req_post(req, ev);
464                 break;
465         case FSCTL_SET_ZERO_DATA:
466                 status = fsctl_zero_data(state, ev, state->fsp,
467                                          &state->in_input);
468                 if (!tevent_req_nterror(req, status)) {
469                         tevent_req_done(req);
470                 }
471                 return tevent_req_post(req, ev);
472                 break;
473         case FSCTL_QUERY_ALLOCATED_RANGES:
474                 status = fsctl_qar(state, ev, state->fsp,
475                                    &state->in_input,
476                                    state->in_max_output,
477                                    &state->out_output);
478                 if (!tevent_req_nterror(req, status)) {
479                         tevent_req_done(req);
480                 }
481                 return tevent_req_post(req, ev);
482                 break;
483         default: {
484                 uint8_t *out_data = NULL;
485                 uint32_t out_data_len = 0;
486
487                 if (state->fsp == NULL) {
488                         status = NT_STATUS_NOT_SUPPORTED;
489                 } else {
490                         status = SMB_VFS_FSCTL(state->fsp,
491                                                state,
492                                                ctl_code,
493                                                state->smbreq->flags2,
494                                                state->in_input.data,
495                                                state->in_input.length,
496                                                &out_data,
497                                                state->in_max_output,
498                                                &out_data_len);
499                         state->out_output = data_blob_const(out_data, out_data_len);
500                         if (NT_STATUS_IS_OK(status)) {
501                                 tevent_req_done(req);
502                                 return tevent_req_post(req, ev);
503                         }
504                 }
505
506                 if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
507                         if (IS_IPC(state->smbreq->conn)) {
508                                 status = NT_STATUS_FS_DRIVER_REQUIRED;
509                         } else {
510                                 status = NT_STATUS_INVALID_DEVICE_REQUEST;
511                         }
512                 }
513
514                 tevent_req_nterror(req, status);
515                 return tevent_req_post(req, ev);
516                 break;
517         }
518         }
519
520         tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
521         return tevent_req_post(req, ev);
522 }