xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_write.c (revision ca9327a6de44d69ddab3668cc1e143ce781387a3)
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  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <sys/sdt.h>
29 #include <smbsrv/smb_incl.h>
30 #include <smbsrv/smb_fsops.h>
31 #include <smbsrv/mbuf.h>
32 #include <smbsrv/netbios.h>
33 
34 
35 #define	SMB_WRMODE_WRITE_THRU	0x0001
36 #define	SMB_WRMODE_IS_STABLE(M)	((M) & SMB_WRMODE_WRITE_THRU)
37 
38 
39 static int smb_write_common(smb_request_t *, smb_rw_param_t *);
40 static int smb_write_truncate(smb_request_t *, smb_rw_param_t *);
41 int smb_set_file_size(smb_request_t *);
42 
43 
44 /*
45  * Write count bytes at the specified offset in a file.  The offset is
46  * limited to 32-bits.  If the count is zero, the file is truncated to
47  * the length specified by the offset.
48  *
49  * The response count indicates the actual number of bytes written, which
50  * will equal the requested count on success.  If request and response
51  * counts differ but there is no error, the client will assume that the
52  * server encountered a resource issue.
53  */
54 smb_sdrc_t
55 smb_pre_write(smb_request_t *sr)
56 {
57 	smb_rw_param_t *param;
58 	uint32_t off;
59 	int rc;
60 
61 	param = kmem_zalloc(sizeof (smb_rw_param_t), KM_SLEEP);
62 	sr->arg.rw = param;
63 	param->rw_magic = SMB_RW_MAGIC;
64 
65 	rc = smbsr_decode_vwv(sr, "wwl", &sr->smb_fid, &param->rw_count, &off);
66 
67 	param->rw_offset = (uint64_t)off;
68 	param->rw_vdb.uio.uio_loffset = (offset_t)param->rw_offset;
69 
70 	DTRACE_SMB_2(op__Write__start, smb_request_t *, sr,
71 	    smb_rw_param_t *, param);
72 
73 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
74 }
75 
76 void
77 smb_post_write(smb_request_t *sr)
78 {
79 	DTRACE_SMB_2(op__Write__done, smb_request_t *, sr,
80 	    smb_rw_param_t *, sr->arg.rw);
81 
82 	kmem_free(sr->arg.rw, sizeof (smb_rw_param_t));
83 }
84 
85 smb_sdrc_t
86 smb_com_write(smb_request_t *sr)
87 {
88 	smb_rw_param_t *param = sr->arg.rw;
89 	int rc;
90 
91 	sr->fid_ofile = smb_ofile_lookup_by_fid(sr->tid_tree, sr->smb_fid);
92 	if (sr->fid_ofile == NULL) {
93 		smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
94 		return (SDRC_ERROR);
95 	}
96 
97 	if (param->rw_count == 0) {
98 		rc = smb_write_truncate(sr, param);
99 	} else {
100 		rc = smbsr_decode_data(sr, "D", &param->rw_vdb);
101 
102 		if ((rc != 0) || (param->rw_vdb.len != param->rw_count)) {
103 			smbsr_error(sr, NT_STATUS_INVALID_PARAMETER,
104 			    ERRDOS, ERROR_INVALID_PARAMETER);
105 			return (SDRC_ERROR);
106 		}
107 
108 		param->rw_vdb.uio.uio_loffset = (offset_t)param->rw_offset;
109 
110 		rc = smb_write_common(sr, param);
111 	}
112 
113 	if (rc != 0) {
114 		if (sr->smb_error.status != NT_STATUS_FILE_LOCK_CONFLICT)
115 			smbsr_errno(sr, rc);
116 		return (SDRC_ERROR);
117 	}
118 
119 	rc = smbsr_encode_result(sr, 1, 0, "bww", 1, param->rw_count, 0);
120 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
121 }
122 
123 /*
124  * Write count bytes to a file and then close the file.  This function
125  * can only be used to write to 32-bit offsets and the client must set
126  * WordCount (6 or 12) correctly in order to locate the data to be
127  * written.  If an error occurs on the write, the file should still be
128  * closed.  If Count is 0, the file is truncated (or extended) to offset.
129  *
130  * If the last_write time is non-zero, last_write should be used to set
131  * the mtime.  Otherwise the file system stamps the mtime.  Failure to
132  * set mtime should not result in an error response.
133  */
134 smb_sdrc_t
135 smb_pre_write_and_close(smb_request_t *sr)
136 {
137 	smb_rw_param_t *param;
138 	uint32_t off;
139 	int rc;
140 
141 	param = kmem_zalloc(sizeof (smb_rw_param_t), KM_SLEEP);
142 	sr->arg.rw = param;
143 	param->rw_magic = SMB_RW_MAGIC;
144 
145 	if (sr->smb_wct == 12) {
146 		rc = smbsr_decode_vwv(sr, "wwll12.", &sr->smb_fid,
147 		    &param->rw_count, &off, &param->rw_last_write);
148 	} else {
149 		rc = smbsr_decode_vwv(sr, "wwll", &sr->smb_fid,
150 		    &param->rw_count, &off, &param->rw_last_write);
151 	}
152 
153 	param->rw_offset = (uint64_t)off;
154 
155 	DTRACE_SMB_2(op__WriteAndClose__start, smb_request_t *, sr,
156 	    smb_rw_param_t *, param);
157 
158 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
159 }
160 
161 void
162 smb_post_write_and_close(smb_request_t *sr)
163 {
164 	DTRACE_SMB_2(op__WriteAndClose__done, smb_request_t *, sr,
165 	    smb_rw_param_t *, sr->arg.rw);
166 
167 	kmem_free(sr->arg.rw, sizeof (smb_rw_param_t));
168 }
169 
170 smb_sdrc_t
171 smb_com_write_and_close(smb_request_t *sr)
172 {
173 	smb_rw_param_t *param = sr->arg.rw;
174 	int rc = 0;
175 
176 	sr->fid_ofile = smb_ofile_lookup_by_fid(sr->tid_tree, sr->smb_fid);
177 	if (sr->fid_ofile == NULL) {
178 		smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
179 		return (SDRC_ERROR);
180 	}
181 
182 	if (param->rw_count == 0) {
183 		rc = smb_write_truncate(sr, param);
184 	} else {
185 		/*
186 		 * There may be a bug here: should this be "3.#B"?
187 		 */
188 		rc = smbsr_decode_data(sr, ".#B", param->rw_count,
189 		    &param->rw_vdb);
190 
191 		if ((rc != 0) || (param->rw_vdb.len != param->rw_count)) {
192 			smbsr_error(sr, NT_STATUS_INVALID_PARAMETER,
193 			    ERRDOS, ERROR_INVALID_PARAMETER);
194 			return (SDRC_ERROR);
195 		}
196 
197 		param->rw_vdb.uio.uio_loffset = (offset_t)param->rw_offset;
198 
199 		rc = smb_write_common(sr, param);
200 	}
201 
202 	if (rc != 0) {
203 		if (sr->smb_error.status != NT_STATUS_FILE_LOCK_CONFLICT)
204 			smbsr_errno(sr, rc);
205 		return (SDRC_ERROR);
206 	}
207 
208 	if ((rc = smb_common_close(sr, param->rw_last_write)) != 0) {
209 		smbsr_errno(sr, rc);
210 		return (SDRC_ERROR);
211 	}
212 
213 	rc = smbsr_encode_result(sr, 1, 0, "bww", 1, param->rw_count, 0);
214 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
215 }
216 
217 /*
218  * Write count bytes to a file at the specified offset and then unlock
219  * them.  Write behind is safe because the client should have the range
220  * locked and this request is allowed to extend the file - note that
221  * offset is limited to 32-bits.
222  *
223  * Spec advice: it is an error for count to be zero.  For compatibility,
224  * we take no action and return success.
225  *
226  * The SmbLockAndRead/SmbWriteAndUnlock sub-dialect is only valid on disk
227  * files.  Reject any attempt to use it on other shares.
228  *
229  * The response count indicates the actual number of bytes written, which
230  * will equal the requested count on success.  If request and response
231  * counts differ but there is no error, the client will assume that the
232  * server encountered a resource issue.
233  */
234 smb_sdrc_t
235 smb_pre_write_and_unlock(smb_request_t *sr)
236 {
237 	smb_rw_param_t *param;
238 	uint32_t off;
239 	uint16_t remcnt;
240 	int rc;
241 
242 	param = kmem_zalloc(sizeof (smb_rw_param_t), KM_SLEEP);
243 	sr->arg.rw = param;
244 	param->rw_magic = SMB_RW_MAGIC;
245 
246 	rc = smbsr_decode_vwv(sr, "wwlw", &sr->smb_fid, &param->rw_count, &off,
247 	    &remcnt);
248 
249 	param->rw_offset = (uint64_t)off;
250 
251 	DTRACE_SMB_2(op__WriteAndUnlock__start, smb_request_t *, sr,
252 	    smb_rw_param_t *, param);
253 
254 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
255 }
256 
257 void
258 smb_post_write_and_unlock(smb_request_t *sr)
259 {
260 	DTRACE_SMB_2(op__WriteAndUnlock__done, smb_request_t *, sr,
261 	    smb_rw_param_t *, sr->arg.rw);
262 
263 	kmem_free(sr->arg.rw, sizeof (smb_rw_param_t));
264 }
265 
266 smb_sdrc_t
267 smb_com_write_and_unlock(smb_request_t *sr)
268 {
269 	smb_rw_param_t *param = sr->arg.rw;
270 	uint32_t status;
271 	int rc = 0;
272 
273 	if (STYPE_ISDSK(sr->tid_tree->t_res_type) == 0) {
274 		smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRDOS, ERRnoaccess);
275 		return (SDRC_ERROR);
276 	}
277 
278 	sr->fid_ofile = smb_ofile_lookup_by_fid(sr->tid_tree, sr->smb_fid);
279 	if (sr->fid_ofile == NULL) {
280 		smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
281 		return (SDRC_ERROR);
282 	}
283 
284 	if (param->rw_count == 0) {
285 		rc = smbsr_encode_result(sr, 1, 0, "bww", 1, 0, 0);
286 		return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
287 	}
288 
289 	rc = smbsr_decode_data(sr, "D", &param->rw_vdb);
290 
291 	if ((rc != 0) || (param->rw_count != param->rw_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.uio.uio_loffset = (offset_t)param->rw_offset;
298 
299 	if ((rc = smb_write_common(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 	status = smb_unlock_range(sr, sr->fid_ofile->f_node, param->rw_offset,
306 	    (uint64_t)param->rw_count);
307 	if (status != NT_STATUS_SUCCESS) {
308 		smbsr_error(sr, NT_STATUS_RANGE_NOT_LOCKED,
309 		    ERRDOS, ERRnotlocked);
310 		return (SDRC_ERROR);
311 	}
312 
313 	rc = smbsr_encode_result(sr, 1, 0, "bww", 1, param->rw_count, 0);
314 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
315 }
316 
317 /*
318  * Write bytes to a file (SMB Core).  This request was extended in
319  * LM 0.12 to support 64-bit offsets, indicated by sending a wct of
320  * 14, instead of 12, and including additional offset information.
321  *
322  * A ByteCount of 0 does not truncate the file - use SMB_COM_WRITE
323  * to truncate a file.  A zero length merely transfers zero bytes.
324  *
325  * If bit 0 of WriteMode is set, Fid must refer to a disk file and
326  * the data must be on stable storage before responding.
327  */
328 smb_sdrc_t
329 smb_pre_write_andx(smb_request_t *sr)
330 {
331 	smb_rw_param_t *param;
332 	uint32_t off_low;
333 	uint32_t off_high;
334 	uint16_t remcnt;
335 	int rc;
336 
337 	param = kmem_zalloc(sizeof (smb_rw_param_t), KM_SLEEP);
338 	sr->arg.rw = param;
339 	param->rw_magic = SMB_RW_MAGIC;
340 
341 	if (sr->smb_wct == 14) {
342 		rc = smbsr_decode_vwv(sr, "4.wl4.ww2.wwl", &sr->smb_fid,
343 		    &off_low, &param->rw_mode, &remcnt, &param->rw_count,
344 		    &param->rw_dsoff, &off_high);
345 
346 		param->rw_dsoff -= 63;
347 		param->rw_offset = ((uint64_t)off_high << 32) | off_low;
348 	} else {
349 		rc = smbsr_decode_vwv(sr, "4.wl4.ww2.ww", &sr->smb_fid,
350 		    &off_low, &param->rw_mode, &remcnt, &param->rw_count,
351 		    &param->rw_dsoff);
352 
353 		param->rw_offset = (uint64_t)off_low;
354 		param->rw_dsoff -= 59;
355 	}
356 
357 	DTRACE_SMB_2(op__WriteX__start, smb_request_t *, sr,
358 	    smb_rw_param_t *, param);
359 
360 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
361 }
362 
363 void
364 smb_post_write_andx(smb_request_t *sr)
365 {
366 	DTRACE_SMB_2(op__WriteX__done, smb_request_t *, sr,
367 	    smb_rw_param_t *, sr->arg.rw);
368 
369 	kmem_free(sr->arg.rw, sizeof (smb_rw_param_t));
370 }
371 
372 smb_sdrc_t
373 smb_com_write_andx(smb_request_t *sr)
374 {
375 	smb_rw_param_t *param = sr->arg.rw;
376 	int rc;
377 
378 	ASSERT(param);
379 	ASSERT(param->rw_magic == SMB_RW_MAGIC);
380 
381 	sr->fid_ofile = smb_ofile_lookup_by_fid(sr->tid_tree, sr->smb_fid);
382 	if (sr->fid_ofile == NULL) {
383 		smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
384 		return (SDRC_ERROR);
385 	}
386 
387 	if (SMB_WRMODE_IS_STABLE(param->rw_mode) &&
388 	    STYPE_ISDSK(sr->tid_tree->t_res_type) == 0) {
389 		smbsr_error(sr, 0, ERRSRV, ERRaccess);
390 		return (SDRC_ERROR);
391 	}
392 
393 	rc = smbsr_decode_data(sr, "#.#B", param->rw_dsoff, param->rw_count,
394 	    &param->rw_vdb);
395 	if ((rc != 0) || (param->rw_vdb.len != param->rw_count)) {
396 		smbsr_error(sr, NT_STATUS_INVALID_PARAMETER,
397 		    ERRDOS, ERROR_INVALID_PARAMETER);
398 		return (SDRC_ERROR);
399 	}
400 
401 	param->rw_vdb.uio.uio_loffset = (offset_t)param->rw_offset;
402 
403 	if (param->rw_count != 0) {
404 		if ((rc = smb_write_common(sr, param)) != 0) {
405 			if (sr->smb_error.status !=
406 			    NT_STATUS_FILE_LOCK_CONFLICT)
407 				smbsr_errno(sr, rc);
408 			return (SDRC_ERROR);
409 		}
410 	}
411 
412 	rc = smbsr_encode_result(sr, 6, 0, "bb1.ww6.w",
413 	    6, sr->andx_com, 15, param->rw_count, 0);
414 
415 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
416 }
417 
418 /*
419  * Common function for writing files or IPC/MSRPC named pipes.
420  *
421  * Returns errno values.
422  */
423 static int
424 smb_write_common(smb_request_t *sr, smb_rw_param_t *param)
425 {
426 	struct smb_ofile *ofile = sr->fid_ofile;
427 	smb_node_t *node;
428 	uint32_t stability = FSSTAB_UNSTABLE;
429 	uint32_t lcount;
430 	int rc = 0;
431 
432 	switch (sr->tid_tree->t_res_type & STYPE_MASK) {
433 	case STYPE_DISKTREE:
434 		node = ofile->f_node;
435 
436 		if (node->attr.sa_vattr.va_type != VDIR) {
437 			rc = smb_lock_range_access(sr, node, param->rw_offset,
438 			    param->rw_count, B_TRUE);
439 			if (rc != NT_STATUS_SUCCESS) {
440 				smbsr_error(sr, NT_STATUS_FILE_LOCK_CONFLICT,
441 				    ERRDOS, ERROR_LOCK_VIOLATION);
442 				return (EACCES);
443 			}
444 		}
445 
446 		if (SMB_WRMODE_IS_STABLE(param->rw_mode) ||
447 		    (node->flags & NODE_FLAGS_WRITE_THROUGH)) {
448 			stability = FSSTAB_FILE_SYNC;
449 		}
450 
451 		rc = smb_fsop_write(sr, sr->user_cr, node,
452 		    &param->rw_vdb.uio, &lcount, &node->attr, &stability);
453 
454 		if (rc)
455 			return (rc);
456 
457 		node->flags |= NODE_FLAGS_SYNCATIME;
458 
459 		if (node->flags & NODE_FLAGS_SET_SIZE) {
460 			if ((param->rw_offset + lcount) >= node->n_size) {
461 				node->flags &= ~NODE_FLAGS_SET_SIZE;
462 				node->n_size = param->rw_offset + lcount;
463 			}
464 		}
465 
466 		param->rw_count = (uint16_t)lcount;
467 		break;
468 
469 	case STYPE_IPC:
470 		param->rw_count = (uint16_t)param->rw_vdb.uio.uio_resid;
471 
472 		if ((rc = smb_rpc_write(sr, &param->rw_vdb.uio)) != 0)
473 			param->rw_count = 0;
474 		break;
475 
476 	default:
477 		rc = EACCES;
478 		break;
479 	}
480 
481 	if (rc != 0)
482 		return (rc);
483 
484 	mutex_enter(&ofile->f_mutex);
485 	ofile->f_seek_pos = param->rw_offset + param->rw_count;
486 	mutex_exit(&ofile->f_mutex);
487 	return (rc);
488 }
489 
490 /*
491  * Truncate a disk file to the specified offset.
492  * Typically, w_count will be zero here.
493  *
494  * Returns errno values.
495  */
496 static int
497 smb_write_truncate(smb_request_t *sr, smb_rw_param_t *param)
498 {
499 	struct smb_ofile *ofile = sr->fid_ofile;
500 	smb_node_t *node = ofile->f_node;
501 	boolean_t append_only = B_FALSE;
502 	uint32_t status;
503 	int rc;
504 
505 	if (STYPE_ISDSK(sr->tid_tree->t_res_type) == 0)
506 		return (0);
507 
508 	status = smb_ofile_access(sr->fid_ofile, sr->user_cr, FILE_WRITE_DATA);
509 	if (status != NT_STATUS_SUCCESS) {
510 		status = smb_ofile_access(sr->fid_ofile, sr->user_cr,
511 		    FILE_APPEND_DATA);
512 		if (status != NT_STATUS_SUCCESS)
513 			return (EACCES);
514 		else
515 			append_only = B_TRUE;
516 	}
517 
518 	smb_rwx_xenter(&node->n_lock);
519 
520 	if (append_only && (param->rw_offset < node->n_size)) {
521 		smb_rwx_xexit(&node->n_lock);
522 		return (EACCES);
523 	}
524 
525 	if (node->attr.sa_vattr.va_type != VDIR) {
526 		status = smb_lock_range_access(sr, node, param->rw_offset,
527 		    param->rw_count, B_TRUE);
528 		if (status != NT_STATUS_SUCCESS) {
529 			smb_rwx_xexit(&node->n_lock);
530 			smbsr_error(sr, NT_STATUS_FILE_LOCK_CONFLICT,
531 			    ERRDOS, ERROR_LOCK_VIOLATION);
532 			return (EACCES);
533 		}
534 	}
535 
536 	node->flags |= NODE_FLAGS_SET_SIZE;
537 	node->n_size = param->rw_offset;
538 
539 	smb_rwx_xexit(&node->n_lock);
540 
541 	if ((rc = smb_set_file_size(sr)) != 0)
542 		return (rc);
543 
544 	mutex_enter(&ofile->f_mutex);
545 	ofile->f_seek_pos = param->rw_offset + param->rw_count;
546 	mutex_exit(&ofile->f_mutex);
547 	return (0);
548 }
549 
550 /*
551  * Set the file size using the value in the node. The file will only be
552  * updated if NODE_FLAGS_SET_SIZE is set.  It is safe to pass a null node
553  * pointer, we just return success.
554  *
555  * The node attributes are refreshed here from the file system. So any
556  * attributes that are affected by file size changes, i.e. the mtime,
557  * will be current.
558  *
559  * Note that smb_write_andx cannot be used to reduce the file size so,
560  * if this is required, smb_write is called with a count of zero and
561  * the appropriate file length in offset. The file should be resized
562  * to the length specified by the offset.
563  *
564  * Returns 0 on success. Otherwise returns EACCES.
565  */
566 int
567 smb_set_file_size(smb_request_t *sr)
568 {
569 	struct smb_node *node;
570 	smb_attr_t new_attr;
571 	uint32_t dosattr;
572 
573 	if ((node = sr->fid_ofile->f_node) == 0)
574 		return (0);
575 
576 	if ((node->flags & NODE_FLAGS_SET_SIZE) == 0)
577 		return (0);
578 
579 	node->flags &= ~NODE_FLAGS_SET_SIZE;
580 
581 	dosattr = smb_node_get_dosattr(node);
582 
583 	if (dosattr & SMB_FA_READONLY) {
584 		if (((node->flags & NODE_FLAGS_CREATED) == 0) ||
585 		    (sr->session->s_kid != node->n_orig_session_id))
586 			return (EACCES);
587 	}
588 
589 	bzero(&new_attr, sizeof (new_attr));
590 	new_attr.sa_vattr.va_size = node->n_size;
591 	new_attr.sa_mask = SMB_AT_SIZE;
592 
593 	(void) smb_fsop_setattr(sr, sr->user_cr, node, &new_attr,
594 	    &node->attr);
595 
596 	return (0);
597 }
598