APFS: Beyond, to vfs and volfs

APFS is but one of many file systems supported by macOS. Others currently include:

ACFS, Apple’s cluster file system for Xsan systems
AFP, Apple’s network file system, now deprecated
cd9660, the standard optical disk file system of ISO 9660
cddafs, a virtual file system for CDs
ExFAT, Microsoft’s Windows file system
ftp, a network file system for FTP
HFS and HFS+, Apple’s Macintosh Extended file system, predecessor to APFS
MSDOS, the older MS-DOS file systems of FAT16 and FAT32 preceding ExFAT
NFS, a distributed network file system originating from Sun
NTFS, Microsoft’s Windows file system, in read-only form
SMB, the network file system now preferred over AFP
tmpfs, a virtual temporary file storage system
UDF, a replacement for cd9660 supporting DVDs and other optical storage
union, a virtual file system described below
Virtiofs, the file system supported by macOS virtual machines running on Apple silicon
volfs, a virtual file system described below
WebDAV, a network file system supporting WebDAV servers.

Most software running on Macs doesn’t want to be bothered having to deal individually with each of the many file systems it supports, so macOS provides APIs that abstract file system functions for convenience. Instead of an app having to explicitly create a clone file in APFS, or a simple duplicate in HFS+, a single call to FileManager’s copyItem() does both, depending on the underlying file system.

That can’t of course work for the kernel, which achieves its abstraction through the layer of its Virtual File System vfs. This has its origins in SunOS 2.0 of 1985, from where it was adopted in 4.4BSD, and from there to NeXTSTEP and into Mac OS X.

vfs and vnodes

vfs brings two important structures: vnodes and mount structures. Each mounted file system is represented as a mount structure, and each inode or equivalent for a file, directory or other storage item has its own vnode. vfs then has a suite of functions such as vfs_mount to provide the kernel with the calls it needs to access and work with items in those user file systems.

Fuller details are concentrated in bsd/sys/vnode.h in Apple’s XNU open source code, with its function implementations in bsd/vfs. Most of that code hasn’t changed for years, apart from the addition of support for APFS. One recent innovation is the addition of vfs support for the kernel’s new exclaves that are thought to be part of a programme to refactor the kernel into a central micro-kernel with protected exclaves.

vnodes refer to items in user file systems using their inode or an equivalent structure for those not using inodes. vnode attributes are extensive, and even include Finder Info and information about clone files. They also play an important role in code security with cached hashes for apps and other signed code.

Hash caching

Signing an app builds a tree of SHA-256 hashes of its components, as explained in detail in Apple’s Tech Note 3126. Those hashes are hashed together to generate a Code Directory Hash, its cdhash. A notarization ticket, normally stapled to its app bundle where possible, is a set of cdhashes signed by Apple’s Notary service when the process of notarization has completed successfully.

The kernel then uses cdhashes to verify apps and other signed code. Those cdhashes are cached and compared against recomputed hashes; if the hashes match, that demonstrates that the code and other protected resources such as an app’s Info.plist remain unaltered from those obtained at the time of signing and notarization. However, as the kernel indexes into those hashes using vnodes, which are then accessed as inodes, updating an app can break synchrony between hashes and those in the kernel’s cache. This is explained in the diagram below, based on information here.

When the app is first fully installed and its signature checked, its cdhashes are checked and saved to the kernel’s cache against the vnode, and in turn its APFS inode. When that app is run again and the cdhashes are checked, they should match those in the cache, and its signature is validated.

If an updater then updates that executable code in place, even if it correctly updates locally saved cdhashes, if that inode remains unchanged, then the cached cdhashes for the vnode for that inode will be out of date. The kernel will detect this mismatch, and that code will fail its signature check. This can happen in APFS if the updated code is moved into place using the cp command, for example.

If the updater instead replaces the code file completely, so that the replacement has a different inode, the kernel will then update its vnode record and cache the new code hash, so it will correctly validate the new cdhash for the app. Apple recommends developers inspect the inode before and after update to detect this problem: if those don’t change, then the signature is unlikely to validate.

volfs

The best-known virtual file system accessible by the user is the Volume File System or volfs, whose primary purpose is to support POSIX and Carbon APIs. volfs is rooted in the hidden folder named .vol at the root level of the file system. Within that, each volume is mounted in a numbered subdirectory, used as the volume ID. The item ID within a volume is the same as the inode number. Thus
/.vol/16777240/147320537
is a volfs path on volume ID 16777240 to an item with an inode number of 147320537.

volfs knows nothing of directory hierarchies, so can’t enumerate files within a directory. Individual items can be accessed through their volfs path, for example
ls -li /.vol/16777240/147320537

As with File Reference URLs, volfs paths don’t change when an item is moved within the same volume. While volfs can be used to access files on APFS and HFS+ volumes, it doesn’t work with some other volume file systems.

File Reference URL

Although not normally considered as a virtual file system, File Reference URLs are an alternative to conventional URL paths that are resilient to movement within a volume regardless of its underlying file system. These use a path syntax identifying a file system object such as a file or directory by reference instead. That contains volume and file IDs, such as
file:///.file/id=6571367.147320537
where 6571367 is the volume ID, which differs from that used in volfs, and 147320537 is the item’s inode number, which is identical across APFS, volfs and the File Reference URL.

macOS supports conversion from a path URL to File Reference URL with NSURL’s fileReferenceURL() property. Apple cautions about storing File Reference URLs, as they’re not guaranteed to remain unchanged after rebooting or remounting a volume.

union file system

Finally, many operating systems derived from Unix and Linux, including macOS, support the union mount file system as a means of mounting a file system spanning more than one volume. This enables the merging of multiple directories into a new directory tree, and has various other potential uses.

Union is normally accomplished using the mount command with the -o union option when mounting individual volumes, such as
mount -t apfs -o union /dev/disk8s1 /mountpoint/union
mount -t apfs -o union /dev/disk10s1 /mountpoint/union
to mount disk8s1 and disk10s1 in union.

For some this may be an attractive feature; although the documentation doesn’t state this is incompatible with APFS file systems, it has here only ever returned errors claiming that apfs isn’t compatible with the union file system.

There are other virtual file systems that have been available and may remain accessible on macOS, detailed in Amit Singh’s book.

Articles in this series

1. Files and clones
2. Directories and names
3. Containers and volumes
4. Snapshots
5. Encryption and sealing
6. Special file types
7. Command tools

References

https://developer.apple.com/documentation/security/updating_mac_software
Apple’s APFS Reference (PDF), last revised 22 June 2020.
Amit Singh (2007) Max OS X Internals, A Systems Approach, Addison Wesley. ISBN 0 321 27854 2.