Skip to content
macOSDeep Dive Published Updated 7 min readViews unavailable

FSKit on macOS: Build a User-Space File-System Extension

Trace FSKit resources through a macOS file-system extension and volume, with current unary limits, lifecycle, entitlements, and cache-safety checks.

FSKit lets a developer add a mountable file-system implementation to macOS as a user-space app extension. The framework connects that module to facilities such as Disk Arbitration, NetFS, and mount(8). This is a different contract from a File Provider extension, which presents synchronized remote documents through the system’s cloud-file model, and from DriverKit, which implements supported device drivers. FSKit is for a module that interprets a resource and exposes a file hierarchy.

The architectural limit matters before any code is written: Apple’s current FSKit overview says the framework supports FSUnaryFileSystem only. That design accepts one resource and presents one volume. The documentation describes a more general FSFileSystem flow for multiple resources and volumes, but marks it unsupported in the current FSKit version. A design that depends on aggregating multiple devices or exporting several volumes from one container should not assume that the broader class is available merely because it appears in the API reference.

Model the supported unary path

In the unary flow, the extension supplies a file-system object that can recognize a resource, load it, and produce one FSVolume. A useful mental model is:

mount request or system probe
        -> probeResource(resource)
        -> loadResource(resource)
        -> FSVolume activation and mount
        -> item lookups, enumeration, and requested I/O
        -> synchronize, unmount, deactivate, unload

These stages have different responsibilities. Probing should decide whether the module recognizes a resource and whether it can proceed; it should not format media or perform irreversible writes. Loading establishes the resource-to-volume relationship. The volume then implements the operations it actually supports, reports its capabilities honestly, and tears down state when FSKit asks it to unload.

Apple’s extension pattern uses an app extension conforming to UnaryFileSystemExtension, backed by a subclass of FSUnaryFileSystem that conforms to FSUnaryFileSystemOperations. The app’s extension metadata declares the module’s identity and file-system type. Apple’s passthrough sample uses the com.apple.developer.fskit.fsmodule entitlement and an FSShortName value in extension metadata; that short name is what the sample passes to mount -t. Treat the sample as a development reference, not as a promise that every module has the same signing, distribution, authorization, or mount privileges. Confirm the entitlement and deployment requirements for the SDK and distribution channel you intend to ship.

For a disposable sample test, follow Apple’s passthrough flow: enable the sample extension under System Settings > General > Login Items & Extensions, create a dedicated empty mount point, and use the exact sample file-system short name. Keep the source and target paths inside a test directory. Do not test an unfamiliar module against a production volume or a path containing unique user data.

Preserve the resource’s access boundary

FSKit resources can represent different kinds of input, including a block-device resource or a URL-based resource. Apple’s passthrough example accepts an FSPathURLResource; when its URL is security-scoped, the extension must begin access using the security-scoped URL and retain that access while it uses the resource. The example stops access during unload. A plain path string extracted from such a URL is not a substitute for the security scope.

Keep the original resource object and URL associated with the loaded volume. On load, reject an unexpected resource type and return a defined error rather than guessing how to interpret it. If access cannot be started, fail the load cleanly. Once access starts successfully, pair it with exactly one stop: if any later setup step or volume construction fails, stop access before returning the load error, because FSKit may not call unload for a resource that never loaded. For a successful load, retain access while the volume uses the resource and stop it during unload; release descriptors and caches, and clear the stored resource only after the volume no longer needs it. Track whether the load transferred ownership of the scope to the volume so failure cleanup and unload cannot both stop the same start. This is both a correctness condition and a resource-lifetime rule: leaking security-scoped access can consume system resources, while ending it too early can make later reads fail.

Implement a volume with explicit contracts

An FSVolume subclass represents the hierarchy visible to file-system clients. Apple’s current handler reference requires a volume to implement the handler interfaces for required behavior, including activation, mount and unmount, item operations, synchronization, and volume statistics. Activation occurs before mounting; a successful activation returns the root FSItem, which FSKit caches as the starting point for lookups during that volume’s lifetime. A volume can adopt additional handler protocols for features such as open/close, read/write, and extended attributes, but should only advertise capabilities whose behavior it can actually maintain.

The June 2026 FSKit update directs implementers to migrate from the older FSVolume.Operations family to FSVolume.Handler and related handler protocols. The newer result types can return attributes and free-space details, and selected handlers receive an FSContext with user and group identifiers. Apple’s current reference lists FSVolume.Handler as introduced in macOS 27.0, not as beta. Check the headers and availability annotations in the exact Xcode SDK and deployment target you build with; do not infer compatibility with earlier macOS releases from the existence of a web page. Avoid mixing an older sample’s operation-protocol implementation with current handler requirements without an explicit migration plan.

Capabilities and synchronization are not decorative metadata. If a backing store is read-only, report that and reject mutating operations consistently. If it does not support hard links, journaling, or another capability, do not advertise it. Define how synchronize maps to the backing store’s durability contract; returning success before required writes are durable can mislead applications that requested synchronization. If the data lives on a remote service, specify how network loss, retry, and partial writes affect the visible volume state.

Make cache coherency a correctness decision

Kernel data caching can reduce repeated I/O, but only if the module and its backing store agree about when cached data remains valid. FSKit provides FSVolume.DataCacheHandler for a module that needs to negotiate cache modes and coherency with the kernel. The module receives the requested cache mode and returns the coherency it can support. For a downgrade to a less permissive coherency type, the handler chooses an action such as pushing dirty pages, invalidating cached data, or doing both, and handles dirty data by flushing or purging it before the downgrade is committed. A more permissive upgrade does not require a flush or purge. If a file system does not implement this handler, the kernel may still cache data without the module controlling those decisions.

Start with conservative caching unless the consistency model is understood and tested. A passthrough implementation must account for changes made through the original path, not only writes made through the mounted view. A remote implementation must account for server-side updates, reconnects, and invalidation. Test concurrent access through the backing path and mounted volume; a fast stale read is still a bug.

Verify failures as well as the happy path

Use Apple’s passthrough sample as a bounded way to learn extension discovery and mounting, then test your own implementation with disposable resources. Record the macOS build, Xcode and SDK version, extension bundle identifier, entitlement state, resource type, mount options, and exact command. Exercise at least these cases:

  • A recognized resource, an unrecognized resource, and a malformed or truncated resource.
  • A revoked or unavailable security-scoped URL, plus an unload after a successful load.
  • A read-only resource and a write request that must be rejected.
  • Repeated lookup and enumeration, short reads, end-of-file, and an injected backing-store error.
  • Unmount while clients still hold items, normal synchronization, and a failed synchronization.
  • Backing data changed outside the mounted view, to validate the chosen cache policy.

The test should prove more than “mount returned success.” Confirm that the reported filesystem identity and capabilities are correct, that data remains consistent under the supported access pattern, that unmount releases access, and that failure leaves the resource in a recoverable state. Do not use format or repair operations on valuable media until the exact target, authorization model, and recovery path have been independently reviewed.

FSKit is a focused extension point, not a shortcut around filesystem engineering. Its current unary limit, security-scoped resource lifetime, volume contract, evolving handler APIs, and cache-coherency model define the real implementation boundary. A small, read-only prototype that rejects unsupported resources is a safer first milestone than a broad writable driver whose advertised semantics have not been verified.

Related:

Sources:

Comments