2021-04-30 08:14:54 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
2021-08-31 06:28:41 -04:00
# include "VirtualizationFileBackend.h"
2021-04-30 08:14:54 -04:00
# include "HAL/FileManager.h"
2021-09-17 06:02:48 -04:00
# include "HAL/PlatformProcess.h"
2021-04-30 08:14:54 -04:00
# include "Misc/Parse.h"
# include "Misc/Paths.h"
# include "Virtualization/VirtualizationManager.h"
2021-05-03 05:01:37 -04:00
# include "VirtualizationUtilities.h"
2021-04-30 08:14:54 -04:00
namespace UE : : Virtualization
{
2021-07-22 05:43:20 -04:00
/**
* Fill in the given string builder with the human readable message of the current system
* code , followed by the code value itself .
* In the system value is currently 0 , then we assume that it was cleared before this was
* able to be called and write that the error is unknown instead of assuming that the
* operation was a success .
*/
void GetFormattedSystemError ( FStringBuilderBase & SystemErrorMessage )
{
SystemErrorMessage . Reset ( ) ;
const uint32 SystemError = FPlatformMisc : : GetLastError ( ) ;
// If we have a system error we can give a more informative error message but don't output it if the error is zero as
// this can lead to very confusing error messages.
if ( SystemError ! = 0 )
{
TCHAR SystemErrorMsg [ MAX_SPRINTF ] = { 0 } ;
FPlatformMisc : : GetSystemErrorMessage ( SystemErrorMsg , sizeof ( SystemErrorMsg ) , SystemError ) ;
SystemErrorMessage . Appendf ( TEXT ( " '%s' (%d) " ) , SystemErrorMsg , SystemError ) ;
}
else
{
SystemErrorMessage < < TEXT ( " 'unknown reason' (0) " ) ;
}
}
2021-04-30 08:14:54 -04:00
2021-08-31 06:28:41 -04:00
FFileSystemBackend : : FFileSystemBackend ( FStringView ConfigName )
: IVirtualizationBackend ( EOperations : : Both )
2021-04-30 08:14:54 -04:00
{
2021-09-17 06:02:48 -04:00
DebugName = WriteToString < 256 > ( TEXT ( " FFileSystemBackend - " ) , ConfigName ) . ToString ( ) ;
2021-08-31 06:28:41 -04:00
}
bool FFileSystemBackend : : Initialize ( const FString & ConfigEntry )
{
if ( ! FParse : : Value ( * ConfigEntry , TEXT ( " Path= " ) , RootDirectory ) )
2021-04-30 08:14:54 -04:00
{
2021-08-31 06:28:41 -04:00
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] 'Path=' not found in the config file " ) , * GetDebugString ( ) ) ;
return false ;
2021-04-30 08:14:54 -04:00
}
2021-08-31 06:28:41 -04:00
FPaths : : NormalizeDirectoryName ( RootDirectory ) ;
2021-04-30 08:14:54 -04:00
2021-08-31 06:28:41 -04:00
if ( RootDirectory . IsEmpty ( ) )
2021-04-30 08:14:54 -04:00
{
2021-08-31 06:28:41 -04:00
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Config file entry 'Path=' was empty " ) , * GetDebugString ( ) ) ;
return false ;
2021-04-30 08:14:54 -04:00
}
2021-08-31 06:28:41 -04:00
// TODO: Validate that the given path is usable?
2021-09-17 06:02:48 -04:00
int32 RetryCountIniFile = INDEX_NONE ;
if ( FParse : : Value ( * ConfigEntry , TEXT ( " RetryCount= " ) , RetryCountIniFile ) )
{
RetryCount = RetryCountIniFile ;
}
int32 RetryWaitTimeMSIniFile = INDEX_NONE ;
if ( FParse : : Value ( * ConfigEntry , TEXT ( " RetryWaitTime= " ) , RetryWaitTimeMSIniFile ) )
{
RetryWaitTimeMS = RetryWaitTimeMSIniFile ;
}
// Now log a summary of the backend settings to make issues easier to diagnose
2021-08-31 06:28:41 -04:00
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Using path: '%s' " ) , * GetDebugString ( ) , * RootDirectory ) ;
2021-09-17 06:02:48 -04:00
UE_LOG ( LogVirtualization , Log , TEXT ( " [%s] Will retry failed read attempts %d times with a gap of %dms betwen them " ) , * GetDebugString ( ) , RetryCount , RetryWaitTimeMS ) ;
2021-08-31 06:28:41 -04:00
return true ;
}
EPushResult FFileSystemBackend : : PushData ( const FPayloadId & Id , const FCompressedBuffer & Payload )
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FFileSystemBackend : : PushData ) ;
if ( DoesExist ( Id ) )
2021-04-30 08:14:54 -04:00
{
2021-08-31 06:28:41 -04:00
UE_LOG ( LogVirtualization , Verbose , TEXT ( " [%s] Already has a copy of the payload '%s'. " ) , * GetDebugString ( ) , * Id . ToString ( ) ) ;
return EPushResult : : PayloadAlreadyExisted ;
2021-04-30 08:14:54 -04:00
}
2021-09-17 06:02:48 -04:00
// Make sure to log any disk write failures to the user, even if this backend will often be optional as they are
// not expected and could indicate bigger problems.
//
// First we will write out the payload to a temp file, after which we will move it to the correct storage location
// this helps reduce the chance of leaving corrupted data on disk in the case of a power failure etc.
const FString TempFilePath = FPaths : : CreateTempFilename ( * FPaths : : ProjectSavedDir ( ) , TEXT ( " miragepayload " ) ) ;
TUniquePtr < FArchive > FileAr ( IFileManager : : Get ( ) . CreateFileWriter ( * TempFilePath ) ) ;
2021-08-31 06:28:41 -04:00
if ( FileAr = = nullptr )
2021-04-30 08:14:54 -04:00
{
2021-08-31 06:28:41 -04:00
TStringBuilder < MAX_SPRINTF > SystemErrorMsg ;
GetFormattedSystemError ( SystemErrorMsg ) ;
2021-04-30 08:14:54 -04:00
2021-08-31 06:28:41 -04:00
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to write payload '%s' to '%s' due to system error: %s " ) ,
2021-09-17 06:02:48 -04:00
* GetDebugString ( ) ,
* Id . ToString ( ) ,
* TempFilePath ,
SystemErrorMsg . ToString ( ) ) ;
2021-04-30 08:14:54 -04:00
2021-08-31 06:28:41 -04:00
return EPushResult : : Failed ;
2021-04-30 08:14:54 -04:00
}
2021-08-31 06:28:41 -04:00
for ( const FSharedBuffer & Buffer : Payload . GetCompressed ( ) . GetSegments ( ) )
2021-04-30 08:14:54 -04:00
{
2021-08-31 06:28:41 -04:00
// Const cast because FArchive requires a non-const pointer!
FileAr - > Serialize ( const_cast < void * > ( Buffer . GetData ( ) ) , static_cast < int64 > ( Buffer . GetSize ( ) ) ) ;
2021-04-30 08:14:54 -04:00
}
2021-09-17 06:02:48 -04:00
if ( ! FileAr - > Close ( ) )
2021-04-30 08:14:54 -04:00
{
2021-09-17 06:02:48 -04:00
TStringBuilder < MAX_SPRINTF > SystemErrorMsg ;
GetFormattedSystemError ( SystemErrorMsg ) ;
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to write payload '%s' contents to '%s' due to system error: %s " ) ,
* GetDebugString ( ) ,
* Id . ToString ( ) ,
* TempFilePath ,
SystemErrorMsg . ToString ( ) ) ;
IFileManager : : Get ( ) . Delete ( * TempFilePath , true , false , true ) ; // Clean up the temp file if it is still around but do not failure cases to the user
2021-08-31 06:28:41 -04:00
return EPushResult : : Failed ;
2021-09-17 06:02:48 -04:00
}
TStringBuilder < 512 > FilePath ;
CreateFilePath ( Id , FilePath ) ;
if ( ! IFileManager : : Get ( ) . Move ( FilePath . ToString ( ) , * TempFilePath ) )
{
// Store the error message in case we need to display it
TStringBuilder < MAX_SPRINTF > SystemErrorMsg ;
GetFormattedSystemError ( SystemErrorMsg ) ;
IFileManager : : Get ( ) . Delete ( * TempFilePath , true , false , true ) ; // Clean up the temp file if it is still around but do not failure cases to the user
// Check if another thread or process was writing out the payload at the same time, if so we
// don't need to give an error message.
if ( DoesExist ( Id ) )
{
UE_LOG ( LogVirtualization , Verbose , TEXT ( " [%s] Already has a copy of the payload '%s'. " ) , * GetDebugString ( ) , * Id . ToString ( ) ) ;
return EPushResult : : PayloadAlreadyExisted ;
}
else
{
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to move payload '%s' to it's final location '%s' due to system error: %s " ) ,
* GetDebugString ( ) ,
* Id . ToString ( ) ,
* FilePath ,
SystemErrorMsg . ToString ( ) ) ;
return EPushResult : : Failed ;
}
}
return EPushResult : : Success ;
2021-08-31 06:28:41 -04:00
}
FCompressedBuffer FFileSystemBackend : : PullData ( const FPayloadId & Id )
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FFileSystemBackend : : PullData ) ;
TStringBuilder < 512 > FilePath ;
CreateFilePath ( Id , FilePath ) ;
// TODO: Should we allow the error severity to be configured via ini or just not report this case at all?
if ( ! IFileManager : : Get ( ) . FileExists ( FilePath . ToString ( ) ) )
{
UE_LOG ( LogVirtualization , Verbose , TEXT ( " [%s] Does not contain the payload '%s' " ) , * GetDebugString ( ) , * Id . ToString ( ) ) ;
return FCompressedBuffer ( ) ;
2021-04-30 08:14:54 -04:00
}
2021-09-17 06:02:48 -04:00
TUniquePtr < FArchive > FileAr = OpenFileForReading ( FilePath . ToString ( ) ) ;
2021-08-31 06:28:41 -04:00
if ( FileAr = = nullptr )
2021-04-30 08:14:54 -04:00
{
2021-08-31 06:28:41 -04:00
TStringBuilder < MAX_SPRINTF > SystemErrorMsg ;
GetFormattedSystemError ( SystemErrorMsg ) ;
2021-05-03 02:47:29 -04:00
2021-09-17 06:02:48 -04:00
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to load payload '%s' from file '%s' due to system error: %s " ) ,
* GetDebugString ( ) ,
* Id . ToString ( ) ,
FilePath . ToString ( ) ,
SystemErrorMsg . ToString ( ) ) ;
2021-08-31 06:28:41 -04:00
return FCompressedBuffer ( ) ;
2021-04-30 08:14:54 -04:00
}
2021-08-31 06:28:41 -04:00
return FCompressedBuffer : : FromCompressed ( * FileAr ) ;
}
2021-05-03 02:47:29 -04:00
2021-08-31 06:28:41 -04:00
FString FFileSystemBackend : : GetDebugString ( ) const
{
2021-09-17 06:02:48 -04:00
return DebugName ;
2021-08-31 06:28:41 -04:00
}
bool FFileSystemBackend : : DoesExist ( const FPayloadId & Id )
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FFileSystemBackend : : DoesExist ) ;
TStringBuilder < 512 > FilePath ;
CreateFilePath ( Id , FilePath ) ;
return IFileManager : : Get ( ) . FileExists ( FilePath . ToString ( ) ) ;
}
void FFileSystemBackend : : CreateFilePath ( const FPayloadId & PayloadId , FStringBuilderBase & OutPath )
{
TStringBuilder < 52 > PayloadPath ;
Utils : : PayloadIdToPath ( PayloadId , PayloadPath ) ;
OutPath < < RootDirectory < < TEXT ( " / " ) < < PayloadPath ;
}
2021-04-30 08:14:54 -04:00
2021-09-17 06:02:48 -04:00
TUniquePtr < FArchive > FFileSystemBackend : : OpenFileForReading ( const TCHAR * FilePath )
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FFileSystemBackend : : OpenFileForReading ) ;
int32 Retries = 0 ;
while ( Retries < RetryCount )
{
TUniquePtr < FArchive > FileAr ( IFileManager : : Get ( ) . CreateFileReader ( FilePath ) ) ;
if ( FileAr )
{
return FileAr ;
}
else
{
UE_LOG ( LogVirtualization , Warning , TEXT ( " [%s] Failed to open '%s' for reading attempt retrying (%d/%d) in %dms... " ) , * GetDebugString ( ) , FilePath , Retries , RetryCount , RetryWaitTimeMS ) ;
FPlatformProcess : : SleepNoStats ( RetryWaitTimeMS * 0.001f ) ;
Retries + + ;
}
}
return nullptr ;
}
2021-04-30 08:14:54 -04:00
UE_REGISTER_VIRTUALIZATION_BACKEND_FACTORY ( FFileSystemBackend , FileSystem ) ;
} // namespace UE::Virtualization