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
27typedef struct chunk {
28	uint64_t src_off;
29	uint64_t dst_off;
30	uint32_t length;
31	uint32_t _reserved;
32} chunk_t;
33
34struct copychunk_resp {
35	uint32_t ChunksWritten;
36	uint32_t ChunkBytesWritten;
37	uint32_t TotalBytesWritten;
38};
39
40typedef 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
48uint32_t smb2_copychunk_max_cnt = 256;
49uint32_t smb2_copychunk_max_seg = (1<<20); /* 1M, == smb2_max_rwsize */
50uint32_t smb2_copychunk_max_total = (1<<24); /* 16M */
51
52static uint32_t smb2_fsctl_copychunk_decode(smb_request_t *, mbuf_chain_t *);
53static uint32_t smb2_fsctl_copychunk_array(smb_request_t *, smb_ofile_t *,
54	struct copychunk_resp *);
55static uint32_t smb2_fsctl_copychunk_aapl(smb_request_t *, smb_ofile_t *,
56	struct copychunk_resp *);
57static uint32_t smb2_fsctl_copychunk_1(smb_request_t *, smb_ofile_t *,
58	struct chunk *);
59static 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 */
73uint32_t
74smb2_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
222out:
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 */
257static uint32_t
258smb2_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
283out:
284	return (status);
285}
286
287/*
288 * Run the actual I/O described by the copychunks array.
289 * (normal, non-apple case)
290 */
291static uint32_t
292smb2_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
337out:
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 */
354int smb2_fsctl_copychunk_aapl_timeout = 10;	/* sec */
355static uint32_t
356smb2_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 */
423static int
424smb2_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	smb_fssd_init(&fs_sd, secinfo, sd_flags);
452	sr->fid_ofile = NULL;
453	error = smb_fsop_sdread(sr, sr->user_cr, src_of->f_node, &fs_sd);
454	if (error == 0) {
455		error = smb_fsop_sdwrite(sr, sr->user_cr, dst_of->f_node,
456		    &fs_sd, 1);
457	}
458	sr->fid_ofile = dst_of;
459	smb_fssd_term(&fs_sd);
460
461	return (error);
462}
463
464/*
465 * Copy one chunk from src_of to sr->fid_ofile,
466 * with offsets and length from chunk *cc
467 */
468static uint32_t
469smb2_fsctl_copychunk_1(smb_request_t *sr, smb_ofile_t *src_ofile,
470    struct chunk *cc)
471{
472	copychunk_args_t *args = sr->arg.other;
473	smb_ofile_t *dst_ofile = sr->fid_ofile;
474	uint32_t status;
475
476	if (cc->length > args->bufsize)
477		return (NT_STATUS_INTERNAL_ERROR);
478
479	/*
480	 * Check for lock conflicting with the read.
481	 */
482	status = smb_lock_range_access(sr, src_ofile->f_node,
483	    cc->src_off, cc->length, B_FALSE);
484	if (status != 0)
485		return (status);
486
487	/*
488	 * Check for lock conflicting with the write.
489	 */
490	status = smb_lock_range_access(sr, dst_ofile->f_node,
491	    cc->dst_off, cc->length, B_TRUE);
492	if (status != 0)
493		return (status);
494
495	/*
496	 * Copy src to dst for cc->length
497	 */
498	status = smb2_sparse_copy(sr, src_ofile, dst_ofile,
499	    cc->src_off, cc->dst_off, &cc->length,
500	    args->buffer, args->bufsize);
501
502	return (status);
503}
504