Apple introduced a new way of file system hierarchy that can separate the system and user data within an APFS system container since macOS Catalina: Macintosh HD, a volume which is mounted as read-only for system files; and Macintosh HD - Data, a volume which is mounted as writable and for all user intractable files such as applications and preferences. There are also other volumes such as Recovery that is used for RecoveryOS:
$ diskutil list
/dev/disk0 (internal, physical):
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme *1.0 TB disk0
1: Apple_APFS_ISC Container disk2 524.3 MB disk0s1
2: Apple_APFS Container disk3 994.7 GB disk0s2
3: Apple_APFS_Recovery Container disk1 5.4 GB disk0s3
/dev/disk3 (synthesized):
#: TYPE NAME SIZE IDENTIFIER
0: APFS Container Scheme - +994.7 GB disk3
Physical Store disk0s2
1: APFS Volume Macintosh HD - Data 410.4 GB disk3s1
2: APFS Volume Macintosh HD 8.9 GB disk3s3
3: APFS Snapshot com.apple.os.update-... 8.9 GB disk3s3s1
4: APFS Volume Preboot 4.7 GB disk3s4
5: APFS Volume Recovery 769.9 MB disk3s5
6: APFS Volume VM 2.1 GB disk3s6
The role of each volume was assigned in its superblock structure (apfs_superblock_t
), which can be found in Apple’s APFS Reference:
#define APFS_VOL_ROLE_NONE 0x0000
#define APFS_VOL_ROLE_SYSTEM 0x0001
#define APFS_VOL_ROLE_USER 0x0002
#define APFS_VOL_ROLE_RECOVERY 0x0004
#define APFS_VOL_ROLE_VM 0x0008
#define APFS_VOL_ROLE_PREBOOT 0x0010
#define APFS_VOL_ROLE_INSTALLER 0x0020
#define APFS_VOL_ROLE_DATA 0x0040
#define APFS_VOL_ROLE_BASEBAND 0x0080
Although this is new to macOS at the time, the scheme was already used in iOS for a while. When the system booted, both Macintosh HD (APFS_VOL_ROLE_SYSTEM
) and Macintosh HD - Data (APFS_VOL_ROLE_DATA
) will be presented as a single logical volume with both their contents combined with the magic of firmlinks.
Firmlinks
Firmlinks are similar to symbolic links but allows bi-directional travel and only for directories. This effectively allow macOS to merge the contents of Macintosh HD and Macintosh HD - Data, e.g. /Users
on the read-only Macintosh HD is essentially a firmlink of /Shared
on the writable Macintosh HD - Data. This would also make Apple’s System Integrity Protection (SIP) more seamless, e.g. SIP-protected system applications resides in read-only /System/Applications
and user applications can be installed in the /Applications
, while the user see the combination of both in the root /Applications
.
The system firmlinks can be found at /usr/share/firmlinks
:
$ cat /usr/share/firmlinks
/AppleInternal AppleInternal
/Applications Applications
/Library Library
/System/Library/Caches System/Library/Caches
/System/Library/Assets System/Library/Assets
/System/Library/PreinstalledAssets System/Library/PreinstalledAssets
/System/Library/AssetsV2 System/Library/AssetsV2
/System/Library/PreinstalledAssetsV2 System/Library/PreinstalledAssetsV2
/System/Library/CoreServices/CoreTypes.bundle/Contents/Library System/Library/CoreServices/CoreTypes.bundle/Contents/Library
/System/Library/Speech System/Library/Speech
/Users Users
/Volumes Volumes
/cores cores
/opt opt
/private private
/usr/local usr/local
/usr/libexec/cups usr/libexec/cups
/usr/share/snmp usr/share/snmp
Create Your Own Synthetic Firmlinks
To create root-level directories since Catalina, we need to rely on synthetic firmlinks:
- Create a file in
/etc
namedsynthetic.conf
. - Make sure its permissions were set to:
root: read, write
wheel: read
everyone: read
(Usually command “sudo touch /etc/synthetic.conf
” would do all the above.)
In order to link directory /Users/foo/bar
to /baz
, we can add a new entry to the file we just created as following:
# create a firmlink named "baz" at / which points to "Users/foo/bar"
baz Users/foo/bar
You can also check man synthetic.conf
for all available options. Note that the two should be separate with a tab instead of spaces or it won’t work. Also the paths are not started with /
. Now reboot the system and see the changes we just made.