Skip to content

Commit 16fff93

Browse files
authored
Rollup merge of #59727 - alexcrichton:wasi-apis, r=fitzgen
wasi: Use shared API for preopened fds This commit updates the wasi target with supported added in WebAssembly/wasi-libc#10. That function allows both C and Rust to cooperate in how preopened files are managed, enabling us to learn about propened files through the same interface. The `open_parent` function in the wasi `fs` module was updated to avoid its own initialization of a global preopened map and instead delegate to libc to perform this functionality. This should both be more robust into the future in terms of handling path logic as well as ensuring the propened map is correctly set up at process boot time. This does currently require some unfortunate allocations on our side, but if that becomes an issue we can always paper over those in time!
2 parents d2936c3 + bb2c0d1 commit 16fff93

File tree

2 files changed

+87
-88
lines changed

2 files changed

+87
-88
lines changed

src/ci/docker/dist-various-2/build-wasi-toolchain.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export PATH=`pwd`/clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-14.04/bin:$PATH
1212
git clone https://github.com/CraneStation/wasi-sysroot
1313

1414
cd wasi-sysroot
15-
git reset --hard 320054e84f8f2440def3b1c8700cedb8fd697bf8
15+
git reset --hard e5f14be38362f1ab83302895a6e74b2ffd0e2302
1616
make -j$(nproc) INSTALL_DIR=/wasm32-unknown-wasi install
1717

1818
cd ..

src/libstd/sys/wasi/fs.rs

+86-87
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
use crate::collections::HashMap;
2-
use crate::ffi::{OsStr, OsString};
1+
use crate::ffi::{CStr, CString, OsStr, OsString};
32
use crate::fmt;
43
use crate::io::{self, IoVec, IoVecMut, SeekFrom};
54
use crate::iter;
65
use crate::mem::{self, ManuallyDrop};
76
use crate::os::wasi::ffi::{OsStrExt, OsStringExt};
87
use crate::path::{Path, PathBuf};
98
use crate::ptr;
10-
use crate::sync::atomic::{AtomicPtr, Ordering::SeqCst};
119
use crate::sync::Arc;
1210
use crate::sys::fd::{DirCookie, WasiFd};
1311
use crate::sys::time::SystemTime;
14-
use crate::sys::{cvt_wasi, unsupported};
12+
use crate::sys::unsupported;
1513
use crate::sys_common::FromInner;
1614

1715
pub use crate::sys_common::fs::copy;
@@ -230,7 +228,11 @@ impl DirEntry {
230228
}
231229

232230
pub fn metadata(&self) -> io::Result<FileAttr> {
233-
metadata_at(&self.inner.dir.fd, 0, OsStr::from_bytes(&self.name).as_ref())
231+
metadata_at(
232+
&self.inner.dir.fd,
233+
0,
234+
OsStr::from_bytes(&self.name).as_ref(),
235+
)
234236
}
235237

236238
pub fn file_type(&self) -> io::Result<FileType> {
@@ -377,8 +379,8 @@ impl OpenOptions {
377379

378380
impl File {
379381
pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
380-
let (dir, file) = open_parent(path)?;
381-
open_at(&dir, file, opts)
382+
let (dir, file) = open_parent(path, libc::__WASI_RIGHT_PATH_OPEN)?;
383+
open_at(&dir, &file, opts)
382384
}
383385

384386
pub fn open_at(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> {
@@ -475,7 +477,7 @@ impl DirBuilder {
475477
}
476478

477479
pub fn mkdir(&self, p: &Path) -> io::Result<()> {
478-
let (dir, file) = open_parent(p)?;
480+
let (dir, file) = open_parent(p, libc::__WASI_RIGHT_PATH_CREATE_DIRECTORY)?;
479481
dir.create_directory(file.as_os_str().as_bytes())
480482
}
481483
}
@@ -506,13 +508,13 @@ pub fn readdir(p: &Path) -> io::Result<ReadDir> {
506508
}
507509

508510
pub fn unlink(p: &Path) -> io::Result<()> {
509-
let (dir, file) = open_parent(p)?;
511+
let (dir, file) = open_parent(p, libc::__WASI_RIGHT_PATH_UNLINK_FILE)?;
510512
dir.unlink_file(file.as_os_str().as_bytes())
511513
}
512514

513515
pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
514-
let (old, old_file) = open_parent(old)?;
515-
let (new, new_file) = open_parent(new)?;
516+
let (old, old_file) = open_parent(old, libc::__WASI_RIGHT_PATH_RENAME_SOURCE)?;
517+
let (new, new_file) = open_parent(new, libc::__WASI_RIGHT_PATH_RENAME_TARGET)?;
516518
old.rename(
517519
old_file.as_os_str().as_bytes(),
518520
&new,
@@ -527,13 +529,13 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> {
527529
}
528530

529531
pub fn rmdir(p: &Path) -> io::Result<()> {
530-
let (dir, file) = open_parent(p)?;
532+
let (dir, file) = open_parent(p, libc::__WASI_RIGHT_PATH_REMOVE_DIRECTORY)?;
531533
dir.remove_directory(file.as_os_str().as_bytes())
532534
}
533535

534536
pub fn readlink(p: &Path) -> io::Result<PathBuf> {
535-
let (dir, file) = open_parent(p)?;
536-
read_link(&dir, file)
537+
let (dir, file) = open_parent(p, libc::__WASI_RIGHT_PATH_READLINK)?;
538+
read_link(&dir, &file)
537539
}
538540

539541
fn read_link(fd: &WasiFd, file: &Path) -> io::Result<PathBuf> {
@@ -568,13 +570,13 @@ fn read_link(fd: &WasiFd, file: &Path) -> io::Result<PathBuf> {
568570
}
569571

570572
pub fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
571-
let (dst, dst_file) = open_parent(dst)?;
573+
let (dst, dst_file) = open_parent(dst, libc::__WASI_RIGHT_PATH_SYMLINK)?;
572574
dst.symlink(src.as_os_str().as_bytes(), dst_file.as_os_str().as_bytes())
573575
}
574576

575577
pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
576-
let (src, src_file) = open_parent(src)?;
577-
let (dst, dst_file) = open_parent(dst)?;
578+
let (src, src_file) = open_parent(src, libc::__WASI_RIGHT_PATH_LINK_SOURCE)?;
579+
let (dst, dst_file) = open_parent(dst, libc::__WASI_RIGHT_PATH_LINK_TARGET)?;
578580
src.link(
579581
libc::__WASI_LOOKUP_SYMLINK_FOLLOW,
580582
src_file.as_os_str().as_bytes(),
@@ -584,13 +586,13 @@ pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
584586
}
585587

586588
pub fn stat(p: &Path) -> io::Result<FileAttr> {
587-
let (dir, file) = open_parent(p)?;
588-
metadata_at(&dir, libc::__WASI_LOOKUP_SYMLINK_FOLLOW, file)
589+
let (dir, file) = open_parent(p, libc::__WASI_RIGHT_PATH_FILESTAT_GET)?;
590+
metadata_at(&dir, libc::__WASI_LOOKUP_SYMLINK_FOLLOW, &file)
589591
}
590592

591593
pub fn lstat(p: &Path) -> io::Result<FileAttr> {
592-
let (dir, file) = open_parent(p)?;
593-
metadata_at(&dir, 0, file)
594+
let (dir, file) = open_parent(p, libc::__WASI_RIGHT_PATH_FILESTAT_GET)?;
595+
metadata_at(&dir, 0, &file)
594596
}
595597

596598
fn metadata_at(
@@ -621,72 +623,69 @@ fn open_at(fd: &WasiFd, path: &Path, opts: &OpenOptions) -> io::Result<File> {
621623
Ok(File { fd })
622624
}
623625

624-
// FIXME: we shouldn't implement this. It'd be much better to share this between
625-
// libc (the wasi-sysroot) and Rust as the logic here is likely far more tricky
626-
// than what we're executing below. For now this is a stopgap to enable this
627-
// module, but we should add an official API in upstream wasi-libc which looks
628-
// like this.
629-
//
630-
// In the meantime this is highly unlikely to be correct. It allows some basic
631-
// testing but is not at all robust.
632-
fn open_parent(p: &Path) -> io::Result<(&'static WasiFd, &Path)> {
633-
let map = preopened_map();
634-
for ancestor in p.ancestors() {
635-
if let Some(fd) = map.get(ancestor) {
636-
let tail = p.strip_prefix(ancestor).unwrap();
637-
let tail = if tail == Path::new("") {
638-
".".as_ref()
639-
} else {
640-
tail
641-
};
642-
return Ok((fd, tail))
643-
}
644-
}
645-
let msg = format!("failed to find a preopened file descriptor to open {:?}", p);
646-
return Err(io::Error::new(io::ErrorKind::Other, msg));
647-
648-
type Preopened = HashMap<PathBuf, ManuallyDrop<WasiFd>>;
649-
fn preopened_map() -> &'static Preopened {
650-
static PTR: AtomicPtr<Preopened> = AtomicPtr::new(ptr::null_mut());
651-
unsafe {
652-
let ptr = PTR.load(SeqCst);
653-
if !ptr.is_null() {
654-
return &*ptr;
655-
}
656-
657-
let mut map = Box::new(HashMap::new());
658-
for fd in 3.. {
659-
let mut buf = mem::zeroed();
660-
if cvt_wasi(libc::__wasi_fd_prestat_get(fd, &mut buf)).is_err() {
661-
break;
662-
}
663-
if buf.pr_type != libc::__WASI_PREOPENTYPE_DIR {
664-
continue;
665-
}
666-
let len = buf.u.dir.pr_name_len;
667-
let mut v = vec![0u8; len];
668-
let res = cvt_wasi(libc::__wasi_fd_prestat_dir_name(
669-
fd,
670-
v.as_mut_ptr() as *mut i8,
671-
v.len(),
672-
));
673-
if res.is_err() {
674-
continue;
675-
}
676-
let path = PathBuf::from(OsString::from_vec(v));
677-
map.insert(path, ManuallyDrop::new(WasiFd::from_raw(fd)));
678-
}
679-
let ptr = Box::into_raw(map);
680-
match PTR.compare_exchange(ptr::null_mut(), ptr, SeqCst, SeqCst) {
681-
Ok(_) => &*ptr,
682-
683-
// If we lost the race for initialization clean up the map we
684-
// made and just use the one that's already there
685-
Err(other) => {
686-
drop(Box::from_raw(ptr));
687-
&*other
688-
}
689-
}
626+
/// Attempts to open a bare path `p`.
627+
///
628+
/// WASI has no fundamental capability to do this. All syscalls and operations
629+
/// are relative to already-open file descriptors. The C library, however,
630+
/// manages a map of preopened file descriptors to their path, and then the C
631+
/// library provides an API to look at this. In other words, when you want to
632+
/// open a path `p`, you have to find a previously opened file descriptor in a
633+
/// global table and then see if `p` is relative to that file descriptor.
634+
///
635+
/// This function, if successful, will return two items:
636+
///
637+
/// * The first is a `ManuallyDrop<WasiFd>`. This represents a preopened file
638+
/// descriptor which we don't have ownership of, but we can use. You shouldn't
639+
/// actually drop the `fd`.
640+
///
641+
/// * The second is a path that should be a part of `p` and represents a
642+
/// relative traversal from the file descriptor specified to the desired
643+
/// location `p`.
644+
///
645+
/// If successful you can use the returned file descriptor to perform
646+
/// file-descriptor-relative operations on the path returned as well. The
647+
/// `rights` argument indicates what operations are desired on the returned file
648+
/// descriptor, and if successful the returned file descriptor should have the
649+
/// appropriate rights for performing `rights` actions.
650+
///
651+
/// Note that this can fail if `p` doesn't look like it can be opened relative
652+
/// to any preopened file descriptor.
653+
fn open_parent(
654+
p: &Path,
655+
rights: libc::__wasi_rights_t,
656+
) -> io::Result<(ManuallyDrop<WasiFd>, PathBuf)> {
657+
let p = CString::new(p.as_os_str().as_bytes())?;
658+
unsafe {
659+
let mut ret = ptr::null();
660+
let fd = __wasilibc_find_relpath(p.as_ptr(), rights, 0, &mut ret);
661+
if fd == -1 {
662+
let msg = format!(
663+
"failed to find a preopened file descriptor \
664+
through which {:?} could be opened",
665+
p
666+
);
667+
return Err(io::Error::new(io::ErrorKind::Other, msg));
690668
}
669+
let path = Path::new(OsStr::from_bytes(CStr::from_ptr(ret).to_bytes()));
670+
671+
// FIXME: right now `path` is a pointer into `p`, the `CString` above.
672+
// When we return `p` is deallocated and we can't use it, so we need to
673+
// currently separately allocate `path`. If this becomes an issue though
674+
// we should probably turn this into a closure-taking interface or take
675+
// `&CString` and then pass off `&Path` tied to the same lifetime.
676+
let path = path.to_path_buf();
677+
678+
return Ok((ManuallyDrop::new(WasiFd::from_raw(fd as u32)), path));
679+
}
680+
681+
// FIXME(rust-lang/libc#1314) use the `libc` crate for this when the API
682+
// there is published
683+
extern "C" {
684+
pub fn __wasilibc_find_relpath(
685+
path: *const libc::c_char,
686+
rights_base: libc::__wasi_rights_t,
687+
rights_inheriting: libc::__wasi_rights_t,
688+
relative_path: *mut *const libc::c_char,
689+
) -> libc::c_int;
691690
}
692691
}

0 commit comments

Comments
 (0)