1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 07:54:58 +00:00
serenity/Userland/Utilities/tar.cpp
Tim Schumacher d5871f5717 AK: Rename Stream::{read,write} to Stream::{read_some,write_some}
Similar to POSIX read, the basic read and write functions of AK::Stream
do not have a lower limit of how much data they read or write (apart
from "none at all").

Rename the functions to "read some [data]" and "write some [data]" (with
"data" being omitted, since everything here is reading and writing data)
to make them sufficiently distinct from the functions that ensure to
use the entire buffer (which should be the go-to function for most
usages).

No functional changes, just a lot of new FIXMEs.
2023-03-13 15:16:20 +00:00

285 lines
11 KiB
C++

/*
* Copyright (c) 2020, Peter Elliott <pelliott@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <AK/LexicalPath.h>
#include <AK/Span.h>
#include <AK/Vector.h>
#include <LibArchive/TarStream.h>
#include <LibCompress/Gzip.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/DeprecatedFile.h>
#include <LibCore/DirIterator.h>
#include <LibCore/Directory.h>
#include <LibCore/System.h>
#include <LibMain/Main.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
constexpr size_t buffer_size = 4096;
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
bool create = false;
bool extract = false;
bool list = false;
bool verbose = false;
bool gzip = false;
bool no_auto_compress = false;
StringView archive_file;
bool dereference;
StringView directory;
Vector<DeprecatedString> paths;
Core::ArgsParser args_parser;
args_parser.add_option(create, "Create archive", "create", 'c');
args_parser.add_option(extract, "Extract archive", "extract", 'x');
args_parser.add_option(list, "List contents", "list", 't');
args_parser.add_option(verbose, "Print paths", "verbose", 'v');
args_parser.add_option(gzip, "Compress or decompress file using gzip", "gzip", 'z');
args_parser.add_option(no_auto_compress, "Do not use the archive suffix to select the compression algorithm", "no-auto-compress", 0);
args_parser.add_option(directory, "Directory to extract to/create from", "directory", 'C', "DIRECTORY");
args_parser.add_option(archive_file, "Archive file", "file", 'f', "FILE");
args_parser.add_option(dereference, "Follow symlinks", "dereference", 'h');
args_parser.add_positional_argument(paths, "Paths", "PATHS", Core::ArgsParser::Required::No);
args_parser.parse(arguments);
if (create + extract + list != 1) {
warnln("exactly one of -c, -x, and -t can be used");
return 1;
}
if (!no_auto_compress && !archive_file.is_empty()) {
if (archive_file.ends_with(".gz"sv) || archive_file.ends_with(".tgz"sv))
gzip = true;
}
if (list || extract) {
if (!directory.is_empty())
TRY(Core::System::chdir(directory));
NonnullOwnPtr<Stream> input_stream = TRY(Core::File::open_file_or_standard_stream(archive_file, Core::File::OpenMode::Read));
if (gzip)
input_stream = make<Compress::GzipDecompressor>(move(input_stream));
auto tar_stream = TRY(Archive::TarInputStream::construct(move(input_stream)));
HashMap<DeprecatedString, DeprecatedString> global_overrides;
HashMap<DeprecatedString, DeprecatedString> local_overrides;
auto get_override = [&](StringView key) -> Optional<DeprecatedString> {
Optional<DeprecatedString> maybe_local = local_overrides.get(key);
if (maybe_local.has_value())
return maybe_local;
Optional<DeprecatedString> maybe_global = global_overrides.get(key);
if (maybe_global.has_value())
return maybe_global;
return {};
};
while (!tar_stream->finished()) {
Archive::TarFileHeader const& header = tar_stream->header();
// Handle meta-entries earlier to avoid consuming the file content stream.
if (header.content_is_like_extended_header()) {
switch (header.type_flag()) {
case Archive::TarFileType::GlobalExtendedHeader: {
TRY(tar_stream->for_each_extended_header([&](StringView key, StringView value) {
if (value.length() == 0)
global_overrides.remove(key);
else
global_overrides.set(key, value);
}));
break;
}
case Archive::TarFileType::ExtendedHeader: {
TRY(tar_stream->for_each_extended_header([&](StringView key, StringView value) {
local_overrides.set(key, value);
}));
break;
}
default:
warnln("Unknown extended header type '{}' of {}", (char)header.type_flag(), header.filename());
VERIFY_NOT_REACHED();
}
TRY(tar_stream->advance());
continue;
}
Archive::TarFileStream file_stream = tar_stream->file_contents();
// Handle other header types that don't just have an effect on extraction.
switch (header.type_flag()) {
case Archive::TarFileType::LongName: {
StringBuilder long_name;
Array<u8, buffer_size> buffer;
while (!file_stream.is_eof()) {
auto slice = TRY(file_stream.read_some(buffer));
long_name.append(reinterpret_cast<char*>(slice.data()), slice.size());
}
local_overrides.set("path", long_name.to_deprecated_string());
TRY(tar_stream->advance());
continue;
}
default:
// None of the relevant headers, so continue as normal.
break;
}
LexicalPath path = LexicalPath(header.filename());
if (!header.prefix().is_empty())
path = path.prepend(header.prefix());
DeprecatedString filename = get_override("path"sv).value_or(path.string());
if (list || verbose)
outln("{}", filename);
if (extract) {
DeprecatedString absolute_path = Core::DeprecatedFile::absolute_path(filename);
auto parent_path = LexicalPath(absolute_path).parent();
auto header_mode = TRY(header.mode());
switch (header.type_flag()) {
case Archive::TarFileType::NormalFile:
case Archive::TarFileType::AlternateNormalFile: {
MUST(Core::Directory::create(parent_path, Core::Directory::CreateDirectories::Yes));
int fd = TRY(Core::System::open(absolute_path, O_CREAT | O_WRONLY, header_mode));
Array<u8, buffer_size> buffer;
while (!file_stream.is_eof()) {
auto slice = TRY(file_stream.read_some(buffer));
TRY(Core::System::write(fd, slice));
}
TRY(Core::System::close(fd));
break;
}
case Archive::TarFileType::SymLink: {
MUST(Core::Directory::create(parent_path, Core::Directory::CreateDirectories::Yes));
TRY(Core::System::symlink(header.link_name(), absolute_path));
break;
}
case Archive::TarFileType::Directory: {
MUST(Core::Directory::create(parent_path, Core::Directory::CreateDirectories::Yes));
auto result_or_error = Core::System::mkdir(absolute_path, header_mode);
if (result_or_error.is_error() && result_or_error.error().code() != EEXIST)
return result_or_error.release_error();
break;
}
default:
// FIXME: Implement other file types
warnln("file type '{}' of {} is not yet supported", (char)header.type_flag(), header.filename());
VERIFY_NOT_REACHED();
}
}
// Non-global headers should be cleared after every file.
local_overrides.clear();
TRY(tar_stream->advance());
}
return 0;
}
if (create) {
if (paths.size() == 0) {
warnln("you must provide at least one path to be archived");
return 1;
}
NonnullOwnPtr<Stream> output_stream = TRY(Core::File::standard_output());
if (!archive_file.is_empty())
output_stream = TRY(Core::File::open(archive_file, Core::File::OpenMode::Write));
if (!directory.is_empty())
TRY(Core::System::chdir(directory));
if (gzip)
output_stream = TRY(try_make<Compress::GzipCompressor>(move(output_stream)));
Archive::TarOutputStream tar_stream(move(output_stream));
auto add_file = [&](DeprecatedString path) -> ErrorOr<void> {
auto file = Core::DeprecatedFile::construct(path);
if (!file->open(Core::OpenMode::ReadOnly)) {
warnln("Failed to open {}: {}", path, file->error_string());
return {};
}
auto statbuf = TRY(Core::System::lstat(path));
auto canonicalized_path = TRY(String::from_deprecated_string(LexicalPath::canonicalized_path(path)));
TRY(tar_stream.add_file(canonicalized_path, statbuf.st_mode, file->read_all()));
if (verbose)
outln("{}", canonicalized_path);
return {};
};
auto add_link = [&](DeprecatedString path) -> ErrorOr<void> {
auto statbuf = TRY(Core::System::lstat(path));
auto canonicalized_path = TRY(String::from_deprecated_string(LexicalPath::canonicalized_path(path)));
TRY(tar_stream.add_link(canonicalized_path, statbuf.st_mode, TRY(Core::System::readlink(path))));
if (verbose)
outln("{}", canonicalized_path);
return {};
};
auto add_directory = [&](DeprecatedString path, auto handle_directory) -> ErrorOr<void> {
auto statbuf = TRY(Core::System::lstat(path));
auto canonicalized_path = TRY(String::from_deprecated_string(LexicalPath::canonicalized_path(path)));
TRY(tar_stream.add_directory(canonicalized_path, statbuf.st_mode));
if (verbose)
outln("{}", canonicalized_path);
Core::DirIterator it(path, Core::DirIterator::Flags::SkipParentAndBaseDir);
while (it.has_next()) {
auto child_path = it.next_full_path();
if (!dereference && Core::DeprecatedFile::is_link(child_path)) {
TRY(add_link(child_path));
} else if (!Core::DeprecatedFile::is_directory(child_path)) {
TRY(add_file(child_path));
} else {
TRY(handle_directory(child_path, handle_directory));
}
}
return {};
};
for (auto const& path : paths) {
if (Core::DeprecatedFile::is_directory(path)) {
TRY(add_directory(path, add_directory));
} else {
TRY(add_file(path));
}
}
TRY(tar_stream.finish());
return 0;
}
return 0;
}