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