windows-nt/Source/XPSP1/NT/base/fs/srv/move.c

1404 lines
42 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1989 Microsoft Corporation
Module Name:
move.c
Abstract:
This module contains the routine to rename or copy a file. This
routine is used by the routines SrvSmbRenameFile,
SrvSmbRenameFileExtended, and SrvSmbCopyFile.
Author:
David Treadwell (davidtr) 22-Jan-1990
Revision History:
--*/
#include "precomp.h"
#include "move.tmh"
#pragma hdrstop
#define BugCheckFileId SRV_FILE_MOVE
NTSTATUS
DoCopy (
IN PWORK_CONTEXT WorkContext,
IN PUNICODE_STRING Source,
IN HANDLE SourceHandle,
IN PUNICODE_STRING Target,
IN PSHARE TargetShare,
IN USHORT SmbOpenFunction,
IN PUSHORT SmbFlags
);
NTSTATUS
DoRename (
IN PWORK_CONTEXT WorkContext,
IN PUNICODE_STRING Source,
IN HANDLE SourceHandle,
IN PUNICODE_STRING Target,
IN PSHARE TargetShare,
IN USHORT SmbOpenFunction,
IN PUSHORT SmbFlags,
IN BOOLEAN FailIfTargetIsDirectory,
IN USHORT InformationLevel,
IN ULONG ClusterCount
);
#ifdef ALLOC_PRAGMA
#pragma alloc_text( PAGE, SrvMoveFile )
#pragma alloc_text( PAGE, DoCopy )
#pragma alloc_text( PAGE, DoRename )
#endif
NTSTATUS
SrvMoveFile(
IN PWORK_CONTEXT WorkContext,
IN PSHARE TargetShare,
IN USHORT SmbOpenFunction,
IN OUT PUSHORT SmbFlags,
IN USHORT SmbSearchAttributes,
IN BOOLEAN FailIfTargetIsDirectory,
IN USHORT InformationLevel,
IN ULONG ClusterCount,
IN PUNICODE_STRING Source,
IN OUT PUNICODE_STRING Target
)
/*++
Routine Description:
This routine moves a file, which may be a copy or a rename.
Arguments:
WorkContext - a pointer to the work context block for the operation. The
Session, TreeConnect, and RequestHeader fields are used.
TargetShare - a pointer to the share on which the target should
be. The RootDirectoryHandle field is used to do relative opens.
SmbOpenFunction - the "OpenFunction" field of the request SMB. This
parameter is used to determine what should be done if the target
file does or does not exist.
SmbFlags - a pointer to the "Flags" field of the request SMB. This
parameter is used to determine whether we know that the target
is supposed to be a file or directory. In addition, if this has
no information about the target, it is set to reflect whether
the target was a directory or file. This is useful when doing
multiple renames or copies as a result of wildcards--move a*.* b
might call this routine many times, and if b is a directory,
this routine will set this parameter appropiately such that if
does not have to reopen the directory for each move.
SmbSearchAttributes - the search attributes specified in the request
SMB. The attributes on the source file are checked against
these to make sure that the move can be done.
FailIfTargetIsDirectory - if TRUE and the target already exists as
a directory, fail the operation. Otherwise, rename the file
into the directory.
InformationLevel - Move/Rename/CopyOnWrite/Link/MoveCluster
ClusterCount - MoveCluster count
Source - a pointer to a string describing the name of the source file
relative to the share directory in which it is located.
Target - a pathname to the target file. This may contain directory
information--it should be the raw information from the SMB,
unadulterated by the SMB processing routine except for
canonicalization. This name may end in a directory name, in
which case the source name is used as the filename.
Return Value:
Status.
--*/
{
NTSTATUS status;
HANDLE sourceHandle;
BOOLEAN isCompatibilityOpen;
PMFCB mfcb;
PNONPAGED_MFCB nonpagedMfcb;
PLFCB lfcb;
OBJECT_ATTRIBUTES sourceObjectAttributes;
IO_STATUS_BLOCK ioStatusBlock;
ULONG sourceAccess = 0;
BOOLEAN isNtRename;
ULONG hashValue;
PSESSION session;
PSHARE sourceShare;
PSRV_LOCK mfcbLock;
PAGED_CODE( );
IF_SMB_DEBUG(FILE_CONTROL2) SrvPrint0( "SrvMoveFile entered.\n" );
//
// Set handles and pointers to NULL so we know how to clean up on
// exit.
//
sourceHandle = NULL;
isCompatibilityOpen = FALSE;
lfcb = NULL;
//mfcb = NULL; // not really necessary--SrvFindMfcb sets it correctly
//
// Set up the block pointers that will be needed.
//
session = WorkContext->Session;
sourceShare = WorkContext->TreeConnect->Share;
isNtRename = (BOOLEAN)(WorkContext->RequestHeader->Command == SMB_COM_NT_RENAME);
//
// See if we already have this file open in compatibility mode. If
// we do, and this session owns it, then we must use that open
// handle and, if this is a rename, close all the handles when we
// are done.
//
// *** SrvFindMfcb references the MFCB--remember to dereference it.
//
if ( (WorkContext->RequestHeader->Flags & SMB_FLAGS_CASE_INSENSITIVE) ||
WorkContext->Session->UsingUppercasePaths ) {
mfcb = SrvFindMfcb( Source, TRUE, &mfcbLock, &hashValue, WorkContext );
} else {
mfcb = SrvFindMfcb( Source, FALSE, &mfcbLock, &hashValue, WorkContext );
}
if ( mfcb != NULL ) {
nonpagedMfcb = mfcb->NonpagedMfcb;
ACQUIRE_LOCK( &nonpagedMfcb->Lock );
}
if( mfcbLock ) {
RELEASE_LOCK( mfcbLock );
}
if ( mfcb == NULL || !mfcb->CompatibilityOpen ) {
//
// Either the file wasn't opened by the server or it was not
// a compatibility/FCB open, so open it here.
//
// Release the open lock--we don't need it any more.
//
if ( mfcb != NULL ) {
RELEASE_LOCK( &nonpagedMfcb->Lock );
}
//
// Use DELETE access for a rename, and the appropriate copy access
// for Copy/Link/Move/MoveCluster.
//
switch (InformationLevel) {
case SMB_NT_RENAME_RENAME_FILE:
sourceAccess = DELETE;
break;
case SMB_NT_RENAME_MOVE_CLUSTER_INFO:
sourceAccess = SRV_COPY_TARGET_ACCESS & ~(WRITE_DAC | WRITE_OWNER);
break;
case SMB_NT_RENAME_SET_LINK_INFO:
case SMB_NT_RENAME_MOVE_FILE:
sourceAccess = SRV_COPY_SOURCE_ACCESS;
break;
default:
ASSERT(FALSE);
}
SrvInitializeObjectAttributes_U(
&sourceObjectAttributes,
Source,
(WorkContext->RequestHeader->Flags & SMB_FLAGS_CASE_INSENSITIVE ||
session->UsingUppercasePaths) ? OBJ_CASE_INSENSITIVE : 0L,
NULL,
NULL
);
IF_SMB_DEBUG(FILE_CONTROL2) {
SrvPrint1( "Opening source: %wZ\n",
sourceObjectAttributes.ObjectName );
}
//
// Open the source file. We allow read access for other processes.
//
INCREMENT_DEBUG_STAT( SrvDbgStatistics.TotalOpenAttempts );
INCREMENT_DEBUG_STAT( SrvDbgStatistics.TotalOpensForPathOperations );
#ifdef SLMDBG
if ( SrvIsSlmStatus( Target ) ) {
sourceAccess |= GENERIC_READ;
}
#endif
//
// !!! Currently we can't specify complete if oplocked, because
// this won't break a batch oplock. Unfortunately this also
// means that we can't timeout the open (if the oplock break
// takes too long) and fail this SMB gracefully.
//
status = SrvIoCreateFile(
WorkContext,
&sourceHandle,
sourceAccess | SYNCHRONIZE, // DesiredAccess
&sourceObjectAttributes,
&ioStatusBlock,
NULL, // AllocationSize
0, // FileAttributes
FILE_SHARE_READ, // ShareAccess
FILE_OPEN, // Disposition
FILE_SYNCHRONOUS_IO_NONALERT // CreateOptions
| FILE_OPEN_REPARSE_POINT,
NULL, // EaBuffer
0, // EaLength
CreateFileTypeNone, // File type
NULL, // ExtraCreateParameters
IO_FORCE_ACCESS_CHECK, // Options
WorkContext->TreeConnect->Share
);
if( status == STATUS_INVALID_PARAMETER ) {
status = SrvIoCreateFile(
WorkContext,
&sourceHandle,
sourceAccess | SYNCHRONIZE, // DesiredAccess
&sourceObjectAttributes,
&ioStatusBlock,
NULL, // AllocationSize
0, // FileAttributes
FILE_SHARE_READ, // ShareAccess
FILE_OPEN, // Disposition
FILE_SYNCHRONOUS_IO_NONALERT, // CreateOptions
NULL, // EaBuffer
0, // EaLength
CreateFileTypeNone, // File type
NULL, // ExtraCreateParameters
IO_FORCE_ACCESS_CHECK, // Options
WorkContext->TreeConnect->Share
);
}
if ( NT_SUCCESS(status) ) {
SRVDBG_CLAIM_HANDLE( sourceHandle, "MOV", 4, 0 );
} else if ( status == STATUS_ACCESS_DENIED ) {
//
// If the user didn't have this permission, update the statistics
// database.
//
SrvStatistics.AccessPermissionErrors++;
}
ASSERT( status != STATUS_OPLOCK_BREAK_IN_PROGRESS );
if ( !NT_SUCCESS(status) ) {
IF_DEBUG(ERRORS) {
SrvPrint1( "SrvMoveFile: SrvIoCreateFile failed (source): %X\n",
status );
}
goto exit;
}
IF_SMB_DEBUG(FILE_CONTROL2) {
SrvPrint1( "SrvIoCreateFile succeeded (source), handle = 0x%p\n",
sourceHandle );
}
SrvStatistics.TotalFilesOpened++;
} else {
//
// The file was opened by the server in compatibility mode or as
// an FCB open.
//
lfcb = CONTAINING_RECORD( mfcb->LfcbList.Blink, LFCB, MfcbListEntry );
//
// Make sure that the session which sent this request is the
// same as the one which has the file open.
//
if ( lfcb->Session != session ) {
//
// A different session has the file open in compatibility
// mode, so reject the request.
//
status = STATUS_ACCESS_DENIED;
RELEASE_LOCK( &nonpagedMfcb->Lock );
goto exit;
}
//
// Set isCompatibilityOpen so that we'll know on exit to close
// all the open instances of this file.
//
isCompatibilityOpen = TRUE;
sourceHandle = lfcb->FileHandle;
sourceAccess = lfcb->GrantedAccess;
}
//
// Make sure that the search attributes jive with the attributes
// on the file.
//
status = SrvCheckSearchAttributesForHandle( sourceHandle, SmbSearchAttributes );
if ( !NT_SUCCESS(status) ) {
goto exit;
}
//
// If the target has length 0, then it is the share root, which must
// be a directory. If the target is supposed to be a file, fail,
// otherwise indicate that the target is a directory.
//
if ( Target->Length == 0 ) {
if ( *SmbFlags & SMB_TARGET_IS_FILE ) {
status = STATUS_INVALID_PARAMETER;
goto exit;
}
*SmbFlags |= SMB_TARGET_IS_DIRECTORY;
}
//
// We now have the source file open. Call the appropriate routine
// to rename or copy the file.
//
if (InformationLevel != SMB_NT_RENAME_MOVE_FILE) {
#ifdef SLMDBG
if (InformationLevel == SMB_NT_RENAME_RENAME_FILE &&
SrvIsSlmStatus( Source ) || SrvIsSlmStatus( Target ) ) {
ULONG offset;
status = SrvValidateSlmStatus(
sourceHandle,
WorkContext,
&offset
);
if ( !NT_SUCCESS(status) ) {
SrvReportCorruptSlmStatus(
Source,
status,
offset,
SLMDBG_RENAME,
WorkContext->Session
);
SrvDisallowSlmAccess(
Source,
WorkContext->TreeConnect->Share->RootDirectoryHandle
);
status = STATUS_DISK_CORRUPT_ERROR;
goto exit;
}
}
#endif
status = DoRename(
WorkContext,
Source,
sourceHandle,
Target,
TargetShare,
SmbOpenFunction,
SmbFlags,
FailIfTargetIsDirectory,
InformationLevel,
ClusterCount
);
} else {
FILE_BASIC_INFORMATION fileBasicInformation;
//
// Check whether this is a tree copy request. If so, allow it only if
// this is a single file copy operation.
//
if ( (*SmbFlags & SMB_COPY_TREE) != 0 ) {
//
// Get the attributes on the file.
//
status = SrvQueryBasicAndStandardInformation(
sourceHandle,
NULL,
&fileBasicInformation,
NULL
);
if ( !NT_SUCCESS(status) ) {
INTERNAL_ERROR(
ERROR_LEVEL_UNEXPECTED,
"SrvMoveFile: NtQueryInformationFile (basic "
"information) returned %X",
NULL,
NULL
);
SrvLogServiceFailure( SRV_SVC_NT_QUERY_INFO_FILE, status );
goto exit;
}
if ( ( fileBasicInformation.FileAttributes &
FILE_ATTRIBUTE_DIRECTORY ) != 0 ) {
//
// Fail this copy.
//
INTERNAL_ERROR(
ERROR_LEVEL_EXPECTED,
"Tree copy not implemented.",
NULL,
NULL
);
status = STATUS_NOT_IMPLEMENTED;
goto exit;
}
}
status = DoCopy(
WorkContext,
Source,
sourceHandle,
Target,
TargetShare,
SmbOpenFunction,
SmbFlags
);
}
exit:
if ( sourceHandle != NULL && !isCompatibilityOpen ) {
SRVDBG_RELEASE_HANDLE( sourceHandle, "MOV", 9, 0 );
SrvNtClose( sourceHandle, TRUE );
} else if (isCompatibilityOpen &&
InformationLevel == SMB_NT_RENAME_RENAME_FILE) {
SrvCloseRfcbsOnLfcb( lfcb );
}
//
// If the file is open in compatibility mode, then we have held the
// MFCB lock all along. Release it now.
//
if ( isCompatibilityOpen ) {
RELEASE_LOCK( &nonpagedMfcb->Lock );
}
if ( mfcb != NULL ) {
SrvDereferenceMfcb( mfcb );
}
return status;
} // SrvMoveFile
NTSTATUS
DoCopy (
IN PWORK_CONTEXT WorkContext,
IN PUNICODE_STRING Source,
IN HANDLE SourceHandle,
IN PUNICODE_STRING Target,
IN PSHARE TargetShare,
IN USHORT SmbOpenFunction,
IN PUSHORT SmbFlags
)
/*++
Routine Description:
This routine sets up for a call to SrvCopyFile. It opens the target,
determining, if necessary, whether the target is a file or directory.
If this information is unknown, it writes it into the SmbFlags
location.
Arguments:
WorkContext - a pointer to the work context block for the operation.
The session pointer is used, and the block itself is used for
an impersonation.
Source - the name of the source file relative to its share.
SourceHandle - the handle to the source file.
Target - the name of the target file relative to its share.
TargetShare - the share of the target file. The RootDirectoryHandle
field is used for a relative rename.
SmbOpenFunction - describes whether we are allowed to overwrite an
existing file, or we should append to existing files.
SmbFlags - can tell if the target is a file, directory, or unknown.
This routine writes the true information into the location if
it is unknown.
Return Value:
Status.
--*/
{
NTSTATUS status;
IO_STATUS_BLOCK ioStatusBlock;
ULONG createDisposition;
UNICODE_STRING sourceBaseName;
BOOLEAN create;
HANDLE targetHandle = NULL;
OBJECT_ATTRIBUTES targetObjectAttributes;
UNICODE_STRING targetName;
PAGED_CODE( );
//
// Set the buffer field of targetName to NULL so that we'll know
// if we have to deallocate it at the end.
//
targetName.Buffer = NULL;
//
// Open the target file. If we know that it is a directory, generate
// the full file name. Otherwise, open the target as a file.
//
SrvInitializeObjectAttributes_U(
&targetObjectAttributes,
Target,
(WorkContext->RequestHeader->Flags & SMB_FLAGS_CASE_INSENSITIVE ||
WorkContext->Session->UsingUppercasePaths) ? OBJ_CASE_INSENSITIVE : 0L,
NULL,
NULL
);
//
// Determine the create disposition from the open function.
//
create = SmbOfunCreate( SmbOpenFunction );
if ( SmbOfunTruncate( SmbOpenFunction ) ) {
createDisposition = create ? FILE_OVERWRITE_IF : FILE_OVERWRITE;
} else if ( SmbOfunAppend( SmbOpenFunction ) ) {
createDisposition = create ? FILE_OPEN_IF : FILE_OPEN;
} else {
createDisposition = FILE_CREATE;
}
//
// If we know that the target is a directory, generate the real target
// name.
//
if ( *SmbFlags & SMB_TARGET_IS_DIRECTORY ) {
SrvGetBaseFileName( Source, &sourceBaseName );
SrvAllocateAndBuildPathName(
Target,
&sourceBaseName,
NULL,
&targetName
);
if ( targetName.Buffer == NULL ) {
status = STATUS_INSUFF_SERVER_RESOURCES;
goto copy_done;
}
targetObjectAttributes.ObjectName = &targetName;
}
IF_SMB_DEBUG(FILE_CONTROL2) {
SrvPrint1( "Opening target: %wZ\n", targetObjectAttributes.ObjectName );
}
INCREMENT_DEBUG_STAT( SrvDbgStatistics.TotalOpenAttempts );
INCREMENT_DEBUG_STAT( SrvDbgStatistics.TotalOpensForPathOperations );
//
// !!! Currently we can't specify complete if oplocked, because
// this won't break a batch oplock. Unfortunately this also
// means that we can't timeout the open (if the oplock break
// takes too long) and fail this SMB gracefully.
//
status = SrvIoCreateFile(
WorkContext,
&targetHandle,
SRV_COPY_TARGET_ACCESS | SYNCHRONIZE, // DesiredAccess
&targetObjectAttributes,
&ioStatusBlock,
NULL, // AllocationSize
0, // FileAttributes
FILE_SHARE_READ, // ShareAccess
createDisposition,
FILE_NON_DIRECTORY_FILE | // CreateOptions
FILE_OPEN_REPARSE_POINT |
FILE_SYNCHRONOUS_IO_NONALERT,
// | FILE_COMPLETE_IF_OPLOCKED,
NULL, // EaBuffer
0, // EaLength
CreateFileTypeNone, // File type
NULL, // ExtraCreateParameters
IO_FORCE_ACCESS_CHECK, // Options
TargetShare
);
if( status == STATUS_INVALID_PARAMETER ) {
status = SrvIoCreateFile(
WorkContext,
&targetHandle,
SRV_COPY_TARGET_ACCESS | SYNCHRONIZE, // DesiredAccess
&targetObjectAttributes,
&ioStatusBlock,
NULL, // AllocationSize
0, // FileAttributes
FILE_SHARE_READ, // ShareAccess
createDisposition,
FILE_NON_DIRECTORY_FILE | // CreateOptions
FILE_SYNCHRONOUS_IO_NONALERT,
// | FILE_COMPLETE_IF_OPLOCKED,
NULL, // EaBuffer
0, // EaLength
CreateFileTypeNone, // File type
NULL, // ExtraCreateParameters
IO_FORCE_ACCESS_CHECK, // Options
TargetShare
);
}
//
// If the open failed because the target is a directory, and we didn't
// know that it was supposed to be a file, then concatenate the
// source base name to the target and retry the open.
//
// !!! NOT THE CORRECT STATUS CODE. It should be something like
// STATUS_FILE_IS_DIRECTORY.
if ( status == STATUS_INVALID_PARAMETER &&
!( *SmbFlags & SMB_TARGET_IS_FILE ) &&
!( *SmbFlags & SMB_TARGET_IS_DIRECTORY ) ) {
//
// Set the flags so that future calls to this routine will do
// the right thing first time around.
//
*SmbFlags |= SMB_TARGET_IS_DIRECTORY;
SrvGetBaseFileName( Source, &sourceBaseName );
SrvAllocateAndBuildPathName(
Target,
&sourceBaseName,
NULL,
&targetName
);
if ( targetName.Buffer == NULL ) {
status = STATUS_INSUFF_SERVER_RESOURCES;
goto copy_done;
}
targetObjectAttributes.ObjectName = &targetName;
IF_SMB_DEBUG(FILE_CONTROL2) {
SrvPrint1( "Opening target: %wZ\n", targetObjectAttributes.ObjectName );
}
INCREMENT_DEBUG_STAT( SrvDbgStatistics.TotalOpenAttempts );
INCREMENT_DEBUG_STAT( SrvDbgStatistics.TotalOpensForPathOperations );
//
// !!! Currently we can't specify complete if oplocked, because
// this won't break a batch oplock. Unfortunately this also
// means that we can't timeout the open (if the oplock break
// takes too long) and fail this SMB gracefully.
//
status = SrvIoCreateFile(
WorkContext,
&targetHandle,
SRV_COPY_TARGET_ACCESS | SYNCHRONIZE, // DesiredAccess
&targetObjectAttributes,
&ioStatusBlock,
NULL, // AllocationSize
0, // FileAttributes
FILE_SHARE_READ, // ShareAccess
createDisposition,
FILE_NON_DIRECTORY_FILE | // CreateOptions
FILE_OPEN_REPARSE_POINT |
FILE_SYNCHRONOUS_IO_NONALERT,
NULL, // EaBuffer
0, // EaLength
CreateFileTypeNone, // File Type
NULL, // ExtraCreateParameters
IO_FORCE_ACCESS_CHECK, // Options
TargetShare
);
if( status == STATUS_INVALID_PARAMETER ) {
status = SrvIoCreateFile(
WorkContext,
&targetHandle,
SRV_COPY_TARGET_ACCESS | SYNCHRONIZE, // DesiredAccess
&targetObjectAttributes,
&ioStatusBlock,
NULL, // AllocationSize
0, // FileAttributes
FILE_SHARE_READ, // ShareAccess
createDisposition,
FILE_NON_DIRECTORY_FILE | // CreateOptions
FILE_SYNCHRONOUS_IO_NONALERT,
NULL, // EaBuffer
0, // EaLength
CreateFileTypeNone, // File Type
NULL, // ExtraCreateParameters
IO_FORCE_ACCESS_CHECK, // Options
TargetShare
);
}
}
if ( targetHandle != NULL ) {
SRVDBG_CLAIM_HANDLE( targetHandle, "CPY", 5, 0 );
}
//
// Is the target is a directory, and the copy move is append if exists,
// create if the file does not exist, fail the request. We must do
// this, because we have no way of knowing whether the original request
// expects us append to the file, or truncate it.
//
if ( (*SmbFlags & SMB_TARGET_IS_DIRECTORY) &&
((SmbOpenFunction & SMB_OFUN_OPEN_MASK) == SMB_OFUN_OPEN_OPEN) &&
((SmbOpenFunction & SMB_OFUN_CREATE_MASK) == SMB_OFUN_CREATE_CREATE)) {
status = STATUS_OS2_CANNOT_COPY;
goto copy_done;
}
//
// If the user didn't have this permission, update the statistics
// database.
//
if ( status == STATUS_ACCESS_DENIED ) {
SrvStatistics.AccessPermissionErrors++;
}
ASSERT( status != STATUS_OPLOCK_BREAK_IN_PROGRESS );
if ( !NT_SUCCESS(status) ) {
IF_DEBUG(ERRORS) {
SrvPrint1( "Unable to open target: %X\n", status );
}
goto copy_done;
}
SrvStatistics.TotalFilesOpened++;
//
// Copy the source to the target handle just opened.
//
status = SrvCopyFile(
SourceHandle,
targetHandle,
SmbOpenFunction,
*SmbFlags,
(ULONG)ioStatusBlock.Information // TargetOpenAction
);
if ( !NT_SUCCESS(status) ) {
IF_DEBUG(ERRORS) {
SrvPrint1( "SrvCopyFile failed, status = %X\n", status );
}
}
copy_done:
if ( targetName.Buffer != NULL ) {
FREE_HEAP( targetName.Buffer );
}
if ( targetHandle != NULL ) {
SRVDBG_RELEASE_HANDLE( targetHandle, "CPY", 10, 0 );
SrvNtClose( targetHandle, TRUE );
}
return status;
} // DoCopy
NTSTATUS
DoRename (
IN PWORK_CONTEXT WorkContext,
IN PUNICODE_STRING Source,
IN HANDLE SourceHandle,
IN PUNICODE_STRING Target,
IN PSHARE TargetShare,
IN USHORT SmbOpenFunction,
IN OUT PUSHORT SmbFlags,
IN BOOLEAN FailIfTargetIsDirectory,
IN USHORT InformationLevel,
IN ULONG ClusterCount
)
/*++
Routine Description:
This routine does the actual rename of an open file. The target may
be a file or directory, but is bound by the constraints of SmbFlags.
If SmbFlags does not indicate what the target is, then it is first
assumed to be a file; if this fails, then the rename if performed
again with the target as the original target string plus the source
base name.
*** If the source and target are on different volumes, then this
routine will fail. We could make this work by doing a copy
then delete, but this seems to be of limited usefulness and
possibly incorrect due to the fact that a big file would take
a long time, something the user would not expect.
Arguments:
WorkContext - a pointer to the work context block for this operation
used for an impersonation.
Source - the name of the source file relative to its share.
SourceHandle - the handle to the source file.
Target - the name of the target file relative to its share.
TargetShare - the share of the target file. The RootDirectoryHandle
field is used for a relative rename.
SmbOpenFunction - describes whether we are allowed to overwrite an
existing file.
SmbFlags - can tell if the target is a file, directory, or unknown.
This routine writes the true information into the location if
it is unknown.
FailIfTargetIsDirectory - if TRUE and the target already exists as
a directory, fail the operation. Otherwise, rename the file
into the directory.
InformationLevel - Rename/CopyOnWrite/Link/MoveCluster
ClusterCount - MoveCluster count
Return Value:
Status.
--*/
{
NTSTATUS status;
IO_STATUS_BLOCK ioStatusBlock;
PFILE_RENAME_INFORMATION fileRenameInformation;
ULONG renameBlockSize;
USHORT NtInformationLevel;
UNICODE_STRING sourceBaseName;
UNICODE_STRING targetBaseName;
PWCH s, es;
PAGED_CODE( );
//
// Allocate enough heap to hold a FILE_RENAME_INFORMATION block and
// the target file name. Allocate enough extra to hold the source
// name in case the target turns out to be a directory and we have
// to concatenate the source and target.
//
renameBlockSize = sizeof(FILE_RENAME_INFORMATION) + Target->Length +
Source->Length;
fileRenameInformation = ALLOCATE_HEAP_COLD(
renameBlockSize,
BlockTypeDataBuffer
);
if ( fileRenameInformation == NULL ) {
IF_DEBUG(ERRORS) {
SrvPrint0( "SrvMoveFile: Unable to allocate heap.\n" );
}
return STATUS_INSUFF_SERVER_RESOURCES;
}
//
// Get the Share root handle.
//
status = SrvGetShareRootHandle( TargetShare );
if ( !NT_SUCCESS(status) ) {
IF_DEBUG(ERRORS) {
SrvPrint1( "DoRename: SrvGetShareRootHandle failed. %X\n", status );
}
FREE_HEAP( fileRenameInformation );
return(status);
}
//
// Set up the rename block.
//
if (InformationLevel == SMB_NT_RENAME_MOVE_CLUSTER_INFO) {
((FILE_MOVE_CLUSTER_INFORMATION *)fileRenameInformation)->ClusterCount =
ClusterCount;
} else {
fileRenameInformation->ReplaceIfExists =
SmbOfunTruncate( SmbOpenFunction );
}
fileRenameInformation->RootDirectory = TargetShare->RootDirectoryHandle;
//
// If the target file has wildcards, expand name.
//
if ( FsRtlDoesNameContainWildCards( Target ) ) {
ULONG tempUlong;
UNICODE_STRING newTargetBaseName;
if (InformationLevel != SMB_NT_RENAME_RENAME_FILE) {
return(STATUS_OBJECT_PATH_SYNTAX_BAD);
}
//
// Get source and target filenames. The target filename is to be
// used as a template for wildcard expansion.
//
SrvGetBaseFileName( Source, &sourceBaseName );
SrvGetBaseFileName( Target, &targetBaseName );
tempUlong = sourceBaseName.Length + targetBaseName.Length;
newTargetBaseName.Length = (USHORT)tempUlong;
newTargetBaseName.MaximumLength = (USHORT)tempUlong;
newTargetBaseName.Buffer = ALLOCATE_NONPAGED_POOL(
tempUlong,
BlockTypeDataBuffer
);
if ( newTargetBaseName.Buffer == NULL ) {
INTERNAL_ERROR(
ERROR_LEVEL_EXPECTED,
"DoRename: Unable to allocate %d bytes from nonpaged pool.\n",
tempUlong,
NULL
);
//
// Release the share root handle if device is removable
//
SrvReleaseShareRootHandle( TargetShare );
FREE_HEAP( fileRenameInformation );
return STATUS_INSUFF_SERVER_RESOURCES;
}
//
// Get expanded filename
//
status = SrvWildcardRename(
&targetBaseName,
&sourceBaseName,
&newTargetBaseName
);
if ( !NT_SUCCESS( status ) ) {
//
// Release the share root handle if device is removable
//
SrvReleaseShareRootHandle( TargetShare );
DEALLOCATE_NONPAGED_POOL( newTargetBaseName.Buffer );
FREE_HEAP( fileRenameInformation );
return STATUS_OBJECT_NAME_INVALID;
}
//
// tempUlong is equal to the directory path without this filename
// but including the last delimeter.
//
tempUlong = Target->Length - targetBaseName.Length;
//
// Copy the directory path (including the delimeter.
//
RtlCopyMemory(
fileRenameInformation->FileName,
Target->Buffer,
tempUlong
);
s = (PWCH) ((PCHAR)fileRenameInformation->FileName + tempUlong);
//
// Copy the expanded file name
//
RtlCopyMemory(
s,
newTargetBaseName.Buffer,
newTargetBaseName.Length
);
fileRenameInformation->FileNameLength = tempUlong +
newTargetBaseName.Length;
DEALLOCATE_NONPAGED_POOL( newTargetBaseName.Buffer );
} else {
fileRenameInformation->FileNameLength = Target->Length;
RtlCopyMemory(
fileRenameInformation->FileName,
Target->Buffer,
Target->Length
);
// Check if we can do a fast rename if they are in the same path (which is usually the case)
SrvGetBaseFileName( Source, &sourceBaseName );
SrvGetBaseFileName( Target, &targetBaseName );
if ((Source->Length - sourceBaseName.Length) == (Target->Length - targetBaseName.Length)) {
ULONG i;
PWCH sptr,tptr;
i = Source->Length - sourceBaseName.Length;
i=i>>1;
sptr = &Source->Buffer[i-1];
tptr = &Target->Buffer[i-1];
while ( i > 0) {
if (*sptr-- != *tptr--) {
goto no_match;
}
i--;
}
// If the names matched, we're set for a quick rename (where the directory is not needed,
// since they are in the same path)
fileRenameInformation->RootDirectory = NULL;
fileRenameInformation->FileNameLength = targetBaseName.Length;
RtlCopyMemory(
fileRenameInformation->FileName,
targetBaseName.Buffer,
targetBaseName.Length
);
}
}
no_match:
//
// If we know that the target is a directory, then concatenate the
// source base name to the end of the target name.
//
if ( *SmbFlags & SMB_TARGET_IS_DIRECTORY ) {
SrvGetBaseFileName( Source, &sourceBaseName );
s = (PWCH)((PCHAR)fileRenameInformation->FileName +
fileRenameInformation->FileNameLength);
//
// Only add in a directory separator if the target had some path
// information. This avoids having a new name like "\NAME", which
// is illegal with a relative rename (there should be no
// leading backslash).
//
if ( Target->Length > 0 ) {
*s++ = DIRECTORY_SEPARATOR_CHAR;
}
RtlCopyMemory( s, sourceBaseName.Buffer, sourceBaseName.Length );
fileRenameInformation->FileNameLength +=
sizeof(WCHAR) + sourceBaseName.Length;
}
//
// Call NtSetInformationFile to actually rename the file.
//
IF_SMB_DEBUG(FILE_CONTROL2) {
UNICODE_STRING name;
name.Length = (USHORT)fileRenameInformation->FileNameLength;
name.Buffer = fileRenameInformation->FileName;
SrvPrint2( "Renaming %wZ to %wZ\n", Source, &name );
}
switch (InformationLevel) {
case SMB_NT_RENAME_RENAME_FILE:
NtInformationLevel = FileRenameInformation;
//
// If we are renaming a substream, we do not supply
// fileRenameInformation->RootDirectory
//
es = fileRenameInformation->FileName +
fileRenameInformation->FileNameLength / sizeof( WCHAR );
for( s = fileRenameInformation->FileName; s < es; s++ ) {
if( *s == L':' ) {
fileRenameInformation->RootDirectory = 0;
break;
}
}
break;
case SMB_NT_RENAME_MOVE_CLUSTER_INFO:
NtInformationLevel = FileMoveClusterInformation;
break;
case SMB_NT_RENAME_SET_LINK_INFO:
NtInformationLevel = FileLinkInformation;
break;
default:
ASSERT(FALSE);
}
status = IMPERSONATE( WorkContext );
if( NT_SUCCESS( status ) ) {
status = NtSetInformationFile(
SourceHandle,
&ioStatusBlock,
fileRenameInformation,
renameBlockSize,
NtInformationLevel
);
//
// If the media was changed and we can come up with a new share root handle,
// then we should retry the operation
//
if( SrvRetryDueToDismount( TargetShare, status ) ) {
fileRenameInformation->RootDirectory = TargetShare->RootDirectoryHandle;
status = NtSetInformationFile(
SourceHandle,
&ioStatusBlock,
fileRenameInformation,
renameBlockSize,
NtInformationLevel
);
}
REVERT( );
}
if ( NT_SUCCESS(status) ) {
status = ioStatusBlock.Status;
SrvRemoveCachedDirectoryName( WorkContext, Source );
}
//
// If the status was STATUS_OBJECT_NAME_COLLISION then the target
// already existed as a directory. Unless the target name was
// supposed to indicate a file or we have already tried used the
// source name, retry by concatenating the source base name to the
// target.
//
if ( status == STATUS_OBJECT_NAME_COLLISION &&
!FailIfTargetIsDirectory &&
!( *SmbFlags & SMB_TARGET_IS_FILE ) &&
!( *SmbFlags & SMB_TARGET_IS_DIRECTORY ) ) {
IF_SMB_DEBUG(FILE_CONTROL2) {
SrvPrint0( "Retrying rename with source name.\n" );
}
//
// Set the flags so that future calls to this routine will do
// the right thing first time around.
//
*SmbFlags |= SMB_TARGET_IS_DIRECTORY;
//
// Generate the new target name.
//
SrvGetBaseFileName( Source, &sourceBaseName );
s = (PWCH)((PCHAR)fileRenameInformation->FileName +
fileRenameInformation->FileNameLength);
*s++ = DIRECTORY_SEPARATOR_CHAR;
RtlCopyMemory( s, sourceBaseName.Buffer, sourceBaseName.Length );
fileRenameInformation->FileNameLength +=
sizeof(WCHAR) + sourceBaseName.Length;
//
// Do the rename again. If it fails this time, too bad.
//
// *** Note that it may fail because the source and target
// exist on different volumes. This could potentially
// cause confusion for DOS clients in the presence of
// links.
IF_SMB_DEBUG(FILE_CONTROL2) {
UNICODE_STRING name;
name.Length = (USHORT)fileRenameInformation->FileNameLength;
name.Buffer = fileRenameInformation->FileName;
SrvPrint2( "Renaming %wZ to %wZ\n", Source, &name );
}
status = IMPERSONATE( WorkContext );
if( NT_SUCCESS( status ) ) {
status = NtSetInformationFile(
SourceHandle,
&ioStatusBlock,
fileRenameInformation,
renameBlockSize,
NtInformationLevel
);
//
// If the media was changed and we can come up with a new share root handle,
// then we should retry the operation
//
if( SrvRetryDueToDismount( TargetShare, status ) ) {
fileRenameInformation->RootDirectory = TargetShare->RootDirectoryHandle;
status = NtSetInformationFile(
SourceHandle,
&ioStatusBlock,
fileRenameInformation,
renameBlockSize,
NtInformationLevel
);
}
REVERT( );
}
if ( NT_SUCCESS(status) ) {
status = ioStatusBlock.Status;
}
}
//
// Release the share root handle if device is removable
//
SrvReleaseShareRootHandle( TargetShare );
FREE_HEAP( fileRenameInformation );
if ( !NT_SUCCESS(status) ) {
IF_DEBUG(ERRORS) {
SrvPrint1( "DoRename: NtSetInformationFile failed, status = %X\n",
status );
}
}
return status;
} // DoRename