diff options
author | Idan Horowitz <idan.horowitz@gmail.com> | 2021-03-18 16:22:05 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-03-23 16:09:36 +0100 |
commit | a809db90ddff280fd523e924789ed907b2b950b9 (patch) | |
tree | 5b1e1c8447fb8dd9b93afef2ce07c8d7ebeda346 /Userland/Libraries/LibArchive | |
parent | 40eab55e7d4e757d6b3fcf18d26db8c6c40bbc25 (diff) | |
download | serenity-a809db90ddff280fd523e924789ed907b2b950b9.zip |
Libraries: Rename LibTar to LibArchive
This is in preparation for a new implementation of zip archive
extraction and creation.
Diffstat (limited to 'Userland/Libraries/LibArchive')
-rw-r--r-- | Userland/Libraries/LibArchive/CMakeLists.txt | 6 | ||||
-rw-r--r-- | Userland/Libraries/LibArchive/Tar.h | 141 | ||||
-rw-r--r-- | Userland/Libraries/LibArchive/TarStream.cpp | 190 | ||||
-rw-r--r-- | Userland/Libraries/LibArchive/TarStream.h | 87 |
4 files changed, 424 insertions, 0 deletions
diff --git a/Userland/Libraries/LibArchive/CMakeLists.txt b/Userland/Libraries/LibArchive/CMakeLists.txt new file mode 100644 index 0000000000..29dce137b0 --- /dev/null +++ b/Userland/Libraries/LibArchive/CMakeLists.txt @@ -0,0 +1,6 @@ +set(SOURCES + TarStream.cpp +) + +serenity_lib(LibArchive archive) +target_link_libraries(LibArchive LibCore) diff --git a/Userland/Libraries/LibArchive/Tar.h b/Userland/Libraries/LibArchive/Tar.h new file mode 100644 index 0000000000..cdd001c23f --- /dev/null +++ b/Userland/Libraries/LibArchive/Tar.h @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2020, Peter Elliott <pelliott@ualberta.ca> + * Copyright (c) 2021, Idan Horowitz <idan.horowitz@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <AK/String.h> +#include <AK/StringView.h> +#include <string.h> +#include <sys/types.h> + +namespace Archive { + +enum class TarFileType : char { + NormalFile = '0', + AlternateNormalFile = '\0', + HardLink = '1', + SymLink = '2', + CharacterSpecialFile = '3', + BlockSpecialFile = '4', + Directory = '5', + FIFO = '6', + ContiguousFile = '7', + GlobalExtendedHeader = 'g', + ExtendedHeader = 'x' +}; + +constexpr size_t block_size = 512; +constexpr const char* gnu_magic = "ustar "; // gnu format magic +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 [[gnu::packed]] TarFileHeader { +public: + 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); } + gid_t gid() const { return get_tar_field(m_gid); } + // FIXME: support 2001-star size encoding + size_t size() const { return get_tar_field(m_size); } + time_t timestamp() const { return get_tar_field(m_timestamp); } + TarFileType type_flag() const { return TarFileType(m_type_flag); } + const StringView link_name() const { return m_link_name; } + const StringView magic() const { return StringView(m_magic, min(__builtin_strlen(m_magic), sizeof(m_magic))); } // in some cases this is a null terminated string, in others its not + const StringView version() const { return StringView(m_version, min(__builtin_strlen(m_version), sizeof(m_version))); } // in some cases this is a null terminated string, in others its not + const StringView owner_name() const { return m_owner_name; } + 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(TarFileType type) { m_type_flag = static_cast<char>(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]; + char m_mode[8]; + char m_uid[8]; + char m_gid[8]; + char m_size[12]; + char m_timestamp[12]; + 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]; + char m_version[2]; + char m_owner_name[32]; + char m_group_name[32]; + char m_major[8]; + char m_minor[8]; + char m_prefix[155]; // zero out the prefix for archiving + + template<size_t N> + static size_t get_tar_field(const char (&field)[N]); +}; + +template<size_t N> +size_t TarFileHeader::get_tar_field(const char (&field)[N]) +{ + size_t value = 0; + for (size_t i = 0; i < N; ++i) { + if (field[i] == 0) + break; + + VERIFY(field[i] >= '0' && field[i] <= '7'); + value *= 8; + value += field[i] - '0'; + } + return value; +} +void TarFileHeader::calculate_checksum() +{ + memset(m_checksum, ' ', sizeof(m_checksum)); + auto checksum = 0u; + for (auto i = 0u; i < sizeof(TarFileHeader); ++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/LibArchive/TarStream.cpp b/Userland/Libraries/LibArchive/TarStream.cpp new file mode 100644 index 0000000000..b3763bef17 --- /dev/null +++ b/Userland/Libraries/LibArchive/TarStream.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2020, Peter Elliott <pelliott@ualberta.ca> + * Copyright (c) 2021, Idan Horowitz <idan.horowitz@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <LibArchive/TarStream.h> +#include <string.h> + +namespace Archive { +TarFileStream::TarFileStream(TarInputStream& tar_stream) + : m_tar_stream(tar_stream) + , m_generation(tar_stream.m_generation) +{ +} + +size_t TarFileStream::read(Bytes bytes) +{ + // verify that the stream has not advanced + VERIFY(m_tar_stream.m_generation == m_generation); + + if (has_any_error()) + return 0; + + auto to_read = min(bytes.size(), m_tar_stream.header().size() - m_tar_stream.m_file_offset); + + auto nread = m_tar_stream.m_stream.read(bytes.trim(to_read)); + m_tar_stream.m_file_offset += nread; + return nread; +} + +bool TarFileStream::unreliable_eof() const +{ + // verify that the stream has not advanced + VERIFY(m_tar_stream.m_generation == m_generation); + + return m_tar_stream.m_stream.unreliable_eof() + || m_tar_stream.m_file_offset >= m_tar_stream.header().size(); +} + +bool TarFileStream::read_or_error(Bytes bytes) +{ + // verify that the stream has not advanced + VERIFY(m_tar_stream.m_generation == m_generation); + + if (read(bytes) < bytes.size()) { + set_fatal_error(); + return false; + } + + return true; +} + +bool TarFileStream::discard_or_error(size_t count) +{ + // verify that the stream has not advanced + VERIFY(m_tar_stream.m_generation == m_generation); + + if (count > m_tar_stream.header().size() - m_tar_stream.m_file_offset) { + return false; + } + m_tar_stream.m_file_offset += count; + return m_tar_stream.m_stream.discard_or_error(count); +} + +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(TarFileHeader))); +} + +static constexpr unsigned long block_ceiling(unsigned long offset) +{ + return block_size * (1 + ((offset - 1) / block_size)); +} + +void TarInputStream::advance() +{ + if (m_finished) + return; + + m_generation++; + VERIFY(m_stream.discard_or_error(block_ceiling(m_header.size()) - m_file_offset)); + m_file_offset = 0; + + if (!m_stream.read_or_error(Bytes(&m_header, sizeof(m_header)))) { + m_finished = true; + return; + } + if (!valid()) { + m_finished = true; + return; + } + + VERIFY(m_stream.discard_or_error(block_size - sizeof(TarFileHeader))); +} + +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 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); + TarFileHeader 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(TarFileType::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); + TarFileHeader header; + memset(&header, 0, sizeof(header)); + header.set_size(bytes.size()); + header.set_file_name(path); + header.set_type_flag(TarFileType::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/LibArchive/TarStream.h b/Userland/Libraries/LibArchive/TarStream.h new file mode 100644 index 0000000000..9a486b9efe --- /dev/null +++ b/Userland/Libraries/LibArchive/TarStream.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020, Peter Elliott <pelliott@ualberta.ca> + * Copyright (c) 2021, Idan Horowitz <idan.horowitz@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <AK/Span.h> +#include <AK/Stream.h> +#include <LibArchive/Tar.h> + +namespace Archive { + +class TarInputStream; + +class TarFileStream : public InputStream { +public: + size_t read(Bytes) override; + bool unreliable_eof() const override; + + bool read_or_error(Bytes) override; + bool discard_or_error(size_t count) override; + +private: + TarFileStream(TarInputStream& stream); + TarInputStream& m_tar_stream; + int m_generation; + + friend class TarInputStream; +}; + +class TarInputStream { +public: + TarInputStream(InputStream&); + void advance(); + bool finished() const { return m_finished; } + bool valid() const; + const TarFileHeader& header() const { return m_header; } + TarFileStream file_contents(); + +private: + TarFileHeader m_header; + InputStream& m_stream; + unsigned long m_file_offset { 0 }; + int m_generation { 0 }; + bool m_finished { false }; + + 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; +}; + +} |