Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions src/uu/split/src/split.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1148,22 +1148,28 @@ fn n_chunks_by_line(
// empty files in place(s) of skipped chunk(s)
let num_line_bytes = bytes.len() as u64;
num_bytes_written += num_line_bytes;
let mut skipped = -1;
let first_chunk_number = chunk_number;
// Cap at the last chunk to avoid an infinite loop when trailing chunks are
// zero-sized, and keep excess input from indexing past out_files.
while chunk_number < num_chunks && num_bytes_should_be_written <= num_bytes_written {
num_bytes_should_be_written +=
chunk_size_base + (chunk_size_reminder > chunk_number) as u64;
let chunk_size = chunk_size_base + (chunk_size_reminder > chunk_number) as u64;
if chunk_size == 0 {
// Every remaining chunk is zero-sized as well, so all of them
// would be skipped one at a time, which takes prohibitively
// long for a huge number of chunks. Jump to the last one.
chunk_number = num_chunks;
break;
}
num_bytes_should_be_written += chunk_size;
chunk_number += 1;
skipped += 1;
}

// If a chunk was skipped and `elide_empty_files` flag is set,
// roll chunk_number back to preserve sequential continuity
// of file names for files written to,
// except for Kth chunk of N mode
if settings.elide_empty_files && skipped > 0 && kth_chunk.is_none() {
chunk_number -= skipped as u64;
if settings.elide_empty_files && kth_chunk.is_none() {
chunk_number = chunk_number.min(first_chunk_number + 1);
}
if kth_chunk.is_some_and(|k| chunk_number > k) {
break;
Expand Down
14 changes: 14 additions & 0 deletions tests/by-util/test_split.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,20 @@ fn test_number_by_lines_fewer_bytes_than_chunks_elide() {
assert!(!at.plus("xac").exists());
}

/// A huge chunk count must not overflow the skipped-chunk counter.
///
/// With far more chunks than input bytes, all of the trailing chunks are
/// zero-sized and get skipped in one go. Counting them used to overflow and
/// panic in a build with overflow checks enabled.
#[test]
fn test_number_by_lines_kth_huge_number_of_chunks() {
let (at, mut ucmd) = at_and_ucmd!();
at.write("in", "a");
ucmd.args(&["-n", "l/1/9999999999", "in"])
.succeeds()
.stdout_only("a");
}

#[test]
#[cfg(unix)]
fn test_number_by_lines_kth_dev_null() {
Expand Down
Loading