Skip to content

Fix/posix block size - #467

Open
ftarasenko wants to merge 2 commits into
k0sproject:mainfrom
ftarasenko:fix/posix-block-size
Open

Fix/posix block size#467
ftarasenko wants to merge 2 commits into
k0sproject:mainfrom
ftarasenko:fix/posix-block-size

Conversation

@ftarasenko

Copy link
Copy Markdown

Fixes #466

PosixFile.fsBlockSize asked stat for %s where %o was meant. %s is the
size of the probed directory's own data; %o is the optimal I/O block size,
which is what the BSD %k in the same fallback already asks for.

On ext4 a directory occupies whole blocks, so %s answers 4096 and happens to
agree with %o — the reason this survived. XFS keeps a small directory inline
in the inode, so /var/lib/k0s/images holding one 15-character filename
answers 29, and a k0sctl airgap bundle upload ran as dd ... bs=29. That is
~18 million syscall pairs for a 500 MB bundle, and turns a seconds-long upload
into an hours-long one. The issue has the byte-by-byte breakdown of the 29.

Three commits' worth of behaviour in two:

Block size probe. Ask for %o, and reject an answer that is not a
plausible power-of-two block size so a future misprobe degrades to the default
instead of to something unusable. That also removes a crash: when stat exited 0
but printed something unparseable, blockSize stayed 0 and ddParams panicked
on numBytes % 0.

CopyFrom. dd's bs there is only the size of the chunks it streams stdin
into the file with, unrelated to the remote filesystem, so it now uses a fixed
1 MiB rather than the probed value — 4096 was costing 128k syscall pairs per
gigabyte even in the good case. The same line passed f.pos to seek=, which
dd counts in output blocks and not bytes, so a resumed copy wrote at
f.pos * bs; the offset is 0 for an ordinary upload, which is why it went
unnoticed. The block size now shrinks when it does not divide the offset, which
is a real if unlikely regression in throughput for an unaligned resume — an
appending write would avoid it, but oflag=append is not portable to the BSD
and macOS dd this library targets, so I left it.

WinFS has no block-size probe, so there is nothing to mirror there. No
exported API changes.

Verified: both commits build, vet and test green independently; the two new
test cases fail against the unfixed code (one by assertion, one by the panic
above). I could not run make lint — the golangci-lint I have is built against
Go 1.25 and refuses this repo's 1.27 target — so the lint job is unverified.


Written by an AI coding agent working for @ftarasenko, who hit this on their
own XFS hosts, confirmed the stat -c "%s %o" output above and reviewed the
change before it was opened.

That agent's session has no API access to this repository, so it will not see
review comments or CI results here. Follow-up is @ftarasenko's — they will
relay review feedback and push changes.


Generated by Claude Code

fsBlockSize probes the parent directory to decide what block size to hand
dd, and asked for stat's %s. That is the size of the directory's own data,
not a block size. On ext4 a directory occupies whole blocks, so %s returns
4096 and matches what %o would have said, which is why this looked correct
everywhere it was tried. XFS stores a small directory inline in the inode:
/var/lib/k0s/images holding one 15-character filename reports 29 bytes, and
the upload then ran as

    dd of=/var/lib/k0s/images/bundle.tar bs=29 seek=0 conv=notrunc

Streaming a multi-hundred-megabyte airgap bundle 29 bytes at a time takes
hours instead of seconds. Ask for %o, the optimal I/O block size, which is
what the BSD %k in the same fallback already means.

Reject an implausible answer as well, so a future misprobe degrades to the
default rather than to a block size that cannot work. That also removes a
crash: when stat exited 0 but printed something unparseable, blockSize was
left at 0 and ddParams panicked with an integer divide by zero.

Signed-off-by: Fedor Tarasenko <ftarasenko@itkey.com>
CopyFrom pipes a whole file into dd's stdin, so dd's bs is only the size of
the chunks it reads and writes; it has nothing to do with the remote file
system's block size. Taking it from fsBlockSize meant a 4096 byte block even
in the good case, which is 128k syscall pairs per gigabyte for no reason.
Use a fixed 1 MiB instead.

The same line also passed f.pos to seek, which dd counts in output blocks
rather than in bytes, so a resumed copy started writing at f.pos * bs. It
went unnoticed because the offset is 0 for an ordinary upload. Divide the
offset by the block size, and shrink the block size when it does not divide
the offset evenly.

Signed-off-by: Fedor Tarasenko <ftarasenko@itkey.com>
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.

PosixFile.fsBlockSize asks stat for %s, making dd transfers to XFS hosts crawl

1 participant