Files
markbase/docs/FSKIT_API_ANALYSIS.md
Warren 1300a4e223
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled
MarkBase架构升级:Multi-Volume Virtual Tree + Dual-View Management + Git Remote修正
核心功能:
-  Categories/Series双视图管理(category_view.rs + import_markdown.rs)
-  FUSE Multi-Volume支持(tree_type参数)
-  SSH/SFTP/SCP/rsync协议完整实现(4042行)
-  NFS/SMB Module Phase 1-3完成
-  Archive Module Phase 1-4完成(2916行)
-  Download Center API完整实现
-  S3兼容API实现(560行)

Git配置修正:
-  删除错误origin(gitea.momentry.ddns.net)
-  删除m5max128(指向机器名)
-  设置origin = m5max128gitea.momentry.ddns.net/admin/markbase
-  设置m4minigitea = m4minigitea.momentry.ddns.net/warren/markbase

数据清理:
-  删除38个临时SQLite(保留accusys.sqlite、demo.sqlite)
-  删除.bak、test_*.bin、调试脚本等临时文件
-  删除临时目录(build/、download files/、raid_test/等)
-  更新.gitignore排除临时文件

架构优化:
- 52个文件修改,2434行新增,4739行删除
- Workspace成员整合(16个crate)
- 数据库状态:accusys.sqlite保留(主demo测试)

远程同步:
-  准备推送到m5max128gitea(远程Gitea)
-  准备推送到m4minigitea(本地Gitea)
2026-06-12 12:59:54 +08:00

26 KiB

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)

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)

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)

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:

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):

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:

extension MyFSVolume: FSVolume.OpenCloseOperations {
    
    func openItem(_ item: FSItem, modes: FSVolume.OpenModes) async throws {
    }
    
    func closeItem(_ item: FSItem, modes: FSVolume.OpenModes) async throws {
    }
}

FSVolume.XattrOperations:

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:

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)

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(&timespec, 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 version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>EXAppExtensionAttributes</key>
	<dict>
		<key>EXExtensionPointIdentifier</key>
		<string>com.apple.fskit.fsmodule</string>
		
		<key>FSShortName</key>
		<string>MyFS</string>
		
		<key>FSPersonalities</key>
		<dict>
			<key>FSKitExpExtensionPersonality</key>
			<dict>
				<key>FSName</key>
				<string>MyFS</string>
				<key>FSfileObjectsAreCaseSensitive</key>
				<false/>
			</dict>
		</dict>
		
		<key>FSSupportsBlockResources</key>
		<true/>
		
		<key>FSSupportsGenericURLResources</key>
		<false/>
		
		<key>FSSupportsPathURLs</key>
		<false/>
		
		<key>FSSupportsServerURLs</key>
		<false/>
		
		<key>FSRequiresSecurityScopedPathURLResources</key>
		<false/>
		
		<key>FSMediaTypes</key>
		<dict/>
		
		<key>FSActivateOptionSyntax</key>
		<dict>
			<key>shortOptions</key>
			<string>g:m:o:u:</string>
		</dict>
		
		<key>FSCheckOptionSyntax</key>
		<dict>
			<key>shortOptions</key>
			<string>nqy</string>
		</dict>
		
		<key>FSFormatOptionSyntax</key>
		<dict>
			<key>shortOptions</key>
			<string>v</string>
		</dict>
	</dict>
</dict>
</plist>

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 version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
    <key>com.apple.developer.fskit.fsmodule</key>
    <true/>
</dict>
</plist>

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

import SwiftUI

@main
struct FSKitExpApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

ContentView.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

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

# 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

# Create mount point
mkdir /tmp/TestVol

# Mount the filesystem
mount -F -t MyFS disk18 /tmp/TestVol

Unmount Filesystem

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):

@main
class HelloFSExtension: FSUnaryFileSystemExtension {
    // ERROR: FSUnaryFileSystemExtension doesn't exist
}

FSKitSample (Correct):

@main
struct FSKitExpExtension : UnaryFileSystemExtension {
    var fileSystem : FSUnaryFileSystem & FSUnaryFileSystemOperations {
        MyFS()
    }
}

Probe Resource

HelloFS (Wrong):

func probeResource(_ resource: FSResource) -> FSProbeResult {
    // Sync return
}

FSKitSample (Correct):

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):

func loadResource(_ resource: FSResource) -> FSVolume {
    // Sync return
}

FSKitSample (Correct):

func loadResource(
    resource: FSResource,
    options: FSTaskOptions,
    replyHandler: @escaping (FSVolume?, (any Error)?) -> Void
) {
    // Async with replyHandler
    replyHandler(MyFSVolume(resource: resource), nil)
}

Volume Operations

HelloFS (Wrong):

func activate() -> FSItem {
    // Sync return
}

FSKitSample (Correct):

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

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

// 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

// Change from:
@main
class HelloFSExtension: FSUnaryFileSystemExtension

// To:
@main
struct HelloFSExtension: UnaryFileSystemExtension {
    var fileSystem: FSUnaryFileSystem & FSUnaryFileSystemOperations {
        HelloFS()
    }
}

2. Fix Filesystem Class

// 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