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 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 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 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 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 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