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 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <smbsrv/nterror.h>
27 #include <sys/synch.h>
28 #include <smbsrv/smb_incl.h>
29 #include <smbsrv/smb_fsops.h>
30 #include <sys/nbmlock.h>
31 
32 static int smb_do_rename(smb_request_t *, smb_fqi_t *, smb_fqi_t *);
33 
34 /*
35  * smb_com_rename
36  *
37  * Rename a file. Files OldFileName must exist and NewFileName must not.
38  * Both pathnames must be relative to the Tid specified in the request.
39  * Open files may be renamed.
40  *
41  * Multiple files may be renamed in response to a single request as Rename
42  * File supports wildcards in the file name (last component of the path).
43  * NOTE: we don't support rename with wildcards.
44  *
45  * SearchAttributes indicates the attributes that the target file(s) must
46  * have. If SearchAttributes is zero then only normal files are renamed.
47  * If the system file or hidden attributes are specified then the rename
48  * is inclusive - both the specified type(s) of files and normal files are
49  * renamed. The encoding of SearchAttributes is described in section 3.10
50  * - File Attribute Encoding.
51  */
52 smb_sdrc_t
53 smb_pre_rename(smb_request_t *sr)
54 {
55 	smb_fqi_t *src_fqi = &sr->arg.dirop.fqi;
56 	smb_fqi_t *dst_fqi = &sr->arg.dirop.dst_fqi;
57 	int rc;
58 
59 	if ((rc = smbsr_decode_vwv(sr, "w", &src_fqi->srch_attr)) == 0) {
60 		rc = smbsr_decode_data(sr, "%SS", sr, &src_fqi->path,
61 		    &dst_fqi->path);
62 
63 		dst_fqi->srch_attr = 0;
64 	}
65 
66 	DTRACE_SMB_2(op__Rename__start, smb_request_t *, sr,
67 	    struct dirop *, &sr->arg.dirop);
68 
69 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
70 }
71 
72 void
73 smb_post_rename(smb_request_t *sr)
74 {
75 	DTRACE_SMB_1(op__Rename__done, smb_request_t *, sr);
76 }
77 
78 smb_sdrc_t
79 smb_com_rename(smb_request_t *sr)
80 {
81 	static kmutex_t mutex;
82 	smb_fqi_t *src_fqi = &sr->arg.dirop.fqi;
83 	smb_fqi_t *dst_fqi = &sr->arg.dirop.dst_fqi;
84 	struct smb_node *dst_node;
85 	int rc;
86 
87 	if (!STYPE_ISDSK(sr->tid_tree->t_res_type)) {
88 		smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
89 		    ERRDOS, ERROR_ACCESS_DENIED);
90 		return (SDRC_ERROR);
91 	}
92 
93 	mutex_enter(&mutex);
94 	rc = smb_do_rename(sr, src_fqi, dst_fqi);
95 	mutex_exit(&mutex);
96 
97 	if (rc != 0) {
98 		/*
99 		 * The following values are based on observed WFWG,
100 		 * Windows 9x, NT and Windows 2000 behaviour.
101 		 * ERROR_FILE_EXISTS doesn't work for Windows 98 clients.
102 		 * Windows 95 clients don't see the problem because the
103 		 * target is deleted before the rename request.
104 		 */
105 		switch (rc) {
106 		case EEXIST:
107 			smbsr_error(sr, NT_STATUS_OBJECT_NAME_COLLISION,
108 			    ERRDOS, ERROR_ALREADY_EXISTS);
109 			break;
110 		case EPIPE:
111 			smbsr_error(sr, NT_STATUS_SHARING_VIOLATION,
112 			    ERRDOS, ERROR_SHARING_VIOLATION);
113 			break;
114 		case ENOENT:
115 			smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND,
116 			    ERRDOS, ERROR_FILE_NOT_FOUND);
117 			break;
118 		default:
119 			smbsr_errno(sr, rc);
120 			break;
121 		}
122 
123 		return (SDRC_ERROR);
124 	}
125 
126 	if (src_fqi->dir_snode)
127 		smb_node_release(src_fqi->dir_snode);
128 
129 	dst_node = dst_fqi->dir_snode;
130 	if (dst_node) {
131 		if (dst_node->flags & NODE_FLAGS_NOTIFY_CHANGE) {
132 			dst_node->flags |= NODE_FLAGS_CHANGED;
133 			smb_process_node_notify_change_queue(dst_node);
134 		}
135 		smb_node_release(dst_node);
136 	}
137 
138 	SMB_NULL_FQI_NODES(*src_fqi);
139 	SMB_NULL_FQI_NODES(*dst_fqi);
140 
141 	rc = smbsr_encode_empty_result(sr);
142 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
143 }
144 
145 /*
146  * smb_do_rename
147  *
148  * Backend to smb_com_rename to ensure that the rename operation is atomic.
149  * This function should be called within a mutual exclusion region. If the
150  * source and destination are identical, we don't actually do a rename, we
151  * just check that the conditions are right. If the source and destination
152  * files differ only in case, we a case-sensitive rename. Otherwise, we do
153  * a full case-insensitive rename.
154  *
155  * This function should always return errno values.
156  *
157  * Upon success, the last_snode's and dir_snode's of both src_fqi and dst_fqi
158  * are not released in this routine but in smb_com_rename().
159  */
160 static int
161 smb_do_rename(
162     smb_request_t *sr,
163     smb_fqi_t *src_fqi,
164     smb_fqi_t *dst_fqi)
165 {
166 	smb_node_t *src_node;
167 	char *dstname;
168 	DWORD status;
169 	int rc;
170 	int count;
171 
172 	if ((rc = smbd_fs_query(sr, src_fqi, FQM_PATH_MUST_EXIST)) != 0) {
173 		return (rc);
174 	}
175 
176 	src_node = src_fqi->last_snode;
177 
178 	/*
179 	 * Break the oplock before access checks. If a client
180 	 * has a file open, this will force a flush or close,
181 	 * which may affect the outcome of any share checking.
182 	 */
183 	(void) smb_oplock_break(src_node, sr->session, B_FALSE);
184 
185 	for (count = 0; count <= 3; count++) {
186 		if (count) {
187 			smb_node_end_crit(src_node);
188 			delay(MSEC_TO_TICK(400));
189 		}
190 
191 		smb_node_start_crit(src_node, RW_READER);
192 
193 		status = smb_node_rename_check(src_node);
194 
195 		if (status != NT_STATUS_SHARING_VIOLATION)
196 			break;
197 	}
198 
199 	if (status == NT_STATUS_SHARING_VIOLATION) {
200 		smb_node_end_crit(src_node);
201 
202 		smb_node_release(src_node);
203 		smb_node_release(src_fqi->dir_snode);
204 
205 		SMB_NULL_FQI_NODES(*src_fqi);
206 		SMB_NULL_FQI_NODES(*dst_fqi);
207 		return (EPIPE); /* = ERRbadshare */
208 	}
209 
210 	status = smb_range_check(sr, src_node, 0, UINT64_MAX, B_TRUE);
211 
212 	if (status != NT_STATUS_SUCCESS) {
213 		smb_node_end_crit(src_node);
214 
215 		smb_node_release(src_node);
216 		smb_node_release(src_fqi->dir_snode);
217 
218 		SMB_NULL_FQI_NODES(*src_fqi);
219 		SMB_NULL_FQI_NODES(*dst_fqi);
220 		return (EACCES);
221 	}
222 
223 	if (utf8_strcasecmp(src_fqi->path, dst_fqi->path) == 0) {
224 		if ((rc = smbd_fs_query(sr, dst_fqi, 0)) != 0) {
225 			smb_node_end_crit(src_node);
226 
227 			smb_node_release(src_node);
228 			smb_node_release(src_fqi->dir_snode);
229 
230 			SMB_NULL_FQI_NODES(*src_fqi);
231 			SMB_NULL_FQI_NODES(*dst_fqi);
232 			return (rc);
233 		}
234 
235 		/*
236 		 * Because the fqm parameter to smbd_fs_query() was 0,
237 		 * a successful return value means that dst_fqi->last_snode
238 		 * may be NULL.
239 		 */
240 		if (dst_fqi->last_snode)
241 			smb_node_release(dst_fqi->last_snode);
242 
243 		rc = strcmp(src_fqi->last_comp_od, dst_fqi->last_comp);
244 		if (rc == 0) {
245 			smb_node_end_crit(src_node);
246 
247 			smb_node_release(src_node);
248 			smb_node_release(src_fqi->dir_snode);
249 			smb_node_release(dst_fqi->dir_snode);
250 
251 			SMB_NULL_FQI_NODES(*src_fqi);
252 			SMB_NULL_FQI_NODES(*dst_fqi);
253 			return (0);
254 		}
255 
256 		rc = smb_fsop_rename(sr, sr->user_cr,
257 		    src_fqi->dir_snode,
258 		    src_fqi->last_comp_od,
259 		    dst_fqi->dir_snode,
260 		    dst_fqi->last_comp);
261 
262 		if (rc != 0) {
263 			smb_node_release(src_fqi->dir_snode);
264 			smb_node_release(dst_fqi->dir_snode);
265 
266 			SMB_NULL_FQI_NODES(*src_fqi);
267 			SMB_NULL_FQI_NODES(*dst_fqi);
268 		}
269 
270 		smb_node_end_crit(src_node);
271 
272 		smb_node_release(src_node);
273 		return (rc);
274 	}
275 
276 	rc = smbd_fs_query(sr, dst_fqi, FQM_PATH_MUST_NOT_EXIST);
277 	if (rc != 0) {
278 		smb_node_end_crit(src_node);
279 
280 		smb_node_release(src_node);
281 		smb_node_release(src_fqi->dir_snode);
282 
283 		SMB_NULL_FQI_NODES(*src_fqi);
284 		SMB_NULL_FQI_NODES(*dst_fqi);
285 		return (rc);
286 	}
287 
288 	/*
289 	 * Because of FQM_PATH_MUST_NOT_EXIST and the successful return
290 	 * value, only dst_fqi->dir_snode is valid (dst_fqi->last_snode
291 	 * is NULL).
292 	 */
293 
294 	/*
295 	 * Use the unmangled form of the destination name if the
296 	 * source and destination names are the same and the source
297 	 * name is mangled.  (We are taking a chance here, assuming
298 	 * that this is what the user wants.)
299 	 */
300 
301 	if ((smb_maybe_mangled_name(src_fqi->last_comp)) &&
302 	    (strcmp(src_fqi->last_comp, dst_fqi->last_comp) == 0)) {
303 		dstname = src_fqi->last_comp_od;
304 	} else {
305 		dstname = dst_fqi->last_comp;
306 	}
307 
308 	rc = smb_fsop_rename(sr, sr->user_cr,
309 	    src_fqi->dir_snode,
310 	    src_fqi->last_comp_od,
311 	    dst_fqi->dir_snode,
312 	    dstname);
313 
314 	if (rc != 0) {
315 		smb_node_release(src_fqi->dir_snode);
316 		smb_node_release(dst_fqi->dir_snode);
317 
318 		SMB_NULL_FQI_NODES(*src_fqi);
319 		SMB_NULL_FQI_NODES(*dst_fqi);
320 	}
321 
322 	smb_node_end_crit(src_node);
323 
324 	smb_node_release(src_node);
325 
326 	return (rc);
327 }
328