Files
markbase/docs/FSKIT_FIX_TEMPLATE.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

23 KiB

FSKit HelloFS Fix Template

Based on: KhaosT/FSKitSample
Purpose: Direct code templates for fixing HelloFS compilation errors


1. Entry Point Fix

Current HelloFS (WRONG):

@main
class HelloFSExtension: FSUnaryFileSystemExtension {
    static var shared: HelloFSExtension!
    
    override init() {
        HelloFSExtension.shared = self
        super.init()
    }
    
    var fileSystem: FSUnaryFileSystem {
        HelloFS()
    }
}

Fixed HelloFS (CORRECT):

import Foundation
import FSKit

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

Changes:

  • classstruct
  • FSUnaryFileSystemExtensionUnaryFileSystemExtension
  • Add FSUnaryFileSystemOperations to return type
  • Remove static var shared and override init()

2. Filesystem Class Fix

Current HelloFS (WRONG):

class HelloFS: FSUnaryFileSystemExtension {
    func probeResource(_ resource: FSResource) -> FSProbeResult {
        return FSProbeResult.usable(
            name: "HelloFS",
            containerID: FSContainerIdentifier(uuid: UUID())
        )
    }
    
    func loadResource(_ resource: FSResource) -> FSVolume {
        return HelloFSVolume(resource: resource)
    }
    
    func unloadResource(_ resource: FSResource) {
        // Cleanup
    }
}

Fixed HelloFS (CORRECT):

import Foundation
import FSKit
import os

final class HelloFS: FSUnaryFileSystem, FSUnaryFileSystemOperations {
    
    private let logger = Logger(subsystem: "com.markbase.hellofs", category: "HelloFS")
    
    func probeResource(
        _ resource: FSResource,
        replyHandler: @escaping (FSProbeResult?, (any Error)?) -> Void
    ) {
        logger.debug("probeResource: \(resource, privacy: .public)")
        
        replyHandler(
            FSProbeResult.usable(
                name: "HelloFS",
                containerID: FSContainerIdentifier(uuid: Constants.containerIdentifier)
            ),
            nil
        )
    }
    
    func loadResource(
        _ resource: FSResource,
        options: FSTaskOptions,
        replyHandler: @escaping (FSVolume?, (any Error)?) -> Void
    ) {
        logger.debug("loadResource: \(resource, privacy: .public)")
        containerStatus = .ready
        replyHandler(HelloFSVolume(resource: resource), nil)
    }
    
    func unloadResource(
        _ resource: FSResource,
        options: FSTaskOptions,
        replyHandler: @escaping ((any Error)?) -> Void
    ) {
        logger.debug("unloadResource: \(resource, privacy: .public)")
        replyHandler(nil)
    }
    
    func didFinishLoading() {
        logger.debug("didFinishLoading")
    }
}

Changes:

  • class HelloFS: FSUnaryFileSystemExtensionfinal class HelloFS: FSUnaryFileSystem, FSUnaryFileSystemOperations
  • Add FSUnaryFileSystemOperations protocol
  • Add os.Logger for debugging
  • Change all methods to async with replyHandler
  • Add options: FSTaskOptions parameter
  • Add didFinishLoading() method

3. Volume Class Fix

Current HelloFS (WRONG):

class HelloFSVolume: FSVolume {
    init(resource: FSResource) {
        super.init(volumeID: FSVolume.Identifier(), volumeName: FSFileName(string: "HelloFS"))
    }
    
    func activate() -> FSItem {
        return rootItem
    }
    
    func deactivate() {
        // Cleanup
    }
}

Fixed HelloFS (CORRECT):

import Foundation
import FSKit
import os

final class HelloFSVolume: FSVolume {
    
    private let resource: FSResource
    private let logger = Logger(subsystem: "com.markbase.hellofs", category: "HelloFSVolume")
    
    private let root: HelloFSItem = {
        let item = HelloFSItem(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: "HelloFS")
        )
    }
}

// MARK: - FSVolume.PathConfOperations
extension HelloFSVolume: 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
    }
}

// MARK: - FSVolume.Operations
extension HelloFSVolume: FSVolume.Operations {
    
    var supportedVolumeCapabilities: FSVolume.SupportedCapabilities {
        logger.debug("supportedVolumeCapabilities")
        
        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 {
        logger.debug("volumeStatistics")

        let result = FSStatFSResult(fileSystemTypeName: "HelloFS")
        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 {
        logger.debug("activate")
        return root
    }
    
    func deactivate(options: FSDeactivateOptions = []) async throws {
        logger.debug("deactivate")
    }
    
    func mount(options: FSTaskOptions) async throws {
        logger.debug("mount")
    }
    
    func unmount() async {
        logger.debug("unmount")
    }
    
    func synchronize(flags: FSSyncFlags) async throws {
        logger.debug("synchronize")
    }
    
    func attributes(
        _ desiredAttributes: FSItem.GetAttributesRequest,
        of item: FSItem
    ) async throws -> FSItem.Attributes {
        if let item = item as? HelloFSItem {
            logger.debug("getItemAttributes: \(item.name)")
            return item.attributes
        } else {
            logger.error("getItemAttributes: invalid item")
            throw fs_errorForPOSIXError(POSIXError.EIO.rawValue)
        }
    }
    
    func setAttributes(
        _ newAttributes: FSItem.SetAttributesRequest,
        on item: FSItem
    ) async throws -> FSItem.Attributes {
        logger.debug("setItemAttributes: \(item)")
        if let item = item as? HelloFSItem {
            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) {
        logger.debug("lookupItem: \(name.string ?? "nil")")
        
        guard let directory = directory as? HelloFSItem 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 {
        logger.debug("reclaimItem: \(item)")
    }
    
    func readSymbolicLink(_ item: FSItem) async throws -> FSFileName {
        logger.debug("readSymbolicLink: \(item)")
        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) {
        logger.debug("createItem: \(name.string ?? "nil")")
        
        guard let directory = directory as? HelloFSItem else {
            throw fs_errorForPOSIXError(POSIXError.EIO.rawValue)
        }
        
        let item = HelloFSItem(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) {
        logger.debug("createSymbolicLink: \(name)")
        throw fs_errorForPOSIXError(POSIXError.EIO.rawValue)
    }
    
    func createLink(
        to item: FSItem,
        named name: FSFileName,
        inDirectory directory: FSItem
    ) async throws -> FSFileName {
        logger.debug("createLink: \(name)")
        throw fs_errorForPOSIXError(POSIXError.EIO.rawValue)
    }
    
    func removeItem(
        _ item: FSItem,
        named name: FSFileName,
        fromDirectory directory: FSItem
    ) async throws {
        logger.debug("removeItem: \(name)")
        if let item = item as? HelloFSItem, let directory = directory as? HelloFSItem {
            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 {
        logger.debug("renameItem: \(item)")
        throw fs_errorForPOSIXError(POSIXError.EIO.rawValue)
    }
    
    func enumerateDirectory(
        _ directory: FSItem,
        startingAt cookie: FSDirectoryCookie,
        verifier: FSDirectoryVerifier,
        attributes: FSItem.GetAttributesRequest?,
        packer: FSDirectoryEntryPacker
    ) async throws -> FSDirectoryVerifier {
        logger.debug("enumerateDirectory: \(directory)")

        guard let directory = directory as? HelloFSItem else {
            throw fs_errorForPOSIXError(POSIXError.ENOENT.rawValue)
        }
        
        for (idx, item) in directory.children.values.enumerated() {
            _ = 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)
    }
    
    private func mergeAttributes(_ existing: FSItem.Attributes, request: FSItem.SetAttributesRequest) {
        if request.isValid(FSItem.Attribute.uid) {
            existing.uid = request.uid
        }
        
        if request.isValid(FSItem.Attribute.gid) {
            existing.gid = request.gid
        }
        
        if request.isValid(FSItem.Attribute.type) {
            existing.type = request.type
        }
        
        if request.isValid(FSItem.Attribute.mode) {
            existing.mode = request.mode
        }
        
        if request.isValid(FSItem.Attribute.linkCount) {
            existing.linkCount = request.linkCount
        }
        
        if request.isValid(FSItem.Attribute.flags) {
            existing.flags = request.flags
        }
        
        if request.isValid(FSItem.Attribute.size) {
            existing.size = request.size
        }
        
        if request.isValid(FSItem.Attribute.allocSize) {
            existing.allocSize = request.allocSize
        }
        
        if request.isValid(FSItem.Attribute.fileID) {
            existing.fileID = request.fileID
        }

        if request.isValid(FSItem.Attribute.parentID) {
            existing.parentID = request.parentID
        }

        if request.isValid(FSItem.Attribute.accessTime) {
            let timespec = timespec()
            request.accessTime = timespec
            existing.accessTime = timespec
        }
        
        if request.isValid(FSItem.Attribute.changeTime) {
            let timespec = timespec()
            request.changeTime = timespec
            existing.changeTime = timespec
        }
        
        if request.isValid(FSItem.Attribute.modifyTime) {
            let timespec = timespec()
            request.modifyTime = timespec
            existing.modifyTime = timespec
        }
        
        if request.isValid(FSItem.Attribute.addedTime) {
            let timespec = timespec()
            request.addedTime = timespec
            existing.addedTime = timespec
        }
        
        if request.isValid(FSItem.Attribute.birthTime) {
            let timespec = timespec()
            request.birthTime = timespec
            existing.birthTime = timespec
        }
        
        if request.isValid(FSItem.Attribute.backupTime) {
            let timespec = timespec()
            request.backupTime = timespec
            existing.backupTime = timespec
        }
    }
}

// MARK: - FSVolume.OpenCloseOperations
extension HelloFSVolume: FSVolume.OpenCloseOperations {
    
    func openItem(_ item: FSItem, modes: FSVolume.OpenModes) async throws {
        if let item = item as? HelloFSItem {
            logger.debug("openItem: \(item.name)")
        } else {
            logger.debug("openItem: \(item)")
        }
    }
    
    func closeItem(_ item: FSItem, modes: FSVolume.OpenModes) async throws {
        if let item = item as? HelloFSItem {
            logger.debug("closeItem: \(item.name)")
        } else {
            logger.debug("closeItem: \(item)")
        }
    }
}

// MARK: - FSVolume.XattrOperations
extension HelloFSVolume: FSVolume.XattrOperations {

    func xattr(named name: FSFileName, of item: FSItem) async throws -> Data {
        logger.debug("xattr: \(item) - \(name.string ?? "NA")")
        
        if let item = item as? HelloFSItem {
            return item.xattrs[name] ?? Data()
        } else {
            return Data()
        }
    }
    
    func setXattr(named name: FSFileName, to value: Data?, on item: FSItem, policy: FSVolume.SetXattrPolicy) async throws {
        logger.debug("setXattr: \(item)")
        
        if let item = item as? HelloFSItem {
            item.xattrs[name] = value
        }
    }
    
    func xattrs(of item: FSItem) async throws -> [FSFileName] {
        logger.debug("xattrs: \(item)")
        
        if let item = item as? HelloFSItem {
            return Array(item.xattrs.keys)
        } else {
            return []
        }
    }
}

// MARK: - FSVolume.ReadWriteOperations
extension HelloFSVolume: FSVolume.ReadWriteOperations {

    func read(from item: FSItem, at offset: off_t, length: Int, into buffer: FSMutableFileDataBuffer) async throws -> Int {
        logger.debug("read: \(item) at \(offset)")
        
        var bytesRead = 0
        
        if let item = item as? HelloFSItem, 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 {
        logger.debug("write: \(item) at \(offset)")
        
        if let item = item as? HelloFSItem {
            item.data = contents
            item.attributes.size = UInt64(contents.count)
            item.attributes.allocSize = UInt64(contents.count)
        }
        
        return contents.count
    }
}

Changes:

  • Add FSVolume.PathConfOperations protocol
  • Add FSVolume.Operations protocol with all required methods
  • Add FSVolume.OpenCloseOperations protocol
  • Add FSVolume.XattrOperations protocol
  • Add FSVolume.ReadWriteOperations protocol
  • Change all methods to async throws syntax
  • Add options: FSTaskOptions parameter where required
  • Implement proper error handling with fs_errorForPOSIXError

4. FSItem Implementation

HelloFSItem.swift (Complete Implementation):

import Foundation
import FSKit

final class HelloFSItem: 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 = HelloFSItem.getNextID()
    
    var attributes = FSItem.Attributes()
    var xattrs: [FSFileName: Data] = [:]
    var data: Data?
    
    private(set) var children: [FSFileName: HelloFSItem] = [:]
    
    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: HelloFSItem) {
        children[item.name] = item
    }
    
    func removeItem(_ item: HelloFSItem) {
        children[item.name] = nil
    }
}

5. Constants.swift

import Foundation

enum Constants {
    
    static let containerIdentifier: UUID = UUID(uuidString: "8E055EB2-12FD-4EB8-A315-C082CBCFBDD3")!
    static let volumeIdentifier: UUID = UUID(uuidString: "CDCB994E-677C-482B-B1D2-E7BC1E07546E")!
}

6. Info.plist Configuration

<?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>HelloFS</string>
		
		<key>FSPersonalities</key>
		<dict>
			<key>HelloFSExtensionPersonality</key>
			<dict>
				<key>FSName</key>
				<string>HelloFS</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>

7. Entitlements Configuration

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

8. Main App (for Extension Management)

HelloFSApp.swift:

import SwiftUI

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

ContentView.swift:

import SwiftUI

struct ContentView: View {
    
    @State
    private var viewModel = ViewModel()
    
    var body: some View {
        VStack {
            Text("HelloFS Extension Manager")
                .font(.title)
            
            List {
                ForEach(viewModel.modules, id: \.self) { module in
                    VStack(alignment: .leading) {
                        Text(module.bundleIdentifier)
                            .bold()
                        Text(module.url.absoluteString)
                            .font(.caption)
                        Text("Enabled: \(module.isEnabled ? "Yes" : "No")")
                            .font(.caption)
                    }
                }
            }
        }
    }
}

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

9. Build Configuration

Xcode Project Setup:

  1. Create macOS App Target (HelloFSApp)

    • Platform: macOS Sequoia 15.4+
    • Language: Swift 5.7+
    • User Interface: SwiftUI
    • Add HelloFSApp.swift, ContentView.swift, ViewModel.swift
  2. Create File System Extension Target (HelloFSExtension)

    • Platform: macOS Sequoia 15.4+
    • Language: Swift 5.7+
    • Add HelloFSExtension.swift, HelloFS.swift, HelloFSVolume.swift, HelloFSItem.swift, Constants.swift
  3. Configure Extension Info.plist

    • Add EXAppExtensionAttributes dictionary
    • Configure FSKit settings
  4. Configure Entitlements

    • Enable App Sandbox
    • Enable FSKit module capability
  5. Build & Run

    • Build the app target
    • Run the app
    • Enable extension in System Settings
    • Test mounting

10. Testing Commands

# Create dummy block device
mkfile -n 100m hellofs_dummy

# Mount as block device
hdiutil attach -imagekey diskimage-class=CRawDiskImage -nomount hellofs_dummy
# Output: /dev/disk2

# Create mount point
mkdir /tmp/HelloFS

# Mount filesystem
mount -F -t HelloFS disk2 /tmp/HelloFS

# Test filesystem
ls /tmp/HelloFS
touch /tmp/HelloFS/test.txt
echo "Hello World" > /tmp/HelloFS/test.txt
cat /tmp/HelloFS/test.txt

# Unmount
umount /tmp/HelloFS

Last Updated: 2026-06-11
Status: Complete Template Ready for Implementation
Next Steps: Apply these templates to fix HelloFS compilation errors