/*++ Copyright (c) 1989 Microsoft Corporation Module Name: copy.c Abstract: This module contains the routine to copy a file. Author: David Treadwell (davidtr) 24-Jan-1990 Revision History: --*/ #include "precomp.h" #pragma hdrstop #define BugCheckFileId SRV_FILE_COPY #ifdef ALLOC_PRAGMA #pragma alloc_text( PAGE, SrvCopyFile ) #endif #define EOF 0x1A // Control-Z == end of file NTSTATUS SrvCopyFile ( IN HANDLE SourceHandle, IN HANDLE TargetHandle, IN USHORT SmbOpenFunction, IN USHORT SmbFlags, IN ULONG ActionTaken ) /*++ Routine Description: This routine copies or appends from the source file to the target file. It does the following: read sources EAs, attributes, size create/open target using source's info read data from source and write it to target Arguments: SourceHandle - handle to source file opened with SRV_COPY_SOURCE_ACCESS for synchronous access. TargetHandle - handle to target file opened with SRV_COPY_SOURCE_ACCESS for synchronous access. SmbOpenFunction - used to determine whether the source should be appended to the end of the target. ActionTaken - Information field of IO status block from the NtCreateFile where the target was opened. This is used to determine whether the target should be deleted if an error occurs. Return Value: NTSTATUS - STATUS_SUCCESS or error. --*/ { NTSTATUS status; NTSTATUS readStatus = STATUS_SUCCESS; NTSTATUS writeStatus = STATUS_SUCCESS; IO_STATUS_BLOCK ioStatusBlock; FILE_EA_INFORMATION eaInfo; SRV_NETWORK_OPEN_INFORMATION sourceNetworkOpenInformation; SRV_NETWORK_OPEN_INFORMATION targetNetworkOpenInformation; FILE_POSITION_INFORMATION positionInfo; FILE_ALLOCATION_INFORMATION allocationInfo; LARGE_INTEGER fileOffset; BOOLEAN append; BOOLEAN sourceIsAscii; BOOLEAN targetIsAscii; PCHAR ioBuffer; ULONG ioBufferSize; ULONG offset = 0; ULONG bytesRead; BOOLEAN eofFound = FALSE; CHAR lastByte; PAGED_CODE( ); // // Find out if we are supposed to append to the target file or // overwrite it, and whether this is a binary or ASCII copy for // the source and target. In a binary copy for the source, we stop // the first time we see EOF (control-Z). In a binary copy for the // target, we must make sure that there is exactly one EOF in the // file and that this is the last character of the file. // append = SmbOfunAppend( SmbOpenFunction ); sourceIsAscii = (BOOLEAN)((SmbFlags & SMB_COPY_SOURCE_ASCII) != 0); targetIsAscii = (BOOLEAN)((SmbFlags & SMB_COPY_TARGET_ASCII) != 0); // // Find the size of the EAs on the source. // status = NtQueryInformationFile( SourceHandle, &ioStatusBlock, &eaInfo, sizeof(eaInfo), FileEaInformation ); if ( !NT_SUCCESS(status) ) { INTERNAL_ERROR( ERROR_LEVEL_UNEXPECTED, "SrvCopyFile: NtQueryInformationFile (source EA size) failed: %X", status, NULL ); SrvLogServiceFailure( SRV_SVC_NT_QUERY_INFO_FILE, status ); return status; } // // If the source file has EAs, get them and write them to the target // file. // if ( eaInfo.EaSize > 0 ) { PCHAR eaBuffer; ULONG eaBufferSize; // // Allocate a buffer large enough to hold the EAs. // // // HACKHACK: eaInfo.EaSize is the size needed by OS/2. For NT, // the system has no way of telling us how big a buffer we need. // According to BrianAn, this should not be bigger than twice // what OS/2 needs. // eaBufferSize = eaInfo.EaSize * EA_SIZE_FUDGE_FACTOR; eaBuffer = ALLOCATE_NONPAGED_POOL( eaBufferSize, BlockTypeDataBuffer ); if ( eaBuffer == NULL ) { INTERNAL_ERROR( ERROR_LEVEL_EXPECTED, "SrvCopyFile: Unable to allocate %d bytes nonpaged pool", eaBufferSize, NULL ); return STATUS_INSUFF_SERVER_RESOURCES; } status = SrvIssueQueryEaRequest( SourceHandle, eaBuffer, eaBufferSize, NULL, 0L, FALSE, NULL ); if ( NT_SUCCESS(status) ) { status = ioStatusBlock.Status; } if ( !NT_SUCCESS(status) ) { INTERNAL_ERROR( ERROR_LEVEL_UNEXPECTED, "SrvCopyFile: SrvIssueQueryEaRequest failed: %X", status, NULL ); SrvLogServiceFailure( SRV_SVC_NT_QUERY_EAS, status ); DEALLOCATE_NONPAGED_POOL( eaBuffer ); return status; } status = SrvIssueSetEaRequest( TargetHandle, eaBuffer, eaBufferSize, NULL ); if ( NT_SUCCESS(status) ) { status = ioStatusBlock.Status; } if ( status == STATUS_EAS_NOT_SUPPORTED || status == STATUS_NOT_IMPLEMENTED ) { if ( SrvAreEasNeeded( (PFILE_FULL_EA_INFORMATION)eaBuffer ) ) { DEALLOCATE_NONPAGED_POOL( eaBuffer ); return STATUS_EAS_NOT_SUPPORTED; } status = STATUS_SUCCESS; } DEALLOCATE_NONPAGED_POOL( eaBuffer ); if ( !NT_SUCCESS(status) ) { INTERNAL_ERROR( ERROR_LEVEL_UNEXPECTED, "SrvCopyFile: SrvIssueSetEaRequest failed: %X", status, NULL ); SrvLogServiceFailure( SRV_SVC_NT_SET_EAS, status ); return status; } } // // Get the various attributes of the source file--size, times, etc. // These are used later on to set attributes of the target file. // status = SrvQueryNetworkOpenInformation( SourceHandle, NULL, &sourceNetworkOpenInformation, FALSE ); if ( !NT_SUCCESS(status) ) { INTERNAL_ERROR( ERROR_LEVEL_UNEXPECTED, "SrvCopyFile: NtQueryInformationFile " "returned %X", status, NULL ); SrvLogServiceFailure( SRV_SVC_NT_QUERY_INFO_FILE, status ); return status; } // // If target was opened and we're in append mode, save the target's // original size and time and set target file pointer to the end of // the file. // if ( append ) { status = SrvQueryNetworkOpenInformation( TargetHandle, NULL, &targetNetworkOpenInformation, FALSE ); if ( !NT_SUCCESS(status) ) { INTERNAL_ERROR( ERROR_LEVEL_UNEXPECTED, "SrvCopyFile: NtQueryInformationFile " "for target returned %X", status, NULL ); SrvLogServiceFailure( SRV_SVC_NT_QUERY_INFO_FILE, status ); return status; } // // If the target is in ASCII mode, then see if the last character // of the target file is EOF (^Z). If so, then set EndOfFile // such that this character will be overwritten. // if ( targetIsAscii && (targetNetworkOpenInformation.EndOfFile.QuadPart > 0) ) { LARGE_INTEGER fileOffset; fileOffset.QuadPart = targetNetworkOpenInformation.EndOfFile.QuadPart - 1; status = NtReadFile( TargetHandle, NULL, NULL, NULL, &ioStatusBlock, &lastByte, sizeof(lastByte), &fileOffset, NULL ); if ( !NT_SUCCESS(status) ) { INTERNAL_ERROR( ERROR_LEVEL_UNEXPECTED, "SrvCopyFile: NtReadFile for target last byte" "returned %X", status, NULL ); SrvLogServiceFailure( SRV_SVC_NT_READ_FILE, status ); return status; } if ( lastByte == EOF ) { targetNetworkOpenInformation.EndOfFile = fileOffset; } } positionInfo.CurrentByteOffset = targetNetworkOpenInformation.EndOfFile; status = NtSetInformationFile( TargetHandle, &ioStatusBlock, &positionInfo, sizeof(positionInfo), FilePositionInformation ); if ( !NT_SUCCESS(status) ) { INTERNAL_ERROR( ERROR_LEVEL_UNEXPECTED, "SrvCopyFile: NtSetInformationFile(position information)" "for target returned %X", status, NULL ); SrvLogServiceFailure( SRV_SVC_NT_SET_INFO_FILE, status ); return status; } } // // Set the new size of the output file. Doing this avoids forcing // the file system to automatically extend the file bit by bit. // if ( append ) { allocationInfo.AllocationSize.QuadPart = targetNetworkOpenInformation.EndOfFile.QuadPart + sourceNetworkOpenInformation.EndOfFile.QuadPart; } else { allocationInfo.AllocationSize = sourceNetworkOpenInformation.EndOfFile; } if ( 0 ) { KdPrint(( "SrvCopyFile: Setting allocation size of target to " "%ld (0x%lx) bytes\n", allocationInfo.AllocationSize.LowPart, allocationInfo.AllocationSize.LowPart )); KdPrint(( " %ld (0x%lx) blocks + %ld (0x%lx) bytes\n", allocationInfo.AllocationSize.LowPart / 512, allocationInfo.AllocationSize.LowPart / 512, allocationInfo.AllocationSize.LowPart % 512, allocationInfo.AllocationSize.LowPart % 512 )); } status = NtSetInformationFile( TargetHandle, &ioStatusBlock, &allocationInfo, sizeof(allocationInfo), FileAllocationInformation ); if ( !NT_SUCCESS(status) ) { INTERNAL_ERROR( ERROR_LEVEL_UNEXPECTED, "SrvCopyFile: NtSetInformationFile(allocation information)" "for target returned %X", status, NULL ); SrvLogServiceFailure( SRV_SVC_NT_SET_INFO_FILE, status ); return status; } // // Allocate a buffer from server heap to use for the data copy. // ioBufferSize = 4096; ioBuffer = ALLOCATE_HEAP( ioBufferSize, BlockTypeDataBuffer ); if ( ioBuffer == NULL ) { INTERNAL_ERROR( ERROR_LEVEL_EXPECTED, "SrvCopyFile: Unable to allocate %d bytes from heap.", ioBufferSize, NULL ); return STATUS_INSUFF_SERVER_RESOURCES; } // // Copy data--read from source, write to target. Do this until // all the data is written or an error occurs. // fileOffset.QuadPart = (LONG)FILE_USE_FILE_POINTER_POSITION; while ( !eofFound ) { if ( 0 ) { KdPrint(( "SrvCopyFile: reading %ld (0x%lx) bytes at " "offset %ld (0x%lx)\n", ioBufferSize, ioBufferSize, offset, offset )); } readStatus = NtReadFile( SourceHandle, NULL, // Event NULL, // ApcRoutine NULL, // ApcContext &ioStatusBlock, ioBuffer, ioBufferSize, &fileOffset, NULL // Key ); if ( !NT_SUCCESS(readStatus) && readStatus != STATUS_END_OF_FILE ) { INTERNAL_ERROR( ERROR_LEVEL_UNEXPECTED, "SrvCopyFile: NtReadFile returned %X", readStatus, NULL ); FREE_HEAP( ioBuffer ); SrvLogServiceFailure( SRV_SVC_NT_READ_FILE, readStatus ); return readStatus; } IF_SMB_DEBUG(FILE_CONTROL2) { KdPrint(( "NtReadFile: Read %u bytes from source file\n", ioStatusBlock.Information )); } if ( ioStatusBlock.Information == 0 || readStatus == STATUS_END_OF_FILE ) { break; } bytesRead = ioStatusBlock.Information; if ( 0 ) { IO_STATUS_BLOCK iosb; status = NtQueryInformationFile( SourceHandle, &iosb, &positionInfo, sizeof(positionInfo), FilePositionInformation ); if ( !NT_SUCCESS( status ) ) { INTERNAL_ERROR( ERROR_LEVEL_UNEXPECTED, "SrvCopyFile: NtQueryInformationFile returned %X", status, NULL ); FREE_HEAP( ioBuffer ); SrvLogServiceFailure( SRV_SVC_NT_QUERY_INFO_FILE, status ); return status; } if ( positionInfo.CurrentByteOffset.LowPart != offset + bytesRead ) { KdPrint(( "SrvCopyFile: SOURCE FILE POSITION NOT PROPERLY " "UPDATED!!!\n" )); KdPrint(( " expected %ld + %ld = %ld (0x%lx); ", offset, bytesRead, offset + bytesRead, offset + bytesRead )); KdPrint(( "got %ld (0x%lx)\n", positionInfo.CurrentByteOffset.LowPart, positionInfo.CurrentByteOffset.LowPart )); } KdPrint(( "SrvCopyFile: writing %ld (0x%lx) bytes at " "offset %ld (0x%lx)\n", ioStatusBlock.Information, ioStatusBlock.Information, offset, offset )); } // // If the source file is in ASCII mode, then search for EOF in the // buffer. We copy until we hit the first EOF, at which point // we quit. // if ( sourceIsAscii ) { ULONG i; for ( i = 0; i < bytesRead; i++ ) { if ( ((PCHAR)ioBuffer)[i] == EOF ) { bytesRead = i + 1; eofFound = TRUE; break; } } } // // Save the last byte read. This is useful to make sure that // there is an EOF character if the target file is ASCII. // lastByte = ((PCHAR)ioBuffer)[bytesRead-1]; writeStatus = NtWriteFile( TargetHandle, NULL, // Event NULL, // ApcRoutine NULL, // ApcContext &ioStatusBlock, ioBuffer, bytesRead, &fileOffset, NULL // Key ); if ( !NT_SUCCESS(writeStatus) ) { INTERNAL_ERROR( ERROR_LEVEL_UNEXPECTED, "SrvCopyFile: NtWriteFile returned %X", writeStatus, NULL ); FREE_HEAP( ioBuffer ); SrvLogServiceFailure( SRV_SVC_NT_WRITE_FILE, writeStatus ); return writeStatus; } IF_SMB_DEBUG(FILE_CONTROL2) { KdPrint(( "NtWriteFile: wrote %u bytes to target file\n", ioStatusBlock.Information )); } if ( 0 ) { IO_STATUS_BLOCK iosb; if ( ioStatusBlock.Information != bytesRead ) { KdPrint(( "SrvCopyFile: WRITE COUNT MISMATCH!!!\n" )); KdPrint(( " Bytes read: %ld (0x%lx); " "Bytes written: %ld (0x%lx)\n", bytesRead, bytesRead, ioStatusBlock.Information, ioStatusBlock.Information )); } status = NtQueryInformationFile( SourceHandle, &iosb, &positionInfo, sizeof(positionInfo), FilePositionInformation ); if ( !NT_SUCCESS( status ) ) { INTERNAL_ERROR( ERROR_LEVEL_UNEXPECTED, "SrvCopyFile: NtQueryInformationFile returned %X", status, NULL ); FREE_HEAP( ioBuffer ); SrvLogServiceFailure( SRV_SVC_NT_QUERY_INFO_FILE, status ); return status; } if ( positionInfo.CurrentByteOffset.LowPart != offset + ioStatusBlock.Information ) { KdPrint(( "SrvCopyFile: TARGET FILE POSITION NOT PROPERLY " "UPDATED!!!\n" )); KdPrint(( " expected %ld + %ld = %ld (0x%lx); ", offset, ioStatusBlock.Information, offset + ioStatusBlock.Information, offset + ioStatusBlock.Information )); KdPrint(( "got %ld (0x%lx)\n", positionInfo.CurrentByteOffset.LowPart, positionInfo.CurrentByteOffset.LowPart )); } } offset += bytesRead; } FREE_HEAP( ioBuffer ); // // If target was created or replaced, set its time to that of the source. // if ( ActionTaken == FILE_CREATED || ActionTaken == FILE_SUPERSEDED ) { FILE_BASIC_INFORMATION basicInfo; basicInfo.CreationTime = sourceNetworkOpenInformation.CreationTime; basicInfo.LastAccessTime = sourceNetworkOpenInformation.LastAccessTime; basicInfo.LastWriteTime = sourceNetworkOpenInformation.LastWriteTime; basicInfo.ChangeTime = sourceNetworkOpenInformation.ChangeTime; basicInfo.FileAttributes = sourceNetworkOpenInformation.FileAttributes; status = NtSetInformationFile( TargetHandle, &ioStatusBlock, &basicInfo, sizeof(basicInfo), FileBasicInformation ); if ( !NT_SUCCESS(status) ) { INTERNAL_ERROR( ERROR_LEVEL_UNEXPECTED, "SrvCopyFile: NtSetInformationFile(basic information) for" "target returned %X", status, NULL ); SrvLogServiceFailure( SRV_SVC_NT_SET_INFO_FILE, status ); return status; } } // // If the target is ASCII and the last byte was not an EOF, then // put on an EOF character. // if ( targetIsAscii && lastByte != EOF ) { lastByte = EOF; status = NtWriteFile( TargetHandle, NULL, NULL, NULL, &ioStatusBlock, &lastByte, sizeof(lastByte), NULL, NULL ); if ( !NT_SUCCESS(status) ) { INTERNAL_ERROR( ERROR_LEVEL_UNEXPECTED, "SrvCopyFile: NtWriteFile returned %X", status, NULL ); SrvLogServiceFailure( SRV_SVC_NT_WRITE_FILE, status ); return status; } } return STATUS_SUCCESS; } // SrvCopyFile