Swift FileManager on iOS device: wrong/different resourceValues for execute/read/write

I hope I am not wrong here with my (first posted) question.

I’m getting wrong/different resourceValues with following code (it lists all directory items in the sandbox directory).

The „URL-method“ returns for some directory items other resourceValues for execute/read/write than the „path-methods“.

For instance folder „SystemData“:

  • with „URL-method“: isExecutable=true, isReadable=true, isWritable=true
  • with „path-methods“: isExecutableFile: true, isReadableFile: false, isWritableFile: false

The PosixPermissions for this folder gives me 755 … which is not correct.

So two questions:

  1. Why am I getting different values depending on method and which one are correct?
  2. Why are the PosixPermissions wrong?

I'm thankful for any help!

Xcode Version 12.4 (12D4e)
Tested on real device (iPhone 11, iOS 14.4)

func FMTest() {
let fM = FileManager.default
do {
    let currentRootDirURL = try fM.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).resourceValues(forKeys:[.parentDirectoryURLKey]).parentDirectory!
    
    let dirItems: [URL] = try fM.contentsOfDirectory(
        at: currentRootDirURL,
        includingPropertiesForKeys:[.nameKey,.isExecutableKey,.isReadableKey,.isWritableKey]
    ) 
    
    for dirItem in dirItems {

        // "URL-method"
        let dirItemAttributes = try dirItem.resourceValues(forKeys:[.nameKey,.isExecutableKey,.isReadableKey,.isWritableKey])
        
        print("+===\nnameKey: \(String(describing: dirItemAttributes.name))")
        print("URL:")
        print("..isExecutable: \(String(describing: dirItemAttributes.isExecutable))")
        print("..isReadable: \(String(describing: dirItemAttributes.isReadable))")
        print("..isWritable: \(String(describing: dirItemAttributes.isWritable))")
        
        // "path-method"
        let exec = fM.isExecutableFile(atPath: dirItem.path)
        let read = fM.isReadableFile(atPath: dirItem.path)
        let write = fM.isWritableFile(atPath: dirItem.path)
        
        print("\nPATH:")
        print("..isExecutableFile: \(exec)")
        print("..isReadableFile: \(read)")
        print("..isWritableFile: \(write)")
        
        // try? just for avoiding "Operation not permitted" error in this example code
        if let dirItemAttributesPATH = try? fM.attributesOfItem(atPath: dirItem.path) {

           **// "PosixPermission"**
            **let dirItemPosixPermissionsDecimal = dirItemAttributesPATH[.posixPermissions] as? NSNumber**
            **let dirItemPosixPermisionsOctal =  String(dirItemPosixPermissionsDecimal!.intValue, radix: 8, uppercase: false)**
            print("\ndirItemPosixPermisionsOctal: \(dirItemPosixPermisionsOctal)\n+===\n")
        } else {
            print("\ndirItemPosixPermisionsOctal: - \n+===\n")
        }
    }
} catch {
    print(error)
}
}

Why are the Posix permissions wrong?

They’re not wrong (-: The issue here is that Apple platforms have multiple levels of access control:

  • Posix permissions

  • ACLs

  • Mandatory access control (on macOS)

  • App Sandbox

The isXxxFile(atPath:) methods are based on access (see its man page) which takes into account all of these. I suspect that SystemData is protected by the sandbox, which is why access is reporting less privileges than the Posix permissions would suggest.

I’m not sure why resourceValues(forKeys:) is return rwx.

Honestly, I don’t lose a lot of sleep on this stuff. If there’s one thing you should take away from the above is that it’s very hard to preflight file system operations. Rather than looking at the permissions to see whether you can do something, you should instead try that thing and then deal with the error.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

Yes, that surely will be the smartest thing to do.

Thanks for your help! :slight_smile: :+1:

Just for my understanding:
The isXxxFile(atPath:) methods as well as the resourceValues(forKeys:) method are based on access and should (theoretically) return the same permissions. Or am I misunderstanding?

EDIT (12.02.21):
@Eskimo's answer was an important clue for my research.
I have now researched the following:

  • isXxxFile(atPath:) methods are based on real user ID (RUID) and real group ID (RGID) (see here).
  • The resourceValues(forKeys:) method is based on the effective user (EUID) and group IDs (EGID) (see here).

Thus different access values can be returned.

Informations for EUID vs RUID see here or here.

Regarding @eskimo's hint:

you should instead try that thing and then deal with the error

Found this on developer documentation:
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. For more information on file system race conditions, see Race Conditions and Secure File Operations in Secure Coding Guide.

Further helpful hints and answers are welcome.