Skip to content

macOS ARM support for f3probe#279

Open
mgarciaisaia wants to merge 10 commits into
AltraMayor:masterfrom
mgarciaisaia:f3probe-macos
Open

macOS ARM support for f3probe#279
mgarciaisaia wants to merge 10 commits into
AltraMayor:masterfrom
mgarciaisaia:f3probe-macos

Conversation

@mgarciaisaia
Copy link
Copy Markdown

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

mgarciaisaia and others added 10 commits May 31, 2026 04:27
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.
@mgarciaisaia
Copy link
Copy Markdown
Author

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.

@AltraMayor
Copy link
Copy Markdown
Owner

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 f3probe available on macOS. Since version 10 removed the need for f3probe to reset the drive under test, it has lowered the hurdles to porting f3probe to other platforms. And your PR highlights the remaining hurdles.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants