diff --git a/Userland/Libraries/LibTar/Tar.h b/Userland/Libraries/LibTar/Tar.h index ce06f56844..38f42bcb03 100644 --- a/Userland/Libraries/LibTar/Tar.h +++ b/Userland/Libraries/LibTar/Tar.h @@ -27,7 +27,9 @@ #pragma once +#include #include +#include #include namespace Tar { @@ -52,9 +54,8 @@ constexpr const char* gnu_version = " "; // gnu format version constexpr const char* ustar_magic = "ustar"; // ustar format magic constexpr const char* ustar_version = "00"; // ustar format version -class Header { +class [[gnu::packed]] Header { public: - // FIXME: support ustar filename prefix const StringView file_name() const { return m_file_name; } mode_t mode() const { return get_tar_field(m_mode); } uid_t uid() const { return get_tar_field(m_uid); } @@ -70,6 +71,26 @@ public: const StringView group_name() const { return m_group_name; } int major() const { return get_tar_field(m_major); } int minor() const { return get_tar_field(m_minor); } + // FIXME: support ustar filename prefix + const StringView prefix() const { return m_prefix; } + + void set_file_name(const String& file_name) { VERIFY(file_name.copy_characters_to_buffer(m_file_name, sizeof(m_file_name))); } + void set_mode(mode_t mode) { VERIFY(String::formatted("{:o}", mode).copy_characters_to_buffer(m_mode, sizeof(m_mode))); } + void set_uid(uid_t uid) { VERIFY(String::formatted("{:o}", uid).copy_characters_to_buffer(m_uid, sizeof(m_uid))); } + void set_gid(gid_t gid) { VERIFY(String::formatted("{:o}", gid).copy_characters_to_buffer(m_gid, sizeof(m_gid))); } + void set_size(size_t size) { VERIFY(String::formatted("{:o}", size).copy_characters_to_buffer(m_size, sizeof(m_size))); } + void set_timestamp(time_t timestamp) { VERIFY(String::formatted("{:o}", timestamp).copy_characters_to_buffer(m_timestamp, sizeof(m_timestamp))); } + void set_type_flag(FileType type) { m_type_flag = type; } + void set_link_name(const String& link_name) { VERIFY(link_name.copy_characters_to_buffer(m_link_name, sizeof(m_link_name))); } + void set_magic(const char* magic) { memcpy(m_magic, magic, sizeof(m_magic)); } // magic doesnt necessarily include a null byte + void set_version(const char* version) { memcpy(m_version, version, sizeof(m_version)); } // version doesnt necessarily include a null byte + void set_owner_name(const String& owner_name) { VERIFY(owner_name.copy_characters_to_buffer(m_owner_name, sizeof(m_owner_name))); } + void set_group_name(const String& group_name) { VERIFY(group_name.copy_characters_to_buffer(m_group_name, sizeof(m_group_name))); } + void set_major(int major) { VERIFY(String::formatted("{:o}", major).copy_characters_to_buffer(m_major, sizeof(m_major))); } + void set_minor(int minor) { VERIFY(String::formatted("{:o}", minor).copy_characters_to_buffer(m_minor, sizeof(m_minor))); } + void set_prefix(const String& prefix) { VERIFY(prefix.copy_characters_to_buffer(m_prefix, sizeof(m_prefix))); } + + void calculate_checksum(); private: char m_file_name[100]; @@ -78,7 +99,7 @@ private: char m_gid[8]; char m_size[12]; char m_timestamp[12]; - char m_checksum[8]; + char m_checksum[8]; // an uninitialized header's checksum is filled with spaces char m_type_flag; char m_link_name[100]; char m_magic[6]; @@ -87,7 +108,7 @@ private: char m_group_name[32]; char m_major[8]; char m_minor[8]; - char m_prefix[155]; + char m_prefix[155]; // zero out the prefix for archiving template static size_t get_tar_field(const char (&field)[N]); @@ -107,5 +128,14 @@ size_t Header::get_tar_field(const char (&field)[N]) } return value; } +void Header::calculate_checksum() +{ + memset(m_checksum, ' ', sizeof(m_checksum)); + auto checksum = 0u; + for (auto i = 0u; i < sizeof(Header); ++i) { + checksum += ((unsigned char*)this)[i]; + } + VERIFY(String::formatted("{:o}", checksum).copy_characters_to_buffer(m_checksum, sizeof(m_checksum))); +} } diff --git a/Userland/Libraries/LibTar/TarStream.cpp b/Userland/Libraries/LibTar/TarStream.cpp index bccc225a7b..2803d2aa5a 100644 --- a/Userland/Libraries/LibTar/TarStream.cpp +++ b/Userland/Libraries/LibTar/TarStream.cpp @@ -26,9 +26,10 @@ */ #include +#include namespace Tar { -TarFileStream::TarFileStream(TarStream& tar_stream) +TarFileStream::TarFileStream(TarInputStream& tar_stream) : m_tar_stream(tar_stream) , m_generation(tar_stream.m_generation) { @@ -83,11 +84,12 @@ bool TarFileStream::discard_or_error(size_t count) return m_tar_stream.m_stream.discard_or_error(count); } -TarStream::TarStream(InputStream& stream) +TarInputStream::TarInputStream(InputStream& stream) : m_stream(stream) { if (!m_stream.read_or_error(Bytes(&m_header, sizeof(m_header)))) { m_finished = true; + m_stream.handle_any_error(); // clear out errors so we dont assert return; } VERIFY(m_stream.discard_or_error(block_size - sizeof(Header))); @@ -98,7 +100,7 @@ static constexpr unsigned long block_ceiling(unsigned long offset) return block_size * (1 + ((offset - 1) / block_size)); } -void TarStream::advance() +void TarInputStream::advance() { if (m_finished) return; @@ -119,17 +121,70 @@ void TarStream::advance() VERIFY(m_stream.discard_or_error(block_size - sizeof(Header))); } -bool TarStream::valid() const +bool TarInputStream::valid() const { auto& header_magic = header().magic(); auto& header_version = header().version(); return (header_magic == gnu_magic && header_version == gnu_version) || (header_magic == ustar_magic && header_version == ustar_version); } -TarFileStream TarStream::file_contents() +TarFileStream TarInputStream::file_contents() { VERIFY(!m_finished); return TarFileStream(*this); } +TarOutputStream::TarOutputStream(OutputStream& stream) + : m_stream(stream) +{ +} + +void TarOutputStream::add_directory(const String& path, mode_t mode) +{ + VERIFY(!m_finished); + Header header; + memset(&header, 0, sizeof(header)); + header.set_size(0); + header.set_file_name(String::formatted("{}/", path)); // Old tar implementations assume directory names end with a / + header.set_type_flag(Directory); + header.set_mode(mode); + header.set_magic(gnu_magic); + header.set_version(gnu_version); + header.calculate_checksum(); + VERIFY(m_stream.write_or_error(Bytes { &header, sizeof(header) })); + u8 padding[block_size] = { 0 }; + VERIFY(m_stream.write_or_error(Bytes { &padding, block_size - sizeof(header) })); +} + +void TarOutputStream::add_file(const String& path, mode_t mode, const ReadonlyBytes& bytes) +{ + VERIFY(!m_finished); + Header header; + memset(&header, 0, sizeof(header)); + header.set_size(bytes.size()); + header.set_file_name(path); + header.set_type_flag(NormalFile); + header.set_mode(mode); + header.set_magic(gnu_magic); + header.set_version(gnu_version); + header.calculate_checksum(); + VERIFY(m_stream.write_or_error(Bytes { &header, sizeof(header) })); + u8 padding[block_size] = { 0 }; + VERIFY(m_stream.write_or_error(Bytes { &padding, block_size - sizeof(header) })); + size_t n_written = 0; + while (n_written < bytes.size()) { + n_written += m_stream.write(bytes.slice(n_written, min(bytes.size() - n_written, block_size))); + } + VERIFY(m_stream.write_or_error(Bytes { &padding, block_size - (n_written % block_size) })); +} + +void TarOutputStream::finish() +{ + VERIFY(!m_finished); + u8 padding[block_size] = { 0 }; + m_stream.write_or_error(Bytes { &padding, block_size }); // 2 empty records that are used to signify the end of the archive + m_stream.write_or_error(Bytes { &padding, block_size }); + m_finished = true; +} + } diff --git a/Userland/Libraries/LibTar/TarStream.h b/Userland/Libraries/LibTar/TarStream.h index adcf0cdb5f..a59243ee55 100644 --- a/Userland/Libraries/LibTar/TarStream.h +++ b/Userland/Libraries/LibTar/TarStream.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Peter Elliott + * Copyright (c) 2021, Idan Horowitz * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,7 +33,7 @@ namespace Tar { -class TarStream; +class TarInputStream; class TarFileStream : public InputStream { public: @@ -43,16 +44,16 @@ public: bool discard_or_error(size_t count) override; private: - TarFileStream(TarStream& stream); - TarStream& m_tar_stream; + TarFileStream(TarInputStream& stream); + TarInputStream& m_tar_stream; int m_generation; - friend class TarStream; + friend class TarInputStream; }; -class TarStream { +class TarInputStream { public: - TarStream(InputStream&); + TarInputStream(InputStream&); void advance(); bool finished() const { return m_finished; } bool valid() const; @@ -69,4 +70,18 @@ private: friend class TarFileStream; }; +class TarOutputStream { +public: + TarOutputStream(OutputStream&); + void add_file(const String& path, mode_t, const ReadonlyBytes&); + void add_directory(const String& path, mode_t); + void finish(); + +private: + OutputStream& m_stream; + bool m_finished { false }; + + friend class TarFileStream; +}; + } diff --git a/Userland/Utilities/tar.cpp b/Userland/Utilities/tar.cpp index 3e74e09042..09f56ab330 100644 --- a/Userland/Utilities/tar.cpp +++ b/Userland/Utilities/tar.cpp @@ -78,7 +78,7 @@ int main(int argc, char** argv) InputStream& file_input_stream = file_stream; InputStream& gzip_input_stream = gzip_stream; - Tar::TarStream tar_stream((gzip) ? gzip_input_stream : file_input_stream); + Tar::TarInputStream tar_stream((gzip) ? gzip_input_stream : file_input_stream); if (!tar_stream.valid()) { warnln("the provided file is not a well-formatted ustar file"); return 1;