📖 Also available in Simplified Chinese.
A macOS app + FSKit file-system extension that reads and writes NTFS volumes using the ntfs-3g engine. No kernel extension, no macFUSE.
The ntfs3g app-extension reuses the portable libntfs-3g core (all the NTFS logic — MFT,
runlists, compression, security descriptors, $LogFile) behind a thin FSKit FSVolume mapping
layer, the same way ntfs-3g's own src/ntfs-3g.c maps FUSE onto libntfs.
The extension lets macOS mount supported NTFS drives under /Volumes without a background app.
The app lists drives and disk images, shows their actual mount and read-only status, and helps
diagnose extension setup. Mounting controls depend on the macOS version; see below.
xntfs.app (SwiftUI, App Sandbox — control panel; not a background agent)
├─ AppModel ── DiskArbitrationMonitor detect/list NTFS drives (detection only)
│ └─ MountService manual mount/unmount (DiskArbitration), image attach
└─ Contents/Extensions/ntfs3g.appex FSKit module — the actual NTFS driver (+ auto-mount)
ntfs3g.swift @main UnaryFileSystemExtension
ntfs3gFileSystem.swift probe / load / unload / fsck
ntfs3gVolume.swift every FSVolume operation → nfsk_* bridge
bridge/ntfs_fskit.c libntfs logic (mount, getattr, readdir, read, write, create…)
bridge/ntfs_device_fskit.m block I/O over FSBlockDeviceResource (sector-aligned RMW)
+ libntfs-3g.a (arm64 + x86_64, statically linked)
| Piece | State |
|---|---|
ntfs3g extension (NTFS read/write engine) |
✅ builds; engine statically linked; implemented operations and limits |
xntfs app (UI, monitor, mount service, settings) |
✅ builds |
| English + Simplified Chinese localization | ✅ Localizable.xcstrings (en, zh-Hans) |
| App icon | ✅ generated, full AppIcon set |
| End-to-end mount on a Mac | Verified on macOS 15.8 and 27.0; the extension still needs FSKit provisioning and user enablement |
The extension uses a restricted FSKit entitlement. A locally built copy requires a signing team whose provisioning profile authorizes it; building alone does not enable the extension.
The bundled engine is pinned to ntfs-3g 2026.9.28,
which fixes the 2026.9.18 regression that could reject new files and directories with
EINVAL. See issue #7 validation.
- macOS 15 open-file lifetime: native fix validated on macOS 15.8. The current source retains deleted/replaced open files until final close, without depending on macOS 26 APIs. Bridge, direct Swift and mounted lifetime tests passed on macOS 15.8 x86_64 with TestFlight 1.0.9 (13). Older builds can still lose access through existing handles; close files before deleting or replacing them. macOS 26+ keeps FSKit's emulation, tested on macOS 27; its unlinked open files still report a link count of 1. See issue #4 and current validation.
- macOS 15 allocation reporting can remain stale while a file is open. Mounted tests reproduced outdated allocated-space values after ordinary writes and preallocation, until the last descriptor closed. Data and logical file size remained correct in these tests. The same probe passed on macOS 27; the cause is not yet established. See the focused retest.
The current source supports native creation-time and hidden-flag updates, symbolic links, volume renaming, persistent preallocation for ordinary files, and sparse-region queries on macOS 27. It performs a read-only mount preflight, not a full filesystem check or repair. These are source capabilities, not a claim that every published build includes them. See the design, tests and remaining limits.
The current source stores extended attributes (including Finder tags and resource forks)
in same-named NTFS alternate data streams, instead of creating new AppleDouble ._ files.
Each attribute is limited to 128 KiB, matching the limit observed in mounted FSKit
tests on macOS 27. This limit does not apply to ordinary file contents.
Existing ._ files are not automatically migrated or deleted. Until migrated, their
metadata is not exposed by the native xattr API. Back up the files and their ._ companions
before upgrading or migrating. With the volume mounted read/write using the new driver,
macOS's dot_clean can merge the old metadata into native attributes. For one directory
(without descending into subdirectories):
dot_clean -f -v "/Volumes/YourDrive/YourFolder"This command changes metadata and removes successfully merged ._ files. Verify tags and
resource forks afterward, and keep the backup. Do not bulk-delete ._ files or use cleanup
options that discard them without merging. The app does not run this command automatically.
- Localization (English + Simplified Chinese) —
xntfs/Localizable.xcstrings(String Catalog). Add more languages by addinglocalizationsentries. - Auto-mount removable NTFS — handled by the system, not the app: the extension's
Info.plistregisters NTFSFSMediaTypes(Windows_NTFS, MS Basic-Data GUID, partitionless), so DiskArbitration can auto-mount supported NTFS drives under/Volumeswithout the app running. On macOS 15, the built-in NTFS driver may take priority; see the manual workaround. - Mount location — drives mount under
/Volumes/<name>; the system picks the path and de-duplicates names. Mounting to a custom folder is not possible for a sandboxed FSKit volume (the extension can only reach/Volumesand its own sandbox temp paths), so/Volumesis the only target. - Multiple drives at once — devices are tracked by BSD name and mounted independently.
- Manual mount / unmount — on macOS 27, the app offers in-app mounting with a read-only choice. On macOS 15, it shows a copyable Terminal command to mount a selected volume with xntfs, including volumes already attached by Disk Utility. On macOS 26, use Disk Utility. Mounted volumes can be revealed in Finder or ejected from the app.
- Disk images — on macOS 27, the app can select and mount a raw, single-volume NTFS image. Partitioned or compressed images are opened in Disk Utility. On older systems, attach images in Disk Utility; a selected NTFS partition can then use the macOS 15 manual workaround.
The repo is a normal Xcode project — open xntfs.xcodeproj and build the xntfs scheme.
Build settings for the extension (bridging header, libntfs-3g.a link, header search paths,
HAVE_CONFIG_H) and the app (entitlements, zh-Hans region) are already committed; they
were applied with scripts/wire_project.rb (re-runnable, idempotent, needs the xcodeproj gem).
Before building in Xcode, generate the universal static library and shared config.h
(requires Autotools and GNU libtool):
./scripts/build-libntfs.sh
lipo -archs build/libntfs-universal/libntfs-3g.aThe script builds arm64 and x86_64 separately, then combines them under
build/libntfs-universal/. That directory is ignored by Git; run the script again after a clean
checkout or when updating the ntfs-3g submodule.
The project uses the maintainer's signing team by default. Choose your own team in Xcode and configure the FSKit capability before running a local build.
The extension declares the restricted entitlement com.apple.developer.fskit.fsmodule
(ntfs3g/ntfs3g.entitlements). macOS (AMFI) refuses to load the extension unless that entitlement
is authorized by a provisioning profile. With automatic signing and team 529LJDH392, this
works only if that team's App ID has the FSKit File System Module capability enabled in the
Apple Developer portal. Replace the project team with your own before signing. Once provisioned,
enable the module under System Settings → General →
Login Items & Extensions → File System Extensions.
The mounting paths differ by operating system:
- macOS 27: the app uses FSKit and Disk Arbitration APIs for in-app volume and image mounts.
- macOS 26: automatic mounting works when the extension is enabled; Disk Utility handles manual mounts and image attachment.
- macOS 15: the built-in NTFS driver can win automatic selection. The app provides a
copyable, per-volume Terminal command that temporarily selects xntfs. It asks for confirmation
and administrator authentication, then removes its temporary
.fsentry. The app does not execute this command itself. See the compatibility details.
This is a technical capability result; App Review is a separate policy gate.
xntfs/ app target (SwiftUI)
xntfsApp.swift @main App + Settings scene
ContentView.swift device/image list + OS-specific mount controls
DiskUtility.swift opens Apple's Disk Utility (mount / attach images)
MountSheet.swift in-app mount sheet on macOS 27
LegacyMountSheet.swift manual mount guidance on macOS 15
CopyableCommand.swift copyable-command control
DiagnosticsView.swift extension-status diagnostics page
SettingsView.swift read-only-by-default preference
Model/NTFSDevice.swift
Services/ AppModel, AppSettings, DiskArbitrationMonitor, MountService,
ExtensionStatus
Localizable.xcstrings en + zh-Hans
Assets.xcassets/AppIcon generated icon set
ntfs3g/ FSKit extension target
ntfs3g*.swift @main + FSUnaryFileSystem + FSVolume + FSItem
bridge/ ntfs_fskit.{h,c}, ntfs_device_fskit.m, bridging header
Info.plist FSShortName=xntfs, FSMediaTypes, block resources
ntfs3g.entitlements com.apple.developer.fskit.fsmodule + sandbox
ntfs-3g/ upstream source + built libntfs-3g.a
scripts/wire_project.rb applies extension/app build settings (xcodeproj gem)
assets/xntfs-icon.svg icon source