mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 06:48:12 +00:00
LibArchive: Use Core::Stream inside TarInputStream
This commit is contained in:
parent
71d1d9e2b5
commit
cbeaba0c12
4 changed files with 95 additions and 59 deletions
|
@ -4,23 +4,35 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <AK/MemoryStream.h>
|
#include <AK/NonnullOwnPtr.h>
|
||||||
#include <LibArchive/TarStream.h>
|
#include <LibArchive/TarStream.h>
|
||||||
|
#include <LibCore/MemoryStream.h>
|
||||||
|
#include <LibCore/Stream.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
|
||||||
{
|
{
|
||||||
InputMemoryStream input_stream(ReadonlyBytes { data, size });
|
// FIXME: Create a ReadonlyBytes variant of Core::Stream::MemoryStream.
|
||||||
Archive::TarInputStream tar_stream(input_stream);
|
auto input_stream_or_error = Core::Stream::MemoryStream::construct(Bytes { const_cast<uint8_t*>(data), size });
|
||||||
|
|
||||||
if (!tar_stream.valid())
|
if (input_stream_or_error.is_error())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
while (!tar_stream.finished()) {
|
auto tar_stream_or_error = Archive::TarInputStream::construct(input_stream_or_error.release_value());
|
||||||
auto const& header = tar_stream.header();
|
|
||||||
|
if (tar_stream_or_error.is_error())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
auto tar_stream = tar_stream_or_error.release_value();
|
||||||
|
|
||||||
|
if (!tar_stream->valid())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
while (!tar_stream->finished()) {
|
||||||
|
auto const& header = tar_stream->header();
|
||||||
|
|
||||||
if (!header.content_is_like_extended_header()) {
|
if (!header.content_is_like_extended_header()) {
|
||||||
if (tar_stream.advance().is_error())
|
if (tar_stream->advance().is_error())
|
||||||
return 0;
|
return 0;
|
||||||
else
|
else
|
||||||
continue;
|
continue;
|
||||||
|
@ -29,7 +41,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
|
||||||
switch (header.type_flag()) {
|
switch (header.type_flag()) {
|
||||||
case Archive::TarFileType::GlobalExtendedHeader:
|
case Archive::TarFileType::GlobalExtendedHeader:
|
||||||
case Archive::TarFileType::ExtendedHeader: {
|
case Archive::TarFileType::ExtendedHeader: {
|
||||||
auto result = tar_stream.for_each_extended_header([&](StringView, StringView) {});
|
auto result = tar_stream->for_each_extended_header([&](StringView, StringView) {});
|
||||||
if (result.is_error())
|
if (result.is_error())
|
||||||
return 0;
|
return 0;
|
||||||
break;
|
break;
|
||||||
|
@ -38,7 +50,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tar_stream.advance().is_error())
|
if (tar_stream->advance().is_error())
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,10 +26,10 @@ ErrorOr<Bytes> TarFileStream::read(Bytes bytes)
|
||||||
|
|
||||||
auto to_read = min(bytes.size(), header_size - m_tar_stream.m_file_offset);
|
auto to_read = min(bytes.size(), header_size - m_tar_stream.m_file_offset);
|
||||||
|
|
||||||
auto nread = m_tar_stream.m_stream.read(bytes.trim(to_read));
|
auto slice = TRY(m_tar_stream.m_stream->read(bytes.trim(to_read)));
|
||||||
m_tar_stream.m_file_offset += nread;
|
m_tar_stream.m_file_offset += slice.size();
|
||||||
|
|
||||||
return bytes.slice(0, nread);
|
return slice;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TarFileStream::is_eof() const
|
bool TarFileStream::is_eof() const
|
||||||
|
@ -42,7 +42,7 @@ bool TarFileStream::is_eof() const
|
||||||
return true;
|
return true;
|
||||||
auto header_size = header_size_or_error.release_value();
|
auto header_size = header_size_or_error.release_value();
|
||||||
|
|
||||||
return m_tar_stream.m_stream.unreliable_eof()
|
return m_tar_stream.m_stream->is_eof()
|
||||||
|| m_tar_stream.m_file_offset >= header_size;
|
|| m_tar_stream.m_file_offset >= header_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,14 +52,24 @@ ErrorOr<size_t> TarFileStream::write(ReadonlyBytes)
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
TarInputStream::TarInputStream(InputStream& stream)
|
ErrorOr<NonnullOwnPtr<TarInputStream>> TarInputStream::construct(NonnullOwnPtr<Core::Stream::Stream> stream)
|
||||||
: m_stream(stream)
|
{
|
||||||
|
auto tar_stream = TRY(adopt_nonnull_own_or_enomem(new (nothrow) TarInputStream(move(stream))));
|
||||||
|
|
||||||
|
// Try and read the header.
|
||||||
|
auto header_span = TRY(tar_stream->m_stream->read(Bytes(&tar_stream->m_header, sizeof(m_header))));
|
||||||
|
if (header_span.size() != sizeof(m_header))
|
||||||
|
return Error::from_string_literal("Failed to read the entire header");
|
||||||
|
|
||||||
|
// Discard the rest of the block.
|
||||||
|
TRY(tar_stream->m_stream->discard(block_size - sizeof(TarFileHeader)));
|
||||||
|
|
||||||
|
return tar_stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
TarInputStream::TarInputStream(NonnullOwnPtr<Core::Stream::Stream> stream)
|
||||||
|
: m_stream(move(stream))
|
||||||
{
|
{
|
||||||
if (!m_stream.read_or_error(Bytes(&m_header, sizeof(m_header))) || !m_stream.discard_or_error(block_size - sizeof(TarFileHeader))) {
|
|
||||||
m_finished = true;
|
|
||||||
m_stream.handle_any_error(); // clear out errors so we don't assert
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr unsigned long block_ceiling(unsigned long offset)
|
static constexpr unsigned long block_ceiling(unsigned long offset)
|
||||||
|
@ -69,26 +79,27 @@ static constexpr unsigned long block_ceiling(unsigned long offset)
|
||||||
|
|
||||||
ErrorOr<void> TarInputStream::advance()
|
ErrorOr<void> TarInputStream::advance()
|
||||||
{
|
{
|
||||||
if (m_finished)
|
if (finished())
|
||||||
return Error::from_string_literal("Attempted to read a finished stream");
|
return Error::from_string_literal("Attempted to advance a finished stream");
|
||||||
|
|
||||||
m_generation++;
|
m_generation++;
|
||||||
|
|
||||||
auto header_size = TRY(m_header.size());
|
// Discard the pending bytes of the current entry.
|
||||||
VERIFY(m_stream.discard_or_error(block_ceiling(header_size) - m_file_offset));
|
auto file_size = TRY(m_header.size());
|
||||||
|
TRY(m_stream->discard(block_ceiling(file_size) - m_file_offset));
|
||||||
m_file_offset = 0;
|
m_file_offset = 0;
|
||||||
|
|
||||||
if (!m_stream.read_or_error(Bytes(&m_header, sizeof(m_header)))) {
|
// FIXME: This is not unlike the initial initialization. Maybe we should merge those two.
|
||||||
m_finished = true;
|
auto header_span = TRY(m_stream->read(Bytes(&m_header, sizeof(m_header))));
|
||||||
m_stream.handle_any_error(); // clear out errors so we don't assert
|
if (header_span.size() != sizeof(m_header))
|
||||||
return Error::from_string_literal("Failed to read the header");
|
return Error::from_string_literal("Failed to read the entire header");
|
||||||
}
|
|
||||||
if (!valid()) {
|
if (!valid())
|
||||||
m_finished = true;
|
return Error::from_string_literal("Header is not valid");
|
||||||
return {};
|
|
||||||
}
|
// Discard the rest of the header block.
|
||||||
|
TRY(m_stream->discard(block_size - sizeof(TarFileHeader)));
|
||||||
|
|
||||||
VERIFY(m_stream.discard_or_error(block_size - sizeof(TarFileHeader)));
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +122,7 @@ bool TarInputStream::valid() const
|
||||||
|
|
||||||
TarFileStream TarInputStream::file_contents()
|
TarFileStream TarInputStream::file_contents()
|
||||||
{
|
{
|
||||||
VERIFY(!m_finished);
|
VERIFY(!finished());
|
||||||
return TarFileStream(*this);
|
return TarFileStream(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,9 +33,9 @@ private:
|
||||||
|
|
||||||
class TarInputStream {
|
class TarInputStream {
|
||||||
public:
|
public:
|
||||||
TarInputStream(InputStream&);
|
static ErrorOr<NonnullOwnPtr<TarInputStream>> construct(NonnullOwnPtr<Core::Stream::Stream>);
|
||||||
ErrorOr<void> advance();
|
ErrorOr<void> advance();
|
||||||
bool finished() const { return m_finished; }
|
bool finished() const { return m_stream->is_eof(); }
|
||||||
bool valid() const;
|
bool valid() const;
|
||||||
TarFileHeader const& header() const { return m_header; }
|
TarFileHeader const& header() const { return m_header; }
|
||||||
TarFileStream file_contents();
|
TarFileStream file_contents();
|
||||||
|
@ -44,11 +44,12 @@ public:
|
||||||
ErrorOr<void> for_each_extended_header(F func);
|
ErrorOr<void> for_each_extended_header(F func);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
TarInputStream(NonnullOwnPtr<Core::Stream::Stream>);
|
||||||
|
|
||||||
TarFileHeader m_header;
|
TarFileHeader m_header;
|
||||||
InputStream& m_stream;
|
NonnullOwnPtr<Core::Stream::Stream> m_stream;
|
||||||
unsigned long m_file_offset { 0 };
|
unsigned long m_file_offset { 0 };
|
||||||
int m_generation { 0 };
|
int m_generation { 0 };
|
||||||
bool m_finished { false };
|
|
||||||
|
|
||||||
friend class TarFileStream;
|
friend class TarFileStream;
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include <LibCore/DirIterator.h>
|
#include <LibCore/DirIterator.h>
|
||||||
#include <LibCore/File.h>
|
#include <LibCore/File.h>
|
||||||
#include <LibCore/FileStream.h>
|
#include <LibCore/FileStream.h>
|
||||||
|
#include <LibCore/Stream.h>
|
||||||
#include <LibCore/System.h>
|
#include <LibCore/System.h>
|
||||||
#include <LibMain/Main.h>
|
#include <LibMain/Main.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
@ -61,22 +62,34 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list || extract) {
|
if (list || extract) {
|
||||||
auto file = Core::File::standard_input();
|
|
||||||
|
|
||||||
if (!archive_file.is_empty())
|
|
||||||
file = TRY(Core::File::open(archive_file, Core::OpenMode::ReadOnly));
|
|
||||||
|
|
||||||
if (!directory.is_empty())
|
if (!directory.is_empty())
|
||||||
TRY(Core::System::chdir(directory));
|
TRY(Core::System::chdir(directory));
|
||||||
|
|
||||||
Core::InputFileStream file_stream(file);
|
// FIXME: Remove these once we have smart pointers everywhere in LibArchive and LibCompress (or just ported the whole stack to Core::Stream).
|
||||||
Compress::GzipDecompressor gzip_stream(file_stream);
|
// Until then, we have to hold on to _some_ instance of the file AK::Stream.
|
||||||
|
// Note that this is only in use together with gzip.
|
||||||
|
OwnPtr<Core::InputFileStream> file_stream;
|
||||||
|
|
||||||
InputStream& file_input_stream = file_stream;
|
auto input_stream = TRY([&]() -> ErrorOr<NonnullOwnPtr<Core::Stream::Stream>> {
|
||||||
InputStream& gzip_input_stream = gzip_stream;
|
if (gzip) {
|
||||||
Archive::TarInputStream tar_stream((gzip) ? gzip_input_stream : file_input_stream);
|
// FIXME: Port gzip to Core::Stream.
|
||||||
|
auto file = Core::File::standard_input();
|
||||||
|
|
||||||
|
if (!archive_file.is_empty())
|
||||||
|
file = TRY(Core::File::open(archive_file, Core::OpenMode::ReadOnly));
|
||||||
|
|
||||||
|
file_stream = adopt_own(*new Core::InputFileStream(file));
|
||||||
|
NonnullOwnPtr<InputStream> gzip_stream = make<Compress::GzipDecompressor>(*file_stream);
|
||||||
|
|
||||||
|
return make<Core::Stream::WrappedAKInputStream>(move(gzip_stream));
|
||||||
|
} else {
|
||||||
|
return TRY(Core::Stream::File::open_file_or_standard_stream(archive_file, Core::Stream::OpenMode::Read));
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
|
||||||
|
auto tar_stream = TRY(Archive::TarInputStream::construct(move(input_stream)));
|
||||||
// FIXME: implement ErrorOr<TarInputStream>?
|
// FIXME: implement ErrorOr<TarInputStream>?
|
||||||
if (!tar_stream.valid()) {
|
if (!tar_stream->valid()) {
|
||||||
warnln("the provided file is not a well-formatted ustar file");
|
warnln("the provided file is not a well-formatted ustar file");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -98,14 +111,14 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
|
|
||||||
while (!tar_stream.finished()) {
|
while (!tar_stream->finished()) {
|
||||||
Archive::TarFileHeader const& header = tar_stream.header();
|
Archive::TarFileHeader const& header = tar_stream->header();
|
||||||
|
|
||||||
// Handle meta-entries earlier to avoid consuming the file content stream.
|
// Handle meta-entries earlier to avoid consuming the file content stream.
|
||||||
if (header.content_is_like_extended_header()) {
|
if (header.content_is_like_extended_header()) {
|
||||||
switch (header.type_flag()) {
|
switch (header.type_flag()) {
|
||||||
case Archive::TarFileType::GlobalExtendedHeader: {
|
case Archive::TarFileType::GlobalExtendedHeader: {
|
||||||
TRY(tar_stream.for_each_extended_header([&](StringView key, StringView value) {
|
TRY(tar_stream->for_each_extended_header([&](StringView key, StringView value) {
|
||||||
if (value.length() == 0)
|
if (value.length() == 0)
|
||||||
global_overrides.remove(key);
|
global_overrides.remove(key);
|
||||||
else
|
else
|
||||||
|
@ -114,7 +127,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Archive::TarFileType::ExtendedHeader: {
|
case Archive::TarFileType::ExtendedHeader: {
|
||||||
TRY(tar_stream.for_each_extended_header([&](StringView key, StringView value) {
|
TRY(tar_stream->for_each_extended_header([&](StringView key, StringView value) {
|
||||||
local_overrides.set(key, value);
|
local_overrides.set(key, value);
|
||||||
}));
|
}));
|
||||||
break;
|
break;
|
||||||
|
@ -124,11 +137,11 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
TRY(tar_stream.advance());
|
TRY(tar_stream->advance());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Archive::TarFileStream file_stream = tar_stream.file_contents();
|
Archive::TarFileStream file_stream = tar_stream->file_contents();
|
||||||
|
|
||||||
// Handle other header types that don't just have an effect on extraction.
|
// Handle other header types that don't just have an effect on extraction.
|
||||||
switch (header.type_flag()) {
|
switch (header.type_flag()) {
|
||||||
|
@ -143,7 +156,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
local_overrides.set("path", long_name.to_string());
|
local_overrides.set("path", long_name.to_string());
|
||||||
TRY(tar_stream.advance());
|
TRY(tar_stream->advance());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -204,9 +217,8 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
// Non-global headers should be cleared after every file.
|
// Non-global headers should be cleared after every file.
|
||||||
local_overrides.clear();
|
local_overrides.clear();
|
||||||
|
|
||||||
TRY(tar_stream.advance());
|
TRY(tar_stream->advance());
|
||||||
}
|
}
|
||||||
file_stream.close();
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue