diff --git a/Userland/Libraries/LibArchive/TarStream.cpp b/Userland/Libraries/LibArchive/TarStream.cpp index bd45ab67b0..6ae5a1c938 100644 --- a/Userland/Libraries/LibArchive/TarStream.cpp +++ b/Userland/Libraries/LibArchive/TarStream.cpp @@ -165,6 +165,23 @@ void TarOutputStream::add_file(String const& path, mode_t mode, ReadonlyBytes by VERIFY(m_stream.write_or_error(ReadonlyBytes { &padding, block_size - (n_written % block_size) })); } +void TarOutputStream::add_link(String const& path, mode_t mode, StringView link_name) +{ + VERIFY(!m_finished); + TarFileHeader header {}; + header.set_size(0); + header.set_filename_and_prefix(path); + header.set_type_flag(TarFileType::SymLink); + header.set_mode(mode); + header.set_magic(gnu_magic); + header.set_version(gnu_version); + header.set_link_name(link_name); + 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::finish() { VERIFY(!m_finished); diff --git a/Userland/Libraries/LibArchive/TarStream.h b/Userland/Libraries/LibArchive/TarStream.h index a27c30c971..6fa668ec29 100644 --- a/Userland/Libraries/LibArchive/TarStream.h +++ b/Userland/Libraries/LibArchive/TarStream.h @@ -57,6 +57,7 @@ class TarOutputStream { public: TarOutputStream(OutputStream&); void add_file(String const& path, mode_t, ReadonlyBytes); + void add_link(String const& path, mode_t, StringView); void add_directory(String const& path, mode_t); void finish(); diff --git a/Userland/Utilities/tar.cpp b/Userland/Utilities/tar.cpp index bfe28640ec..a165abcebe 100644 --- a/Userland/Utilities/tar.cpp +++ b/Userland/Utilities/tar.cpp @@ -33,6 +33,7 @@ ErrorOr serenity_main(Main::Arguments arguments) bool gzip = false; bool no_auto_compress = false; StringView archive_file; + bool dereference; StringView directory; Vector paths; @@ -45,6 +46,7 @@ ErrorOr serenity_main(Main::Arguments arguments) 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); @@ -230,11 +232,7 @@ ErrorOr serenity_main(Main::Arguments arguments) return {}; } - auto statbuf_or_error = Core::System::lstat(path); - if (statbuf_or_error.is_error()) - return statbuf_or_error.error(); - - auto statbuf = statbuf_or_error.value(); + auto statbuf = TRY(Core::System::lstat(path)); auto canonicalized_path = LexicalPath::canonicalized_path(path); tar_stream.add_file(canonicalized_path, statbuf.st_mode, file->read_all()); if (verbose) @@ -243,12 +241,20 @@ ErrorOr serenity_main(Main::Arguments arguments) return {}; }; - auto add_directory = [&](String path, auto handle_directory) -> ErrorOr { - auto statbuf_or_error = Core::System::lstat(path); - if (statbuf_or_error.is_error()) - return statbuf_or_error.error(); + auto add_link = [&](String path) -> ErrorOr { + auto statbuf = TRY(Core::System::lstat(path)); + + auto canonicalized_path = LexicalPath::canonicalized_path(path); + tar_stream.add_link(canonicalized_path, statbuf.st_mode, TRY(Core::System::readlink(path))); + if (verbose) + outln("{}", canonicalized_path); + + return {}; + }; + + auto add_directory = [&](String path, auto handle_directory) -> ErrorOr { + auto statbuf = TRY(Core::System::lstat(path)); - auto statbuf = statbuf_or_error.value(); auto canonicalized_path = LexicalPath::canonicalized_path(path); tar_stream.add_directory(canonicalized_path, statbuf.st_mode); if (verbose) @@ -257,7 +263,9 @@ ErrorOr serenity_main(Main::Arguments arguments) Core::DirIterator it(path, Core::DirIterator::Flags::SkipParentAndBaseDir); while (it.has_next()) { auto child_path = it.next_full_path(); - if (!Core::File::is_directory(child_path)) { + if (!dereference && Core::File::is_link(child_path)) { + TRY(add_link(child_path)); + } else if (!Core::File::is_directory(child_path)) { TRY(add_file(child_path)); } else { TRY(handle_directory(child_path, handle_directory));