A small C library (libtether) plus two CLIs — attach-dmg and detach-dmg —
that mount and unmount disk images the same way hdiutil does, by calling the
same underlying macOS APIs.
Named for what it does: it tethers a disk image to the running system. Attach mounts the image as a
/dev/diskNdevice; detach unmounts and releases it. That is the whole library — a tether you fasten and cast off, with no image creation or format manipulation.macOS only. This depends on
DiskArbitration.framework,CoreFoundation,IOKit, and the privateDiskImages.framework. The CMake build hard-fails on non-Apple platforms.
Attaching a .dmg is not a DiskArbitration operation. DiskArbitration only
mounts/unmounts/ejects devices that already exist. Creating the /dev/diskN
backing device from an image file is done by the private DiskImages.framework.
So:
| Operation | Framework | Key call |
|---|---|---|
| attach (create device + mount) | DiskImages.framework (private) |
DIHLDiskImageAttach |
| detach (unmount + tear down) | DiskArbitration.framework (public) |
DADiskUnmount (whole) + DADiskEject |
This split is exactly how hdiutil itself is built (its detach path is literally
named unmount_and_eject).
The signature was recovered from three independent sources that all agree:
-
The
hdiutilbinary (Ghidra) — call site decompiles toDIHLDiskImageAttach(optionsDict, progressCb, 0, &outDict)returningint(0 = ok). -
An in-the-wild caller —
_dihlDiskImageAttach((CFDictionaryRef)dict, nil, nil, (CFDictionaryRef*)&results)(callback + context may benil; out-param is aCFDictionaryRef). -
Apple's open-source Darwin
libsecurity_filevault(FVDIHLInterface.h) — the authoritative typedefs:typedef OSStatus _dihlDiskImageAttachProc(CFDictionaryRef inOptions, void *inStatusProc, void *inContext, CFDictionaryRef *outResults); typedef int _dihlDIInitializeProc(); typedef void _dihlDIDeinitializeProc();
Note
inStatusProcis plainvoid *in Apple's header,DIInitializereturnsint, andDIDeinitializereturnsvoid.
We declare these ourselves in include/diskimages_private.h
and resolve them at runtime via CFBundle (see below).
| Key | Type | Notes |
|---|---|---|
main-url |
CFURLRef |
the image to attach (required) |
shadow-url |
CFURLRef |
optional shadow file (read-write overlay) |
read-only |
CFBoolean |
force read-only |
agent |
CFString |
set to framework (the value hdiutil uses on this path) |
verbose / quiet |
CFBoolean |
diagnostics level |
skip-verify, skip-verify-locked, skip-verify-remote |
CFBoolean |
skip checksum verification |
A CFDictionaryRef whose system-entities key is a CFArray of dictionaries,
each describing one attached node:
| Entity key | Example |
|---|---|
dev-entry |
/dev/disk4s1 |
mount-point |
/Volumes/Foo |
content-hint |
Apple_HFS |
On current macOS, private frameworks live in the dyld shared cache with no
on-disk .tbd stub, so link-time -framework DiskImages typically fails. We
therefore resolve the needed symbols (DIInitialize, DIDeinitialize,
DIHLDiskImageAttach) at runtime — using CFBundle, exactly as Apple's own
libsecurity_filevault (FVDIHLInterface.cpp) does:
CFURLRef url = CFURLCreateWithFileSystemPath(NULL,
CFSTR("/System/Library/PrivateFrameworks/DiskImages.framework"),
kCFURLPOSIXPathStyle, false);
CFBundleRef bundle = CFBundleCreate(NULL, url);
fp = (_dihlDiskImageAttachProc *)
CFBundleGetFunctionPointerForName(bundle, CFSTR("DIHLDiskImageAttach"));We also try CFBundleGetBundleWithIdentifier(CFSTR("com.apple.DiskImagesFramework"))
first (that bundle identifier is present in hdiutil's strings). This needs no
private headers or SDK stubs. (hdiutil itself hard-links DiskImages — which is
why its import table lists the _DIHL* symbols directly — but we can't hard-link
without a stub.)
Because attach-dmg only hands main-url to DIHLDiskImageAttach, it attaches
any format DiskImages recognizes, not just .dmg: UDIF (.dmg),
sparse images (.sparseimage), sparse bundles (.sparsebundle, a directory —
handled), ISO/CD masters (.iso, .cdr), NDIF, and encrypted images. The
framework sniffs the format itself.
cd native
cmake -S . -B build
cmake --build buildBy default this builds only the library, build/libtether.a. The attach-dmg and
detach-dmg CLIs are optional — enable them with -DBUILD_TOOLS=ON:
cmake -S . -B build -DBUILD_TOOLS=ON
cmake --build buildwhich additionally produces build/attach-dmg and build/detach-dmg.
# Attach (mount). Prints one line per attached entity: dev-entry, hint, mount-point.
./attach-dmg /path/to/Image.dmg
./attach-dmg -readwrite /path/to/Image.sparseimage
./attach-dmg -noverify -quiet /path/to/Image.dmg
# Detach (unmount + eject the whole disk). Accepts a BSD name, /dev path, or mount point.
./detach-dmg disk4
./detach-dmg /dev/disk4s1
./detach-dmg /Volumes/Foo
./detach-dmg -force -timeout 60 disk4native/
├── CMakeLists.txt # project, options, add_subdirectory(src/tools)
├── include/
│ ├── libtether.h # public library API
│ └── diskimages_private.h # reverse-engineered private DiskImages decls
├── src/
│ ├── CMakeLists.txt # builds libtether (always)
│ ├── teth_attach.c # attach via DIHLDiskImageAttach
│ ├── teth_detach.c # detach via DiskArbitration unmount+eject
│ └── diskimages_private.c # CFBundle loader for DiskImages.framework
└── tools/
├── CMakeLists.txt # builds attach-dmg/detach-dmg (BUILD_TOOLS=ON)
├── attach_dmg.c # attach-dmg CLI
└── detach_dmg.c # detach-dmg CLI
- Some images may require entitlements or admin rights to attach; run with appropriate privileges if attach returns a permission error.
- The status callback (
inStatusProc) is plainvoid *in Apple's header and we always passNULL, which all observed callers (filevault) do.hdiutilpasses a real callback; if a future OS requires a non-NULL one, its prototype would need to be recovered. - Not yet compiled on macOS in this environment (developed/verified for API correctness on Linux, which has no macOS SDK). Build on macOS to validate.