smb_rename.c revision dc20a3024900c47dd2ee44b9707e6df38f7d62a5
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <smbsrv/nterror.h>
#include <sys/synch.h>
#include <smbsrv/smb_incl.h>
#include <smbsrv/smb_fsops.h>
#include <sys/nbmlock.h>
static int smb_do_rename(struct smb_request *sr,
struct smb_fqi *src_fqi,
struct smb_fqi *dst_fqi);
/*
* smb_com_rename
*
* Rename a file. Files OldFileName must exist and NewFileName must not.
* Both pathnames must be relative to the Tid specified in the request.
* Open files may be renamed.
*
* Multiple files may be renamed in response to a single request as Rename
* File supports wildcards in the file name (last component of the path).
* NOTE: we don't support rename with wildcards.
*
* SearchAttributes indicates the attributes that the target file(s) must
* have. If SearchAttributes is zero then only normal files are renamed.
* If the system file or hidden attributes are specified then the rename
* is inclusive - both the specified type(s) of files and normal files are
* renamed. The encoding of SearchAttributes is described in section 3.10
* - File Attribute Encoding.
*/
int
smb_com_rename(struct smb_request *sr)
{
static kmutex_t mutex;
struct smb_fqi *src_fqi;
struct smb_fqi *dst_fqi;
struct smb_node *dst_node;
int rc;
if (!STYPE_ISDSK(sr->tid_tree->t_res_type)) {
smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
ERRDOS, ERROR_ACCESS_DENIED);
/* NOTREACHED */
}
src_fqi = &sr->arg.dirop.fqi;
dst_fqi = &sr->arg.dirop.dst_fqi;
if (smbsr_decode_vwv(sr, "w", &src_fqi->srch_attr) != 0) {
smbsr_decode_error(sr);
/* NOTREACHED */
}
rc = smbsr_decode_data(sr, "%SS", sr, &src_fqi->path, &dst_fqi->path);
if (rc != 0) {
smbsr_decode_error(sr);
/* NOTREACHED */
}
dst_fqi->srch_attr = 0;
mutex_enter(&mutex);
rc = smb_do_rename(sr, src_fqi, dst_fqi);
mutex_exit(&mutex);
if (rc != 0) {
/*
* ERROR_FILE_EXISTS doesn't work for Windows98 clients.
*
* Windows95 clients don't see this problem because the target
* is deleted before the rename request.
*
* The following values are based on observed WFWG, Win9x,
* NT and W2K client behaviour.
*/
if (rc == EEXIST) {
smbsr_error(sr, NT_STATUS_OBJECT_NAME_COLLISION,
ERRDOS, ERROR_ALREADY_EXISTS);
/* NOTREACHED */
}
if (rc == EPIPE) {
smbsr_error(sr, NT_STATUS_SHARING_VIOLATION,
ERRDOS, ERROR_SHARING_VIOLATION);
/* NOTREACHED */
}
smbsr_errno(sr, rc);
/* NOTREACHED */
}
if (src_fqi->dir_snode)
smb_node_release(src_fqi->dir_snode);
dst_node = dst_fqi->dir_snode;
if (dst_node) {
if (dst_node->flags & NODE_FLAGS_NOTIFY_CHANGE) {
dst_node->flags |= NODE_FLAGS_CHANGED;
smb_process_node_notify_change_queue(dst_node);
}
smb_node_release(dst_node);
}
SMB_NULL_FQI_NODES(*src_fqi);
SMB_NULL_FQI_NODES(*dst_fqi);
smbsr_encode_empty_result(sr);
return (SDRC_NORMAL_REPLY);
}
/*
* smb_do_rename
*
* Backend to smb_com_rename to ensure that the rename operation is atomic.
* This function should be called within a mutual exclusion region. If the
* source and destination are identical, we don't actually do a rename, we
* just check that the conditions are right. If the source and destination
* files differ only in case, we a case-sensitive rename. Otherwise, we do
* a full case-insensitive rename.
*
* This function should always return errno values.
*
* Upon success, the last_snode's and dir_snode's of both src_fqi and dst_fqi
* are not released in this routine but in smb_com_rename().
*/
static int
smb_do_rename(
struct smb_request *sr,
struct smb_fqi *src_fqi,
struct smb_fqi *dst_fqi)
{
struct smb_node *src_node;
char *dstname;
DWORD status;
int rc;
int count;
if ((rc = smbd_fs_query(sr, src_fqi, FQM_PATH_MUST_EXIST)) != 0) {
return (rc);
}
src_node = src_fqi->last_snode;
/*
* Break the oplock before access checks. If a client
* has a file open, this will force a flush or close,
* which may affect the outcome of any share checking.
*/
if (OPLOCKS_IN_FORCE(src_node)) {
status = smb_break_oplock(sr, src_node);
if (status != NT_STATUS_SUCCESS) {
smb_node_release(src_node);
smb_node_release(src_fqi->dir_snode);
SMB_NULL_FQI_NODES(*src_fqi);
SMB_NULL_FQI_NODES(*dst_fqi);
return (EACCES);
}
}
for (count = 0; count <= 3; count++) {
if (count) {
smb_node_end_crit(src_node);
delay(MSEC_TO_TICK(400));
}
smb_node_start_crit(src_node, RW_READER);
status = smb_node_rename_check(src_node);
if (status != NT_STATUS_SHARING_VIOLATION)
break;
}
if (status == NT_STATUS_SHARING_VIOLATION) {
smb_node_end_crit(src_node);
smb_node_release(src_node);
smb_node_release(src_fqi->dir_snode);
SMB_NULL_FQI_NODES(*src_fqi);
SMB_NULL_FQI_NODES(*dst_fqi);
return (EPIPE); /* = ERRbadshare */
}
status = smb_range_check(sr, sr->user_cr, src_node, 0, UINT64_MAX,
B_TRUE);
if (status != NT_STATUS_SUCCESS) {
smb_node_end_crit(src_node);
smb_node_release(src_node);
smb_node_release(src_fqi->dir_snode);
SMB_NULL_FQI_NODES(*src_fqi);
SMB_NULL_FQI_NODES(*dst_fqi);
return (EACCES);
}
if (utf8_strcasecmp(src_fqi->path, dst_fqi->path) == 0) {
if ((rc = smbd_fs_query(sr, dst_fqi, 0)) != 0) {
smb_node_end_crit(src_node);
smb_node_release(src_node);
smb_node_release(src_fqi->dir_snode);
SMB_NULL_FQI_NODES(*src_fqi);
SMB_NULL_FQI_NODES(*dst_fqi);
return (rc);
}
/*
* Because the fqm parameter to smbd_fs_query() was 0,
* a successful return value means that dst_fqi->last_snode
* may be NULL.
*/
if (dst_fqi->last_snode)
smb_node_release(dst_fqi->last_snode);
rc = strcmp(src_fqi->last_comp_od, dst_fqi->last_comp);
if (rc == 0) {
smb_node_end_crit(src_node);
smb_node_release(src_node);
smb_node_release(src_fqi->dir_snode);
smb_node_release(dst_fqi->dir_snode);
SMB_NULL_FQI_NODES(*src_fqi);
SMB_NULL_FQI_NODES(*dst_fqi);
return (0);
}
rc = smb_fsop_rename(sr, sr->user_cr,
src_fqi->dir_snode,
src_fqi->last_comp_od,
dst_fqi->dir_snode,
dst_fqi->last_comp);
if (rc != 0) {
smb_node_release(src_fqi->dir_snode);
smb_node_release(dst_fqi->dir_snode);
SMB_NULL_FQI_NODES(*src_fqi);
SMB_NULL_FQI_NODES(*dst_fqi);
}
smb_node_end_crit(src_node);
smb_node_release(src_node);
return (rc);
}
rc = smbd_fs_query(sr, dst_fqi, FQM_PATH_MUST_NOT_EXIST);
if (rc != 0) {
smb_node_end_crit(src_node);
smb_node_release(src_node);
smb_node_release(src_fqi->dir_snode);
SMB_NULL_FQI_NODES(*src_fqi);
SMB_NULL_FQI_NODES(*dst_fqi);
return (rc);
}
/*
* Because of FQM_PATH_MUST_NOT_EXIST and the successful return
* value, only dst_fqi->dir_snode is valid (dst_fqi->last_snode
* is NULL).
*/
/*
* Use the unmangled form of the destination name if the
* source and destination names are the same and the source
* name is mangled. (We are taking a chance here, assuming
* that this is what the user wants.)
*/
if ((smb_maybe_mangled_name(src_fqi->last_comp)) &&
(strcmp(src_fqi->last_comp, dst_fqi->last_comp) == 0)) {
dstname = src_fqi->last_comp_od;
} else {
dstname = dst_fqi->last_comp;
}
rc = smb_fsop_rename(sr, sr->user_cr,
src_fqi->dir_snode,
src_fqi->last_comp_od,
dst_fqi->dir_snode,
dstname);
if (rc != 0) {
smb_node_release(src_fqi->dir_snode);
smb_node_release(dst_fqi->dir_snode);
SMB_NULL_FQI_NODES(*src_fqi);
SMB_NULL_FQI_NODES(*dst_fqi);
}
smb_node_end_crit(src_node);
smb_node_release(src_node);
return (rc);
}