getattrlistbulk inode

Why is that getattrlistbulk returns a 64 bit inode for /Applications, /Library, /Users, /Volumes

BUT finder, terminal, stat are retuning a shorter version

stat /Applications
16777253 78133671 drwxrwxr-x 66 root admin 0 2112 "Feb 11 14:39:30 2022" "Feb 11 14:39:29 2022" "Feb 11 14:39:29 2022" "Jan  1 03:00:00 2020" 4096 0 0x100000 /Applications

ls -ail / | grep Applications
           78133671 drwxrwxr-x  66 root  admin  2112 Feb 13 01:28 Applications/

getattrlistbulk returns
1152921500311879699

I was using getattrlistbulk code example from https://developer.apple.com/forums/thread/656787/

Consider this:

% lldb
(lldb) p/x 78133671
(int) $0 = 0x04a839a7
(lldb) p/x 1152921500311879699
(long) $1 = 0x0fffffff00000013

I’m not sure what attribute your code is fetching, but it’s definitely not an inode number.

Share and Enjoy

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

Interesting ... Here are some examples

inode mismatch: '/Applications                   ' ' 1152921500311879699' vs '            78133671'
inode mismatch: '/Library                        ' ' 1152921500311879700' vs '            78110879'
inode mismatch: '/System/Library/Assets          ' ' 1152921500312008804' vs '            78132729'
inode mismatch: '/System/Library/AssetsV2        ' ' 1152921500312008805' vs '            78131512'
inode mismatch: '/System/Library/Caches          ' ' 1152921500312115767' vs '            78132730'
inode mismatch: '/System/Library/Speech          ' ' 1152921500312658038' vs '            78131497'
inode mismatch: '/System/Volumes/Data            ' ' 1152921500312709177' vs ' 1152921500311879682'
inode mismatch: '/System/Volumes/Preboot         ' ' 1152921500312709197' vs '                   2'
inode mismatch: '/System/Volumes/Update          ' ' 1152921500312709199' vs '                   2'
inode mismatch: '/System/Volumes/VM              ' ' 1152921500312709200' vs '                   2'
inode mismatch: '/Users                          ' ' 1152921500312764772' vs '                 319'
inode mismatch: '/Volumes                        ' ' 1152921500312764773' vs '                 320'
inode mismatch: '/Volumes/970Raid                ' '            78720565' vs '                   2'
inode mismatch: '/Volumes/Macintosh HD           ' '            78716885' vs '                   2'
inode mismatch: '/Volumes/Macintosh HD - Data    ' '            78716879' vs '                   2'
inode mismatch: '/Volumes/TimeMachine            ' '            78716884' vs '                   2'
inode mismatch: '/Volumes/Vault                  ' '            78716876' vs '                   2'
inode mismatch: '/cores                          ' ' 1152921500312764847' vs '                 321'
inode mismatch: '/dev                            ' ' 1152921500312764848' vs '                 388'
inode mismatch: '/opt                            ' ' 1152921500312764850' vs '                 324'
inode mismatch: '/private                        ' ' 1152921500312764851' vs '            78132742'
inode mismatch: '/usr/libexec/cups               ' ' 1152921500312768002' vs '            78110424'
inode mismatch: '/usr/local                      ' ' 1152921500312773577' vs '            78110530'
inode mismatch: '/usr/share/snmp                 ' ' 1152921500312787193' vs '            78110532'

/Volumes/$* are mounts.

The left column is the 'inode' from getattrlistbulk

The right is the 'inode' as I fetch using lstat

        typedef struct IDDNodeAttribute {
            uint32_t         length;          // ATTR_BIT_MAP_COUNT
            attribute_set_t  returned;        // ATTR_CMN_RETURNED_ATTRS
            char             *name;
            attrreference_t  name_info;       // ATTR_CMN_NAME
            fsid_t           fsid;            // ATTR_CMN_FSID
            fsobj_type_t     obj_type;        // ATTR_CMN_OBJTYPE
            struct timespec  modtime;         // ATTR_CMN_MODTIME
            uint64_t         inode;           // ATTR_CMN_FILEID
            struct {
                u_int32_t   link_count;       // ATTR_FILE_LINKCOUNT
                off_t       alloc_size;       // ATTR_FILE_ALLOCSIZE
                off_t       total_size;       // ATTR_FILE_TOTALSIZE
            };
        } __attribute__((aligned(4), packed)) IDDNodeAttribute;
        // buffer to place our results
        const int IDDNodeAttributeBufferSize = 32 * sizeof(IDDNodeAttribute);
        struct attrlist attrList = {
            .bitmapcount    = ATTR_BIT_MAP_COUNT,
            .commonattr     = ATTR_CMN_RETURNED_ATTRS | ATTR_CMN_NAME | ATTR_CMN_FSID | ATTR_CMN_OBJTYPE | ATTR_CMN_MODTIME | ATTR_CMN_FILEID,
            .dirattr        = ATTR_DIR_ENTRYCOUNT,
            .fileattr       = ATTR_FILE_LINKCOUNT | ATTR_FILE_ALLOCSIZE | ATTR_FILE_TOTALSIZE
        };
        int itemCount = getattrlistbulk(dirfd, &attrList, attrBuf, IDDNodeAttributeBufferSize, 1);

        if (itemCount == -1) {
            _logError(NSStringFromSelector(_cmd), [NSString stringWithFormat:@"error: '%ld = %s' path: '%s'", (long)errno, strerror(errno), path]);
            break;
        } else if (itemCount == 0) {
            break;
        } else {
            char* entry_start = attrBuf;

            for (int index = 0; index < itemCount; index++) {
                IDDNode* child = [self _fetchChild:&entry_start :filePath :pnode];

                if (child) {
                    [rv addObject:child];
                }
            }
        }

- (IDDNode*)_fetchChild:(char**)entry_start :(NSString*)parentPath :(uint64)pnode {
    IDDNodeAttribute attribute;
    char* field = *entry_start;

    attribute.length = *(uint32_t *)field;
    field += sizeof(uint32_t);
    *entry_start += attribute.length;
    
    attribute.returned = *(attribute_set_t *)field;
    field += sizeof(attribute_set_t);

    if (attribute.returned.commonattr & ATTR_CMN_NAME) {
        attribute.name = field;
        attribute.name_info = *(attrreference_t *)field;
        field += sizeof(attrreference_t);
        
        // DEBUG
        NSString* childPath = [parentPath stringByAppendingPathComponent:[NSString stringWithUTF8String:(attribute.name + attribute.name_info.attr_dataoffset)]];
        _logInfo(NSStringFromSelector(_cmd), [NSString stringWithFormat:@"filePath: '%@'", childPath]);
    }

    if (attribute.returned.commonattr & ATTR_CMN_FSID) {
        attribute.fsid = *(fsid_t *)field;
        field += sizeof(fsid_t);
        int32_t fileSystemID = (int32_t)attribute.fsid.val[0];
        
        // DEBUG
        NSString* childPath = [parentPath stringByAppendingPathComponent:[NSString stringWithUTF8String:(attribute.name + attribute.name_info.attr_dataoffset)]];
        _logInfo(NSStringFromSelector(_cmd), [NSString stringWithFormat:@"filePath: '%@' fileSystemID: '%d'", childPath, fileSystemID]);
    }

    if (attribute.returned.commonattr & ATTR_CMN_OBJTYPE) {
        attribute.obj_type = *(fsobj_type_t *)field;
        field += sizeof(fsobj_type_t);
        
        // DEBUG
        NSString* childPath = [parentPath stringByAppendingPathComponent:[NSString stringWithUTF8String:(attribute.name + attribute.name_info.attr_dataoffset)]];
        _logInfo(NSStringFromSelector(_cmd), [NSString stringWithFormat:@"filePath: '%@' type: '%@'", childPath, [self _fileType:attribute.obj_type]]);
    }

    if (attribute.returned.commonattr & ATTR_CMN_MODTIME) {
        struct timespec time = *(struct timespec*)field;
        field += sizeof(struct timespec);

        NSDate* modificationDate = [NSDate dateWithTimeIntervalSince1970:time.tv_sec];
        
        // DEBUG
        NSString* childPath = [parentPath stringByAppendingPathComponent:[NSString stringWithUTF8String:(attribute.name + attribute.name_info.attr_dataoffset)]];
        _logInfo(NSStringFromSelector(_cmd), [NSString stringWithFormat:@"filePath: '%@' modificationDate: '%@'", childPath, modificationDate]);
    }

    if (attribute.returned.commonattr & ATTR_CMN_FILEID) {
        attribute.inode = *(uint64_t *)field;
            
        // DEDA DEBUG
        struct stat file_status;
        NSString* childPath = [parentPath stringByAppendingPathComponent:[NSString stringWithUTF8String:(attribute.name + attribute.name_info.attr_dataoffset)]];
        _logInfo(NSStringFromSelector(_cmd), [NSString stringWithFormat:@"filePath: '%@' inode: '%lld'", childPath, attribute.inode]);

        if (lstat((const char *)[childPath fileSystemRepresentation], &file_status) == 0) {
            int64_t inode2 = file_status.st_ino;
            if (attribute.inode != inode2) {
                _logError(NSStringFromSelector(_cmd), [NSString stringWithFormat:@"filePath: '%@' inode mismatch: '%lld'", childPath, inode2]);
            } else {
                _logInfo(NSStringFromSelector(_cmd), [NSString stringWithFormat:@"filePath: '%@' inode match: '%lld'", childPath, inode2]);
            }
        }
        field += sizeof(uint64_t);
    }
    return nil;
}

After spending a few more hours debuging this, it is possible I have a bad pointer arithmetic somewhere.

However this code fails on about a dozen paths in my giant file system. (more than 4m inodes)

The examples you posted all look related to either synthetic symbolic links (see the synthetic.conf man page) or mount points. It’s possible that lstat is following those ‘links’ and getattrlistbulk is not.

Let’s try a couple of tests;

  • For the synthetic symbolic link case, you can add custom ones using synthetic.conf. Try that out and see if it exhibits the same behaviour.

    IMPORTANT The delimiter in that file is a tab. Lots of folks, including myself, have wasted lots of time not understanding that )-:

  • For the mount point case, create a mount point directory without mounting on it and run your test, then mount on it and run your test again. I suspect you’ll see the inode change in lstat but not in getaddrlistbulk.

Share and Enjoy

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

These are special links, indeed, I knew a few under /Volume/ were mounts, but forgot that some others are also links such as /Applications

[macos-bigsur]/usr/local> ls -ail /
        12886009979 drwxrwxr-x   7 root  admin   224 Feb  4 11:00 Applications/

[macos-bigsur]/usr/local> ls -ail /System/Volumes/Data/
        12886009979 drwxrwxr-x    7 root  admin    224 Feb  4 11:00 Applications/

/Applications to /System/Volumes/Data/Applications

My problem comes from inconsistent results of stat() vs getattrlistbulk()

Now if I could just figure that out :-)

My problem comes from inconsistent results of stat vs getattrlistbulk

Now if I could just figure that out :-)

I suspect that, ultimately, my advice here is that you open a DTS tech support incident and talk to DTS’s file system specialist. However…

Can you explain more about your higher-level goals here? It seems that all the issues you’re having stem from crossing mount points, and those tend to be special cases even getattrlistbulk isn’t in play. If I had a better idea of why the current behaviour is causing you grief, I may be able to suggest an alternative path.

Share and Enjoy

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

getattrlistbulk inode
 
 
Q