Serenity Operating System
at master 211 lines 6.6 kB view raw
1/* 2 * Copyright (c) 2020, Peter Elliott <pelliott@serenityos.org> 3 * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> 4 * Copyright (c) 2022, the SerenityOS developers. 5 * 6 * SPDX-License-Identifier: BSD-2-Clause 7 */ 8 9#include <AK/Array.h> 10#include <AK/OwnPtr.h> 11#include <LibArchive/TarStream.h> 12#include <string.h> 13 14namespace Archive { 15TarFileStream::TarFileStream(TarInputStream& tar_stream) 16 : m_tar_stream(tar_stream) 17 , m_generation(tar_stream.m_generation) 18{ 19} 20 21ErrorOr<Bytes> TarFileStream::read_some(Bytes bytes) 22{ 23 // Verify that the stream has not advanced. 24 VERIFY(m_tar_stream.m_generation == m_generation); 25 26 auto header_size = TRY(m_tar_stream.header().size()); 27 28 auto to_read = min(bytes.size(), header_size - m_tar_stream.m_file_offset); 29 30 auto slice = TRY(m_tar_stream.m_stream->read_some(bytes.trim(to_read))); 31 m_tar_stream.m_file_offset += slice.size(); 32 33 return slice; 34} 35 36bool TarFileStream::is_eof() const 37{ 38 // Verify that the stream has not advanced. 39 VERIFY(m_tar_stream.m_generation == m_generation); 40 41 auto header_size_or_error = m_tar_stream.header().size(); 42 if (header_size_or_error.is_error()) 43 return true; 44 auto header_size = header_size_or_error.release_value(); 45 46 return m_tar_stream.m_stream->is_eof() 47 || m_tar_stream.m_file_offset >= header_size; 48} 49 50ErrorOr<size_t> TarFileStream::write_some(ReadonlyBytes) 51{ 52 return Error::from_errno(EBADF); 53} 54 55ErrorOr<NonnullOwnPtr<TarInputStream>> TarInputStream::construct(NonnullOwnPtr<Stream> stream) 56{ 57 auto tar_stream = TRY(adopt_nonnull_own_or_enomem(new (nothrow) TarInputStream(move(stream)))); 58 59 TRY(tar_stream->load_next_header()); 60 61 return tar_stream; 62} 63 64TarInputStream::TarInputStream(NonnullOwnPtr<Stream> stream) 65 : m_stream(move(stream)) 66{ 67} 68 69static constexpr unsigned long block_ceiling(unsigned long offset) 70{ 71 return block_size * (1 + ((offset - 1) / block_size)); 72} 73 74ErrorOr<void> TarInputStream::advance() 75{ 76 if (finished()) 77 return Error::from_string_literal("Attempted to advance a finished stream"); 78 79 m_generation++; 80 81 // Discard the pending bytes of the current entry. 82 auto file_size = TRY(m_header.size()); 83 TRY(m_stream->discard(block_ceiling(file_size) - m_file_offset)); 84 m_file_offset = 0; 85 86 TRY(load_next_header()); 87 88 return {}; 89} 90 91ErrorOr<void> TarInputStream::load_next_header() 92{ 93 size_t number_of_consecutive_zero_blocks = 0; 94 while (true) { 95 m_header = TRY(m_stream->read_value<TarFileHeader>()); 96 97 // Discard the rest of the header block. 98 TRY(m_stream->discard(block_size - sizeof(TarFileHeader))); 99 100 if (!header().is_zero_block()) 101 break; 102 103 number_of_consecutive_zero_blocks++; 104 105 // Two zero blocks in a row marks the end of the archive. 106 if (number_of_consecutive_zero_blocks >= 2) { 107 m_found_end_of_archive = true; 108 return {}; 109 } 110 } 111 112 if (!TRY(valid())) 113 return Error::from_string_literal("Header has an invalid magic or checksum"); 114 115 return {}; 116} 117 118ErrorOr<bool> TarInputStream::valid() const 119{ 120 auto const header_magic = header().magic(); 121 auto const header_version = header().version(); 122 123 if (!((header_magic == gnu_magic && header_version == gnu_version) 124 || (header_magic == ustar_magic && header_version == ustar_version) 125 || (header_magic == posix1_tar_magic && header_version == posix1_tar_version))) 126 return false; 127 128 // POSIX.1-1988 tar does not have magic numbers, so we also need to verify the header checksum. 129 return TRY(header().checksum()) == header().expected_checksum(); 130} 131 132TarFileStream TarInputStream::file_contents() 133{ 134 VERIFY(!finished()); 135 return TarFileStream(*this); 136} 137 138TarOutputStream::TarOutputStream(MaybeOwned<Stream> stream) 139 : m_stream(move(stream)) 140{ 141} 142 143ErrorOr<void> TarOutputStream::add_directory(StringView path, mode_t mode) 144{ 145 VERIFY(!m_finished); 146 TarFileHeader header {}; 147 TRY(header.set_size(0)); 148 header.set_filename_and_prefix(TRY(String::formatted("{}/", path))); // Old tar implementations assume directory names end with a / 149 header.set_type_flag(TarFileType::Directory); 150 TRY(header.set_mode(mode)); 151 header.set_magic(gnu_magic); 152 header.set_version(gnu_version); 153 TRY(header.calculate_checksum()); 154 TRY(m_stream->write_until_depleted(Bytes { &header, sizeof(header) })); 155 u8 padding[block_size] = { 0 }; 156 TRY(m_stream->write_until_depleted(Bytes { &padding, block_size - sizeof(header) })); 157 return {}; 158} 159 160ErrorOr<void> TarOutputStream::add_file(StringView path, mode_t mode, ReadonlyBytes bytes) 161{ 162 VERIFY(!m_finished); 163 TarFileHeader header {}; 164 TRY(header.set_size(bytes.size())); 165 header.set_filename_and_prefix(path); 166 header.set_type_flag(TarFileType::NormalFile); 167 TRY(header.set_mode(mode)); 168 header.set_magic(gnu_magic); 169 header.set_version(gnu_version); 170 TRY(header.calculate_checksum()); 171 TRY(m_stream->write_until_depleted(ReadonlyBytes { &header, sizeof(header) })); 172 constexpr Array<u8, block_size> padding { 0 }; 173 TRY(m_stream->write_until_depleted(ReadonlyBytes { &padding, block_size - sizeof(header) })); 174 size_t n_written = 0; 175 while (n_written < bytes.size()) { 176 n_written += MUST(m_stream->write_some(bytes.slice(n_written, min(bytes.size() - n_written, block_size)))); 177 } 178 TRY(m_stream->write_until_depleted(ReadonlyBytes { &padding, block_size - (n_written % block_size) })); 179 return {}; 180} 181 182ErrorOr<void> TarOutputStream::add_link(StringView path, mode_t mode, StringView link_name) 183{ 184 VERIFY(!m_finished); 185 TarFileHeader header {}; 186 TRY(header.set_size(0)); 187 header.set_filename_and_prefix(path); 188 header.set_type_flag(TarFileType::SymLink); 189 TRY(header.set_mode(mode)); 190 header.set_magic(gnu_magic); 191 header.set_version(gnu_version); 192 header.set_link_name(link_name); 193 TRY(header.calculate_checksum()); 194 TRY(m_stream->write_until_depleted(Bytes { &header, sizeof(header) })); 195 u8 padding[block_size] = { 0 }; 196 TRY(m_stream->write_until_depleted(Bytes { &padding, block_size - sizeof(header) })); 197 return {}; 198} 199 200ErrorOr<void> TarOutputStream::finish() 201{ 202 VERIFY(!m_finished); 203 constexpr Array<u8, block_size> padding { 0 }; 204 // 2 empty records that are used to signify the end of the archive. 205 TRY(m_stream->write_until_depleted(ReadonlyBytes { &padding, block_size })); 206 TRY(m_stream->write_until_depleted(ReadonlyBytes { &padding, block_size })); 207 m_finished = true; 208 return {}; 209} 210 211}