# 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