From 212ac993304af4200b001a460020a1a40c8799ce Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Mon, 4 May 2026 10:41:42 +0200 Subject: [PATCH] Restore symlink mtime on Linux after extracting an entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The symlink path in `FileManager.setAttributes(_:ofItemAtURL:traverseLink:)` has been an Apple-only no-op since the file's `#if` was added: every non-Darwin platform took the `return`, dropping both stored permission bits *and* the stored modification date for symlink entries. Permission bits genuinely don't apply on Linux — the kernel ignores them on symlinks no matter what `lchmod` does — so leaving that half of the codepath off matches reality. The modification date is a different story. `lutimes(2)` writes to the symlink itself rather than chasing the link, has shipped in Glibc since 2.6, and the existing `setSymlinkModificationDate` helper already calls it correctly. The only reason it wasn't being invoked on Linux is that the call site was Apple-gated. Add a Linux branch that calls `setSymlinkModificationDate` (and only that) so extracting a ZIP archive on Linux preserves stored symlink mtimes — same fidelity Apple platforms already get. Bionic and Windows fall through to the existing no-op path; Bionic ships `lutimes` only behind `__INTRODUCED_IN(26)`, and Windows has no equivalent symlink-targeted utimes call, so leave them alone here. Comment on the `#else` branch is updated to reflect what's actually left out (Bionic + Windows, not "non-Darwin POSIX" generally). --- Sources/ZIPFoundation/FileManager+ZIP.swift | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Sources/ZIPFoundation/FileManager+ZIP.swift b/Sources/ZIPFoundation/FileManager+ZIP.swift index 0b774795..9e157a80 100644 --- a/Sources/ZIPFoundation/FileManager+ZIP.swift +++ b/Sources/ZIPFoundation/FileManager+ZIP.swift @@ -185,11 +185,25 @@ extension FileManager { throw Entry.EntryError.missingModificationDateAttributeError } + try self.setSymlinkModificationDate(modificationDate, ofItemAtURL: url) +#elseif os(Linux) + // Permission bits on symlinks are ignored by the Linux kernel, so the + // archive's stored permissions for symlinks have nothing to attach to + // — `lchmod` is effectively a no-op even when Glibc exposes it. The + // modification date *is* respected though: `lutimes` writes to the + // symlink itself rather than its target, and Glibc has shipped it + // since 2.6. swift-corelibs-foundation's `setAttributes` doesn't + // round-trip `.modificationDate` for symlinks today, so we go + // straight to the libc syscall here to preserve mtime parity with + // Apple platforms when extracting. + guard let modificationDate = attributes[.modificationDate] as? Date else { + throw Entry.EntryError.missingModificationDateAttributeError + } + try self.setSymlinkModificationDate(modificationDate, ofItemAtURL: url) #else - // Since non-Darwin POSIX platforms ignore permissions on symlinks and swift-corelibs-foundation - // currently doesn't support setting the modification date, this codepath is currently a no-op - // on these platforms. + // Bionic and Windows lack a fully equivalent symlink-targeted + // `lutimes`; leave this codepath as a no-op there. return #endif }