# FSKit API Real-World Analysis **Source**: KhaosT/FSKitSample GitHub Repository **Date**: 2026-06-11 **Purpose**: Fix HelloFS compilation errors with actual FSKit API --- ## Executive Summary **FSKit is the new macOS Sequoia 15.4+ framework for user-space filesystem implementations.** This analysis extracts real working code patterns from the FSKitSample repository to fix compilation errors in HelloFS. --- ## 1. Repository Structure ``` FSKitSample/ ├── FSKitExp/ # Main Application (SwiftUI) │ ├── FSKitExpApp.swift # App entry point │ ├── ContentView.swift # UI for managing extensions │ ├── ViewModel.swift # FSClient interaction │ ├── Info.plist │ └── FSKitExp.entitlements │ ├── FSKitExpExtension/ # File System Extension (Target) │ ├── FSKitExpExtension.swift # Extension entry point ⭐ │ ├── MyFS.swift # FSUnaryFileSystem implementation ⭐ │ ├── MyFSVolume.swift # FSVolume implementation ⭐ │ ├── MyFSItem.swift # FSItem implementation ⭐ │ ├── Constants.swift # UUID constants │ ├── Info.plist # Extension configuration ⭐ │ └── FSKitExpExtension.entitlements ⭐ │ └── FSKitExp.xcodeproj/ # Xcode project ``` **Key Insight**: FSKit requires **two targets**: 1. **Main App** - For UI and extension management 2. **File System Extension** - The actual filesystem implementation --- ## 2. FSKit Extension Entry Point ### FSKitExpExtension.swift (Complete Code) ```swift import Foundation import FSKit @main struct FSKitExpExtension : UnaryFileSystemExtension { var fileSystem : FSUnaryFileSystem & FSUnaryFileSystemOperations { MyFS() } } ``` **Critical Finding**: - Entry point is `UnaryFileSystemExtension` (NOT `FSUnaryFileSystemExtension`!) - Returns `FSUnaryFileSystem & FSUnaryFileSystemOperations` - Uses `@main` attribute for Swift 5.7+ entry point --- ## 3. Filesystem Implementation ### MyFS.swift (Complete Code) ```swift import Foundation import FSKit import os final class MyFS: FSUnaryFileSystem, FSUnaryFileSystemOperations { private let logger = Logger(subsystem: "FSKitExp", category: "MyFS") func probeResource( resource: FSResource, replyHandler: @escaping (FSProbeResult?, (any Error)?) -> Void ) { logger.debug("probeResource: \(resource, privacy: .public)") replyHandler( FSProbeResult.usable( name: "Test1", containerID: FSContainerIdentifier(uuid: Constants.containerIdentifier) ), nil ) } func loadResource( resource: FSResource, options: FSTaskOptions, replyHandler: @escaping (FSVolume?, (any Error)?) -> Void ) { containerStatus = .ready logger.debug("loadResource: \(resource, privacy: .public)") replyHandler( MyFSVolume(resource: resource), nil ) } func unloadResource( resource: FSResource, options: FSTaskOptions, replyHandler reply: @escaping ((any Error)?) -> Void ) { logger.debug("unloadResource: \(resource, privacy: .public)") reply(nil) } func didFinishLoading() { logger.debug("didFinishLoading") } } ``` **Key API Differences from HelloFS**: | HelloFS (Wrong) | FSKitSample (Correct) | |----------------|----------------------| | `FSUnaryFileSystemExtension` | `UnaryFileSystemExtension` | | `probeResource()` sync | `probeResource()` with replyHandler | | `loadResource()` sync | `loadResource()` with replyHandler | | `unloadResource()` sync | `unloadResource()` with replyHandler | | No `didFinishLoading()` | Has `didFinishLoading()` | --- ## 4. Volume Implementation ### MyFSVolume.swift (Key Extracts) ```swift final class MyFSVolume: FSVolume { private let resource: FSResource private let logger = Logger(subsystem: "FSKitExp", category: "MyFSVolume") private let root: MyFSItem = { let item = MyFSItem(name: FSFileName(string: "/")) item.attributes.parentID = .parentOfRoot item.attributes.fileID = .rootDirectory item.attributes.uid = 0 item.attributes.gid = 0 item.attributes.linkCount = 1 item.attributes.type = .directory item.attributes.mode = UInt32(S_IFDIR | 0b111_000_000) item.attributes.allocSize = 1 item.attributes.size = 1 return item }() init(resource: FSResource) { self.resource = resource super.init( volumeID: FSVolume.Identifier(uuid: Constants.volumeIdentifier), volumeName: FSFileName(string: "Test1") ) } } ``` ### Volume Protocols **FSVolume.PathConfOperations**: ```swift extension MyFSVolume: FSVolume.PathConfOperations { var maximumLinkCount: Int { return -1 } var maximumNameLength: Int { return -1 } var restrictsOwnershipChanges: Bool { return false } var truncatesLongNames: Bool { return false } var maximumXattrSize: Int { return Int.max } var maximumFileSize: UInt64 { return UInt64.max } } ``` **FSVolume.Operations** (Core Operations): ```swift extension MyFSVolume: FSVolume.Operations { var supportedVolumeCapabilities: FSVolume.SupportedCapabilities { let capabilities = FSVolume.SupportedCapabilities() capabilities.supportsHardLinks = true capabilities.supportsSymbolicLinks = true capabilities.supportsPersistentObjectIDs = true capabilities.doesNotSupportVolumeSizes = true capabilities.supportsHiddenFiles = true capabilities.supports64BitObjectIDs = true capabilities.caseFormat = .insensitiveCasePreserving return capabilities } var volumeStatistics: FSStatFSResult { let result = FSStatFSResult(fileSystemTypeName: "MyFS") result.blockSize = 1024000 result.ioSize = 1024000 result.totalBlocks = 1024000 result.availableBlocks = 1024000 result.freeBlocks = 1024000 result.totalFiles = 1024000 result.freeFiles = 1024000 return result } func activate(options: FSTaskOptions) async throws -> FSItem { return root } func deactivate(options: FSDeactivateOptions = []) async throws { } func mount(options: FSTaskOptions) async throws { } func unmount() async { } func synchronize(flags: FSSyncFlags) async throws { } func attributes( _ desiredAttributes: FSItem.GetAttributesRequest, of item: FSItem ) async throws -> FSItem.Attributes { if let item = item as? MyFSItem { return item.attributes } else { throw fs_errorForPOSIXError(POSIXError.EIO.rawValue) } } func setAttributes( _ newAttributes: FSItem.SetAttributesRequest, on item: FSItem ) async throws -> FSItem.Attributes { if let item = item as? MyFSItem { mergeAttributes(item.attributes, request: newAttributes) return item.attributes } else { throw fs_errorForPOSIXError(POSIXError.EIO.rawValue) } } func lookupItem( named name: FSFileName, inDirectory directory: FSItem ) async throws -> (FSItem, FSFileName) { guard let directory = directory as? MyFSItem else { throw fs_errorForPOSIXError(POSIXError.ENOENT.rawValue) } if let item = directory.children[name] { return (item, name) } else { throw fs_errorForPOSIXError(POSIXError.ENOENT.rawValue) } } func reclaimItem(_ item: FSItem) async throws { } func readSymbolicLink(_ item: FSItem) async throws -> FSFileName { throw fs_errorForPOSIXError(POSIXError.EIO.rawValue) } func createItem( named name: FSFileName, type: FSItem.ItemType, inDirectory directory: FSItem, attributes newAttributes: FSItem.SetAttributesRequest ) async throws -> (FSItem, FSFileName) { guard let directory = directory as? MyFSItem else { throw fs_errorForPOSIXError(POSIXError.EIO.rawValue) } let item = MyFSItem(name: name) mergeAttributes(item.attributes, request: newAttributes) item.attributes.parentID = directory.attributes.fileID item.attributes.type = type directory.addItem(item) return (item, name) } func createSymbolicLink( named name: FSFileName, inDirectory directory: FSItem, attributes newAttributes: FSItem.SetAttributesRequest, linkContents contents: FSFileName ) async throws -> (FSItem, FSFileName) { throw fs_errorForPOSIXError(POSIXError.EIO.rawValue) } func createLink( to item: FSItem, named name: FSFileName, inDirectory directory: FSItem ) async throws -> FSFileName { throw fs_errorForPOSIXError(POSIXError.EIO.rawValue) } func removeItem( _ item: FSItem, named name: FSFileName, fromDirectory directory: FSItem ) async throws { if let item = item as? MyFSItem, let directory = directory as? MyFSItem { directory.removeItem(item) } else { throw fs_errorForPOSIXError(POSIXError.EIO.rawValue) } } func renameItem( _ item: FSItem, inDirectory sourceDirectory: FSItem, named sourceName: FSFileName, to destinationName: FSFileName, inDirectory destinationDirectory: FSItem, overItem: FSItem? ) async throws -> FSFileName { throw fs_errorForPOSIXError(POSIXError.EIO.rawValue) } func enumerateDirectory( _ directory: FSItem, startingAt cookie: FSDirectoryCookie, verifier: FSDirectoryVerifier, attributes: FSItem.GetAttributesRequest?, packer: FSDirectoryEntryPacker ) async throws -> FSDirectoryVerifier { guard let directory = directory as? MyFSItem else { throw fs_errorForPOSIXError(POSIXError.ENOENT.rawValue) } for (idx, item) in directory.children.values.enumerated() { let isLast = (idx == directory.children.count - 1) let v = packer.packEntry( name: item.name, itemType: item.attributes.type, itemID: item.attributes.fileID, nextCookie: FSDirectoryCookie(UInt64(idx)), attributes: attributes != nil ? item.attributes : nil ) } return FSDirectoryVerifier(0) } } ``` **FSVolume.OpenCloseOperations**: ```swift extension MyFSVolume: FSVolume.OpenCloseOperations { func openItem(_ item: FSItem, modes: FSVolume.OpenModes) async throws { } func closeItem(_ item: FSItem, modes: FSVolume.OpenModes) async throws { } } ``` **FSVolume.XattrOperations**: ```swift extension MyFSVolume: FSVolume.XattrOperations { func xattr(named name: FSFileName, of item: FSItem) async throws -> Data { if let item = item as? MyFSItem { return item.xattrs[name] ?? Data() } else { return Data() } } func setXattr(named name: FSFileName, to value: Data?, on item: FSItem, policy: FSVolume.SetXattrPolicy) async throws { if let item = item as? MyFSItem { item.xattrs[name] = value } } func xattrs(of item: FSItem) async throws -> [FSFileName] { if let item = item as? MyFSItem { return Array(item.xattrs.keys) } else { return [] } } } ``` **FSVolume.ReadWriteOperations**: ```swift extension MyFSVolume: FSVolume.ReadWriteOperations { func read(from item: FSItem, at offset: off_t, length: Int, into buffer: FSMutableFileDataBuffer) async throws -> Int { var bytesRead = 0 if let item = item as? MyFSItem, let data = item.data { bytesRead = data.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) in let length = min(buffer.length, data.count) _ = buffer.withUnsafeMutableBytes { dst in memcpy(dst.baseAddress, ptr.baseAddress, length) } return length } } return bytesRead } func write(contents: Data, to item: FSItem, at offset: off_t) async throws -> Int { if let item = item as? MyFSItem { item.data = contents item.attributes.size = UInt64(contents.count) item.attributes.allocSize = UInt64(contents.count) } return contents.count } } ``` --- ## 5. FSItem Implementation ### MyFSItem.swift (Complete Code) ```swift import Foundation import FSKit final class MyFSItem: FSItem { private static var id: UInt64 = FSItem.Identifier.rootDirectory.rawValue + 1 static func getNextID() -> UInt64 { let current = id id += 1 return current } let name: FSFileName let id = MyFSItem.getNextID() var attributes = FSItem.Attributes() var xattrs: [FSFileName: Data] = [:] var data: Data? private(set) var children: [FSFileName: MyFSItem] = [:] init(name: FSFileName) { self.name = name attributes.fileID = FSItem.Identifier(rawValue: id) ?? .invalid attributes.size = 0 attributes.allocSize = 0 attributes.flags = 0 var timespec = timespec() timespec_get(×pec, TIME_UTC) attributes.addedTime = timespec attributes.birthTime = timespec attributes.changeTime = timespec attributes.modifyTime = timespec attributes.accessTime = timespec } func addItem(_ item: MyFSItem) { children[item.name] = item } func removeItem(_ item: MyFSItem) { children[item.name] = nil } } ``` **Key Points**: - Subclasses `FSItem` directly - Manages file ID allocation - Stores attributes, xattrs, data, and children - Helper methods for adding/removing children --- ## 6. Info.plist Configuration (CRITICAL) ### FSKitExpExtension/Info.plist ```xml EXAppExtensionAttributes EXExtensionPointIdentifier com.apple.fskit.fsmodule FSShortName MyFS FSPersonalities FSKitExpExtensionPersonality FSName MyFS FSfileObjectsAreCaseSensitive FSSupportsBlockResources FSSupportsGenericURLResources FSSupportsPathURLs FSSupportsServerURLs FSRequiresSecurityScopedPathURLResources FSMediaTypes FSActivateOptionSyntax shortOptions g:m:o:u: FSCheckOptionSyntax shortOptions nqy FSFormatOptionSyntax shortOptions v ``` **Critical Configuration Keys**: | Key | Value | Purpose | |-----|-------|---------| | `EXExtensionPointIdentifier` | `com.apple.fskit.fsmodule` | FSKit extension type | | `FSShortName` | `MyFS` | Filesystem short name | | `FSName` | `MyFS` | Filesystem display name | | `FSSupportsBlockResources` | `true` | Supports block devices | | `FSSupportsGenericURLResources` | `false` | No generic URL support | | `FSSupportsPathURLs` | `false` | No path URL support | | `FSSupportsServerURLs` | `false` | No server URL support | | `FSActivateOptionSyntax` | `-g:m:o:u:` | Mount options syntax | | `FSCheckOptionSyntax` | `-nqy` | Check options syntax | | `FSFormatOptionSyntax` | `-v` | Format options syntax | --- ## 7. Entitlements Configuration ### FSKitExpExtension.entitlements ```xml com.apple.security.app-sandbox com.apple.developer.fskit.fsmodule ``` **Required Entitlements**: 1. `com.apple.security.app-sandbox` - Must be enabled 2. `com.apple.developer.fskit.fsmodule` - FSKit module capability --- ## 8. Main App Structure ### FSKitExpApp.swift ```swift import SwiftUI @main struct FSKitExpApp: App { var body: some Scene { WindowGroup { ContentView() } } } ``` ### ContentView.swift ```swift import SwiftUI struct ContentView: View { @State private var viewModel = ViewModel() var body: some View { List { ForEach(viewModel.modules, id: \.self) { module in VStack(alignment: .leading) { Text(module.bundleIdentifier) .bold() Text(module.url.absoluteString) Text("\(module.isEnabled)") } } } } } ``` ### ViewModel.swift ```swift import Foundation import FSKit import Observation @Observable @MainActor final class ViewModel { private var client: FSClient? private(set) var modules: [FSModuleIdentity] = [] init() { client = FSClient.shared client?.fetchInstalledExtensions { modules, errors in if let modules { self.modules = modules } } } } ``` **Purpose**: Main app lists installed FSKit extensions and their status. --- ## 9. Testing and Mounting ### Create Dummy Block Device ```bash # Create a 100MB dummy file mkfile -n 100m dummy # Mount it as a raw block device hdiutil attach -imagekey diskimage-class=CRawDiskImage -nomount dummy # Output: /dev/disk18 ``` ### Mount Filesystem ```bash # Create mount point mkdir /tmp/TestVol # Mount the filesystem mount -F -t MyFS disk18 /tmp/TestVol ``` ### Unmount Filesystem ```bash umount /tmp/TestVol ``` ### Enable Extension After building and running the app: 1. Open System Settings 2. Navigate to General → Login Items & Extensions 3. Enable the File System Extension under "File System Extensions" --- ## 10. Critical API Differences from HelloFS ### Entry Point **HelloFS (Wrong)**: ```swift @main class HelloFSExtension: FSUnaryFileSystemExtension { // ERROR: FSUnaryFileSystemExtension doesn't exist } ``` **FSKitSample (Correct)**: ```swift @main struct FSKitExpExtension : UnaryFileSystemExtension { var fileSystem : FSUnaryFileSystem & FSUnaryFileSystemOperations { MyFS() } } ``` ### Probe Resource **HelloFS (Wrong)**: ```swift func probeResource(_ resource: FSResource) -> FSProbeResult { // Sync return } ``` **FSKitSample (Correct)**: ```swift func probeResource( resource: FSResource, replyHandler: @escaping (FSProbeResult?, (any Error)?) -> Void ) { // Async with replyHandler replyHandler( FSProbeResult.usable( name: "MyFS", containerID: FSContainerIdentifier(uuid: Constants.containerIdentifier) ), nil ) } ``` ### Load Resource **HelloFS (Wrong)**: ```swift func loadResource(_ resource: FSResource) -> FSVolume { // Sync return } ``` **FSKitSample (Correct)**: ```swift func loadResource( resource: FSResource, options: FSTaskOptions, replyHandler: @escaping (FSVolume?, (any Error)?) -> Void ) { // Async with replyHandler replyHandler(MyFSVolume(resource: resource), nil) } ``` ### Volume Operations **HelloFS (Wrong)**: ```swift func activate() -> FSItem { // Sync return } ``` **FSKitSample (Correct)**: ```swift func activate(options: FSTaskOptions) async throws -> FSItem { // Async with options parameter } ``` --- ## 11. Protocol Hierarchy ``` UnaryFileSystemExtension (Entry Point) └─ Returns: FSUnaryFileSystem & FSUnaryFileSystemOperations FSUnaryFileSystemOperations: ├─ probeResource(_:replyHandler:) ├─ loadResource(_:options:replyHandler:) ├─ unloadResource(_:options:replyHandler:) └─ didFinishLoading() FSVolume: ├─ FSVolume.Operations (Required) ├─ FSVolume.PathConfOperations ├─ FSVolume.OpenCloseOperations ├─ FSVolume.XattrOperations └─ FSVolume.ReadWriteOperations FSVolume.Operations: ├─ supportedVolumeCapabilities ├─ volumeStatistics ├─ activate(options:) ├─ deactivate(options:) ├─ mount(options:) ├─ unmount() ├─ synchronize(flags:) ├─ attributes(_:of:) ├─ setAttributes(_:on:) ├─ lookupItem(named:inDirectory:) ├─ reclaimItem(_:) ├─ readSymbolicLink(_:) ├─ createItem(named:type:inDirectory:attributes:) ├─ createSymbolicLink(named:inDirectory:attributes:linkContents:) ├─ createLink(to:named:inDirectory:) ├─ removeItem(_:named:fromDirectory:) ├─ renameItem(_:inDirectory:named:to:inDirectory:overItem:) └─ enumerateDirectory(_:startingAt:verifier:attributes:packer:) FSVolume.PathConfOperations: ├─ maximumLinkCount ├─ maximumNameLength ├─ restrictsOwnershipChanges ├─ truncatesLongNames ├─ maximumXattrSize └─ maximumFileSize FSVolume.OpenCloseOperations: ├─ openItem(_:modes:) └─ closeItem(_:modes:) FSVolume.XattrOperations: ├─ xattr(named:of:) ├─ setXattr(named:to:on:policy:) └─ xattrs(of:) FSVolume.ReadWriteOperations: ├─ read(from:at:length:into:) └─ write(contents:to:at:) ``` --- ## 12. FSItem Structure ```swift FSItem.Attributes: ├─ fileID: FSItem.Identifier ├─ parentID: FSItem.Identifier ├─ uid: uid_t ├─ gid: gid_t ├─ type: FSItem.ItemType ├─ mode: UInt32 ├─ linkCount: UInt32 ├─ flags: UInt32 ├─ size: UInt64 ├─ allocSize: UInt64 ├─ accessTime: timespec ├─ modifyTime: timespec ├─ changeTime: timespec ├─ birthTime: timespec ├─ addedTime: timespec └─ backupTime: timespec FSItem.GetAttributesRequest: └─ isValid(_ attribute: FSItem.Attribute) -> Bool FSItem.SetAttributesRequest: ├─ All attributes from FSItem.Attributes └─ isValid(_ attribute: FSItem.Attribute) -> Bool FSItem.Identifier: ├─ rootDirectory ├─ invalid ├─ parentOfRoot └─ init(rawValue: UInt64) FSItem.ItemType: ├─ directory ├─ file ├─ symbolicLink └─ etc. FSFileName: ├─ init(string: String) └─ string: String? FSDirectoryCookie: └─ init(UInt64) FSDirectoryVerifier: └─ init(UInt64) FSDirectoryEntryPacker: └─ packEntry(name:itemType:itemID:nextCookie:attributes:) -> Bool ``` --- ## 13. Error Handling ```swift // POSIX error helper fs_errorForPOSIXError(POSIXError.ENOENT.rawValue) // File not found fs_errorForPOSIXError(POSIXError.EIO.rawValue) // I/O error fs_errorForPOSIXError(POSIXError.EACCES.rawValue) // Permission denied ``` --- ## 14. Build Configuration ### Xcode Project Structure - **Main App Target**: FSKitExp (macOS App) - **Extension Target**: FSKitExpExtension (File System Extension) - **Platform**: macOS Sequoia 15.4+ - **Language**: Swift 5.7+ - **Frameworks**: - SwiftUI (Main App) - FSKit (Extension) ### Build Phases **Main App**: - Compile Sources: FSKitExpApp.swift, ContentView.swift, ViewModel.swift - Link Binary: SwiftUI.framework - Copy Bundle Resources: Assets.xcassets **Extension**: - Compile Sources: FSKitExpExtension.swift, MyFS.swift, MyFSVolume.swift, MyFSItem.swift, Constants.swift - Link Binary: FSKit.framework - Copy Bundle Resources: None --- ## 15. Key Takeaways for HelloFS ### 1. Fix Entry Point ```swift // Change from: @main class HelloFSExtension: FSUnaryFileSystemExtension // To: @main struct HelloFSExtension: UnaryFileSystemExtension { var fileSystem: FSUnaryFileSystem & FSUnaryFileSystemOperations { HelloFS() } } ``` ### 2. Fix Filesystem Class ```swift // Change from: class HelloFS: FSUnaryFileSystemExtension // To: final class HelloFS: FSUnaryFileSystem, FSUnaryFileSystemOperations ``` ### 3. Fix Probe/Load/Unload Methods - Change sync methods to async with replyHandler - Add `options: FSTaskOptions` parameter - Add `didFinishLoading()` method ### 4. Fix Volume Operations - Add `options: FSTaskOptions` parameter to async methods - Use proper async/await syntax - Implement all required protocols ### 5. Add Info.plist Configuration - Add `EXAppExtensionAttributes` with FSKit configuration - Set `FSShortName` and `FSName` - Configure `FSSupportsBlockResources` = true ### 6. Add Entitlements - Enable `com.apple.security.app-sandbox` - Enable `com.apple.developer.fskit.fsmodule` --- ## 16. Summary of Compilation Errors to Fix | Error | Cause | Fix | |-------|-------|-----| | `Cannot find type 'FSUnaryFileSystemExtension' in scope` | Wrong class name | Use `UnaryFileSystemExtension` | | `Method signature mismatch` | Sync vs async | Use replyHandler pattern | | `Missing parameter` | Missing `options` | Add `FSTaskOptions` parameter | | `Missing method` | No `didFinishLoading()` | Add method | | `Missing Info.plist keys` | No FSKit config | Add `EXAppExtensionAttributes` | | `Missing entitlements` | No FSKit capability | Add entitlements | --- **Last Updated**: 2026-06-11 **Status**: Complete Analysis **Next Steps**: Apply fixes to HelloFS implementation