Sorry, I didn't know that clicking on the checkmark would mark your answer as the correct one (it looks more like a badge to show that your account is verified, so I was wondering where the link would bring me). Is it possible to revert this action?
Anyway, what you suggest is exactly what I'm doing (except that I cannot use withSecurityScope since I'm on iOS), and it's not working.
Post
Replies
Boosts
Views
Activity
I'm building an app that makes periodic backups and would like to know what files have changed between two backups (in particular when a file has been renamed so that I can rename it as well on the backup drive instead of removing the old file and copying the new one over again), which as far as I understand is only possible by comparing the inode numbers.
Thank you, I already filed FB8308273.
The problem even when only using stat is that FileManager will already have been its own call to get the other attributes, so it's always approximately double the scan time.
What is the difference between getdirentriesattr and getattrlistbulk? Are there any examples in Swift? I already tried using getattrlistbulk in the past, but could only find examples written in Objective-C which were quite difficult to translate to Swift.
Also, do you know why I'm not getting any email notification when a new answer is posted? I was told in the past that there should be a setting for this, but I cannot find any. I have to keep refreshing this page every now and then to check for new answers.
Thank you so much! Although I don't understand why you use a while loop inside getattrlistbulk2. Shouldn't it be like this:
func getattrlistbulk2(_ dirFD: CInt, _ attrListPtr: UnsafeMutablePointer<attrlist>, _ attrBuf: UnsafeMutableRawBufferPointer, _ options: UInt64) throws -> Int {
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)
}
return 0
}
I kept wondering why your code is slightly slower than enumeration done with FileManager. It turns out that using let attrBuf = UnsafeMutableRawBufferPointer.allocate(byteCount: 1024, alignment: 16) instead of let attrBuf = UnsafeMutableRawBufferPointer.allocate(byteCount: 256, alignment: 16) is about 25% faster when scanning my Documents directory.
In order to get the inode I use
var fileId: UInt32 = 0
if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_FILEID)) != 0 {
fileId = field.load(as: UInt32.self)
field += MemoryLayout<UInt32>.size
}
which seems to work fine (at least for all files in my Documents directory it's equal to FileManager.attributesOfItem(atPath: url.path)[.systemFileNumber]). Is UInt32 the right type? (The documentation says it should be u_int64_t, but using UInt64 crashes at runtime at the line fileId = field.load(as: UInt64.self) with an error Fatal error: load from misaligned raw pointer.)
I'm also having difficulties reading the modification date. With this code
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) |
		attrgroup_t(bitPattern: ATTR_CMN_MODTIME) |
		attrgroup_t(bitPattern: ATTR_CMN_FILEID)
and this code appended to the end of printAttrs
var modtime: timespec
if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_MODTIME)) != 0 {
modtime = field.load(as: timespec.self)
field += MemoryLayout<timespec>.size
}
var fileId: UInt32 = 0
if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_FILEID)) != 0 {
fileId = field.load(as: UInt32.self)
field += MemoryLayout<UInt32>.size
}
I get again the same error Fatal error: load from misaligned raw pointer at the line modtime = field.load(as: timespec.self). Any idea what the problem could be?
Thanks Kevin, for apps targeting macOS 11+ fileContentIdentifierKey will be useful, but for my existing apps that still run on older systems I will probably have to use getattrlistbulk. Does that key effectively return the inode, or what kind of identifier is it? It would be helpful to mention it in the documentation.
Thanks also Quinn, I didn't know the meaning of EINTR. If someone could have a look at the issues with ATTR_CMN_MODTIME and ATTR_CMN_FILEID I mentioned in my previous post, that would be great.
Thank you, that was it.
I hope I'm now facing the last issue with this solution: thanks to your code we have name = String(cString: (base + Int(nameInfo.attr_dataoffset)).assumingMemoryBound(to: CChar.self)). I need the absolute path so I did let path = "\(dirPath)/\(name)", but this produces some kind of string that is not "fast": using the Instruments app I found out that when using path as a key for a Swift dictionary with some custom object as the value, the scan is more than double the time than if I use let path = String(format: "%@/%@", directoryPath, name) or even let path = URL(fileURLWithPath: "\(dirPath)/\(name)").path. Is there an easy explanation for this? I would have thought that string interpolation should produce the same kind of string as when using String(format:), only more efficiently.
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
Thanks for your help, what you say definitely makes sense. What I wanted to point out is that when commenting out line 8 (map[s] = s), the version with the string interpolation is faster than the one with String(format:), that's why I mentioned the dictionary insertion in the title.
What still doesn't make sense is that the two variants inside the transform() function both use a Swift String, still one is faster to insert into the dictionary than the other.
I find it confusing that even when it's not prefixed with NS it's automatically bridged to NSString. Thanks for your help.
Thanks, that seems to be the case. Is there a more efficient way of checking that a file is an AppleDouble file other than testing if a file exists with the same name but without the ._ prefix or check the first bytes against that magic number? During my test I noticed that the dot underscore file seems to be enumerated by getattrlistbulk immediately after the original file: is this always guaranteed and is there a documentation somewhere that explains this in more detail? Any idea how FileManager does it?
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?