Photo of Joe T. Sylve

Joe T. Sylve, Ph.D.

Digital Forensic Researcher and Educator

Object Maps

Earlier in this series, we discussed APFS Containers and how they address physical objects via a fixed block size. This was followed by a discussion on enumerating Checkpoint Maps to locate ephemeral objects. The last remaining kind of objects that we need to know how to find are virtual objects. Today, we will discuss an essential specialization of B-Trees, the Object Map (OMAP), and their critical role in managing these virtual objects in APFS.

Object Maps perform two essential roles in APFS. They facilitate the address translation needed to locate virtual objects on disk and provide snapshot capabilities that can instantly roll back the set of virtual objects to an earlier point in time. The Container and each Volume maintain their own independent OMAPs. Each OMAP has its own virtual address space, so when dereferencing a virtual object, it is essential to understand how that object is used to know which object map to query.

On-Disk Structures

OMAP Objects have a relatively simple on-disk structure. Along with some minor metadata, the primary use of the OMAP Object is to store the physical address of its tree. Optionally, they also store an address to a Snapshot Tree. Both trees are structured as B-Tree Objects.

Incidentally, there are indications that APFS was intended to support more than one type of tree structure. Until recently, Apple’s apfs.kext contained references to an undocumented “M-Tree” type, which appeared to be designed to be used in place of B-Trees in some instances. M-Trees are never mentioned in Apple’s APFS File System Reference, I have never seen them on disk, and all references of M-Trees were removed from the macOS 13 apfs.kext.

typedef struct omap_phys {
    obj_phys_t om_o;                // 0x00
    uint32_t om_flags;              // 0x20
    uint32_t om_snap_count;         // 0x24
    uint32_t om_tree_type;          // 0x28
    uint32_t om_snapshot_tree_type; // 0x2C
    oid_t om_tree_oid;              // 0x30
    oid_t om_snapshot_tree_oid;     // 0x38
    xid_t om_most_recent_snap;      // 0x40
    xid_t om_pending_revert_min;    // 0x48
    xid_t om_pending_revert_max;    // 0x50
    oid_t om_min_oid;               // 0x58
} omap_phys_t;                      // 0x60

NOTE: Apple’s APFS File System Reference incorrectly lists the om_tree_oid and om_snapshot_tree_oid members as _virtual object identifiers. They are not virtual; each is a physical object identifier when its *_tree_type is physical, or an ephemeral object identifier when it is ephemeral.

Object Map Flags

Name Value Description
OMAP_MANUALLY_MANAGED 0x00000001 Does not support snapshots
OMAP_ENCRYPTING 0x00000002 Encryption in progress
OMAP_DECRYPTING 0x00000004 Decryption in progress
OMAP_KEYROLLING 0x00000008 Encryption key change in progress
OMAP_CRYPTO_GENERATION 0x00000010 Tracks the current encryption configuration generation
OMAP_CONVERSION_IN_PROGRESS 0x00000020 A crypto conversion operation is actively in progress

Object Map B-Tree

An OMAP tree is a specialized B-Tree type that maps fixed-size omap_key_t and omap_val_t key/value pairs. There can be more than one referenced object with the same virtual object identifier (oid) stored in the tree. Transaction identifiers (xid) are stored to identify these versions.

Keys are sorted in ascending order, first by oid and then xid. Values contain the size and physical address of the mapped object. We will discuss APFS encryption in the future, but for now, it is sufficient to note that omap_val_t marks encrypted objects via the OMAP_VAL_ENCRYPTED bit-flag.

typedef struct omap_key {
    oid_t ok_oid; // 0x00
    xid_t ok_xid; // 0x08
} omap_key_t;     // 0x10
typedef struct omap_val {
    uint32_t ov_flags; // 0x00
    uint32_t ov_size;  // 0x04
    paddr_t ov_paddr;  // 0x08
} omap_val_t;          // 0x10

Object Map Value Flags

Name Value Description
OMAP_VAL_DELETED 0x00000001 Object mapping has been removed from the map and this is a placeholder
OMAP_VAL_SAVED 0x00000002 This object mapping shouldnʼt be replaced when the object is updated. (currently unused)
OMAP_VAL_ENCRYPTED 0x00000004 The mapped object is encrypted
OMAP_VAL_NOHEADER 0x00000008 The mapped object has a zero’d object header
OMAP_VAL_CRYPTO_GENERATION 0x00000010 Encryption change marker

Object Map Snapshot Tree

Volume OMAPs (those without OMAP_MANUALLY_MANAGED set) maintain an entry for each of their snapshots in a Snapshot Tree. The container’s OMAP is manually managed and does not support snapshots. These trees map xid_t transaction identifiers to omap_snapshot_t values. Other than the deletion state of a snapshot, there is very little information to be gained from enumerating this tree. Volumes maintain additional trees that store more detailed metadata about snapshots. We will discuss those trees in the future.

typedef struct omap_snapshot {
    uint32_t oms_flags; // 0x00
    uint32_t oms_pad;   // 0x04
    oid_t oms_oid;      // 0x08
} omap_snapshot_t;      // 0x10
Name Value Description
OMAP_SNAPSHOT_DELETED 0x00000001 The snapshot has been deleted
OMAP_SNAPSHOT_REVERTED 0x00000002 The snapshot has been deleted as part of a revert

Parsing Object Maps

Once you understand the structure of the OMAP key/value pairs, the process of parsing an OMAP is the same as parsing other B-Trees. When looking up a virtual object from the active filesystem state, choose the key with the highest xid available. If the selected value has the OMAP_VAL_DELETED flag set in ov_flags, the mapping is a tombstone and no valid object exists. If parsing the state from a snapshot, ignore all keys whose xid is greater than the xid of the snapshot in question.

If om_pending_revert_min is nonzero (indicating a revert is in progress), skip any entry whose xid falls within [om_pending_revert_min, om_pending_revert_max] and use the next-best match. This is how APFS makes the revert take effect immediately while background cleanup proceeds.

Snapshot Lifecycle

The OMAP’s snapshot capabilities are what make APFS snapshots nearly instantaneous. Rather than copying data, snapshots simply freeze a transaction boundary. All object versions at or before the snapshot’s xid are preserved, and any subsequent modifications create new entries via copy-on-write without disturbing the snapshot’s view.

Snapshot Creation

Creating a snapshot is lightweight:

  1. If om_snapshot_tree_oid is zero (no snapshot tree exists yet), a new physical B-Tree is created for the snapshot tree.
  2. A new entry is inserted into the snapshot tree with the snapshot’s transaction identifier as the key and a zeroed omap_snapshot_t as the value.
  3. om_most_recent_snap is set to the new snapshot’s transaction identifier.
  4. om_snap_count is incremented.

From this point forward, any modification to a virtual object that was last written at or before the snapshot’s xid triggers a copy-on-write: the old mapping remains in the OMAP tree (accessible to the snapshot), and a new mapping is created at the current xid pointing to the modified copy.

Snapshot Deletion

Deleting a snapshot is more involved because it may require freeing physical blocks that were only preserved for that snapshot:

  1. The snapshot entry in the snapshot tree has OMAP_SNAPSHOT_DELETED set in its flags.
  2. The OMAP is added to the Reaper’s work list for background cleanup.

The Reaper processes the deletion in two phases:

Snapshot Revert

Reverting to a snapshot restores the OMAP’s view to an earlier point in time:

  1. A new snapshot is created at the current transaction identifier to preserve the current state (as a safety net).
  2. om_pending_revert_min is set to target_snapshot_xid + 1.
  3. om_pending_revert_max is set to the current transaction identifier.
  4. All snapshots between the target and the current transaction are marked with both OMAP_SNAPSHOT_DELETED and OMAP_SNAPSHOT_REVERTED.

The revert takes effect immediately: all OMAP lookups skip entries with transaction identifiers in the range [om_pending_revert_min, om_pending_revert_max], which effectively restores the view to the target snapshot. The Reaper cleans up the reverted entries in the background, progressively advancing om_pending_revert_min as each snapshot in the range is processed. When om_pending_revert_min reaches om_pending_revert_max, both fields are set to zero, completing the revert.

Object Map Eviction

The container can evict OMAP entries from a physical address range to reclaim disk space. This is used during defragmentation, tier migration (on Fusion Drives), and container resize operations. The eviction process:

  1. Iterates the OMAP B-Tree looking for entries whose ov_paddr falls within the target range.
  2. For each matching entry, reads the object from its current physical address.
  3. Writes the object to a new location allocated by the Space Manager.
  4. Updates the OMAP entry to point to the new location.

For objects that have a snapshot-protected prior version at the same physical address, eviction creates a new version in the object map pointing to the new location without disturbing the snapshot’s view of the old address. Eviction is cancellable: if interrupted (for example, due to unmount), the operation stops and returns ECANCELED.

The container’s NX Superblock tracks eviction state via nx_evict_mapping_tree_oid, which points to an evict-mapping tree that records source-to-destination block relocations. This tree uses simple key/value pairs:

typedef struct evict_mapping_key {
    paddr_t paddr;        // source physical address
} evict_mapping_key_t;

typedef struct evict_mapping_val {
    paddr_t dst_paddr;    // destination physical address
    uint64_t len;         // number of blocks relocated
} evict_mapping_val_t;

Conclusion

Object Maps are an essential component of APFS that serve two key roles. They provide the address translation needed to locate virtual objects on disk and enable snapshot capabilities that allow instant rollback to earlier points in time. The snapshot lifecycle (creation, deletion, and revert) leverages the OMAP’s versioned key structure and the Reaper’s multi-phase cleanup to provide efficient, crash-safe snapshot management. Object map eviction further enables live container operations like defragmentation and resize without unmounting.

Now that we can locate physical, ephemeral, and virtual objects, we have everything we need to explore the container’s remaining subsystems. In the next few posts, we will examine the Space Manager, the Reaper, and EFI Jumpstart before turning our attention to volumes and their file systems.

Find an issue or technical inaccuracy in this post? Please file an issue so that it may be corrected.