Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fatal Error: NSFileModificationDate not implemented when unzip item on Linux. #65

Closed
IsaacXen opened this issue May 11, 2018 · 10 comments
Closed

Comments

@IsaacXen
Copy link

Summary

Fatal error when tried to unzip on linux.
Same code, everything works well on macOS, but failed on linux.

Steps to Reproduce

try FileManager.default.unzipItem(at: urlToZip, to: urlToDest)

I make a small demo to reproduce this:

git clone https://gitlab.com/isaacxen/zipFoundationErrorDemo.git
cd zipFoundationErrorDemo/
cp example.epub /tmp
swift package update
swift run

Expected Results

successfully unzip.

Actual Results

Fatal error: Attribute type not implemented: FileAttributeKey(rawValue: "NSFileModificationDate"): file Foundation/FileManager.swift, line 139

Regression & Version

ZipFoundation: 0.9.6
Ubuntu 16.04
Swift 4.1

Related Link

@weichsel
Copy link
Owner

weichsel commented May 14, 2018

I was able to reproduce the issue with your test repository. Thanks for taking the effort to write a quick sample - this really helps.

The problem is, that Swift's corelibs Foundation (which is used on Linux as replacement for the closed source Foundation on iOS/macOS/...) lacks file modification date support in FileManager.

Update (13.12.2018)
The corelibs Foundation FileManager still has no .fileModificationDate support.
A workaround to extract archive entries on Linux is to use the closure based extract API to avoid the failing setAttributes: call.
The URL based extract API in ZIP Foundation is built on top of the closure based one and can be used as example:

public func extract(_ entry: Entry, to url: URL, bufferSize: UInt32 = defaultReadChunkSize,
progress: Progress? = nil) throws -> CRC32 {
let fileManager = FileManager()
var checksum = CRC32(0)
switch entry.type {
case .file:
guard !fileManager.fileExists(atPath: url.path) else {
throw CocoaError.error(.fileWriteFileExists, userInfo: [NSFilePathErrorKey: url.path], url: nil)
}
try fileManager.createParentDirectoryStructure(for: url)
let destinationFileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: url.path)
let destinationFile: UnsafeMutablePointer<FILE> = fopen(destinationFileSystemRepresentation, "wb+")
defer { fclose(destinationFile) }
let consumer = { _ = try Data.write(chunk: $0, to: destinationFile) }
checksum = try self.extract(entry, bufferSize: bufferSize, progress: progress, consumer: consumer)
case .directory:
let consumer = { (_: Data) in
try fileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
}
checksum = try self.extract(entry, bufferSize: bufferSize, progress: progress, consumer: consumer)
case .symlink:
guard !fileManager.fileExists(atPath: url.path) else {
throw CocoaError.error(.fileWriteFileExists, userInfo: [NSFilePathErrorKey: url.path], url: nil)
}
let consumer = { (data: Data) in
guard let linkPath = String(data: data, encoding: .utf8) else { throw ArchiveError.invalidEntryPath }
try fileManager.createParentDirectoryStructure(for: url)
try fileManager.createSymbolicLink(atPath: url.path, withDestinationPath: linkPath)
}
checksum = try self.extract(entry, bufferSize: bufferSize, progress: progress, consumer: consumer)
}
let attributes = FileManager.attributes(from: entry)
try fileManager.setAttributes(attributes, ofItemAtPath: url.path)
return checksum
}

(Avoid the problematic setAttributes when building a workaround extract method)

@TheVanDoom
Copy link

Is there any way to extract the archive on Linux now? Iterating over the archive is nice and all, but the execution fails when calling the extract-method of the archive. Or is there another way to get the extracted result from the entry than calling archive.extract(...) ?

@weichsel
Copy link
Owner

Extracting via Archive should work on Linux. Only the FileManager extension cannot be used because corelibs Foundation still has no implementation for the file modification date attribute.

If you can provide some details about your environment (Swift Version, Linux distribution, ...) and a source snippet of your extraction code I can have a look.

@TheVanDoom
Copy link

TheVanDoom commented Dec 13, 2018

The application is build with Swift 4.2.1 and runs in a docker-container with Ubuntu 18.04. I use the following code to extract the archive:

...
guard let archive = Archive(url: zipURL, accessMode: .read) else  {
    return
}
archive.forEach {
    try archive.extract(entry, to: workDir. appendPathComponent($0.path))
}
...

@weichsel
Copy link
Owner

What Error do you get?

@TheVanDoom
Copy link

The same as stated in this issue.
Fatal error: Attribute type not implemented: FileAttributeKey(rawValue: "NSFileModificationDate"): file Foundation/FileManager.swift, line 139

@weichsel
Copy link
Owner

weichsel commented Dec 13, 2018

I just wrote a small test program to check the current state of Linux support.

My original statement, that only the FileManager based extraction is affected by the missing modificationDate support is wrong. My mistake - Sorry for the confusion.
Archive.extract(_ entry, to url) also tries to set the modification date and therefore fails on Linux. (I will update my first response in this issue to make this clearer).

However, there is a second Archive.extract method, that takes a consumer closure. This method does not create the destination file and therefore does not set the Entry file attributes. This one can be used on Linux but you have to create the destination file yourself and append the data passed into the consumer closure.

@TheVanDoom
Copy link

Ok, thank you very much. I'll give that a try.

@weichsel
Copy link
Owner

I updated my first reply with some details on how to work around this issue.

@andrewtheis
Copy link
Contributor

If I'm understanding correctly, the workaround is to copy that method and just remove the setAttributes call?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants