Skip to content

macOS + exFAT: F3 produces false corruption due to fsync semantics and POSIX_FADV_DONTNEED #252

@luoqiangwei

Description

@luoqiangwei

When running F3 on macOS with exFAT-formatted devices, the tool may report systematic data corruption (typically exactly one cluster, e.g. 128 KB = 256 sectors) even on brand-new, healthy storage.

After extensive investigation, this appears to be a tool–OS–filesystem semantic mismatch, not actual media corruption.

This issue does not reproduce on:

  • APFS (macOS)

  • Linux (ext4 / exFAT)

  • Repeated runs on the same device (often disappears)


Observed Symptoms

Typical output from f3read on macOS + exFAT:

  • Exactly one missing or corrupted cluster

  • Often 128 KB (256 sectors)

  • Reported as corrupted or overwritten

  • More visible on HDDs or slower devices

Example:

Corrupted: 128.00 KB (256 sectors)

exfat fs default cluster = 128KiB (256 sectors)


Image

Root Cause Analysis

static int bdev_write_blocks(struct device *dev, const char *buf,
		uint64_t first_pos, uint64_t last_pos)
{
	struct block_device *bdev = dev_bdev(dev);
	const int block_order = dev_get_block_order(dev);
	size_t length = (last_pos - first_pos + 1) << block_order;
	off_t offset = first_pos << block_order;
	off_t off_ret = lseek(bdev->fd, offset, SEEK_SET);
	int rc;
	if (off_ret < 0)
		return - errno;
	assert(off_ret == offset);
	rc = write_all(bdev->fd, buf, length);
	if (rc)
		return rc;
	rc = fsync(bdev->fd); // <----- Not ready sync in macOS with exfat
	if (rc)
		return rc;
	return posix_fadvise(bdev->fd, 0, 0, POSIX_FADV_DONTNEED); // <----- Drop page cache but not ready sync data to storage, data lose...
}

Key point

On macOS, fsync() does NOT guarantee physical media persistence.

This is a documented semantic difference from Linux.

What F3 currently does (simplified)

write(...) fsync(fd) posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED)

What happens on macOS + exFAT

  1. write() populates the page cache

  2. fsync() flushes known dirty pages, but:

    • does not guarantee all delayed clusters are committed
  3. The last cluster (commonly one exFAT cluster = 128 KB) may still be pending

  4. POSIX_FADV_DONTNEED aggressively discards page cache

  5. On subsequent read():

    • data is reloaded from disk

    • but the last cluster may contain:

      • old data

      • zeroes

      • uninitialized content

👉 Result: exactly one missing cluster, consistently aligned to exFAT cluster size.

This is not a block alignment issuenot an ioctl issue, and not a hardware failure.


Why APFS Does Not Show This Problem

APFS uses:

  • Copy-on-write

  • Transactional block allocation + data + metadata updates

  • fsync() commits the whole transaction

Therefore, F3’s assumption:

“fsync means data is safely persistent”

happens to hold on APFS, but does not hold on exFAT.


Why This Is Not an exFAT “Bug”

exFAT:

  • Has no journaling

  • Has weak ordering guarantees

  • Relies on cooperative cache behavior

macOS’s exFAT driver prioritizes performance and allows delayed allocation.

The behavior is legal under POSIX and macOS semantics.


Why This Is Dangerous

  • Users may falsely conclude:

    • “My new SD card is defective”

    • “The device has bad sectors”

  • Especially harmful for:

    • camera media

    • large-capacity cards

    • HDD-based storage

In reality, the data may never have been written incorrectly at all.


Recommended Fixes

Option 1 (Best): Use macOS-specific full sync

On macOS, replace fsync() with:

fcntl(fd, F_FULLFSYNC)

This guarantees data reaches physical media.

Option 2: Remove POSIX_FADV_DONTNEED on macOS + exFAT

Avoid dropping page cache immediately after fsync() on macOS.

Option 3: Document the limitation clearly

At minimum, warn users:

F3 results on macOS + exFAT may be unreliable unless F_FULLFSYNC is used.


Summary

  • This is not hardware corruption

  • This is not block misalignment

  • This is a filesystem + OS semantic mismatch

  • F3’s assumptions are valid on Linux and APFS

  • They are not valid on macOS + exFAT

I’m reporting this to prevent false negatives and unnecessary device returns.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions