1404 lines
42 KiB
C
1404 lines
42 KiB
C
|
/*++
|
|||
|
|
|||
|
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
|