xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb2_fsctl_copychunk.c (revision 8459c777fc1aaabb2f7dad05de1313aa169417cd)
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 | KM_NORMALPRI);
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