// Copyright Epic Games, Inc. All Rights Reserved. #include #include "compactcas.h" #include #include #include #include #include #include #include #include #include #include #include #include ////////////////////////////////////////////////////////////////////////// namespace zen { uint32_t CasLogFile::FileHeader::ComputeChecksum() { return XXH32(&this->Magic, sizeof(FileHeader) - 4, 0xC0C0'BABA); } CasLogFile::CasLogFile() { } CasLogFile::~CasLogFile() { } void CasLogFile::Open(std::filesystem::path FileName, size_t RecordSize, bool IsCreate) { m_RecordSize = RecordSize; std::error_code Ec; m_File.Open(FileName, IsCreate, Ec); if (Ec) { throw std::system_error(Ec, fmt::format("Failed to open log file '{}'", FileName)); } uint64_t AppendOffset = 0; if (IsCreate || (m_File.FileSize() < sizeof(FileHeader))) { // Initialize log by writing header FileHeader Header = {.RecordSize = gsl::narrow(RecordSize), .LogId = Oid::NewOid(), .ValidatedTail = 0}; memcpy(Header.Magic, FileHeader::MagicSequence, sizeof Header.Magic); Header.Finalize(); m_File.Write(&Header, sizeof Header, 0); AppendOffset = sizeof(FileHeader); m_Header = Header; } else { // Validate header and log contents and prepare for appending/replay FileHeader Header; m_File.Read(&Header, sizeof Header, 0); if ((0 != memcmp(Header.Magic, FileHeader::MagicSequence, sizeof Header.Magic)) || (Header.Checksum != Header.ComputeChecksum())) { throw std::runtime_error(fmt::format("Mangled log header (invalid header magic) in '{}'", FileName)); } AppendOffset = m_File.FileSize(); // Adjust the offset to ensure we end up on a good boundary, in case there is some garbage appended AppendOffset -= sizeof Header; AppendOffset -= AppendOffset % RecordSize; AppendOffset += sizeof Header; m_Header = Header; } m_AppendOffset = AppendOffset; } void CasLogFile::Close() { // TODO: update header and maybe add trailer Flush(); m_File.Close(); } uint64_t CasLogFile::GetLogSize() { return m_File.FileSize(); } void CasLogFile::Replay(std::function&& Handler) { uint64_t LogFileSize = m_File.FileSize(); // Ensure we end up on a clean boundary const uint64_t LogBaseOffset = sizeof(FileHeader); const size_t LogEntryCount = (LogFileSize - LogBaseOffset) / m_RecordSize; if (LogEntryCount == 0) { return; } // This should really be streaming the data rather than just // reading it into memory, though we don't tend to get very // large logs so it may not matter const uint64_t LogDataSize = LogEntryCount * m_RecordSize; std::vector ReadBuffer; ReadBuffer.resize(LogDataSize); m_File.Read(ReadBuffer.data(), LogDataSize, LogBaseOffset); for (int i = 0; i < int(LogEntryCount); ++i) { Handler(ReadBuffer.data() + (i * m_RecordSize)); } m_AppendOffset = LogBaseOffset + (m_RecordSize * LogEntryCount); } void CasLogFile::Append(const void* DataPointer, uint64_t DataSize) { ZEN_ASSERT(DataSize == m_RecordSize); uint64_t AppendOffset = m_AppendOffset.fetch_add(DataSize); std::error_code Ec; m_File.Write(DataPointer, gsl::narrow(DataSize), AppendOffset, Ec); if (Ec) { throw std::system_error(Ec, fmt::format("Failed to write to log file '{}'", PathFromHandle(m_File.Handle()))); } } void CasLogFile::Flush() { m_File.Flush(); } } // namespace zen