Commit Graph
20496 Commits
Author SHA1 Message Date
Christoph Böhmwalder e8460fa78e dd: align the read buffer for O_DIRECT (iflag=direct)
The read scratch had no alignment guarantee beyond
`align_of::<u8>() == 1`. Block devices that enforce a strict
`dma_alignment` (e.g. `sd`, `loop`, `virtio_blk`, `nbd`, `zram`, which
all default to 511) reject O_DIRECT reads whose user buffer is below
that alignment with EINVAL.

Wrap the read scratch in `AlignedBuf`, which over-allocates the zeroed
buffer from `alloc_copy_buffer` by one 4 KiB alignment unit and slices
at the first aligned byte, so the pages still arrive zeroed from the
kernel and are not faulted in before the first read. No `unsafe`
involved.

`fill_consecutive` and `fill_blocks` now take `&mut [u8]` and report
the buffer length (read bytes for the former; reads + padding for the
latter) so the caller can slice without `Vec::truncate`. `read_helper`
returns the data slice directly: into `read_buf` for the common path,
or into a separate `Vec` scratch for `conv=block` / `conv=unblock`,
which can change the byte count and so cannot be done in place. This
also removes the truncate-and-regrow cycle that used to re-fault the
buffer after a short read.

Taken over from #12143 by Christoph Böhmwalder: rebased, storage
switched from `#[repr(align)]` pages to over-allocate-and-offset on
top of `alloc_copy_buffer`, and unit tests for `AlignedBuf` plus an
integration test reading a regular file with `iflag=direct` added.

Closes #12143.
Closes #12085.
Fixes https://bugs.launchpad.net/ubuntu/+source/rust-coreutils/+bug/2125968
2026-08-24 21:12:00 +02:00
Luan Taraschi d6049ea677 dd: stop faulting in the whole copy buffer before reading
The copy buffer was reserved and then filled with BUF_INIT_BYTE, so every
page of bs= was written before the first read. A bs= far larger than the
data being copied therefore cost its full size in resident memory and in
the time to write it, even when the input was empty.

Read::read does need an initialised slice, but zeroed pages are free:
vec![0; n] allocates through alloc_zeroed, so the pages come from the
kernel already zero and are never touched until something is read into
them. The reservation is kept ahead of it so that an unobtainable bs=
still reports an error instead of aborting.
2026-08-25 19:35:37 +02:00
Sylvestre Ledru b1837a1b80 CI: don't use the lagging server-side status filter to pick the reference run 2026-08-25 18:18:33 +02:00
Daniel Hofstetter 68e11dd990 pr: show alias "-f" in help 2026-08-25 18:12:57 +02:00
Sylvestre Ledru 5504b70454 stdbuf: explain why both platforms wait on the child
The Windows build never creates a temporary directory: build.rs enables
feat_external_libstdbuf off Unix and the DLL comes from the Cygwin
package. Saying spawn() is there for the TempDir destructor only
described the Unix half of a shared path.
2026-08-25 17:24:00 +02:00
Sylvestre Ledru f2634b4c26 stdbuf: fix the Windows build and report signalled commands
PathBuf lost its import when the exec() use was removed, and the 0700
tempdir helpers were gated on the feature alone rather than on Unix.
The external-libstdbuf path no longer creates a temp directory it
immediately throws away, and waiting on the child now maps a signal
death to 128+signal so the caller still sees what exec() would have
shown it.
2026-08-25 17:24:00 +02:00
Etienne CordonnierandCursor b6810b4664 stdbuf: link cargo issue tracking cdylib install support
Point at rust-lang/cargo#8317 as the still-open request for cargo install
to support installing shared libraries, since that's the root cause of
the /tmp fallback described in the comment above.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-25 17:24:00 +02:00
Etienne CordonnierandCursor b96a00f5c6 stdbuf: create embedded libstdbuf with mode 0600 on Unix
Open the injected shared library with an explicit mode so umask 0 cannot
leave a world-writable .so in the private temp directory.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-25 17:24:00 +02:00
Etienne CordonnierandCursor 3fb38a3820 stdbuf: create embedded tmpdir with 0700 at mkdir time
Use tempfile::Builder::permissions so the directory is never world-accessible
between tempdir() and chmod, closing a TOCTOU window on permissive umasks.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-25 17:24:00 +02:00
Sylvestre Ledru ad2c741680 stdbuf: fix tmpdir leak and world-readable permissions
Two security/correctness issues reported in #13939:

1. Leaked temporary directories: using exec() replaced the stdbuf
   process before TempDir's destructor could run, leaving one
   .tmp* directory per invocation in $TMPDIR forever.
   Fix: use spawn() + wait() so the parent process survives to drop
   the TempDir after the child exits.

2. World-readable temporary directory: the tmpdir was created with
   default permissions, making libstdbuf.so writable by any user on
   a multi-user system (privilege-escalation risk).
   Fix: call set_permissions(0o700) immediately after creation,
   bypassing the umask.

Both fixes apply only when feat_external_libstdbuf is not set
(i.e. the embedded .so path).
2026-08-25 17:24:00 +02:00
Nikola Lukovic 0688cbfc17 df: pin the Windows -i notice in the tests 2026-08-25 16:55:12 +02:00
Nikola Lukovic 1653993c6d df: split platform-specific code into platform/{unix,windows}.rs 2026-08-25 16:55:12 +02:00
mtvb 4c565eb5da Fix for Lines fuse if chunk boundary is exactly at EOF #14126 2026-08-25 16:54:46 +02:00
Daniel Hofstetter 87b61f8c61 pr: add --join-lines as long option for -J 2026-08-25 16:53:36 +02:00
Sylvestre Ledru 45446f9bc8 rm: don't report ELOOP when the operand's inode changed
A dev/ino mismatch between the lstat and the opened fd isn't necessarily
a symlink swap: a directory replaced by another directory, or an
automount that lstat didn't trigger, lands there too. Report that the
file changed instead of pointing at a symlink that doesn't exist.
2026-08-25 16:13:35 +02:00
Sylvestre Ledru c53f75a15c rm: do not follow a symlink swapped in at the -r operand
`safe_remove_dir_recursive` lstats the operand, finds a directory, then
re-resolves the same path with `DirFd::open(.., Follow)`. An operand replaced
by a symlink between the two is followed, and the recursive delete runs over
a tree the user never named.

Open `NoFollow`, and reconcile the opened fd's dev/ino against the lstat:
`O_NOFOLLOW` rejects a symlink only as the final component, and a trailing
slash ("dir/") stops it being final.
2026-08-25 16:13:35 +02:00
Daniel Hofstetter 49042676eb pr: rename "--column" to "--columns" 2026-08-25 16:12:53 +02:00
Devel 357ad44175 tee: show_err instead of writing directly in stderr and strip error 2026-08-25 15:40:07 +02:00
oech3 152cc0986c tail: remove overhead by stat 2026-08-25 15:38:57 +02:00
Sylvestre Ledru e006771dd0 util: factor the shared setup of the check-*.sh scripts into check-common.sh 2026-08-25 15:38:42 +02:00
Sylvestre Ledru 62d40de5ab chmod: use the libc fchmodat symbol for recursive mode changes
The raw syscall(SYS_fchmodat2, ...) in DirFd::chmod_at bypassed LD_PRELOAD
tools (fakeroot, fakechroot, pseudo), so a recursive mode change landed on
disk but their bookkeeping never saw it, silently and with exit status 0.
Try libc fchmodat() first and keep the raw syscall as a fallback for the
NoFollow case libc declines; glibc issues fchmodat2 from there anyway.

Add util/check-libc-interposition.sh, which runs chown -R and chmod -R
under fakeroot and compares what fakeroot reports afterwards.

Fixes #14028
2026-08-25 15:38:42 +02:00
Coro c22ee2421d printf: avoid panic on %s/%c field width above u16::MAX
write_padded padded with write!("{: <padlen$}"), which panics once the
dynamic width exceeds u16::MAX. Write the padding spaces directly as bytes so
that %s and %c field widths above that bound work, resolving the existing TODO
in the string arm. Adds a regression test.

Fixes #12593. Fixes #12900.
2026-08-25 11:01:04 +02:00
Coro 88be4e4f82 numfmt: reject a leading '+' and scientific notation to match GNU
GNU numfmt rejects a number with a leading '+' sign ("+5") as an invalid
number, and rejects scientific notation ("1e-3") as an invalid suffix in
input. Rust's i128 and f64 parsers accept both, so numfmt silently accepted
them and printed a wrong value with exit 0. Reject them in parse_number_part,
mirroring GNU's two distinct diagnostics.
2026-08-25 10:28:22 +02:00
Coro 8eec67be23 pr: make -n/--number-lines argument optional to match GNU
GNU pr treats -n/--number-lines as optional: a bare -n numbers the lines with a
5-wide, tab-separated line number. uutils required a value, so `pr -t -n` failed.
Set num_args(0..=1); the existing fallback supplies the default numbering mode.
2026-08-25 10:11:10 +02:00
Daniel Hofstetter 259fdfc30d ci: add check for empty output assertions (#14116) 2026-08-25 08:37:55 +02:00