Error scrutiny:
struct FileInfo: Equatable {
init?(url: URL) {
guard
let resourceVals = try?url.resourceValues(forKeys: [.fileResourceIdentifierKey, .fileSizeKey]),
let fileID = resourceVals.fileResourceIdentifier,
let fileSize = resourceVals.fileSize
else {
return nil
}
self.fileID = fileID
self.fileSize = fileSize
}
private let fileID: (any NSCopying & NSSecureCoding & NSObjectProtocol)
private let fileSize: Int
static func == (lhs: ViewController.FileInfo, rhs: ViewController.FileInfo) -> Bool {
return lhs.fileSize == rhs.fileSize && lhs.fileID.isEqual(rhs.fileID)
}
}
func isSafeReplaceError(_ error: Error, fileURL: URL, tempURL: URL, oldFileInfo: FileInfo?, oldTempFileInfo: FileInfo?) -> Bool {
// Using the file resource IDs and file size, ensure that the temp file and original file have been swapped.
guard
let oldFileInfo,
let oldTempFileInfo,
let fileInfo = FileInfo(url: fileURL),
let tempFileInfo = FileInfo(url: tempURL),
oldFileInfo == tempFileInfo,
oldTempFileInfo == fileInfo,
tempFileInfo != fileInfo
else {
return false
}
let nsError = error as NSError
guard
// Check this is a permissions error in the Cocoa error domain.
nsError.domain == NSCocoaErrorDomain,
nsError.code == NSFileWriteNoPermissionError,
// Check "NSURL" and "NSFileNewItemLocationKey" keys both point to the file we tried to replace.
let errorURL = nsError.userInfo[NSURLErrorKey] as? URL,
let newItemURL = nsError.userInfo["NSFileNewItemLocationKey"] as? URL,
errorURL.path(percentEncoded: false) == newItemURL.path(percentEncoded: false),
newItemURL.path(percentEncoded: false) == fileURL.path(percentEncoded: false),
// Check "NSFileOriginalItemLocationKey" and "NSFileBackupItemLeftBehindLocationKey" both point to the temp file.
let originalURL = nsError.userInfo["NSFileOriginalItemLocationKey"] as? URL,
let leftBehindURL = nsError.userInfo["NSFileBackupItemLeftBehindLocationKey"] as? URL,
originalURL.path(percentEncoded: false) == leftBehindURL.path(percentEncoded: false),
originalURL.path(percentEncoded: false) == tempURL.path(percentEncoded: false),
// Ensure there is only a single underlying error.
nsError.underlyingErrors.count == 1
else {
return false
}
// Now get the underlying error.
let underlyingError = nsError.underlyingErrors[0] as NSError
guard
// Check the underlying error is also a permissions error in the Cocoa domain.
underlyingError.domain == NSCocoaErrorDomain,
underlyingError.code == NSFileWriteNoPermissionError,
// And ensure the the error is with the temp file.
let underlyingErrorURL = underlyingError.userInfo[NSURLErrorKey] as? URL,
underlyingErrorURL.path(percentEncoded: false) == tempURL.path(percentEncoded: false),
// Ensure the underlying error also has a single underlying error.
underlyingError.underlyingErrors.count == 1
else {
return false
}
// Now get the underlying error for the underlying error. This should be a POSIX error with error code 1 ("Operation not permitted").
let rootError = underlyingError.underlyingErrors[0] as NSError
return rootError.domain == NSPOSIXErrorDomain && rootError.code == 1
}
Topic:
App & System Services
SubTopic:
Core OS
Tags: