xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_write.c (revision dd72704bd9e794056c558153663c739e2012d721)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Copyright 2020 Tintri by DDN, Inc. All rights reserved.
25  * Copyright 2022 RackTop Systems, Inc.
26  */
27 
28 #include <sys/sdt.h>
29 #include <smbsrv/smb_kproto.h>
30 #include <smbsrv/smb_fsops.h>
31 #include <smbsrv/netbios.h>
32 
33 
34 static int smb_write_truncate(smb_request_t *, smb_rw_param_t *);
35 
36 
37 /*
38  * Write count bytes at the specified offset in a file.  The offset is
39  * limited to 32-bits.  If the count is zero, the file is truncated to
40  * the length specified by the offset.
41  *
42  * The response count indicates the actual number of bytes written, which
43  * will equal the requested count on success.  If request and response
44  * counts differ but there is no error, the client will assume that the
45  * server encountered a resource issue.
46  */
47 smb_sdrc_t
48 smb_pre_write(smb_request_t *sr)
49 {
50 	smb_rw_param_t *param;
51 	uint32_t off;
52 	uint16_t count;
53 	int rc;
54 
55 	param = kmem_zalloc(sizeof (smb_rw_param_t), KM_SLEEP);
56 	sr->arg.rw = param;
57 	param->rw_magic = SMB_RW_MAGIC;
58 
59 	rc = smbsr_decode_vwv(sr, "wwl", &sr->smb_fid, &count, &off);
60 
61 	param->rw_count = (uint32_t)count;
62 	param->rw_offset = (uint64_t)off;
63 	param->rw_vdb.vdb_uio.uio_loffset = (offset_t)param->rw_offset;
64 
65 	DTRACE_SMB_START(op__Write, smb_request_t *, sr); /* arg.rw */
66 
67 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
68 }
69 
70 void
71 smb_post_write(smb_request_t *sr)
72 {
73 	DTRACE_SMB_DONE(op__Write, smb_request_t *, sr); /* arg.rw */
74 
75 	kmem_free(sr->arg.rw, sizeof (smb_rw_param_t));
76 }
77 
78 smb_sdrc_t
79 smb_com_write(smb_request_t *sr)
80 {
81 	smb_rw_param_t *param = sr->arg.rw;
82 	int rc;
83 
84 	smbsr_lookup_file(sr);
85 	if (sr->fid_ofile == NULL) {
86 		smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
87 		return (SDRC_ERROR);
88 	}
89 
90 	sr->user_cr = smb_ofile_getcred(sr->fid_ofile);
91 
92 	if (param->rw_count == 0) {
93 		rc = smb_write_truncate(sr, param);
94 	} else {
95 		rc = smbsr_decode_data(sr, "D", &param->rw_vdb);
96 
97 		if ((rc != 0) || (param->rw_vdb.vdb_len != param->rw_count)) {
98 			smbsr_error(sr, NT_STATUS_INVALID_PARAMETER,
99 			    ERRDOS, ERROR_INVALID_PARAMETER);
100 			return (SDRC_ERROR);
101 		}
102 
103 		param->rw_vdb.vdb_uio.uio_loffset = (offset_t)param->rw_offset;
104 
105 		rc = smb_common_write(sr, param);
106 	}
107 
108 	if (rc != 0) {
109 		if (sr->smb_error.status != NT_STATUS_FILE_LOCK_CONFLICT)
110 			smbsr_errno(sr, rc);
111 		return (SDRC_ERROR);
112 	}
113 
114 	rc = smbsr_encode_result(sr, 1, 0, "bww", 1,
115 	    (uint16_t)param->rw_count, 0);
116 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
117 }
118 
119 /*
120  * Write count bytes to a file and then close the file.  This function
121  * can only be used to write to 32-bit offsets and the client must set
122  * WordCount (6 or 12) correctly in order to locate the data to be
123  * written.  If an error occurs on the write, the file should still be
124  * closed.  If Count is 0, the file is truncated (or extended) to offset.
125  *
126  * If the last_write time is non-zero, last_write should be used to set
127  * the mtime.  Otherwise the file system stamps the mtime.  Failure to
128  * set mtime should not result in an error response.
129  */
130 smb_sdrc_t
131 smb_pre_write_and_close(smb_request_t *sr)
132 {
133 	smb_rw_param_t *param;
134 	uint32_t off;
135 	uint16_t count;
136 	int rc;
137 
138 	param = kmem_zalloc(sizeof (smb_rw_param_t), KM_SLEEP);
139 	sr->arg.rw = param;
140 	param->rw_magic = SMB_RW_MAGIC;
141 
142 	if (sr->smb_wct == 12) {
143 		rc = smbsr_decode_vwv(sr, "wwll12.", &sr->smb_fid,
144 		    &count, &off, &param->rw_last_write);
145 	} else {
146 		rc = smbsr_decode_vwv(sr, "wwll", &sr->smb_fid,
147 		    &count, &off, &param->rw_last_write);
148 	}
149 
150 	param->rw_count = (uint32_t)count;
151 	param->rw_offset = (uint64_t)off;
152 
153 	DTRACE_SMB_START(op__WriteAndClose, smb_request_t *, sr); /* arg.rw */
154 
155 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
156 }
157 
158 void
159 smb_post_write_and_close(smb_request_t *sr)
160 {
161 	DTRACE_SMB_DONE(op__WriteAndClose, smb_request_t *, sr); /* arg.rw */
162 
163 	kmem_free(sr->arg.rw, sizeof (smb_rw_param_t));
164 }
165 
166 smb_sdrc_t
167 smb_com_write_and_close(smb_request_t *sr)
168 {
169 	smb_rw_param_t *param = sr->arg.rw;
170 	uint16_t count;
171 	int rc = 0;
172 
173 	smbsr_lookup_file(sr);
174 	if (sr->fid_ofile == NULL) {
175 		smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
176 		return (SDRC_ERROR);
177 	}
178 
179 	sr->user_cr = smb_ofile_getcred(sr->fid_ofile);
180 
181 	if (param->rw_count == 0) {
182 		rc = smb_write_truncate(sr, param);
183 	} else {
184 		/*
185 		 * There may be a bug here: should this be "3.#B"?
186 		 */
187 		rc = smbsr_decode_data(sr, ".#B", param->rw_count,
188 		    &param->rw_vdb);
189 
190 		if ((rc != 0) || (param->rw_vdb.vdb_len != param->rw_count)) {
191 			smbsr_error(sr, NT_STATUS_INVALID_PARAMETER,
192 			    ERRDOS, ERROR_INVALID_PARAMETER);
193 			return (SDRC_ERROR);
194 		}
195 
196 		param->rw_vdb.vdb_uio.uio_loffset = (offset_t)param->rw_offset;
197 
198 		rc = smb_common_write(sr, param);
199 	}
200 
201 	if (rc != 0) {
202 		if (sr->smb_error.status != NT_STATUS_FILE_LOCK_CONFLICT)
203 			smbsr_errno(sr, rc);
204 		return (SDRC_ERROR);
205 	}
206 
207 	smb_ofile_close(sr->fid_ofile, param->rw_last_write);
208 
209 	count = (uint16_t)param->rw_count;
210 	rc = smbsr_encode_result(sr, 1, 0, "bww", 1, count, 0);
211 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
212 }
213 
214 /*
215  * Write count bytes to a file at the specified offset and then unlock
216  * them.  Write behind is safe because the client should have the range
217  * locked and this request is allowed to extend the file - note that
218  * offset is limited to 32-bits.
219  *
220  * Spec advice: it is an error for count to be zero.  For compatibility,
221  * we take no action and return success.
222  *
223  * The SmbLockAndRead/SmbWriteAndUnlock sub-dialect is only valid on disk
224  * files.  Reject any attempt to use it on other shares.
225  *
226  * The response count indicates the actual number of bytes written, which
227  * will equal the requested count on success.  If request and response
228  * counts differ but there is no error, the client will assume that the
229  * server encountered a resource issue.
230  */
231 smb_sdrc_t
232 smb_pre_write_and_unlock(smb_request_t *sr)
233 {
234 	smb_rw_param_t *param;
235 	uint32_t off;
236 	uint16_t count;
237 	uint16_t remcnt;
238 	int rc;
239 
240 	param = kmem_zalloc(sizeof (smb_rw_param_t), KM_SLEEP);
241 	sr->arg.rw = param;
242 	param->rw_magic = SMB_RW_MAGIC;
243 
244 	rc = smbsr_decode_vwv(sr, "wwlw", &sr->smb_fid, &count, &off, &remcnt);
245 
246 	param->rw_count = (uint32_t)count;
247 	param->rw_offset = (uint64_t)off;
248 
249 	DTRACE_SMB_START(op__WriteAndUnlock, smb_request_t *, sr); /* arg.rw */
250 
251 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
252 }
253 
254 void
255 smb_post_write_and_unlock(smb_request_t *sr)
256 {
257 	DTRACE_SMB_DONE(op__WriteAndUnlock, smb_request_t *, sr); /* arg.rw */
258 
259 	kmem_free(sr->arg.rw, sizeof (smb_rw_param_t));
260 }
261 
262 smb_sdrc_t
263 smb_com_write_and_unlock(smb_request_t *sr)
264 {
265 	smb_rw_param_t *param = sr->arg.rw;
266 	uint32_t lk_pid;
267 	uint32_t status;
268 	int rc = 0;
269 
270 	if (STYPE_ISDSK(sr->tid_tree->t_res_type) == 0) {
271 		smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRDOS, ERRnoaccess);
272 		return (SDRC_ERROR);
273 	}
274 
275 	smbsr_lookup_file(sr);
276 	if (sr->fid_ofile == NULL) {
277 		smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
278 		return (SDRC_ERROR);
279 	}
280 
281 	sr->user_cr = smb_ofile_getcred(sr->fid_ofile);
282 
283 	if (param->rw_count == 0) {
284 		rc = smbsr_encode_result(sr, 1, 0, "bww", 1, 0, 0);
285 		return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
286 	}
287 
288 
289 	rc = smbsr_decode_data(sr, "D", &param->rw_vdb);
290 
291 	if ((rc != 0) || (param->rw_count != param->rw_vdb.vdb_len)) {
292 		smbsr_error(sr, NT_STATUS_INVALID_PARAMETER,
293 		    ERRDOS, ERROR_INVALID_PARAMETER);
294 		return (SDRC_ERROR);
295 	}
296 
297 	param->rw_vdb.vdb_uio.uio_loffset = (offset_t)param->rw_offset;
298 
299 	if ((rc = smb_common_write(sr, param)) != 0) {
300 		if (sr->smb_error.status != NT_STATUS_FILE_LOCK_CONFLICT)
301 			smbsr_errno(sr, rc);
302 		return (SDRC_ERROR);
303 	}
304 
305 
306 	/* Note: SMB1 locking uses 16-bit PIDs. */
307 	lk_pid = sr->smb_pid & 0xFFFF;
308 
309 	status = smb_unlock_range(sr, param->rw_offset,
310 	    (uint64_t)param->rw_count, lk_pid);
311 	if (status != NT_STATUS_SUCCESS) {
312 		smbsr_error(sr, NT_STATUS_RANGE_NOT_LOCKED,
313 		    ERRDOS, ERROR_NOT_LOCKED);
314 		return (SDRC_ERROR);
315 	}
316 
317 	rc = smbsr_encode_result(sr, 1, 0, "bww", 1,
318 	    (uint16_t)param->rw_count, 0);
319 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
320 }
321 
322 /*
323  * The SMB_COM_WRITE_RAW protocol was a negotiated option introduced in
324  * SMB Core Plus to maximize performance when writing a large block
325  * of data to a server.  It's obsolete and no longer supported.
326  *
327  * We keep a handler for it so the dtrace provider can see if
328  * the client tried to use this command.
329  */
330 smb_sdrc_t
331 smb_pre_write_raw(smb_request_t *sr)
332 {
333 	DTRACE_SMB_START(op__WriteRaw, smb_request_t *, sr);
334 	return (SDRC_SUCCESS);
335 }
336 
337 void
338 smb_post_write_raw(smb_request_t *sr)
339 {
340 	DTRACE_SMB_DONE(op__WriteRaw, smb_request_t *, sr);
341 }
342 
343 smb_sdrc_t
344 smb_com_write_raw(struct smb_request *sr)
345 {
346 	smbsr_error(sr, NT_STATUS_NOT_SUPPORTED, ERRDOS,
347 	    ERROR_NOT_SUPPORTED);
348 	return (SDRC_ERROR);
349 }
350 
351 /*
352  * Write bytes to a file (SMB Core).  This request was extended in
353  * LM 0.12 to support 64-bit offsets, indicated by sending a wct of
354  * 14, instead of 12, and including additional offset information.
355  *
356  * A ByteCount of 0 does not truncate the file - use SMB_COM_WRITE
357  * to truncate a file.  A zero length merely transfers zero bytes.
358  *
359  * If bit 0 of WriteMode is set, Fid must refer to a disk file and
360  * the data must be on stable storage before responding.
361  *
362  * MS-SMB 3.3.5.8 update to LM 0.12 4.2.5:
363  * If CAP_LARGE_WRITEX is set, the byte count may be larger than the
364  * negotiated buffer size and the server is expected to write the
365  * number of bytes specified.
366  */
367 smb_sdrc_t
368 smb_pre_write_andx(smb_request_t *sr)
369 {
370 	smb_rw_param_t *param;
371 	uint32_t off_low;
372 	uint32_t off_high;
373 	uint16_t datalen_low;
374 	uint16_t datalen_high;
375 	uint16_t remcnt;
376 	int rc;
377 
378 	param = kmem_zalloc(sizeof (smb_rw_param_t), KM_SLEEP);
379 	sr->arg.rw = param;
380 	param->rw_magic = SMB_RW_MAGIC;
381 
382 	if (sr->smb_wct == 14) {
383 		rc = smbsr_decode_vwv(sr, "4.wl4.wwwwwl", &sr->smb_fid,
384 		    &off_low, &param->rw_mode, &remcnt, &datalen_high,
385 		    &datalen_low, &param->rw_dsoff, &off_high);
386 
387 		if (param->rw_dsoff >= 63)
388 			param->rw_dsoff -= 63;
389 		param->rw_offset = ((uint64_t)off_high << 32) | off_low;
390 	} else if (sr->smb_wct == 12) {
391 		rc = smbsr_decode_vwv(sr, "4.wl4.wwwww", &sr->smb_fid,
392 		    &off_low, &param->rw_mode, &remcnt, &datalen_high,
393 		    &datalen_low, &param->rw_dsoff);
394 
395 		if (param->rw_dsoff >= 59)
396 			param->rw_dsoff -= 59;
397 		param->rw_offset = (uint64_t)off_low;
398 		/* off_high not present */
399 	} else {
400 		rc = -1;
401 	}
402 
403 	param->rw_count = (uint32_t)datalen_low;
404 
405 	/*
406 	 * Work-around a Win7 bug, where it fails to set the
407 	 * CAP_LARGE_WRITEX flag during session setup.  Assume
408 	 * a large write if the data remaining is >= 64k.
409 	 */
410 	if ((sr->session->capabilities & CAP_LARGE_WRITEX) != 0 ||
411 	    (sr->smb_data.max_bytes > (sr->smb_data.chain_offset + 0xFFFF)))
412 		param->rw_count |= ((uint32_t)datalen_high << 16);
413 
414 	DTRACE_SMB_START(op__WriteX, smb_request_t *, sr); /* arg.rw */
415 
416 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
417 }
418 
419 void
420 smb_post_write_andx(smb_request_t *sr)
421 {
422 	DTRACE_SMB_DONE(op__WriteX, smb_request_t *, sr); /* arg.rw */
423 
424 	kmem_free(sr->arg.rw, sizeof (smb_rw_param_t));
425 }
426 
427 smb_sdrc_t
428 smb_com_write_andx(smb_request_t *sr)
429 {
430 	smb_rw_param_t *param = sr->arg.rw;
431 	uint16_t count_high;
432 	uint16_t count_low;
433 	int rc;
434 
435 	ASSERT(param);
436 	ASSERT(param->rw_magic == SMB_RW_MAGIC);
437 
438 	smbsr_lookup_file(sr);
439 	if (sr->fid_ofile == NULL) {
440 		smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
441 		return (SDRC_ERROR);
442 	}
443 
444 	sr->user_cr = smb_ofile_getcred(sr->fid_ofile);
445 
446 	if (SMB_WRMODE_IS_STABLE(param->rw_mode) &&
447 	    STYPE_ISIPC(sr->tid_tree->t_res_type)) {
448 		smbsr_error(sr, 0, ERRSRV, ERRaccess);
449 		return (SDRC_ERROR);
450 	}
451 
452 	rc = smbsr_decode_data(sr, "#.#B", param->rw_dsoff, param->rw_count,
453 	    &param->rw_vdb);
454 
455 	if ((rc != 0) || (param->rw_vdb.vdb_len != param->rw_count)) {
456 		smbsr_error(sr, NT_STATUS_INVALID_PARAMETER,
457 		    ERRDOS, ERROR_INVALID_PARAMETER);
458 		return (SDRC_ERROR);
459 	}
460 
461 	param->rw_vdb.vdb_uio.uio_loffset = (offset_t)param->rw_offset;
462 
463 	if (param->rw_count != 0) {
464 		if ((rc = smb_common_write(sr, param)) != 0) {
465 			if (sr->smb_error.status !=
466 			    NT_STATUS_FILE_LOCK_CONFLICT)
467 				smbsr_errno(sr, rc);
468 			return (SDRC_ERROR);
469 		}
470 	}
471 
472 	count_low = param->rw_count & 0xFFFF;
473 	count_high = (param->rw_count >> 16) & 0xFF;
474 
475 	rc = smbsr_encode_result(sr, 6, 0, "bb1.wwwwww",
476 	    6, sr->andx_com, 15, count_low, 0, count_high, 0, 0);
477 
478 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
479 }
480 
481 /*
482  * Common function for writing files or IPC/MSRPC named pipes.
483  *
484  * Returns errno values.
485  */
486 int
487 smb_common_write(smb_request_t *sr, smb_rw_param_t *param)
488 {
489 	smb_ofile_t *ofile = sr->fid_ofile;
490 	smb_node_t *node;
491 	int stability = 0;
492 	uint32_t lcount;
493 	int rc = 0;
494 
495 	switch (sr->tid_tree->t_res_type & STYPE_MASK) {
496 	case STYPE_DISKTREE:
497 	case STYPE_PRINTQ:
498 		node = ofile->f_node;
499 
500 		if (!smb_node_is_dir(node)) {
501 			rc = smb_lock_range_access(sr, node, param->rw_offset,
502 			    param->rw_count, B_TRUE);
503 			if (rc != NT_STATUS_SUCCESS) {
504 				smbsr_error(sr, NT_STATUS_FILE_LOCK_CONFLICT,
505 				    ERRDOS, ERROR_LOCK_VIOLATION);
506 				return (EACCES);
507 			}
508 		}
509 
510 		if (SMB_WRMODE_IS_STABLE(param->rw_mode) ||
511 		    (node->flags & NODE_FLAGS_WRITE_THROUGH)) {
512 			stability = FSYNC;
513 		}
514 
515 		rc = smb_fsop_write(sr, sr->user_cr, node, ofile,
516 		    &param->rw_vdb.vdb_uio, &lcount, stability);
517 		if (rc)
518 			return (rc);
519 		param->rw_count = lcount;
520 		/* This revokes read cache delegations. */
521 		(void) smb_oplock_break_WRITE(node, ofile);
522 		/*
523 		 * Don't want the performance cost of generating
524 		 * change notify events on every write.  Instead:
525 		 * Keep track of the fact that we have written
526 		 * data via this handle, and do change notify
527 		 * work on the first write, and during close.
528 		 */
529 		if (ofile->f_written == B_FALSE) {
530 			ofile->f_written = B_TRUE;
531 			smb_node_notify_modified(node);
532 		}
533 		break;
534 
535 	case STYPE_IPC:
536 		param->rw_count = param->rw_vdb.vdb_uio.uio_resid;
537 
538 		if ((rc = smb_opipe_write(sr, &param->rw_vdb.vdb_uio)) != 0)
539 			param->rw_count = 0;
540 		break;
541 
542 	default:
543 		rc = EACCES;
544 		break;
545 	}
546 
547 	if (rc != 0)
548 		return (rc);
549 
550 	mutex_enter(&ofile->f_mutex);
551 	ofile->f_seek_pos = param->rw_offset + param->rw_count;
552 	mutex_exit(&ofile->f_mutex);
553 	return (rc);
554 }
555 
556 /*
557  * Truncate a disk file to the specified offset.
558  * Typically, w_count will be zero here.
559  *
560  * Note that smb_write_andx cannot be used to reduce the file size so,
561  * if this is required, smb_write is called with a count of zero and
562  * the appropriate file length in offset. The file should be resized
563  * to the length specified by the offset.
564  *
565  * Returns errno values.
566  */
567 static int
568 smb_write_truncate(smb_request_t *sr, smb_rw_param_t *param)
569 {
570 	smb_ofile_t *ofile = sr->fid_ofile;
571 	smb_node_t *node = ofile->f_node;
572 	smb_attr_t attr;
573 	uint32_t status;
574 	int rc;
575 
576 	if (STYPE_ISIPC(sr->tid_tree->t_res_type))
577 		return (0);
578 
579 	mutex_enter(&node->n_mutex);
580 	if (!smb_node_is_dir(node)) {
581 		status = smb_lock_range_access(sr, node, param->rw_offset,
582 		    param->rw_count, B_TRUE);
583 		if (status != NT_STATUS_SUCCESS) {
584 			mutex_exit(&node->n_mutex);
585 			smbsr_error(sr, NT_STATUS_FILE_LOCK_CONFLICT,
586 			    ERRDOS, ERROR_LOCK_VIOLATION);
587 			return (EACCES);
588 		}
589 	}
590 	mutex_exit(&node->n_mutex);
591 
592 	bzero(&attr, sizeof (smb_attr_t));
593 	attr.sa_mask = SMB_AT_SIZE;
594 	attr.sa_vattr.va_size = param->rw_offset;
595 	rc = smb_node_setattr(sr, node, sr->user_cr, ofile, &attr);
596 	if (rc != 0)
597 		return (rc);
598 
599 	mutex_enter(&ofile->f_mutex);
600 	ofile->f_seek_pos = param->rw_offset + param->rw_count;
601 	mutex_exit(&ofile->f_mutex);
602 	return (0);
603 }
604