# FSKit HelloFS Fix Template **Based on**: KhaosT/FSKitSample **Purpose**: Direct code templates for fixing HelloFS compilation errors --- ## 1. Entry Point Fix ### Current HelloFS (WRONG): ```swift @main class HelloFSExtension: FSUnaryFileSystemExtension { static var shared: HelloFSExtension! override init() { HelloFSExtension.shared = self super.init() } var fileSystem: FSUnaryFileSystem { HelloFS() } } ``` ### Fixed HelloFS (CORRECT): ```swift import Foundation import FSKit @main struct HelloFSExtension: UnaryFileSystemExtension { var fileSystem: FSUnaryFileSystem & FSUnaryFileSystemOperations { HelloFS() } } ``` **Changes**: - `class` → `struct` - `FSUnaryFileSystemExtension` → `UnaryFileSystemExtension` - Add `FSUnaryFileSystemOperations` to return type - Remove `static var shared` and `override init()` --- ## 2. Filesystem Class Fix ### Current HelloFS (WRONG): ```swift 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): ```swift 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: FSUnaryFileSystemExtension` → `final 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): ```swift 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): ```swift 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): ```swift 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(×pec, 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 ```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 EXAppExtensionAttributes EXExtensionPointIdentifier com.apple.fskit.fsmodule FSShortName HelloFS FSPersonalities HelloFSExtensionPersonality FSName HelloFS FSfileObjectsAreCaseSensitive FSSupportsBlockResources FSSupportsGenericURLResources FSSupportsPathURLs FSSupportsServerURLs FSRequiresSecurityScopedPathURLResources FSMediaTypes FSActivateOptionSyntax shortOptions g:m:o:u: FSCheckOptionSyntax shortOptions nqy FSFormatOptionSyntax shortOptions v ``` --- ## 7. Entitlements Configuration ```xml com.apple.security.app-sandbox com.apple.developer.fskit.fsmodule ``` --- ## 8. Main App (for Extension Management) ### HelloFSApp.swift: ```swift import SwiftUI @main struct HelloFSApp: App { var body: some Scene { WindowGroup { ContentView() } } } ``` ### ContentView.swift: ```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: ```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 ```bash # 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