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
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.
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>
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).
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.
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.
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.
An unknown conv=/iflag=/oflag= flag was met with a help line about
separating flags with commas, when the commas were fine and the flag
name was not. Each now labels the span and lists what it accepts.
An invalid oflag= flag was reported as an input flag, and the rest read
like debug output: "Unrecognized conv=CONV -> zap" where GNU says
"invalid conversion: 'zap'". The messages now match GNU byte for byte,
usage hint included.
A size past u64 saturated to u64::MAX instead of failing, so count= and
skip= took a bogus value: dd copied the whole input and exited 0 where
GNU rejects the operand.
to_table_short_file built the table with lines.len() / columns rows, so a
page whose line count is not a multiple of the column count lost the
remainder. `seq 9 | pr -2` dropped the ninth line, and a page holding fewer
lines than it has columns came out empty.
Spread the lines the way GNU pr does instead: every column takes the ceiling
of what is still unplaced over the number of columns left to fill, which
leaves nothing over and keeps the longer columns on the left.
Two things followed from that. The short-file branch was tested before the
across-mode branch, so `pr -a` read a partly filled page downwards, and the
last row of such a page needs its line terminator without a trailing column
separator.
GNU interprets the format as it prints, so `stat -c '%d%.3' f` writes the
device number before failing; we parsed the whole format up front and
printed nothing at all. Keep the tokens parsed before the bad directive
and raise the error from the printing loop instead, flushing stdout so the
caret report still follows the output it belongs to.
Fixes#14101
GNU heads the two forms with opposite gids: `groups=` with the effective
gid, `-G` with the real gid then the effective one. Both were derived
from the same `gid` local, so only one could be right.
Only observable when the real and effective gids differ, e.g. under
`setpriv --regid=A --egid=B`. `-r` still does not affect `-G`.
--random-source exists so a shuffle can be reproduced from a file, but
salt_from_random_source hashed whatever it managed to read and never
checked the length. An empty file, or any file under 16 bytes, produced
a salt the file did not supply and sort shuffled with it, exit 0.
GNU wants the same 128 bits and reports "end of file" with exit 2 below
that, whatever the size of the input. shuf already stops on a short
source; sort was the one that did not.