From 382f9a7a3d15580c15531428f39bf47f55093d42 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 31 Mar 2019 14:39:03 -0700 Subject: [PATCH 01/11] wasi: Load arguments via syscalls This commit switches the wasi target to loading CLI arguments via the syscalls provided by wasi rather than through the argc/argv passed to the main function. While serving the same purpose it's hoped that using syscalls will make us a bit more portable (less reliance from libstd on an external C library) as well as avoiding the need for a lock! --- src/libstd/sys/wasi/args.rs | 50 ++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/src/libstd/sys/wasi/args.rs b/src/libstd/sys/wasi/args.rs index 20558a8042db4..9c8e59e4fb5e1 100644 --- a/src/libstd/sys/wasi/args.rs +++ b/src/libstd/sys/wasi/args.rs @@ -1,30 +1,15 @@ -use crate::any::Any; use crate::ffi::CStr; +use crate::io; +use crate::sys::cvt_wasi; use crate::ffi::OsString; use crate::marker::PhantomData; use crate::os::wasi::ffi::OsStringExt; -use crate::ptr; use crate::vec; -static mut ARGC: isize = 0; -static mut ARGV: *const *const u8 = ptr::null(); - -#[cfg(not(target_feature = "atomics"))] -pub unsafe fn args_lock() -> impl Any { - // No need for a lock if we're single-threaded, but this function will need - // to get implemented for multi-threaded scenarios -} - -pub unsafe fn init(argc: isize, argv: *const *const u8) { - let _guard = args_lock(); - ARGC = argc; - ARGV = argv; +pub unsafe fn init(_argc: isize, _argv: *const *const u8) { } pub unsafe fn cleanup() { - let _guard = args_lock(); - ARGC = 0; - ARGV = ptr::null(); } pub struct Args { @@ -34,18 +19,31 @@ pub struct Args { /// Returns the command line arguments pub fn args() -> Args { + maybe_args().unwrap_or_else(|_| { + Args { + iter: Vec::new().into_iter(), + _dont_send_or_sync_me: PhantomData + } + }) +} + +fn maybe_args() -> io::Result { unsafe { - let _guard = args_lock(); - let args = (0..ARGC) - .map(|i| { - let cstr = CStr::from_ptr(*ARGV.offset(i) as *const libc::c_char); - OsStringExt::from_vec(cstr.to_bytes().to_vec()) - }) + let (mut argc, mut argv_buf_size) = (0, 0); + cvt_wasi(libc::__wasi_args_sizes_get(&mut argc, &mut argv_buf_size))?; + + let mut argc = vec![0 as *mut libc::c_char; argc]; + let mut argv_buf = vec![0; argv_buf_size]; + cvt_wasi(libc::__wasi_args_get(argc.as_mut_ptr(), argv_buf.as_mut_ptr()))?; + + let args = argc.into_iter() + .map(|ptr| CStr::from_ptr(ptr).to_bytes().to_vec()) + .map(|bytes| OsString::from_vec(bytes)) .collect::>(); - Args { + Ok(Args { iter: args.into_iter(), _dont_send_or_sync_me: PhantomData, - } + }) } } From 60f6cbd0028c61bcca181318f48cdf0c6be61231 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 31 Mar 2019 14:56:56 -0700 Subject: [PATCH 02/11] wasi: Use raw syscalls for stdio I've since learned that the mapping between libc fds and wasi fds are expected to be one-to-one, so we can use the raw syscalls for writing to stdout/stderr and reading from stdin! This should help ensure that we don't depend on a C library too unnecessarily. --- src/libstd/sys/wasi/stdio.rs | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/libstd/sys/wasi/stdio.rs b/src/libstd/sys/wasi/stdio.rs index f6a4958897d9b..192947886668f 100644 --- a/src/libstd/sys/wasi/stdio.rs +++ b/src/libstd/sys/wasi/stdio.rs @@ -1,6 +1,7 @@ -use crate::io; +use crate::io::{self, IoVec, IoVecMut}; use crate::libc; -use crate::sys::cvt; +use crate::mem::ManuallyDrop; +use crate::sys::fd::WasiFd; pub struct Stdin; pub struct Stdout; @@ -12,10 +13,8 @@ impl Stdin { } pub fn read(&self, data: &mut [u8]) -> io::Result { - let amt = cvt(unsafe { - libc::read(libc::STDIN_FILENO, data.as_mut_ptr() as *mut _, data.len()) - })?; - Ok(amt as usize) + ManuallyDrop::new(unsafe { WasiFd::from_raw(libc::STDIN_FILENO as u32) }) + .read(&mut [IoVecMut::new(data)]) } } @@ -25,10 +24,8 @@ impl Stdout { } pub fn write(&self, data: &[u8]) -> io::Result { - let amt = cvt(unsafe { - libc::write(libc::STDOUT_FILENO, data.as_ptr() as *const _, data.len()) - })?; - Ok(amt as usize) + ManuallyDrop::new(unsafe { WasiFd::from_raw(libc::STDOUT_FILENO as u32) }) + .write(&[IoVec::new(data)]) } pub fn flush(&self) -> io::Result<()> { @@ -42,10 +39,8 @@ impl Stderr { } pub fn write(&self, data: &[u8]) -> io::Result { - let amt = cvt(unsafe { - libc::write(libc::STDERR_FILENO, data.as_ptr() as *const _, data.len()) - })?; - Ok(amt as usize) + ManuallyDrop::new(unsafe { WasiFd::from_raw(libc::STDERR_FILENO as u32) }) + .write(&[IoVec::new(data)]) } pub fn flush(&self) -> io::Result<()> { From 32a76844c47924a1d230f647db3bafb53c3e20ea Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 1 Apr 2019 05:29:17 -0700 Subject: [PATCH 03/11] wasi: Implement `error_string` to get readable errors This routes the `error_string` API to `strerror` in libc which should have more human readable descriptions. --- src/libstd/sys/wasi/os.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/libstd/sys/wasi/os.rs b/src/libstd/sys/wasi/os.rs index 6d4d6aae61b9f..822ea02a11b89 100644 --- a/src/libstd/sys/wasi/os.rs +++ b/src/libstd/sys/wasi/os.rs @@ -27,8 +27,21 @@ pub fn errno() -> i32 { unsafe { errno as i32 } } -pub fn error_string(_errno: i32) -> String { - "operation failed".to_string() +pub fn error_string(errno: i32) -> String { + extern { + fn strerror_r(errnum: libc::c_int, buf: *mut libc::c_char, + buflen: libc::size_t) -> libc::c_int; + } + + let mut buf = [0 as libc::c_char; 1024]; + + let p = buf.as_mut_ptr(); + unsafe { + if strerror_r(errno as libc::c_int, p, buf.len()) < 0 { + panic!("strerror_r failure"); + } + str::from_utf8(CStr::from_ptr(p).to_bytes()).unwrap().to_owned() + } } pub fn getcwd() -> io::Result { From fb575c0ea9fdda046285d527a9063255e08507d4 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 2 Apr 2019 11:30:36 -0700 Subject: [PATCH 04/11] Never return uninhabited values at all Functions with uninhabited return values are already marked `noreturn`, but we were still generating return instructions for this. When running with `C passes=lint`, LLVM prints: Unusual: Return statement in function with noreturn attribute The LLVM manual makes a stronger statement about `noreturn` though: > This produces undefined behavior at runtime if the function ever does dynamically return. We now mark such return values with a new `IgnoreMode::Uninhabited`, and emit an `abort` anywhere that would have returned. --- src/librustc_codegen_llvm/abi.rs | 9 +++++-- src/librustc_codegen_ssa/mir/block.rs | 6 +++++ src/librustc_codegen_ssa/mir/mod.rs | 3 ++- src/librustc_target/abi/call/mod.rs | 2 ++ src/test/codegen/noreturn-uninhabited.rs | 32 ++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 src/test/codegen/noreturn-uninhabited.rs diff --git a/src/librustc_codegen_llvm/abi.rs b/src/librustc_codegen_llvm/abi.rs index 3a0d9e1334cf6..6a80051569f6a 100644 --- a/src/librustc_codegen_llvm/abi.rs +++ b/src/librustc_codegen_llvm/abi.rs @@ -518,7 +518,11 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> { let arg_of = |ty: Ty<'tcx>, arg_idx: Option| { let is_return = arg_idx.is_none(); let mut arg = mk_arg_type(ty, arg_idx); - if arg.layout.is_zst() { + if is_return && arg.layout.abi.is_uninhabited() { + // Functions with uninhabited return values are marked `noreturn`, + // so we don't actually need to do anything with the value. + arg.mode = PassMode::Ignore(IgnoreMode::Uninhabited); + } else if arg.layout.is_zst() { // For some forsaken reason, x86_64-pc-windows-gnu // doesn't ignore zero-sized struct arguments. // The same is true for s390x-unknown-linux-gnu @@ -677,7 +681,8 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> { ); let llreturn_ty = match self.ret.mode { - PassMode::Ignore(IgnoreMode::Zst) => cx.type_void(), + PassMode::Ignore(IgnoreMode::Zst) + | PassMode::Ignore(IgnoreMode::Uninhabited) => cx.type_void(), PassMode::Ignore(IgnoreMode::CVarArgs) => bug!("`va_list` should never be a return type"), PassMode::Direct(_) | PassMode::Pair(..) => { diff --git a/src/librustc_codegen_ssa/mir/block.rs b/src/librustc_codegen_ssa/mir/block.rs index 53e8f7ed88b4e..76357078c4514 100644 --- a/src/librustc_codegen_ssa/mir/block.rs +++ b/src/librustc_codegen_ssa/mir/block.rs @@ -244,6 +244,12 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { return; } + PassMode::Ignore(IgnoreMode::Uninhabited) => { + bx.abort(); + bx.unreachable(); + return; + } + PassMode::Ignore(IgnoreMode::CVarArgs) => { bug!("C-variadic arguments should never be the return type"); } diff --git a/src/librustc_codegen_ssa/mir/mod.rs b/src/librustc_codegen_ssa/mir/mod.rs index 91aa9bcc7808b..0b3476c44e2ce 100644 --- a/src/librustc_codegen_ssa/mir/mod.rs +++ b/src/librustc_codegen_ssa/mir/mod.rs @@ -521,7 +521,8 @@ fn arg_local_refs<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>>( PassMode::Ignore(IgnoreMode::Zst) => { return local(OperandRef::new_zst(bx, arg.layout)); } - PassMode::Ignore(IgnoreMode::CVarArgs) => {} + PassMode::Ignore(IgnoreMode::CVarArgs) + | PassMode::Ignore(IgnoreMode::Uninhabited) => {} PassMode::Direct(_) => { let llarg = bx.get_param(llarg_idx); bx.set_value_name(llarg, &name); diff --git a/src/librustc_target/abi/call/mod.rs b/src/librustc_target/abi/call/mod.rs index fbbd120f934be..8cbe99e3d8c76 100644 --- a/src/librustc_target/abi/call/mod.rs +++ b/src/librustc_target/abi/call/mod.rs @@ -29,6 +29,8 @@ pub enum IgnoreMode { CVarArgs, /// A zero-sized type. Zst, + /// An uninhabited type. + Uninhabited, } #[derive(Clone, Copy, PartialEq, Eq, Debug)] diff --git a/src/test/codegen/noreturn-uninhabited.rs b/src/test/codegen/noreturn-uninhabited.rs new file mode 100644 index 0000000000000..1b65da9f2877a --- /dev/null +++ b/src/test/codegen/noreturn-uninhabited.rs @@ -0,0 +1,32 @@ +// compile-flags: -g -C no-prepopulate-passes +// ignore-tidy-linelength + +#![crate_type = "lib"] + +#[derive(Clone, Copy)] +pub enum EmptyEnum {} + +#[no_mangle] +pub fn empty(x: &EmptyEnum) -> EmptyEnum { + // CHECK: @empty({{.*}}) unnamed_addr #0 + // CHECK-NOT: ret void + // CHECK: call void @llvm.trap() + // CHECK: unreachable + *x +} + +pub struct Foo(String, EmptyEnum); + +#[no_mangle] +pub fn foo(x: String, y: &EmptyEnum) -> Foo { + // CHECK: @foo({{.*}}) unnamed_addr #0 + // CHECK-NOT: ret %Foo + // CHECK: call void @llvm.trap() + // CHECK: unreachable + Foo(x, *y) +} + +// CHECK: attributes #0 = {{{.*}} noreturn {{.*}}} + +// CHECK: DISubprogram(name: "empty", {{.*}} DIFlagNoReturn +// CHECK: DISubprogram(name: "foo", {{.*}} DIFlagNoReturn From 7eb2efdb125039f51a339eb3c204e717bb25aff6 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 2 Apr 2019 13:38:51 -0700 Subject: [PATCH 05/11] std: Upgrade `compiler_builtins` to fix wasi linkage Turns out we needed to exclude a number of math functions on the `wasm32-unknown-wasi` target, and this was fixed in 0.1.9 of compiler-builtins and this is pulling in the fix to libstd's own build. --- Cargo.lock | 32 ++++++++++++++++---------------- src/libstd/Cargo.toml | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c071a2e11d269..086058a8761d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,7 +17,7 @@ dependencies = [ name = "alloc" version = "0.0.0" dependencies = [ - "compiler_builtins 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "compiler_builtins 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "core 0.0.0", "rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -106,7 +106,7 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", - "compiler_builtins 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "compiler_builtins 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-std-workspace-core 1.0.0", ] @@ -464,7 +464,7 @@ dependencies = [ [[package]] name = "compiler_builtins" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", @@ -750,7 +750,7 @@ name = "dlmalloc" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "compiler_builtins 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "compiler_builtins 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-std-workspace-core 1.0.0", ] @@ -916,7 +916,7 @@ name = "fortanix-sgx-abi" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "compiler_builtins 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "compiler_builtins 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-std-workspace-core 1.0.0", ] @@ -1768,7 +1768,7 @@ dependencies = [ name = "panic_abort" version = "0.0.0" dependencies = [ - "compiler_builtins 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "compiler_builtins 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "core 0.0.0", "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1778,7 +1778,7 @@ name = "panic_unwind" version = "0.0.0" dependencies = [ "alloc 0.0.0", - "compiler_builtins 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "compiler_builtins 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "core 0.0.0", "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "unwind 0.0.0", @@ -1963,7 +1963,7 @@ name = "profiler_builtins" version = "0.0.0" dependencies = [ "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", - "compiler_builtins 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "compiler_builtins 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "core 0.0.0", ] @@ -2498,7 +2498,7 @@ name = "rustc-demangle" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "compiler_builtins 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "compiler_builtins 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-std-workspace-core 1.0.0", ] @@ -2598,7 +2598,7 @@ dependencies = [ "alloc 0.0.0", "build_helper 0.1.0", "cmake 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)", - "compiler_builtins 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "compiler_builtins 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "core 0.0.0", ] @@ -2825,7 +2825,7 @@ dependencies = [ "alloc 0.0.0", "build_helper 0.1.0", "cmake 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)", - "compiler_builtins 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "compiler_builtins 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "core 0.0.0", ] @@ -2887,7 +2887,7 @@ dependencies = [ "alloc 0.0.0", "build_helper 0.1.0", "cmake 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)", - "compiler_builtins 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "compiler_builtins 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "core 0.0.0", ] @@ -3008,7 +3008,7 @@ dependencies = [ "alloc 0.0.0", "build_helper 0.1.0", "cmake 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)", - "compiler_builtins 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "compiler_builtins 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "core 0.0.0", ] @@ -3275,7 +3275,7 @@ dependencies = [ "alloc 0.0.0", "backtrace-sys 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", - "compiler_builtins 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "compiler_builtins 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "core 0.0.0", "dlmalloc 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "fortanix-sgx-abi 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3848,7 +3848,7 @@ dependencies = [ name = "unwind" version = "0.0.0" dependencies = [ - "compiler_builtins 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "compiler_builtins 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "core 0.0.0", "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -4044,7 +4044,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum colored 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b0aa3473e85a3161b59845d6096b289bb577874cafeaf75ea1b1beaa6572c7fc" "checksum commoncrypto 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d056a8586ba25a1e4d61cb090900e495952c7886786fc55f909ab2f819b69007" "checksum commoncrypto-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1fed34f46747aa73dfaa578069fd8279d2818ade2b55f38f22a9401c7f4083e2" -"checksum compiler_builtins 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "a28c3898d0c57b26fa6f92de141ba665fa5ac5179f795db06db408be84302395" +"checksum compiler_builtins 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d7de11892d9f9f1bc76d43011c8233d27d58300d629dc9dfb51b6626ef7f6077" "checksum compiletest_rs 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "56c799b1f7142badf3b047b4c1f2074cc96b6b784fb2432f2ed9c87da0a03749" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum core-foundation 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4e2640d6d0bf22e82bed1b73c6aef8d5dd31e5abe6666c57e6d45e2649f4f887" diff --git a/src/libstd/Cargo.toml b/src/libstd/Cargo.toml index 9ac03adfc2778..b2b9c94ea4fe4 100644 --- a/src/libstd/Cargo.toml +++ b/src/libstd/Cargo.toml @@ -19,7 +19,7 @@ panic_unwind = { path = "../libpanic_unwind", optional = true } panic_abort = { path = "../libpanic_abort" } core = { path = "../libcore" } libc = { version = "0.2.51", default-features = false, features = ['rustc-dep-of-std'] } -compiler_builtins = { version = "0.1.8" } +compiler_builtins = { version = "0.1.9" } profiler_builtins = { path = "../libprofiler_builtins", optional = true } unwind = { path = "../libunwind" } rustc-demangle = { version = "0.1.10", features = ['rustc-dep-of-std'] } From 1cfed0d452d739b502c73904d61a1f4496b1acf5 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 3 Apr 2019 11:19:16 +0300 Subject: [PATCH 06/11] be more direct about borrow requirenments --- src/libcore/borrow.rs | 4 ++++ src/libcore/convert.rs | 10 ++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/libcore/borrow.rs b/src/libcore/borrow.rs index 89668dc06505b..4d58aaca94183 100644 --- a/src/libcore/borrow.rs +++ b/src/libcore/borrow.rs @@ -32,6 +32,10 @@ /// on the identical behavior of these additional trait implementations. /// These traits will likely appear as additional trait bounds. /// +/// In particular `Eq`, `Ord` and `Hash` must be equivalent for +/// borrowed and owned values: `x.borrow() == y.borrow()` should give the +/// same result as `x == y`. +/// /// If generic code merely needs to work for all types that can /// provide a reference to related type `T`, it is often better to use /// [`AsRef`] as more types can safely implement it. diff --git a/src/libcore/convert.rs b/src/libcore/convert.rs index a6c65e890a5ed..7b9e19e36a293 100644 --- a/src/libcore/convert.rs +++ b/src/libcore/convert.rs @@ -105,11 +105,13 @@ pub const fn identity(x: T) -> T { x } /// `&T` or write a custom function. /// /// -/// `AsRef` is very similar to, but serves a slightly different purpose than [`Borrow`]: +/// `AsRef` has the same signature as [`Borrow`], but `Borrow` is different in few aspects: /// -/// - Use `AsRef` when the goal is to simply convert into a reference -/// - Use `Borrow` when the goal is related to writing code that is agnostic to -/// the type of borrow and whether it is a reference or value +/// - Unlike `AsRef`, `Borrow` has a blanket impl for any `T`, and can be used to accept either +/// a reference or a value. +/// - `Borrow` also requires that `Hash`, `Eq` and `Ord` for borrowed value are +/// equivalent to those of the owned value. For this reason, if you want to +/// borrow only a single field of a struct you can implement `AsRef`, but not `Borrow`. /// /// [`Borrow`]: ../../std/borrow/trait.Borrow.html /// From ab3b65737380d2c2a15a868452026df49c58131a Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 3 Apr 2019 10:54:07 +0200 Subject: [PATCH 07/11] Updated the documentation of core::hints::spin_loop and core::sync::spin_loop_hint --- src/libcore/hint.rs | 27 ++++++++++++++++++++------- src/libcore/sync/atomic.rs | 27 ++++++++++++++++++++------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/libcore/hint.rs b/src/libcore/hint.rs index d1ccc148654ca..7d2006e03c409 100644 --- a/src/libcore/hint.rs +++ b/src/libcore/hint.rs @@ -50,15 +50,28 @@ pub unsafe fn unreachable_unchecked() -> ! { intrinsics::unreachable() } -/// Save power or switch hyperthreads in a busy-wait spin-loop. +/// Signals the processor that it is entering a busy-wait spin-loop. /// -/// This function is deliberately more primitive than -/// [`std::thread::yield_now`](../../std/thread/fn.yield_now.html) and -/// does not directly yield to the system's scheduler. -/// In some cases it might be useful to use a combination of both functions. -/// Careful benchmarking is advised. +/// Upon receiving spin-loop signal the processor can optimize its behavior by, for example, saving +/// power or switching hyper-threads. /// -/// On some platforms this function may not do anything at all. +/// This function is different than [`std::thread::yield_now`] which directly yields to the +/// system's scheduler, whereas `spin_loop` only signals the processor that it is entering a +/// busy-wait spin-loop without yielding control to the system's scheduler. +/// +/// Using a busy-wait spin-loop with `spin_loop` is ideally used in situations where a +/// contended lock is held by another thread executed on a different CPU and where the waiting +/// times are relatively small. Because entering busy-wait spin-loop does not trigger the system's +/// scheduler, no overhead for switching threads occurs. However, if the thread holding the +/// contended lock is running on the same CPU, the spin-loop is likely to occupy an entire CPU slice +/// before switching to the thread that holds the lock. If the contending lock is held by a thread +/// on the same CPU or if the waiting times for acquiring the lock are longer, it is often better to +/// use [`std::thread::yield_now`]. +/// +/// **Note**: On platforms that do not support receiving spin-loop hints this function does not +/// do anything at all. +/// +/// [`std::thread::yield_now`]: ../../../std/thread/fn.yield_now.html #[inline] #[unstable(feature = "renamed_spin_loop", issue = "55002")] pub fn spin_loop() { diff --git a/src/libcore/sync/atomic.rs b/src/libcore/sync/atomic.rs index 04a49d253015f..26b59969e18af 100644 --- a/src/libcore/sync/atomic.rs +++ b/src/libcore/sync/atomic.rs @@ -124,15 +124,28 @@ use fmt; use hint::spin_loop; -/// Save power or switch hyperthreads in a busy-wait spin-loop. +/// Signals the processor that it is entering a busy-wait spin-loop. /// -/// This function is deliberately more primitive than -/// [`std::thread::yield_now`](../../../std/thread/fn.yield_now.html) and -/// does not directly yield to the system's scheduler. -/// In some cases it might be useful to use a combination of both functions. -/// Careful benchmarking is advised. +/// Upon receiving spin-loop signal the processor can optimize its behavior by, for example, saving +/// power or switching hyper-threads. /// -/// On some platforms this function may not do anything at all. +/// This function is different than [`std::thread::yield_now`] which directly yields to the +/// system's scheduler, whereas `spin_loop_hint` only signals the processor that it is entering a +/// busy-wait spin-loop without yielding control to the system's scheduler. +/// +/// Using a busy-wait spin-loop with `spin_loop_hint` is ideally used in situations where a +/// contended lock is held by another thread executed on a different CPU and where the waiting +/// times are relatively small. Because entering busy-wait spin-loop does not trigger the system's +/// scheduler, no overhead for switching threads occurs. However, if the thread holding the +/// contended lock is running on the same CPU, the spin-loop is likely to occupy an entire CPU slice +/// before switching to the thread that holds the lock. If the contending lock is held by a thread +/// on the same CPU or if the waiting times for acquiring the lock are longer, it is often better to +/// use [`std::thread::yield_now`]. +/// +/// **Note**: On platforms that do not support receiving spin-loop hints this function does not +/// do anything at all. +/// +/// [`std::thread::yield_now`]: ../../../std/thread/fn.yield_now.html #[inline] #[stable(feature = "spin_loop_hint", since = "1.24.0")] pub fn spin_loop_hint() { From becee90cfd9f174ed050c1d4b366fd13d89f1913 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 3 Apr 2019 15:47:38 +0200 Subject: [PATCH 08/11] Updated the reference in core::hint::spin_loop to the correct relative path. --- src/libcore/hint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcore/hint.rs b/src/libcore/hint.rs index 7d2006e03c409..d43e6c49f4c99 100644 --- a/src/libcore/hint.rs +++ b/src/libcore/hint.rs @@ -71,7 +71,7 @@ pub unsafe fn unreachable_unchecked() -> ! { /// **Note**: On platforms that do not support receiving spin-loop hints this function does not /// do anything at all. /// -/// [`std::thread::yield_now`]: ../../../std/thread/fn.yield_now.html +/// [`std::thread::yield_now`]: ../../std/thread/fn.yield_now.html #[inline] #[unstable(feature = "renamed_spin_loop", issue = "55002")] pub fn spin_loop() { From 7e37b46d2098160f78cddd3ed51f6f57d438b58c Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 3 Apr 2019 15:50:20 +0200 Subject: [PATCH 09/11] Updated the environment description in rustc. --- src/doc/man/rustc.1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/man/rustc.1 b/src/doc/man/rustc.1 index 8f611063dbe5d..2faf0c2e3e1fa 100644 --- a/src/doc/man/rustc.1 +++ b/src/doc/man/rustc.1 @@ -265,8 +265,8 @@ Optimize with possible levels 0\[en]3 .SH ENVIRONMENT -Some of these affect the output of the compiler, while others affect programs -which link to the standard library. +Some of these affect only test harness programs (generated via rustc --test); +others affect all programs which link to the Rust standard library. .TP \fBRUST_TEST_THREADS\fR From 61b487ca8be1d8667a82c1357dc2729cfe56186d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 29 Mar 2019 15:57:14 -0700 Subject: [PATCH 10/11] wasi: Fill out `std::fs` module for WASI This commit fills out the `std::fs` module and implementation for WASI. Not all APIs are implemented, such as permissions-related ones and `canonicalize`, but all others APIs have been implemented and very lightly tested so far. We'll eventually want to run a more exhaustive test suite! For now the highlights of this commit are: * The `std::fs::File` type is now backed by `WasiFd`, a raw WASI file descriptor. * All APIs in `std::fs` (except permissions/canonicalize) have implementations for the WASI target. * A suite of unstable extension traits were added to `std::os::wasi::fs`. These traits expose the raw filesystem functionality of WASI, namely `*at` syscalls (opening a file relative to an already opened one, for example). Additionally metadata only available on wasi is exposed through these traits. Perhaps one of the most notable parts is the implementation of path-taking APIs. WASI actually has no fundamental API that just takes a path, but rather everything is relative to a previously opened file descriptor. To allow existing APIs to work (that only take a path) WASI has a few syscalls to learn about "pre opened" file descriptors by the runtime. We use these to build a map of existing directory names to file descriptors, and then when using a path we try to anchor it at an already-opened file. This support is very rudimentary though and is intended to be shared with C since it's likely to be so tricky. For now though the C library doesn't expose quite an API for us to use, so we implement it for now and will swap it out as soon as one is available. --- src/libstd/fs.rs | 8 + src/libstd/sys/redox/fs.rs | 42 +- src/libstd/sys/unix/fs.rs | 23 +- src/libstd/sys/wasi/ext/ffi.rs | 57 +-- src/libstd/sys/wasi/ext/fs.rs | 412 ++++++++++++++++++++ src/libstd/sys/wasi/ext/io.rs | 87 +++++ src/libstd/sys/wasi/ext/mod.rs | 8 + src/libstd/sys/wasi/fd.rs | 34 +- src/libstd/sys/wasi/fs.rs | 682 ++++++++++++++++++++++++++------- src/libstd/sys/wasi/process.rs | 4 +- src/libstd/sys/wasi/time.rs | 4 + src/libstd/sys_common/fs.rs | 41 ++ src/libstd/sys_common/mod.rs | 1 + 13 files changed, 1141 insertions(+), 262 deletions(-) create mode 100644 src/libstd/sys/wasi/ext/fs.rs create mode 100644 src/libstd/sys/wasi/ext/io.rs create mode 100644 src/libstd/sys_common/fs.rs diff --git a/src/libstd/fs.rs b/src/libstd/fs.rs index 705dc8f40b5a0..29f4c78e27b48 100644 --- a/src/libstd/fs.rs +++ b/src/libstd/fs.rs @@ -881,6 +881,10 @@ impl OpenOptions { } } +impl AsInner for OpenOptions { + fn as_inner(&self) -> &fs_imp::OpenOptions { &self.0 } +} + impl AsInnerMut for OpenOptions { fn as_inner_mut(&mut self) -> &mut fs_imp::OpenOptions { &mut self.0 } } @@ -1104,6 +1108,10 @@ impl AsInner for Metadata { fn as_inner(&self) -> &fs_imp::FileAttr { &self.0 } } +impl FromInner for Metadata { + fn from_inner(attr: fs_imp::FileAttr) -> Metadata { Metadata(attr) } +} + impl Permissions { /// Returns `true` if these permissions describe a readonly (unwritable) file. /// diff --git a/src/libstd/sys/redox/fs.rs b/src/libstd/sys/redox/fs.rs index 3ef9925705fb8..ebefbc942103c 100644 --- a/src/libstd/sys/redox/fs.rs +++ b/src/libstd/sys/redox/fs.rs @@ -2,7 +2,7 @@ use crate::os::unix::prelude::*; use crate::ffi::{OsString, OsStr}; use crate::fmt; -use crate::io::{self, Error, ErrorKind, SeekFrom}; +use crate::io::{self, Error, SeekFrom}; use crate::path::{Path, PathBuf}; use crate::sync::Arc; use crate::sys::fd::FileDesc; @@ -10,6 +10,9 @@ use crate::sys::time::SystemTime; use crate::sys::{cvt, syscall}; use crate::sys_common::{AsInner, FromInner}; +pub use crate::sys_common::fs::copy; +pub use crate::sys_common::fs::remove_dir_all; + pub struct File(FileDesc); #[derive(Clone)] @@ -392,27 +395,6 @@ pub fn rmdir(p: &Path) -> io::Result<()> { Ok(()) } -pub fn remove_dir_all(path: &Path) -> io::Result<()> { - let filetype = lstat(path)?.file_type(); - if filetype.is_symlink() { - unlink(path) - } else { - remove_dir_all_recursive(path) - } -} - -fn remove_dir_all_recursive(path: &Path) -> io::Result<()> { - for child in readdir(path)? { - let child = child?; - if child.file_type()?.is_dir() { - remove_dir_all_recursive(&child.path())?; - } else { - unlink(&child.path())?; - } - } - rmdir(path) -} - pub fn readlink(p: &Path) -> io::Result { let fd = cvt(syscall::open(p.to_str().unwrap(), syscall::O_CLOEXEC | syscall::O_SYMLINK | syscall::O_RDONLY))?; @@ -455,19 +437,3 @@ pub fn canonicalize(p: &Path) -> io::Result { let file = File(FileDesc::new(fd)); file.path() } - -pub fn copy(from: &Path, to: &Path) -> io::Result { - use crate::fs::{File, set_permissions}; - if !from.is_file() { - return Err(Error::new(ErrorKind::InvalidInput, - "the source path is not an existing regular file")) - } - - let mut reader = File::open(from)?; - let mut writer = File::create(to)?; - let perm = reader.metadata()?.permissions(); - - let ret = io::copy(&mut reader, &mut writer)?; - set_permissions(to, perm)?; - Ok(ret) -} diff --git a/src/libstd/sys/unix/fs.rs b/src/libstd/sys/unix/fs.rs index a36dae2f5a19d..dc3dcb5817c05 100644 --- a/src/libstd/sys/unix/fs.rs +++ b/src/libstd/sys/unix/fs.rs @@ -36,6 +36,8 @@ use libc::{stat as stat64, fstat as fstat64, lstat as lstat64, off_t as off64_t, target_os = "fuchsia")))] use libc::{readdir_r as readdir64_r}; +pub use crate::sys_common::fs::remove_dir_all; + pub struct File(FileDesc); #[derive(Clone)] @@ -734,27 +736,6 @@ pub fn rmdir(p: &Path) -> io::Result<()> { Ok(()) } -pub fn remove_dir_all(path: &Path) -> io::Result<()> { - let filetype = lstat(path)?.file_type(); - if filetype.is_symlink() { - unlink(path) - } else { - remove_dir_all_recursive(path) - } -} - -fn remove_dir_all_recursive(path: &Path) -> io::Result<()> { - for child in readdir(path)? { - let child = child?; - if child.file_type()?.is_dir() { - remove_dir_all_recursive(&child.path())?; - } else { - unlink(&child.path())?; - } - } - rmdir(path) -} - pub fn readlink(p: &Path) -> io::Result { let c_path = cstr(p)?; let p = c_path.as_ptr(); diff --git a/src/libstd/sys/wasi/ext/ffi.rs b/src/libstd/sys/wasi/ext/ffi.rs index 07b93dd143f8f..f71f316d1ba10 100644 --- a/src/libstd/sys/wasi/ext/ffi.rs +++ b/src/libstd/sys/wasi/ext/ffi.rs @@ -2,60 +2,5 @@ #![stable(feature = "rust1", since = "1.0.0")] -use crate::ffi::{OsStr, OsString}; -use crate::mem; -use crate::sys::os_str::Buf; -use crate::sys_common::{FromInner, IntoInner, AsInner}; - -/// WASI-specific extensions to [`OsString`]. -/// -/// [`OsString`]: ../../../../std/ffi/struct.OsString.html -#[stable(feature = "rust1", since = "1.0.0")] -pub trait OsStringExt { - /// Creates an `OsString` from a byte vector. - #[stable(feature = "rust1", since = "1.0.0")] - fn from_vec(vec: Vec) -> Self; - - /// Yields the underlying byte vector of this `OsString`. - #[stable(feature = "rust1", since = "1.0.0")] - fn into_vec(self) -> Vec; -} - -#[stable(feature = "rust1", since = "1.0.0")] -impl OsStringExt for OsString { - fn from_vec(vec: Vec) -> OsString { - FromInner::from_inner(Buf { inner: vec }) - } - fn into_vec(self) -> Vec { - self.into_inner().inner - } -} - -/// WASI-specific extensions to [`OsStr`]. -/// -/// [`OsStr`]: ../../../../std/ffi/struct.OsStr.html #[stable(feature = "rust1", since = "1.0.0")] -pub trait OsStrExt { - #[stable(feature = "rust1", since = "1.0.0")] - /// Creates an [`OsStr`] from a byte slice. - /// - /// [`OsStr`]: ../../../ffi/struct.OsStr.html - fn from_bytes(slice: &[u8]) -> &Self; - - /// Gets the underlying byte view of the [`OsStr`] slice. - /// - /// [`OsStr`]: ../../../ffi/struct.OsStr.html - #[stable(feature = "rust1", since = "1.0.0")] - fn as_bytes(&self) -> &[u8]; -} - -#[stable(feature = "rust1", since = "1.0.0")] -impl OsStrExt for OsStr { - fn from_bytes(slice: &[u8]) -> &OsStr { - unsafe { mem::transmute(slice) } - } - fn as_bytes(&self) -> &[u8] { - &self.as_inner().inner - } -} - +pub use crate::sys_common::os_str_bytes::*; diff --git a/src/libstd/sys/wasi/ext/fs.rs b/src/libstd/sys/wasi/ext/fs.rs new file mode 100644 index 0000000000000..53f415c78219e --- /dev/null +++ b/src/libstd/sys/wasi/ext/fs.rs @@ -0,0 +1,412 @@ +//! WASI-specific extensions to primitives in the `std::fs` module. + +#![unstable(feature = "wasi_ext", issue = "0")] + +use crate::fs::{self, File, Metadata, OpenOptions}; +use crate::io::{self, IoVec, IoVecMut}; +use crate::os::wasi::ffi::OsStrExt; +use crate::path::{Path, PathBuf}; +use crate::sys_common::{AsInner, AsInnerMut, FromInner}; + +/// WASI-specific extensions to [`File`]. +/// +/// [`File`]: ../../../../std/fs/struct.File.html +pub trait FileExt { + /// Reads a number of bytes starting from a given offset. + /// + /// Returns the number of bytes read. + /// + /// The offset is relative to the start of the file and thus independent + /// from the current cursor. + /// + /// The current file cursor is not affected by this function. + /// + /// Note that similar to [`File::read_vectored`], it is not an error to + /// return with a short read. + /// + /// [`File::read`]: ../../../../std/fs/struct.File.html#method.read_vectored + fn read_at(&self, bufs: &mut [IoVecMut<'_>], offset: u64) -> io::Result; + + /// Writes a number of bytes starting from a given offset. + /// + /// Returns the number of bytes written. + /// + /// The offset is relative to the start of the file and thus independent + /// from the current cursor. + /// + /// The current file cursor is not affected by this function. + /// + /// When writing beyond the end of the file, the file is appropriately + /// extended and the intermediate bytes are initialized with the value 0. + /// + /// Note that similar to [`File::write_vectored`], it is not an error to return a + /// short write. + /// + /// [`File::write`]: ../../../../std/fs/struct.File.html#method.write_vectored + fn write_at(&self, bufs: &[IoVec<'_>], offset: u64) -> io::Result; + + /// Returns the current position within the file. + /// + /// This corresponds to the `__wasi_fd_tell` syscall and is similar to + /// `seek` where you offset 0 bytes from the current position. + fn tell(&self) -> io::Result; + + /// Adjust the flags associated with this file. + /// + /// This corresponds to the `__wasi_fd_fdstat_set_flags` syscall. + fn fdstat_set_flags(&self, flags: u16) -> io::Result<()>; + + /// Adjust the rights associated with this file. + /// + /// This corresponds to the `__wasi_fd_fdstat_set_rights` syscall. + fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()>; + + /// Provide file advisory information on a file descriptor. + /// + /// This corresponds to the `__wasi_fd_advise` syscall. + fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()>; + + /// Force the allocation of space in a file. + /// + /// This corresponds to the `__wasi_fd_allocate` syscall. + fn allocate(&self, offset: u64, len: u64) -> io::Result<()>; + + /// Create a directory. + /// + /// This corresponds to the `__wasi_path_create_directory` syscall. + fn create_directory>(&self, dir: P) -> io::Result<()>; + + /// Read the contents of a symbolic link. + /// + /// This corresponds to the `__wasi_path_readlink` syscall. + fn read_link>(&self, path: P) -> io::Result; + + /// Return the attributes of a file or directory. + /// + /// This corresponds to the `__wasi_path_filestat_get` syscall. + fn metadata_at>(&self, lookup_flags: u32, path: P) -> io::Result; + + /// Unlink a file. + /// + /// This corresponds to the `__wasi_path_unlink_file` syscall. + fn remove_file>(&self, path: P) -> io::Result<()>; + + /// Remove a directory. + /// + /// This corresponds to the `__wasi_path_remove_directory` syscall. + fn remove_directory>(&self, path: P) -> io::Result<()>; +} + +// FIXME: bind __wasi_fd_fdstat_get - need to define a custom return type +// FIXME: bind __wasi_fd_readdir - can't return `ReadDir` since we only have entry name +// FIXME: bind __wasi_fd_filestat_set_times maybe? - on crates.io for unix +// FIXME: bind __wasi_path_filestat_set_times maybe? - on crates.io for unix +// FIXME: bind __wasi_poll_oneoff maybe? - probably should wait for I/O to settle +// FIXME: bind __wasi_random_get maybe? - on crates.io for unix + +impl FileExt for fs::File { + fn read_at(&self, bufs: &mut [IoVecMut<'_>], offset: u64) -> io::Result { + self.as_inner().fd().pread(bufs, offset) + } + + fn write_at(&self, bufs: &[IoVec<'_>], offset: u64) -> io::Result { + self.as_inner().fd().pwrite(bufs, offset) + } + + fn tell(&self) -> io::Result { + self.as_inner().fd().tell() + } + + fn fdstat_set_flags(&self, flags: u16) -> io::Result<()> { + self.as_inner().fd().set_flags(flags) + } + + fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()> { + self.as_inner().fd().set_rights(rights, inheriting) + } + + fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()> { + self.as_inner().fd().advise(offset, len, advice) + } + + fn allocate(&self, offset: u64, len: u64) -> io::Result<()> { + self.as_inner().fd().allocate(offset, len) + } + + fn create_directory>(&self, dir: P) -> io::Result<()> { + self.as_inner() + .fd() + .create_directory(dir.as_ref().as_os_str().as_bytes()) + } + + fn read_link>(&self, path: P) -> io::Result { + self.as_inner().read_link(path.as_ref()) + } + + fn metadata_at>(&self, lookup_flags: u32, path: P) -> io::Result { + let m = self.as_inner().metadata_at(lookup_flags, path.as_ref())?; + Ok(FromInner::from_inner(m)) + } + + fn remove_file>(&self, path: P) -> io::Result<()> { + self.as_inner() + .fd() + .unlink_file(path.as_ref().as_os_str().as_bytes()) + } + + fn remove_directory>(&self, path: P) -> io::Result<()> { + self.as_inner() + .fd() + .remove_directory(path.as_ref().as_os_str().as_bytes()) + } +} + +/// WASI-specific extensions to [`fs::OpenOptions`]. +/// +/// [`fs::OpenOptions`]: ../../../../std/fs/struct.OpenOptions.html +pub trait OpenOptionsExt { + /// Pass custom `dirflags` argument to `__wasi_path_open`. + /// + /// This option configures the `dirflags` argument to the + /// `__wasi_path_open` syscall which `OpenOptions` will eventually call. The + /// `dirflags` argument configures how the file is looked up, currently + /// primarily affecting whether symlinks are followed or not. + /// + /// By default this value is `__WASI_LOOKUP_SYMLINK_FOLLOW`, or symlinks are + /// followed. You can call this method with 0 to disable following symlinks + fn lookup_flags(&mut self, flags: u32) -> &mut Self; + + /// Indicates whether `OpenOptions` must open a directory or not. + /// + /// This method will configure whether the `__WASI_O_DIRECTORY` flag is + /// passed when opening a file. When passed it will require that the opened + /// path is a directory. + /// + /// This option is by default `false` + fn directory(&mut self, dir: bool) -> &mut Self; + + /// Indicates whether `__WASI_FDFLAG_DSYNC` is passed in the `fs_flags` + /// field of `__wasi_path_open`. + /// + /// This option is by default `false` + fn dsync(&mut self, dsync: bool) -> &mut Self; + + /// Indicates whether `__WASI_FDFLAG_NONBLOCK` is passed in the `fs_flags` + /// field of `__wasi_path_open`. + /// + /// This option is by default `false` + fn nonblock(&mut self, nonblock: bool) -> &mut Self; + + /// Indicates whether `__WASI_FDFLAG_RSYNC` is passed in the `fs_flags` + /// field of `__wasi_path_open`. + /// + /// This option is by default `false` + fn rsync(&mut self, rsync: bool) -> &mut Self; + + /// Indicates whether `__WASI_FDFLAG_SYNC` is passed in the `fs_flags` + /// field of `__wasi_path_open`. + /// + /// This option is by default `false` + fn sync(&mut self, sync: bool) -> &mut Self; + + /// Indicates the value that should be passed in for the `fs_rights_base` + /// parameter of `__wasi_path_open`. + /// + /// This option defaults based on the `read` and `write` configuration of + /// this `OpenOptions` builder. If this method is called, however, the + /// exact mask passed in will be used instead. + fn fs_rights_base(&mut self, rights: u64) -> &mut Self; + + /// Indicates the value that should be passed in for the + /// `fs_rights_inheriting` parameter of `__wasi_path_open`. + /// + /// The default for this option is the same value as what will be passed + /// for the `fs_rights_base` parameter but if this method is called then + /// the specified value will be used instead. + fn fs_rights_inheriting(&mut self, rights: u64) -> &mut Self; + + /// Open a file or directory. + /// + /// This corresponds to the `__wasi_path_open` syscall. + fn open_at>(&self, file: &File, path: P) -> io::Result; +} + +impl OpenOptionsExt for OpenOptions { + fn lookup_flags(&mut self, flags: u32) -> &mut OpenOptions { + self.as_inner_mut().lookup_flags(flags); + self + } + + fn directory(&mut self, dir: bool) -> &mut OpenOptions { + self.as_inner_mut().directory(dir); + self + } + + fn dsync(&mut self, enabled: bool) -> &mut OpenOptions { + self.as_inner_mut().dsync(enabled); + self + } + + fn nonblock(&mut self, enabled: bool) -> &mut OpenOptions { + self.as_inner_mut().nonblock(enabled); + self + } + + fn rsync(&mut self, enabled: bool) -> &mut OpenOptions { + self.as_inner_mut().rsync(enabled); + self + } + + fn sync(&mut self, enabled: bool) -> &mut OpenOptions { + self.as_inner_mut().sync(enabled); + self + } + + fn fs_rights_base(&mut self, rights: u64) -> &mut OpenOptions { + self.as_inner_mut().fs_rights_base(rights); + self + } + + fn fs_rights_inheriting(&mut self, rights: u64) -> &mut OpenOptions { + self.as_inner_mut().fs_rights_inheriting(rights); + self + } + + fn open_at>(&self, file: &File, path: P) -> io::Result { + let inner = file.as_inner().open_at(path.as_ref(), self.as_inner())?; + Ok(File::from_inner(inner)) + } +} + +/// WASI-specific extensions to [`fs::Metadata`]. +/// +/// [`fs::Metadata`]: ../../../../std/fs/struct.Metadata.html +pub trait MetadataExt { + /// Returns the `st_dev` field of the internal `__wasi_filestat_t` + fn dev(&self) -> u64; + /// Returns the `st_ino` field of the internal `__wasi_filestat_t` + fn ino(&self) -> u64; + /// Returns the `st_nlink` field of the internal `__wasi_filestat_t` + fn nlink(&self) -> u32; + /// Returns the `st_atim` field of the internal `__wasi_filestat_t` + fn atim(&self) -> u64; + /// Returns the `st_mtim` field of the internal `__wasi_filestat_t` + fn mtim(&self) -> u64; + /// Returns the `st_ctim` field of the internal `__wasi_filestat_t` + fn ctim(&self) -> u64; +} + +impl MetadataExt for fs::Metadata { + fn dev(&self) -> u64 { + self.as_inner().as_wasi().st_dev + } + fn ino(&self) -> u64 { + self.as_inner().as_wasi().st_ino + } + fn nlink(&self) -> u32 { + self.as_inner().as_wasi().st_nlink + } + fn atim(&self) -> u64 { + self.as_inner().as_wasi().st_atim + } + fn mtim(&self) -> u64 { + self.as_inner().as_wasi().st_mtim + } + fn ctim(&self) -> u64 { + self.as_inner().as_wasi().st_ctim + } +} + +/// WASI-specific extensions for [`FileType`]. +/// +/// Adds support for special WASI file types such as block/character devices, +/// pipes, and sockets. +/// +/// [`FileType`]: ../../../../std/fs/struct.FileType.html +pub trait FileTypeExt { + /// Returns `true` if this file type is a block device. + fn is_block_device(&self) -> bool; + /// Returns `true` if this file type is a character device. + fn is_character_device(&self) -> bool; + /// Returns `true` if this file type is a socket datagram. + fn is_socket_dgram(&self) -> bool; + /// Returns `true` if this file type is a socket stream. + fn is_socket_stream(&self) -> bool; +} + +impl FileTypeExt for fs::FileType { + fn is_block_device(&self) -> bool { + self.as_inner().bits() == libc::__WASI_FILETYPE_BLOCK_DEVICE + } + fn is_character_device(&self) -> bool { + self.as_inner().bits() == libc::__WASI_FILETYPE_CHARACTER_DEVICE + } + fn is_socket_dgram(&self) -> bool { + self.as_inner().bits() == libc::__WASI_FILETYPE_SOCKET_DGRAM + } + fn is_socket_stream(&self) -> bool { + self.as_inner().bits() == libc::__WASI_FILETYPE_SOCKET_STREAM + } +} + +/// WASI-specific extension methods for [`fs::DirEntry`]. +/// +/// [`fs::DirEntry`]: ../../../../std/fs/struct.DirEntry.html +pub trait DirEntryExt { + /// Returns the underlying `d_ino` field of the `__wasi_dirent_t` + fn ino(&self) -> u64; +} + +impl DirEntryExt for fs::DirEntry { + fn ino(&self) -> u64 { + self.as_inner().ino() + } +} + +/// Create a hard link. +/// +/// This corresponds to the `__wasi_path_link` syscall. +pub fn link, U: AsRef>( + old_fd: &File, + old_flags: u32, + old_path: P, + new_fd: &File, + new_path: U, +) -> io::Result<()> { + old_fd.as_inner().fd().link( + old_flags, + old_path.as_ref().as_os_str().as_bytes(), + new_fd.as_inner().fd(), + new_path.as_ref().as_os_str().as_bytes(), + ) +} + +/// Rename a file or directory. +/// +/// This corresponds to the `__wasi_path_rename` syscall. +pub fn rename, U: AsRef>( + old_fd: &File, + old_path: P, + new_fd: &File, + new_path: U, +) -> io::Result<()> { + old_fd.as_inner().fd().rename( + old_path.as_ref().as_os_str().as_bytes(), + new_fd.as_inner().fd(), + new_path.as_ref().as_os_str().as_bytes(), + ) +} + +/// Create a symbolic link. +/// +/// This corresponds to the `__wasi_path_symlink` syscall. +pub fn symlink, U: AsRef>( + old_path: P, + fd: &File, + new_path: U, +) -> io::Result<()> { + fd.as_inner().fd().symlink( + old_path.as_ref().as_os_str().as_bytes(), + new_path.as_ref().as_os_str().as_bytes(), + ) +} diff --git a/src/libstd/sys/wasi/ext/io.rs b/src/libstd/sys/wasi/ext/io.rs new file mode 100644 index 0000000000000..cf75a96d28c1a --- /dev/null +++ b/src/libstd/sys/wasi/ext/io.rs @@ -0,0 +1,87 @@ +//! WASI-specific extensions to general I/O primitives + +#![unstable(feature = "wasi_ext", issue = "0")] + +use crate::fs; +use crate::io; +use crate::sys; +use crate::sys_common::{AsInner, FromInner, IntoInner}; + +/// Raw file descriptors. +pub type RawFd = u32; + +/// A trait to extract the raw WASI file descriptor from an underlying +/// object. +pub trait AsRawFd { + /// Extracts the raw file descriptor. + /// + /// This method does **not** pass ownership of the raw file descriptor + /// to the caller. The descriptor is only guaranteed to be valid while + /// the original object has not yet been destroyed. + fn as_raw_fd(&self) -> RawFd; +} + +/// A trait to express the ability to construct an object from a raw file +/// descriptor. +pub trait FromRawFd { + /// Constructs a new instance of `Self` from the given raw file + /// descriptor. + /// + /// This function **consumes ownership** of the specified file + /// descriptor. The returned object will take responsibility for closing + /// it when the object goes out of scope. + /// + /// This function is also unsafe as the primitives currently returned + /// have the contract that they are the sole owner of the file + /// descriptor they are wrapping. Usage of this function could + /// accidentally allow violating this contract which can cause memory + /// unsafety in code that relies on it being true. + unsafe fn from_raw_fd(fd: RawFd) -> Self; +} + +/// A trait to express the ability to consume an object and acquire ownership of +/// its raw file descriptor. +pub trait IntoRawFd { + /// Consumes this object, returning the raw underlying file descriptor. + /// + /// This function **transfers ownership** of the underlying file descriptor + /// to the caller. Callers are then the unique owners of the file descriptor + /// and must close the descriptor once it's no longer needed. + fn into_raw_fd(self) -> RawFd; +} + +impl AsRawFd for fs::File { + fn as_raw_fd(&self) -> RawFd { + self.as_inner().fd().as_raw() + } +} + +impl FromRawFd for fs::File { + unsafe fn from_raw_fd(fd: RawFd) -> fs::File { + fs::File::from_inner(sys::fs::File::from_inner(fd)) + } +} + +impl IntoRawFd for fs::File { + fn into_raw_fd(self) -> RawFd { + self.into_inner().into_fd().into_raw() + } +} + +impl AsRawFd for io::Stdin { + fn as_raw_fd(&self) -> RawFd { + libc::STDIN_FILENO as u32 + } +} + +impl AsRawFd for io::Stdout { + fn as_raw_fd(&self) -> RawFd { + libc::STDOUT_FILENO as u32 + } +} + +impl AsRawFd for io::Stderr { + fn as_raw_fd(&self) -> RawFd { + libc::STDERR_FILENO as u32 + } +} diff --git a/src/libstd/sys/wasi/ext/mod.rs b/src/libstd/sys/wasi/ext/mod.rs index 877b9ed89d81e..1c24b244b8cd0 100644 --- a/src/libstd/sys/wasi/ext/mod.rs +++ b/src/libstd/sys/wasi/ext/mod.rs @@ -1,4 +1,6 @@ pub mod ffi; +pub mod fs; +pub mod io; /// A prelude for conveniently writing platform-specific code. /// @@ -7,4 +9,10 @@ pub mod ffi; pub mod prelude { #[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")] pub use crate::sys::ext::ffi::{OsStringExt, OsStrExt}; + #[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")] + pub use crate::sys::ext::fs::{FileExt, DirEntryExt, MetadataExt, OpenOptionsExt}; + #[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")] + pub use crate::sys::ext::fs::FileTypeExt; + #[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")] + pub use crate::sys::ext::io::{AsRawFd, IntoRawFd, FromRawFd}; } diff --git a/src/libstd/sys/wasi/fd.rs b/src/libstd/sys/wasi/fd.rs index 29e1880b8f135..0b68b6f4d9c72 100644 --- a/src/libstd/sys/wasi/fd.rs +++ b/src/libstd/sys/wasi/fd.rs @@ -6,6 +6,7 @@ use crate::net::Shutdown; use crate::sys::cvt_wasi; use libc::{self, c_char, c_void}; +#[derive(Debug)] pub struct WasiFd { fd: libc::__wasi_fd_t, } @@ -52,6 +53,16 @@ impl WasiFd { WasiFd { fd } } + pub fn into_raw(self) -> libc::__wasi_fd_t { + let ret = self.fd; + mem::forget(self); + ret + } + + pub fn as_raw(&self) -> libc::__wasi_fd_t { + self.fd + } + pub fn datasync(&self) -> io::Result<()> { cvt_wasi(unsafe { libc::__wasi_fd_datasync(self.fd) }) } @@ -123,7 +134,7 @@ impl WasiFd { cvt_wasi(unsafe { libc::__wasi_fd_allocate(self.fd, offset, len) }) } - pub fn crate_directory(&self, path: &[u8]) -> io::Result<()> { + pub fn create_directory(&self, path: &[u8]) -> io::Result<()> { cvt_wasi(unsafe { libc::__wasi_path_create_directory(self.fd, path.as_ptr() as *const c_char, path.len()) }) @@ -217,7 +228,9 @@ impl WasiFd { }) } - // FIXME: __wasi_fd_filestat_get + pub fn filestat_get(&self, buf: *mut libc::__wasi_filestat_t) -> io::Result<()> { + cvt_wasi(unsafe { libc::__wasi_fd_filestat_get(self.fd, buf) }) + } pub fn filestat_set_times( &self, @@ -232,7 +245,22 @@ impl WasiFd { cvt_wasi(unsafe { libc::__wasi_fd_filestat_set_size(self.fd, size) }) } - // FIXME: __wasi_path_filestat_get + pub fn path_filestat_get( + &self, + flags: LookupFlags, + path: &[u8], + buf: *mut libc::__wasi_filestat_t, + ) -> io::Result<()> { + cvt_wasi(unsafe { + libc::__wasi_path_filestat_get( + self.fd, + flags, + path.as_ptr() as *const c_char, + path.len(), + buf, + ) + }) + } pub fn path_filestat_set_times( &self, diff --git a/src/libstd/sys/wasi/fs.rs b/src/libstd/sys/wasi/fs.rs index 68c8e9356a89a..7b1c2bd79cc16 100644 --- a/src/libstd/sys/wasi/fs.rs +++ b/src/libstd/sys/wasi/fs.rs @@ -1,138 +1,144 @@ -use crate::ffi::OsString; +use crate::collections::HashMap; +use crate::ffi::{OsStr, OsString}; use crate::fmt; -use crate::hash::{Hash, Hasher}; -use crate::io::{self, SeekFrom}; +use crate::io::{self, IoVec, IoVecMut, SeekFrom}; +use crate::iter; +use crate::mem::{self, ManuallyDrop}; +use crate::os::wasi::ffi::{OsStrExt, OsStringExt}; use crate::path::{Path, PathBuf}; +use crate::ptr; +use crate::sync::atomic::{AtomicPtr, Ordering::SeqCst}; +use crate::sync::Arc; +use crate::sys::fd::{DirCookie, WasiFd}; use crate::sys::time::SystemTime; -use crate::sys::{unsupported, Void}; +use crate::sys::{cvt_wasi, unsupported}; +use crate::sys_common::FromInner; -pub struct File(Void); +pub use crate::sys_common::fs::copy; +pub use crate::sys_common::fs::remove_dir_all; -pub struct FileAttr(Void); +pub struct File { + fd: WasiFd, +} + +#[derive(Clone)] +pub struct FileAttr { + meta: libc::__wasi_filestat_t, +} -pub struct ReadDir(Void); +pub struct ReadDir { + inner: Arc, + cookie: Option, + buf: Vec, + offset: usize, + cap: usize, +} -pub struct DirEntry(Void); +struct ReadDirInner { + root: PathBuf, + dir: File, +} + +pub struct DirEntry { + meta: libc::__wasi_dirent_t, + name: Vec, + inner: Arc, +} -#[derive(Clone, Debug)] -pub struct OpenOptions { } +#[derive(Clone, Debug, Default)] +pub struct OpenOptions { + read: bool, + write: bool, + dirflags: libc::__wasi_lookupflags_t, + fdflags: libc::__wasi_fdflags_t, + oflags: libc::__wasi_oflags_t, + rights_base: Option, + rights_inheriting: Option, +} -pub struct FilePermissions(Void); +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct FilePermissions { + readonly: bool, +} -pub struct FileType(Void); +#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] +pub struct FileType { + bits: libc::__wasi_filetype_t, +} #[derive(Debug)] -pub struct DirBuilder { } +pub struct DirBuilder {} impl FileAttr { + fn zero() -> FileAttr { + FileAttr { + meta: unsafe { mem::zeroed() }, + } + } + pub fn size(&self) -> u64 { - match self.0 {} + self.meta.st_size } pub fn perm(&self) -> FilePermissions { - match self.0 {} + // not currently implemented in wasi yet + FilePermissions { readonly: false } } pub fn file_type(&self) -> FileType { - match self.0 {} + FileType { + bits: self.meta.st_filetype, + } } pub fn modified(&self) -> io::Result { - match self.0 {} + Ok(SystemTime::from_wasi_timestamp(self.meta.st_mtim)) } pub fn accessed(&self) -> io::Result { - match self.0 {} + Ok(SystemTime::from_wasi_timestamp(self.meta.st_atim)) } pub fn created(&self) -> io::Result { - match self.0 {} + Ok(SystemTime::from_wasi_timestamp(self.meta.st_ctim)) } -} -impl Clone for FileAttr { - fn clone(&self) -> FileAttr { - match self.0 {} + pub fn as_wasi(&self) -> &libc::__wasi_filestat_t { + &self.meta } } impl FilePermissions { pub fn readonly(&self) -> bool { - match self.0 {} - } - - pub fn set_readonly(&mut self, _readonly: bool) { - match self.0 {} - } -} - -impl Clone for FilePermissions { - fn clone(&self) -> FilePermissions { - match self.0 {} - } -} - -impl PartialEq for FilePermissions { - fn eq(&self, _other: &FilePermissions) -> bool { - match self.0 {} + self.readonly } -} - -impl Eq for FilePermissions { -} -impl fmt::Debug for FilePermissions { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.0 {} + pub fn set_readonly(&mut self, readonly: bool) { + self.readonly = readonly; } } impl FileType { pub fn is_dir(&self) -> bool { - match self.0 {} + self.bits == libc::__WASI_FILETYPE_DIRECTORY } pub fn is_file(&self) -> bool { - match self.0 {} + self.bits == libc::__WASI_FILETYPE_REGULAR_FILE } pub fn is_symlink(&self) -> bool { - match self.0 {} - } -} - -impl Clone for FileType { - fn clone(&self) -> FileType { - match self.0 {} - } -} - -impl Copy for FileType {} - -impl PartialEq for FileType { - fn eq(&self, _other: &FileType) -> bool { - match self.0 {} - } -} - -impl Eq for FileType { -} - -impl Hash for FileType { - fn hash(&self, _h: &mut H) { - match self.0 {} + self.bits == libc::__WASI_FILETYPE_SYMBOLIC_LINK } -} -impl fmt::Debug for FileType { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.0 {} + pub fn bits(&self) -> libc::__wasi_filetype_t { + self.bits } } impl fmt::Debug for ReadDir { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.0 {} + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ReadDir").finish() } } @@ -140,155 +146,547 @@ impl Iterator for ReadDir { type Item = io::Result; fn next(&mut self) -> Option> { - match self.0 {} + loop { + // If we've reached the capacity of our buffer then we need to read + // some more from the OS, otherwise we pick up at our old offset. + let offset = if self.offset == self.cap { + let cookie = self.cookie.take()?; + match self.inner.dir.fd.readdir(&mut self.buf, cookie) { + Ok(bytes) => self.cap = bytes, + Err(e) => return Some(Err(e)), + } + self.offset = 0; + self.cookie = Some(cookie); + + // If we didn't actually read anything, this is in theory the + // end of the directory. + if self.cap == 0 { + self.cookie = None; + return None; + } + + 0 + } else { + self.offset + }; + let data = &self.buf[offset..self.cap]; + + // If we're not able to read a directory entry then that means it + // must have been truncated at the end of the buffer, so reset our + // offset so we can go back and reread into the buffer, picking up + // where we last left off. + let dirent_size = mem::size_of::(); + if data.len() < dirent_size { + assert!(self.cookie.is_some()); + assert!(self.buf.len() >= dirent_size); + self.offset = self.cap; + continue; + } + let (dirent, data) = data.split_at(dirent_size); + let dirent = + unsafe { ptr::read_unaligned(dirent.as_ptr() as *const libc::__wasi_dirent_t) }; + + // If the file name was truncated, then we need to reinvoke + // `readdir` so we truncate our buffer to start over and reread this + // descriptor. Note that if our offset is 0 that means the file name + // is massive and we need a bigger buffer. + if data.len() < dirent.d_namlen as usize { + if offset == 0 { + let amt_to_add = self.buf.capacity(); + self.buf.extend(iter::repeat(0).take(amt_to_add)); + } + assert!(self.cookie.is_some()); + self.offset = self.cap; + continue; + } + self.cookie = Some(dirent.d_next); + self.offset = offset + dirent_size + dirent.d_namlen as usize; + + let name = &data[..(dirent.d_namlen as usize)]; + + // These names are skipped on all other platforms, so let's skip + // them here too + if name == b"." || name == b".." { + continue; + } + + return Some(Ok(DirEntry { + meta: dirent, + name: name.to_vec(), + inner: self.inner.clone(), + })); + } } } impl DirEntry { pub fn path(&self) -> PathBuf { - match self.0 {} + let name = OsStr::from_bytes(&self.name); + self.inner.root.join(name) } pub fn file_name(&self) -> OsString { - match self.0 {} + OsString::from_vec(self.name.clone()) } pub fn metadata(&self) -> io::Result { - match self.0 {} + metadata_at(&self.inner.dir.fd, 0, OsStr::from_bytes(&self.name).as_ref()) } pub fn file_type(&self) -> io::Result { - match self.0 {} + Ok(FileType { + bits: self.meta.d_type, + }) + } + + pub fn ino(&self) -> libc::__wasi_inode_t { + self.meta.d_ino } } impl OpenOptions { pub fn new() -> OpenOptions { - OpenOptions { } + let mut base = OpenOptions::default(); + base.dirflags = libc::__WASI_LOOKUP_SYMLINK_FOLLOW; + return base; + } + + pub fn read(&mut self, read: bool) { + self.read = read; + } + + pub fn write(&mut self, write: bool) { + self.write = write; + } + + pub fn truncate(&mut self, truncate: bool) { + self.oflag(libc::__WASI_O_TRUNC, truncate); + } + + pub fn create(&mut self, create: bool) { + self.oflag(libc::__WASI_O_CREAT, create); + } + + pub fn create_new(&mut self, create_new: bool) { + self.oflag(libc::__WASI_O_EXCL, create_new); + self.oflag(libc::__WASI_O_CREAT, create_new); + } + + pub fn directory(&mut self, directory: bool) { + self.oflag(libc::__WASI_O_DIRECTORY, directory); + } + + fn oflag(&mut self, bit: libc::__wasi_oflags_t, set: bool) { + if set { + self.oflags |= bit; + } else { + self.oflags &= !bit; + } + } + + pub fn append(&mut self, set: bool) { + self.fdflag(libc::__WASI_FDFLAG_APPEND, set); + } + + pub fn dsync(&mut self, set: bool) { + self.fdflag(libc::__WASI_FDFLAG_DSYNC, set); + } + + pub fn nonblock(&mut self, set: bool) { + self.fdflag(libc::__WASI_FDFLAG_NONBLOCK, set); + } + + pub fn rsync(&mut self, set: bool) { + self.fdflag(libc::__WASI_FDFLAG_RSYNC, set); + } + + pub fn sync(&mut self, set: bool) { + self.fdflag(libc::__WASI_FDFLAG_SYNC, set); + } + + fn fdflag(&mut self, bit: libc::__wasi_fdflags_t, set: bool) { + if set { + self.fdflags |= bit; + } else { + self.fdflags &= !bit; + } + } + + pub fn fs_rights_base(&mut self, rights: libc::__wasi_rights_t) { + self.rights_base = Some(rights); + } + + pub fn fs_rights_inheriting(&mut self, rights: libc::__wasi_rights_t) { + self.rights_inheriting = Some(rights); + } + + fn rights_base(&self) -> libc::__wasi_rights_t { + if let Some(rights) = self.rights_base { + return rights; + } + + // If rights haven't otherwise been specified try to pick a reasonable + // set. This can always be overridden by users via extension traits, and + // implementations may give us fewer rights silently than we ask for. So + // given that, just look at `read` and `write` and bucket permissions + // based on that. + let mut base = 0; + if self.read { + base |= libc::__WASI_RIGHT_FD_READ; + base |= libc::__WASI_RIGHT_FD_READDIR; + } + if self.write { + base |= libc::__WASI_RIGHT_FD_WRITE; + base |= libc::__WASI_RIGHT_FD_DATASYNC; + base |= libc::__WASI_RIGHT_FD_ALLOCATE; + base |= libc::__WASI_RIGHT_FD_FILESTAT_SET_SIZE; + } + + // FIXME: some of these should probably be read-only or write-only... + base |= libc::__WASI_RIGHT_FD_ADVISE; + base |= libc::__WASI_RIGHT_FD_FDSTAT_SET_FLAGS; + base |= libc::__WASI_RIGHT_FD_FILESTAT_SET_TIMES; + base |= libc::__WASI_RIGHT_FD_SEEK; + base |= libc::__WASI_RIGHT_FD_SYNC; + base |= libc::__WASI_RIGHT_FD_TELL; + base |= libc::__WASI_RIGHT_PATH_CREATE_DIRECTORY; + base |= libc::__WASI_RIGHT_PATH_CREATE_FILE; + base |= libc::__WASI_RIGHT_PATH_FILESTAT_GET; + base |= libc::__WASI_RIGHT_PATH_LINK_SOURCE; + base |= libc::__WASI_RIGHT_PATH_LINK_TARGET; + base |= libc::__WASI_RIGHT_PATH_OPEN; + base |= libc::__WASI_RIGHT_PATH_READLINK; + base |= libc::__WASI_RIGHT_PATH_REMOVE_DIRECTORY; + base |= libc::__WASI_RIGHT_PATH_RENAME_SOURCE; + base |= libc::__WASI_RIGHT_PATH_RENAME_TARGET; + base |= libc::__WASI_RIGHT_PATH_SYMLINK; + base |= libc::__WASI_RIGHT_PATH_UNLINK_FILE; + base |= libc::__WASI_RIGHT_POLL_FD_READWRITE; + + return base; } - pub fn read(&mut self, _read: bool) { } - pub fn write(&mut self, _write: bool) { } - pub fn append(&mut self, _append: bool) { } - pub fn truncate(&mut self, _truncate: bool) { } - pub fn create(&mut self, _create: bool) { } - pub fn create_new(&mut self, _create_new: bool) { } + fn rights_inheriting(&self) -> libc::__wasi_rights_t { + self.rights_inheriting.unwrap_or_else(|| self.rights_base()) + } + + pub fn lookup_flags(&mut self, flags: libc::__wasi_lookupflags_t) { + self.dirflags = flags; + } } impl File { - pub fn open(_path: &Path, _opts: &OpenOptions) -> io::Result { - unsupported() + pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { + let (dir, file) = open_parent(path)?; + open_at(&dir, file, opts) + } + + pub fn open_at(&self, path: &Path, opts: &OpenOptions) -> io::Result { + open_at(&self.fd, path, opts) } pub fn file_attr(&self) -> io::Result { - match self.0 {} + let mut ret = FileAttr::zero(); + self.fd.filestat_get(&mut ret.meta)?; + Ok(ret) + } + + pub fn metadata_at( + &self, + flags: libc::__wasi_lookupflags_t, + path: &Path, + ) -> io::Result { + metadata_at(&self.fd, flags, path) } pub fn fsync(&self) -> io::Result<()> { - match self.0 {} + self.fd.sync() } pub fn datasync(&self) -> io::Result<()> { - match self.0 {} + self.fd.datasync() + } + + pub fn truncate(&self, size: u64) -> io::Result<()> { + self.fd.filestat_set_size(size) } - pub fn truncate(&self, _size: u64) -> io::Result<()> { - match self.0 {} + pub fn read(&self, buf: &mut [u8]) -> io::Result { + self.read_vectored(&mut [IoVecMut::new(buf)]) } - pub fn read(&self, _buf: &mut [u8]) -> io::Result { - match self.0 {} + pub fn read_vectored(&self, bufs: &mut [IoVecMut<'_>]) -> io::Result { + self.fd.read(bufs) } - pub fn write(&self, _buf: &[u8]) -> io::Result { - match self.0 {} + pub fn write(&self, buf: &[u8]) -> io::Result { + self.write_vectored(&[IoVec::new(buf)]) + } + + pub fn write_vectored(&self, bufs: &[IoVec<'_>]) -> io::Result { + self.fd.write(bufs) } pub fn flush(&self) -> io::Result<()> { - match self.0 {} + Ok(()) } - pub fn seek(&self, _pos: SeekFrom) -> io::Result { - match self.0 {} + pub fn seek(&self, pos: SeekFrom) -> io::Result { + self.fd.seek(pos) } pub fn duplicate(&self) -> io::Result { - match self.0 {} + // https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-rationale.md#why-no-dup + unsupported() } pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> { - match self.0 {} + // Permissions haven't been fully figured out in wasi yet, so this is + // likely temporary + unsupported() } - pub fn diverge(&self) -> ! { - match self.0 {} + pub fn fd(&self) -> &WasiFd { + &self.fd } -} -impl DirBuilder { - pub fn new() -> DirBuilder { - DirBuilder { } + pub fn into_fd(self) -> WasiFd { + self.fd } - pub fn mkdir(&self, _p: &Path) -> io::Result<()> { - unsupported() + pub fn read_link(&self, file: &Path) -> io::Result { + read_link(&self.fd, file) } } -impl fmt::Debug for File { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.0 {} +impl FromInner for File { + fn from_inner(fd: u32) -> File { + unsafe { + File { + fd: WasiFd::from_raw(fd), + } + } } } -pub fn readdir(_p: &Path) -> io::Result { - unsupported() -} +impl DirBuilder { + pub fn new() -> DirBuilder { + DirBuilder {} + } -pub fn unlink(_p: &Path) -> io::Result<()> { - unsupported() + pub fn mkdir(&self, p: &Path) -> io::Result<()> { + let (dir, file) = open_parent(p)?; + dir.create_directory(file.as_os_str().as_bytes()) + } } -pub fn rename(_old: &Path, _new: &Path) -> io::Result<()> { +impl fmt::Debug for File { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("File") + .field("fd", &self.fd.as_raw()) + .finish() + } +} + +pub fn readdir(p: &Path) -> io::Result { + let mut opts = OpenOptions::new(); + opts.directory(true); + opts.read(true); + let dir = File::open(p, &opts)?; + Ok(ReadDir { + cookie: Some(0), + buf: vec![0; 128], + offset: 0, + cap: 0, + inner: Arc::new(ReadDirInner { + dir, + root: p.to_path_buf(), + }), + }) +} + +pub fn unlink(p: &Path) -> io::Result<()> { + let (dir, file) = open_parent(p)?; + dir.unlink_file(file.as_os_str().as_bytes()) +} + +pub fn rename(old: &Path, new: &Path) -> io::Result<()> { + let (old, old_file) = open_parent(old)?; + let (new, new_file) = open_parent(new)?; + old.rename( + old_file.as_os_str().as_bytes(), + &new, + new_file.as_os_str().as_bytes(), + ) +} + +pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { + // Permissions haven't been fully figured out in wasi yet, so this is + // likely temporary unsupported() } -pub fn set_perm(_p: &Path, perm: FilePermissions) -> io::Result<()> { - match perm.0 {} +pub fn rmdir(p: &Path) -> io::Result<()> { + let (dir, file) = open_parent(p)?; + dir.remove_directory(file.as_os_str().as_bytes()) } -pub fn rmdir(_p: &Path) -> io::Result<()> { - unsupported() +pub fn readlink(p: &Path) -> io::Result { + let (dir, file) = open_parent(p)?; + read_link(&dir, file) } -pub fn remove_dir_all(_path: &Path) -> io::Result<()> { - unsupported() +fn read_link(fd: &WasiFd, file: &Path) -> io::Result { + // Try to get a best effort initial capacity for the vector we're going to + // fill. Note that if it's not a symlink we don't use a file to avoid + // allocating gigabytes if you read_link a huge movie file by accident. + // Additionally we add 1 to the initial size so if it doesn't change until + // when we call `readlink` the returned length will be less than the + // capacity, guaranteeing that we got all the data. + let meta = metadata_at(fd, 0, file)?; + let initial_size = if meta.file_type().is_symlink() { + (meta.size() as usize).saturating_add(1) + } else { + 1 // this'll fail in just a moment + }; + + // Now that we have an initial guess of how big to make our buffer, call + // `readlink` in a loop until it fails or reports it filled fewer bytes than + // we asked for, indicating we got everything. + let file = file.as_os_str().as_bytes(); + let mut destination = vec![0u8; initial_size]; + loop { + let len = fd.readlink(file, &mut destination)?; + if len < destination.len() { + destination.truncate(len); + destination.shrink_to_fit(); + return Ok(PathBuf::from(OsString::from_vec(destination))); + } + let amt_to_add = destination.len(); + destination.extend(iter::repeat(0).take(amt_to_add)); + } } -pub fn readlink(_p: &Path) -> io::Result { - unsupported() +pub fn symlink(src: &Path, dst: &Path) -> io::Result<()> { + let (dst, dst_file) = open_parent(dst)?; + dst.symlink(src.as_os_str().as_bytes(), dst_file.as_os_str().as_bytes()) } -pub fn symlink(_src: &Path, _dst: &Path) -> io::Result<()> { - unsupported() +pub fn link(src: &Path, dst: &Path) -> io::Result<()> { + let (src, src_file) = open_parent(src)?; + let (dst, dst_file) = open_parent(dst)?; + src.link( + libc::__WASI_LOOKUP_SYMLINK_FOLLOW, + src_file.as_os_str().as_bytes(), + &dst, + dst_file.as_os_str().as_bytes(), + ) } -pub fn link(_src: &Path, _dst: &Path) -> io::Result<()> { - unsupported() +pub fn stat(p: &Path) -> io::Result { + let (dir, file) = open_parent(p)?; + metadata_at(&dir, libc::__WASI_LOOKUP_SYMLINK_FOLLOW, file) } -pub fn stat(_p: &Path) -> io::Result { - unsupported() +pub fn lstat(p: &Path) -> io::Result { + let (dir, file) = open_parent(p)?; + metadata_at(&dir, 0, file) } -pub fn lstat(_p: &Path) -> io::Result { - unsupported() +fn metadata_at( + fd: &WasiFd, + flags: libc::__wasi_lookupflags_t, + path: &Path, +) -> io::Result { + let mut ret = FileAttr::zero(); + fd.path_filestat_get(flags, path.as_os_str().as_bytes(), &mut ret.meta)?; + Ok(ret) } pub fn canonicalize(_p: &Path) -> io::Result { + // This seems to not be in wasi's API yet, and we may need to end up + // emulating it ourselves. For now just return an error. unsupported() } -pub fn copy(_from: &Path, _to: &Path) -> io::Result { - unsupported() +fn open_at(fd: &WasiFd, path: &Path, opts: &OpenOptions) -> io::Result { + let fd = fd.open( + opts.dirflags, + path.as_os_str().as_bytes(), + opts.oflags, + opts.rights_base(), + opts.rights_inheriting(), + opts.fdflags, + )?; + Ok(File { fd }) +} + +// FIXME: we shouldn't implement this. It'd be much better to share this between +// libc (the wasi-sysroot) and Rust as the logic here is likely far more tricky +// than what we're executing below. For now this is a stopgap to enable this +// module, but we should add an official API in upstream wasi-libc which looks +// like this. +// +// In the meantime this is highly unlikely to be correct. It allows some basic +// testing but is not at all robust. +fn open_parent(p: &Path) -> io::Result<(&'static WasiFd, &Path)> { + let map = preopened_map(); + for ancestor in p.ancestors() { + if let Some(fd) = map.get(ancestor) { + let tail = p.strip_prefix(ancestor).unwrap(); + let tail = if tail == Path::new("") { + ".".as_ref() + } else { + tail + }; + return Ok((fd, tail)) + } + } + let msg = format!("failed to find a preopened file descriptor to open {:?}", p); + return Err(io::Error::new(io::ErrorKind::Other, msg)); + + type Preopened = HashMap>; + fn preopened_map() -> &'static Preopened { + static PTR: AtomicPtr = AtomicPtr::new(ptr::null_mut()); + unsafe { + let ptr = PTR.load(SeqCst); + if !ptr.is_null() { + return &*ptr; + } + + let mut map = Box::new(HashMap::new()); + for fd in 3.. { + let mut buf = mem::zeroed(); + if cvt_wasi(libc::__wasi_fd_prestat_get(fd, &mut buf)).is_err() { + break; + } + if buf.pr_type != libc::__WASI_PREOPENTYPE_DIR { + continue; + } + let len = buf.u.dir.pr_name_len; + let mut v = vec![0u8; len]; + let res = cvt_wasi(libc::__wasi_fd_prestat_dir_name( + fd, + v.as_mut_ptr() as *mut i8, + v.len(), + )); + if res.is_err() { + continue; + } + let path = PathBuf::from(OsString::from_vec(v)); + map.insert(path, ManuallyDrop::new(WasiFd::from_raw(fd))); + } + let ptr = Box::into_raw(map); + match PTR.compare_exchange(ptr::null_mut(), ptr, SeqCst, SeqCst) { + Ok(_) => &*ptr, + + // If we lost the race for initialization clean up the map we + // made and just use the one that's already there + Err(other) => { + drop(Box::from_raw(ptr)); + &*other + } + } + } + } } diff --git a/src/libstd/sys/wasi/process.rs b/src/libstd/sys/wasi/process.rs index a02e009d95356..788b829f4bac9 100644 --- a/src/libstd/sys/wasi/process.rs +++ b/src/libstd/sys/wasi/process.rs @@ -67,8 +67,8 @@ impl From for Stdio { } impl From for Stdio { - fn from(file: File) -> Stdio { - file.diverge() + fn from(_file: File) -> Stdio { + panic!("unsupported") } } diff --git a/src/libstd/sys/wasi/time.rs b/src/libstd/sys/wasi/time.rs index e1b92e7c5a705..3f14c80928c67 100644 --- a/src/libstd/sys/wasi/time.rs +++ b/src/libstd/sys/wasi/time.rs @@ -57,6 +57,10 @@ impl SystemTime { SystemTime(current_time(libc::__WASI_CLOCK_REALTIME)) } + pub fn from_wasi_timestamp(ts: libc::__wasi_timestamp_t) -> SystemTime { + SystemTime(Duration::from_nanos(ts)) + } + pub fn sub_time(&self, other: &SystemTime) -> Result { self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0) diff --git a/src/libstd/sys_common/fs.rs b/src/libstd/sys_common/fs.rs new file mode 100644 index 0000000000000..7152fcd215c9a --- /dev/null +++ b/src/libstd/sys_common/fs.rs @@ -0,0 +1,41 @@ +#![allow(dead_code)] // not used on all platforms + +use crate::path::Path; +use crate::fs; +use crate::io::{self, Error, ErrorKind}; + +pub fn copy(from: &Path, to: &Path) -> io::Result { + if !from.is_file() { + return Err(Error::new(ErrorKind::InvalidInput, + "the source path is not an existing regular file")) + } + + let mut reader = fs::File::open(from)?; + let mut writer = fs::File::create(to)?; + let perm = reader.metadata()?.permissions(); + + let ret = io::copy(&mut reader, &mut writer)?; + fs::set_permissions(to, perm)?; + Ok(ret) +} + +pub fn remove_dir_all(path: &Path) -> io::Result<()> { + let filetype = fs::symlink_metadata(path)?.file_type(); + if filetype.is_symlink() { + fs::remove_file(path) + } else { + remove_dir_all_recursive(path) + } +} + +fn remove_dir_all_recursive(path: &Path) -> io::Result<()> { + for child in fs::read_dir(path)? { + let child = child?; + if child.file_type()?.is_dir() { + remove_dir_all_recursive(&child.path())?; + } else { + fs::remove_file(&child.path())?; + } + } + fs::remove_dir(path) +} diff --git a/src/libstd/sys_common/mod.rs b/src/libstd/sys_common/mod.rs index 883ab34f07c58..c911d772bf0fc 100644 --- a/src/libstd/sys_common/mod.rs +++ b/src/libstd/sys_common/mod.rs @@ -52,6 +52,7 @@ pub mod util; pub mod wtf8; pub mod bytestring; pub mod process; +pub mod fs; cfg_if! { if #[cfg(any(target_os = "cloudabi", From 5b292ecdb1ca33a81f80ac2ff8f2b9866498cb46 Mon Sep 17 00:00:00 2001 From: O01eg Date: Wed, 3 Apr 2019 18:55:37 +0300 Subject: [PATCH 11/11] Revert rust-lld place changes. --- src/bootstrap/dist.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs index a4d924d64ee78..a923f2d2ca37e 100644 --- a/src/bootstrap/dist.rs +++ b/src/bootstrap/dist.rs @@ -518,8 +518,7 @@ impl Step for Rustc { .join("bin") .join(&exe); // for the rationale about this rename check `compile::copy_lld_to_sysroot` - let dst = image.join(libdir_relative) - .join("rustlib") + let dst = image.join("lib/rustlib") .join(&*host) .join("bin") .join(&exe);