// Georgy Treshchev 2024. #include "ArchiverTar/RuntimeArchiverTarHeader.h" #include "RuntimeArchiverDefines.h" #include "RuntimeArchiverTypes.h" #include "HAL/UnrealMemory.h" #include "Containers/StringConv.h" #include "ArchiverTar/RuntimeArchiverTarOperations.h" /** * Helper that handles type flags */ class FTarTypeFlagHelper { /** File type flags */ static const ANSICHAR FileTypeFlag; static const ANSICHAR FileTypeFlag1; /** Directory type flag */ static const ANSICHAR DirectoryTypeFlag; /** Unsupported type flags */ static const ANSICHAR HardLinkTypeFlag; static const ANSICHAR SymbolicLinkTypeFlag; static const ANSICHAR CharacterDeviceTypeFlag; static const ANSICHAR BlockDeviceTypeFlag; static const ANSICHAR FIFOTypeFlag; /** Array of string representation of type flags */ static const TMap Strings; public: /** * Get a string representation of the specified type flag */ static FString ToString(ANSICHAR TypeFlag) { FString const* String = Strings.Find(TypeFlag); if (!String) { return TEXT("Unrecognized type flag"); } return *String; } /** * Check if the specified type flag applies to a directory */ static bool IsDirectory(ANSICHAR TypeFlag) { if (TypeFlag == FileTypeFlag || TypeFlag == FileTypeFlag1) { return false; } if (TypeFlag == DirectoryTypeFlag) { return true; } UE_LOG(LogRuntimeArchiver, Error, TEXT("The type flag %c (%s) is not supported. Supported type flags are %c/%c (%s) and %c (%s)"), TCHAR(TypeFlag), *ToString(TypeFlag), TCHAR(FileTypeFlag), TCHAR(FileTypeFlag1), *ToString(FileTypeFlag), TCHAR(DirectoryTypeFlag), *ToString(DirectoryTypeFlag)); return false; } /** * Get type flag */ static ANSICHAR GetTypeFlag(bool bIsDirectory) { return bIsDirectory ? DirectoryTypeFlag : FileTypeFlag; } }; const ANSICHAR FTarTypeFlagHelper::FileTypeFlag{'0'}; const ANSICHAR FTarTypeFlagHelper::FileTypeFlag1{'\0'}; const ANSICHAR FTarTypeFlagHelper::DirectoryTypeFlag{'5'}; const ANSICHAR FTarTypeFlagHelper::HardLinkTypeFlag{'1'}; const ANSICHAR FTarTypeFlagHelper::SymbolicLinkTypeFlag{'2'}; const ANSICHAR FTarTypeFlagHelper::CharacterDeviceTypeFlag{'3'}; const ANSICHAR FTarTypeFlagHelper::BlockDeviceTypeFlag{'4'}; const ANSICHAR FTarTypeFlagHelper::FIFOTypeFlag{'6'}; const TMap FTarTypeFlagHelper::Strings{ {FileTypeFlag, TEXT("File")}, {FileTypeFlag1, TEXT("File")}, {DirectoryTypeFlag, TEXT("Directory")}, {HardLinkTypeFlag, TEXT("Hard link")}, {SymbolicLinkTypeFlag, TEXT("Symbolic link")}, {CharacterDeviceTypeFlag, TEXT("Character device")}, {BlockDeviceTypeFlag, TEXT("Block device")}, {FIFOTypeFlag, TEXT("FIFO")} }; /** * Helper that handles checksum */ class FTarChecksumHelper { public: /** * Check if the specified tar header has a valid checksum * * @param Header Header to check * @return Whether the checksum is valid or not */ static bool IsValid(const FTarHeader& Header) { if (*Header.Checksum == '\0') { UE_LOG(LogRuntimeArchiver, Error, TEXT("Checksum in tar header '%s' is NULL"), StringCast(Header.GetName()).Get()); return false; } if (BuildChecksum(Header) != Header.GetChecksum()) { UE_LOG(LogRuntimeArchiver, Error, TEXT("Checksum in tar header %s' is invalid"), StringCast(Header.GetName()).Get()) return false; } return true; } /** * Build a checksum based primarily on tar header offsets * * @param Header Tar header based on which to build the checksum * @return Built checksum */ static uint32 BuildChecksum(const FTarHeader& Header) { const uint8* HeaderPtr = reinterpret_cast(&Header); uint32 Checksum = 256; for (size_t Index = 0; Index < STRUCT_OFFSET(FTarHeader, Checksum); ++Index) { Checksum += HeaderPtr[Index]; } for (size_t Index = STRUCT_OFFSET(FTarHeader, TypeFlag); Index < sizeof(Header); ++Index) { Checksum += HeaderPtr[Index]; } return Checksum; } }; FTarHeader::FTarHeader() { FMemory::Memset(this, 0, sizeof(FTarHeader)); } bool FTarHeader::ToEntry(const FTarHeader& Header, int32 Index, FRuntimeArchiveEntry& Entry) { if (!FTarChecksumHelper::IsValid(Header)) { return false; } Entry.Index = Index; Entry.Name = StringCast(Header.GetName()).Get(); Entry.bIsDirectory = FTarTypeFlagHelper::IsDirectory(Header.GetTypeFlag()); Entry.UncompressedSize = Header.GetSize(); Entry.CompressedSize = RuntimeArchiverTarOperations::RoundUp(Entry.UncompressedSize, 512); Entry.CreationTime = FDateTime::FromUnixTimestamp(Header.GetTime()); return true; } bool FTarHeader::FromEntry(const FRuntimeArchiveEntry& Entry, FTarHeader& Header) { return GenerateHeader(Entry.Name, Entry.UncompressedSize, Entry.CreationTime, Entry.bIsDirectory, Header); } bool FTarHeader::GenerateHeader(const FString& Name, int64 Size, const FDateTime& CreationTime, bool bIsDirectory, FTarHeader& Header) { Header = FTarHeader(); if (!Header.SetName(StringCast(*Name).Get())) { return false; } // Setting all possible permissions for Unix Header.SetMode(0777); Header.SetSize(Size); Header.SetTime(CreationTime.ToUnixTimestamp()); Header.SetTypeFlag(FTarTypeFlagHelper::GetTypeFlag(bIsDirectory)); Header.SetChecksum(FTarChecksumHelper::BuildChecksum(Header)); return true; } const RA_UTF8CHAR* FTarHeader::GetName() const { return Name; } bool FTarHeader::SetName(const RA_UTF8CHAR* InName) { // Check if length is within bounds if (TCString::Strlen(InName) > UE_ARRAY_COUNT(Name)) { UE_LOG(LogRuntimeArchiver, Error, TEXT("Name '%s' is too long for tar header. Maximum length is %d"), StringCast(InName).Get(), static_cast(UE_ARRAY_COUNT(Name))); return false; } TCString::Strncpy(Name, InName, UE_ARRAY_COUNT(Name)); return true; } uint32 FTarHeader::GetMode() const { return RuntimeArchiverTarOperations::OctalToDecimal(Mode); } void FTarHeader::SetMode(uint32 InMode) { RuntimeArchiverTarOperations::DecimalToOctal(InMode, Mode, UE_ARRAY_COUNT(Mode)); } uint32 FTarHeader::GetOwner() const { return RuntimeArchiverTarOperations::OctalToDecimal(Owner); } void FTarHeader::SetOwner(uint32 InOwner) { RuntimeArchiverTarOperations::DecimalToOctal(InOwner, Owner, UE_ARRAY_COUNT(Owner)); } const ANSICHAR* FTarHeader::GetGroup() const { return Group; } bool FTarHeader::SetGroup(const ANSICHAR* InGroup) { // Check if length is within bounds if (FCStringAnsi::Strlen(InGroup) > UE_ARRAY_COUNT(Group)) { UE_LOG(LogRuntimeArchiver, Error, TEXT("Group '%s' is too long for tar header. Maximum length is %d"), StringCast(InGroup).Get(), static_cast(UE_ARRAY_COUNT(Group))); return false; } FCStringAnsi::Strncpy(Group, InGroup, UE_ARRAY_COUNT(Group)); return true; } int64 FTarHeader::GetSize() const { return RuntimeArchiverTarOperations::OctalToDecimal(Size); } void FTarHeader::SetSize(int64 InSize) { RuntimeArchiverTarOperations::DecimalToOctal(InSize, Size, UE_ARRAY_COUNT(Size)); } int64 FTarHeader::GetTime() const { return RuntimeArchiverTarOperations::OctalToDecimal(Time); } void FTarHeader::SetTime(int64 InTime) { RuntimeArchiverTarOperations::DecimalToOctal(InTime, Time, UE_ARRAY_COUNT(Time)); } uint32 FTarHeader::GetChecksum() const { return RuntimeArchiverTarOperations::OctalToDecimal(Checksum); } void FTarHeader::SetChecksum(uint32 InChecksum) { RuntimeArchiverTarOperations::DecimalToOctal(InChecksum, Checksum, UE_ARRAY_COUNT(Checksum)); } ANSICHAR FTarHeader::GetTypeFlag() const { return TypeFlag; } void FTarHeader::SetTypeFlag(ANSICHAR InTypeFlag) { TypeFlag = InTypeFlag; } const ANSICHAR* FTarHeader::GetLinkName() const { return LinkName; } bool FTarHeader::SetLinkName(const ANSICHAR* InLinkName) { if (FCStringAnsi::Strlen(InLinkName) > UE_ARRAY_COUNT(LinkName)) { UE_LOG(LogRuntimeArchiver, Error, TEXT("Link name '%s' is too long for tar header. Maximum length is %d"), StringCast(InLinkName).Get(), static_cast(UE_ARRAY_COUNT(LinkName))); return false; } FCStringAnsi::Strncpy(LinkName, InLinkName, UE_ARRAY_COUNT(LinkName)); return true; }