macOS ARM support for f3probe#279
Conversation
Add a Darwin implementation of the libdevs.c block-device backend so f3probe
builds and runs natively on arm64 macOS. libprobe.c (the validated algorithm)
is byte-for-byte unchanged; all changes are in the device backend and the build.
Key finding that shaped the port: f3probe uses create_block_device(.., RT_NONE)
and libprobe.c never calls dev_reset() — so the Linux USBDEVFS_RESET is NOT used
by f3probe (it is an f3brew feature, out of scope). f3probe defeats caches with
unbuffered raw I/O + overwhelm_cache, so no IOKit/USB reset is needed.
Darwin backend (guarded by #ifdef __APPLE__ / #ifdef __linux__):
- bdev_open: open /dev/rdiskN + fcntl(F_NOCACHE) instead of O_DIRECT
- size: DKIOCGETBLOCKCOUNT x DKIOCGETBLOCKSIZE instead of BLKGETSIZE64/BLKSSZGET
- write flush: F_FULLFSYNC + DKIOCSYNCHRONIZECACHE instead of fsync/FADV_DONTNEED
- create_block_device: parse/validate the disk path (reject slices),
diskutil unmountDisk the whole disk, RT_NONE only
- all libudev/USBDEVFS machinery wrapped in #ifdef __linux__
Makefile: drop -ludev on non-Linux; on macOS build only f3probe among the extra
tools; resolve argp via `brew --prefix argp-standalone`.
Also adds spike.c (Phase-1 cache-defeat de-risking test), PORT-LOG.md (audit
trail), and README-macOS.md (build/usage + validation runbook).
IMPORTANT: this was authored on a Linux container with no compiler and no
hardware. It is NOT compiled and NOT validated against real cards. f3probe is a
fraud-detection tool; its verdicts must not be trusted until the Phase 1 spike
and Phase 4 correctness battery in PORT-LOG.md / README-macOS.md pass on a Mac.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The spike wrote pattern B to the LAST announced block, but on a wraparound fake the last block rarely aliases onto block 0 (aliasing is modulo the real size), and limbo-type fakes don't corrupt low blocks at all — so the test could report "no aliasing" on a genuinely fake card and make the cross-check inconclusive. Add --real-size=BYTES: write B at the first announced block past the real capacity (the wrap boundary), which physical-aliases onto block 0 on a clean wraparound fake. Default unchanged (last block) but now clearly labelled as possibly-not-aliasing. Also clarify the VERDICT and the physical cross-check text (power-cycle the card; node may change on reinsert). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…NCHRONIZECACHE
Phase-1 spike run on macOS showed fcntl(fd, F_FULLFSYNC) failing with ENOTTY
("inappropriate ioctl for device") on /dev/rdiskN: F_FULLFSYNC applies to
regular files, not raw device nodes. The write itself succeeded; only the flush
aborted. f3probe's bdev_write_blocks would have failed identically.
Flush the drive's write cache with DKIOCSYNCHRONIZECACHE instead (the analogue
of Linux fsync on a block device). The raw node is opened with F_NOCACHE, so the
OS buffer cache is already bypassed. Tolerate ENOTTY/ENOTSUP (readers that don't
implement SYNCHRONIZE CACHE) with a one-time warning and surface other errno.
Also a positive signal: the raw write to block 0 worked, so the buffer
page-alignment concern did not materialise on this reader.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Spike (post-flush-fix) on the known-limbo card: block 0 in-process == dd after reseat (A); limbo block #249856 written with B read back as zeros in-process and zeros via dd after power-cycle — the discarded write never returned from cache. No cache lied on this reader. Recipe confirmed: rdisk + F_NOCACHE + DKIOCSYNCHRONIZECACHE, no USB reset. Definitive proof (f3probe + overwhelm_cache matching ground truth) still pending. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
f3probe reported the limbo card as "damaged / 0 blocks" in 0.5ms: its first write (at the last announced block, offset ~2TB) failed and the non-verbose probe hid the errno behind a silenced callback. Add a Darwin diagnostic on the raw read/write error paths printing offset, block range, errno, and the buffer's alignment vs pagesize and blocksize, to distinguish an alignment bug (EINVAL + unaligned buffer) from the device/USB stack rejecting a high-LBA access (EIO/ENXIO at a huge offset). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
getpagesize() is a legacy BSD call hidden when _POSIX_C_SOURCE is defined (libdevs.c sets it), causing -Wimplicit-function-declaration -> error on macOS. sysconf(_SC_PAGESIZE) is core POSIX and always declared. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
macOS f3probe verdict is identical to Linux ground truth: limbo, usable 122 MB / 249856 blocks, last good 249855, module 2^41, announced 1.95 TB. macOS found cache size 0 vs Linux 512 MB (verdict unaffected; consistent with the spike showing no cache lie on this reader). Caveat recorded: the immediately-prior run returned "damaged/0 blocks" from the same I/O code (first write at the ~2TB last block failed twice). Non-determinism to characterize in Phase 5; direction is safe (intermittent damaged, not a false good) but unquantified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5/5 f3probe runs on the limbo card returned identical limbo/122MB; the earlier one-off "damaged" did not recur (safe-direction transient at the extreme LBA). Update the README banner from "UNVALIDATED" to the accurate scope: validated on one limbo card + one reader against Linux ground truth, deterministic, physical read confirmed; NOT proven for genuine cards, other archetypes, other readers, or 4K media. Fill in the results table and honest limits statement. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Under sudo (uid 0) an EACCES on /dev/rdiskN fell through to a bare "Permission denied": the only EACCES hint was gated behind getuid(), and there was no read-only/write-protect detection at all. A locked SD card (physical lock switch) thus produced a confusing error even though the cause is mundane. - Add darwin_media_is_write_protected() (O_RDONLY open + DKIOCISWRITABLE, #ifdef-guarded, true only when definitely locked) and darwin_warn_write_protected(). - EACCES now branches: write-protected -> lock-switch hint; non-root -> existing run-as-root hint; root -> new message naming write-protect + Full Disk Access (root bypasses classic UNIX DAC, so those are the real causes). Proactive post-open check catches bridges that open RW yet reject writes. The err() path restores the saved open_errno. - Document as a PORT-LOG field finding and a README-macOS troubleshooting note. Not yet built on macOS; verify lock ON -> clear message, lock OFF -> probe proceeds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
They may be interesting to someone checking the port, but they don't make sense in the upstream repo if merged.
|
I will probably not be able to maintain this code - I even already returned the faulty SD card, so I can't reproduce issues anymore. I totally understand this not getting merge - I mostly wanted to put it out there so folks in #8 may benefit from it. It worked for me. |
|
Hi @mgarciaisaia, Thank you for posting this PR. As you've educately guessed, I won't merge it. But your PR is a step towards eventually making |
|
Hi @mgarciaisaia, Can you help by testing pull request #278? That pull request is unrelated to this one, but I don´t have a Mac to test, and the user who opened issue #277 hasn't responded. |
This PR makes f3probe compile in macOS running ARM.
It was entirely vibe-coded with Claude 4.8 at xhigh effort, and I was able to verify with 2 SD cards (a legitimate 128GB one, and a fake, limbo 2TB-that's-actually-122MB one), comparing against f3probe runnign in an Ubuntu 24.04 VM running in VirtualBox in this same computer.
I don't know if it works on an Intel macOS.
See #8