{"slug": "how-i-vibe-coded-a-macos-driver-to-rescue-my-drobo-5d-from", "title": "How I vibe-coded a macOS driver to rescue my Drobo 5D from", "summary": "A developer used Anthropic's Claude Code to reverse-engineer and write a macOS DriverKit driver for the Drobo 5D, rescuing data from the device after the original kext stopped working. The driver, which implements vendor-specific SCSI commands over Thunderbolt, was built by extracting the command set from the old kext and using AI to generate the IOKit glue and a Rust userspace daemon.", "body_md": "# How I vibe-coded a macOS driver to rescue my Drobo 5D from\n\n[Claude Code](/en/tags/claude%20code/)write the IOKit glue while I handled the hardware spec reverse-engineering.\n\n## The problem space\n\nDrobo speaks a custom SCSI-over-Thunderbolt protocol. The old kext implemented a user-client that exposed `/dev/drobo*`\n\ncharacter devices, then a userspace daemon translated block requests into vendor-specific CDBs. Without the kext, the Thunderbolt controller enumerates but the SCSI target never appears in `diskutil list`\n\n.\n\n``` bash\n$ ioreg -l -p IOService | grep -i drobo\n    |   |   | +-o IOThunderboltDevice  <class IOThunderboltDevice, id 0x1000003b5, registered, matched, active, busy 0 (0 ms), retain 7>\n    |   |   |   | +-o DROBO 5D  <class IOThunderboltDevice, id 0x1000003b6, registered, matched, active, busy 0 (0 ms), retain 6>\n```\n\nDevice shows up in IORegistry but no block storage node gets created.\n\n## Step 1: Extract the command set from the old kext\n\n```\n# Dump the binary for string analysis\notool -v -s __TEXT __cstring /Library/Extensions/DroboDriver.kext/Contents/MacOS/DroboDriver > strings.txt\n\n# Find vendor-specific opcodes\ngrep -i \"0x[0-9a-f]\\{2\\}\" strings.txt | head -30\n```\n\n[Claude](/en/tags/claude/) parsed the output and identified the command table: `0xC1`\n\n= get array status, `0xC2`\n\n= read capacity, `0xC3`\n\n= read blocks, `0xC4`\n\n= write blocks, `0xC5`\n\n= SMART passthrough. All wrapped in a 16-byte header with little-endian LBA and block count.\n\n## Step 2: DriverKit skeleton with AI assist\n\nI prompted for a minimal `IOUserClient`\n\nsubclass that vends a `IOUserClient`\n\ninterface:\n\n``` python\n// DroboUserClient.swift\nimport DriverKit\nimport IOKit\n\nclass DroboUserClient: IOUserClient {\n    private var device: DroboDevice?\n    private var commandQueue: DispatchQueue\n    \n    override func initWithTask(_ owningTask: task_t,\n                               securityToken: UInt64,\n                               type: UInt32,\n                               properties: OSDictionary?) -> kern_return_t {\n        let ret = super.initWithTask(owningTask, securityToken: type, properties: properties)\n        guard ret == KERN_SUCCESS else { return ret }\n        commandQueue = DispatchQueue(label: \"com.drobo.driver.command\")\n        return KERN_SUCCESS\n    }\n    \n    override func clientClose() {\n        device?.close()\n        device = nil\n        super.clientClose()\n    }\n    \n    // Async command submission via external method\n    override func externalMethodAsync(_ selector: UInt32,\n                                      _ arguments: IOExternalMethodArguments,\n                                      _ completion: IOAsyncCallback,\n                                      _ refcon: UnsafeMutableRawPointer?) -> kern_return_t {\n        // Claude generated the switch statement mapping selectors to CDB builders\n    }\n}\n```\n\nThe AI got the `IOExternalMethodDispatch`\n\ntable 90% right on first try. I only had to fix the memory descriptor mapping for scatter-gather I/O.\n\n## Step 3: Userspace daemon in Rust\n\nSwift is fine for the kernel boundary but I wanted zero-cost abstractions for the RAID reconstruction logic. The daemon speaks `io_connect_method_structureI_structureO`\n\nto the user client:\n\n```\n// drobo-daemon/src/main.rs\nuse io_kit_sys::{io_connect_t, IOConnectCallStructMethod};\nuse std::os::raw::c_void;\n\nconst K_DROBO_CMD_READ: u32 = 0xC3;\nconst K_DROBO_CMD_WRITE: u32 = 0xC4;\n\nfn submit_cdb(conn: io_connect_t, cdb: &[u8; 16], data: &mut [u8]) -> kern_return_t {\n    let mut input = DroboCommandInput { cdb: *cdb, data_len: data.len() as u32 };\n    let mut output = DroboCommandOutput { status: 0, residue: 0 };\n    let mut out_size = std::mem::size_of::<DroboCommandOutput>();\n    \n    unsafe {\n        IOConnectCallStructMethod(\n            conn,\n            K_DROBO_CMD_READ,\n            &input as *const _ as *const c_void,\n            std::mem::size_of::<DroboCommandInput>(),\n            &mut output as *mut _ as *mut c_void,\n            &mut out_size,\n        )\n    }\n}\n```\n\n## Step 4: Rebuild the virtual block device\n\nThe daemon presents a NBD-compatible socket so `nbdkit`\n\ncan expose `/dev/nbd0`\n\nto the OS:\n\n```\n# Build and load\nxcodebuild -scheme DroboDriver -configuration Release\nsudo kmutil load -p ./build/Release/DroboDriver.dext\n\n# Start daemon\ncargo build --release\nsudo ./target/release/drobo-daemon --socket /var/run/drobo.sock\n\n# Expose as block device\nnbdkit -U - --filter=readonly file /var/run/drobo.sock\n```\n\nNow `diskutil list`\n\nshows the array, `fsck_apfs -y /dev/disk4`\n\nrepairs the filesystem, and `mount_apfs /dev/disk4s1 /Volumes/Drobo`\n\nbrings 16 TB back online.\n\n## Gotchas that burned hours\n\n**Thunderbolt power management**: The controller drops link after 30 s idle. Fixed by sending a`0xC1`\n\nheartbeat every 15 s from the daemon.**Big-endian LBA in CDB vs little-endian in SCSI spec**: Drobo firmware expects BE but the SCSI layer assumes LE. Byte-swap in the user client.** DriverKit entitlements**: Need`com.apple.developer.driverkit.transport.iokit`\n\nand`com.apple.developer.driverkit.userclient-access`\n\nin the`.entitlements`\n\nfile, otherwise`IOServiceOpen`\n\nreturns`kIOReturnNotPermitted`\n\n.\n\n## Current status\n\n- Read/write throughput: 1.1 GB/s sustained (Thunderbolt 2 limit)\n- RAID-5 rebuild verified after simulated drive pull\n- SMART passthrough works —\n`smartctl -a /dev/disk4`\n\nshows per-drive health - Time Machine backs up to it nightly\n\nThe driver is ~1,200 lines of Swift + 800 lines of Rust. Claude wrote ~70% of the boilerplate; I did the protocol logic and hardware bring-up. If you have orphaned Thunderbolt storage, the pattern generalizes: extract the CDB set, wrap it in a minimal user client, push complexity to userspace.\n\n[Next Claude Code's concise output mode drops token usage by 40% in my →](/en/threads/7063/)", "url": "https://wpnews.pro/news/how-i-vibe-coded-a-macos-driver-to-rescue-my-drobo-5d-from", "canonical_source": "https://promptcube3.com/en/threads/7095/", "published_at": "2026-08-20 20:57:04+00:00", "updated_at": "2026-08-20 21:14:42.207028+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence"], "entities": ["Anthropic", "Claude Code", "Drobo 5D", "DriverKit", "IOKit", "Rust"], "alternates": {"html": "https://wpnews.pro/news/how-i-vibe-coded-a-macos-driver-to-rescue-my-drobo-5d-from", "markdown": "https://wpnews.pro/news/how-i-vibe-coded-a-macos-driver-to-rescue-my-drobo-5d-from.md", "text": "https://wpnews.pro/news/how-i-vibe-coded-a-macos-driver-to-rescue-my-drobo-5d-from.txt", "jsonld": "https://wpnews.pro/news/how-i-vibe-coded-a-macos-driver-to-rescue-my-drobo-5d-from.jsonld"}}