Reading Finder Info ATTR_CMN_FNDRINFO with getattrlistbulk

After successfully implementing a scan with getattrlistbulk (thanks to https://developer.apple.com/forums/thread/656787/), I'm now trying for each file to get whether the extension is shown in the Finder or not. I think this could be read from the Finder Info (i.e.ATTR_CMN_FNDRINFO), although I'm not sure, since there doesn't seem do be any documentation about what this attribute actually contains. From the setattrlist documentation archive it seems that it is char[32], and I've tried different changes within readUnaligned() so that it would work with an array, but I couldn't get it to work.

Below is my current implementation. The comment How to get Finder Info? shows where the implementation currently doesn't work.

class AppDelegate: NSObject, NSApplicationDelegate {

    private static var attributeKeys: attrlist = {
        var attributeKeys = attrlist()
        attributeKeys.bitmapcount = u_short(ATTR_BIT_MAP_COUNT)
        attributeKeys.commonattr = attrgroup_t(ATTR_CMN_RETURNED_ATTRS) | attrgroup_t(bitPattern: ATTR_CMN_ERROR | ATTR_CMN_NAME | ATTR_CMN_OBJTYPE | ATTR_CMN_MODTIME | ATTR_CMN_FNDRINFO | ATTR_CMN_FILEID)
        attributeKeys.fileattr = attrgroup_t(bitPattern: ATTR_FILE_DATALENGTH)
        return attributeKeys
    }()
    
    private let bufferWithAlignment16 = UnsafeMutableRawBufferPointer.allocate(byteCount: 256, alignment: 16)
    
    func applicationDidFinishLaunching(_ notification: Notification) {
        let openPanel = NSOpenPanel()
        openPanel.canChooseDirectories = true
        openPanel.canChooseFiles = false
        openPanel.runModal()
        try! scan(directoryPath: openPanel.urls[0].path)
    }
    
    deinit {
        bufferWithAlignment16.deallocate()
    }
    
    func scan(directoryPath: String) throws {
        let fileDescriptor = open(directoryPath, O_RDONLY)
        if fileDescriptor < 0 {
            throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
        }
        defer {
            let result = close(fileDescriptor)
            assert(result == 0)
        }
        let attributeBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 2048, alignment: 16)
        defer {
            attributeBuffer.deallocate()
        }
        while true {
            let itemCount = Int(getattrlistbulk(fileDescriptor, &AppDelegate.attributeKeys, attributeBuffer.baseAddress!, attributeBuffer.count, 0))
            if itemCount == 0 {
                return
            } else if itemCount > 0 {
                var entryOffset = attributeBuffer.baseAddress!
                for _ in 0..<itemCount {
                    let length = Int(entryOffset.load(as: UInt32.self))
                    try unpackResources(at: entryOffset + MemoryLayout<UInt32>.size, parentDirectory: directoryPath)
                    entryOffset += length
                }
            } else if errno != EINTR {
                throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
            }
        }
    }
    
    private func unpackResources(at attributeOffset: UnsafeMutableRawPointer, parentDirectory: String) throws {
        var attributeOffset = attributeOffset
        let returned = attributeOffset.load(as: attribute_set_t.self)
        attributeOffset += MemoryLayout<attribute_set_t>.size
        
        var error: Error?
        if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_ERROR)) != 0 {
            error = NSError(domain: NSPOSIXErrorDomain, code: Int(attributeOffset.load(as: UInt32.self)))
            attributeOffset += MemoryLayout<UInt32>.size
        }
        let name: String
        if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_NAME)) != 0 {
            let nameInfo = attributeOffset.load(as: attrreference_t.self)
            name = String(cString: (attributeOffset + Int(nameInfo.attr_dataoffset)).assumingMemoryBound(to: CChar.self))
            attributeOffset += MemoryLayout<attrreference_t>.size
        } else {
            name = ""
        }
        
        let fileType: fsobj_type_t
        if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_OBJTYPE)) != 0 {
            fileType = attributeOffset.load(as: fsobj_type_t.self)
            attributeOffset += MemoryLayout<fsobj_type_t>.size
        } else {
            fileType = VNON.rawValue
        }
        var modificationDate: Date?
        if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_MODTIME)) != 0 {
            modificationDate = Date(timeIntervalSince1970: TimeInterval(readUnaligned(pointer: attributeOffset, as: timespec.self).tv_sec))
            attributeOffset += MemoryLayout<timespec>.size
        }
        
        
        // How to get Finder Info?
        let finderInfo: [Int8]
        if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_FNDRINFO)) != 0 {
            finderInfo = readUnaligned(pointer: attributeOffset, as: [Int8].self)
            attributeOffset += MemoryLayout<Int8>.size
        } else {
            finderInfo = []
        }
        
        
        let fileId: UInt64
        if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_FILEID)) != 0 {
            fileId = readUnaligned(pointer: attributeOffset, as: UInt64.self)
            attributeOffset += MemoryLayout<UInt64>.size
        } else {
            fileId = 0
        }
        let size: Int64
        if (returned.fileattr & attrgroup_t(bitPattern: ATTR_FILE_DATALENGTH)) != 0 {
            size = Int64(readUnaligned(pointer: attributeOffset, as: off_t.self))
            attributeOffset += MemoryLayout<off_t>.size
        } else {
            size = 0
        }
        
        if let error = error {
            throw error
        }
        let isDirectory = fileType == VDIR.rawValue
        let isSymbolicLink = fileType == VLNK.rawValue
        print(name, isDirectory, isSymbolicLink, modificationDate as Any, finderInfo, fileId, size)
    }
    
    private func readUnaligned<Result>(pointer: UnsafeRawPointer, as: Result.Type) -> Result {
        bufferWithAlignment16.copyMemory(from: UnsafeRawBufferPointer(start: pointer, count: MemoryLayout<Result>.size))
        return bufferWithAlignment16.baseAddress!.load(as: Result.self)
    }

}

I generally recommend that you use FileManager for this stuff. Its contentsOfDirectory(at:includingPropertiesForKeys:options:) method is surprisingly fast, to the point where there’s usually little to be gained by dropping down to the lower-level routines.

One of the key performance wins of that API is that the URLs you get back have the requested properties cached, so subsequent resourceValues(forKeys:) calls don’t hit the disk. And that means you can get the hasHiddenExtensionKey property without messing around with Finder info.

So, it’s easy and fast and future proof. Nice!


If you insist on doing this the hard way…

I think this could be read from the Finder Info … although I'm not sure, since there doesn't seem do be any documentation about what this attribute actually contains.

You’ll find structures for this in Finder.h within the CarbonCore subframework of the CoreServices framework. Look FileInfo, FolderInfo, ExtendedFileInfo, and ExtendedFolderInfo, and their associated constants. I don’t think there’s a specific constant for ‘has hidden extension’ flag but it’s easy to work that out based on this context.

Share and Enjoy

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

Reading Finder Info ATTR_CMN_FNDRINFO with getattrlistbulk
 
 
Q