2023-11-29 18:47:11 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "UbaFileAccessor.h"
# include "UbaFile.h"
2024-03-03 20:42:41 -05:00
# include "UbaLogger.h"
2023-11-29 18:47:11 -05:00
# include "UbaStats.h"
2023-12-15 20:48:28 -05:00
# if PLATFORM_LINUX
# include <sys/sendfile.h>
# elif PLATFORM_MAC
# include <copyfile.h>
# endif
2024-03-05 12:41:52 -05:00
# define UBA_USE_WRITE_THROUGH 0
2023-11-29 18:47:11 -05:00
namespace uba
{
# if PLATFORM_WINDOWS
void GetProcessHoldingFile ( StringBufferBase & out , const tchar * fileName ) ;
HANDLE asHANDLE ( FileHandle fh ) ;
# else
int asFileDescriptor ( FileHandle fh ) ;
2023-11-29 19:41:28 -05:00
Atomic < u32 > g_tempFileCounter ;
2023-11-29 18:47:11 -05:00
# endif
2023-11-29 19:41:28 -05:00
# if PLATFORM_WINDOWS
2023-11-29 18:47:11 -05:00
bool SetDeleteOnClose ( Logger & logger , const tchar * fileName , FileHandle & handle , bool value )
{
2024-06-02 18:14:50 -04:00
ExtendedTimerScope ts ( KernelStats : : GetCurrent ( ) . setFileInfo ) ;
2023-11-29 18:47:11 -05:00
FILE_DISPOSITION_INFO info ;
info . DeleteFile = value ;
if ( ! : : SetFileInformationByHandle ( asHANDLE ( handle ) , FileDispositionInfo , & info , sizeof ( info ) ) )
return logger . Error ( TC ( " SetFileInformationByHandle (FileDispositionInfo) failed on %llu %s (%s) " ) , uintptr_t ( handle ) , fileName , LastErrorToText ( ) . data ) ;
return true ;
}
2023-11-29 19:41:28 -05:00
# endif
2023-11-29 18:47:11 -05:00
FileAccessor : : FileAccessor ( Logger & logger , const tchar * fileName )
: m_logger ( logger )
, m_fileName ( fileName )
{
}
FileAccessor : : ~ FileAccessor ( )
{
InternalClose ( false , nullptr ) ;
}
2023-11-29 19:41:28 -05:00
bool FileAccessor : : CreateWrite ( bool allowRead , u32 flagsAndAttributes , u64 fileSize , const tchar * tempPath )
2023-11-29 18:47:11 -05:00
{
UBA_ASSERT ( flagsAndAttributes ! = 0 ) ;
m_size = fileSize ;
2024-06-10 20:26:41 -04:00
m_flagsAndAttributes = flagsAndAttributes ;
2023-11-29 18:47:11 -05:00
2023-11-29 19:41:28 -05:00
const tchar * realFileName = m_fileName ;
# if !PLATFORM_WINDOWS
m_tempPath = tempPath ;
StringBuffer < > tempFile ;
if ( tempPath )
{
m_tempFileIndex = g_tempFileCounter + + ;
realFileName = tempFile . Append ( tempPath ) . Append ( " Temp_ " ) . AppendValue ( m_tempFileIndex ) . data ;
}
# endif
2024-03-05 12:41:52 -05:00
# if UBA_USE_WRITE_THROUGH
flagsAndAttributes | = FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH ;
# endif
2023-11-29 18:47:11 -05:00
u32 createDisp = CREATE_ALWAYS ;
u32 dwDesiredAccess = GENERIC_WRITE | DELETE ;
if ( allowRead )
dwDesiredAccess | = GENERIC_READ ;
u32 dwShareMode = 0 ; // FILE_SHARE_READ | FILE_SHARE_WRITE;
2024-03-15 15:28:20 -04:00
u32 retryCount = 0 ;
StringBuffer < 256 > additionalInfo ;
while ( true )
2023-11-29 18:47:11 -05:00
{
2024-03-15 15:28:20 -04:00
m_fileHandle = uba : : CreateFileW ( realFileName , dwDesiredAccess , dwShareMode , createDisp , flagsAndAttributes ) ;
if ( m_fileHandle ! = InvalidFileHandle )
{
if ( retryCount )
m_logger . Warning ( TC ( " Had to retry %u times to open file %s for write (because is was being used%s) " ) , retryCount , realFileName , additionalInfo . data ) ;
break ;
}
2023-11-29 18:47:11 -05:00
u32 lastError = GetLastError ( ) ;
2024-03-15 15:28:20 -04:00
# if PLATFORM_WINDOWS
2023-11-29 18:47:11 -05:00
if ( lastError = = ERROR_SHARING_VIOLATION )
2024-03-15 15:28:20 -04:00
{
if ( retryCount = = 0 )
GetProcessHoldingFile ( additionalInfo , m_fileName ) ;
if ( retryCount < 5 )
{
Sleep ( 1000 ) ;
+ + retryCount ;
continue ;
}
}
# endif
2023-11-29 19:41:28 -05:00
return m_logger . Error ( TC ( " ERROR opening file %s for write (%s%s) " ) , realFileName , LastErrorToText ( lastError ) . data , additionalInfo . data ) ;
2023-11-29 18:47:11 -05:00
}
2023-11-29 19:41:28 -05:00
# if PLATFORM_WINDOWS
2023-11-29 18:47:11 -05:00
if ( ! SetDeleteOnClose ( m_logger , m_fileName , m_fileHandle , true ) )
return false ;
if ( flagsAndAttributes & FILE_FLAG_OVERLAPPED )
{
if ( fileSize ! = ~ u64 ( 0 ) )
{
DWORD dwTemp ;
if ( ! : : DeviceIoControl ( asHANDLE ( m_fileHandle ) , FSCTL_SET_SPARSE , NULL , 0 , NULL , 0 , & dwTemp , NULL ) )
{
DWORD lastError = GetLastError ( ) ;
if ( lastError ! = ERROR_INVALID_FUNCTION ) // Some file systems don't support this
2023-11-29 19:41:28 -05:00
return m_logger . Error ( TC ( " Failed to make file %s sparse (%s) " ) , realFileName , LastErrorToText ( lastError ) . data ) ;
2023-11-29 18:47:11 -05:00
}
2023-11-29 19:41:28 -05:00
SetEndOfFile ( m_logger , realFileName , m_fileHandle , fileSize ) ;
2023-11-29 18:47:11 -05:00
}
( u64 & ) m_fileHandle | = OverlappedIoFlag ;
}
# endif
m_isWrite = true ;
return true ;
}
2023-11-29 19:41:28 -05:00
bool FileAccessor : : CreateMemoryWrite ( bool allowRead , u32 flagsAndAttributes , u64 size , const tchar * tempPath )
2023-11-29 18:47:11 -05:00
{
2024-03-03 20:42:41 -05:00
allowRead = true ; // It is not possible to have write only access to file mappings it seems
2023-11-29 18:47:11 -05:00
UBA_ASSERT ( flagsAndAttributes ! = 0 ) ;
2023-11-29 19:41:28 -05:00
if ( ! CreateWrite ( allowRead , flagsAndAttributes , size , tempPath ) )
2023-11-29 18:47:11 -05:00
return false ;
2024-04-24 19:46:13 -04:00
const tchar * realFileName = m_fileName ;
# if !PLATFORM_WINDOWS
StringBuffer < > tempFile ;
if ( m_tempPath )
realFileName = tempFile . Append ( m_tempPath ) . Append ( " Temp_ " ) . AppendValue ( m_tempFileIndex ) . data ;
# endif
m_mappingHandle = uba : : CreateFileMappingW ( m_fileHandle , PAGE_READWRITE , size , realFileName ) ;
2023-11-29 18:47:11 -05:00
if ( ! m_mappingHandle . IsValid ( ) )
2024-04-24 19:46:13 -04:00
return m_logger . Error ( TC ( " Failed to create memory map %s (%s) " ) , realFileName , LastErrorToText ( ) . data ) ;
2023-11-29 18:47:11 -05:00
m_data = MapViewOfFile ( m_mappingHandle , FILE_MAP_WRITE , 0 , size ) ;
if ( ! m_data )
2024-04-24 19:46:13 -04:00
return m_logger . Error ( TC ( " Failed to map view of file %s with size %llu, for write (%s) " ) , realFileName , size , LastErrorToText ( ) . data ) ;
2023-11-29 18:47:11 -05:00
return true ;
}
bool FileAccessor : : Close ( u64 * lastWriteTime )
{
return InternalClose ( true , lastWriteTime ) ;
}
bool FileAccessor : : Write ( const void * data , u64 dataLen , u64 offset )
{
2024-06-02 18:14:50 -04:00
auto & stats = KernelStats : : GetCurrent ( ) ;
ExtendedTimerScope ts ( stats . writeFile ) ;
2023-11-29 18:47:11 -05:00
if ( ! m_isWrite )
return false ;
2024-06-02 18:14:50 -04:00
stats . writeFile . bytes + = dataLen ;
2024-03-05 12:41:52 -05:00
# if UBA_USE_WRITE_THROUGH
bool useWriteThrough = true ;
2023-11-29 18:47:11 -05:00
u8 writeThroughBuffer [ 4 * 1024 ] ;
bool setFileSize = false ;
2024-03-05 12:41:52 -05:00
# endif
2023-11-29 18:47:11 -05:00
# if PLATFORM_WINDOWS
if ( ( u64 ) m_fileHandle & OverlappedIoFlag )
{
constexpr u64 BlockSize = 1024 * 1024 ;
constexpr u64 BlockCount = 256 ;
OVERLAPPED ol [ BlockCount ] ;
Event ev [ BlockCount ] ;
u64 writeLeft = dataLen ;
u8 * pos = ( u8 * ) data ;
u64 i = 0 ;
2024-02-14 01:37:37 -05:00
auto WaitAndCheckError = [ & ] ( u64 index )
{
if ( ! ev [ index ] . IsSet ( ) )
return m_logger . Error ( L " Overlapped I/O WriteFile FAILED on waiting for event! " ) ;
u32 error = u32 ( ol [ index ] . Internal ) ;
if ( error ! = ERROR_SUCCESS )
return m_logger . Error ( L " Overlapped I/O WriteFile FAILED!: %s " , LastErrorToText ( error ) . data ) ;
return true ;
} ;
2023-11-29 18:47:11 -05:00
auto eg = MakeGuard ( [ & ] ( )
{
u64 index = i % BlockCount ;
if ( i > BlockCount )
for ( u64 j = index ; j ! = BlockCount ; + + j )
2024-02-14 01:37:37 -05:00
if ( ! WaitAndCheckError ( j ) )
return false ;
2023-11-29 18:47:11 -05:00
for ( u64 j = 0 ; j ! = index ; + + j )
2024-02-14 01:37:37 -05:00
if ( ! WaitAndCheckError ( j ) )
return false ;
return true ;
2023-11-29 18:47:11 -05:00
} ) ;
while ( writeLeft )
{
u64 index = i % BlockCount ;
if ( i < BlockCount )
ev [ i ] . Create ( false ) ;
else
2024-02-14 01:37:37 -05:00
{
if ( ! WaitAndCheckError ( index ) )
return false ;
}
2023-11-29 18:47:11 -05:00
u64 toWrite = Min ( writeLeft , BlockSize ) ;
u64 toActuallyWrite = toWrite ;
2024-03-05 12:41:52 -05:00
# if UBA_USE_WRITE_THROUGH
2023-11-29 18:47:11 -05:00
if ( useWriteThrough )
{
if ( toWrite < BlockSize )
{
toActuallyWrite = ( toWrite / 4096 ) * 4096 ;
if ( ! toActuallyWrite )
{
memcpy ( writeThroughBuffer , pos , toWrite ) ;
toActuallyWrite = 4096 ;
setFileSize = true ;
}
else
toWrite = toActuallyWrite ;
}
}
2024-03-05 12:41:52 -05:00
# endif
2023-11-29 18:47:11 -05:00
ol [ index ] = { } ;
ol [ index ] . hEvent = ev [ index ] . GetHandle ( ) ;
ol [ index ] . Offset = ToLow ( offset + i * BlockSize ) ;
ol [ index ] . OffsetHigh = ToHigh ( offset + i * BlockSize ) ;
if ( ! : : WriteFile ( asHANDLE ( m_fileHandle ) , pos , u32 ( toActuallyWrite ) , NULL , ol + index ) )
{
u32 lastError = GetLastError ( ) ;
if ( lastError ! = ERROR_IO_PENDING )
2024-02-14 01:37:37 -05:00
return m_logger . Error ( L " FAILED!: %s " , LastErrorToText ( lastError ) . data ) ;
2023-11-29 18:47:11 -05:00
}
+ + i ;
pos + = toWrite ;
writeLeft - = toWrite ;
}
2024-02-14 01:37:37 -05:00
if ( ! eg . Execute ( ) )
return false ;
2024-03-05 12:41:52 -05:00
# if UBA_USE_WRITE_THROUGH
2023-11-29 18:47:11 -05:00
if ( setFileSize )
2024-03-05 12:41:52 -05:00
SetEndOfFile ( m_logger , m_fileName , m_fileHandle , dataLen ) ;
2024-02-14 01:37:37 -05:00
# endif
2023-11-29 18:47:11 -05:00
return true ;
}
# endif
u64 writeLeft = dataLen ;
u8 * pos = ( u8 * ) data ;
while ( writeLeft )
{
2023-12-11 00:32:16 -05:00
u32 toWrite = u32 ( Min ( writeLeft , 256llu * 1024 * 1024 ) ) ;
2023-11-29 18:47:11 -05:00
u32 toActuallyWrite = toWrite ;
2024-03-05 12:41:52 -05:00
# if UBA_USE_WRITE_THROUGH
2023-11-29 18:47:11 -05:00
if ( useWriteThrough )
{
toActuallyWrite = ( toWrite / 4096 ) * 4096 ;
if ( ! toActuallyWrite )
{
memcpy ( writeThroughBuffer , pos , toWrite ) ;
toActuallyWrite = 4096 ;
setFileSize = true ;
}
}
2024-03-05 12:41:52 -05:00
# endif
2023-11-29 18:47:11 -05:00
# if PLATFORM_WINDOWS
DWORD written ;
if ( ! : : WriteFile ( asHANDLE ( m_fileHandle ) , pos , toActuallyWrite , & written , NULL ) )
{
DWORD lastError = GetLastError ( ) ;
m_logger . Error ( TC ( " ERROR writing file %s writing %u bytes (%llu bytes written out of %llu) (%s) " ) , m_fileName , toWrite , ( dataLen - writeLeft ) , dataLen , LastErrorToText ( lastError ) . data ) ;
if ( lastError = = ERROR_DISK_FULL )
ExitProcess ( ERROR_DISK_FULL ) ;
return false ;
}
if ( written > toWrite )
written = toWrite ;
# else
ssize_t written = write ( asFileDescriptor ( m_fileHandle ) , pos , toActuallyWrite ) ;
if ( written = = - 1 )
{
UBA_ASSERTF ( false , TC ( " WriteFile error handling not implemented for %i (%s) " ) , errno , strerror ( errno ) ) ;
return false ;
}
# endif
writeLeft - = written ;
pos + = written ;
}
2024-03-05 12:41:52 -05:00
# if UBA_USE_WRITE_THROUGH
2023-11-29 18:47:11 -05:00
if ( setFileSize )
2024-03-05 12:41:52 -05:00
SetEndOfFile ( m_logger , m_fileName , m_fileHandle , dataLen ) ;
# endif
2023-11-29 18:47:11 -05:00
return true ;
}
bool FileAccessor : : OpenRead ( )
{
UBA_ASSERT ( false ) ;
return false ;
}
2024-04-05 19:10:53 -04:00
bool FileAccessor : : OpenMemoryRead ( u64 offset , bool errorOnFail )
2023-11-29 18:47:11 -05:00
{
if ( ! OpenFileSequentialRead ( m_logger , m_fileName , m_fileHandle ) )
2024-04-05 19:10:53 -04:00
return errorOnFail ? m_logger . Error ( TC ( " Failed to open file %s for read " ) , m_fileName ) : false ;
2023-11-29 18:47:11 -05:00
FileInformation info ;
2024-06-04 14:07:46 -04:00
if ( ! GetFileInformationByHandle ( info ) )
2023-11-29 18:47:11 -05:00
return m_logger . Error ( TC ( " GetFileInformationByHandle failed on %s " ) , m_fileName ) ;
m_size = info . size ;
# if PLATFORM_WINDOWS
if ( m_size )
2024-01-31 14:16:07 -05:00
m_mappingHandle = uba : : CreateFileMappingW ( m_fileHandle , PAGE_READONLY , m_size , m_fileName ) ;
2023-11-29 18:47:11 -05:00
else
2024-01-31 14:16:07 -05:00
m_mappingHandle = uba : : CreateFileMappingW ( InvalidFileHandle , PAGE_READONLY , 1 , m_fileName ) ;
2023-11-29 18:47:11 -05:00
if ( ! m_mappingHandle . IsValid ( ) )
return m_logger . Error ( TC ( " Failed to create mapping handle for %s (%s) " ) , m_fileName , LastErrorToText ( ) . data ) ;
# else
m_mappingHandle = { asFileDescriptor ( m_fileHandle ) } ;
if ( offset = = m_size )
return true ;
# endif
m_data = MapViewOfFile ( m_mappingHandle , FILE_MAP_READ , offset , m_size ) ;
if ( ! m_data )
return m_logger . Error ( TC ( " %s - MapViewOfFile failed (%s) " ) , m_fileName , LastErrorToText ( ) . data ) ;
return true ;
}
2024-06-04 14:07:46 -04:00
bool FileAccessor : : GetFileInformationByHandle ( FileInformation & out )
{
return uba : : GetFileInformationByHandle ( out , m_logger , m_fileName , m_fileHandle ) ;
}
2023-11-29 18:47:11 -05:00
bool FileAccessor : : InternalClose ( bool success , u64 * lastWriteTime )
{
if ( m_data )
{
2024-02-09 16:12:12 -05:00
if ( ! UnmapViewOfFile ( m_data , m_size , m_fileName ) )
2023-11-29 18:47:11 -05:00
return m_logger . Error ( TC ( " Failed to unmap memory for %s (%s) " ) , m_fileName , LastErrorToText ( ) . data ) ;
m_data = nullptr ;
}
if ( m_mappingHandle . IsValid ( ) )
{
if ( ! CloseFileMapping ( m_mappingHandle ) )
return m_logger . Error ( TC ( " Failed to close file mapping for %s (%s) " ) , m_fileName , LastErrorToText ( ) . data ) ;
m_mappingHandle = { } ;
}
if ( m_fileHandle ! = InvalidFileHandle )
{
2023-11-29 19:41:28 -05:00
const tchar * realFileName = m_fileName ;
StringBuffer < > tempFile ;
if ( m_isWrite )
2023-11-29 18:47:11 -05:00
{
2023-11-29 19:41:28 -05:00
# if !PLATFORM_WINDOWS
if ( m_tempPath )
realFileName = tempFile . Append ( m_tempPath ) . Append ( " Temp_ " ) . AppendValue ( m_tempFileIndex ) . data ;
# endif
if ( success )
2023-11-29 18:47:11 -05:00
{
2023-11-29 19:41:28 -05:00
# if PLATFORM_WINDOWS
if ( ! SetDeleteOnClose ( m_logger , realFileName , m_fileHandle , false ) )
return m_logger . Error ( TC ( " Failed to remove delete on close for file %s (%s) " ) , realFileName , LastErrorToText ( ) . data ) ;
# else
if ( m_tempPath & & rename ( realFileName , m_fileName ) = = - 1 )
2023-12-15 20:48:28 -05:00
{
if ( errno ! = EXDEV )
return m_logger . Error ( TC ( " Failed to rename temporary file %s to %s (%s) " ) , realFileName , m_fileName , strerror ( errno ) ) ;
// Need to copy, can't rename over devices
2024-06-10 20:26:41 -04:00
int targetFd = open ( m_fileName , O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC , m_flagsAndAttributes ) ;
2023-12-15 20:48:28 -05:00
auto g = MakeGuard ( [ targetFd ] ( ) { close ( targetFd ) ; } ) ;
if ( targetFd = = - 1 )
return m_logger . Error ( TC ( " Failed to create file %s for move from temporary file %s (%s) " ) , m_fileName , realFileName , strerror ( errno ) ) ;
# if PLATFORM_MAC
2024-01-31 14:16:07 -05:00
if ( fcopyfile ( asFileDescriptor ( m_fileHandle ) , targetFd , 0 , COPYFILE_ALL ) = = - 1 )
return m_logger . Error ( TC ( " Failed to do fcopyfile from temporary %s to file %s (%s) " ) , realFileName , m_fileName , strerror ( errno ) ) ;
2023-12-15 20:48:28 -05:00
# else
2024-01-31 14:16:07 -05:00
int sourceFd = asFileDescriptor ( m_fileHandle ) ;
if ( lseek ( sourceFd , 0 , SEEK_SET ) = = - 1 )
return m_logger . Error ( TC ( " Failed to do lseek to beginning for sendfile (%s) " ) , strerror ( errno ) ) ;
if ( sendfile ( targetFd , sourceFd , NULL , m_size ) ! = m_size )
2023-12-15 20:48:28 -05:00
return m_logger . Error ( TC ( " Failed to do sendfile from temporary %s to file %s (%s) " ) , realFileName , m_fileName , strerror ( errno ) ) ;
2024-01-31 14:16:07 -05:00
# endif
2023-12-15 20:48:28 -05:00
remove ( realFileName ) ; // Remove real file now when we have copied it over
}
2023-11-29 19:41:28 -05:00
# endif
if ( lastWriteTime )
{
* lastWriteTime = 0 ;
if ( ! GetFileLastWriteTime ( * lastWriteTime , m_fileHandle ) )
m_logger . Warning ( TC ( " Failed to get file time for %s (%s) " ) , realFileName , LastErrorToText ( ) . data ) ;
}
}
else
{
# if !PLATFORM_WINDOWS
if ( m_tempPath & & remove ( realFileName ) = = - 1 )
return m_logger . Error ( TC ( " Failed to remove temporary file %s (%s) " ) , realFileName , strerror ( errno ) ) ;
# endif
2023-11-29 18:47:11 -05:00
}
}
2023-11-29 19:41:28 -05:00
if ( ! CloseFile ( realFileName , m_fileHandle ) )
return m_logger . Error ( TC ( " Failed to close file %s (%s) " ) , realFileName , LastErrorToText ( ) . data ) ;
2023-11-29 18:47:11 -05:00
m_fileHandle = InvalidFileHandle ;
}
return true ;
}
}