How do you get group/user info from file ACLs?

Documentation is sparse on this and it doesn't help that Apple's version differs from POSIX.

I know to specify ACL_TYPE_EXTENDED for acl_get_file() and know how to cycle through entries. From what I can tell, the tag is either ALLOW or DENY and you can check the permset for which actions the tag applies to.

I can't figure out how get the group or user the entry applies to. The only way I've been able to get this info is by using acl_to_text and scraping that output. Anyone know how to do this using the acl APIs?

Answered by DTS Engineer in 807216022

Kevin and I talked about this overnight and I think I have a solution for you.

The fact that mbr_identifier_translate is not API is not a showstopper because it’s using that routine in a very limited way, namely to turn a UUID into an user or group ID. And you can do that using the API in <membership.h>.

Consider this:

import System
import Darwin.membership

func main() throws {
    let fd = try FileDescriptor.open("/Users/quinn", .readOnly)
    let acl = acl_get_fd(fd.rawValue)!
    let s = sequence(state: nil) { (entry: inout acl_entry_t?) -> acl_entry_t? in
        let success = acl_get_entry(acl, (entry == nil ? ACL_FIRST_ENTRY : ACL_NEXT_ENTRY).rawValue, &entry) >= 0
        guard success else { return nil }
        return entry!
    }
    for (i, entry) in zip(0..., s) {
        let uuidPtr = acl_get_qualifier(entry)!.assumingMemoryBound(to: uuid_t.self)
        var id: uid_t = 0
        var type: CInt = 0
        let success = mbr_uuid_to_id(uuidPtr, &id, &type) >= 0
        assert(success)
        let typeStr: String
        switch type {
        case ID_TYPE_UID: typeStr = "uid"
        case ID_TYPE_GID: typeStr = "gid"
        default: typeStr = "???"
        }
        print("[\(i)] \(typeStr) \(id)")
    }
}

try main()

On my machine it prints:

[0] gid 12

This matches:

% ls -led /Users/quinn
drwxr-x---+ 68 quinn  staff  2176  3 Oct 13:47 /Users/quinn
 0: group:everyone deny delete

given that everyone is group ID 12.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I can't figure out how get the group or user the entry applies to. The only way I've been able to get this info is by using acl_to_text and scraping that output. Anyone know how to do this using the acl APIs?

Unfortunately, I don't think there is a good way to do this. You can see how ls does this by looking at "printacl" inside the "ls" commands source, which shows the entire process of converting the value returned by acl_get_qualifier into a readable value. The problem is that "mbr_identifier_translate" is SPI and I'm not aware of a public alternative.

Please file a bug on this and the post the bug number back here, as this really shouldn't require SPI or text parsing.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thanks for the response. Seems like a weird oversight. In any case, filed FB15370199.

BTW, which method would you recommend in the meantime? I suspect the scraping but wanted to double-check.

Accepted Answer

Kevin and I talked about this overnight and I think I have a solution for you.

The fact that mbr_identifier_translate is not API is not a showstopper because it’s using that routine in a very limited way, namely to turn a UUID into an user or group ID. And you can do that using the API in <membership.h>.

Consider this:

import System
import Darwin.membership

func main() throws {
    let fd = try FileDescriptor.open("/Users/quinn", .readOnly)
    let acl = acl_get_fd(fd.rawValue)!
    let s = sequence(state: nil) { (entry: inout acl_entry_t?) -> acl_entry_t? in
        let success = acl_get_entry(acl, (entry == nil ? ACL_FIRST_ENTRY : ACL_NEXT_ENTRY).rawValue, &entry) >= 0
        guard success else { return nil }
        return entry!
    }
    for (i, entry) in zip(0..., s) {
        let uuidPtr = acl_get_qualifier(entry)!.assumingMemoryBound(to: uuid_t.self)
        var id: uid_t = 0
        var type: CInt = 0
        let success = mbr_uuid_to_id(uuidPtr, &id, &type) >= 0
        assert(success)
        let typeStr: String
        switch type {
        case ID_TYPE_UID: typeStr = "uid"
        case ID_TYPE_GID: typeStr = "gid"
        default: typeStr = "???"
        }
        print("[\(i)] \(typeStr) \(id)")
    }
}

try main()

On my machine it prints:

[0] gid 12

This matches:

% ls -led /Users/quinn
drwxr-x---+ 68 quinn  staff  2176  3 Oct 13:47 /Users/quinn
 0: group:everyone deny delete

given that everyone is group ID 12.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Whoops, edited my post before seeing yours. Will look into that. Thanks!

Ok, quick check and it works. Many thanks to both of you!

How do you get group/user info from file ACLs?
 
 
Q