Allow WASI to open directories without O_DIRECTORY (#6163)

* Allow WASI to open directories without O_DIRECTORY

The `O_DIRECTORY` flag is a request that open should fail if the named
path is not a directory. Opening a path which turns out to be a
directory is not supposed to fail if this flag is not specified.
However, wasi-common required callers to use it when opening
directories.

With this PR, we always open the path the same way whether or not the
`O_DIRECTORY` flag is specified. However, after opening it, we `stat` it
to check whether it turned out to be a directory, and determine which
operations the file descriptor should support accordingly. In addition,
we explicitly check whether the precondition defined by `O_DIRECTORY` is
satisfied.

Closes #4947 and closes #4967, which were earlier attempts at fixing the
same issue, but which had race conditions.

prtest:full

* Add tests from #4967/#4947

This test was authored by Roman Volosatovs <rvolosatovs@riseup.net> as
part of #4947.

* Tests: Close FDs before trying to unlink files

On Windows, when opening a path which might be a directory using
`CreateFile`, cap-primitives also removes the `FILE_SHARE_DELETE` mode.

That means that if we implement WASI's `path_open` such that it always
uses `CreateFile` on Windows, for both files and directories, then
holding an open file handle prevents deletion of that file.

So I'm changing these test programs to make sure they've closed the
handle before trying to delete the file.
This commit is contained in:
Jamey Sharp
2023-04-21 09:55:35 -07:00
committed by GitHub
parent 91d1d246cd
commit efdfc361f8
8 changed files with 170 additions and 93 deletions

View File

@@ -5,6 +5,11 @@ use std::any::Any;
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
pub enum OpenResult {
File(Box<dyn WasiFile>),
Dir(Box<dyn WasiDir>),
}
#[wiggle::async_trait]
pub trait WasiDir: Send + Sync {
fn as_any(&self) -> &dyn Any;
@@ -17,15 +22,7 @@ pub trait WasiDir: Send + Sync {
_read: bool,
_write: bool,
_fdflags: FdFlags,
) -> Result<Box<dyn WasiFile>, Error> {
Err(Error::not_supported())
}
async fn open_dir(
&self,
_symlink_follow: bool,
_path: &str,
) -> Result<Box<dyn WasiDir>, Error> {
) -> Result<OpenResult, Error> {
Err(Error::not_supported())
}

View File

@@ -1,5 +1,8 @@
use crate::{
dir::{DirCaps, DirEntry, DirEntryExt, DirFdStat, ReaddirCursor, ReaddirEntity, TableDirExt},
dir::{
DirCaps, DirEntry, DirEntryExt, DirFdStat, OpenResult, ReaddirCursor, ReaddirEntity,
TableDirExt,
},
file::{
Advice, FdFlags, FdStat, FileCaps, FileEntry, FileEntryExt, FileType, Filestat, OFlags,
RiFlags, RoFlags, SdFlags, SiFlags, TableFileExt, WasiFile,
@@ -747,41 +750,36 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
let oflags = OFlags::from(&oflags);
let fdflags = FdFlags::from(fdflags);
let path = path.as_cow()?;
if oflags.contains(OFlags::DIRECTORY) {
if oflags.contains(OFlags::CREATE)
|| oflags.contains(OFlags::EXCLUSIVE)
|| oflags.contains(OFlags::TRUNCATE)
{
return Err(Error::invalid_argument().context("directory oflags"));
}
let dir_caps = dir_entry.child_dir_caps(DirCaps::from(&fs_rights_base));
let file_caps = dir_entry.child_file_caps(FileCaps::from(&fs_rights_inheriting));
let dir = dir_entry.get_cap(DirCaps::OPEN)?;
let child_dir = dir.open_dir(symlink_follow, path.deref()).await?;
drop(dir);
let fd = table.push(Arc::new(DirEntry::new(
dir_caps, file_caps, None, child_dir,
)))?;
Ok(types::Fd::from(fd))
} else {
let mut required_caps = DirCaps::OPEN;
if oflags.contains(OFlags::CREATE) {
required_caps = required_caps | DirCaps::CREATE_FILE;
}
let file_caps = dir_entry.child_file_caps(FileCaps::from(&fs_rights_base));
let dir = dir_entry.get_cap(required_caps)?;
let read = file_caps.contains(FileCaps::READ);
let write = file_caps.contains(FileCaps::WRITE)
|| file_caps.contains(FileCaps::ALLOCATE)
|| file_caps.contains(FileCaps::FILESTAT_SET_SIZE);
let file = dir
.open_file(symlink_follow, path.deref(), oflags, read, write, fdflags)
.await?;
drop(dir);
let fd = table.push(Arc::new(FileEntry::new(file_caps, file)))?;
Ok(types::Fd::from(fd))
let mut required_caps = DirCaps::OPEN;
if oflags.contains(OFlags::CREATE) {
required_caps = required_caps | DirCaps::CREATE_FILE;
}
let dir_dir_caps = dir_entry.child_dir_caps(DirCaps::from(&fs_rights_base));
let dir_file_caps = dir_entry.child_file_caps(FileCaps::from(&fs_rights_inheriting));
let file_caps = dir_entry.child_file_caps(FileCaps::from(&fs_rights_base));
let dir = dir_entry.get_cap(required_caps)?;
let read = file_caps.contains(FileCaps::READ);
let write = file_caps.contains(FileCaps::WRITE)
|| file_caps.contains(FileCaps::ALLOCATE)
|| file_caps.contains(FileCaps::FILESTAT_SET_SIZE);
let file = dir
.open_file(symlink_follow, path.deref(), oflags, read, write, fdflags)
.await?;
drop(dir);
let fd = match file {
OpenResult::File(file) => table.push(Arc::new(FileEntry::new(file_caps, file)))?,
OpenResult::Dir(child_dir) => table.push(Arc::new(DirEntry::new(
dir_dir_caps,
dir_file_caps,
None,
child_dir,
)))?,
};
Ok(types::Fd::from(fd))
}
async fn path_readlink<'a>(