diff --git a/luna/CMakeLists.txt b/luna/CMakeLists.txt index 77f51628..54b55b97 100644 --- a/luna/CMakeLists.txt +++ b/luna/CMakeLists.txt @@ -11,6 +11,7 @@ set(FREESTANDING_SOURCES src/Alloc.cpp src/OwnedStringView.cpp src/Utf8.cpp + src/TarStream.cpp ) set(SOURCES diff --git a/luna/include/luna/TarStream.h b/luna/include/luna/TarStream.h new file mode 100644 index 00000000..baf8322d --- /dev/null +++ b/luna/include/luna/TarStream.h @@ -0,0 +1,66 @@ +#pragma once +#include +#include + +class TarStream +{ + public: + enum class EntryType + { + RegularFile, + Directory + }; + + struct Entry + { + char name[257]; + usize size; + EntryType type; + + private: + usize pos; + + friend class TarStream; + }; + + TarStream(void* base, usize size); + + void initialize(void* base, usize size); + + Result read_next_entry(); + + void rewind(); + + usize read_contents(const Entry& entry, char* buf, usize offset, usize length); + + private: + struct [[gnu::packed]] TarHeader + { + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char chksum[8]; + char typeflag; + char linkname[100]; + char magic[6]; + char version[2]; + char uname[32]; + char gname[32]; + char devmajor[8]; + char devminor[8]; + char prefix[155]; + }; + + Result find_valid_header(TarHeader* out, bool& success); + + Result read_header(TarHeader* out); + Result parse_header(const TarHeader* hdr); + + void* m_base; + void* m_pos; + usize m_size; + usize m_offset = 0; +}; \ No newline at end of file diff --git a/luna/src/TarStream.cpp b/luna/src/TarStream.cpp new file mode 100644 index 00000000..70093ac3 --- /dev/null +++ b/luna/src/TarStream.cpp @@ -0,0 +1,96 @@ +#include +#include +#include +#include + +TarStream::TarStream(void* base, usize size) : m_base(base), m_pos(base), m_size(size) +{ +} + +void TarStream::initialize(void* base, usize size) +{ + m_base = m_pos = base; + m_size = size; + m_offset = 0; +} + +void TarStream::rewind() +{ + m_pos = m_base; + m_offset = 0; +} + +Result TarStream::read_header(TarHeader* out) +{ + if ((m_offset + 512) > m_size) return err(0); + memcpy(out, m_pos, sizeof(TarHeader)); + m_pos = offset_ptr(m_pos, 512); + m_offset += 512; + return {}; +} + +Result TarStream::parse_header(const TarStream::TarHeader* hdr) +{ + Entry entry; + + char size[13]; + nullcpy(size, hdr->size, 12); + entry.size = parse_unsigned_integer(size, nullptr, 8); + + entry.pos = m_offset; + + switch (hdr->typeflag) + { + case '\0': + case '0': entry.type = EntryType::RegularFile; break; + case '5': entry.type = EntryType::Directory; break; + default: return err(EFIXME); + } + + if (!strlen(hdr->prefix)) { nullcpy(entry.name, hdr->name, 100); } + else { return err(EFIXME); } + + if (entry.size) + { + m_pos = offset_ptr(m_pos, align_up<512ul>(entry.size)); + m_offset += align_up<512ul>(entry.size); + } + + return entry; +} + +Result TarStream::find_valid_header(TarStream::TarHeader* out, bool& success) +{ + success = false; + while (true) + { + TRY(read_header(out)); + if (!memcmp(out->magic, "ustar", 5)) + { + success = true; + return {}; + } + } +} + +// FIXME: How do we know whether an error is an error or just EOF? Returning an error code of 0 as EOF seems a bit +// clunky to me... +Result TarStream::read_next_entry() +{ + bool success; + TarHeader header; + auto rc = find_valid_header(&header, success); + if (!success) return rc.release_error(); + + return parse_header(&header); +} + +usize TarStream::read_contents(const Entry& entry, char* buf, usize offset, usize length) +{ + if (offset >= entry.size) return 0; + if ((length + offset) > entry.size) length = entry.size - offset; + + memcpy(buf, offset_ptr(m_base, entry.pos + offset), length); + + return length; +} \ No newline at end of file