Skip to content

Commit d406ca1

Browse files
committed
LibArchive/Zip+Utilities: Support unix permissions
Implements reading and writing unix permissions for LibArchive/Zip and the zip,unzip utilities.
1 parent 103d6a0 commit d406ca1

File tree

4 files changed

+52
-12
lines changed

4 files changed

+52
-12
lines changed

Userland/Libraries/LibArchive/Zip.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,10 @@ ErrorOr<bool> Zip::for_each_member(Function<ErrorOr<IterationDecision>(ZipMember
9494
member.crc32 = central_directory_record.crc32;
9595
member.modification_time = central_directory_record.modification_time;
9696
member.modification_date = central_directory_record.modification_date;
97-
member.is_directory = central_directory_record.external_attributes & zip_directory_external_attribute || member.name.bytes_as_string_view().ends_with('/'); // FIXME: better directory detection
97+
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
98+
if (central_directory_record.made_by_version.made_by == zip_made_by_unix) {
99+
member.mode = static_cast<mode_t>(central_directory_record.external_attributes.unix);
100+
}
98101

99102
if (TRY(callback(member)) == IterationDecision::Break)
100103
return false;
@@ -158,7 +161,7 @@ ErrorOr<void> ZipOutputStream::add_member(ZipMember const& member)
158161
return local_file_header.write(*m_stream);
159162
}
160163

161-
ErrorOr<ZipOutputStream::MemberInformation> ZipOutputStream::add_member_from_stream(StringView path, Stream& stream, Optional<Core::DateTime> const& modification_time)
164+
ErrorOr<ZipOutputStream::MemberInformation> ZipOutputStream::add_member_from_stream(StringView path, Stream& stream, Optional<Core::DateTime> const& modification_time, Optional<mode_t> mode)
162165
{
163166
auto buffer = TRY(stream.read_until_eof());
164167

@@ -190,13 +193,14 @@ ErrorOr<ZipOutputStream::MemberInformation> ZipOutputStream::add_member_from_str
190193
Crypto::Checksum::CRC32 checksum { buffer.bytes() };
191194
member.crc32 = checksum.digest();
192195
member.is_directory = false;
196+
member.mode = mode;
193197

194198
TRY(add_member(member));
195199

196200
return MemberInformation { compression_ratio, compressed_size };
197201
}
198202

199-
ErrorOr<void> ZipOutputStream::add_directory(StringView name, Optional<Core::DateTime> const& modification_time)
203+
ErrorOr<void> ZipOutputStream::add_directory(StringView name, Optional<Core::DateTime> const& modification_time, Optional<mode_t> mode)
200204
{
201205
Archive::ZipMember member {};
202206
member.name = TRY(String::from_utf8(name));
@@ -205,6 +209,7 @@ ErrorOr<void> ZipOutputStream::add_directory(StringView name, Optional<Core::Dat
205209
member.uncompressed_size = 0;
206210
member.crc32 = 0;
207211
member.is_directory = true;
212+
member.mode = mode;
208213

209214
if (modification_time.has_value()) {
210215
member.modification_date = to_packed_dos_date(modification_time->year(), modification_time->month(), modification_time->day());
@@ -223,8 +228,12 @@ ErrorOr<void> ZipOutputStream::finish()
223228
auto central_directory_size = 0u;
224229
for (ZipMember const& member : m_members) {
225230
auto zip_version = minimum_version_needed(member.compression_method);
231+
ZipExternalAttributes external_attributes = {
232+
.msdos = static_cast<u16>(member.is_directory ? zip_directory_msdos_attribute : 0),
233+
.unix = member.mode.value_or(0),
234+
};
226235
CentralDirectoryRecord central_directory_record {
227-
.made_by_version = zip_version,
236+
.made_by_version = { .version = static_cast<u16>(zip_version), .made_by = zip_made_by_unix },
228237
.minimum_version = zip_version,
229238
.general_purpose_flags = { .flags = 0 },
230239
.compression_method = member.compression_method,
@@ -238,7 +247,7 @@ ErrorOr<void> ZipOutputStream::finish()
238247
.comment_length = 0,
239248
.start_disk = 0,
240249
.internal_attributes = 0,
241-
.external_attributes = member.is_directory ? zip_directory_external_attribute : 0,
250+
.external_attributes = external_attributes,
242251
.local_file_header_offset = file_header_offset, // FIXME: we assume the wrapped output stream was never written to before us
243252
.name = reinterpret_cast<u8 const*>(member.name.bytes_as_string_view().characters_without_null_termination()),
244253
.extra_data = nullptr,

Userland/Libraries/LibArchive/Zip.h

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ static bool read_helper(ReadonlyBytes buffer, T* self)
3333
}
3434

3535
// NOTE: Due to the format of zip files compression is streamed and decompression is random access.
36+
// Zip format specification: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
3637

3738
static constexpr auto signature_length = 4;
3839

@@ -109,10 +110,20 @@ union ZipGeneralPurposeFlags {
109110
};
110111
static_assert(sizeof(ZipGeneralPurposeFlags) == sizeof(u16));
111112

113+
struct ZipMadeByVersion {
114+
u16 version : 8;
115+
u16 made_by : 8;
116+
};
117+
118+
struct ZipExternalAttributes {
119+
u32 msdos : 16;
120+
u32 unix : 16;
121+
};
122+
112123
struct [[gnu::packed]] CentralDirectoryRecord {
113124
static constexpr Array<u8, signature_length> signature = { 0x50, 0x4b, 0x01, 0x02 }; // 'PK\x01\x02'
114125

115-
u16 made_by_version;
126+
ZipMadeByVersion made_by_version;
116127
u16 minimum_version;
117128
ZipGeneralPurposeFlags general_purpose_flags;
118129
ZipCompressionMethod compression_method;
@@ -126,7 +137,7 @@ struct [[gnu::packed]] CentralDirectoryRecord {
126137
u16 comment_length;
127138
u16 start_disk;
128139
u16 internal_attributes;
129-
u32 external_attributes;
140+
ZipExternalAttributes external_attributes;
130141
u32 local_file_header_offset;
131142
u8 const* name;
132143
u8 const* extra_data;
@@ -182,7 +193,8 @@ struct [[gnu::packed]] CentralDirectoryRecord {
182193
return signature.size() + (sizeof(CentralDirectoryRecord) - (sizeof(u8*) * 3)) + name_length + extra_data_length + comment_length;
183194
}
184195
};
185-
static constexpr u32 zip_directory_external_attribute = 1 << 4;
196+
static constexpr u16 zip_directory_msdos_attribute = 1 << 4;
197+
static constexpr u8 zip_made_by_unix = 3;
186198

187199
struct [[gnu::packed]] LocalFileHeader {
188200
static constexpr Array<u8, signature_length> signature = { 0x50, 0x4b, 0x03, 0x04 }; // 'PK\x03\x04'
@@ -250,6 +262,7 @@ struct ZipMember {
250262
bool is_directory;
251263
DOSPackedTime modification_time;
252264
DOSPackedDate modification_date;
265+
Optional<mode_t> mode;
253266
};
254267

255268
class Zip {
@@ -282,11 +295,11 @@ class ZipOutputStream {
282295
ZipOutputStream(NonnullOwnPtr<Stream>);
283296

284297
ErrorOr<void> add_member(ZipMember const&);
285-
ErrorOr<MemberInformation> add_member_from_stream(StringView, Stream&, Optional<Core::DateTime> const& = {});
298+
ErrorOr<MemberInformation> add_member_from_stream(StringView, Stream&, Optional<Core::DateTime> const& = {}, Optional<mode_t> mode = {});
286299

287300
// NOTE: This does not add any of the files within the directory,
288301
// it just adds an entry for it.
289-
ErrorOr<void> add_directory(StringView, Optional<Core::DateTime> const& = {});
302+
ErrorOr<void> add_directory(StringView, Optional<Core::DateTime> const& = {}, Optional<mode_t> mode = {});
290303

291304
ErrorOr<void> finish();
292305

Userland/Utilities/unzip.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ static ErrorOr<void> adjust_modification_time(Archive::ZipMember const& zip_memb
3232
return Core::System::utime(zip_member.name, buf);
3333
}
3434

35+
static ErrorOr<void> adjust_permissions(Archive::ZipMember const& zip_member)
36+
{
37+
if (zip_member.mode.has_value())
38+
return Core::System::chmod(zip_member.name, zip_member.mode.value() & 0777);
39+
40+
return {};
41+
}
42+
3543
static bool unpack_zip_member(Archive::ZipMember zip_member, bool quiet)
3644
{
3745
if (zip_member.is_directory) {
@@ -90,6 +98,11 @@ static bool unpack_zip_member(Archive::ZipMember zip_member, bool quiet)
9098
return false;
9199
}
92100

101+
if (adjust_permissions(zip_member).is_error()) {
102+
warnln("Failed setting permissions for file {}", zip_member.name);
103+
return false;
104+
}
105+
93106
new_file->close();
94107

95108
if (checksum.digest() != zip_member.crc32) {
@@ -197,6 +210,11 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
197210
warnln("Failed setting modification time for directory {}", directory.name);
198211
return 1;
199212
}
213+
214+
if (adjust_permissions(directory).is_error()) {
215+
warnln("Failed setting permissions for directory {}", directory.name);
216+
return 1;
217+
}
200218
}
201219

202220
return success ? 0 : 1;

Userland/Utilities/zip.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
5656
auto stat = TRY(Core::System::fstat(file->fd()));
5757
auto date = Core::DateTime::from_timestamp(stat.st_mtim.tv_sec);
5858

59-
auto information = TRY(zip_stream.add_member_from_stream(canonicalized_path, *file, date));
59+
auto information = TRY(zip_stream.add_member_from_stream(canonicalized_path, *file, date, stat.st_mode));
6060
if (information.compression_ratio < 1.f) {
6161
outln(" adding: {} (deflated {}%)", canonicalized_path, (int)(information.compression_ratio * 100));
6262
} else {
@@ -71,7 +71,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
7171

7272
auto stat = TRY(Core::System::stat(path));
7373
auto date = Core::DateTime::from_timestamp(stat.st_mtim.tv_sec);
74-
TRY(zip_stream.add_directory(canonicalized_path, date));
74+
TRY(zip_stream.add_directory(canonicalized_path, date, stat.st_mode));
7575

7676
if (!recurse)
7777
return {};

0 commit comments

Comments
 (0)