Starting with Catalina Beta 6 and now with Beta 7 I’m seeing the following error in my Mac app when I try to write an NSFileWrapper to disk:
ObjC:
-[NSFileWrapper regularFileContents] tried to read the file wrapper's contents lazily but an error occurred: The file “problem.txt” couldn’t be opened because you don’t have permission to view it.
Swift:
Error Domain=NSCocoaErrorDomain Code=257 "The file “problem.txt” couldn’t be opened because you don’t have permission to view it." UserInfo={NSFilePath=/Users/test/Library/Caches/03B8158C-31C2-4CCA-8CCF-327E4EC7CCE3.bundle/problem.txt
I can reproduce this in a sample app. Here is what the sample app does:
(1) It creates the following sample folder:
~/Library/Caches/<UUID>/
test.txt
problem.txt
(2) It initializes a new NSFileWrapper with the folder.
(3) It removes the child file wrapper “problem.txt” and adds it again (to simulate an update)
(4) It writes the file wrapper to disk for the first time (this works)
(5) Right away it writes the file wrapper to disk a second time. This fails each time with the error above.
This is a serious issue, because it results in data-loss for all users of my app (sandboxed, Mac App Store) that are running the latest Catalina beta. I can also reproduce this in a sample app that is not sandboxed. Interestingly enough, the problem does not occur in the sample app when I enable sandboxing here.
Back in the day, the macOS High Sierra beta build 17C88 contained a similar issue which was fixed in the official release. This looks like it could be a regression.
References:
REFERENCES:
- FB5353407: macOS High Sierra beta bug report
- FB7101246: macOS Catalina beta 6 bug report
- FB7164053: macOS Catalina beta 7 bug report
To reproduce, run the code below in a new Cocoa app (not sandboxed):
import Foundation
class FileWrapperTestCaseSwift
{
func reproduce()
{
// Prepare sample data:
// ~/Library/Caches/<UUID>/
// test.txt
// problem.txt
let sampleDataFolderURL = temporaryDataFolderURL()
try! FileManager.default.createDirectory(at: sampleDataFolderURL, withIntermediateDirectories: true, attributes: nil)
try! "test" .write(to: sampleDataFolderURL.appendingPathComponent("test.txt" ), atomically: true, encoding: .utf8)
try! "problem".write(to: sampleDataFolderURL.appendingPathComponent("problem.txt"), atomically: true, encoding: .utf8)
// Read sample data:
let mainFileWrapper = try! FileWrapper(url: sampleDataFolderURL, options: [])
// Simulate a change:
updateFileWrapper(mainFileWrapper)
NSLog("About to write file wrapper for first time")
try! mainFileWrapper.write(to: sampleDataFolderURL, options: .atomic, originalContentsURL: sampleDataFolderURL)
updateFileWrapper(mainFileWrapper)
NSLog("About to write file wrapper a second time.")
do
{
// This fails on macOS Catalina Beta 6 & 7:
try mainFileWrapper.write(to: sampleDataFolderURL, options: .atomic, originalContentsURL: sampleDataFolderURL)
NSLog("Test PASSED")
}
catch
{
fatalError("TEST FAILED. Error: \(error)")
}
}
private func updateFileWrapper(_ mainFileWrapper:FileWrapper)
{
mainFileWrapper.fcRemoveFileWrapperWithName("test.txt")
mainFileWrapper.fcAddRegularFileNamed("test.txt", data: "test".data(using: .utf8)!)
#warning ("*** This file wrapper causes problems:")
if mainFileWrapper.fileWrappers?["problem.txt"] == nil
{
// For some reason, this file wrapper will cause problems the second time the parent
// file wrapper is written to disk. The file wrapper test.txt is removed and added each time
// to simulate an "update" in the app. Whereas problem.txt remains unchanged and is only added once.
// Error: Error Domain=NSCocoaErrorDomain Code=257 "The file “problem.txt” couldn’t be opened because you don’t have permission to view it." UserInfo={NSFilePath=/Users/test/Library/Caches/03B8158C-31C2-4CCA-8CCF-327E4EC7CCE3.bundle/problem.txt,
// Error in console: -[NSFileWrapper regularFileContents] tried to read the file wrapper's contents lazily but an error occurred: The file “problem.txt” couldn’t be opened because you don’t have permission to view it.
mainFileWrapper.fcAddRegularFileNamed("problem.txt", data: "problem".data(using: .utf8)!)
}
}
private func temporaryDataFolderURL() -> URL
{
let cachesPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first ?? NSTemporaryDirectory()
let tempDocumentName = UUID().uuidString + ".bundle";
return URL(fileURLWithPath: cachesPath, isDirectory: true).appendingPathComponent(tempDocumentName)
}
}
extension FileWrapper
{
func fcRemoveFileWrapperWithName(_ name:String)
{
if let fileWrapper = fileWrappers?[name]
{
removeFileWrapper(fileWrapper)
}
}
func fcAddRegularFileNamed(_ name:String, data:Data)
{
let child = FileWrapper(regularFileWithContents: data)
child.preferredFilename = name
addFileWrapper(child)
}
}