Merge v3-t15: shared compile cache, precompile, /bin/*.wasm and /src/sausage copies (T15)
This commit is contained in:
+8
-1
@@ -5,6 +5,9 @@ use std::path::PathBuf;
|
||||
#[derive(Parser)]
|
||||
#[command(version, about)]
|
||||
struct Cli {
|
||||
/// Shared host-user compile cache (otherwise SAUSAGE_CACHE or the XDG cache folder).
|
||||
#[arg(long, global = true)]
|
||||
cache: Option<PathBuf>,
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
@@ -29,7 +32,11 @@ enum Command {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
match Cli::parse().command {
|
||||
let cli = Cli::parse();
|
||||
if let Some(cache) = cli.cache {
|
||||
sausage::process::configure_cache(cache)?;
|
||||
}
|
||||
match cli.command {
|
||||
Command::Run { world, userland } => {
|
||||
let bundle =
|
||||
userland.or_else(|| std::env::var_os("SAUSAGE_USERLAND").map(PathBuf::from));
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
//! Shares compiled programs and single-flight compilation across worlds in one host process.
|
||||
use super::{Compiled, State, adapter, bindings};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, Mutex, OnceLock},
|
||||
};
|
||||
use tokio::sync::watch;
|
||||
type Compilation = watch::Receiver<Option<Result<Compiled, String>>>;
|
||||
use wasmtime::{
|
||||
Engine,
|
||||
component::{Component, Linker},
|
||||
};
|
||||
use wasmtime_wasi::p2::bindings::CommandPre;
|
||||
|
||||
static DIRECTORY: OnceLock<PathBuf> = OnceLock::new();
|
||||
static SHARED: Mutex<Option<Arc<Compiler>>> = Mutex::new(None);
|
||||
|
||||
/// Select the host user's cache before opening a world.
|
||||
pub fn configure(directory: PathBuf) -> anyhow::Result<()> {
|
||||
DIRECTORY
|
||||
.set(directory)
|
||||
.map_err(|_| anyhow::anyhow!("cache directory already configured"))
|
||||
}
|
||||
fn directory() -> anyhow::Result<PathBuf> {
|
||||
if let Some(dir) = DIRECTORY.get() {
|
||||
return Ok(dir.clone());
|
||||
}
|
||||
if let Some(dir) = std::env::var_os("SAUSAGE_CACHE") {
|
||||
return Ok(dir.into());
|
||||
}
|
||||
if let Some(dir) = std::env::var_os("XDG_CACHE_HOME") {
|
||||
return Ok(PathBuf::from(dir).join("sausage"));
|
||||
}
|
||||
Ok(
|
||||
PathBuf::from(std::env::var_os("HOME").ok_or_else(|| anyhow::anyhow!("HOME is not set"))?)
|
||||
.join(".cache/sausage"),
|
||||
)
|
||||
}
|
||||
pub(super) fn shared() -> anyhow::Result<Arc<Compiler>> {
|
||||
let mut shared = SHARED.lock().unwrap();
|
||||
if shared.is_none() {
|
||||
*shared = Some(Compiler::new(&directory()?)?);
|
||||
}
|
||||
Ok(shared.as_ref().unwrap().clone())
|
||||
}
|
||||
pub(super) struct Compiler {
|
||||
pub engine: Engine,
|
||||
programs: Mutex<HashMap<Vec<u8>, Compilation>>,
|
||||
#[cfg(test)]
|
||||
before_compile: Mutex<Option<Box<dyn FnOnce() + Send>>>,
|
||||
#[cfg(test)]
|
||||
compilations: std::sync::atomic::AtomicUsize,
|
||||
}
|
||||
impl Compiler {
|
||||
pub fn new(directory: &Path) -> anyhow::Result<Arc<Self>> {
|
||||
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
||||
let mut config = wasmtime::Config::new();
|
||||
config.epoch_interruption(true);
|
||||
// Keep Cranelift's default optimization: compiles are cached per content, and guests run
|
||||
// many times. OptLevel::None saved only 12.7% of compile time (T15) but slows every run.
|
||||
let mut cache = wasmtime::CacheConfig::new();
|
||||
cache.with_directory(directory);
|
||||
config.cache(Some(wasmtime::Cache::new(cache)?));
|
||||
Ok(Arc::new(Self {
|
||||
engine: Engine::new(&config)?,
|
||||
programs: Mutex::new(HashMap::new()),
|
||||
#[cfg(test)]
|
||||
before_compile: Mutex::new(None),
|
||||
#[cfg(test)]
|
||||
compilations: std::sync::atomic::AtomicUsize::new(0),
|
||||
}))
|
||||
}
|
||||
pub async fn get(self: &Arc<Self>, bytes: Vec<u8>) -> anyhow::Result<Compiled> {
|
||||
let mut result = self
|
||||
.programs
|
||||
.lock()
|
||||
.unwrap()
|
||||
.entry(bytes.clone())
|
||||
.or_insert_with(|| {
|
||||
let (tx, rx) = watch::channel(None);
|
||||
let compiler = self.clone();
|
||||
// The job owns completion even if a command or a precompile waiter is cancelled.
|
||||
tokio::spawn(async move {
|
||||
let result =
|
||||
tokio::task::spawn_blocking(move || compiler.compile(&bytes)).await;
|
||||
tx.send_replace(Some(
|
||||
result
|
||||
.map_err(anyhow::Error::from)
|
||||
.and_then(|r| r)
|
||||
.map_err(|e| format!("{e:#}")),
|
||||
));
|
||||
});
|
||||
rx
|
||||
})
|
||||
.clone();
|
||||
loop {
|
||||
if let Some(result) = result.borrow().clone() {
|
||||
return result.map_err(|e| anyhow::anyhow!(e));
|
||||
}
|
||||
result.changed().await?;
|
||||
}
|
||||
}
|
||||
fn compile(&self, bytes: &[u8]) -> anyhow::Result<Compiled> {
|
||||
#[cfg(test)]
|
||||
{
|
||||
self.compilations
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
if let Some(hook) = self.before_compile.lock().unwrap().take() {
|
||||
hook();
|
||||
}
|
||||
}
|
||||
if bytes.get(..8) == Some(b"\0asm\x01\0\0\0") {
|
||||
let module = wasmtime::Module::new(&self.engine, bytes)?;
|
||||
let mut linker = wasmtime::Linker::new(&self.engine);
|
||||
sausage_vfs::preview1::add_to_linker(&mut linker, |s: &mut State| {
|
||||
s.preview1.as_mut().unwrap()
|
||||
})?;
|
||||
Ok(Compiled::Module(linker.instantiate_pre(&module)?))
|
||||
} else {
|
||||
let component = Component::new(&self.engine, bytes)?;
|
||||
let mut linker = Linker::new(&self.engine);
|
||||
wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
|
||||
wasmtime_wasi_http::p2::add_only_http_to_linker_async(&mut linker)?;
|
||||
adapter::add_to_linker(&mut linker, |s: &mut State| adapter::View {
|
||||
adapter: &mut s.fs,
|
||||
table: &mut s.table,
|
||||
})?;
|
||||
bindings::sausage::process::host::add_to_linker::<_, wasmtime::component::HasSelf<_>>(
|
||||
&mut linker,
|
||||
|s| s,
|
||||
)?;
|
||||
Ok(Compiled::Component(CommandPre::new(
|
||||
linker.instantiate_pre(&component)?,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Compile programs in both command folders without blocking world startup.
|
||||
pub(super) fn precompile(
|
||||
compiler: Arc<Compiler>,
|
||||
root: Arc<dyn sausage_vfs::Layer>,
|
||||
) -> tokio::task::JoinHandle<anyhow::Result<usize>> {
|
||||
tokio::spawn(async move {
|
||||
let mut tasks = tokio::task::JoinSet::new();
|
||||
// Leave CPU and file bandwidth for commands during startup.
|
||||
let slots = Arc::new(tokio::sync::Semaphore::new(2));
|
||||
for folder in ["/bin", "/usr/local/bin"] {
|
||||
let entries = match root.list(folder).await {
|
||||
Ok(entries) => entries,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
for entry in entries.into_iter().filter(|entry| !entry.directory) {
|
||||
let root = root.clone();
|
||||
let compiler = compiler.clone();
|
||||
let path = format!("{folder}/{}", entry.name);
|
||||
let slots = slots.clone();
|
||||
tasks.spawn(async move {
|
||||
let _permit = slots.acquire().await?;
|
||||
let file = root
|
||||
.open(
|
||||
&path,
|
||||
sausage_vfs::Open {
|
||||
follow: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let bytes = file.read(0, file.stat().await?.size as usize).await?;
|
||||
if bytes.get(..4) != Some(b"\0asm") {
|
||||
return Ok::<_, anyhow::Error>(false);
|
||||
}
|
||||
compiler.get(bytes).await?;
|
||||
Ok(true)
|
||||
});
|
||||
}
|
||||
}
|
||||
let mut count = 0;
|
||||
while let Some(result) = tasks.join_next().await {
|
||||
if result?? {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
Ok(count)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::exec::{Command, Exec, Options};
|
||||
use std::{future::Future, sync::atomic::Ordering};
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn command_joins_precompile_even_after_waiter_cancellation() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let compiler = Compiler::new(&temp.path().join("cache")).unwrap();
|
||||
let world = crate::world::World::open(&temp.path().join("world")).unwrap();
|
||||
let bytes =
|
||||
wat::parse_str(r#"(module (memory (export "memory") 1) (func (export "_start")))"#)
|
||||
.unwrap();
|
||||
std::fs::create_dir_all(temp.path().join("world/world/bin")).unwrap();
|
||||
std::fs::write(temp.path().join("world/world/bin/program.wasm"), &bytes).unwrap();
|
||||
let (started_tx, started_rx) = tokio::sync::oneshot::channel();
|
||||
let (finish_tx, finish_rx) = std::sync::mpsc::channel();
|
||||
*compiler.before_compile.lock().unwrap() = Some(Box::new(move || {
|
||||
started_tx.send(()).unwrap();
|
||||
finish_rx.recv().unwrap();
|
||||
}));
|
||||
// Startup returns while the compiler remains blocked behind the test gate.
|
||||
let runtime = super::super::Runtime::with_compiler(world, compiler.clone()).unwrap();
|
||||
started_rx.await.unwrap();
|
||||
let background = compiler.clone();
|
||||
let precompile = tokio::spawn(async move { background.get(bytes).await });
|
||||
let mut output = |_: crate::exec::Stream, _: &[u8]| {};
|
||||
let command = runtime.exec(
|
||||
Command::Argv(vec!["/bin/program".into()]),
|
||||
Options::default(),
|
||||
&mut output,
|
||||
);
|
||||
tokio::pin!(command);
|
||||
let pending = std::future::poll_fn(|cx| {
|
||||
std::task::Poll::Ready(command.as_mut().poll(cx).is_pending())
|
||||
})
|
||||
.await;
|
||||
assert!(pending);
|
||||
assert_eq!(compiler.compilations.load(Ordering::Relaxed), 1);
|
||||
precompile.abort();
|
||||
let _ = precompile.await;
|
||||
finish_tx.send(()).unwrap();
|
||||
assert_eq!(command.await.unwrap(), 0);
|
||||
assert_eq!(compiler.compilations.load(Ordering::Relaxed), 1);
|
||||
assert!(!temp.path().join("world/host/compile-cache").exists());
|
||||
}
|
||||
}
|
||||
+60
-47
@@ -5,7 +5,7 @@ use crate::{
|
||||
};
|
||||
use sausage_vfs::{
|
||||
Mounts,
|
||||
adapter::{self, Adapter, View},
|
||||
adapter::{self, Adapter},
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@@ -22,7 +22,7 @@ use tokio::{
|
||||
};
|
||||
use wasmtime::{
|
||||
Engine, Store,
|
||||
component::{Component, Linker, Resource, ResourceTable},
|
||||
component::{Resource, ResourceTable},
|
||||
};
|
||||
use wasmtime_wasi::{
|
||||
WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView,
|
||||
@@ -45,6 +45,8 @@ mod bindings {
|
||||
});
|
||||
}
|
||||
use bindings::sausage::process::host::{Host, HostChild, Request, Stdio};
|
||||
mod cache;
|
||||
pub use cache::configure as configure_cache;
|
||||
mod services;
|
||||
mod stdio;
|
||||
pub use services::supervise;
|
||||
@@ -77,7 +79,8 @@ struct Entry {
|
||||
pub struct Runtime {
|
||||
world: World,
|
||||
engine: Engine,
|
||||
cache: Mutex<HashMap<Vec<u8>, Compiled>>,
|
||||
compiler: Arc<cache::Compiler>,
|
||||
cache: Mutex<std::collections::HashSet<Vec<u8>>>,
|
||||
entries: Mutex<HashMap<u32, Entry>>,
|
||||
next: AtomicU32,
|
||||
alive: Arc<AtomicBool>,
|
||||
@@ -138,14 +141,14 @@ impl Runtime {
|
||||
Self::new(World::open(path)?)
|
||||
}
|
||||
pub fn new(world: World) -> anyhow::Result<Arc<Self>> {
|
||||
// Deno and WASI HTTP enable different providers. Select one before HTTPS creates a client.
|
||||
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
||||
let mut config = wasmtime::Config::new();
|
||||
config.epoch_interruption(true);
|
||||
let mut cache = wasmtime::CacheConfig::new();
|
||||
cache.with_directory(world.host_dir.join("compile-cache"));
|
||||
config.cache(Some(wasmtime::Cache::new(cache)?));
|
||||
let engine = Engine::new(&config)?;
|
||||
Self::with_compiler(world, cache::shared()?)
|
||||
}
|
||||
/// Open an independent engine using an explicit shared disk cache.
|
||||
pub fn new_with_cache(world: World, directory: &Path) -> anyhow::Result<Arc<Self>> {
|
||||
Self::with_compiler(world, cache::Compiler::new(directory)?)
|
||||
}
|
||||
fn with_compiler(world: World, compiler: Arc<cache::Compiler>) -> anyhow::Result<Arc<Self>> {
|
||||
let engine = compiler.engine.clone();
|
||||
let alive = Arc::new(AtomicBool::new(true));
|
||||
let ticking = alive.clone();
|
||||
let clock = engine.clone();
|
||||
@@ -155,19 +158,57 @@ impl Runtime {
|
||||
clock.increment_epoch();
|
||||
}
|
||||
});
|
||||
Ok(Arc::new(Self {
|
||||
let runtime = Arc::new(Self {
|
||||
world,
|
||||
engine,
|
||||
alive,
|
||||
cache: Mutex::new(HashMap::new()),
|
||||
compiler,
|
||||
cache: Mutex::new(Default::default()),
|
||||
entries: Mutex::new(HashMap::new()),
|
||||
next: AtomicU32::new(1),
|
||||
}))
|
||||
});
|
||||
cache::precompile(runtime.compiler.clone(), runtime.world.root.clone());
|
||||
Ok(runtime)
|
||||
}
|
||||
/// Number of distinct program contents compiled by this runtime.
|
||||
pub fn compiled_programs(&self) -> usize {
|
||||
self.cache.lock().unwrap().len()
|
||||
}
|
||||
async fn resolve_program(
|
||||
&self,
|
||||
cwd: &str,
|
||||
name: &str,
|
||||
env: &[(String, String)],
|
||||
) -> anyhow::Result<String> {
|
||||
let paths = if name.contains('/') {
|
||||
vec![sausage_vfs::resolve(cwd, name)]
|
||||
} else {
|
||||
let path = env
|
||||
.iter()
|
||||
.find(|(key, _)| key == "PATH")
|
||||
.map(|(_, value)| value.as_str())
|
||||
.unwrap_or("/usr/local/bin:/bin");
|
||||
path.split(':')
|
||||
.map(|folder| {
|
||||
sausage_vfs::resolve(
|
||||
cwd,
|
||||
&std::path::Path::new(folder).join(name).to_string_lossy(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
for path in paths {
|
||||
for candidate in [path.clone(), format!("{path}.wasm")] {
|
||||
match self.world.root.stat(&candidate, true).await {
|
||||
Ok(stat) if !stat.directory => return Ok(candidate),
|
||||
Ok(_) => {}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
anyhow::bail!("program not found: {name}")
|
||||
}
|
||||
fn kill_command(&self, command: u32) {
|
||||
for entry in self.entries.lock().unwrap().values() {
|
||||
if entry.command == command {
|
||||
@@ -222,7 +263,9 @@ impl Runtime {
|
||||
request.cwd = sausage_vfs::resolve("/", &request.cwd);
|
||||
request.env.retain(|(name, _)| name != "PWD");
|
||||
request.env.push(("PWD".into(), request.cwd.clone()));
|
||||
let executable = sausage_vfs::resolve(&request.cwd, &request.program);
|
||||
let executable = self
|
||||
.resolve_program(&request.cwd, &request.program, &request.env)
|
||||
.await?;
|
||||
let file = self
|
||||
.world
|
||||
.root
|
||||
@@ -237,38 +280,8 @@ impl Runtime {
|
||||
let bytes = file
|
||||
.read(0, usize::try_from(file.stat().await?.size)?)
|
||||
.await?;
|
||||
let pre = {
|
||||
let mut cache = self.cache.lock().unwrap();
|
||||
if let Some(pre) = cache.get(&bytes) {
|
||||
pre.clone()
|
||||
} else {
|
||||
let pre = if bytes.get(..8) == Some(b"\0asm\x01\0\0\0") {
|
||||
let module = wasmtime::Module::new(&self.engine, &bytes)?;
|
||||
let mut linker = wasmtime::Linker::new(&self.engine);
|
||||
sausage_vfs::preview1::add_to_linker(&mut linker, |s: &mut State| {
|
||||
s.preview1.as_mut().unwrap()
|
||||
})?;
|
||||
Compiled::Module(linker.instantiate_pre(&module)?)
|
||||
} else {
|
||||
let component = Component::new(&self.engine, &bytes)?;
|
||||
let mut linker = Linker::new(&self.engine);
|
||||
wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
|
||||
// The library linker includes exit-with-code. Only Wasmtime's CLI gates it.
|
||||
wasmtime_wasi_http::p2::add_only_http_to_linker_async(&mut linker)?;
|
||||
adapter::add_to_linker(&mut linker, |s: &mut State| View {
|
||||
adapter: &mut s.fs,
|
||||
table: &mut s.table,
|
||||
})?;
|
||||
bindings::sausage::process::host::add_to_linker::<
|
||||
_,
|
||||
wasmtime::component::HasSelf<_>,
|
||||
>(&mut linker, |s| s)?;
|
||||
Compiled::Component(CommandPre::new(linker.instantiate_pre(&component)?)?)
|
||||
};
|
||||
cache.insert(bytes, pre.clone());
|
||||
pre
|
||||
}
|
||||
};
|
||||
let pre = self.compiler.get(bytes.clone()).await?;
|
||||
self.cache.lock().unwrap().insert(bytes);
|
||||
let pid = self.next.fetch_add(1, Ordering::Relaxed);
|
||||
let command = if command == 0 { pid } else { command };
|
||||
let group = match request.group {
|
||||
|
||||
+23
-9
@@ -19,17 +19,37 @@ pub struct World {
|
||||
pub processes: crate::process::Processes,
|
||||
}
|
||||
|
||||
fn copy_tree(source: &Path, destination: &Path) -> std::io::Result<()> {
|
||||
std::fs::create_dir_all(destination)?;
|
||||
for entry in std::fs::read_dir(source)? {
|
||||
let entry = entry?;
|
||||
let target = destination.join(entry.file_name());
|
||||
if entry.file_type()?.is_dir() {
|
||||
copy_tree(&entry.path(), &target)?;
|
||||
} else {
|
||||
std::fs::copy(entry.path(), target)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl World {
|
||||
/// Open (and create if missing) the world directory `dir`, including `/proc` and `/run` mounts.
|
||||
pub fn open(dir: &Path) -> anyhow::Result<Self> {
|
||||
Self::open_with_userland(dir, None)
|
||||
}
|
||||
|
||||
/// Mount an optional host bundle read-only at `/bin` (D43).
|
||||
/// Copy an optional bundle into a new world. Existing worlds retain their programs.
|
||||
pub fn open_with_userland(dir: &Path, userland: Option<&Path>) -> anyhow::Result<Self> {
|
||||
let root_dir = dir.join("world");
|
||||
let host_dir = dir.join("host");
|
||||
let fresh = !root_dir.exists();
|
||||
std::fs::create_dir_all(root_dir.join("usr/local/bin"))?;
|
||||
if fresh && let Some(bundle) = userland {
|
||||
for folder in ["bin", "src"] {
|
||||
copy_tree(&bundle.join(folder), &root_dir.join(folder))?;
|
||||
}
|
||||
}
|
||||
std::fs::create_dir_all(&host_dir)?;
|
||||
let mut mounts = Mounts::default();
|
||||
mounts.mount(
|
||||
@@ -55,17 +75,11 @@ impl World {
|
||||
}
|
||||
Ok(text.into_bytes())
|
||||
});
|
||||
if let Some(bundle) = userland {
|
||||
mounts.mount(
|
||||
"/bin",
|
||||
Arc::new(Host::new(bundle, Process::default())?),
|
||||
true,
|
||||
);
|
||||
}
|
||||
mounts.mount("/proc", Arc::new(proc), true);
|
||||
mounts.mount("/run", Arc::new(Memory::default()), false);
|
||||
let root: Arc<dyn Layer> = Arc::new(mounts);
|
||||
Ok(World {
|
||||
root: Arc::new(mounts),
|
||||
root,
|
||||
host_dir,
|
||||
processes,
|
||||
})
|
||||
|
||||
+114
-8
@@ -1,13 +1,13 @@
|
||||
//! P1 integration: a read-only bundle and real shell calls from the durable harness.
|
||||
//! P1 integration: independent bundle copies and real shell calls from the durable harness.
|
||||
use sausage::{harness::run_module, world::World};
|
||||
use sausage_vfs::Open;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn bundle_mount_and_real_shell() -> anyhow::Result<()> {
|
||||
async fn bundle_copy_and_real_shell() -> anyhow::Result<()> {
|
||||
let repo = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..");
|
||||
let bundle = repo.join("target/userland/bin");
|
||||
if !bundle.join("sh").exists() {
|
||||
let bundle = repo.join("target/userland/bundle");
|
||||
if !bundle.join("bin/sh.wasm").exists() {
|
||||
assert!(
|
||||
std::process::Command::new("nu")
|
||||
.env_remove("CARGO_TARGET_DIR")
|
||||
@@ -18,9 +18,16 @@ async fn bundle_mount_and_real_shell() -> anyhow::Result<()> {
|
||||
}
|
||||
let dir = tempfile::tempdir()?;
|
||||
let world = World::open_with_userland(dir.path(), Some(&bundle))?;
|
||||
assert!(world.root.stat("/bin/sh", true).await?.size > 0);
|
||||
assert!(world.root.stat("/bin/sh.wasm", true).await?.size > 0);
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
for entry in std::fs::read_dir(dir.path().join("world/bin"))? {
|
||||
let entry = entry?;
|
||||
assert_eq!(entry.path().extension().unwrap(), "wasm");
|
||||
assert_eq!(entry.metadata()?.nlink(), 1);
|
||||
assert!(!entry.file_type()?.is_symlink());
|
||||
}
|
||||
assert!(world.root.stat("/usr/local/bin", true).await?.directory);
|
||||
let denied = world
|
||||
let writable = world
|
||||
.root
|
||||
.open(
|
||||
"/bin/forbidden",
|
||||
@@ -31,17 +38,72 @@ async fn bundle_mount_and_real_shell() -> anyhow::Result<()> {
|
||||
},
|
||||
)
|
||||
.await;
|
||||
assert!(matches!(denied, Err(e) if e.kind() == std::io::ErrorKind::ReadOnlyFilesystem));
|
||||
assert!(writable.is_ok());
|
||||
let exec = sausage::process::exec(World {
|
||||
root: world.root.clone(),
|
||||
host_dir: world.host_dir.clone(),
|
||||
processes: world.processes.clone(),
|
||||
})?;
|
||||
use sausage::exec::{Command, Options, Stream};
|
||||
for (script, expected) in [
|
||||
("command -v ls", "/bin/ls.wasm\n"),
|
||||
("ls", "bin"),
|
||||
("git --version", "git version"),
|
||||
("printf 'a\\n' | xargs echo", "a\n"),
|
||||
("env FOO=1 printenv FOO", "1\n"),
|
||||
] {
|
||||
let mut out = Vec::new();
|
||||
let code = exec
|
||||
.exec(
|
||||
Command::Shell(script.into()),
|
||||
Options::default(),
|
||||
&mut |stream, bytes| {
|
||||
if matches!(stream, Stream::Stdout) {
|
||||
out.extend_from_slice(bytes);
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(code, 0, "{script}");
|
||||
assert!(String::from_utf8(out)?.contains(expected), "{script}");
|
||||
}
|
||||
let mut history = Vec::new();
|
||||
assert_eq!(
|
||||
exec.exec(
|
||||
Command::Shell("cd /src/sausage && git log --oneline".into()),
|
||||
Options::default(),
|
||||
&mut |stream, bytes| {
|
||||
if matches!(stream, Stream::Stdout) {
|
||||
history.extend_from_slice(bytes);
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
0,
|
||||
);
|
||||
let history = String::from_utf8(history)?;
|
||||
assert_eq!(history.lines().count(), 1);
|
||||
assert!(history.contains("Bundle source "));
|
||||
let mut status = Vec::new();
|
||||
assert_eq!(
|
||||
exec.exec(
|
||||
Command::Shell("cd /src/sausage && git status --short".into()),
|
||||
Options::default(),
|
||||
&mut |stream, bytes| {
|
||||
if matches!(stream, Stream::Stdout) {
|
||||
status.extend_from_slice(bytes);
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
0,
|
||||
);
|
||||
assert!(status.is_empty(), "{}", String::from_utf8_lossy(&status));
|
||||
let source = format!(
|
||||
"globalThis.testP1 = true;\n{}",
|
||||
include_str!("../js/test-bundle.js")
|
||||
);
|
||||
run_module(world, exec, &source).await?;
|
||||
run_module(world, exec.clone(), &source).await?;
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(dir.path().join("world/test.txt"))?,
|
||||
"after\n"
|
||||
@@ -59,6 +121,50 @@ async fn bundle_mount_and_real_shell() -> anyhow::Result<()> {
|
||||
let actual = std::fs::read_to_string(spills[0].path())?;
|
||||
let expected: String = (1..=10000).map(|n| format!("{n}\n")).collect();
|
||||
assert_eq!(actual, expected);
|
||||
let source_cat = std::fs::read(bundle.join("bin/cat.wasm"))?;
|
||||
let head = std::fs::read(dir.path().join("world/bin/head.wasm"))?;
|
||||
let mut out = Vec::new();
|
||||
assert_eq!(
|
||||
exec.exec(
|
||||
Command::Shell(
|
||||
"cp /bin/true.wasm /bin/cat.wasm; cat /test.txt; head -n 1 /test.txt".into()
|
||||
),
|
||||
Options::default(),
|
||||
&mut |stream, bytes| {
|
||||
if matches!(stream, Stream::Stdout) {
|
||||
out.extend_from_slice(bytes);
|
||||
}
|
||||
}
|
||||
)
|
||||
.await?,
|
||||
0
|
||||
);
|
||||
assert_eq!(out, b"after\n");
|
||||
assert_eq!(std::fs::read(bundle.join("bin/cat.wasm"))?, source_cat);
|
||||
assert_eq!(std::fs::read(dir.path().join("world/bin/head.wasm"))?, head);
|
||||
assert_eq!(
|
||||
exec.exec(
|
||||
Command::Shell("cp /bin/true.wasm /usr/local/bin/head.wasm; head /test.txt".into()),
|
||||
Options::default(),
|
||||
&mut |_, bytes| assert!(bytes.is_empty())
|
||||
)
|
||||
.await?,
|
||||
0
|
||||
);
|
||||
let changed = std::fs::read(dir.path().join("world/bin/cat.wasm"))?;
|
||||
let reopened = World::open_with_userland(
|
||||
dir.path(),
|
||||
Some(std::path::Path::new("/nonexistent-bundle")),
|
||||
)?;
|
||||
assert_eq!(
|
||||
reopened
|
||||
.root
|
||||
.open("/bin/cat.wasm", Open::default())
|
||||
.await?
|
||||
.read(0, changed.len())
|
||||
.await?,
|
||||
changed
|
||||
);
|
||||
// An unbundled world remains usable by process tests and file-only callers.
|
||||
let unbundled = tempfile::tempdir()?;
|
||||
let world = World::open(unbundled.path())?;
|
||||
|
||||
+56
-11
@@ -9,6 +9,39 @@ use std::{
|
||||
sync::{Arc, Once},
|
||||
time::Duration,
|
||||
};
|
||||
#[test]
|
||||
fn world_copies_programs_and_nested_source_only_once() -> anyhow::Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let bundle = temp.path().join("bundle");
|
||||
std::fs::create_dir_all(bundle.join("bin"))?;
|
||||
std::fs::create_dir_all(bundle.join("src/sausage/.git"))?;
|
||||
std::fs::write(bundle.join("bin/probe.wasm"), b"program")?;
|
||||
std::fs::write(
|
||||
bundle.join("src/sausage/.git/HEAD"),
|
||||
b"ref: refs/heads/main\n",
|
||||
)?;
|
||||
std::fs::write(bundle.join("src/sausage/README.md"), b"source")?;
|
||||
let path = temp.path().join("world");
|
||||
let _world = World::open_with_userland(&path, Some(&bundle))?;
|
||||
assert_eq!(
|
||||
std::fs::read(path.join("world/bin/probe.wasm"))?,
|
||||
b"program"
|
||||
);
|
||||
assert_eq!(
|
||||
std::fs::read(path.join("world/src/sausage/README.md"))?,
|
||||
b"source"
|
||||
);
|
||||
assert!(path.join("world/src/sausage/.git/HEAD").is_file());
|
||||
std::fs::write(path.join("world/src/sausage/README.md"), b"agent patch")?;
|
||||
let _world = World::open_with_userland(&path, Some(Path::new("/missing-bundle")))?;
|
||||
assert_eq!(
|
||||
std::fs::read(path.join("world/src/sausage/README.md"))?,
|
||||
b"agent patch"
|
||||
);
|
||||
assert!(!path.join("host/compile-cache").exists());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod features {
|
||||
//! Acceptance coverage for preview1, identity, inherited IO, services, HTTP, and bundle aliases.
|
||||
use super::*;
|
||||
@@ -260,28 +293,32 @@ mod features {
|
||||
empty(&processes).await;
|
||||
}
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn relative_aliases_share_compilation() {
|
||||
async fn independent_copies_share_compilation() {
|
||||
let _serial = SERIAL.lock().await;
|
||||
let (temp, runtime, _) = world();
|
||||
let bin = temp.path().join("world/bin");
|
||||
for name in ["true", "echo"] {
|
||||
std::os::unix::fs::symlink("cat", bin.join(name)).unwrap();
|
||||
for name in ["read-a.wasm", "read-b.wasm"] {
|
||||
std::fs::copy(bin.join("cat.wasm"), bin.join(name)).unwrap();
|
||||
}
|
||||
assert_eq!(
|
||||
run(&runtime, Command::Argv(vec!["/bin/true".into()]), "/")
|
||||
.await
|
||||
.0,
|
||||
run(
|
||||
&runtime,
|
||||
Command::Argv(vec!["/bin/read-a".into(), "/f".into()]),
|
||||
"/"
|
||||
)
|
||||
.await
|
||||
.0,
|
||||
0
|
||||
);
|
||||
assert_eq!(runtime.compiled_programs(), 1);
|
||||
let (code, out, _) = run(
|
||||
&runtime,
|
||||
Command::Argv(vec!["/bin/echo".into(), "shared".into()]),
|
||||
Command::Argv(vec!["/bin/read-b".into(), "/f".into()]),
|
||||
"/",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(code, 0);
|
||||
assert_eq!(out, b"shared\n");
|
||||
assert_eq!(out, b"root\n");
|
||||
assert_eq!(runtime.compiled_programs(), 1);
|
||||
}
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@@ -464,13 +501,17 @@ fn world() -> (tempfile::TempDir, Arc<Runtime>, sausage::process::Processes) {
|
||||
let world = World::open(temp.path()).unwrap();
|
||||
let cache = Path::new(env!("CARGO_TARGET_TMPDIR")).join("process-host/compile-cache");
|
||||
std::fs::create_dir_all(&cache).unwrap();
|
||||
std::os::unix::fs::symlink(cache, world.host_dir.join("compile-cache")).unwrap();
|
||||
|
||||
let processes = world.processes.clone();
|
||||
fixture(&temp.path().join("world"));
|
||||
let artifacts = repo().join("userland/target/wasm32-wasip2/release");
|
||||
let bin = temp.path().join("world/bin");
|
||||
for tool in ["cat", "tr", "ls", "wc", "yes", "head", "sleep"] {
|
||||
std::fs::copy(artifacts.join("sausage-coreutils.wasm"), bin.join(tool)).unwrap();
|
||||
std::fs::copy(
|
||||
artifacts.join(format!("{tool}.wasm")),
|
||||
bin.join(format!("{tool}.wasm")),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
for tool in ["ps", "kill"] {
|
||||
std::fs::copy(artifacts.join("sausage-procps.wasm"), bin.join(tool)).unwrap();
|
||||
@@ -491,7 +532,11 @@ fn world() -> (tempfile::TempDir, Arc<Runtime>, sausage::process::Processes) {
|
||||
bin.join("git"),
|
||||
)
|
||||
.unwrap();
|
||||
(temp, Runtime::new(world).unwrap(), processes)
|
||||
(
|
||||
temp,
|
||||
Runtime::new_with_cache(world, &cache).unwrap(),
|
||||
processes,
|
||||
)
|
||||
}
|
||||
async fn run(runtime: &Arc<Runtime>, command: Command, cwd: &str) -> (i32, Vec<u8>, Vec<u8>) {
|
||||
let (mut stdout, mut stderr) = (Vec::new(), Vec::new());
|
||||
|
||||
@@ -18,6 +18,12 @@ def main [] {
|
||||
if (^git -C $source rev-parse HEAD | str trim) != $revision { error make {msg: 'unexpected brush revision'} }
|
||||
let core = ($source | path join brush-core)
|
||||
copy-if-changed ($here | path join process.rs) ($core | path join src sys wasm process.rs)
|
||||
copy-if-changed ($here | path join lookup.rs) ($core | path join src sys wasm sausage-lookup.rs)
|
||||
let fs = ($core | path join src sys wasm fs.rs)
|
||||
let fs_text = (open --raw $fs)
|
||||
if not ($fs_text | str contains 'include!("sausage-lookup.rs")') {
|
||||
$fs_text + "\ninclude!(\"sausage-lookup.rs\");\n" | save --force $fs
|
||||
}
|
||||
mkdir ($core | path join sausage-process)
|
||||
let wit = ($here | path join .. .. host wit | path expand)
|
||||
copy-if-changed ($wit | path join process.wit) ($core | path join sausage-process process.wit)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/// Resolve ordinary files before the WASI suffix in each PATH folder.
|
||||
pub fn resolve_executable(path: std::path::PathBuf) -> Option<std::path::PathBuf> {
|
||||
if path.is_file() {
|
||||
return Some(path);
|
||||
}
|
||||
let mut name = path.into_os_string();
|
||||
name.push(".wasm");
|
||||
let path = std::path::PathBuf::from(name);
|
||||
path.is_file().then_some(path)
|
||||
}
|
||||
pub(crate) fn resolve_executable_path(
|
||||
path: std::path::PathBuf,
|
||||
working_dir: &std::path::Path,
|
||||
) -> std::path::PathBuf {
|
||||
let absolute = working_dir.join(&path);
|
||||
if absolute.is_file() {
|
||||
return path;
|
||||
}
|
||||
let mut name = absolute.into_os_string();
|
||||
name.push(".wasm");
|
||||
if std::path::PathBuf::from(name).is_file() {
|
||||
let mut name = path.into_os_string();
|
||||
name.push(".wasm");
|
||||
name.into()
|
||||
} else {
|
||||
path
|
||||
}
|
||||
}
|
||||
+31
-7
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env nu
|
||||
# Build every workspace CLI and assemble the read-only /bin bundle.
|
||||
# Build independent commands and a committed source snapshot of their repository.
|
||||
def main [destination?: path] {
|
||||
let here = $env.FILE_PWD
|
||||
let destination = ($destination | default ($here | path join .. target userland bin) | path expand)
|
||||
let destination = ($destination | default ($here | path join .. target userland bundle) | path expand)
|
||||
let bin = ($destination | path join bin)
|
||||
let object = ($here | path join cwd-init init.o)
|
||||
let sources = [($here | path join cwd-init init.rs) ($here | path join cwd-init exit.wit) ($here | path join cwd-init build.nu)]
|
||||
if not ($object | path exists) or ($sources | any {|source| (ls $source | get modified.0) > (ls $object | get modified.0) }) {
|
||||
@@ -14,23 +15,46 @@ def main [destination?: path] {
|
||||
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'userland build failed'} }
|
||||
^nu ($here | path join brush build.nu)
|
||||
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'brush build failed'} }
|
||||
mkdir $destination
|
||||
mkdir $bin
|
||||
for package in ($metadata.packages | where {|p| $p.id in $metadata.workspace_members }) {
|
||||
let tools = ($package.manifest_path | path dirname | path join tools.txt)
|
||||
for binary in ($package.targets | where {|t| 'bin' in $t.kind }) {
|
||||
let source = ($metadata.target_directory | path join wasm32-wasip2 release $'($binary.name).wasm')
|
||||
let names = if ($tools | path exists) { open --raw $tools | lines | where {|s| ($s | str trim) != '' } } else { [$binary.name] }
|
||||
for name in $names {
|
||||
^ln -f $source ($destination | path join $name)
|
||||
if $env.LAST_EXIT_CODE != 0 { error make {msg: $'bundle link failed: ($name)'} }
|
||||
^cp --remove-destination $source ($bin | path join $'($name).wasm')
|
||||
if $env.LAST_EXIT_CODE != 0 { error make {msg: $'bundle copy failed: ($name)'} }
|
||||
}
|
||||
}
|
||||
}
|
||||
let brush_metadata = (^cargo metadata --manifest-path ($here | path join brush source Cargo.toml) --no-deps --format-version 1 | from json)
|
||||
let brush = ($brush_metadata.target_directory | path join wasm32-wasip2 release brush.wasm)
|
||||
for name in [sh bash] {
|
||||
^ln -f $brush ($destination | path join $name)
|
||||
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'brush link failed'} }
|
||||
^cp --remove-destination $brush ($bin | path join $'($name).wasm')
|
||||
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'brush copy failed'} }
|
||||
}
|
||||
let repo = ($here | path join .. | path expand)
|
||||
let revision = (^git -C $repo rev-parse HEAD | str trim)
|
||||
let snapshot = ($destination | path join src sausage)
|
||||
if ($snapshot | path exists) { rm --recursive --force $snapshot }
|
||||
mkdir $snapshot
|
||||
let archive = ($destination | path join source.tar)
|
||||
^git -C $repo archive --output $archive HEAD host vfs userland Cargo.toml Cargo.lock flake.nix flake.lock README.md AGENTS.md .gitignore
|
||||
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'source archive failed'} }
|
||||
^tar -xf $archive -C $snapshot
|
||||
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'source extraction failed'} }
|
||||
rm $archive
|
||||
^git -C $snapshot init --quiet --initial-branch=main
|
||||
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'source git init failed'} }
|
||||
^git -C $snapshot add host vfs userland Cargo.toml Cargo.lock flake.nix flake.lock README.md AGENTS.md .gitignore
|
||||
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'source git add failed'} }
|
||||
with-env {
|
||||
GIT_AUTHOR_NAME: 'Sausage', GIT_AUTHOR_EMAIL: 'sausage@localhost',
|
||||
GIT_COMMITTER_NAME: 'Sausage', GIT_COMMITTER_EMAIL: 'sausage@localhost',
|
||||
GIT_AUTHOR_DATE: '2000-01-01T00:00:00+0000', GIT_COMMITTER_DATE: '2000-01-01T00:00:00+0000'
|
||||
} {
|
||||
^git -C $snapshot commit --no-gpg-sign --quiet -m $'Bundle source ($revision)'
|
||||
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'source git commit failed'} }
|
||||
}
|
||||
print $destination
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the arch command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("arch").expect("utility supplies localization");
|
||||
std::process::exit(uu_arch::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the b2sum command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("b2sum").expect("utility supplies localization");
|
||||
std::process::exit(uu_b2sum::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the base32 command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("base32").expect("utility supplies localization");
|
||||
std::process::exit(uu_base32::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the base64 command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("base64").expect("utility supplies localization");
|
||||
std::process::exit(uu_base64::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the basename command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("basename").expect("utility supplies localization");
|
||||
std::process::exit(uu_basename::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the basenc command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("basenc").expect("utility supplies localization");
|
||||
std::process::exit(uu_basenc::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the cat command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("cat").expect("utility supplies localization");
|
||||
std::process::exit(uu_cat::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the cksum command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("cksum").expect("utility supplies localization");
|
||||
std::process::exit(uu_cksum::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the comm command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("comm").expect("utility supplies localization");
|
||||
std::process::exit(uu_comm::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the cp command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("cp").expect("utility supplies localization");
|
||||
std::process::exit(uu_cp::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the csplit command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("csplit").expect("utility supplies localization");
|
||||
std::process::exit(uu_csplit::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the cut command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("cut").expect("utility supplies localization");
|
||||
std::process::exit(uu_cut::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the date command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("date").expect("utility supplies localization");
|
||||
std::process::exit(uu_date::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the dd command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("dd").expect("utility supplies localization");
|
||||
std::process::exit(uu_dd::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the dir command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("ls").expect("utility supplies localization");
|
||||
std::process::exit(uu_dir::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the dircolors command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("dircolors").expect("utility supplies localization");
|
||||
std::process::exit(uu_dircolors::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the dirname command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("dirname").expect("utility supplies localization");
|
||||
std::process::exit(uu_dirname::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the echo command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("echo").expect("utility supplies localization");
|
||||
std::process::exit(uu_echo::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the expand command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("expand").expect("utility supplies localization");
|
||||
std::process::exit(uu_expand::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the expr command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("expr").expect("utility supplies localization");
|
||||
std::process::exit(uu_expr::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the factor command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("factor").expect("utility supplies localization");
|
||||
std::process::exit(uu_factor::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the false command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("false").expect("utility supplies localization");
|
||||
std::process::exit(uu_false::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the fmt command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("fmt").expect("utility supplies localization");
|
||||
std::process::exit(uu_fmt::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the fold command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("fold").expect("utility supplies localization");
|
||||
std::process::exit(uu_fold::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the head command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("head").expect("utility supplies localization");
|
||||
std::process::exit(uu_head::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the hostid command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("hostid").expect("utility supplies localization");
|
||||
std::process::exit(uu_hostid::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the join command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("join").expect("utility supplies localization");
|
||||
std::process::exit(uu_join::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the link command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("link").expect("utility supplies localization");
|
||||
std::process::exit(uu_link::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the ln command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("ln").expect("utility supplies localization");
|
||||
std::process::exit(uu_ln::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the ls command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("ls").expect("utility supplies localization");
|
||||
std::process::exit(uu_ls::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the md5sum command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("md5sum").expect("utility supplies localization");
|
||||
std::process::exit(uu_md5sum::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the mkdir command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("mkdir").expect("utility supplies localization");
|
||||
std::process::exit(uu_mkdir::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the mktemp command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("mktemp").expect("utility supplies localization");
|
||||
std::process::exit(uu_mktemp::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the mv command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("mv").expect("utility supplies localization");
|
||||
std::process::exit(uu_mv::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the nice command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("nice").expect("utility supplies localization");
|
||||
std::process::exit(uu_nice::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the nl command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("nl").expect("utility supplies localization");
|
||||
std::process::exit(uu_nl::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the nproc command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("nproc").expect("utility supplies localization");
|
||||
std::process::exit(uu_nproc::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the numfmt command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("numfmt").expect("utility supplies localization");
|
||||
std::process::exit(uu_numfmt::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the od command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("od").expect("utility supplies localization");
|
||||
std::process::exit(uu_od::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the paste command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("paste").expect("utility supplies localization");
|
||||
std::process::exit(uu_paste::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the pathchk command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("pathchk").expect("utility supplies localization");
|
||||
std::process::exit(uu_pathchk::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the pr command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("pr").expect("utility supplies localization");
|
||||
std::process::exit(uu_pr::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the printenv command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("printenv").expect("utility supplies localization");
|
||||
std::process::exit(uu_printenv::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the printf command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("printf").expect("utility supplies localization");
|
||||
std::process::exit(uu_printf::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the ptx command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("ptx").expect("utility supplies localization");
|
||||
std::process::exit(uu_ptx::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the pwd command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("pwd").expect("utility supplies localization");
|
||||
std::process::exit(uu_pwd::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the readlink command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("readlink").expect("utility supplies localization");
|
||||
std::process::exit(uu_readlink::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the realpath command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("realpath").expect("utility supplies localization");
|
||||
std::process::exit(uu_realpath::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the rm command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("rm").expect("utility supplies localization");
|
||||
std::process::exit(uu_rm::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the rmdir command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("rmdir").expect("utility supplies localization");
|
||||
std::process::exit(uu_rmdir::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the seq command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("seq").expect("utility supplies localization");
|
||||
std::process::exit(uu_seq::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the sha1sum command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("sha1sum").expect("utility supplies localization");
|
||||
std::process::exit(uu_sha1sum::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the sha224sum command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("sha224sum").expect("utility supplies localization");
|
||||
std::process::exit(uu_sha224sum::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the sha256sum command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("sha256sum").expect("utility supplies localization");
|
||||
std::process::exit(uu_sha256sum::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the sha384sum command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("sha384sum").expect("utility supplies localization");
|
||||
std::process::exit(uu_sha384sum::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the sha512sum command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("sha512sum").expect("utility supplies localization");
|
||||
std::process::exit(uu_sha512sum::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the shred command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("shred").expect("utility supplies localization");
|
||||
std::process::exit(uu_shred::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the shuf command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("shuf").expect("utility supplies localization");
|
||||
std::process::exit(uu_shuf::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the sleep command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("sleep").expect("utility supplies localization");
|
||||
std::process::exit(uu_sleep::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the sort command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("sort").expect("utility supplies localization");
|
||||
std::process::exit(uu_sort::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the split command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("split").expect("utility supplies localization");
|
||||
std::process::exit(uu_split::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the sum command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("sum").expect("utility supplies localization");
|
||||
std::process::exit(uu_sum::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the tail command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("tail").expect("utility supplies localization");
|
||||
std::process::exit(uu_tail::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the tee command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("tee").expect("utility supplies localization");
|
||||
std::process::exit(uu_tee::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the test command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("test").expect("utility supplies localization");
|
||||
std::process::exit(uu_test::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the touch command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("touch").expect("utility supplies localization");
|
||||
std::process::exit(uu_touch::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the tr command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("tr").expect("utility supplies localization");
|
||||
std::process::exit(uu_tr::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the true command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("true").expect("utility supplies localization");
|
||||
std::process::exit(uu_true::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the truncate command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("truncate").expect("utility supplies localization");
|
||||
std::process::exit(uu_truncate::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the tsort command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("tsort").expect("utility supplies localization");
|
||||
std::process::exit(uu_tsort::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the tty command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("tty").expect("utility supplies localization");
|
||||
std::process::exit(uu_tty::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the uname command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("uname").expect("utility supplies localization");
|
||||
std::process::exit(uu_uname::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the unexpand command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("unexpand").expect("utility supplies localization");
|
||||
std::process::exit(uu_unexpand::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the uniq command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("uniq").expect("utility supplies localization");
|
||||
std::process::exit(uu_uniq::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the unlink command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("unlink").expect("utility supplies localization");
|
||||
std::process::exit(uu_unlink::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the vdir command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("ls").expect("utility supplies localization");
|
||||
std::process::exit(uu_vdir::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the wc command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("wc").expect("utility supplies localization");
|
||||
std::process::exit(uu_wc::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Implements the yes command as an independent WASI binary.
|
||||
fn main() {
|
||||
uucore::locale::setup_localization("yes").expect("utility supplies localization");
|
||||
std::process::exit(uu_yes::uumain(uucore::args_os()));
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
//! Dispatches the portable uutils 0.12 multicall component by argv[0].
|
||||
fn main() {
|
||||
let argv0 = std::env::args().next().unwrap();
|
||||
let name = std::path::Path::new(&argv0)
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap();
|
||||
let name = if name == "[" { "test" } else { name };
|
||||
let locale = match name {
|
||||
"dir" | "vdir" => "ls",
|
||||
_ => name,
|
||||
};
|
||||
uucore::locale::setup_localization(locale).expect("utility supplies localization");
|
||||
let args = uucore::args_os();
|
||||
let code = match name {
|
||||
"arch" => uu_arch::uumain(args),
|
||||
"base32" => uu_base32::uumain(args),
|
||||
"base64" => uu_base64::uumain(args),
|
||||
"basename" => uu_basename::uumain(args),
|
||||
"basenc" => uu_basenc::uumain(args),
|
||||
"cat" => uu_cat::uumain(args),
|
||||
"cksum" => uu_cksum::uumain(args),
|
||||
"b2sum" => uu_b2sum::uumain(args),
|
||||
"md5sum" => uu_md5sum::uumain(args),
|
||||
"nice" => uu_nice::uumain(args),
|
||||
"sha1sum" => uu_sha1sum::uumain(args),
|
||||
"sha224sum" => uu_sha224sum::uumain(args),
|
||||
"sha256sum" => uu_sha256sum::uumain(args),
|
||||
"sha384sum" => uu_sha384sum::uumain(args),
|
||||
"sha512sum" => uu_sha512sum::uumain(args),
|
||||
"comm" => uu_comm::uumain(args),
|
||||
"cp" => uu_cp::uumain(args),
|
||||
"csplit" => uu_csplit::uumain(args),
|
||||
"cut" => uu_cut::uumain(args),
|
||||
"date" => uu_date::uumain(args),
|
||||
"dd" => uu_dd::uumain(args),
|
||||
"dir" => uu_dir::uumain(args),
|
||||
"dircolors" => uu_dircolors::uumain(args),
|
||||
"dirname" => uu_dirname::uumain(args),
|
||||
"echo" => uu_echo::uumain(args),
|
||||
"expand" => uu_expand::uumain(args),
|
||||
"expr" => uu_expr::uumain(args),
|
||||
"factor" => uu_factor::uumain(args),
|
||||
"false" => uu_false::uumain(args),
|
||||
"fmt" => uu_fmt::uumain(args),
|
||||
"fold" => uu_fold::uumain(args),
|
||||
"head" => uu_head::uumain(args),
|
||||
"hostid" => uu_hostid::uumain(args),
|
||||
"join" => uu_join::uumain(args),
|
||||
"link" => uu_link::uumain(args),
|
||||
"ln" => uu_ln::uumain(args),
|
||||
"ls" => uu_ls::uumain(args),
|
||||
"mkdir" => uu_mkdir::uumain(args),
|
||||
"mktemp" => uu_mktemp::uumain(args),
|
||||
"mv" => uu_mv::uumain(args),
|
||||
"nl" => uu_nl::uumain(args),
|
||||
"numfmt" => uu_numfmt::uumain(args),
|
||||
"od" => uu_od::uumain(args),
|
||||
"paste" => uu_paste::uumain(args),
|
||||
"pathchk" => uu_pathchk::uumain(args),
|
||||
"pr" => uu_pr::uumain(args),
|
||||
"printenv" => uu_printenv::uumain(args),
|
||||
"printf" => uu_printf::uumain(args),
|
||||
"ptx" => uu_ptx::uumain(args),
|
||||
"pwd" => uu_pwd::uumain(args),
|
||||
"readlink" => uu_readlink::uumain(args),
|
||||
"realpath" => uu_realpath::uumain(args),
|
||||
"rm" => uu_rm::uumain(args),
|
||||
"rmdir" => uu_rmdir::uumain(args),
|
||||
"seq" => uu_seq::uumain(args),
|
||||
"shred" => uu_shred::uumain(args),
|
||||
"shuf" => uu_shuf::uumain(args),
|
||||
"sleep" => uu_sleep::uumain(args),
|
||||
"sort" => uu_sort::uumain(args),
|
||||
"split" => uu_split::uumain(args),
|
||||
"sum" => uu_sum::uumain(args),
|
||||
"tail" => uu_tail::uumain(args),
|
||||
"tee" => uu_tee::uumain(args),
|
||||
"test" => uu_test::uumain(args),
|
||||
"touch" => uu_touch::uumain(args),
|
||||
"tr" => uu_tr::uumain(args),
|
||||
"true" => uu_true::uumain(args),
|
||||
"truncate" => uu_truncate::uumain(args),
|
||||
"tsort" => uu_tsort::uumain(args),
|
||||
"tty" => uu_tty::uumain(args),
|
||||
"uname" => uu_uname::uumain(args),
|
||||
"unexpand" => uu_unexpand::uumain(args),
|
||||
"uniq" => uu_uniq::uumain(args),
|
||||
"unlink" => uu_unlink::uumain(args),
|
||||
"vdir" => uu_vdir::uumain(args),
|
||||
"wc" => uu_wc::uumain(args),
|
||||
"yes" => uu_yes::uumain(args),
|
||||
"nproc" => uu_nproc::uumain(args),
|
||||
_ => 127,
|
||||
};
|
||||
std::process::exit(code);
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
arch
|
||||
base32
|
||||
base64
|
||||
basename
|
||||
basenc
|
||||
cat
|
||||
cksum
|
||||
b2sum
|
||||
md5sum
|
||||
nice
|
||||
sha1sum
|
||||
sha224sum
|
||||
sha256sum
|
||||
sha384sum
|
||||
sha512sum
|
||||
comm
|
||||
cp
|
||||
csplit
|
||||
cut
|
||||
date
|
||||
dd
|
||||
dir
|
||||
dircolors
|
||||
dirname
|
||||
echo
|
||||
expand
|
||||
expr
|
||||
factor
|
||||
false
|
||||
fmt
|
||||
fold
|
||||
head
|
||||
hostid
|
||||
join
|
||||
link
|
||||
ln
|
||||
ls
|
||||
mkdir
|
||||
mktemp
|
||||
mv
|
||||
nl
|
||||
numfmt
|
||||
od
|
||||
paste
|
||||
pathchk
|
||||
pr
|
||||
printenv
|
||||
printf
|
||||
ptx
|
||||
pwd
|
||||
readlink
|
||||
realpath
|
||||
rm
|
||||
rmdir
|
||||
seq
|
||||
shred
|
||||
shuf
|
||||
sleep
|
||||
sort
|
||||
split
|
||||
sum
|
||||
tail
|
||||
tee
|
||||
test
|
||||
touch
|
||||
tr
|
||||
true
|
||||
truncate
|
||||
tsort
|
||||
tty
|
||||
uname
|
||||
unexpand
|
||||
uniq
|
||||
unlink
|
||||
vdir
|
||||
wc
|
||||
yes
|
||||
nproc
|
||||
@@ -238,6 +238,22 @@ pub fn status(repo: &gix::Repository) -> Result<Status> {
|
||||
.map_err(|e| error("status", e))?;
|
||||
for item in iter {
|
||||
let item = item.map_err(|e| error("status", e))?;
|
||||
#[cfg(target_os = "wasi")]
|
||||
if matches!(
|
||||
&item,
|
||||
Item::Modification {
|
||||
status: gix::status::plumbing::index_as_worktree::EntryStatus::Change(
|
||||
gix::status::plumbing::index_as_worktree::Change::Modification {
|
||||
executable_bit_changed: true,
|
||||
content_change: None,
|
||||
..
|
||||
}
|
||||
),
|
||||
..
|
||||
}
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let path = item.rela_path().to_str_lossy().into_owned();
|
||||
let Some(summary) = item.summary() else {
|
||||
continue;
|
||||
@@ -332,7 +348,7 @@ fn worktree_file(
|
||||
} else {
|
||||
entry.mode
|
||||
};
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(target_os = "wasi")))]
|
||||
let mode = if mode != 0o120000 && caps.executable_bit {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
if meta.permissions().mode() & 0o111 != 0 {
|
||||
|
||||
@@ -229,7 +229,7 @@ fn work_entry(
|
||||
Mode::SYMLINK,
|
||||
)
|
||||
} else if m.is_file() {
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(target_os = "wasi")))]
|
||||
let mode = {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
if m.permissions().mode() & 0o111 != 0 {
|
||||
@@ -238,7 +238,7 @@ fn work_entry(
|
||||
Mode::FILE
|
||||
}
|
||||
};
|
||||
#[cfg(not(unix))]
|
||||
#[cfg(any(not(unix), target_os = "wasi"))]
|
||||
let mode = if previous == Some(Mode::FILE_EXECUTABLE) {
|
||||
Mode::FILE_EXECUTABLE
|
||||
} else {
|
||||
|
||||
@@ -268,6 +268,24 @@ def main [component: path] {
|
||||
print $'($args | str join " "): (if $equal {"PASS documented usage"} else {"FAIL documented usage"}) wasi=($result.exit_code)'
|
||||
if not $equal { $failures += 1; print ($result | to json) }
|
||||
}
|
||||
# WASI checkout and staging retain an existing executable file's indexed mode.
|
||||
let executable = ($root | path join executable)
|
||||
mkdir $executable
|
||||
^git -C $executable init -q -b main
|
||||
"#!/bin/sh\necho executable\n" | save ($executable | path join run.sh)
|
||||
^chmod +x ($executable | path join run.sh)
|
||||
^git -C $executable add run.sh
|
||||
^git -C $executable commit --no-gpg-sign -q -m executable
|
||||
rm ($executable | path join run.sh)
|
||||
mut executable_ok = true
|
||||
for args in [[checkout -q HEAD -- run.sh] [status --short] [diff] [add run.sh] [diff --cached] [status --short]] {
|
||||
let result = (^wasmtime run -S http=y -S cli-exit-with-code=y --dir $'($executable)::/mode' $component -C /mode ...$args | complete)
|
||||
if $result.exit_code != 0 or $result.stdout != '' { $executable_ok = false; print ($result | to json) }
|
||||
}
|
||||
let indexed = (^git -C $executable ls-files --stage run.sh | complete)
|
||||
$executable_ok = ($executable_ok and $indexed.exit_code == 0 and ($indexed.stdout | str starts-with '100755 '))
|
||||
print $'executable checkout stays clean: (if $executable_ok {"PASS"} else {"FAIL"})'
|
||||
if not $executable_ok { $failures += 1 }
|
||||
# A merge tree that differs from both parents requires a combined diff.
|
||||
'combined resolution\n' | save --force ($root | path join native/repo/file.txt)
|
||||
let native = ($root | path join native/repo)
|
||||
|
||||
@@ -5,7 +5,7 @@ mod bindings {
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
match std::path::Path::new(&args[0])
|
||||
.file_name()
|
||||
.file_stem()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
|
||||
@@ -41,13 +41,7 @@ impl Command {
|
||||
pub fn stderr(&mut self, mode: Stdio) -> &mut Self { self.stderr = mode; self }
|
||||
/// Resolve PATH and start the component.
|
||||
pub fn spawn(&self) -> io::Result<Child> {
|
||||
let program = if self.program.contains('/') { self.program.clone() } else {
|
||||
self.env.get("PATH").map(String::as_str).unwrap_or("/usr/local/bin:/bin").split(':')
|
||||
.map(|dir| self.cwd.join(dir).join(&self.program))
|
||||
.find(|p| p.is_file()).ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, &self.program[..]))?
|
||||
.to_string_lossy().into_owned()
|
||||
};
|
||||
let child = host::spawn(&host::Request { program, argv: std::iter::once(self.program.clone()).chain(self.args.clone()).collect(),
|
||||
let child = host::spawn(&host::Request { program: self.program.clone(), argv: std::iter::once(self.program.clone()).chain(self.args.clone()).collect(),
|
||||
env: self.env.clone().into_iter().collect(), cwd: self.cwd.to_string_lossy().into_owned(), group: self.group,
|
||||
stdin: self.stdin, stdout: self.stdout, stderr: self.stderr }).map_err(io::Error::other)?;
|
||||
Ok(Child { stdin: matches!(self.stdin, Stdio::Pipe).then(|| child.stdin()),
|
||||
|
||||
Reference in New Issue
Block a user