Photo of Joe T. Sylve

Joe T. Sylve, Ph.D.

Digital Forensic Researcher and Educator

Snapshot Metadata

Earlier in this series, we covered how Object Maps facilitate the implementation of point-in-time Snapshots of APFS file systems by preserving File System Tree Nodes from earlier transactions. In that discussion, I outlined the on-disk structure of the Object Map Snapshot Tree and how it can be used to enumerate the transaction identifiers of each Volume Snapshot. Today, we will briefly discuss two other sources of information that store additional metadata about each Snapshot.

Snapshot Metadata Tree

The Snapshot Metadata Tree is a B-Tree whose physical address can be located by reading the apfs_snap_meta_tree_oid field of the Volume Superblock. It stores two types of objects, structured as File System Records.

Snapshot Metadata Records

Snapshot Metadata Records store the bulk of metadata about Volume Snapshots. The key-half is a j_snap_metadata_key structure with an encoded type of APFS_TYPE_SNAP_METADATA.

typedef struct j_snap_metadata_key {
  j_key_t hdr;           // 0x00
} j_snap_metadata_key_t; // 0x08

The value-half of the record is a j_snap_metadata_val_t structure and is immediately followed by the UTF-8 encoded name of the snapshot.

typedef struct j_snap_metadata_val {
  oid_t extentref_tree_oid;       // 0x00
  oid_t sblock_oid;               // 0x08
  uint64_t create_time;           // 0x10
  uint64_t change_time;           // 0x18
  uint64_t inum;                  // 0x20
  uint32_t extentref_tree_type;   // 0x28
  uint32_t flags;                 // 0x2C
  uint16_t name_len;              // 0x30
  uint8_t name[0];                // 0x32
} j_snap_metadata_val_t;

Snapshot Metadata Record Flags

Name Value Description
SNAP_META_PENDING_DATALESS 0x00000001 This snapshot is dataless, meaning that it does not preserve the file extents
SNAP_META_MERGE_IN_PROGRESS 0x00000002 The snapshot is in the process of being merged with another

Snapshot Name Records

Snapshot Name Records are used to map snapshot names to their transaction identifiers. The key-half of the record is a j_snap_name_key_t structure with an encoded type of APFS_TYPE_SNAP_NAME. It is followed by the UTF-8 encoded name of the snapshot.

typedef struct j_snap_name_key {
  j_key_t hdr;        // 0x00
  uint16_t name_len;  // 0x08
  uint8_t name[0];    // 0x0A
} j_snap_name_key_t;

The value-half is a j_snap_name_val_t structure.

typedef struct j_snap_name_val {
  xid_t snap_xid;    // 0x00
} j_snap_name_val_t; // 0x08

Snapshot Extended Metadata Object

Each snapshot has a virtual Snapshot Extended Metadata Object in the volume’s Object Map. The virtual object identifier of this object is stored in the apfs_snap_meta_ext_oid field of the Volume Superblock. There are multiple versions of this object whose transaction identifiers correspond to each snapshot.

typedef struct snap_meta_ext_obj_phys {
  obj_phys_t smeop_o;        // 0x00
  snap_meta_ext_t smeop_sme; // 0x20
} snap_meta_ext_obj_phys_t;  // 0x48
typedef struct snap_meta_ext {
  uint32_t sme_version; // 0x00
  uint32_t sme_flags;   // 0x04
  xid_t sme_snap_xid;   // 0x08
  uuid_t sme_uuid;      // 0x10
  uint64_t sme_token;   // 0x20
} snap_meta_ext_t;      // 0x28

Snapshot Deletion

Deleting a snapshot is a multi-step background process that merges physical extent records into an adjacent snapshot before removing the metadata:

  1. The snapshot is renamed to com.apple.apfs.purgatory.<xid> (where <xid> is the transaction identifier in hexadecimal) and a background purgatory cleaner thread is woken.

  2. The purgatory cleaner locates the adjacent (next) snapshot and begins merging the source snapshot’s Extent Reference Tree into the destination’s tree.

  3. During the merge, SNAP_META_MERGE_IN_PROGRESS is set on both source and destination snapshot metadata records. Extents are merged in batches (up to 2048 per transaction). If a transaction cannot allocate space for the full batch, the batch size is halved until it succeeds. The thread yields between batches to allow other work to proceed.

  4. The implementation compares the source and destination extentref tree key counts to choose the merge direction. If the destination count is greater than or equal to the source count, it merges the source into the destination (forward merge). If the source count is strictly greater, it swaps roles so the smaller tree is merged into the larger, minimizing B-tree operations.

  5. After the merge completes, the source extentref tree is deleted, the snapshot metadata and name records are removed from the Snapshot Metadata Tree, and the Object Map snapshot entry is deleted.

If the merge is interrupted by a crash, SNAP_META_MERGE_IN_PROGRESS signals that it must resume on the next mount. Invalid entries (zero physical block, address exceeding device size) are logged and skipped rather than failing the entire merge.

Snapshot Revert

Reverting a volume to a snapshot replaces the live file system state with the snapshot’s state:

  1. If the volume is mounted read-only, the revert is deferred: apfs_revert_to_xid is set to the target snapshot’s transaction identifier in the Volume Superblock, and the actual revert occurs on the next read-write mount.

  2. On read-write mount (or immediately if already read-write), the revert enters a transaction:
    • The snapshot’s Volume Superblock is copied over the current one.
    • Physical extent state is reverted.
    • The Object Map is reverted via omap_revert_to_snapshot, which sets om_pending_revert_min and om_pending_revert_max to skip entries from the reverted range, effectively restoring the OMAP view to the target snapshot.
    • All snapshots newer than the target are marked for deletion.
    • The transaction is committed.
  3. After the revert, caches (integrity metadata, encryption state) are invalidated and rebuilt. The Reaper handles background cleanup of the deleted snapshot entries.

The key insight is that the revert takes effect immediately through the OMAP’s pending revert mechanism. Background cleanup proceeds asynchronously while the volume is already operating at the reverted state.

Dataless Snapshots

A dataless snapshot has had its physical extent data removed but retains its metadata and Volume Superblock backup. This preserves the snapshot timeline (for audit and restoration purposes) without consuming space for extent data.

Making a snapshot dataless follows the same merge path as full deletion, except that metadata and name records are preserved afterward. The SNAP_META_PENDING_DATALESS flag is set, the purgatory cleaner merges the extentref tree into the adjacent snapshot, and upon completion extentref_tree_oid is set to zero and the flag is cleared.

Constraints on making a snapshot dataless:

Conclusion

Snapshot metadata in APFS is distributed across multiple structures: the Snapshot Metadata Tree stores per-snapshot records and name mappings, the Snapshot Extended Metadata Object provides UUIDs and versioning, and the Extent Reference Trees track which physical extents each snapshot owns. Deletion and revert are multi-phase processes that leverage the Reaper and the Object Map’s revert mechanism to maintain consistency across transactions.

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