// Copyright Epic Games, Inc. All Rights Reserved. #include "VirtualizationFileBackend.h" #include "HAL/FileManager.h" #include "Misc/Parse.h" #include "Misc/Paths.h" #include "Virtualization/VirtualizationManager.h" #include "VirtualizationUtilities.h" namespace UE::Virtualization { /** * 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)"); } } FFileSystemBackend::FFileSystemBackend(FStringView ConfigName) : IVirtualizationBackend(EOperations::Both) { Name = WriteToString<256>(TEXT("FFileSystemBackend - "), ConfigName).ToString(); } bool FFileSystemBackend::Initialize(const FString& ConfigEntry) { if (!FParse::Value(*ConfigEntry, TEXT("Path="), RootDirectory)) { UE_LOG(LogVirtualization, Error, TEXT("[%s] 'Path=' not found in the config file"), *GetDebugString()); return false; } FPaths::NormalizeDirectoryName(RootDirectory); if (RootDirectory.IsEmpty()) { UE_LOG(LogVirtualization, Error, TEXT("[%s] Config file entry 'Path=' was empty"), *GetDebugString()); return false; } // TODO: Validate that the given path is usable? UE_LOG(LogVirtualization, Log, TEXT("[%s] Using path: '%s'"), *GetDebugString(), *RootDirectory); return true; } EPushResult FFileSystemBackend::PushData(const FPayloadId& Id, const FCompressedBuffer& Payload) { TRACE_CPUPROFILER_EVENT_SCOPE(FFileSystemBackend::PushData); if (DoesExist(Id)) { UE_LOG(LogVirtualization, Verbose, TEXT("[%s] Already has a copy of the payload '%s'."), *GetDebugString(), *Id.ToString()); return EPushResult::PayloadAlreadyExisted; } TStringBuilder<512> FilePath; CreateFilePath(Id, FilePath); // TODO: Should we write to a temp file and then move it once it has written? TUniquePtr FileAr(IFileManager::Get().CreateFileWriter(FilePath.ToString())); if (FileAr == nullptr) { TStringBuilder SystemErrorMsg; GetFormattedSystemError(SystemErrorMsg); UE_LOG(LogVirtualization, Error, TEXT("[%s] Failed to write payload '%s' to '%s' due to system error: %s"), *GetDebugString(), *Id.ToString(), FilePath.ToString(), SystemErrorMsg.ToString()); return EPushResult::Failed; } for (const FSharedBuffer& Buffer : Payload.GetCompressed().GetSegments()) { // Const cast because FArchive requires a non-const pointer! FileAr->Serialize(const_cast(Buffer.GetData()), static_cast(Buffer.GetSize())); } if (FileAr->Close()) { return EPushResult::Success; } else { // TODO: If we were first saving to a tmp file we could avoid the need to delete the // potentially corrupt output file. IFileManager::Get().Delete(FilePath.ToString()); return EPushResult::Failed; } } 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(); } TUniquePtr FileAr(IFileManager::Get().CreateFileReader(FilePath.ToString())); if (FileAr == nullptr) { TStringBuilder SystemErrorMsg; GetFormattedSystemError(SystemErrorMsg); 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()); return FCompressedBuffer(); } return FCompressedBuffer::FromCompressed(*FileAr); } FString FFileSystemBackend::GetDebugString() const { return Name; } 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; } UE_REGISTER_VIRTUALIZATION_BACKEND_FACTORY(FFileSystemBackend, FileSystem); } // namespace UE::Virtualization