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