Skip to content
Draft
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
4 changes: 2 additions & 2 deletions .github/workflows/wasi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
# Tests incompatible with WASI are annotated with
# #[cfg_attr(wasi_runner, ignore)] in the test source files.
# TODO: add integration tests for these tools as WASI support is extended:
# arch b2sum cat cksum cp csplit date dir dircolors fmt join
# arch b2sum cat cksum csplit date dir dircolors fmt join
# ls md5sum mkdir mv nproc pathchk pr printenv ptx pwd readlink
# realpath rm rmdir seq sha1sum sha224sum sha256sum sha384sum
# sha512sum shred sleep sort split tail touch tsort uname uniq
Expand All @@ -69,7 +69,7 @@ jobs:
UUTESTS_WASM_RUNNER=wasmtime \
cargo test --test tests -- \
test_base32:: test_base64:: test_basenc:: test_basename:: \
test_comm:: test_cut:: test_dirname:: test_echo:: \
test_comm:: test_cp:: test_cut:: test_dirname:: test_echo:: \
test_expand:: test_factor:: test_false:: test_fold:: \
test_head:: test_link:: test_ln:: test_nl:: test_numfmt:: \
test_od:: test_paste:: test_printf:: test_shuf:: test_sum:: \
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@ unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(fuzzing)',
'cfg(target_os, values("cygwin"))',
'cfg(wasi_runner)',
'cfg(wasip2_runner)',
] }
unused_qualifications = "warn"

Expand Down
129 changes: 120 additions & 9 deletions src/uu/sort/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,88 @@ use crate::{
compare_by, open,
};
use itertools::Itertools;
use std::{cmp::Ordering, ffi::OsStr};
#[cfg(not(target_os = "wasi"))]
use std::{
cmp::Ordering,
ffi::OsStr,
io::Read,
iter,
sync::mpsc::{Receiver, SyncSender, sync_channel},
thread,
};
use uucore::error::UResult;

fn buffer_size(settings: &GlobalSettings) -> usize {
if settings.buffer_size < 100 * 1024 {
// when the buffer size is smaller than 100KiB we choose it instead of the default.
// this improves testability.
settings.buffer_size
} else {
100 * 1024
}
}

/// Given the chunks of a file (in order), find the first pair of adjacent
/// lines that violates the requested ordering and report it as a
/// [`SortError::Disorder`].
#[cfg(target_os = "wasi")]
fn check_chunks(
path: &OsStr,
settings: &GlobalSettings,
max_allowed_cmp: Ordering,
chunks: impl Iterator<Item = Chunk>,
) -> UResult<()> {
let mut prev_chunk: Option<Chunk> = None;
let mut line_idx = 0;
for chunk in chunks {
line_idx += 1;
if let Some(prev_chunk) = &prev_chunk {
// Check if the first element of the new chunk is greater than the last
// element from the previous chunk
let prev_last = prev_chunk.lines().last().unwrap();
let new_first = chunk.lines().first().unwrap();

if compare_by(
prev_last,
new_first,
settings,
prev_chunk.line_data(),
chunk.line_data(),
) > max_allowed_cmp
{
return Err(SortError::Disorder {
file: path.to_owned(),
line_number: line_idx,
line: String::from_utf8_lossy(new_first.line).into_owned(),
silent: settings.check_silent,
}
.into());
}
}

for (a, b) in chunk.lines().iter().tuple_windows() {
line_idx += 1;
if compare_by(a, b, settings, chunk.line_data(), chunk.line_data()) > max_allowed_cmp {
return Err(SortError::Disorder {
file: path.to_owned(),
line_number: line_idx,
line: String::from_utf8_lossy(b.line).into_owned(),
silent: settings.check_silent,
}
.into());
}
}

prev_chunk = Some(chunk);
}
Ok(())
}

/// Check if the file at `path` is ordered.
///
/// # Returns
///
/// The code we should exit with.
#[cfg(not(target_os = "wasi"))]
pub fn check(path: &OsStr, settings: &GlobalSettings) -> UResult<()> {
let max_allowed_cmp = if settings.unique {
// If `unique` is enabled, the previous line must compare _less_ to the next one.
Expand All @@ -42,13 +109,7 @@ pub fn check(path: &OsStr, settings: &GlobalSettings) -> UResult<()> {
move || reader(file, &recycled_receiver, &loaded_sender, &settings)
});
for _ in 0..2 {
let _ = recycled_sender.send(RecycledChunk::new(if settings.buffer_size < 100 * 1024 {
// when the buffer size is smaller than 100KiB we choose it instead of the default.
// this improves testability.
settings.buffer_size
} else {
100 * 1024
}));
let _ = recycled_sender.send(RecycledChunk::new(buffer_size(settings)));
}

let mut prev_chunk: Option<Chunk> = None;
Expand Down Expand Up @@ -114,7 +175,27 @@ pub fn check(path: &OsStr, settings: &GlobalSettings) -> UResult<()> {
result
}

/// Check if the file at `path` is ordered.
///
/// WASI has no thread support, so this reads every chunk up front on the
/// current thread instead of streaming them from a background reader thread.
///
/// # Returns
///
/// The code we should exit with.
#[cfg(target_os = "wasi")]
pub fn check(path: &OsStr, settings: &GlobalSettings) -> UResult<()> {
let max_allowed_cmp = if settings.unique {
Ordering::Less
} else {
Ordering::Equal
};
let chunks = read_all_chunks(path, settings)?;
check_chunks(path, settings, max_allowed_cmp, chunks.into_iter())
}

/// The function running on the reader thread.
#[cfg(not(target_os = "wasi"))]
fn reader(
mut file: Box<dyn Read + Send>,
receiver: &Receiver<RecycledChunk>,
Expand All @@ -139,3 +220,33 @@ fn reader(
}
Ok(())
}

/// Read every chunk of `path` up front, without any recycling or background
/// thread. Used on WASI, which has no thread support.
#[cfg(target_os = "wasi")]
fn read_all_chunks(path: &OsStr, settings: &GlobalSettings) -> UResult<Vec<Chunk>> {
let mut file = open(path)?;
let mut carry_over = vec![];
let mut chunks = Vec::new();
let (sender, receiver) = std::sync::mpsc::sync_channel(1);
loop {
let recycled = RecycledChunk::new(buffer_size(settings));
let should_continue = chunks::read(
&sender,
recycled,
None,
&mut carry_over,
&mut file,
&mut std::iter::empty(),
settings.line_ending.into(),
settings,
)?;
while let Ok(chunk) = receiver.try_recv() {
chunks.push(chunk);
}
if !should_continue {
break;
}
}
Ok(chunks)
}
7 changes: 7 additions & 0 deletions src/uu/sort/src/ext_sort/wasi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ pub fn ext_sort(
// moderately sized inputs; very large files may cause OOM.
let mut input = Vec::new();
for file in files {
// Insert the separator between files whose preceding content doesn't
// already end with one; otherwise the last line of one file would
// merge with the first line of the next, e.g. "a\nb" + "b" ->
// "a\nbb" instead of "a\nb" + '\n' + "b".
if !input.is_empty() && input.last() != Some(&separator) {
input.push(separator);
}
file?.read_to_end(&mut input)?;
}
if input.is_empty() {
Expand Down
Loading
Loading