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 2018 Nexenta Systems, 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 data = src_off; 310 rc = smb_fsop_next_alloc_range(src_ofile->f_cr, 311 src_ofile->f_node, &data, &hole); 312 switch (rc) { 313 case 0: 314 /* Found data, hole */ 315 break; 316 case ENXIO: 317 /* No data after here (will skip below). */ 318 data = hole = (src_off + *residp); 319 break; 320 default: 321 cmn_err(CE_NOTE, 322 "smb_fsop_next_alloc_range: rc=%d", rc); 323 /* FALLTHROUGH */ 324 case ENOSYS: /* FS does not support VOP_IOCTL... */ 325 case ENOTTY: /* ... or _FIO_SEEK_DATA, _HOLE */ 326 data = src_off; 327 hole = src_off + *residp; 328 break; 329 } 330 331 /* 332 * Don't try to go past (src_off + *residp) 333 */ 334 if (hole > (src_off + *residp)) 335 hole = src_off + *residp; 336 if (data > hole) 337 data = hole; 338 339 /* 340 * If there's a gap (src_off .. data) 341 * skip in src_ofile, zero in dst_ofile 342 */ 343 if (src_off < data) { 344 off64_t skip = data - src_off; 345 rc = smb_fsop_freesp(sr, dst_ofile->f_cr, 346 dst_ofile, dst_off, skip); 347 if (rc == 0) { 348 src_off += skip; 349 dst_off += skip; 350 *residp -= (uint32_t)skip; 351 } else { 352 /* Fall back to regular copy */ 353 data = src_off; 354 } 355 } 356 ASSERT(src_off == data); 357 358 /* 359 * Copy this segment: src_off .. hole 360 */ 361 while (src_off < hole) { 362 ssize_t tsize = hole - src_off; 363 if (tsize > bufsize) 364 tsize = bufsize; 365 366 /* 367 * Read src_ofile into buffer 368 */ 369 iov.iov_base = buffer; 370 iov.iov_len = tsize; 371 bzero(&uio, sizeof (uio)); 372 uio.uio_iov = &iov; 373 uio.uio_iovcnt = 1; 374 uio.uio_resid = tsize; 375 uio.uio_loffset = src_off; 376 uio.uio_segflg = UIO_SYSSPACE; 377 uio.uio_extflg = UIO_COPY_DEFAULT; 378 379 rc = smb_fsop_read(sr, src_ofile->f_cr, 380 src_ofile->f_node, src_ofile, &uio, 0); 381 if (rc != 0) { 382 status = smb_errno2status(rc); 383 return (status); 384 } 385 /* Note: Could be partial read. */ 386 tsize -= uio.uio_resid; 387 ASSERT(tsize > 0); 388 389 /* 390 * Write buffer to dst_ofile 391 */ 392 iov.iov_base = buffer; 393 iov.iov_len = tsize; 394 bzero(&uio, sizeof (uio)); 395 uio.uio_iov = &iov; 396 uio.uio_iovcnt = 1; 397 uio.uio_resid = tsize; 398 uio.uio_loffset = dst_off; 399 uio.uio_segflg = UIO_SYSSPACE; 400 uio.uio_extflg = UIO_COPY_DEFAULT; 401 402 rc = smb_fsop_write(sr, dst_ofile->f_cr, 403 dst_ofile->f_node, dst_ofile, &uio, &xfer, 0); 404 if (rc != 0) { 405 status = smb_errno2status(rc); 406 return (status); 407 } 408 ASSERT(xfer <= tsize); 409 410 src_off += xfer; 411 dst_off += xfer; 412 *residp -= xfer; 413 } 414 ASSERT(src_off == hole); 415 } 416 417 return (status); 418 } 419 420 /* 421 * Not sure what header this might go in. 422 */ 423 #define FILE_REGION_USAGE_VALID_CACHED_DATA 1 424 #define FILE_REGION_USAGE_VALID_NONCACHED_DATA 2 425 #define FILE_REGION_USAGE_VALID__MASK 3 426 427 typedef struct _FILE_REGION_INFO { 428 uint64_t off; 429 uint64_t len; 430 uint32_t usage; 431 uint32_t reserved; 432 } FILE_REGION_INFO; 433 434 435 /* 436 * FSCTL_QUERY_FILE_REGIONS 437 * 438 * [MS-FSCC] 2.3.39 FSCTL_QUERY_FILE_REGIONS Request 439 * [MS-FSCC] 2.3.40.1 FILE_REGION_INFO 440 * 441 * Looks like Hyper-V uses this to query the "valid data length", 442 * which to us is the beginning offset of the last "hole". 443 * Similar logic as smb2_sparse_copy() 444 */ 445 uint32_t 446 smb2_fsctl_query_file_regions(smb_request_t *sr, smb_fsctl_t *fsctl) 447 { 448 smb_attr_t attr; 449 cred_t *kcr; 450 smb_ofile_t *ofile = sr->fid_ofile; 451 FILE_REGION_INFO arg; 452 off64_t cur_off, end_off, eof; 453 off64_t data, hole; 454 uint32_t tot_regions, put_regions; 455 uint32_t status; 456 int rc; 457 458 if (fsctl->InputCount == 0) { 459 arg.off = 0; 460 arg.len = INT64_MAX; 461 arg.usage = FILE_REGION_USAGE_VALID_CACHED_DATA; 462 arg.reserved = 0; 463 } else { 464 /* min size check: reserved is optional */ 465 rc = smb_mbc_decodef(fsctl->in_mbc, "qql", 466 &arg.off, &arg.len, &arg.usage); 467 if (rc != 0) 468 return (NT_STATUS_BUFFER_TOO_SMALL); 469 470 /* 471 * The given offset and length are int64_t (signed). 472 */ 473 if (arg.off > INT64_MAX || arg.len > INT64_MAX) 474 return (NT_STATUS_INVALID_PARAMETER); 475 if ((arg.off + arg.len) > INT64_MAX) 476 return (NT_STATUS_INVALID_PARAMETER); 477 if ((arg.usage & FILE_REGION_USAGE_VALID__MASK) == 0) 478 return (NT_STATUS_INVALID_PARAMETER); 479 arg.reserved = 0; 480 } 481 482 if (fsctl->MaxOutputResp < (16 + sizeof (FILE_REGION_INFO))) 483 return (NT_STATUS_BUFFER_TOO_SMALL); 484 485 if (!smb_node_is_file(ofile->f_node)) 486 return (NT_STATUS_INVALID_PARAMETER); 487 488 /* 489 * This operation is effectively a read 490 */ 491 status = smb_ofile_access(ofile, ofile->f_cr, FILE_READ_DATA); 492 if (status != NT_STATUS_SUCCESS) 493 return (status); 494 495 /* 496 * Need the file size and dosattr 497 */ 498 bzero(&attr, sizeof (attr)); 499 attr.sa_mask = SMB_AT_SIZE | SMB_AT_DOSATTR; 500 kcr = zone_kcred(); 501 status = smb_node_getattr(sr, ofile->f_node, kcr, ofile, &attr); 502 if (status != NT_STATUS_SUCCESS) 503 return (status); 504 505 cur_off = arg.off; 506 end_off = arg.off + arg.len; 507 eof = attr.sa_vattr.va_size; 508 509 /* 510 * If (InputRegion.FileOffset > Eof) OR 511 * ((InputRegion.FileOffset == Eof) AND (Eof > 0)), 512 * the operation MUST return STATUS_SUCCESS, with 513 * BytesReturned set to 0 (empty data response) 514 */ 515 if ((arg.off > eof) || (arg.off == eof && eof > 0)) 516 return (NT_STATUS_SUCCESS); 517 if (end_off > eof) 518 end_off = eof; 519 520 /* 521 * We're going to return at least one region. Put place-holder 522 * data for the fixed part of the response. Will overwrite this 523 * later, when we know how many regions there are and how many 524 * of those fit in the allowed response buffer space. These are: 525 * Flags, TotalRegionEntryCount, RegionEntryCount, Reserved 526 */ 527 rc = smb_mbc_encodef(fsctl->out_mbc, "llll", 0, 0, 0, 0); 528 if (rc != 0) 529 return (NT_STATUS_BUFFER_TOO_SMALL); 530 tot_regions = put_regions = 0; 531 532 /* 533 * Get the first pair of (data, hole) offsets at or after 534 * the current offset (cur_off). 535 */ 536 data = hole = cur_off; 537 rc = smb_fsop_next_alloc_range(ofile->f_cr, 538 ofile->f_node, &data, &hole); 539 switch (rc) { 540 case 0: 541 /* Found (data, hole) */ 542 break; 543 case ENXIO: 544 /* No more data after cur_off. */ 545 break; 546 default: 547 cmn_err(CE_NOTE, "smb_fsop_next_alloc_range: rc=%d", rc); 548 /* FALLTHROUGH */ 549 case ENOSYS: /* FS does not support VOP_IOCTL... */ 550 case ENOTTY: /* ... or _FIO_SEEK_DATA, _HOLE */ 551 data = cur_off; 552 hole = eof; 553 break; 554 } 555 DTRACE_PROBE2(range0, uint64_t, data, uint64_t, hole); 556 557 /* 558 * Only sparse files should present un-allocated regions. 559 * If non-sparse, MS-FSA says to just return one region. 560 * There still can be a "hole" but only one, starting at 561 * "valid data length" (VDL) and ending at end of file. 562 * To determine VDL, find the last (data,hole) pair, then 563 * VDL is the last "hole" offset. Note that the above 564 * smb_fsop_next_alloc_range may have set data somewhere 565 * above cur_off, so we we have to reset that here. 566 */ 567 if ((attr.sa_dosattr & FILE_ATTRIBUTE_SPARSE_FILE) == 0) { 568 /* 569 * This works, but it's rather inefficient, and 570 * usually just finds VDL==EOF. Should look into 571 * whether there's a faster way to find the VDL. 572 */ 573 #if 0 574 off64_t next_data, next_hole; 575 data = cur_off; 576 do { 577 next_data = next_hole = hole; 578 rc = smb_fsop_next_alloc_range(ofile->f_cr, 579 ofile->f_node, &next_data, &next_hole); 580 if (rc == 0) { 581 hole = next_hole; 582 } 583 } while (rc == 0); 584 #else 585 /* Assume no "holes" anywhere (VDL==EOF) */ 586 data = cur_off; 587 hole = eof; 588 #endif 589 DTRACE_PROBE2(range1, uint64_t, data, uint64_t, hole); 590 } 591 592 /* 593 * Loop terminates in the middle, continuing 594 * while (cur_off < end_off) 595 */ 596 for (;;) { 597 /* 598 * We have a data region that covers (data, hole). 599 * It could be partially or entirely beyond the range 600 * the caller asked about (if so trim it). 601 */ 602 if (hole > end_off) 603 hole = end_off; 604 if (data > hole) 605 data = hole; 606 607 /* 608 * If cur_off < data encode a "hole" region 609 * (cur_off,data) and advance cur_off. 610 */ 611 if (cur_off < data) { 612 rc = smb_mbc_encodef(fsctl->out_mbc, "qqll", 613 cur_off, 614 (data - cur_off), 615 0, // usage (hole) 616 0); // reserved 617 cur_off = data; 618 if (rc == 0) 619 put_regions++; 620 tot_regions++; 621 } 622 623 /* 624 * If cur_off < hole encode a "data" region 625 * (cur_off,hole) and advance cur_off. 626 */ 627 if (cur_off < hole) { 628 rc = smb_mbc_encodef(fsctl->out_mbc, "qqll", 629 cur_off, 630 (hole - cur_off), 631 FILE_REGION_USAGE_VALID_CACHED_DATA, 632 0); // reserved 633 cur_off = hole; 634 if (rc == 0) 635 put_regions++; 636 tot_regions++; 637 } 638 639 /* 640 * Normal loop termination 641 */ 642 if (cur_off >= end_off) 643 break; 644 645 /* 646 * Get the next region (data, hole) starting on or after 647 * the current offset (cur_off). 648 */ 649 data = hole = cur_off; 650 rc = smb_fsop_next_alloc_range(ofile->f_cr, 651 ofile->f_node, &data, &hole); 652 switch (rc) { 653 case 0: 654 /* Found (data, hole) */ 655 break; 656 case ENXIO: 657 /* 658 * No more data after cur_off. 659 * Will encode one last hole. 660 */ 661 data = hole = eof; 662 break; 663 default: 664 cmn_err(CE_NOTE, "smb_fsop_next_alloc_range: rc=%d", 665 rc); 666 /* FALLTHROUGH */ 667 case ENOSYS: /* FS does not support VOP_IOCTL... */ 668 case ENOTTY: /* ... or _FIO_SEEK_DATA, _HOLE */ 669 data = cur_off; 670 hole = eof; 671 break; 672 } 673 DTRACE_PROBE2(range2, uint64_t, data, uint64_t, hole); 674 } 675 676 /* 677 * Overwrite the fixed part of the response with the 678 * final numbers of regions etc. 679 * Flags, TotalRegionEntryCount, RegionEntryCount, Reserved 680 */ 681 (void) smb_mbc_poke(fsctl->out_mbc, 0, "llll", 682 0, // flags 683 tot_regions, 684 put_regions, 685 0); // reserved 686 687 if (put_regions < tot_regions) 688 return (NT_STATUS_BUFFER_OVERFLOW); 689 690 return (NT_STATUS_SUCCESS); 691 } 692