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_SRV_COPYCHUNK 19 * FSCTL_SRV_COPYCHUNK_WRITE 20 * (and related) 21 */ 22 23 #include <smbsrv/smb2_kproto.h> 24 #include <smbsrv/smb_fsops.h> 25 #include <smb/winioctl.h> 26 27 typedef struct chunk { 28 uint64_t src_off; 29 uint64_t dst_off; 30 uint32_t length; 31 uint32_t _reserved; 32 } chunk_t; 33 34 struct copychunk_resp { 35 uint32_t ChunksWritten; 36 uint32_t ChunkBytesWritten; 37 uint32_t TotalBytesWritten; 38 }; 39 40 typedef struct copychunk_args { 41 smb_attr_t src_attr; 42 void *buffer; 43 size_t bufsize; 44 uint32_t ccnt; 45 chunk_t cvec[1]; /* actually longer */ 46 } copychunk_args_t; 47 48 uint32_t smb2_copychunk_max_cnt = 256; 49 uint32_t smb2_copychunk_max_seg = (1<<20); /* 1M, == smb2_max_rwsize */ 50 uint32_t smb2_copychunk_max_total = (1<<24); /* 16M */ 51 52 static uint32_t smb2_fsctl_copychunk_decode(smb_request_t *, mbuf_chain_t *); 53 static uint32_t smb2_fsctl_copychunk_array(smb_request_t *, smb_ofile_t *, 54 struct copychunk_resp *); 55 static uint32_t smb2_fsctl_copychunk_aapl(smb_request_t *, smb_ofile_t *, 56 struct copychunk_resp *); 57 static uint32_t smb2_fsctl_copychunk_1(smb_request_t *, smb_ofile_t *, 58 struct chunk *); 59 static int smb2_fsctl_copychunk_meta(smb_request_t *, smb_ofile_t *); 60 61 /* 62 * FSCTL_SRV_COPYCHUNK 63 * FSCTL_SRV_COPYCHUNK_WRITE 64 * 65 * Copies from a source file identified by a "resume key" 66 * (previously returned by FSCTL_SRV_REQUEST_RESUME_KEY) 67 * to the file on which the ioctl is issues. 68 * 69 * The fsctl appears to _always_ respond with a data payload 70 * (struct copychunk_resp), even on fatal errors. Note that 71 * smb2_ioctl also has special handling to allow that. 72 */ 73 uint32_t 74 smb2_fsctl_copychunk(smb_request_t *sr, smb_fsctl_t *fsctl) 75 { 76 struct copychunk_resp ccr; 77 smb_ofile_t *dst_of = sr->fid_ofile; 78 smb_ofile_t *src_of = NULL; 79 copychunk_args_t *args = NULL; 80 smb2fid_t smb2fid; 81 uint32_t status = NT_STATUS_INVALID_PARAMETER; 82 uint32_t desired_access; /* for dest */ 83 uint32_t chunk_cnt; 84 int rc; 85 boolean_t aapl_copyfile = B_FALSE; 86 87 bzero(&ccr, sizeof (ccr)); 88 if (fsctl->MaxOutputResp < sizeof (ccr)) { 89 status = NT_STATUS_INVALID_PARAMETER; 90 goto out; 91 } 92 93 /* 94 * Make sure dst_of is open on a regular file, and 95 * granted access is sufficient for this operation. 96 * FSCTL_SRV_COPYCHUNK requires READ+WRITE 97 * FSCTL_SRV_COPYCHUNK_WRITE just WRITE 98 */ 99 if (!smb_node_is_file(dst_of->f_node)) { 100 status = NT_STATUS_ACCESS_DENIED; 101 goto out; 102 } 103 desired_access = FILE_WRITE_DATA; 104 if (fsctl->CtlCode == FSCTL_SRV_COPYCHUNK) 105 desired_access |= FILE_READ_DATA; 106 status = smb_ofile_access(dst_of, dst_of->f_cr, desired_access); 107 if (status != NT_STATUS_SUCCESS) 108 goto out; 109 110 /* 111 * Decode the resume key (src file ID) and length of the 112 * "chunks" array. Note the resume key is 24 bytes of 113 * opaque data from FSCTL_SRV_REQUEST_RESUME_KEY, but 114 * here know it's an smb2fid plus 8 bytes of padding. 115 */ 116 rc = smb_mbc_decodef( 117 fsctl->in_mbc, "qq8.l4.", 118 &smb2fid.persistent, /* q */ 119 &smb2fid.temporal, /* q */ 120 /* pad 8. */ 121 &chunk_cnt); /* l */ 122 /* reserved 4. */ 123 if (rc != 0) { 124 status = NT_STATUS_INVALID_PARAMETER; 125 goto out; 126 } 127 128 /* 129 * Lookup the source ofile using the resume key, 130 * which smb2_fsctl_get_resume_key encoded as an 131 * smb2fid_t. Similar to smb2sr_lookup_fid(), 132 * but different error code. 133 */ 134 src_of = smb_ofile_lookup_by_fid(sr, (uint16_t)smb2fid.temporal); 135 if (src_of == NULL || 136 src_of->f_persistid != smb2fid.persistent) { 137 status = NT_STATUS_OBJECT_NAME_NOT_FOUND; 138 goto out; 139 } 140 141 /* 142 * Make sure src_of is open on a regular file, and 143 * granted access includes READ_DATA 144 */ 145 if (!smb_node_is_file(src_of->f_node)) { 146 status = NT_STATUS_ACCESS_DENIED; 147 goto out; 148 } 149 status = smb_ofile_access(src_of, src_of->f_cr, FILE_READ_DATA); 150 if (status != NT_STATUS_SUCCESS) 151 goto out; 152 153 /* 154 * Before decoding the chunks array, check the size. Note: 155 * When we offer the AAPL extensions, MacOS clients assume 156 * they can use chunk_cnt==0 to mean "copy the whole file". 157 */ 158 if (chunk_cnt == 0) { 159 if ((sr->session->s_flags & SMB_SSN_AAPL_CCEXT) != 0) { 160 aapl_copyfile = B_TRUE; 161 } else { 162 status = NT_STATUS_INVALID_PARAMETER; 163 goto out; 164 } 165 } 166 if (chunk_cnt > smb2_copychunk_max_cnt) { 167 status = NT_STATUS_INVALID_PARAMETER; 168 goto out; 169 } 170 171 /* 172 * Get some memory for the array of chunks and decode it. 173 * Also checks the per-chunk and total size limits. 174 * Note that chunk_cnt may be zero here (MacOS). 175 */ 176 args = smb_srm_zalloc(sr, sizeof (*args) + 177 (chunk_cnt * sizeof (args->cvec))); 178 args->ccnt = chunk_cnt; 179 sr->arg.other = args; 180 if (chunk_cnt > 0) { 181 status = smb2_fsctl_copychunk_decode(sr, fsctl->in_mbc); 182 if (status != 0) 183 goto out; 184 } 185 186 /* 187 * Normally need just the source file size, etc. If doing 188 * Apple server-side copy, we want all the attributes. 189 */ 190 if (aapl_copyfile) 191 args->src_attr.sa_mask = SMB_AT_ALL; 192 else 193 args->src_attr.sa_mask = SMB_AT_STANDARD; 194 status = smb2_ofile_getattr(sr, src_of, &args->src_attr); 195 if (status != 0) 196 goto out; 197 198 /* 199 * Get a buffer used for copying, always 200 * smb2_copychunk_max_seg (1M) 201 * 202 * Rather than sleep for this relatively large allocation, 203 * allow the allocation to fail and return an error. 204 * The client should then fall back to normal copy. 205 */ 206 args->bufsize = smb2_copychunk_max_seg; 207 args->buffer = kmem_alloc(args->bufsize, KM_NOSLEEP_LAZY); 208 if (args->buffer == NULL) { 209 status = NT_STATUS_INSUFF_SERVER_RESOURCES; 210 goto out; 211 } 212 213 /* 214 * Finally, do the I/O 215 */ 216 if (aapl_copyfile) { 217 status = smb2_fsctl_copychunk_aapl(sr, src_of, &ccr); 218 } else { 219 status = smb2_fsctl_copychunk_array(sr, src_of, &ccr); 220 } 221 222 out: 223 if (args != NULL) { 224 if (args->buffer != NULL) { 225 kmem_free(args->buffer, args->bufsize); 226 } 227 } 228 229 if (src_of != NULL) 230 smb_ofile_release(src_of); 231 232 if (status == NT_STATUS_INVALID_PARAMETER) { 233 /* 234 * Tell the client our max chunk cnt, size, etc. 235 */ 236 ccr.ChunksWritten = smb2_copychunk_max_cnt; 237 ccr.ChunkBytesWritten = smb2_copychunk_max_seg; 238 ccr.TotalBytesWritten = smb2_copychunk_max_total; 239 } 240 241 /* Checked MaxOutputResp above, so ignore errors here */ 242 (void) smb_mbc_encodef( 243 fsctl->out_mbc, "lll", 244 ccr.ChunksWritten, 245 ccr.ChunkBytesWritten, 246 ccr.TotalBytesWritten); 247 248 sr->arg.other = NULL; 249 /* smb_srm_fini will free args */ 250 251 return (status); 252 } 253 254 /* 255 * Decode the list of chunks and check each. 256 */ 257 static uint32_t 258 smb2_fsctl_copychunk_decode(smb_request_t *sr, mbuf_chain_t *mbc) 259 { 260 copychunk_args_t *args = sr->arg.other; 261 chunk_t *cc; 262 uint32_t status = NT_STATUS_INVALID_PARAMETER; 263 uint32_t total_len = 0; 264 int i, rc; 265 266 for (i = 0; i < args->ccnt; i++) { 267 cc = &args->cvec[i]; 268 rc = smb_mbc_decodef( 269 mbc, "qqll", 270 &cc->src_off, /* q */ 271 &cc->dst_off, /* q */ 272 &cc->length, /* l */ 273 &cc->_reserved); /* l */ 274 if (rc != 0 || cc->length == 0 || 275 cc->length > smb2_copychunk_max_seg) 276 goto out; 277 total_len += cc->length; 278 } 279 if (total_len > smb2_copychunk_max_total) 280 goto out; 281 status = 0; 282 283 out: 284 return (status); 285 } 286 287 /* 288 * Run the actual I/O described by the copychunks array. 289 * (normal, non-apple case) 290 */ 291 static uint32_t 292 smb2_fsctl_copychunk_array(smb_request_t *sr, smb_ofile_t *src_of, 293 struct copychunk_resp *ccr) 294 { 295 copychunk_args_t *args = sr->arg.other; 296 chunk_t *cc; 297 uint64_t src_size = args->src_attr.sa_vattr.va_size; 298 uint32_t save_len; 299 uint32_t copied; 300 uint32_t status = 0; 301 int i; 302 303 for (i = 0; i < args->ccnt; i++) { 304 cc = &args->cvec[i]; 305 306 /* Chunk must be entirely within file bounds. */ 307 if (cc->src_off > src_size || 308 (cc->src_off + cc->length) < cc->src_off || 309 (cc->src_off + cc->length) > src_size) { 310 status = NT_STATUS_INVALID_VIEW_SIZE; 311 goto out; 312 } 313 314 save_len = cc->length; 315 status = smb2_fsctl_copychunk_1(sr, src_of, cc); 316 if (status != 0) { 317 /* no part of this chunk written */ 318 break; 319 } 320 /* 321 * All or part of the chunk written. 322 * cc->length is now the resid count. 323 */ 324 copied = save_len - cc->length; 325 ccr->TotalBytesWritten += copied; 326 if (cc->length != 0) { 327 /* Did not write the whole chunk */ 328 ccr->ChunkBytesWritten = copied; 329 break; 330 } 331 /* Whole chunk moved. */ 332 ccr->ChunksWritten++; 333 } 334 if (ccr->ChunksWritten > 0) 335 status = NT_STATUS_SUCCESS; 336 337 out: 338 return (status); 339 } 340 341 /* 342 * Helper for smb2_fsctl_copychunk, where MacOS uses chunk_cnt==0 343 * to mean "copy the whole file". This interface does not have any 344 * way to report a partial copy (client ignores copychunk_resp) so 345 * if that happens we just report an error. 346 * 347 * This extension makes no provision for the server to impose any 348 * bound on the amount of data moved by one SMB copychunk request. 349 * We could impose a total size, but it's hard to know what size 350 * would be an appropriate limit because performance of various 351 * storage subsystems can vary quite a bit. The best we can do is 352 * limit the time we spend in this copy, and allow cancellation. 353 */ 354 int smb2_fsctl_copychunk_aapl_timeout = 10; /* sec */ 355 static uint32_t 356 smb2_fsctl_copychunk_aapl(smb_request_t *sr, smb_ofile_t *src_of, 357 struct copychunk_resp *ccr) 358 { 359 copychunk_args_t *args = sr->arg.other; 360 chunk_t *cc = args->cvec; /* always at least one element */ 361 uint64_t src_size = args->src_attr.sa_vattr.va_size; 362 uint64_t off; 363 uint32_t xfer; 364 uint32_t status = 0; 365 hrtime_t end_time = sr->sr_time_active + 366 (smb2_fsctl_copychunk_aapl_timeout * NANOSEC); 367 368 off = 0; 369 while (off < src_size) { 370 /* 371 * Check that (a) the request has not been cancelled, 372 * and (b) we've not run past the timeout. 373 */ 374 if (sr->sr_state != SMB_REQ_STATE_ACTIVE) 375 return (NT_STATUS_CANCELLED); 376 if (gethrtime() > end_time) 377 return (NT_STATUS_IO_TIMEOUT); 378 379 xfer = smb2_copychunk_max_seg; 380 if (off + xfer > src_size) 381 xfer = (uint32_t)(src_size - off); 382 cc->src_off = off; 383 cc->dst_off = off; 384 cc->length = xfer; 385 status = smb2_fsctl_copychunk_1(sr, src_of, cc); 386 if (status != 0) 387 break; 388 if (cc->length != 0) { 389 status = NT_STATUS_PARTIAL_COPY; 390 break; 391 } 392 /* 393 * Whole chunk moved. It appears that MacOS clients 394 * ignore the response here, but let's put something 395 * meaningful in it anyway, so one can see how far 396 * the copy went by looking at a network trace. 397 */ 398 ccr->TotalBytesWritten += xfer; 399 ccr->ChunksWritten++; 400 off += xfer; 401 } 402 403 /* 404 * MacOS servers also copy meta-data from the old to new file. 405 * We need to do this because Finder does not set the meta-data 406 * when copying a file with this interface. If we fail to copy 407 * the meta-data, just log. We'd rather not fail the entire 408 * copy job if this fails. 409 */ 410 if (status == 0) { 411 int rc = smb2_fsctl_copychunk_meta(sr, src_of); 412 if (rc != 0) { 413 cmn_err(CE_NOTE, "smb2 copychunk meta, rc=%d", rc); 414 } 415 } 416 417 return (status); 418 } 419 420 /* 421 * Helper for Apple copychunk, to copy meta-data 422 */ 423 static int 424 smb2_fsctl_copychunk_meta(smb_request_t *sr, smb_ofile_t *src_of) 425 { 426 smb_fssd_t fs_sd; 427 copychunk_args_t *args = sr->arg.other; 428 smb_ofile_t *dst_of = sr->fid_ofile; 429 uint32_t sd_flags = 0; 430 uint32_t secinfo = SMB_DACL_SECINFO; 431 int error; 432 433 /* 434 * Copy attributes. We obtained SMB_AT_ALL above. 435 * Now correct the mask for what's settable. 436 */ 437 args->src_attr.sa_mask = SMB_AT_MODE | SMB_AT_SIZE | 438 SMB_AT_ATIME | SMB_AT_MTIME | SMB_AT_CTIME | 439 SMB_AT_DOSATTR | SMB_AT_ALLOCSZ; 440 error = smb_node_setattr(sr, dst_of->f_node, sr->user_cr, 441 dst_of, &args->src_attr); 442 if (error != 0) 443 return (error); 444 445 /* 446 * Copy the ACL. Unfortunately, the ofiles used by the Mac 447 * here don't generally have WRITE_DAC access (sigh) so we 448 * have to bypass ofile access checks for this operation. 449 * The file-system level still does its access checking. 450 * 451 * TODO: this should really copy the SACL, too. 452 */ 453 smb_fssd_init(&fs_sd, secinfo, sd_flags); 454 sr->fid_ofile = NULL; 455 error = smb_fsop_sdread(sr, sr->user_cr, src_of->f_node, &fs_sd); 456 if (error == 0) { 457 error = smb_fsop_sdwrite(sr, sr->user_cr, dst_of->f_node, 458 &fs_sd, 1); 459 } 460 sr->fid_ofile = dst_of; 461 smb_fssd_term(&fs_sd); 462 463 return (error); 464 } 465 466 /* 467 * Copy one chunk from src_of to sr->fid_ofile, 468 * with offsets and length from chunk *cc 469 */ 470 static uint32_t 471 smb2_fsctl_copychunk_1(smb_request_t *sr, smb_ofile_t *src_ofile, 472 struct chunk *cc) 473 { 474 copychunk_args_t *args = sr->arg.other; 475 smb_ofile_t *dst_ofile = sr->fid_ofile; 476 uint32_t status; 477 478 if (cc->length > args->bufsize) 479 return (NT_STATUS_INTERNAL_ERROR); 480 481 /* 482 * Check for lock conflicting with the read. 483 */ 484 status = smb_lock_range_access(sr, src_ofile->f_node, 485 cc->src_off, cc->length, B_FALSE); 486 if (status != 0) 487 return (status); 488 489 /* 490 * Check for lock conflicting with the write. 491 */ 492 status = smb_lock_range_access(sr, dst_ofile->f_node, 493 cc->dst_off, cc->length, B_TRUE); 494 if (status != 0) 495 return (status); 496 497 /* 498 * Copy src to dst for cc->length 499 */ 500 status = smb2_sparse_copy(sr, src_ofile, dst_ofile, 501 cc->src_off, cc->dst_off, &cc->length, 502 args->buffer, args->bufsize); 503 504 return (status); 505 } 506