Search code examples
sandboxnsfilemanager

Mac Sandbox: How to distinguish between "Permission Denied" and "Does not Exist" re: Security-Scoped Bookmarks / URLs?


At launch, my app checks if the user has granted permission to access a certain folder (or not). If access is granted, the app proceeds, and, if not, the app presents a PowerBox NSOpenPanel - set to the necessary directory - to ask permission.

Once permission is granted, I store it as a "Security-Scoped Bookmark" and use that in future sessions to retain access to the directory.

This system boils down to the check at launchtime for the directory's status:

- (BOOL)needsPermissionForPath:(NSString *)path
{
    return ![[NSFileManager defaultManager] isReadableFileAtPath:path];
}

However, there is a major failing with my current pattern: if the folder does not exist, needsPermissionForPath always returns YES, yet the user can never give permission.

The docs for isReadableFileAtPath: state:

Return Value
YES if the current process has read privileges for the file at path; 
otherwise NO if the process does not have read privileges or 
the existence of the file could not be determined.

Discussion
If the file at path is inaccessible to your app, perhaps because it does 
not have search privileges for one or more parent directories, 
this method returns NO. 

Note: Attempting to predicate behavior based on the current state 
of the file system or a particular file on the file system is not 
recommended. Doing so can cause odd behavior or race conditions. 
It's far better to attempt an operation (such as loading a file or 
creating a directory), check for errors, and handle those errors gracefully
than it is to try to figure out ahead of time whether the operation will succeed

This seems to suggest that I should go ahead and try to do what I am trying to do anyway.

The trouble is, I don't see what test or action I can take to distinguish between "User did not grant permission" (which warrants me showing the "Please grant permission" panel again) and the "This folder does not exist" state (which warrants me ignoring the directory and moving on).

How can I tell the difference?

Or, should I try and create the directory in question? Even in this case, the user would still have to grant write permission to the containing directory, so that throws an extra layer of permissions into the equation, seemingly unnecessarily.

I think this is a bit of a chicken-and-egg problem. I can't check if the folder exists without permission, and I can't get permission if the folder doesn't exist.


EDIT

Based on the solution below, here is my new method:

- (BOOL)needsPermissionForPath:(NSString *)path
{
    if ( !path ) {
        return NO;
    }

    NSURL *url = [NSURL fileURLWithPath:path isDirectory:YES];
    if ( !url ) {
        return NO;
    }

    NSError *error = nil;
    BOOL reachable = [url checkResourceIsReachableAndReturnError:&error];
    if ( !reachable ) {
        //NSLog(@"Could not reach path (may not be an error; if file does not exist this is expected behavior) %@ with: %@", [NSURL fileURLWithPath:path], error);
        return NO;
    }

    return ![[NSFileManager defaultManager] isReadableFileAtPath:path];
}

Solution

    • use following code to check if an file URL exists and is reachable. Return correct information even when URL is outside of sandbox.

       NSError *erroer = nil;
       BOOL reachable = [unarchivedURL checkResourceIsReachableAndReturnError:&erroer];
      
    • if it's reachable but not readable: ask for permission