xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb2_fsctl_sparse.c (revision 1baeef3013bd4a2bff922e272ea7cd6da5a99908)
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