I want to mount an APFS snapshot using code running on macOS, using the equivalent of this tool invocation:
mount_apfs -o ro,nobrowse -s snapshot_name snapshot_volume destpath
As there's no mount_apfs()
function, I'm stuck with mount()
. I also cannot use fs_snapshot_mount()
because that requires root permissions, and my app doesn't have those.
man 2 mount
states:
int mount(const char *type, const char *dir, int flags, void *data);
Data is a pointer to a structure that contains the type specific
arguments to mount. The format for these argument structures is
described in the manual page for each filesystem.
The problem is that I am unable to find a definition for this data
structure for APFS. E.g., where's that "manual page"?
In short, how do I pass the root path of the volume and its snapshot name to the mount()
function?
Regarding the current two answers, here's an example of how I'd find the snapshot name and the device name:
% diskutil apfs listSnapshots /System/Volumes/Data
Snapshot for disk3s5 (1 found)
|
+-- A391E61C-0B69-4A45-8543-C455EAF7F788
Name: com.apple.TimeMachine.2025-06-22-165948.local
XID: 37638462
Purgeable: Yes
NOTE: This snapshot limits the minimum size of APFS Container disk3
% mount | grep "/System/Volumes/Data"
/dev/disk3s5 on /System/Volumes/Data (apfs, local, journaled, nobrowse, protect, root data)
map auto_home on /System/Volumes/Data/home (autofs, automounted, nobrowse)
In the above, com.apple.TimeMachine.2025-06-22-165948.local
is the snapshot name and /dev/disk3s5
the device path.
The structure can be found at: mount_args.h from palera1n/jbinit jailbreak repository
struct apfs_mount_args {
#ifndef KERNEL
char* fspec; /* path to device to mount from */
#endif
uint64_t apfs_flags; /* The standard mount flags, OR'd with apfs-specific flags (APFS_FLAGS_* above) */
uint32_t mount_mode; /* APFS_MOUNT_* */
uint32_t pad1; /* padding */
uint32_t unk_flags; /* yet another type some sort of flags (bitfield), possibly volume role related */
union {
char snapshot[256]; /* snapshot name */
struct {
char tier1_dev[128]; /* Tier 1 device (Fusion mount) */
char tier2_dev[128]; /* Tier 2 device (Fusion mount) */
};
};
void* im4p_ptr;
uint32_t im4p_size;
uint32_t pad2; /* padding */
void* im4m_ptr;
uint32_t im4m_size;
uint32_t pad3; /* padding */
uint32_t cryptex_type; /* APFS_CRYPTEX_TYPE_* */
int32_t auth_mode; /* APFS_AUTH_ENV_* */
uid_t uid;
gid_t gid;
}__attribute__((packed, aligned(4)));
With that the call to mount looks like this:
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("call with snapshot name and mount-point\n");
return 0;
}
char* snapshotName = argv[1];
char* mountPath = argv[2];
struct apfs_mount_args mnt_args = {};
mnt_args.apfs_flags = (MNT_RDONLY | MNT_DONTBROWSE);
mnt_args.mount_mode = APFS_MOUNT_SNAPSHOT;
mnt_args.unk_flags = 0x0100003F; // no idea what that is
mnt_args.uid = getuid();
mnt_args.gid = getgid();
strcpy(mnt_args.snapshot, snapshotName);
int result = mount("apfs", mountPath, MNT_RDONLY | MNT_DONTBROWSE, &mnt_args);
if (result == 0)
printf("success!\n");
else
printf("error: %i - %s\n",errno,strerror(errno));
return 1;
}
The snapshot name is part of the structure, there're only 256 bytes reserved for it.
The unk_flags
are unknown flags and i don't know why these specific flags are required, but that's what's being passed when you use the arguments that you provided in your question.
What surprises me a bit is the lack of source-volume. In theory that's the first member of the struct apfs_mount_args
but in practice it's a null-pointer.