A user of my app reported that when trying to remove a file it always fails with the error "file couldn't be removed because you don't have permission to access it (Cocoa Error Domain 513)". After some testing, we found out that it's caused by trying to delete non-empty directories.
I'm using FileManager.removeItem(atPath:)
which has worked fine for many years, but it seems that with their particular NAS, it doesn't work.
I could work around this by checking if the file is a directory, and if it is, enumerating the directory and remove each contained file before removing the directory itself. But shouldn't this already be taken care of? In the source code of FileManager
I see that for Darwin platforms it calls
removefile(pathPtr, state, removefile_flags_t(REMOVEFILE_RECURSIVE))
so it seems that it should already work. Is the REMOVEFILE_RECURSIVE
flag perhaps ignored by the device? But then, is the misleading "you don't have permission to access the file" error thrown by the device or by macOS?
For the FileManager
source code, see https://github.com/swiftlang/swift-foundation/blob/1d5d70997410fc8b7700c8648b10d6fc28194202/Sources/FoundationEssentials/FileManager/FileOperations.swift#L444
Thanks for your insights.
how long is the deepest file path?
Not long. In the video they showed how the removed directory is only 2 levels deep and every filename is 10-20 characters long.
Are you able to remove the sub-directory they added?
Like I mentioned, with FileManager.removeItem(atPath:)
I wasn't able to, but with my custom function below that recursively removes files before the containing folder and I sent to them in a test app today, it worked (and no error alerts were shown).
(Note: before macOS 10.15, the code will leave empty sub-directories and try to remove the top-level directory at the end.)
func removeFileRecursively(atPath path: String) -> Bool {
if case let url = URL(fileURLWithPath: path, isDirectory: false), (try? url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) == true {
var options: FileManager.DirectoryEnumerationOptions = []
if #available(macOS 10.15, *) {
options.insert(.includesDirectoriesPostOrder)
}
guard let enumerator = FileManager.default.enumerator(at: url, includingPropertiesForKeys: [.isDirectoryKey, .isUserImmutableKey], options: options, errorHandler: { url, error in
NSAlert(error: error).runModal()
return true
}) else {
return false
}
while let url = enumerator.nextObject() as? URL {
let isDirectory = (try? url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) == true
let shouldRemove = if #available(macOS 10.15, *) {
!isDirectory || enumerator.isEnumeratingDirectoryPostOrder
} else {
!isDirectory
}
if shouldRemove {
do {
if try url.resourceValues(forKeys: [.isUserImmutableKey]).isUserImmutable == true {
try (url as NSURL).setResourceValues([.isUserImmutableKey: false])
}
if #available(macOS 10.15, *) {
let result = if isDirectory {
rmdir(url.path)
} else {
unlink(url.path)
}
if result < 0 {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
}
} else {
try FileManager.default.removeItem(at: url)
}
} catch {
NSAlert(error: error).runModal()
return false
}
}
}
}
do {
try FileManager.default.removeItem(atPath: path)
} catch {
NSAlert(error: error).runModal()
return false
}
return true
}