mirror of
https://github.com/SerenityOS/serenity
synced 2026-04-25 17:15:42 +02:00
LibArchive/Zip+Utilities: Support unix permissions
Implements reading and writing unix permissions for LibArchive/Zip and the zip,unzip utilities.
This commit is contained in:
committed by
Tim Schumacher
parent
c649e7074d
commit
8abb4806ae
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2022, the SerenityOS developers.
|
||||
* Copyright (c) 2022-2025, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
@@ -94,7 +94,10 @@ ErrorOr<bool> Zip::for_each_member(Function<ErrorOr<IterationDecision>(ZipMember
|
||||
member.crc32 = central_directory_record.crc32;
|
||||
member.modification_time = central_directory_record.modification_time;
|
||||
member.modification_date = central_directory_record.modification_date;
|
||||
member.is_directory = central_directory_record.external_attributes & zip_directory_external_attribute || member.name.bytes_as_string_view().ends_with('/'); // FIXME: better directory detection
|
||||
member.is_directory = central_directory_record.external_attributes.msdos & zip_directory_msdos_attribute || member.name.bytes_as_string_view().ends_with('/'); // FIXME: better directory detection
|
||||
if (central_directory_record.made_by_version.made_by == ZipMadeBy::Unix) {
|
||||
member.mode = static_cast<mode_t>(central_directory_record.external_attributes.unix);
|
||||
}
|
||||
|
||||
if (TRY(callback(member)) == IterationDecision::Break)
|
||||
return false;
|
||||
@@ -158,7 +161,7 @@ ErrorOr<void> ZipOutputStream::add_member(ZipMember const& member)
|
||||
return local_file_header.write(*m_stream);
|
||||
}
|
||||
|
||||
ErrorOr<ZipOutputStream::MemberInformation> ZipOutputStream::add_member_from_stream(StringView path, Stream& stream, Optional<Core::DateTime> const& modification_time)
|
||||
ErrorOr<ZipOutputStream::MemberInformation> ZipOutputStream::add_member_from_stream(StringView path, Stream& stream, Optional<Core::DateTime> const& modification_time, Optional<mode_t> mode)
|
||||
{
|
||||
auto buffer = TRY(stream.read_until_eof());
|
||||
|
||||
@@ -190,13 +193,14 @@ ErrorOr<ZipOutputStream::MemberInformation> ZipOutputStream::add_member_from_str
|
||||
Crypto::Checksum::CRC32 checksum { buffer.bytes() };
|
||||
member.crc32 = checksum.digest();
|
||||
member.is_directory = false;
|
||||
member.mode = mode;
|
||||
|
||||
TRY(add_member(member));
|
||||
|
||||
return MemberInformation { compression_ratio, compressed_size };
|
||||
}
|
||||
|
||||
ErrorOr<void> ZipOutputStream::add_directory(StringView name, Optional<Core::DateTime> const& modification_time)
|
||||
ErrorOr<void> ZipOutputStream::add_directory(StringView name, Optional<Core::DateTime> const& modification_time, Optional<mode_t> mode)
|
||||
{
|
||||
Archive::ZipMember member {};
|
||||
member.name = TRY(String::from_utf8(name));
|
||||
@@ -205,6 +209,7 @@ ErrorOr<void> ZipOutputStream::add_directory(StringView name, Optional<Core::Dat
|
||||
member.uncompressed_size = 0;
|
||||
member.crc32 = 0;
|
||||
member.is_directory = true;
|
||||
member.mode = mode;
|
||||
|
||||
if (modification_time.has_value()) {
|
||||
member.modification_date = to_packed_dos_date(modification_time->year(), modification_time->month(), modification_time->day());
|
||||
@@ -224,7 +229,7 @@ ErrorOr<void> ZipOutputStream::finish()
|
||||
for (ZipMember const& member : m_members) {
|
||||
auto zip_version = minimum_version_needed(member.compression_method);
|
||||
CentralDirectoryRecord central_directory_record {
|
||||
.made_by_version = zip_version,
|
||||
.made_by_version = { .version = static_cast<u8>(zip_version), .made_by = ZipMadeBy::Unix },
|
||||
.minimum_version = zip_version,
|
||||
.general_purpose_flags = { .flags = 0 },
|
||||
.compression_method = member.compression_method,
|
||||
@@ -238,7 +243,10 @@ ErrorOr<void> ZipOutputStream::finish()
|
||||
.comment_length = 0,
|
||||
.start_disk = 0,
|
||||
.internal_attributes = 0,
|
||||
.external_attributes = member.is_directory ? zip_directory_external_attribute : 0,
|
||||
.external_attributes = {
|
||||
.msdos = static_cast<u16>(member.is_directory ? zip_directory_msdos_attribute : 0),
|
||||
.unix = static_cast<u16>(member.mode.value_or(0)),
|
||||
},
|
||||
.local_file_header_offset = file_header_offset, // FIXME: we assume the wrapped output stream was never written to before us
|
||||
.name = reinterpret_cast<u8 const*>(member.name.bytes_as_string_view().characters_without_null_termination()),
|
||||
.extra_data = nullptr,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2022, the SerenityOS developers.
|
||||
* Copyright (c) 2022-2025, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
@@ -33,6 +33,7 @@ static bool read_helper(ReadonlyBytes buffer, T* self)
|
||||
}
|
||||
|
||||
// NOTE: Due to the format of zip files compression is streamed and decompression is random access.
|
||||
// Zip format specification: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
|
||||
|
||||
static constexpr auto signature_length = 4;
|
||||
|
||||
@@ -109,10 +110,18 @@ union ZipGeneralPurposeFlags {
|
||||
};
|
||||
static_assert(sizeof(ZipGeneralPurposeFlags) == sizeof(u16));
|
||||
|
||||
enum class ZipMadeBy : u8 {
|
||||
MSDOS = 0,
|
||||
Unix = 3,
|
||||
};
|
||||
|
||||
struct [[gnu::packed]] CentralDirectoryRecord {
|
||||
static constexpr Array<u8, signature_length> signature = { 0x50, 0x4b, 0x01, 0x02 }; // 'PK\x01\x02'
|
||||
|
||||
u16 made_by_version;
|
||||
struct {
|
||||
u8 version;
|
||||
ZipMadeBy made_by;
|
||||
} made_by_version;
|
||||
u16 minimum_version;
|
||||
ZipGeneralPurposeFlags general_purpose_flags;
|
||||
ZipCompressionMethod compression_method;
|
||||
@@ -126,7 +135,10 @@ struct [[gnu::packed]] CentralDirectoryRecord {
|
||||
u16 comment_length;
|
||||
u16 start_disk;
|
||||
u16 internal_attributes;
|
||||
u32 external_attributes;
|
||||
struct {
|
||||
u16 msdos;
|
||||
u16 unix;
|
||||
} external_attributes;
|
||||
u32 local_file_header_offset;
|
||||
u8 const* name;
|
||||
u8 const* extra_data;
|
||||
@@ -182,7 +194,7 @@ struct [[gnu::packed]] CentralDirectoryRecord {
|
||||
return signature.size() + (sizeof(CentralDirectoryRecord) - (sizeof(u8*) * 3)) + name_length + extra_data_length + comment_length;
|
||||
}
|
||||
};
|
||||
static constexpr u32 zip_directory_external_attribute = 1 << 4;
|
||||
static constexpr u16 zip_directory_msdos_attribute = 1 << 4;
|
||||
|
||||
struct [[gnu::packed]] LocalFileHeader {
|
||||
static constexpr Array<u8, signature_length> signature = { 0x50, 0x4b, 0x03, 0x04 }; // 'PK\x03\x04'
|
||||
@@ -250,6 +262,7 @@ struct ZipMember {
|
||||
bool is_directory;
|
||||
DOSPackedTime modification_time;
|
||||
DOSPackedDate modification_date;
|
||||
Optional<mode_t> mode;
|
||||
};
|
||||
|
||||
class Zip {
|
||||
@@ -282,11 +295,11 @@ public:
|
||||
ZipOutputStream(NonnullOwnPtr<Stream>);
|
||||
|
||||
ErrorOr<void> add_member(ZipMember const&);
|
||||
ErrorOr<MemberInformation> add_member_from_stream(StringView, Stream&, Optional<Core::DateTime> const& = {});
|
||||
ErrorOr<MemberInformation> add_member_from_stream(StringView, Stream&, Optional<Core::DateTime> const& = {}, Optional<mode_t> mode = {});
|
||||
|
||||
// NOTE: This does not add any of the files within the directory,
|
||||
// it just adds an entry for it.
|
||||
ErrorOr<void> add_directory(StringView, Optional<Core::DateTime> const& = {});
|
||||
ErrorOr<void> add_directory(StringView, Optional<Core::DateTime> const& = {}, Optional<mode_t> mode = {});
|
||||
|
||||
ErrorOr<void> finish();
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Andrés Vieira <anvieiravazquez@gmail.com>
|
||||
* Copyright (c) 2025, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
@@ -44,7 +45,8 @@ static bool unpack_zip_member(Archive::ZipMember zip_member, bool quiet)
|
||||
return true;
|
||||
}
|
||||
MUST(Core::Directory::create(LexicalPath(zip_member.name.to_byte_string()).parent(), Core::Directory::CreateDirectories::Yes));
|
||||
auto new_file_or_error = Core::File::open(zip_member.name.to_byte_string(), Core::File::OpenMode::Write);
|
||||
mode_t file_permissions = zip_member.mode.value_or(0644) & 0777;
|
||||
auto new_file_or_error = Core::File::open(zip_member.name.to_byte_string(), Core::File::OpenMode::Write, file_permissions);
|
||||
if (new_file_or_error.is_error()) {
|
||||
warnln("Can't write file {}: {}", zip_member.name, new_file_or_error.release_error());
|
||||
return false;
|
||||
@@ -197,6 +199,13 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||
warnln("Failed setting modification time for directory {}", directory.name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (directory.mode.has_value()) {
|
||||
if (Core::System::chmod(directory.name, directory.mode.value() & 0777).is_error()) {
|
||||
warnln("Failed setting permissions for directory {}", directory.name);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return success ? 0 : 1;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2025, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
@@ -56,7 +57,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||
auto stat = TRY(Core::System::fstat(file->fd()));
|
||||
auto date = Core::DateTime::from_timestamp(stat.st_mtim.tv_sec);
|
||||
|
||||
auto information = TRY(zip_stream.add_member_from_stream(canonicalized_path, *file, date));
|
||||
auto information = TRY(zip_stream.add_member_from_stream(canonicalized_path, *file, date, stat.st_mode));
|
||||
if (information.compression_ratio < 1.f) {
|
||||
outln(" adding: {} (deflated {}%)", canonicalized_path, (int)(information.compression_ratio * 100));
|
||||
} else {
|
||||
@@ -71,7 +72,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||
|
||||
auto stat = TRY(Core::System::stat(path));
|
||||
auto date = Core::DateTime::from_timestamp(stat.st_mtim.tv_sec);
|
||||
TRY(zip_stream.add_directory(canonicalized_path, date));
|
||||
TRY(zip_stream.add_directory(canonicalized_path, date, stat.st_mode));
|
||||
|
||||
if (!recurse)
|
||||
return {};
|
||||
|
||||
Reference in New Issue
Block a user