xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_cmn_rename.c (revision 03270635d68df6a0392fb8f4b7c04acad764648b)
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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
23  * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
24  */
25 
26 #include <sys/synch.h>
27 #include <smbsrv/smb_kproto.h>
28 #include <smbsrv/smb_fsops.h>
29 #include <sys/nbmlock.h>
30 
31 /*
32  * SMB_TRANS2_SET_FILE/PATH_INFO (RENAME_INFORMATION level) flag
33  */
34 #define	SMB_RENAME_FLAG_OVERWRITE	0x001
35 
36 static int smb_rename_check_stream(smb_fqi_t *, smb_fqi_t *);
37 static int smb_rename_check_attr(smb_request_t *, smb_node_t *, uint16_t);
38 static int smb_rename_lookup_src(smb_request_t *);
39 static void smb_rename_release_src(smb_request_t *);
40 static uint32_t smb_rename_errno2status(int);
41 
42 /*
43  * smb_setinfo_rename
44  *
45  * Implements SMB_FILE_RENAME_INFORMATION level of Trans2_Set_FileInfo
46  * and Trans2_Set_PathInfo and SMB2 set_info, FileRenameInformation.
47  * If the new filename (dst_fqi) already exists it may be overwritten
48  * if flags == 1.
49  *
50  * The passed path is a full path relative to the share root.
51  *
52  * Returns NT status codes.
53  *
54  * Similar to smb_setinfo_link(), below.
55  */
56 uint32_t
57 smb_setinfo_rename(smb_request_t *sr, smb_node_t *node, char *path, int flags)
58 {
59 	smb_fqi_t	*src_fqi = &sr->arg.dirop.fqi;
60 	smb_fqi_t	*dst_fqi = &sr->arg.dirop.dst_fqi;
61 	smb_pathname_t	*dst_pn = &dst_fqi->fq_path;
62 	uint32_t	status;
63 
64 	sr->arg.dirop.flags = flags ? SMB_RENAME_FLAG_OVERWRITE : 0;
65 	sr->arg.dirop.info_level = FileRenameInformation;
66 
67 	src_fqi->fq_sattr = SMB_SEARCH_ATTRIBUTES;
68 	src_fqi->fq_fnode = node;
69 	src_fqi->fq_dnode = node->n_dnode;
70 
71 	/* validate the dst pathname */
72 	smb_pathname_init(sr, dst_pn, path);
73 	if (!smb_pathname_validate(sr, dst_pn))
74 		return (NT_STATUS_OBJECT_NAME_INVALID);
75 
76 	status = smb_common_rename(sr, src_fqi, dst_fqi);
77 	return (status);
78 }
79 
80 /*
81  * smb_common_rename
82  *
83  * Common code for renaming a file.
84  *
85  * If the source and destination are identical, we go through all
86  * the checks but we don't actually do the rename.  If the source
87  * and destination files differ only in case, we do a case-sensitive
88  * rename.  Otherwise, we do a full case-insensitive rename.
89  *
90  * Returns NT status values.
91  *
92  * Similar to smb_make_link(), below.
93  */
94 uint32_t
95 smb_common_rename(smb_request_t *sr, smb_fqi_t *src_fqi, smb_fqi_t *dst_fqi)
96 {
97 	smb_node_t *src_fnode, *src_dnode, *dst_dnode;
98 	smb_node_t *dst_fnode = 0;
99 	smb_node_t *tnode;
100 	char *new_name, *path;
101 	DWORD status;
102 	int rc, count;
103 
104 	tnode = sr->tid_tree->t_snode;
105 	path = dst_fqi->fq_path.pn_path;
106 
107 	/* Check if attempting to rename a stream - not yet supported */
108 	rc = smb_rename_check_stream(src_fqi, dst_fqi);
109 	if (rc != 0)
110 		return (smb_rename_errno2status(rc));
111 
112 	/*
113 	 * The source node may already have been provided,
114 	 * i.e. when called by SMB1/SMB2 smb_setinfo_rename.
115 	 * Not provided by smb_com_rename, smb_com_nt_rename.
116 	 */
117 	if (src_fqi->fq_fnode) {
118 		smb_node_start_crit(src_fqi->fq_fnode, RW_READER);
119 		smb_node_ref(src_fqi->fq_fnode);
120 		smb_node_ref(src_fqi->fq_dnode);
121 	} else {
122 		/* lookup and validate src node */
123 		rc = smb_rename_lookup_src(sr);
124 		if (rc != 0)
125 			return (smb_rename_errno2status(rc));
126 	}
127 
128 	src_fnode = src_fqi->fq_fnode;
129 	src_dnode = src_fqi->fq_dnode;
130 
131 	/*
132 	 * Find the destination dnode and last component.
133 	 * May already be provided, i.e. when called via
134 	 * SMB1 trans2 setinfo.
135 	 */
136 	if (dst_fqi->fq_dnode) {
137 		/* called via smb_set_rename_info */
138 		smb_node_ref(dst_fqi->fq_dnode);
139 	} else {
140 		/* called via smb2_setf_rename, smb_com_rename, etc. */
141 		rc = smb_pathname_reduce(sr, sr->user_cr, path, tnode, tnode,
142 		    &dst_fqi->fq_dnode, dst_fqi->fq_last_comp);
143 		if (rc != 0) {
144 			smb_rename_release_src(sr);
145 			return (smb_rename_errno2status(rc));
146 		}
147 	}
148 
149 	dst_dnode = dst_fqi->fq_dnode;
150 	new_name = dst_fqi->fq_last_comp;
151 
152 	/* If exact name match in same directory, we're done */
153 	if ((src_dnode == dst_dnode) &&
154 	    (strcmp(src_fnode->od_name, new_name) == 0)) {
155 		smb_rename_release_src(sr);
156 		smb_node_release(dst_dnode);
157 		return (0);
158 	}
159 
160 	/* Lookup destination node */
161 	rc = smb_fsop_lookup(sr, sr->user_cr, 0, tnode,
162 	    dst_dnode, new_name, &dst_fqi->fq_fnode);
163 
164 	/* If the destination node doesn't already exist, validate new_name. */
165 	if (rc == ENOENT) {
166 		if (smb_is_invalid_filename(new_name)) {
167 			smb_rename_release_src(sr);
168 			smb_node_release(dst_dnode);
169 			return (NT_STATUS_OBJECT_NAME_INVALID);
170 		}
171 	}
172 
173 	/*
174 	 * Handle case where changing case of the same directory entry.
175 	 *
176 	 * If we found the dst node in the same directory as the src node,
177 	 * and their names differ only in case:
178 	 *
179 	 * If the tree is case sensitive (or mixed):
180 	 *  Do case sensitive lookup to see if exact match exists.
181 	 *  If the exact match is the same node as src_node we're done.
182 	 *
183 	 * If the tree is case insensitive:
184 	 *  There is currently no way to tell if the case is different
185 	 *  or not, so do the rename (unless the specified new name was
186 	 *  mangled).
187 	 */
188 	if ((rc == 0) &&
189 	    (src_dnode == dst_dnode) &&
190 	    (smb_strcasecmp(src_fnode->od_name,
191 	    dst_fqi->fq_fnode->od_name, 0) == 0)) {
192 		smb_node_release(dst_fqi->fq_fnode);
193 		dst_fqi->fq_fnode = NULL;
194 
195 		if (smb_tree_has_feature(sr->tid_tree,
196 		    SMB_TREE_NO_CASESENSITIVE)) {
197 			if (smb_strcasecmp(src_fnode->od_name,
198 			    dst_fqi->fq_last_comp, 0) != 0) {
199 				smb_rename_release_src(sr);
200 				smb_node_release(dst_dnode);
201 				return (0);
202 			}
203 		} else {
204 			rc = smb_fsop_lookup(sr, sr->user_cr,
205 			    SMB_CASE_SENSITIVE, tnode, dst_dnode, new_name,
206 			    &dst_fqi->fq_fnode);
207 
208 			if ((rc == 0) &&
209 			    (dst_fqi->fq_fnode == src_fnode)) {
210 				smb_rename_release_src(sr);
211 				smb_node_release(dst_fqi->fq_fnode);
212 				smb_node_release(dst_dnode);
213 				return (0);
214 			}
215 		}
216 	}
217 
218 	if ((rc != 0) && (rc != ENOENT)) {
219 		smb_rename_release_src(sr);
220 		smb_node_release(dst_fqi->fq_dnode);
221 		return (smb_rename_errno2status(rc));
222 	}
223 
224 	if (dst_fqi->fq_fnode) {
225 		/*
226 		 * Destination already exists.  Do delete checks.
227 		 */
228 		dst_fnode = dst_fqi->fq_fnode;
229 
230 		if ((sr->arg.dirop.flags & SMB_RENAME_FLAG_OVERWRITE) == 0) {
231 			smb_rename_release_src(sr);
232 			smb_node_release(dst_fnode);
233 			smb_node_release(dst_dnode);
234 			return (NT_STATUS_OBJECT_NAME_COLLISION);
235 		}
236 
237 		(void) smb_oplock_break(sr, dst_fnode,
238 		    SMB_OPLOCK_BREAK_TO_NONE | SMB_OPLOCK_BREAK_BATCH);
239 
240 		/*
241 		 * Wait (a little) for the oplock break to be
242 		 * responded to by clients closing handles.
243 		 * Hold node->n_lock as reader to keep new
244 		 * ofiles from showing up after we check.
245 		 */
246 		smb_node_rdlock(dst_fnode);
247 		for (count = 0; count <= 12; count++) {
248 			status = smb_node_delete_check(dst_fnode);
249 			if (status != NT_STATUS_SHARING_VIOLATION)
250 				break;
251 			smb_node_unlock(dst_fnode);
252 			delay(MSEC_TO_TICK(100));
253 			smb_node_rdlock(dst_fnode);
254 		}
255 		if (status != NT_STATUS_SUCCESS) {
256 			smb_node_unlock(dst_fnode);
257 			smb_rename_release_src(sr);
258 			smb_node_release(dst_fnode);
259 			smb_node_release(dst_dnode);
260 			return (NT_STATUS_ACCESS_DENIED);
261 		}
262 
263 		/*
264 		 * Note, the combination of these two:
265 		 *	smb_node_rdlock(node);
266 		 *	nbl_start_crit(node->vp, RW_READER);
267 		 * is equivalent to this call:
268 		 *	smb_node_start_crit(node, RW_READER)
269 		 *
270 		 * Cleanup after this point should use:
271 		 *	smb_node_end_crit(dst_fnode)
272 		 */
273 		nbl_start_crit(dst_fnode->vp, RW_READER);
274 
275 		/*
276 		 * This checks nbl_share_conflict, nbl_lock_conflict
277 		 */
278 		status = smb_nbl_conflict(dst_fnode, 0, UINT64_MAX, NBL_REMOVE);
279 		if (status != NT_STATUS_SUCCESS) {
280 			smb_node_end_crit(dst_fnode);
281 			smb_rename_release_src(sr);
282 			smb_node_release(dst_fnode);
283 			smb_node_release(dst_dnode);
284 			return (NT_STATUS_ACCESS_DENIED);
285 		}
286 
287 		new_name = dst_fnode->od_name;
288 	}
289 
290 	rc = smb_fsop_rename(sr, sr->user_cr,
291 	    src_dnode, src_fnode->od_name,
292 	    dst_dnode, new_name);
293 
294 	if (rc == 0) {
295 		/*
296 		 * Note that renames in the same directory are normally
297 		 * delivered in {old,new} pairs, and clients expect them
298 		 * in that order, if both events are delivered.
299 		 */
300 		int a_src, a_dst; /* action codes */
301 		if (src_dnode == dst_dnode) {
302 			a_src = FILE_ACTION_RENAMED_OLD_NAME;
303 			a_dst = FILE_ACTION_RENAMED_NEW_NAME;
304 		} else {
305 			a_src = FILE_ACTION_REMOVED;
306 			a_dst = FILE_ACTION_ADDED;
307 		}
308 		smb_node_notify_change(src_dnode, a_src, src_fnode->od_name);
309 		smb_node_notify_change(dst_dnode, a_dst, new_name);
310 	}
311 
312 	smb_rename_release_src(sr);
313 
314 	if (dst_fqi->fq_fnode) {
315 		smb_node_end_crit(dst_fnode);
316 		smb_node_release(dst_fnode);
317 	}
318 	smb_node_release(dst_dnode);
319 
320 	return (smb_rename_errno2status(rc));
321 }
322 
323 /*
324  * smb_rename_check_stream
325  *
326  * For a stream rename the dst path must begin with ':', or "\\:".
327  * We don't yet support stream rename, Return EACCES.
328  *
329  * If not a stream rename, in accordance with the above rule,
330  * it is not valid for either the src or dst to be a stream.
331  * Return EINVAL.
332  */
333 static int
334 smb_rename_check_stream(smb_fqi_t *src_fqi, smb_fqi_t *dst_fqi)
335 {
336 	smb_node_t *src_fnode = src_fqi->fq_fnode;
337 	char *src_path = src_fqi->fq_path.pn_path;
338 	char *dst_path = dst_fqi->fq_path.pn_path;
339 
340 	/* We do not yet support named stream rename - ACCESS DENIED */
341 	if ((dst_path[0] == ':') ||
342 	    ((dst_path[0] == '\\') && (dst_path[1] == ':'))) {
343 		return (EACCES);
344 	}
345 
346 	/*
347 	 * If not stream rename (above) neither src or dst can be
348 	 * a named stream.
349 	 */
350 
351 	if (smb_is_stream_name(dst_path))
352 		return (EINVAL);
353 
354 	if (src_fqi->fq_fnode) {
355 		if (SMB_IS_STREAM(src_fnode))
356 			return (EINVAL);
357 	} else {
358 		if (smb_is_stream_name(src_path))
359 			return (EINVAL);
360 	}
361 
362 	return (0);
363 }
364 
365 
366 /*
367  * smb_setinfo_link
368  *
369  * Implements FileRenameInformation for SMB1 Trans2 setinfo, SMB2 setinfo.
370  * If the new filename (dst_fqi) already exists it may be overwritten
371  * if flags == 1.
372  *
373  * The passed path is a full path relative to the share root.
374  *
375  * Returns NT status codes.
376  *
377  * Similar to smb_setinfo_rename(), above.
378  */
379 uint32_t
380 smb_setinfo_link(smb_request_t *sr, smb_node_t *node, char *path, int flags)
381 {
382 	smb_fqi_t	*src_fqi = &sr->arg.dirop.fqi;
383 	smb_fqi_t	*dst_fqi = &sr->arg.dirop.dst_fqi;
384 	smb_pathname_t	*dst_pn = &dst_fqi->fq_path;
385 	uint32_t	status;
386 
387 	sr->arg.dirop.flags = flags ? SMB_RENAME_FLAG_OVERWRITE : 0;
388 	sr->arg.dirop.info_level = FileLinkInformation;
389 
390 	src_fqi->fq_sattr = SMB_SEARCH_ATTRIBUTES;
391 	src_fqi->fq_fnode = node;
392 	src_fqi->fq_dnode = node->n_dnode;
393 
394 	/* validate the dst pathname */
395 	smb_pathname_init(sr, dst_pn, path);
396 	if (!smb_pathname_validate(sr, dst_pn))
397 		return (NT_STATUS_OBJECT_NAME_INVALID);
398 
399 	status = smb_make_link(sr, src_fqi, dst_fqi);
400 	return (status);
401 }
402 
403 /*
404  * smb_make_link
405  *
406  * Creating a hard link (adding an additional name) for a file.
407  *
408  * If the source and destination are identical, we go through all
409  * the checks but we don't create a link.
410  *
411  * If the file is a symlink we create the hardlink on the target
412  * of the symlink (i.e. use SMB_FOLLOW_LINKS when looking up src).
413  * If the target of the symlink does not exist we fail with ENOENT.
414  *
415  * Returns NT status values.
416  *
417  * Similar to smb_common_rename() above.
418  */
419 uint32_t
420 smb_make_link(smb_request_t *sr, smb_fqi_t *src_fqi, smb_fqi_t *dst_fqi)
421 {
422 	smb_node_t *tnode;
423 	char *path;
424 	int rc;
425 
426 	tnode = sr->tid_tree->t_snode;
427 	path = dst_fqi->fq_path.pn_path;
428 
429 	/* Cannnot create link on named stream */
430 	if (smb_is_stream_name(src_fqi->fq_path.pn_path) ||
431 	    smb_is_stream_name(dst_fqi->fq_path.pn_path)) {
432 		return (NT_STATUS_INVALID_PARAMETER);
433 	}
434 
435 	/* The source node may already have been provided */
436 	if (src_fqi->fq_fnode) {
437 		smb_node_start_crit(src_fqi->fq_fnode, RW_READER);
438 		smb_node_ref(src_fqi->fq_fnode);
439 		smb_node_ref(src_fqi->fq_dnode);
440 	} else {
441 		/* lookup and validate src node */
442 		rc = smb_rename_lookup_src(sr);
443 		if (rc != 0)
444 			return (smb_rename_errno2status(rc));
445 	}
446 
447 	/* Not valid to create hardlink for directory */
448 	if (smb_node_is_dir(src_fqi->fq_fnode)) {
449 		smb_rename_release_src(sr);
450 		return (NT_STATUS_FILE_IS_A_DIRECTORY);
451 	}
452 
453 	/*
454 	 * Find the destination dnode and last component.
455 	 * May already be provided, i.e. when called via
456 	 * SMB1 trans2 setinfo.
457 	 */
458 	if (dst_fqi->fq_dnode) {
459 		smb_node_ref(dst_fqi->fq_dnode);
460 	} else {
461 		rc = smb_pathname_reduce(sr, sr->user_cr, path, tnode, tnode,
462 		    &dst_fqi->fq_dnode, dst_fqi->fq_last_comp);
463 		if (rc != 0) {
464 			smb_rename_release_src(sr);
465 			return (smb_rename_errno2status(rc));
466 		}
467 	}
468 
469 	/* If CI name match in same directory, we're done */
470 	if ((src_fqi->fq_dnode == dst_fqi->fq_dnode) &&
471 	    (smb_strcasecmp(src_fqi->fq_fnode->od_name,
472 	    dst_fqi->fq_last_comp, 0) == 0)) {
473 		smb_rename_release_src(sr);
474 		smb_node_release(dst_fqi->fq_dnode);
475 		return (0);
476 	}
477 
478 	if (smb_is_invalid_filename(dst_fqi->fq_last_comp)) {
479 		smb_rename_release_src(sr);
480 		smb_node_release(dst_fqi->fq_dnode);
481 		return (NT_STATUS_OBJECT_NAME_INVALID);
482 	}
483 
484 	/* Lookup the destination node. It MUST NOT exist. */
485 	rc = smb_fsop_lookup(sr, sr->user_cr, 0, tnode,
486 	    dst_fqi->fq_dnode, dst_fqi->fq_last_comp, &dst_fqi->fq_fnode);
487 	if (rc == 0) {
488 		smb_node_release(dst_fqi->fq_fnode);
489 		rc = EEXIST;
490 	}
491 	if (rc != ENOENT) {
492 		smb_rename_release_src(sr);
493 		smb_node_release(dst_fqi->fq_dnode);
494 		return (smb_rename_errno2status(rc));
495 	}
496 
497 	rc = smb_fsop_link(sr, sr->user_cr, src_fqi->fq_fnode,
498 	    dst_fqi->fq_dnode, dst_fqi->fq_last_comp);
499 
500 	if (rc == 0) {
501 		smb_node_notify_change(dst_fqi->fq_dnode,
502 		    FILE_ACTION_ADDED, dst_fqi->fq_last_comp);
503 	}
504 
505 	smb_rename_release_src(sr);
506 	smb_node_release(dst_fqi->fq_dnode);
507 	return (smb_rename_errno2status(rc));
508 }
509 
510 /*
511  * smb_rename_lookup_src
512  *
513  * Lookup the src node, checking for sharing violations and
514  * breaking any existing BATCH oplock.
515  * Populate sr->arg.dirop.fqi
516  *
517  * Upon success, the dnode and fnode will have holds and the
518  * fnode will be in a critical section. These should be
519  * released using smb_rename_release_src().
520  *
521  * Returns errno values.
522  */
523 static int
524 smb_rename_lookup_src(smb_request_t *sr)
525 {
526 	smb_node_t *src_node, *tnode;
527 	DWORD status;
528 	int rc;
529 	int count;
530 	char *path;
531 
532 	smb_fqi_t *src_fqi = &sr->arg.dirop.fqi;
533 
534 	if (smb_is_stream_name(src_fqi->fq_path.pn_path))
535 		return (EINVAL);
536 
537 	/* Lookup the source node */
538 	tnode = sr->tid_tree->t_snode;
539 	path = src_fqi->fq_path.pn_path;
540 	rc = smb_pathname_reduce(sr, sr->user_cr, path, tnode, tnode,
541 	    &src_fqi->fq_dnode, src_fqi->fq_last_comp);
542 	if (rc != 0)
543 		return (rc);
544 
545 	rc = smb_fsop_lookup(sr, sr->user_cr, 0, tnode,
546 	    src_fqi->fq_dnode, src_fqi->fq_last_comp, &src_fqi->fq_fnode);
547 	if (rc != 0) {
548 		smb_node_release(src_fqi->fq_dnode);
549 		return (rc);
550 	}
551 	src_node = src_fqi->fq_fnode;
552 
553 	rc = smb_rename_check_attr(sr, src_node, src_fqi->fq_sattr);
554 	if (rc != 0) {
555 		smb_node_release(src_fqi->fq_fnode);
556 		smb_node_release(src_fqi->fq_dnode);
557 		return (rc);
558 	}
559 
560 	/*
561 	 * Break BATCH oplock before ofile checks. If a client
562 	 * has a file open, this will force a flush or close,
563 	 * which may affect the outcome of any share checking.
564 	 */
565 	(void) smb_oplock_break(sr, src_node,
566 	    SMB_OPLOCK_BREAK_TO_LEVEL_II | SMB_OPLOCK_BREAK_BATCH);
567 
568 	/*
569 	 * Wait (a little) for the oplock break to be
570 	 * responded to by clients closing handles.
571 	 * Hold node->n_lock as reader to keep new
572 	 * ofiles from showing up after we check.
573 	 */
574 	smb_node_rdlock(src_node);
575 	for (count = 0; count <= 12; count++) {
576 		status = smb_node_rename_check(src_node);
577 		if (status != NT_STATUS_SHARING_VIOLATION)
578 			break;
579 		smb_node_unlock(src_node);
580 		delay(MSEC_TO_TICK(100));
581 		smb_node_rdlock(src_node);
582 	}
583 	if (status != NT_STATUS_SUCCESS) {
584 		smb_node_unlock(src_node);
585 		smb_node_release(src_fqi->fq_fnode);
586 		smb_node_release(src_fqi->fq_dnode);
587 		return (EPIPE); /* = ERRbadshare */
588 	}
589 
590 	/*
591 	 * Note, the combination of these two:
592 	 *	smb_node_rdlock(node);
593 	 *	nbl_start_crit(node->vp, RW_READER);
594 	 * is equivalent to this call:
595 	 *	smb_node_start_crit(node, RW_READER)
596 	 *
597 	 * Cleanup after this point should use:
598 	 *	smb_node_end_crit(src_node)
599 	 */
600 	nbl_start_crit(src_node->vp, RW_READER);
601 
602 	/*
603 	 * This checks nbl_share_conflict, nbl_lock_conflict
604 	 */
605 	status = smb_nbl_conflict(src_node, 0, UINT64_MAX, NBL_RENAME);
606 	if (status != NT_STATUS_SUCCESS) {
607 		smb_node_end_crit(src_node);
608 		smb_node_release(src_fqi->fq_fnode);
609 		smb_node_release(src_fqi->fq_dnode);
610 		if (status == NT_STATUS_SHARING_VIOLATION)
611 			return (EPIPE); /* = ERRbadshare */
612 		return (EACCES);
613 	}
614 
615 	/* NB: Caller expects holds on src_fqi fnode, dnode */
616 	return (0);
617 }
618 
619 /*
620  * smb_rename_release_src
621  */
622 static void
623 smb_rename_release_src(smb_request_t *sr)
624 {
625 	smb_fqi_t *src_fqi = &sr->arg.dirop.fqi;
626 
627 	smb_node_end_crit(src_fqi->fq_fnode);
628 	smb_node_release(src_fqi->fq_fnode);
629 	smb_node_release(src_fqi->fq_dnode);
630 }
631 
632 
633 static int
634 smb_rename_check_attr(smb_request_t *sr, smb_node_t *node, uint16_t sattr)
635 {
636 	smb_attr_t attr;
637 
638 	bzero(&attr, sizeof (attr));
639 	attr.sa_mask = SMB_AT_DOSATTR;
640 	if (smb_node_getattr(sr, node, zone_kcred(), NULL, &attr) != 0)
641 		return (EACCES);
642 
643 	if ((attr.sa_dosattr & FILE_ATTRIBUTE_HIDDEN) &&
644 	    !(SMB_SEARCH_HIDDEN(sattr)))
645 		return (ESRCH);
646 
647 	if ((attr.sa_dosattr & FILE_ATTRIBUTE_SYSTEM) &&
648 	    !(SMB_SEARCH_SYSTEM(sattr)))
649 		return (ESRCH);
650 
651 	return (0);
652 }
653 
654 /*
655  * The following values are based on observed WFWG, Windows 9x, Windows NT
656  * and Windows 2000 behaviour.
657  *
658  * ERROR_FILE_EXISTS doesn't work for Windows 98 clients.
659  *
660  * Windows 95 clients don't see the problem because the target is deleted
661  * before the rename request.
662  */
663 static uint32_t
664 smb_rename_errno2status(int errnum)
665 {
666 	static struct {
667 		int errnum;
668 		uint32_t status32;
669 	} rc_map[] = {
670 	{ EEXIST, NT_STATUS_OBJECT_NAME_COLLISION },
671 	{ EPIPE,  NT_STATUS_SHARING_VIOLATION },
672 	{ ENOENT, NT_STATUS_OBJECT_NAME_NOT_FOUND },
673 	{ ESRCH,  NT_STATUS_NO_SUCH_FILE },
674 	{ EINVAL, NT_STATUS_INVALID_PARAMETER },
675 	{ EACCES, NT_STATUS_ACCESS_DENIED },
676 	{ EISDIR, NT_STATUS_FILE_IS_A_DIRECTORY },
677 	{ EIO,    NT_STATUS_INTERNAL_ERROR }
678 	};
679 
680 	int i;
681 
682 	if (errnum == 0)
683 		return (0);
684 
685 	for (i = 0; i < sizeof (rc_map)/sizeof (rc_map[0]); ++i) {
686 		if (rc_map[i].errnum == errnum) {
687 			return (rc_map[i].status32);
688 		}
689 	}
690 
691 	return (smb_errno2status(errnum));
692 }
693