#pragma author Hrant Tadevosyan (Axcient, now ConnectWise) #pragma description Apple File System (APFS) #pragma endian little // refs: // - https://developer.apple.com/support/downloads/Apple-File-System-Reference.pdf // - https://github.com/sgan81/apfs-fuse // - https://github.com/libyal/libfsapfs import std.core; import std.hash; import std.mem; import type.guid; import type.magic; import type.time; using paddr_t = s64; struct prange { paddr_t pr_start_paddr; u64 pr_block_count; }; using uuid_t = type::GUID; #define MAX_CKSUM_SIZE 8 using oid_t = u64; using xid_t = u64; #define OID_NX_SUPERBLOCK 1 #define OID_INVALID 0ULL #define OID_RESERVED_COUNT 1024 #define OBJECT_TYPE_MASK 0x0000ffff #define OBJECT_TYPE_FLAGS_MASK 0xffff0000 #define OBJ_STORAGETYPE_MASK 0xc0000000 #define OBJECT_TYPE_FLAGS_DEFINED_MASK 0xf8000000 enum o_type_id_t : u16 { OBJECT_TYPE_NX_SUPERBLOCK = 0x0001, OBJECT_TYPE_BTREE = 0x0002, OBJECT_TYPE_BTREE_NODE = 0x0003, OBJECT_TYPE_SPACEMAN = 0x0005, OBJECT_TYPE_SPACEMAN_CAB = 0x0006, OBJECT_TYPE_SPACEMAN_CIB = 0x0007, OBJECT_TYPE_SPACEMAN_BITMAP = 0x0008, OBJECT_TYPE_SPACEMAN_FREE_QUEUE = 0x0009, OBJECT_TYPE_EXTENT_LIST_TREE = 0x000a, OBJECT_TYPE_OMAP = 0x000b, OBJECT_TYPE_CHECKPOINT_MAP = 0x000c, OBJECT_TYPE_FS = 0x000d, OBJECT_TYPE_FSTREE = 0x000e, OBJECT_TYPE_BLOCKREFTREE = 0x000f, OBJECT_TYPE_SNAPMETATREE = 0x0010, OBJECT_TYPE_NX_REAPER = 0x0011, OBJECT_TYPE_NX_REAP_LIST = 0x0012, OBJECT_TYPE_OMAP_SNAPSHOT = 0x0013, OBJECT_TYPE_EFI_JUMPSTART = 0x0014, OBJECT_TYPE_FUSION_MIDDLE_TREE = 0x0015, OBJECT_TYPE_NX_FUSION_WBC = 0x0016, OBJECT_TYPE_NX_FUSION_WBC_LIST = 0x0017, OBJECT_TYPE_ER_STATE = 0x0018, OBJECT_TYPE_GBITMAP = 0x0019, OBJECT_TYPE_GBITMAP_TREE = 0x001a, OBJECT_TYPE_GBITMAP_BLOCK = 0x001b, OBJECT_TYPE_ER_RECOVERY_BLOCK = 0x001c, OBJECT_TYPE_SNAP_META_EXT = 0x001d, OBJECT_TYPE_INTEGRITY_META = 0x001e, OBJECT_TYPE_FEXT_TREE = 0x001f, OBJECT_TYPE_RESERVED_20 = 0x0020, OBJECT_TYPE_INVALID = 0x0000, OBJECT_TYPE_TEST = 0x00ff, OBJECT_TYPE_CONTAINER_KEYBAG = 0x7973, // ys -> keys OBJECT_TYPE_CONTAINER_KEYBAG_2 = 0x6B65, // ke -> //OBJECT_TYPE_VOLUME_KEYBAG = "recs", //OBJECT_TYPE_MEDIA_KEYBAG = "mkey", }; enum o_flag_id_t : u16 { OBJ_VIRTUAL = 0x0000, OBJ_EPHEMERAL = 0x8000, OBJ_PHYSICAL = 0x4000, OBJ_NOHEADER = 0x2000, OBJ_ENCRYPTED = 0x1000, OBJ_NONPERSISTENT = 0x0800, }; struct o_type_t { o_type_id_t t_type; o_flag_id_t t_flag; }; struct obj_phys_t { u8 o_cksum[MAX_CKSUM_SIZE]; oid_t o_oid; xid_t o_xid; o_type_t o_type; o_type_t o_subtype; }; #define NX_MAGIC_RE "BSXN" #define NX_MAGIC "NXSB" #define NX_MAX_FILE_SYSTEMS 100 #define NX_EPH_INFO_COUNT 4 #define NX_EPH_MIN_BLOCK_COUNT 8 #define NX_MAX_FILE_SYSTEM_EPH_STRUCTS 4 #define NX_TX_MIN_CHECKPOINT_COUNT 4 #define NX_EPH_INFO_VERSION_1 1 #define NX_RESERVED_1 0x00000001LL #define NX_RESERVED_2 0x00000002LL #define NX_CRYPTO_SW 0x00000004LL #define NX_FEATURE_DEFRAG 0x0000000000000001ULL #define NX_FEATURE_LCFD 0x0000000000000002ULL #define NX_SUPPORTED_FEATURES_MASK (NX_FEATURE_DEFRAG | NX_FEATURE_LCFD) bitfield nx_features_t { unsigned defrag : 1; unsigned lcfd : 1; padding : 62; } [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 64)]]; #define NX_SUPPORTED_ROCOMPAT_MASK (0x0ULL) bitfield nx_rocompat_features_t { padding : 64; } [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 64)]]; #define NX_INCOMPAT_VERSION1 0x0000000000000001ULL #define NX_INCOMPAT_VERSION2 0x0000000000000002ULL #define NX_INCOMPAT_FUSION 0x0000000000000100ULL #define NX_SUPPORTED_INCOMPAT_MASK (NX_INCOMPAT_VERSION2 | NX_INCOMPAT_FUSION) bitfield nx_incompat_features_t { version1 : 1; version2 : 1; padding : 6; fusion : 1; padding : 55; } [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 64)]]; #define NX_MINIMUM_BLOCK_SIZE 4096 #define NX_DEFAULT_BLOCK_SIZE 4096 #define NX_MAXIMUM_BLOCK_SIZE 65536 #define NX_MINIMUM_CONTAINER_SIZE 1048576 enum nx_counter_id_t : u64 { NX_CNTR_OBJ_CKSUM_SET = 0, NX_CNTR_OBJ_CKSUM_FAIL = 1, NX_NUM_COUNTERS = 32 }; struct nx_superblock_t { obj_phys_t nx_o; type::Magic nx_magic; u32 nx_block_size; u64 nx_block_count; nx_features_t nx_features; nx_rocompat_features_t nx_readonly_compatible_features; nx_incompat_features_t nx_incompatible_features; uuid_t nx_uuid; oid_t nx_next_oid; xid_t nx_next_xid; u32 nx_xp_desc_blocks; u32 nx_xp_data_blocks; paddr_t nx_xp_desc_base; paddr_t nx_xp_data_base; u32 nx_xp_desc_next; u32 nx_xp_data_next; u32 nx_xp_desc_index; u32 nx_xp_desc_len; u32 nx_xp_data_index; u32 nx_xp_data_len; oid_t nx_spaceman_oid; oid_t nx_omap_oid; oid_t nx_reaper_oid; u32 nx_test_type; u32 nx_max_file_systems; oid_t nx_fs_oid[NX_MAX_FILE_SYSTEMS]; nx_counter_id_t nx_counters[nx_counter_id_t::NX_NUM_COUNTERS]; prange nx_blocked_out_prange; oid_t nx_evict_mapping_tree_oid; u64 nx_flags; paddr_t nx_efi_jumpstart; uuid_t nx_fusion_uuid; prange nx_keylocker; u64 nx_ephemeral_info[NX_EPH_INFO_COUNT]; oid_t nx_test_oid; oid_t nx_fusion_mt_oid; oid_t nx_fusion_wbc_oid; prange nx_fusion_wbc; u64 nx_newest_mounted_version; prange nx_mkb_locker; }; #define CHECKPOINT_MAP_LAST 0x00000001 struct checkpoint_mapping_t { o_type_t cpm_type; o_type_t cpm_subtype; u32 cpm_size; u32 cpm_pad; oid_t cpm_fs_oid; oid_t cpm_oid; oid_t cpm_paddr; }; struct checkpoint_map_phys_t { obj_phys_t cpm_o; u32 cpm_flags; u32 cpm_count; checkpoint_mapping_t cpm_map[cpm_count]; }; struct evict_mapping_val_t { paddr_t dst_paddr; u64 len; }; struct omap_phys_t { obj_phys_t om_o; u32 om_flags; u32 om_snap_count; u32 om_tree_type; u32 om_snapshot_tree_type; oid_t om_tree_oid; oid_t om_snapshot_tree_oid; xid_t om_most_recent_snap; xid_t om_pending_revert_min; xid_t om_pending_revert_max; }; struct omap_key_t { oid_t ok_oid; oid_t ok_xid; }; struct omap_val_t { u32 ov_flags; u32 ov_size; paddr_t ov_paddr; }; struct omap_snapshot_t { u32 oms_flags; u32 oms_pad; oid_t oms_oid; }; struct chunk_info_t { u64 ci_xid; u64 ci_addr; u32 ci_block_count; u32 ci_free_count; paddr_t ci_bitmap_addr; }; struct chunk_info_block_t { obj_phys_t cib_o; u32 cib_index; u32 cib_chunk_info_count; chunk_info_t cib_chunk_info[cib_chunk_info_count]; }; struct cib_addr_block_t { obj_phys_t cab_o; u32 cab_index; u32 cab_cib_count; paddr_t cab_cib_addr[cab_cib_count]; }; struct spaceman_free_queue_key_t { xid_t sfqk_xid; paddr_t sfqk_paddr; }; using spaceman_free_queue_val_t = u64; struct spaceman_free_queue_entry_t { spaceman_free_queue_key_t sfqe_key; spaceman_free_queue_val_t sfqe_count; }; struct spaceman_free_queue_t { u64 sfq_count; oid_t sfq_tree_oid; xid_t sfq_oldest_xid; u16 sfq_tree_node_limit; u16 sfq_pad16; u32 sfq_pad32; u64 sfq_reserved; }; struct spaceman_device_t { u64 sm_block_count; u64 sm_chunk_count; u32 sm_cib_count; u32 sm_cab_count; u64 sm_free_count; u32 sm_addr_offset; u32 sm_reserved; u64 sm_reserved2; }; enum smdev_t : u32 { SD_MAIN = 0, SD_TIER2 = 1, SD_COUNT = 2 }; enum sfq_t : u32 { SFQ_IP = 0, SFQ_MAIN = 1, SFQ_TIER2 = 2, SFQ_COUNT = 3 }; struct spaceman_allocation_zone_boundaries_t { u64 saz_zone_start; u64 saz_zone_end; }; #define SM_ALLOCZONE_INVALID_END_BOUNDARY 0 #define SM_ALLOCZONE_NUM_PREVIOUS_BOUNDARIES 7 struct spaceman_allocation_zone_info_phys_t { spaceman_allocation_zone_boundaries_t saz_current_boundaries; spaceman_allocation_zone_boundaries_t saz_previous_boundaries[SM_ALLOCZONE_NUM_PREVIOUS_BOUNDARIES]; u16 saz_zone_id; u16 saz_previous_boundary_index; u32 saz_reserved; }; struct spaceman_allocation_zones_t { spaceman_allocation_zone_info_phys_t sdz_allocation_zone_infos[smdev_t::SD_COUNT]; }; #define SM_DATAZONE_ALLOCZONE_COUNT 8 struct spaceman_datazone_info_phys_t { spaceman_allocation_zones_t sdz_allocation_zones[SM_DATAZONE_ALLOCZONE_COUNT]; }; struct spaceman_phys_t { obj_phys_t sm_o; u32 sm_block_size; u32 sm_blocks_per_chunk; u32 sm_chunks_per_cib; u32 sm_cibs_per_cab; spaceman_device_t sm_dev[smdev_t::SD_COUNT]; u32 sm_flags; u32 sm_ip_bm_tx_multiplier; u64 sm_ip_block_count; u32 sm_ip_bm_size_in_blocks; u32 sm_ip_bm_block_count; paddr_t sm_ip_bm_base; paddr_t sm_ip_base; u64 sm_fs_reserve_block_count; u64 sm_fs_reserve_alloc_count; spaceman_free_queue_t sm_fq[sfq_t::SFQ_COUNT]; u16 sm_ip_bm_free_head; u16 sm_ip_bm_free_tail; u32 sm_ip_bm_xid_offset; u32 sm_ip_bitmap_offset; u32 sm_ip_bm_free_next_offset; u32 sm_version; u32 sm_struct_size; spaceman_datazone_info_phys_t sm_datazone; }; struct nx_reaper_phys_t { obj_phys_t nr_o; u64 nr_next_reap_id; u64 nr_completed_id; oid_t nr_head; oid_t nr_tail; u32 nr_flags; u32 nr_rlcount; u32 nr_type; u32 nr_size; oid_t nr_fs_oid; oid_t nr_oid; xid_t nr_xid; u32 nr_nrle_flags; u32 nr_state_buffer_size; u8 nr_state_buffer[nr_state_buffer_size]; }; struct nx_reap_list_entry_t { u32 nrle_next; u32 nrle_flags; u32 nrle_type; u32 nrle_size; oid_t nrle_fs_oid; oid_t nrle_oid; xid_t nrle_xid; }; struct nx_reap_list_phys_t { obj_phys_t nrl_o; oid_t nrl_next; u32 nrl_flags; u32 nrl_max; u32 nrl_count; u32 nrl_first; u32 nrl_last; u32 nrl_free; nx_reap_list_entry_t nrl_entries[nrl_count]; }; enum nx_reap_phase_t : u32 { APFS_REAP_PHASE_START = 0, APFS_REAP_PHASE_SNAPSHOTS = 1, APFS_REAP_PHASE_ACTIVE_FS = 2, APFS_REAP_PHASE_DESTROY_OMAP = 3, APFS_REAP_PHASE_DONE = 4, }; struct keybag_entry_t { uuid_t ke_uuid; u16 ke_tag; u16 ke_keylen; padding[4]; u8 ke_keydata[]; }; struct kb_locker_t { u16 kl_version; u16 kl_nkeys; u32 kl_nbytes; padding[8]; u8 kl_entries[]; }; struct mk_obj_t { u8 o_cksum[MAX_CKSUM_SIZE]; oid_t o_oid; xid_t o_xid; o_type_id_t o_type; o_type_id_t o_subtype; }; struct media_keybag_t { mk_obj_t mk_obj; kb_locker_t mk_locker; }; using crypto_flags_t = u32; using cp_key_class_t = u32; using cp_key_os_version_t = u32; using cp_key_revision_t = u16; struct wrapped_crypto_state_t { u16 major_version; u16 minor_version; crypto_flags_t cpflags; cp_key_class_t persistent_class; cp_key_os_version_t key_os_version; cp_key_revision_t key_revision; u16 key_len; u8 persistent_key[0]; }; struct wrapped_meta_crypto_state_t { u16 major_version; u16 minor_version; crypto_flags_t cpflags; cp_key_class_t persistent_class; cp_key_os_version_t key_os_version; cp_key_revision_t key_revision; u16 unused; }; struct j_crypto_key_t { // j_key_t hdr; }; struct j_crypto_val_t { u32 refcnt; wrapped_crypto_state_t state; }; #define APFS_MODIFIED_NAMELEN 32 struct apfs_modified_by_t { u8 id[APFS_MODIFIED_NAMELEN]; u64 timestamp; xid_t last_xid; }; bitfield apfs_fs_flags_t { APFS_FS_UNENCRYPTED : 1; APFS_FS_RESERVED_2 : 1; APFS_FS_RESERVED_4 : 1; APFS_FS_ONEKEY : 1; APFS_FS_SPILLEDOVER : 1; APFS_FS_RUN_SPILLOVER_CLEANER : 1; APFS_FS_ALWAYS_CHECK_EXTENTREF : 1; APFS_FS_RESERVED_80 : 1; APFS_FS_RESERVED_100 : 1; padding : 55; } [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 64)]]; bitfield apfs_incompatible_features_t { APFS_INCOMPAT_CASE_INSENSITIVE : 1; APFS_INCOMPAT_DATALESS_SNAPS : 1; APFS_INCOMPAT_ENC_ROLLED : 1; APFS_INCOMPAT_NORMALIZATION_INSENSITIVE : 1; APFS_INCOMPAT_INCOMPLETE_RESTORE : 1; APFS_INCOMPAT_SEALED_VOLUME : 1; APFS_INCOMPAT_RESERVED_40 : 1; padding : 57; } [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 64)]]; apfs_incompatible_features_t apfs_incompatible_features_null; #define APFS_MAGIC "BSPA" #define APFS_MAGIC_LE "APSB" #define APFS_MAX_HIST 8 #define APFS_VOLNAME_LEN 256 struct apfs_superblock_t { obj_phys_t apfs_o; type::Magic apfs_magic; u32 apfs_fs_index; u64 apfs_features; u64 apfs_readonly_compatible_features; apfs_incompatible_features_t apfs_incompatible_features; u64 apfs_unmount_time; u64 apfs_fs_reserve_block_count; u64 apfs_fs_quota_block_count; u64 apfs_fs_alloc_count; wrapped_meta_crypto_state_t apfs_meta_crypto; o_type_t apfs_root_tree_type; o_type_t apfs_extentref_tree_type; o_type_t apfs_snap_meta_tree_type; oid_t apfs_omap_oid; oid_t apfs_root_tree_oid; oid_t apfs_extentref_tree_oid; oid_t apfs_snap_meta_tree_oid; xid_t apfs_revert_to_xid; oid_t apfs_revert_to_sblock_oid; u64 apfs_next_obj_id; u64 apfs_num_files; u64 apfs_num_directories; u64 apfs_num_symlinks; u64 apfs_num_other_fsobjects; u64 apfs_num_snapshots; u64 apfs_total_blocks_alloced; u64 apfs_total_blocks_freed; uuid_t apfs_vol_uuid; u64 apfs_last_mod_time; apfs_fs_flags_t apfs_fs_flags; apfs_modified_by_t apfs_formatted_by; apfs_modified_by_t apfs_modified_by[APFS_MAX_HIST]; char apfs_volname[APFS_VOLNAME_LEN]; u32 apfs_next_doc_id; u16 apfs_role; u16 reserved; xid_t apfs_root_to_xid; oid_t apfs_er_state_oid; u64 apfs_cloneinfo_id_epoch; u64 apfs_cloneinfo_xid; oid_t apfs_snap_meta_ext_oid; uuid_t apfs_volume_group_id; oid_t apfs_integrity_meta_oid; oid_t apfs_fext_tree_oid; o_type_t apfs_fext_tree_type; u32 reserved_type; oid_t reserved_oid; }; #define OBJ_ID_MASK 0x0FFFFFFFFFFFFFFF #define OBJ_TYPE_MASK 0xF000000000000000 #define OBJ_TYPE_SHIFT 60 struct j_inode_key_t { // j_key_t hdr; }; bitfield j_inode_flags_t { INODE_IS_APFS_PRIVATE : 1; INODE_MAINTAIN_DIR_STATS : 1; INODE_DIR_STATS_ORIGIN : 1; INODE_PROT_CLASS_EXPLICIT : 1; INODE_WAS_CLONED : 1; INODE_FLAGS_UNUSED : 1; INODE_HAS_SECURITY_EA : 1; INODE_BEING_TRUNCATED : 1; INODE_HAS_FINDER_INFO : 1; INODE_IS_SPARSE : 1; INODE_WAS_EVER_CLONED : 1; INODE_ACTIVE_FILE_TRIMMED : 1; INODE_PINNED_TO_MAIN : 1; INODE_PINNED_TO_TIER2 : 1; INODE_HAS_RSRC_FORK : 1; INODE_NO_RSRC_FORK : 1; INODE_ALLOCATION_SPILLEDOVER : 1; INODE_FAST_PROMOTE : 1; INODE_HAS_UNCOMPRESSED_SIZE : 1; INODE_IS_PURGEABLE : 1; INODE_WANTS_TO_BE_PURGEABLE : 1; INODE_IS_SYNC_ROOT : 1; INODE_SNAPSHOT_COW_EXEMPTION : 1; padding : 41; } [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 64)]]; #define INODE_INHERITED_INTERNAL_FLAGS (INODE_MAINTAIN_DIR_STATS | INODE_SNAPSHOT_COW_EXEMPTION) #define INODE_CLONED_INTERNAL_FLAGS (INODE_HAS_RSRC_FORK | INODE_NO_RSRC_FORK | INODE_HAS_FINDER_INFO | INODE_SNAPSHOT_COW_EXEMPTION) #define OWNING_OBJ_ID_INVALID ~0ULL #define OWNING_OBJ_ID_UNKNOWN ~1ULL #define JOBJ_MAX_KEY_SIZE 832 #define JOBJ_MAX_VALUE_SIZE 3808 #define MIN_DOC_ID = 3; #define FEXT_CRYPTO_ID_IS_TWEAK 0x01 enum mode_t : u16 { MODE_S_IFMT = 0o170000, MODE_S_IFIFO = 0o010000, MODE_S_IFCHR = 0o020000, MODE_S_IFDIR = 0o040000, MODE_S_IFBLK = 0o060000, MODE_S_IFREG = 0o100000, MODE_S_IFLNK = 0o120000, MODE_S_IFSOCK = 0o140000, MODE_S_IFWHT = 0o160000, }; struct j_inode_val_t { u64 parent_id; u64 private_id; type::time64_t create_time; type::time64_t mod_time; type::time64_t change_time; type::time64_t access_time; j_inode_flags_t internal_flags; u32 nchildren; // or links cp_key_class_t default_protection_class; u32 write_generation_counter; u32 bsd_flags; u32 owner; u32 group; mode_t mode; u16 pad1; u64 uncompressed_size; u8 xfields[1]; }; #define J_DREC_LEN_MASK 0x000003FF #define J_DREC_HASH_MASK 0xFFFFFC00 #define J_DREC_HASH_SHIFT 10 struct j_drec_key_t { // j_key_t hdr; u16 name_len; char name[name_len]; }; bitfield j_drec_hashed_len_t { length : 10; hash : 22; } [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 32)]]; struct j_drec_hashed_key_t { // j_key_t hdr; j_drec_hashed_len_t name_len_and_hash; char name[name_len_and_hash.length]; }; enum dir_rec_flags_type_t : u8 { DT_UNKNOWN = 0, DT_FIFO = 1, DT_CHR = 2, DT_DIR = 4, DT_BLK = 6, DT_REG = 8, DT_LNK = 10, DT_SOCK = 12, DT_WHT = 14, }; bitfield dir_rec_flags_t { DREC_TYPE_MASK : 8; RESERVED_10 : 1; padding : 55; } [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 64)]]; struct j_drec_val_t { u64 file_id; u64 date_added; dir_rec_flags_t flags; u8 xfields[]; }; struct j_dir_stats_key_t { // j_key_t hdr; }; struct j_dir_stats_val_t { u64 num_children; u64 total_size; u64 chained_key; u64 gen_count; }; struct j_xattr_key_t { // j_key_t hdr; u16 name_len; char name[name_len]; }; bitfield j_xattr_flags_t { XATTR_DATA_STREAM : 1; XATTR_DATA_EMBEDDED : 1; XATTR_FILE_SYSTEM_OWNED : 1; XATTR_RESERVED_8 : 1; padding : 12; } [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 16)]]; #define XATTR_MAX_EMBEDDED_SIZE 3804 #define SYMLINK_EA_NAME "com.apple.fs.symlink" #define FIRMLINK_EA_NAME "com.apple.fs.firmlink" #define APFS_COW_EXEMPT_COUNT_NAME "com.apple.fs.cow-exempt-file-count" struct j_xattr_val_t { j_xattr_flags_t flags; u16 xdata_len; u8 xdata[xdata_len]; }; enum j_obj_kinds_t : u8 { APFS_KIND_ANY = 0, APFS_KIND_NEW = 1, APFS_KIND_UPDATE = 2, APFS_KIND_DEAD = 3, APFS_KIND_UPDATE_REFCNT = 4, APFS_KIND_INVALID = 255 }; bitfield j_inode_bsd_flags { APFS_UF_NODUMP : 1; APFS_UF_IMMUTABLE : 1; APFS_UF_APPEND : 1; APFS_UF_OPAQUE : 1; APFS_UF_NOUNLINK : 1; APFS_UF_COMPRESSED : 1; APFS_UF_TRACKED : 1; APFS_UF_DATAVAULT : 1; reserved : 7; APFS_UF_HIDDEN : 1; APFS_SF_ARCHIVED : 1; APFS_SF_IMMUTABLE : 1; APFS_SF_APPEND : 1; APFS_SF_RESTRICTED : 1; APFS_SF_NOUNLINK : 1; APFS_SF_SNAPSHOT : 1; APFS_SF_FIRMLINK : 1; padding : 6; APFS_SF_DATALESS : 1; } [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 64)]]; #define INVALID_INO_NUM 0 #define ROOT_DIR_PARENT 1 #define ROOT_DIR_INO_NUM 2 #define PRIV_DIR_INO_NUM 3 #define SNAP_DIR_INO_NUM 6 #define PURGEABLE_DIR_INO_NUM 7 #define MIN_USER_INO_NUM 16 #define UNIFIED_ID_SPACE_MARK 0x0800000000000000 struct j_phys_ext_key_t { // j_key_t hdr; }; struct j_phys_ext_val_t { u64 len_and_kind; u64 owning_obj_id; u32 refcnt; }; #define PEXT_LEN_MASK 0x0fffffffffffffffULL #define PEXT_KIND_MASK 0xf000000000000000ULL #define PEXT_KIND_SHIFT 60 struct j_file_extent_key_t { // j_key_t hdr; u64 logical_addr; }; struct j_file_extent_val_t { u64 len_and_flags; u64 phys_block_num; u64 crypto_id; }; #define J_FILE_EXTENT_LEN_MASK 0x00ffffffffffffffULL #define J_FILE_EXTENT_FLAG_MASK 0xff00000000000000ULL #define J_FILE_EXTENT_FLAG_SHIFT 56 struct j_dstream_id_key_t { // j_key_t hdr; }; struct j_dstream_id_val_t { u32 refcnt; }; struct j_dstream_t { u64 size; u64 alloced_size; u64 default_crypto_id; u64 total_bytes_written; u64 total_bytes_read; }; struct j_xattr_dstream_t { u64 xattr_obj_id; j_dstream_t dstream; }; struct xf_blob_t { u16 xf_num_exts; u16 xf_used_data; u8 xf_data[]; }; enum x_type_t : u8 { DREC_EXT_TYPE_SIBLING_ID = 1, INO_EXT_TYPE_SNAP_XID = 1, INO_EXT_TYPE_DELTRA_TREE_OID = 2, INO_EXT_TYPE_DOCUMENT_ID = 3, INO_EXT_TYPE_NAME = 4, INO_EXT_TYPE_PREV_FSIZE = 5, INO_EXT_TYPE_RESERVED_6 = 6, INO_EXT_TYPE_FINDER_INFO = 7, INO_EXT_TYPE_DSTREAM = 8, INO_EXT_TYPE_RESERVED_9 = 9, INO_EXT_TYPE_DIR_STATS_KEY = 10, INO_EXT_TYPE_FS_UUID = 11, INO_EXT_TYPE_RESERVED_12 = 12, INO_EXT_TYPE_SPARSE_BYTES = 13, INO_EXT_TYPE_RDEV = 14, INO_EXT_TYPE_PURGEABLE_FLAGS = 15, INO_EXT_TYPE_ORIG_SYNC_ROOT_ID = 16 }; bitfield x_flags_t { XF_DATA_DEPENDENT : 1; XF_DO_NOT_COPY : 1; XF_RESERVED_4 : 1; XF_CHILDREN_INHERIT : 1; XF_USER_FIELD : 1; XF_SYSTEM_FIELD : 1; XF_RESERVED_40 : 1; XF_RESERVED_80 : 1; } [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 8)]]; struct x_field_t { x_type_t x_type; x_flags_t x_flags; u16 x_size; }; struct j_sibling_key_t { // j_key_t hdr; u64 sibling_id; }; struct j_sibling_val_t { u64 parent_id; u16 name_len; char name[name_len]; }; struct j_sibling_map_key_t { // j_key_t hdr; }; struct j_sibling_map_val_t { u64 file_id; }; struct j_snap_metadata_key_t { // j_key_t hdr; }; bitfield snap_meta_flags_t { SNAP_META_PENDING_DATALESS : 1; SNAP_META_MERGE_IN_PROGRESS : 1; padding : 30; } [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 32)]]; struct j_snap_metadata_val_t { oid_t extentref_tree_oid; oid_t sblock_oid; type::time64_t create_time; type::time64_t change_time; u64 inum; o_type_t extentref_tree_type; snap_meta_flags_t flags; u16 name_len; char name[name_len]; }; struct j_snap_name_key_t { // j_key_t hdr; u16 name_len; char name[name_len]; }; struct j_snap_name_val_t { xid_t snap_xid; }; bitfield j_file_info_lba_t { lba : 56; type : 8; } [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 64)]]; #define J_FILE_INFO_LBA_MASK 0x00FFFFFFFFFFFFFF #define J_FILE_INFO_TYPE_MASK 0xFF00000000000000 #define J_FILE_INFO_TYPE_SHIFT 56 struct j_file_info_key_t { // j_key_t hdr; j_file_info_lba_t info_and_lba; }; struct j_file_data_hash_val_t { u16 hashed_len; u8 hash_size; u8 hash[hash_size]; }; struct j_file_info_val_t { j_file_data_hash_val_t dhash; }; enum j_obj_types_t : u8 { APFS_TYPE_ANY = 0, APFS_TYPE_SNAP_METADATA = 1, APFS_TYPE_EXTENT = 2, APFS_TYPE_INODE = 3, APFS_TYPE_XATTR = 4, APFS_TYPE_SIBLING_LINK = 5, APFS_TYPE_DSTREAM_ID = 6, APFS_TYPE_CRYPTO_STATE = 7, APFS_TYPE_FILE_EXTENT = 8, APFS_TYPE_DIR_REC = 9, APFS_TYPE_DIR_STATS = 10, APFS_TYPE_SNAP_NAME = 11, APFS_TYPE_SIBLING_MAP = 12, APFS_TYPE_FILE_INFO = 13, APFS_TYPE_INVALID = 15, }; bitfield j_key_t { unsigned obj_id : 60; j_obj_types_t obj_type : 4; } [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 64)]]; struct nloc_t { u16 off; u16 len; }; struct kvloc_t { nloc_t key_loc; nloc_t val_loc; }; struct kvoff_t { u16 key_off; u16 val_off; }; struct kvgen_t { u16 key_off = 0; u16 val_off = 0; if (btn_flags.BTNODE_FIXED_KV_SIZE) { kvoff_t range; key_off = range.key_off; val_off = range.val_off; } else { kvloc_t range; key_off = range.key_loc.off; val_off = range.val_loc.off; } match (subtype) { (o_type_id_t::OBJECT_TYPE_OMAP): { omap_key_t key @ key_area + key_off; if (btn_level > 0) { oid_t node_oid @ val_area - val_off; } else { omap_val_t val @ val_area - val_off; } } (o_type_id_t::OBJECT_TYPE_FSTREE | o_type_id_t::OBJECT_TYPE_BLOCKREFTREE | o_type_id_t::OBJECT_TYPE_SNAPMETATREE): { j_key_t key @ key_area + key_off; match (key.obj_type) { (j_obj_types_t::APFS_TYPE_SNAP_METADATA): { j_snap_metadata_key_t snap_meta_key @ key_area + key_off + sizeof (key); if (btn_level > 0) { oid_t node_oid @ val_area - val_off; } else { j_snap_metadata_val_t snap_meta_val @ val_area - val_off; } } (j_obj_types_t::APFS_TYPE_EXTENT): { j_phys_ext_key_t phys_ext_key @ key_area + key_off + sizeof (key); if (btn_level > 0) { oid_t node_oid @ val_area - val_off; } else { j_phys_ext_val_t phys_ext_val @ val_area - val_off; } } (j_obj_types_t::APFS_TYPE_INODE): { j_inode_key_t inode_key @ key_area + key_off + sizeof (key); if (btn_level > 0) { oid_t node_oid @ val_area - val_off; } else { j_inode_val_t inode_val @ val_area - val_off; } } (j_obj_types_t::APFS_TYPE_XATTR): { j_xattr_key_t xattr_key @ key_area + key_off + sizeof (key); if (btn_level > 0) { oid_t node_oid @ val_area - val_off; } else { j_xattr_val_t xattr_val @ val_area - val_off; } } (j_obj_types_t::APFS_TYPE_SIBLING_LINK): { j_sibling_key_t sibling_link_key @ key_area + key_off + sizeof (key); if (btn_level > 0) { oid_t node_oid @ val_area - val_off; } else { j_sibling_val_t sibling_link_val @ val_area - val_off; } } (j_obj_types_t::APFS_TYPE_DSTREAM_ID): { j_dstream_id_key_t dstream_id_key @ key_area + key_off + sizeof (key); if (btn_level > 0) { oid_t node_oid @ val_area - val_off; } else { j_dstream_id_val_t dstream_id_val @ val_area - val_off; } } (j_obj_types_t::APFS_TYPE_CRYPTO_STATE): { j_crypto_key_t crypto_key @ key_area + key_off + sizeof (key); if (btn_level > 0) { oid_t node_oid @ val_area - val_off; } else { j_crypto_val_t crypto_val @ val_area - val_off; } } (j_obj_types_t::APFS_TYPE_FILE_EXTENT): { j_file_extent_key_t file_ext_key @ key_area + key_off + sizeof (key); if (btn_level > 0) { oid_t node_oid @ val_area - val_off; } else { j_file_extent_val_t file_ext_val @ val_area - val_off; } } (j_obj_types_t::APFS_TYPE_DIR_REC): { if (vol_incomp.APFS_INCOMPAT_CASE_INSENSITIVE || vol_incomp.APFS_INCOMPAT_NORMALIZATION_INSENSITIVE) { j_drec_hashed_key_t drec_key @ key_area + key_off + sizeof (key); } else { j_drec_key_t drec_key @ key_area + key_off + sizeof (key); } if (btn_level > 0) { oid_t node_oid @ val_area - val_off; } else { j_drec_val_t drec_val @ val_area - val_off; } } (j_obj_types_t::APFS_TYPE_DIR_STATS): { j_dir_stats_key_t dir_stats_key @ key_area + key_off + sizeof (key); if (btn_level > 0) { oid_t node_oid @ val_area - val_off; } else { j_dir_stats_val_t dir_stats_val @ val_area - val_off; } } (j_obj_types_t::APFS_TYPE_SNAP_NAME): { j_snap_name_key_t snap_name_key @ key_area + key_off + sizeof (key); if (btn_level > 0) { oid_t node_oid @ val_area - val_off; } else { j_snap_name_val_t snap_name_val @ val_area - val_off; } } (j_obj_types_t::APFS_TYPE_SIBLING_MAP): { j_sibling_key_t sibling_key @ key_area + key_off + sizeof (key); if (btn_level > 0) { oid_t node_oid @ val_area - val_off; } else { j_sibling_val_t sibling_val @ val_area - val_off; } } (j_obj_types_t::APFS_TYPE_FILE_INFO): { j_file_info_key_t file_info_key @ key_area + key_off + sizeof (key); if (btn_level > 0) { oid_t node_oid @ val_area - val_off; } else { j_file_info_val_t file_info_val @ val_area - val_off; } } } } } }; bitfield btn_flags_t { BTNODE_ROOT : 1; BTNODE_LEAF : 1; BTNODE_FIXED_KV_SIZE : 1; BTNODE_HASHED : 1; BTNODE_NOHEADER : 1; padding : 10; BTNODE_CHECK_KOFF_INVAL : 1; } [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 16)]]; bitfield bt_flags_t { BTREE_UINT64_KEYS : 1; BTREE_SEQUENTIAL_INSERT : 1; BTREE_ALLOW_GHOSTS : 1; BTREE_EPHEMERAL : 1; BTREE_PHYSICAL : 1; BTREE_NONPERSISTENT : 1; BTREE_KV_NONALIGNED : 1; BTREE_HASHED : 1; BTREE_NOHEADER : 1; padding : 23; } [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 32)]]; struct btree_info_fixed_t { bt_flags_t bt_flags; u32 bt_node_size; u32 bt_key_size; u32 bt_val_size; }; struct btree_info_t { btree_info_fixed_t bt_fixed; u32 bt_longest_key; u32 bt_longest_val; u64 bt_key_count; u64 bt_node_count; }; struct btree_node_phys_t { u32 node_start = $; obj_phys_t btn_o; btn_flags_t btn_flags; u16 btn_level; u32 btn_nkeys; nloc_t btn_table_space; nloc_t btn_free_space; nloc_t btn_key_free_list; nloc_t btn_val_free_list; if (btn_table_space.off > $) { padding[btn_table_space.off - $]; } u32 btn_toc_start = $; padding[btn_table_space.len]; u32 key_area = $; u32 val_area = node_start + block_size; if (btn_flags.BTNODE_ROOT) { val_area -= sizeof (btree_info_t); } u8 btn_key_area; kvgen_t btn_toc[btn_nkeys] @ btn_toc_start; padding[btn_free_space.off]; u8 btn_free_area_start; std::mem::AlignTo; if (btn_flags.BTNODE_ROOT) { btree_info_t info @ node_start + block_size - sizeof (btree_info_t); } }; #define BTREE_NODE_HASH_SIZE_MAX 64 struct btn_index_node_val_t { oid_t binv_child_oid; u8 binv_child_hash[BTREE_NODE_HASH_SIZE_MAX]; }; // ================== HELPERS ================== fn fletcher64(u64 offset, u64 count, u64 init) { u64 sum1 = init & 0xFFFFFFFF; u64 sum2 = init >> 32; for (u64 key = 0, key < count, key += 1) { u32 data @ offset + key * sizeof (u32); sum1 += data; sum2 += sum1; } sum1 %= 0xFFFFFFFF; sum2 %= 0xFFFFFFFF; return (sum2 << 32) | sum1; }; fn block_verify(u64 offset, u64 size) { u64 chk @ offset; if (chk == 0) return false; if (chk == 0xFFFFFFFFFFFFFFFF) return false; u64 cks = fletcher64(offset + MAX_CKSUM_SIZE, size / sizeof (u32) - 2, 0); cks = fletcher64(offset, MAX_CKSUM_SIZE / sizeof (u32), cks); std::assert(cks == 0, std::format("block verification failed, offset: 0x{:X}, size: 0x{:X}", offset, size)); }; fn object_get_latest(u64 offset, u64 count, u64 size, o_type_id_t type, u64 max = 0) { if (max == 0) { max = count; } u64 max_xid = 0; u64 result = 0; for (u64 address = offset, address <= (offset + count), address += 1) { if ((address - offset) >= max) { return result; } obj_phys_t object @ address * size; if (object.o_type.t_type != type) { continue; } if (max_xid < object.o_xid) { max_xid = object.o_xid; result = address * size; } } return result; }; fn checkpoint_map_lookup(checkpoint_map_phys_t checkpoint_map, oid_t oid, o_type_id_t type) { for (u64 iter = 0, iter < checkpoint_map.cpm_count, iter += 1) { if (checkpoint_map.cpm_map[iter].cpm_oid == oid && checkpoint_map.cpm_map[iter].cpm_type.t_type == type) { return checkpoint_map.cpm_map[iter]; } } checkpoint_mapping_t empty; return empty; }; fn keybag_is_encrypted(media_keybag_t keybag) { bool is_decrypted = keybag.mk_obj.o_type == o_type_id_t::OBJECT_TYPE_CONTAINER_KEYBAG && keybag.mk_obj.o_subtype == o_type_id_t::OBJECT_TYPE_CONTAINER_KEYBAG_2; std::assert(is_decrypted, "encrypted keybags are not supported"); }; fn omap_node_lookup(paddr_t off, u64 block_size, oid_t oid, xid_t xid) { while (true) { btree_node_phys_t node @ off; if (node.btn_nkeys <= 0) { return 0; } s64 beg = 0; s64 end = node.btn_nkeys - 1; s64 mid = 0; s64 idx = 0; while (beg <= end) { mid = beg + (end - beg) / 2; omap_key_t key = node.btn_toc[mid].key; if (oid > key.ok_oid) { beg += 1; } else if (oid < key.ok_oid) { end -= 1; } else if (xid > key.ok_xid) { beg += 1; idx = mid; } else if (xid < key.ok_xid) { end -= 1; idx = mid; } else { idx = mid; break; } } if (node.btn_level > 0) { off = node.btn_toc[idx].node_oid * block_size; continue; } omap_key_t key = node.btn_toc[idx].key; if (key.ok_oid == oid) { return node.btn_toc[idx].val.ov_paddr; } else { return 0; } } }; fn fstree_inode_lookup( paddr_t root_off, paddr_t omap_off, u64 block_size, u64 ino, apfs_incompatible_features_t incomp, xid_t xid ) { u64 inode_key = (j_obj_types_t::APFS_TYPE_INODE << OBJ_TYPE_SHIFT) | (ino & OBJ_ID_MASK); u64 node_off = root_off; while (true) { btree_node_phys_t node @ node_off; s64 beg = 0; s64 end = node.btn_nkeys - 1; s64 mid = 0; s64 current = 0; while (beg <= end) { mid = beg + (end - beg) / 2; u64 entry_key @ addressof (node.btn_toc[mid].key); if ((entry_key & OBJ_ID_MASK) > (inode_key & OBJ_ID_MASK)) { end -= 1; } else if ((entry_key & OBJ_ID_MASK) < (inode_key & OBJ_ID_MASK)) { beg += 1; } else if (((entry_key & OBJ_TYPE_MASK) >> OBJ_TYPE_SHIFT) > ((inode_key & OBJ_TYPE_MASK) >> OBJ_TYPE_SHIFT)) { end -= 1; current = mid; } else if (((entry_key & OBJ_TYPE_MASK) >> OBJ_TYPE_SHIFT) < ((inode_key & OBJ_TYPE_MASK) >> OBJ_TYPE_SHIFT)) { beg += 1; current = mid; } else { current = mid; break; } } if (node.btn_level > 0) { node_off = omap_node_lookup( omap_off, block_size, node.btn_toc[current].node_oid, xid) * superblock.nx_block_size; continue; } u64 entry_key @ addressof (node.btn_toc[current].key); if (entry_key != inode_key) { return 0; } return addressof (node.btn_toc[current].inode_val); } }; // ================== PARSE ================== nx_superblock_t initial_superblock @ 0x00; nx_superblock_t superblock @ object_get_latest( initial_superblock.nx_xp_desc_base, initial_superblock.nx_xp_desc_blocks, initial_superblock.nx_block_size, o_type_id_t::OBJECT_TYPE_NX_SUPERBLOCK); checkpoint_map_phys_t checkpoint_map @ (superblock.nx_xp_desc_base + superblock.nx_xp_desc_index) * superblock.nx_block_size; omap_phys_t object_map @ superblock.nx_omap_oid * superblock.nx_block_size; btree_node_phys_t object_map_tree @ object_map.om_tree_oid * superblock.nx_block_size; apfs_superblock_t volume @ omap_node_lookup( addressof (object_map_tree), superblock.nx_block_size, superblock.nx_fs_oid[0], superblock.nx_o.o_xid) * superblock.nx_block_size; omap_phys_t volume_object_map @ volume.apfs_omap_oid * superblock.nx_block_size; btree_node_phys_t volume_object_map_tree @ volume_object_map.om_tree_oid * superblock.nx_block_size; btree_node_phys_t volume_root_tree @ omap_node_lookup( addressof (volume_object_map_tree), superblock.nx_block_size, volume.apfs_root_tree_oid, volume.apfs_o.o_xid) * superblock.nx_block_size; j_inode_val_t volume_root_folder @ fstree_inode_lookup( addressof (volume_root_tree), addressof (volume_object_map_tree), superblock.nx_block_size, ROOT_DIR_INO_NUM, volume.apfs_incompatible_features, volume.apfs_o.o_xid ); btree_node_phys_t volume_extentref_tree @ volume.apfs_extentref_tree_oid * superblock.nx_block_size; btree_node_phys_t volume_snapshot_tree @ volume.apfs_snap_meta_tree_oid * superblock.nx_block_size; apfs_superblock_t snapshot_volume @ volume_snapshot_tree.btn_toc[0].snap_meta_val.sblock_oid * superblock.nx_block_size; btree_node_phys_t snapshot_extentref_tree @ volume_snapshot_tree.btn_toc[0].snap_meta_val.extentref_tree_oid * superblock.nx_block_size; checkpoint_mapping_t spaceman_cp = checkpoint_map_lookup(checkpoint_map, superblock.nx_spaceman_oid, o_type_id_t::OBJECT_TYPE_SPACEMAN); spaceman_phys_t spaceman @ spaceman_cp.cpm_paddr * superblock.nx_block_size; checkpoint_mapping_t spaceman_freequeue_manager_cp = checkpoint_map_lookup( checkpoint_map, spaceman.sm_fq[sfq_t::SFQ_IP].sfq_tree_oid, o_type_id_t::OBJECT_TYPE_BTREE); btree_node_phys_t spaceman_freequeue_manager @ spaceman_freequeue_manager_cp.cpm_paddr * superblock.nx_block_size; checkpoint_mapping_t spaceman_freequeue_volume_cp = checkpoint_map_lookup( checkpoint_map, spaceman.sm_fq[sfq_t::SFQ_MAIN].sfq_tree_oid, o_type_id_t::OBJECT_TYPE_BTREE); btree_node_phys_t spaceman_freequeue_volume @ spaceman_freequeue_volume_cp.cpm_paddr * superblock.nx_block_size; paddr_t spaceman_main_cibs_addr @ addressof (spaceman) + spaceman.sm_dev[smdev_t::SD_MAIN].sm_addr_offset; chunk_info_block_t spaceman_main_cibs[spaceman.sm_dev[smdev_t::SD_MAIN].sm_cib_count] @ spaceman_main_cibs_addr * superblock.nx_block_size; paddr_t spaceman_tier2_cibs_addr @ addressof (spaceman) + spaceman.sm_dev[smdev_t::SD_TIER2].sm_addr_offset; chunk_info_block_t spaceman_tier2_cibs[spaceman.sm_dev[smdev_t::SD_TIER2].sm_cib_count] @ spaceman_tier2_cibs_addr * superblock.nx_block_size; checkpoint_mapping_t reaper_cp = checkpoint_map_lookup(checkpoint_map, superblock.nx_reaper_oid, o_type_id_t::OBJECT_TYPE_NX_REAPER); nx_reaper_phys_t reaper @ reaper_cp.cpm_paddr * superblock.nx_block_size; media_keybag_t container_keybag @ superblock.nx_keylocker.pr_start_paddr * superblock.nx_block_size; // ================== VERIFY ================== block_verify(addressof (initial_superblock), initial_superblock.nx_block_size); block_verify(addressof (superblock), superblock.nx_block_size); block_verify(addressof (object_map), superblock.nx_block_size); block_verify(addressof (object_map_tree), superblock.nx_block_size); block_verify(addressof (volume), superblock.nx_block_size); block_verify(addressof (volume_object_map), superblock.nx_block_size); block_verify(addressof (volume_root_tree), superblock.nx_block_size); block_verify(addressof (spaceman), superblock.nx_block_size); block_verify(addressof (reaper), superblock.nx_block_size); block_verify(addressof (spaceman_freequeue_manager), superblock.nx_block_size); block_verify(addressof (spaceman_freequeue_volume), superblock.nx_block_size); block_verify(addressof (container_keybag), superblock.nx_block_size); keybag_is_encrypted(container_keybag);