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 }