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