Search code examples
objective-cmacoscocoansfilemanagerappstore-sandbox

Permission denied, caused by an NSURL resource leak?


My Mac app copies files the user drags in. The app is sandboxed. I've got a bug now, which I can reliably reproduce in Xcode, but which I can't track down the source of.

When I add a certain number of files, the app suddenly stops being able to access the later ones. All the source files come one level down from the same parent directory, and all of the folders in between have the same permissions, verified with ls -l. The app retains an app-scoped bookmark to Destination Directory, which is accessed before creating .directoryName and beginning the file copy.

I get the error:

Error Domain=NSCocoaErrorDomain Code=513 "“Filename.ext” couldn’t be copied because you don’t have permission to access “.directoryName”." UserInfo=0x6080000eaf80 {NSSourceFilePathErrorKey=/Users/Username/Parent Directory/Subdir/Filename.ext, NSUserStringVariant=( Copy ), NSDestinationFilePath=/Users/Username/Desktop/Destination Directory/.directoryName/Filename.ext, NSFilePath=/Users/Username/Parent Directory/Subdir/Filename.ext, NSUnderlyingError=0x6080004567a0 "The operation couldn’t be completed. Operation not permitted"}

I have done the following while debugging:

  1. Tried adding Subdir/Filename.ext on its own after quitting and relaunching, which worked
  2. Verified (with lsof and Activity Monitor) that I'm not leaking file handles (which I know has caused similar problems for me in the past)
  3. Verified with symbolic breakpoints that every call to -[NSURL startAccessingSecurityScopedResource] to is balanced with a call to -[NSURL stopAccessingSecurityScopedResource] (and their CF equivalents)
  4. Determined that Filename.ext, and the other files that fail, succeed when added without the others
  5. The same behavior occurs for Debug and Release builds, in and out of the debugger
  6. I tried running as root, but my app won't run that way. When running in the debugger, I get an EXC_BAD_INSTRUCTION exception, and running it with sudo on the command line produces a crash (probably the same one)

The behavior seems to indicate some sort of resource leak, but it's not anything I've yet been able to detect. Any ideas what else might be causing this?

Update

I'm not ready to declare an answer, but I started to get these permission errors in a different part of my code, after a user has dropped files into the app. I noticed that a little ways up the logs, I'd see messages like the following:

Consume sandbox extension for itemIdentifier (1) from pasteboard failed!

Tracking down this error led me to a question someone else asked: Sandboxed Mac app exhausting security scoped URL resources

Unfortunately, the accepted answer says (paraphrasing) "tough noogies". It seems that Cocoa and the sandboxing mechanism themselves are leaking the security-scope tokens (though I can't verify with symbolic breakpoints and know of no other way to check). After a certain (unknown) number of file operations on sandboxed files, you'll start getting this error, and the only solution is to quit and relaunch.

I wish there was at least some way to prompt the user to relaunch when it gets close, but I'm not sure of any way to measure how many of these handles are in use. Or, even better, if I could manually clean up after I finish handling the dropped files, but I'm not sure how that would work, since I need to use the NSFilenamesPboardType pasteboard type to get multiple files' paths. I tried creating NSURLs from these and stopping security scope access, but that had no effect.

Update 2

I submitted a DTS ticket for this, as it's affecting users and there is no clear workaround. I'll update the question (and maybe give an answer?) as I find out more.

Response from Apple's DTS team

Apparently, this is a known issue, with no available workaround. I submitted a Radar: rdar://20652066, if you'd like to dupe it.


Solution

  • This was fixed in an El Capitan (10.11) release (possibly the first, but I'm not sure). When I built my app against the updated SDK, the behavior returned to normal.