/*++ 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