1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2020 Tintri by DDN, Inc. All rights reserved.
14 */
15
16 /*
17 * Support functions for smb2_ioctl/fsctl codes:
18 * FSCTL_SET_SPARSE
19 * FSCTL_SET_ZERO_DATA
20 * FSCTL_QUERY_ALLOCATED_RANGES
21 */
22
23 #include <smbsrv/smb2_kproto.h>
24 #include <smbsrv/smb_fsops.h>
25 #include <smb/winioctl.h>
26
27 /*
28 * FSCTL_SET_SPARSE
29 *
30 * In args: one byte flag (optional: default TRUE)
31 */
32 uint32_t
smb2_fsctl_set_sparse(smb_request_t * sr,smb_fsctl_t * fsctl)33 smb2_fsctl_set_sparse(smb_request_t *sr, smb_fsctl_t *fsctl)
34 {
35 smb_attr_t attr;
36 smb_ofile_t *ofile = sr->fid_ofile;
37 cred_t *kcr;
38 uint32_t amask;
39 uint32_t status;
40 uint8_t flag;
41 int rc;
42
43 rc = smb_mbc_decodef(fsctl->in_mbc, "b", &flag);
44 if (rc != 0)
45 flag = 0xff;
46
47 if (!smb_node_is_file(ofile->f_node))
48 return (NT_STATUS_INVALID_PARAMETER);
49
50 /*
51 * Allow if we have any of FILE_WRITE_ATTRIBUTES,
52 * FILE_WRITE_DATA, FILE_APPEND_DATA
53 */
54 amask = FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA | FILE_APPEND_DATA;
55 if ((ofile->f_granted_access & amask) == 0)
56 return (NT_STATUS_ACCESS_DENIED);
57
58 /*
59 * Need the current DOS attributes
60 */
61 bzero(&attr, sizeof (attr));
62 attr.sa_mask = SMB_AT_DOSATTR;
63 kcr = zone_kcred();
64 status = smb_node_getattr(sr, ofile->f_node, kcr, ofile, &attr);
65 if (status != NT_STATUS_SUCCESS)
66 return (status);
67
68 if (flag != 0) {
69 /* Set "sparse" */
70 if (attr.sa_dosattr & FILE_ATTRIBUTE_SPARSE_FILE)
71 return (0);
72 attr.sa_dosattr |= FILE_ATTRIBUTE_SPARSE_FILE;
73 } else {
74 /* Clear "sparse" */
75 if ((attr.sa_dosattr & FILE_ATTRIBUTE_SPARSE_FILE) == 0)
76 return (0);
77 attr.sa_dosattr &= ~FILE_ATTRIBUTE_SPARSE_FILE;
78 }
79
80 attr.sa_mask = SMB_AT_DOSATTR;
81 status = smb_node_setattr(sr, ofile->f_node, kcr, ofile, &attr);
82 return (status);
83 }
84
85 /*
86 * FSCTL_SET_ZERO_DATA
87 *
88 * In args: uint64_t start_off, end_off
89 */
90 uint32_t
smb2_fsctl_set_zero_data(smb_request_t * sr,smb_fsctl_t * fsctl)91 smb2_fsctl_set_zero_data(smb_request_t *sr, smb_fsctl_t *fsctl)
92 {
93 smb_attr_t attr;
94 smb_ofile_t *ofile = sr->fid_ofile;
95 uint64_t start_off, end_off, zero_len;
96 uint32_t status;
97 int rc;
98
99 rc = smb_mbc_decodef(fsctl->in_mbc, "qq",
100 &start_off, &end_off);
101 if (rc != 0)
102 return (NT_STATUS_BUFFER_TOO_SMALL);
103
104 /*
105 * The given offsets are actually int64_t (signed).
106 */
107 if (start_off > INT64_MAX ||
108 end_off > INT64_MAX ||
109 start_off > end_off)
110 return (NT_STATUS_INVALID_PARAMETER);
111
112 if (!smb_node_is_file(ofile->f_node))
113 return (NT_STATUS_INVALID_PARAMETER);
114
115 /*
116 * This operation is effectively a write (of zeros)
117 */
118 status = smb_ofile_access(ofile, ofile->f_cr, FILE_WRITE_DATA);
119 if (status != NT_STATUS_SUCCESS)
120 return (status);
121
122 /*
123 * Need the file size
124 */
125 bzero(&attr, sizeof (attr));
126 attr.sa_mask = SMB_AT_SIZE;
127 status = smb_node_getattr(sr, ofile->f_node, ofile->f_cr,
128 ofile, &attr);
129 if (status != NT_STATUS_SUCCESS)
130 return (status);
131
132 /*
133 * Ignore any zero-ing beyond EOF
134 */
135 if (end_off > attr.sa_vattr.va_size)
136 end_off = attr.sa_vattr.va_size;
137 if (start_off >= end_off)
138 return (0);
139 zero_len = end_off - start_off;
140
141 /*
142 * Check for lock conflicting with the write.
143 */
144 status = smb_lock_range_access(sr, ofile->f_node,
145 start_off, zero_len, B_TRUE);
146 if (status != 0)
147 return (status); /* == FILE_LOCK_CONFLICT */
148
149 rc = smb_fsop_freesp(sr, ofile->f_cr, ofile,
150 start_off, zero_len);
151 if (rc != 0)
152 status = smb_errno2status(rc);
153
154 return (status);
155 }
156
157 /*
158 * FSCTL_QUERY_ALLOCATED_RANGES
159 *
160 * Incoming args: uint64_t start_off, end_off
161 */
162 struct alloc_range {
163 off64_t off;
164 off64_t len;
165 };
166 uint32_t
smb2_fsctl_query_alloc_ranges(smb_request_t * sr,smb_fsctl_t * fsctl)167 smb2_fsctl_query_alloc_ranges(smb_request_t *sr, smb_fsctl_t *fsctl)
168 {
169 smb_attr_t attr;
170 cred_t *kcr;
171 smb_ofile_t *ofile = sr->fid_ofile;
172 struct alloc_range arg, res;
173 off64_t cur_off, end_off;
174 uint32_t status;
175 int err, rc;
176
177 /*
178 * Most ioctls return NT_STATUS_BUFFER_TOO_SMALL for
179 * short in/out buffers, but for this one, MS-FSA
180 * says short input returns invalid parameter.
181 */
182 rc = smb_mbc_decodef(fsctl->in_mbc, "qq", &arg.off, &arg.len);
183 if (rc != 0)
184 return (NT_STATUS_INVALID_PARAMETER);
185
186 /*
187 * The given offsets are actually int64_t (signed).
188 */
189 end_off = arg.off + arg.len;
190 if (arg.off > INT64_MAX || arg.len < 0 ||
191 end_off > INT64_MAX || end_off < arg.off)
192 return (NT_STATUS_INVALID_PARAMETER);
193
194 if (!smb_node_is_file(ofile->f_node))
195 return (NT_STATUS_INVALID_PARAMETER);
196
197 /*
198 * This operation is effectively a read
199 */
200 status = smb_ofile_access(ofile, ofile->f_cr, FILE_READ_DATA);
201 if (status != NT_STATUS_SUCCESS)
202 return (status);
203
204 if (arg.len == 0) {
205 /* MS-FSA says empty result for this. */
206 return (0);
207 }
208
209 /*
210 * Need the file size and dosattr
211 */
212 bzero(&attr, sizeof (attr));
213 attr.sa_mask = SMB_AT_SIZE | SMB_AT_DOSATTR;
214 kcr = zone_kcred();
215 status = smb_node_getattr(sr, ofile->f_node, kcr, ofile, &attr);
216 if (status != NT_STATUS_SUCCESS)
217 return (status);
218 if (end_off > attr.sa_vattr.va_size)
219 end_off = attr.sa_vattr.va_size;
220
221 /*
222 * Only sparse files should present un-allocated ranges.
223 * If non-sparse, MS-FSA says (just return one range).
224 */
225 if ((attr.sa_dosattr & FILE_ATTRIBUTE_SPARSE_FILE) == 0) {
226 if (arg.off < end_off) {
227 res.off = arg.off;
228 res.len = end_off - arg.off;
229 rc = smb_mbc_encodef(fsctl->out_mbc, "qq",
230 res.off, res.len);
231 if (rc != 0)
232 return (NT_STATUS_BUFFER_TOO_SMALL);
233 }
234 return (0);
235 }
236
237 cur_off = arg.off;
238 while (cur_off < end_off) {
239 off64_t data, hole;
240
241 data = cur_off;
242 err = smb_fsop_next_alloc_range(kcr, ofile->f_node,
243 &data, &hole);
244 if (err != 0)
245 break;
246
247 /* sanity check data (ensure progress) */
248 if (data < cur_off) {
249 ASSERT(0);
250 data = cur_off;
251 }
252
253 /* Normal termination */
254 if (data >= end_off)
255 break;
256
257 /* sanity check hole (ensure progress) */
258 if (hole <= data)
259 hole = end_off;
260
261 /* Trim this range as needed. */
262 if (hole > end_off)
263 hole = end_off;
264
265 res.off = data;
266 res.len = hole - data;
267
268 if (res.len > 0) {
269 rc = smb_mbc_encodef(fsctl->out_mbc, "qq",
270 res.off, res.len);
271 if (rc != 0)
272 return (NT_STATUS_BUFFER_TOO_SMALL);
273 }
274
275 cur_off = hole;
276 }
277
278 return (0);
279 }
280
281 /*
282 * Copy a segment of a file, preserving sparseness.
283 * Uses a caller-provided buffer for read/write.
284 * Caller should already have checked for locks.
285 *
286 * On entry, *residp is the length to copy.
287 * On return, it's the "resid" (amount not copied)
288 *
289 * If this gets an error from any I/O, return it, even if some data
290 * have already been copied. The caller should normally ignore an
291 * error when some data have been copied.
292 */
293 uint32_t
smb2_sparse_copy(smb_request_t * sr,smb_ofile_t * src_ofile,smb_ofile_t * dst_ofile,off64_t src_off,off64_t dst_off,uint32_t * residp,void * buffer,size_t bufsize)294 smb2_sparse_copy(
295 smb_request_t *sr,
296 smb_ofile_t *src_ofile, smb_ofile_t *dst_ofile,
297 off64_t src_off, off64_t dst_off, uint32_t *residp,
298 void *buffer, size_t bufsize)
299 {
300 iovec_t iov;
301 uio_t uio;
302 off64_t data, hole;
303 uint32_t xfer;
304 uint32_t status = 0;
305 int rc;
306
307 while (*residp > 0) {
308
309 if (sr->sr_state != SMB_REQ_STATE_ACTIVE)
310 break;
311
312 data = src_off;
313 rc = smb_fsop_next_alloc_range(src_ofile->f_cr,
314 src_ofile->f_node, &data, &hole);
315 switch (rc) {
316 case 0:
317 /* Found data, hole */
318 break;
319 case ENXIO:
320 /* No data after here (will skip below). */
321 data = hole = (src_off + *residp);
322 break;
323 default:
324 cmn_err(CE_NOTE,
325 "smb_fsop_next_alloc_range: rc=%d", rc);
326 /* FALLTHROUGH */
327 case ENOSYS: /* FS does not support VOP_IOCTL... */
328 case ENOTTY: /* ... or _FIO_SEEK_DATA, _HOLE */
329 data = src_off;
330 hole = src_off + *residp;
331 break;
332 }
333
334 /*
335 * Don't try to go past (src_off + *residp)
336 */
337 if (hole > (src_off + *residp))
338 hole = src_off + *residp;
339 if (data > hole)
340 data = hole;
341
342 /*
343 * If there's a gap (src_off .. data)
344 * skip in src_ofile, zero in dst_ofile
345 */
346 if (src_off < data) {
347 off64_t skip = data - src_off;
348 rc = smb_fsop_freesp(sr, dst_ofile->f_cr,
349 dst_ofile, dst_off, skip);
350 if (rc == 0) {
351 src_off += skip;
352 dst_off += skip;
353 *residp -= (uint32_t)skip;
354 } else {
355 /* Fall back to regular copy */
356 data = src_off;
357 }
358 }
359 ASSERT(src_off == data);
360
361 /*
362 * Copy this segment: src_off .. hole
363 */
364 while (src_off < hole) {
365 ssize_t tsize = hole - src_off;
366 if (tsize > bufsize)
367 tsize = bufsize;
368
369 /*
370 * Read src_ofile into buffer
371 */
372 iov.iov_base = buffer;
373 iov.iov_len = tsize;
374 bzero(&uio, sizeof (uio));
375 uio.uio_iov = &iov;
376 uio.uio_iovcnt = 1;
377 uio.uio_resid = tsize;
378 uio.uio_loffset = src_off;
379 uio.uio_segflg = UIO_SYSSPACE;
380 uio.uio_extflg = UIO_COPY_DEFAULT;
381
382 rc = smb_fsop_read(sr, src_ofile->f_cr,
383 src_ofile->f_node, src_ofile, &uio, 0);
384 if (rc != 0) {
385 status = smb_errno2status(rc);
386 return (status);
387 }
388 /* Note: Could be partial read. */
389 tsize -= uio.uio_resid;
390 ASSERT(tsize > 0);
391
392 /*
393 * Write buffer to dst_ofile
394 */
395 iov.iov_base = buffer;
396 iov.iov_len = tsize;
397 bzero(&uio, sizeof (uio));
398 uio.uio_iov = &iov;
399 uio.uio_iovcnt = 1;
400 uio.uio_resid = tsize;
401 uio.uio_loffset = dst_off;
402 uio.uio_segflg = UIO_SYSSPACE;
403 uio.uio_extflg = UIO_COPY_DEFAULT;
404
405 rc = smb_fsop_write(sr, dst_ofile->f_cr,
406 dst_ofile->f_node, dst_ofile, &uio, &xfer, 0);
407 if (rc != 0) {
408 status = smb_errno2status(rc);
409 return (status);
410 }
411 ASSERT(xfer <= tsize);
412
413 src_off += xfer;
414 dst_off += xfer;
415 *residp -= xfer;
416 }
417 ASSERT(src_off == hole);
418 }
419
420 return (status);
421 }
422
423 /*
424 * Not sure what header this might go in.
425 */
426 #define FILE_REGION_USAGE_VALID_CACHED_DATA 1
427 #define FILE_REGION_USAGE_VALID_NONCACHED_DATA 2
428 #define FILE_REGION_USAGE_VALID__MASK 3
429
430 typedef struct _FILE_REGION_INFO {
431 uint64_t off;
432 uint64_t len;
433 uint32_t usage;
434 uint32_t reserved;
435 } FILE_REGION_INFO;
436
437
438 /*
439 * FSCTL_QUERY_FILE_REGIONS
440 *
441 * [MS-FSCC] 2.3.39 FSCTL_QUERY_FILE_REGIONS Request
442 * [MS-FSCC] 2.3.40.1 FILE_REGION_INFO
443 *
444 * Looks like Hyper-V uses this to query the "valid data length",
445 * which to us is the beginning offset of the last "hole".
446 * Similar logic as smb2_sparse_copy()
447 */
448 uint32_t
smb2_fsctl_query_file_regions(smb_request_t * sr,smb_fsctl_t * fsctl)449 smb2_fsctl_query_file_regions(smb_request_t *sr, smb_fsctl_t *fsctl)
450 {
451 smb_attr_t attr;
452 cred_t *kcr;
453 smb_ofile_t *ofile = sr->fid_ofile;
454 FILE_REGION_INFO arg;
455 off64_t cur_off, end_off, eof;
456 off64_t data, hole;
457 uint32_t tot_regions, put_regions;
458 uint32_t status;
459 int rc;
460
461 if (fsctl->InputCount == 0) {
462 arg.off = 0;
463 arg.len = INT64_MAX;
464 arg.usage = FILE_REGION_USAGE_VALID_CACHED_DATA;
465 arg.reserved = 0;
466 } else {
467 /* min size check: reserved is optional */
468 rc = smb_mbc_decodef(fsctl->in_mbc, "qql",
469 &arg.off, &arg.len, &arg.usage);
470 if (rc != 0)
471 return (NT_STATUS_BUFFER_TOO_SMALL);
472
473 /*
474 * The given offset and length are int64_t (signed).
475 */
476 if (arg.off > INT64_MAX || arg.len > INT64_MAX)
477 return (NT_STATUS_INVALID_PARAMETER);
478 if ((arg.off + arg.len) > INT64_MAX)
479 return (NT_STATUS_INVALID_PARAMETER);
480 if ((arg.usage & FILE_REGION_USAGE_VALID__MASK) == 0)
481 return (NT_STATUS_INVALID_PARAMETER);
482 arg.reserved = 0;
483 }
484
485 if (fsctl->MaxOutputResp < (16 + sizeof (FILE_REGION_INFO)))
486 return (NT_STATUS_BUFFER_TOO_SMALL);
487
488 if (!smb_node_is_file(ofile->f_node))
489 return (NT_STATUS_INVALID_PARAMETER);
490
491 /*
492 * This operation is effectively a read
493 */
494 status = smb_ofile_access(ofile, ofile->f_cr, FILE_READ_DATA);
495 if (status != NT_STATUS_SUCCESS)
496 return (status);
497
498 /*
499 * Need the file size and dosattr
500 */
501 bzero(&attr, sizeof (attr));
502 attr.sa_mask = SMB_AT_SIZE | SMB_AT_DOSATTR;
503 kcr = zone_kcred();
504 status = smb_node_getattr(sr, ofile->f_node, kcr, ofile, &attr);
505 if (status != NT_STATUS_SUCCESS)
506 return (status);
507
508 cur_off = arg.off;
509 end_off = arg.off + arg.len;
510 eof = attr.sa_vattr.va_size;
511
512 /*
513 * If (InputRegion.FileOffset > Eof) OR
514 * ((InputRegion.FileOffset == Eof) AND (Eof > 0)),
515 * the operation MUST return STATUS_SUCCESS, with
516 * BytesReturned set to 0 (empty data response)
517 */
518 if ((arg.off > eof) || (arg.off == eof && eof > 0))
519 return (NT_STATUS_SUCCESS);
520 if (end_off > eof)
521 end_off = eof;
522
523 /*
524 * We're going to return at least one region. Put place-holder
525 * data for the fixed part of the response. Will overwrite this
526 * later, when we know how many regions there are and how many
527 * of those fit in the allowed response buffer space. These are:
528 * Flags, TotalRegionEntryCount, RegionEntryCount, Reserved
529 */
530 rc = smb_mbc_encodef(fsctl->out_mbc, "llll", 0, 0, 0, 0);
531 if (rc != 0)
532 return (NT_STATUS_BUFFER_TOO_SMALL);
533 tot_regions = put_regions = 0;
534
535 /*
536 * Get the first pair of (data, hole) offsets at or after
537 * the current offset (cur_off).
538 */
539 data = hole = cur_off;
540 rc = smb_fsop_next_alloc_range(ofile->f_cr,
541 ofile->f_node, &data, &hole);
542 switch (rc) {
543 case 0:
544 /* Found (data, hole) */
545 break;
546 case ENXIO:
547 /* No more data after cur_off. */
548 break;
549 default:
550 cmn_err(CE_NOTE, "smb_fsop_next_alloc_range: rc=%d", rc);
551 /* FALLTHROUGH */
552 case ENOSYS: /* FS does not support VOP_IOCTL... */
553 case ENOTTY: /* ... or _FIO_SEEK_DATA, _HOLE */
554 data = cur_off;
555 hole = eof;
556 break;
557 }
558 DTRACE_PROBE2(range0, uint64_t, data, uint64_t, hole);
559
560 /*
561 * Only sparse files should present un-allocated regions.
562 * If non-sparse, MS-FSA says to just return one region.
563 * There still can be a "hole" but only one, starting at
564 * "valid data length" (VDL) and ending at end of file.
565 * To determine VDL, find the last (data,hole) pair, then
566 * VDL is the last "hole" offset. Note that the above
567 * smb_fsop_next_alloc_range may have set data somewhere
568 * above cur_off, so we we have to reset that here.
569 */
570 if ((attr.sa_dosattr & FILE_ATTRIBUTE_SPARSE_FILE) == 0) {
571 /*
572 * This works, but it's rather inefficient, and
573 * usually just finds VDL==EOF. Should look into
574 * whether there's a faster way to find the VDL.
575 */
576 #if 0
577 off64_t next_data, next_hole;
578 data = cur_off;
579 do {
580 next_data = next_hole = hole;
581 rc = smb_fsop_next_alloc_range(ofile->f_cr,
582 ofile->f_node, &next_data, &next_hole);
583 if (rc == 0) {
584 hole = next_hole;
585 }
586 } while (rc == 0);
587 #else
588 /* Assume no "holes" anywhere (VDL==EOF) */
589 data = cur_off;
590 hole = eof;
591 #endif
592 DTRACE_PROBE2(range1, uint64_t, data, uint64_t, hole);
593 }
594
595 /*
596 * Loop terminates in the middle, continuing
597 * while (cur_off < end_off)
598 */
599 for (;;) {
600 /*
601 * We have a data region that covers (data, hole).
602 * It could be partially or entirely beyond the range
603 * the caller asked about (if so trim it).
604 */
605 if (hole > end_off)
606 hole = end_off;
607 if (data > hole)
608 data = hole;
609
610 /*
611 * If cur_off < data encode a "hole" region
612 * (cur_off,data) and advance cur_off.
613 */
614 if (cur_off < data) {
615 rc = smb_mbc_encodef(fsctl->out_mbc, "qqll",
616 cur_off,
617 (data - cur_off),
618 0, // usage (hole)
619 0); // reserved
620 cur_off = data;
621 if (rc == 0)
622 put_regions++;
623 tot_regions++;
624 }
625
626 /*
627 * If cur_off < hole encode a "data" region
628 * (cur_off,hole) and advance cur_off.
629 */
630 if (cur_off < hole) {
631 rc = smb_mbc_encodef(fsctl->out_mbc, "qqll",
632 cur_off,
633 (hole - cur_off),
634 FILE_REGION_USAGE_VALID_CACHED_DATA,
635 0); // reserved
636 cur_off = hole;
637 if (rc == 0)
638 put_regions++;
639 tot_regions++;
640 }
641
642 /*
643 * Normal loop termination
644 */
645 if (cur_off >= end_off)
646 break;
647
648 /*
649 * Get the next region (data, hole) starting on or after
650 * the current offset (cur_off).
651 */
652 data = hole = cur_off;
653 rc = smb_fsop_next_alloc_range(ofile->f_cr,
654 ofile->f_node, &data, &hole);
655 switch (rc) {
656 case 0:
657 /* Found (data, hole) */
658 break;
659 case ENXIO:
660 /*
661 * No more data after cur_off.
662 * Will encode one last hole.
663 */
664 data = hole = eof;
665 break;
666 default:
667 cmn_err(CE_NOTE, "smb_fsop_next_alloc_range: rc=%d",
668 rc);
669 /* FALLTHROUGH */
670 case ENOSYS: /* FS does not support VOP_IOCTL... */
671 case ENOTTY: /* ... or _FIO_SEEK_DATA, _HOLE */
672 data = cur_off;
673 hole = eof;
674 break;
675 }
676 DTRACE_PROBE2(range2, uint64_t, data, uint64_t, hole);
677 }
678
679 /*
680 * Overwrite the fixed part of the response with the
681 * final numbers of regions etc.
682 * Flags, TotalRegionEntryCount, RegionEntryCount, Reserved
683 */
684 (void) smb_mbc_poke(fsctl->out_mbc, 0, "llll",
685 0, // flags
686 tot_regions,
687 put_regions,
688 0); // reserved
689
690 if (put_regions < tot_regions)
691 return (NT_STATUS_BUFFER_OVERFLOW);
692
693 return (NT_STATUS_SUCCESS);
694 }
695