Skip to content
Open
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
71 changes: 45 additions & 26 deletions src/uu/cp/src/cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use uucore::fsxattr::{copy_xattrs, copy_xattrs_skip_selinux};
use uucore::translate;

use clap::{Arg, ArgAction, ArgMatches, Command, builder::ValueParser, value_parser};
#[cfg(not(target_os = "wasi"))]
use filetime::FileTime;
use indicatif::{ProgressBar, ProgressStyle};
#[cfg(unix)]
Expand Down Expand Up @@ -896,7 +897,12 @@ impl Attributes {
#[cfg(unix)]
ownership: Preserve::Yes { required: true },
mode: Preserve::Yes { required: true },
// WASI: filetime panics in from_last_{access,modification}_time,
// so timestamps cannot be preserved. Mark as optional so -a works.
#[cfg(not(target_os = "wasi"))]
timestamps: Preserve::Yes { required: true },
#[cfg(target_os = "wasi")]
timestamps: Preserve::Yes { required: false },
context: {
#[cfg(feature = "feat_selinux")]
{
Expand Down Expand Up @@ -1335,9 +1341,9 @@ fn parse_path_args(
/// Check if an error is ENOTSUP/EOPNOTSUPP (operation not supported).
/// This is used to suppress xattr errors on filesystems that don't support them.
fn is_enotsup_error(error: &CpError) -> bool {
#[cfg(unix)]
#[cfg(any(unix, target_os = "wasi"))]
const EOPNOTSUPP: i32 = libc::EOPNOTSUPP;
#[cfg(not(unix))]
#[cfg(not(any(unix, target_os = "wasi")))]
const EOPNOTSUPP: i32 = 95;

match error {
Expand Down Expand Up @@ -1837,15 +1843,24 @@ pub(crate) fn copy_attributes(
})?;

handle_preserve(attributes.timestamps, || -> CopyResult<()> {
let atime = FileTime::from_last_access_time(&source_metadata);
let mtime = FileTime::from_last_modification_time(&source_metadata);
if dest.is_symlink() {
filetime::set_symlink_file_times(dest, atime, mtime)?;
} else {
filetime::set_file_times(dest, atime, mtime)?;
}
// filetime's WASI backend panics in from_last_{access,modification}_time,
// so return ENOTSUP. handle_preserve silently suppresses ENOTSUP for
// optional preservation (-a) and reports it for required (--preserve=timestamps).
#[cfg(target_os = "wasi")]
return Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP).into());

Ok(())
#[cfg(not(target_os = "wasi"))]
{
let atime = FileTime::from_last_access_time(&source_metadata);
let mtime = FileTime::from_last_modification_time(&source_metadata);
if dest.is_symlink() {
filetime::set_symlink_file_times(dest, atime, mtime)?;
} else {
filetime::set_file_times(dest, atime, mtime)?;
}

Ok(())
}
})?;

#[cfg(all(feature = "selinux", any(target_os = "linux", target_os = "android")))]
Expand Down Expand Up @@ -1896,19 +1911,9 @@ pub(crate) fn copy_attributes(
fn symlink_file(
source: &Path,
dest: &Path,
#[cfg(not(target_os = "wasi"))] symlinked_files: &mut HashSet<FileInformation>,
#[cfg(target_os = "wasi")] _symlinked_files: &mut HashSet<FileInformation>,
symlinked_files: &mut HashSet<FileInformation>,
) -> CopyResult<()> {
#[cfg(target_os = "wasi")]
{
Err(CpError::IoErrContext(
io::Error::new(io::ErrorKind::Unsupported, "symlinks not supported"),
translate!("cp-error-cannot-create-symlink",
"dest" => get_filename(dest).unwrap_or("?").quote(),
"source" => get_filename(source).unwrap_or("?").quote()),
))
}
#[cfg(not(any(windows, target_os = "wasi")))]
#[cfg(unix)]
{
std::os::unix::fs::symlink(source, dest).map_err(|e| {
CpError::IoErrContext(
Expand All @@ -1930,13 +1935,27 @@ fn symlink_file(
)
})?;
}
#[cfg(not(target_os = "wasi"))]
#[cfg(target_os = "wasi")]
{
if let Ok(file_info) = FileInformation::from_path(dest, false) {
symlinked_files.insert(file_info);
use std::ffi::CString;
use std::os::wasi::ffi::OsStrExt;
let src_c = CString::new(source.as_os_str().as_bytes())
.map_err(|e| CpError::Error(e.to_string()))?;
let dst_c =
CString::new(dest.as_os_str().as_bytes()).map_err(|e| CpError::Error(e.to_string()))?;
if unsafe { libc::symlink(src_c.as_ptr(), dst_c.as_ptr()) } != 0 {
return Err(CpError::IoErrContext(
io::Error::last_os_error(),
translate!("cp-error-cannot-create-symlink",
"dest" => get_filename(dest).unwrap_or("?").quote(),
"source" => get_filename(source).unwrap_or("?").quote()),
));
}
Ok(())
}
if let Ok(file_info) = FileInformation::from_path(dest, false) {
symlinked_files.insert(file_info);
}
Ok(())
}

fn context_for(src: &Path, dest: &Path) -> String {
Expand Down
4 changes: 3 additions & 1 deletion src/uu/env/src/native_int_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
// this conversion needs to be done only once in the beginning and at the end.

use std::ffi::OsString;
#[cfg(not(target_os = "windows"))]
#[cfg(unix)]
use std::os::unix::ffi::{OsStrExt, OsStringExt};
#[cfg(target_os = "wasi")]
use std::os::wasi::ffi::{OsStrExt, OsStringExt};
#[cfg(target_os = "windows")]
use std::os::windows::prelude::*;
use std::{borrow::Cow, ffi::OsStr};
Expand Down
2 changes: 1 addition & 1 deletion src/uu/mktemp/src/mktemp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ fn make_temp_dir(dir: &Path, prefix: &str, rand: usize, suffix: &str) -> UResult
// The directory is created with these permission at creation time, using mkdir(3) syscall.
// This is not relevant on Windows systems. See: https://docs.rs/tempfile/latest/tempfile/#security
// `fs` is not imported on Windows anyways.
#[cfg(not(windows))]
#[cfg(unix)]
builder.permissions(fs::Permissions::from_mode(0o700));

match builder.tempdir_in(dir) {
Expand Down
4 changes: 3 additions & 1 deletion src/uu/sort/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ compare = { workspace = true }
itertools = { workspace = true }
memchr = { workspace = true }
rand = { workspace = true }
rayon = { workspace = true }
self_cell = { workspace = true }
tempfile = { workspace = true }
thiserror = { workspace = true }
Expand All @@ -47,6 +46,9 @@ uucore = { workspace = true, features = [
fluent = { workspace = true }
foldhash = { workspace = true }

[target.'cfg(not(all(target_os = "wasi", not(target_feature = "atomics"))))'.dependencies]
rayon = { workspace = true }

[target.'cfg(not(any(target_os = "redox", target_os = "wasi")))'.dependencies]
ctrlc = { workspace = true }

Expand Down
15 changes: 15 additions & 0 deletions src/uu/sort/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
fn main() {
// Set a short alias for the WASI-without-threads configuration so that
// source files can use `#[cfg(wasi_no_threads)]` instead of the verbose
// `#[cfg(all(target_os = "wasi", not(target_feature = "atomics")))]`.
println!("cargo::rustc-check-cfg=cfg(wasi_no_threads)");

let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
let has_atomics = std::env::var("CARGO_CFG_TARGET_FEATURE")
.map(|f| f.split(',').any(|feat| feat == "atomics"))
.unwrap_or(false);

if target_os == "wasi" && !has_atomics {
println!("cargo::rustc-cfg=wasi_no_threads");
}
}
130 changes: 111 additions & 19 deletions src/uu/sort/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,11 @@ use crate::{
compare_by, open,
};
use itertools::Itertools;
use std::{
cmp::Ordering,
ffi::OsStr,
io::Read,
iter,
sync::mpsc::{Receiver, SyncSender, sync_channel},
thread,
};
#[cfg(not(wasi_no_threads))]
use std::sync::mpsc::{Receiver, SyncSender, sync_channel};
#[cfg(not(wasi_no_threads))]
use std::thread;
use std::{cmp::Ordering, ffi::OsStr, io::Read, iter};
use uucore::error::UResult;

/// Check if the file at `path` is ordered.
Expand All @@ -28,36 +25,50 @@ use uucore::error::UResult;
/// The code we should exit with.
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.
Ordering::Less
} else {
// Otherwise, the line previous line must compare _less or equal_ to the next one.
Ordering::Equal
};
let file = open(path)?;
let chunk_size = if settings.buffer_size < 100 * 1024 {
settings.buffer_size
} else {
100 * 1024
};

#[cfg(not(wasi_no_threads))]
{
check_threaded(path, settings, max_allowed_cmp, file, chunk_size)
}
#[cfg(wasi_no_threads)]
{
check_sync(path, settings, max_allowed_cmp, file, chunk_size)
}
}

#[cfg(not(wasi_no_threads))]
fn check_threaded(
path: &OsStr,
settings: &GlobalSettings,
max_allowed_cmp: Ordering,
file: Box<dyn Read + Send>,
chunk_size: usize,
) -> UResult<()> {
let (recycled_sender, recycled_receiver) = sync_channel(2);
let (loaded_sender, loaded_receiver) = sync_channel(2);
thread::spawn({
let settings = settings.clone();
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(chunk_size));
}

let mut prev_chunk: Option<Chunk> = None;
let mut line_idx = 0;
for chunk in loaded_receiver {
line_idx += 1;
if let Some(prev_chunk) = prev_chunk.take() {
// 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();

Expand Down Expand Up @@ -99,6 +110,7 @@ pub fn check(path: &OsStr, settings: &GlobalSettings) -> UResult<()> {
}

/// The function running on the reader thread.
#[cfg(not(wasi_no_threads))]
fn reader(
mut file: Box<dyn Read + Send>,
receiver: &Receiver<RecycledChunk>,
Expand All @@ -123,3 +135,83 @@ fn reader(
}
Ok(())
}

/// Synchronous check for targets without thread support.
#[cfg(wasi_no_threads)]
fn check_sync(
path: &OsStr,
settings: &GlobalSettings,
max_allowed_cmp: Ordering,
mut file: Box<dyn Read + Send>,
chunk_size: usize,
) -> UResult<()> {
let separator = settings.line_ending.into();
let mut carry_over = vec![];
let mut prev_chunk: Option<Chunk> = None;
let mut spare_recycled: Option<RecycledChunk> = None;
let mut line_idx = 0;

loop {
let recycled = spare_recycled
.take()
.unwrap_or_else(|| RecycledChunk::new(chunk_size));

let (chunk, should_continue) = chunks::read_to_chunk(
recycled,
None,
&mut carry_over,
&mut file,
&mut iter::empty(),
separator,
settings,
)?;

let Some(chunk) = chunk else {
break;
};

line_idx += 1;
if let Some(prev) = prev_chunk.take() {
let prev_last = prev.lines().last().unwrap();
let new_first = chunk.lines().first().unwrap();

if compare_by(
prev_last,
new_first,
settings,
prev.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());
}
spare_recycled = Some(prev.recycle());
}

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);

if !should_continue {
break;
}
}
Ok(())
}
Loading
Loading