Getting inode number from URL

I couldn't find any other way of getting the inode number without using FileManager.attributesOfItem(atPath: url.path)[.systemFileNumber]. I'm already using FileManager.enumerator(at:includingPropertiesForKeys:errorHandler:) for enumerating large directories and using that other FileManager method only for accessing the inode number doubles the scan time. I looked for a URLResourceKey but there doesn't seem to be any. I would be really grateful for any kind of help.
Answered by DTS Engineer in 626656022
Code Block
/// Adapts `getattrlistbulk` to use Swift-style errors.
func getattrlistbulk2(_ dirFD: CInt, _ attrListPtr: UnsafeMutablePointer<attrlist>, _ attrBuf: UnsafeMutableRawBufferPointer, _ options: UInt64) throws -> Int {
while true {
let result = getattrlistbulk(dirFD, attrListPtr, attrBuf.baseAddress!, attrBuf.count, 0)
if result >= 0 {
return Int(result)
}
let err = errno
if err != EINTR {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
}
// continue on `EINTR`
}
}
/// A copy of `vtype`.
///
/// This system enum is not available to Swift. In a real project you’d access it via a
/// bridging header.
enum vtype: fsobj_type_t {
/* 0 */
case VNON
/* 1 - 5 */
case VREG; case VDIR; case VBLK; case VCHR; case VLNK
/* 6 - 10 */
case VSOCK; case VFIFO; case VBAD; case VSTR; case VCPLX
};
/// Prints the attributes in the supplied buffer.
func printAttrs(_ itemCount: Int, _ attrBuf: UnsafeMutableRawBufferPointer) {
var entryStart = attrBuf.baseAddress!
for _ in 0..<itemCount {
var field = entryStart
let length = Int(field.load(as: UInt32.self))
field += MemoryLayout<UInt32>.size
entryStart += length
let returned = field.load(as: attribute_set_t.self)
field += MemoryLayout<attribute_set_t>.size
var error: UInt32 = 0
if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_ERROR)) != 0 {
error = field.load(as: UInt32.self)
field += MemoryLayout<UInt32>.size
}
var name: String = ""
if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_NAME)) != 0 {
let base = field
let nameInfo = field.load(as: attrreference_t.self)
field += MemoryLayout<attrreference_t>.size
name = String(cString: (base + Int(nameInfo.attr_dataoffset)).assumingMemoryBound(to: CChar.self))
}
if error != 0 {
print("Error in reading attributes for directory entry \(error)");
continue
}
var objectType: fsobj_type_t = vtype.VNON.rawValue
if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_OBJTYPE)) != 0 {
objectType = field.load(as: fsobj_type_t.self)
field += MemoryLayout<fsobj_type_t>.size
switch objectType {
case vtype.VREG.rawValue:
print("file \(name)")
case vtype.VDIR.rawValue:
print(" dir \(name)")
default:
print(" *** \(name)")
}
}
}
}
func demo(_ dirPath: String) throws {
let dirFD = open(dirPath, O_RDONLY)
guard dirFD >= 0 else {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
}
defer {
let junk = close(dirFD)
assert(junk == 0)
}
var attrList = attrlist()
attrList.bitmapcount = u_short( ATTR_BIT_MAP_COUNT )
attrList.commonattr =
attrgroup_t(ATTR_CMN_RETURNED_ATTRS) |
attrgroup_t(bitPattern: ATTR_CMN_NAME) |
attrgroup_t(bitPattern: ATTR_CMN_ERROR) |
attrgroup_t(bitPattern: ATTR_CMN_OBJTYPE)
let attrBuf = UnsafeMutableRawBufferPointer.allocate(byteCount: 256, alignment: 16)
defer {
attrBuf.deallocate()
}
while true {
let itemCount = try getattrlistbulk2(dirFD, &attrList, attrBuf, 0)
guard itemCount > 0 else {
return
}
printAttrs(itemCount, attrBuf)
}
}

String(format:) is likely to create an NSString which is then bridged into Swift. This is going to have very different performance characteristics vs the native Swift string you get from interpolation. Normally I’d expect it to run slower, but I guess that’s not the case here.

As to how to proceed with this, I think it’d be worthwhile you reducing this to an isolated test case, independent of your file system issue, and then raising it over on Swift Forums.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
Thanks a lot, after some long debugging session I could isolate the problem. I opened a new thread here: https://developer.apple.com/forums/thread/657340
I'm wondering what I have to do in order to make this work on iOS as well. In the bridging header I have #include <sys/vnode.h> which works fine when building for macOS, but for iOS I get the error 'sys/vnode.h' file not found. Do I have to include some framework or library in the iOS target's Build Phases?
Yeah, <sys/vnode.h> is simple not available on iOS. If you comment out the include which declarations are you missing?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
Use of unresolved identifier 'VNON' and Use of unresolved identifier 'VDIR'.

Use of unresolved identifier 'VNON' and Use of unresolved identifier 'VDIR'.

Right. For the moment you can hardwire those constants. The chances of them changing are very low. As to the long term, you should file a bug requesting that they be added to the iOS SDK. After all, if getattrlistbulk is there, the types needed to use it should be there too.

Please post your bug number, just for the record.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
Thanks, I've created FB8540709.
I've got the following reply to the feedback I opened: Please close this feedback and file a new feedback that suggests making the FileManager APIs fast.. Nothing more. I've replied that it doesn't make sense to have getattrlistbulk and not the enums it uses, I hope they realize this.
I just noticed now that getattrlistbulk returns 0 for the size of files mounted via FTP. I used ATTR_FILE_DATALENGTH which workes fine for local files, but ATTR_FILE_TOTALSIZE, ATTR_FILE_ALLOCSIZE, ATTR_FILE_DATALENGTH, ATTR_FILE_DATAALLOCSIZE, ATTR_FILE_RSRCLENGTH, ATTR_FILE_RSRCALLOCSIZE, ATTR_FORK_TOTALSIZE, ATTR_FORK_ALLOCSIZE all return 0 for FTP files. Does anyone know how to get the size of those files?
It turns out that I had a small bug with reading the ATTR_CMN_CRTIME. The creation time is apparently not available for FTP files, which caused the following attribute, the size, to be read from the wrong memory offset.

I just wanted to add that for some reason the order of imports inside the test files is important in some cases. I have to write:

@testable import MyApp
import XCTest

Inverting those two lines causes several compiler errors inside the bridging header file:

Redefinition of 'vtype'
Redefinition of enumerator 'VBAD'
Redefinition of enumerator 'VBLK'
...

I have no idea why this happens, as in another project with the exact same bridging header file the order of imports seems to be irrelevant and doesn't cause any compiler errors.

Getting inode number from URL
 
 
Q