Skip to content

Fix Windows device size truncation and backup boot sector mismatch - #1679

Merged
tdewey-rpi merged 2 commits into
raspberrypi:mainfrom
unraid:fix/windows-device-size-and-backup-bpb
Aug 5, 2026
Merged

Fix Windows device size truncation and backup boot sector mismatch#1679
tdewey-rpi merged 2 commits into
raspberrypi:mainfrom
unraid:fix/windows-device-size-and-backup-bpb

Conversation

@elibosley

Copy link
Copy Markdown
Contributor

Two independent bugs in the format path, both found while investigating why a USB stick carrying residue from another OS could still be seen by Windows as GPT after a fresh MBR had been written. They are unrelated to each other and can be split into separate PRs if you would prefer — happy to do that.

1. GetSize() under-reports devices on Windows

WindowsFileOperations::GetSize() falls back to IOCTL_DISK_GET_DRIVE_GEOMETRY for devices and multiplies out the reported CHS geometry. That geometry is synthetic, and its product is rounded down to a whole cylinder, so the size comes back short.

Measured on a 28.7 GB SanDisk USB stick:

sectors bytes
actual device 60,125,184 30,784,094,208
reported by geometry 60,115,230 30,778,997,760
short by 9,954 5,096,448 (4.86 MiB)

The reported figure is exactly 3742 cylinders × 255 heads × 63 sectors, i.e. the true size truncated to the last whole cylinder.

The failure mode is quiet: the size is returned as a success, so callers have no way to know it is wrong. Anything positioned relative to the end of the device lands ~5 MiB early, inside the gap, rather than at the true end — which is what led me here, since a structure in the device's final sector was never being overwritten.

IOCTL_DISK_GET_LENGTH_INFO returns the exact byte length and is the documented call for this. This PR tries it first and keeps the geometry path as a fallback, which now logs when it is used.

Worth flagging for review: this changes the device size used for MBR and FAT32 sizing on every Windows write, not only for end-relative work. On affected devices the partition will now extend to the true end rather than stopping at a cylinder boundary. I believe that is the intended behaviour, but it is a real change to the format path and you may want to weigh it.

2. Backup boot sector records a different BPB_HiddSec than the primary

WriteBootSector() derives hidden_sectors from offset_sectors, which is where that copy is being written rather than where the volume starts. The backup copy at partition_start + 6 therefore records a value 6 higher than the primary.

fsck.fat reports it on every check of a card formatted by the tool:

There are differences between boot sector and its backup.
This is mostly harmless. Differences: (offset:original/backup)
  28:00/06

Offset 28 is BPB_HiddSec; 00/06 is the low byte of 8192 vs 8198 for a partition starting at sector 8192.

It is cosmetic — fsck repairs it in place — but it prompts the user on each boot of a system that fsck's its boot partition. Fixed by passing the partition start separately from the write offset so both copies agree.

Testing

Both changes compile and the resulting builds run. Fix 2 was observed directly: the fsck.fat output above came from booting a stick written by the tool, and the arithmetic matches the two values exactly. Fix 1 was diagnosed from instrumented logging on a real device, with the numbers in the table above.

To be straightforward about the limits of my testing: I verified that the bugs are real and precisely measured, but I have not demonstrated a user-visible symptom being cured end-to-end by fix 1 — the drive I measured it on wrote successfully despite the truncated size. So I would treat the size correction as fixing a demonstrated defect rather than as a confirmed fix for any particular report.

GetSize() falls back to IOCTL_DISK_GET_DRIVE_GEOMETRY for devices and
multiplies out the reported CHS geometry. That geometry is synthetic and its
product is rounded down to a whole cylinder, so the result under-reports the
device.

Measured on a 28.7 GB USB stick: the device is 60125184 sectors, but geometry
reports 3742 cylinders * 255 heads * 63 sectors = 60115230 sectors -- 9954
sectors, or 4.86 MiB, short.

Anything positioned relative to the end of the device is then placed inside
that gap instead of at the true end, and callers cannot tell because the size
is returned as a success.

IOCTL_DISK_GET_LENGTH_INFO returns the exact byte length. Geometry is kept as
a fallback and now logs when it is used.
WriteBootSector() derived hidden_sectors from offset_sectors, which is where
the copy is being written rather than where the volume starts. The backup copy
at partition_start + 6 therefore recorded a value 6 higher than the primary.

fsck.fat reports this on every check:

  There are differences between boot sector and its backup.
  Differences: (offset:original/backup)
    28:00/06

Offset 28 is BPB_HiddSec, and 00/06 is the low byte of 8192 vs 8198 for a
partition starting at sector 8192.

Pass the partition start separately from the write offset so both copies agree.
@tdewey-rpi

Copy link
Copy Markdown
Collaborator

An excellent first contribution, @elibosley:

  • Comments add clarity of reasoning
  • Clear, demonstrable user issue resolved
  • Genuinely minimal change

I'd be happy to review any further patches you care to submit.

@tdewey-rpi
tdewey-rpi merged commit 4975ebe into raspberrypi:main Aug 5, 2026
@elibosley

Copy link
Copy Markdown
Contributor Author

Thanks @tdewey-rpi! Really appreciate the feedback. I will have a couple more for you here shortly, we've been using this tool for a long time for Unraid (github.com/unraid/usb-creator-next/) and I'm finally getting around to upstreaming our work :)

@elibosley
elibosley deleted the fix/windows-device-size-and-backup-bpb branch August 5, 2026 13:43
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