11

I'm slowly coming to terms with the black magic behind the APFS system/data volume split. Today, however, I found an artifact that I just don't understand: files that simultaneously exist and do not.

The question is: in the sequence below, why do ls and find see a merged view sometimes and not others?

For example, we know that /usr/share contains entries from both the system volume (high-numbered inodes) and one from the Data volume (low-numbered inodes, specifically snmp):

# ls -li /usr/share
total 8
...
1152921500312798809 drwxr-xr-x    2 root  wheel    64 Jan  1  2020 skel
            4054997 drwxr-xr-x   28 root  wheel   896 Jan  1  2020 snmp
1152921500312798811 drwxr-xr-x    6 root  wheel   192 Jan  1  2020 tabset
...

It makes sense, therefore, that inspecting the Data volume only shows the Data entries:

# ls -li /System/Volumes/Data/usr/share
total 0
4054997 drwxr-xr-x  28 root  wheel  896 Jan  1  2020 snmp

As expected, find sees all of the files through / and only the Data files through /System/Volumes/Data, regardless of -x:

# find /usr/share | grep '^/usr/share/' | wc -l
   14956
# find -x /usr/share | grep '^/usr/share/' | wc -l
   14956
# find /System/Volumes/Data/usr/share | grep '^/System/Volumes/Data/usr/share/' | wc -l
     179
# find -x /System/Volumes/Data/usr/share | grep '^/System/Volumes/Data/usr/share/' | wc -l
     179

Here's where things get strange, however. If I do the same find from one level up, all of a sudden I get the merged view both ways:

# find /usr | grep '^/usr/share/' | wc -l
   14956
# find -x /usr | grep '^/usr/share/' | wc -l
   14956
# find /System/Volumes/Data/usr | grep /System/Volumes/Data/usr/share/ | wc -l
   14956
# find -x /System/Volumes/Data/usr | grep /System/Volumes/Data/usr/share/ | wc -l
   14956

You can see the same behavior if you compare the outputs of ls:

# ls /usr/share
# ls /System/Volumes/Data/usr/share
# ls -R /System/Volumes/Data/usr

I've been digging through the code for find and fts, however, and I don't see any trap doors for firmlinks.

Interestingly, the Gnu version of these tools do not see the merged view, so it's something in the Apple version.

What am I missing?

1 Answer 1

1

The behavior of chdir and fchdir appears to be broken. Consider the following minimal example:

#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>


int main(int argc, const char * argv[]) {
    chdir("/System/Volumes/Data/usr/libexec");
    assert(strcmp(getwd(NULL), "/System/Volumes/Data/usr/libexec") == 0);

    chdir("cups");
    assert(strcmp(getwd(NULL), "/System/Volumes/Data/usr/libexec/cups") == 0); // will be "/usr/libexec/cups" instead.

    return 0;
}

Note that you cannot reproduce it in a shell (e.g. Zsh) because its chdir will unwind the relative path into an absolute path (i.e. /System/Volumes/Data/usr/libexec/cups) before actually changing current directory.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .