diff --git a/build.rs b/build.rs index 8051c68fa1..dda957c59f 100644 --- a/build.rs +++ b/build.rs @@ -131,12 +131,12 @@ cfg_if::cfg_if! { fn ignore(testsuite: &str, name: &str) -> bool { if testsuite == "misc_testsuite" { match name { - "big_random_buf" => false, - "sched_yield" => false, - "file_pread_pwrite" => false, - "renumber" => false, - "file_seek_tell" => false, - _ => true, + "readlink_no_buffer" => true, + "dangling_symlink" => true, + "symlink_loop" => true, + "clock_time_get" => true, + "truncation_rights" => true, + _ => false, } } else { unreachable!() diff --git a/misc_testsuite/dangling_symlink.wasm b/misc_testsuite/dangling_symlink.wasm new file mode 100644 index 0000000000..1c4c39afc6 Binary files /dev/null and b/misc_testsuite/dangling_symlink.wasm differ diff --git a/misc_testsuite/interesting_paths.wasm b/misc_testsuite/interesting_paths.wasm index e83fefc65f..151d85464b 100755 Binary files a/misc_testsuite/interesting_paths.wasm and b/misc_testsuite/interesting_paths.wasm differ diff --git a/misc_testsuite/nofollow_errors.wasm b/misc_testsuite/nofollow_errors.wasm index 0af04124e4..fac99d5be0 100755 Binary files a/misc_testsuite/nofollow_errors.wasm and b/misc_testsuite/nofollow_errors.wasm differ diff --git a/misc_testsuite/readlink.wasm b/misc_testsuite/readlink.wasm new file mode 100755 index 0000000000..ff14dc8336 Binary files /dev/null and b/misc_testsuite/readlink.wasm differ diff --git a/src/hostcalls_impl/fs.rs b/src/hostcalls_impl/fs.rs index 9843e6b54a..332386a509 100644 --- a/src/hostcalls_impl/fs.rs +++ b/src/hostcalls_impl/fs.rs @@ -1,7 +1,10 @@ #![allow(non_camel_case_types)] +use super::fs_helpers::path_get; use crate::ctx::WasiCtx; use crate::fdentry::{Descriptor, FdEntry}; use crate::memory::*; +use crate::sys::fdentry_impl::determine_type_rights; +use crate::sys::hostcalls_impl::fs_helpers::path_open_rights; use crate::sys::{errno_from_host, host_impl, hostcalls_impl}; use crate::{host, wasm32, Result}; use log::trace; @@ -242,9 +245,10 @@ pub(crate) fn fd_seek( host::__WASI_WHENCE_SET => SeekFrom::Start(offset as u64), _ => return Err(host::__WASI_EINVAL), }; - let host_newoffset = fd - .seek(pos) - .map_err(|err| err.raw_os_error().map_or(host::__WASI_EIO, errno_from_host))?; + let host_newoffset = fd.seek(pos).map_err(|err| { + log::debug!("fd_seek error={:?}", err); + err.raw_os_error().map_or(host::__WASI_EIO, errno_from_host) + })?; trace!(" | *newoffset={:?}", host_newoffset); @@ -487,8 +491,9 @@ pub(crate) fn path_create_directory( let dirfd = wasi_ctx .get_fd_entry(dirfd, rights, 0) .and_then(|fe| fe.fd_object.descriptor.as_file())?; + let resolved = path_get(dirfd, 0, path, false)?; - hostcalls_impl::path_create_directory(dirfd, path) + hostcalls_impl::path_create_directory(resolved) } pub(crate) fn path_link( @@ -529,8 +534,10 @@ pub(crate) fn path_link( let new_dirfd = wasi_ctx .get_fd_entry(new_dirfd, host::__WASI_RIGHT_PATH_LINK_TARGET, 0) .and_then(|fe| fe.fd_object.descriptor.as_file())?; + let resolved_old = path_get(old_dirfd, 0, old_path, false)?; + let resolved_new = path_get(new_dirfd, 0, new_path, false)?; - hostcalls_impl::path_link(old_dirfd, new_dirfd, old_path, new_path) + hostcalls_impl::path_link(resolved_old, resolved_new) } pub(crate) fn path_open( @@ -559,6 +566,9 @@ pub(crate) fn path_open( fd_out_ptr ); + // pre-encode fd_out_ptr to -1 in case of error in opening a path + enc_fd_byref(memory, fd_out_ptr, wasm32::__wasi_fd_t::max_value())?; + let dirfd = dec_fd(dirfd); let dirflags = dec_lookupflags(dirflags); let oflags = dec_oflags(oflags); @@ -566,6 +576,17 @@ pub(crate) fn path_open( let fs_rights_inheriting = dec_rights(fs_rights_inheriting); let fs_flags = dec_fdflags(fs_flags); + let path = dec_slice_of::(memory, path_ptr, path_len).and_then(host::path_from_slice)?; + + trace!(" | (path_ptr,path_len)='{}'", path); + + let (needed_base, needed_inheriting) = + path_open_rights(fs_rights_base, fs_rights_inheriting, oflags, fs_flags); + let dirfd = wasi_ctx + .get_fd_entry(dirfd, needed_base, needed_inheriting) + .and_then(|fe| fe.fd_object.descriptor.as_file())?; + let resolved = path_get(dirfd, dirflags, path, oflags & host::__WASI_O_CREAT != 0)?; + // which open mode do we need? let read = fs_rights_base & (host::__WASI_RIGHT_FD_READ | host::__WASI_RIGHT_FD_READDIR) != 0; let write = fs_rights_base @@ -575,37 +596,18 @@ pub(crate) fn path_open( | host::__WASI_RIGHT_FD_FILESTAT_SET_SIZE) != 0; - // which rights are needed on the dirfd? - let needed_base = host::__WASI_RIGHT_PATH_OPEN; - let needed_inheriting = fs_rights_base | fs_rights_inheriting; + let fd = hostcalls_impl::path_open(resolved, read, write, oflags, fs_flags)?; - let path = dec_slice_of::(memory, path_ptr, path_len).and_then(host::path_from_slice)?; + // Determine the type of the new file descriptor and which rights contradict with this type + let (_ty, max_base, max_inheriting) = determine_type_rights(&fd)?; + let mut fe = FdEntry::from(fd)?; + fe.rights_base &= max_base; + fe.rights_inheriting &= max_inheriting; + let guest_fd = wasi_ctx.insert_fd_entry(fe)?; - trace!(" | (path_ptr,path_len)='{}'", path); + trace!(" | *fd={:?}", guest_fd); - hostcalls_impl::path_open( - wasi_ctx, - dirfd, - dirflags, - path, - oflags, - read, - write, - needed_base, - needed_inheriting, - fs_flags, - ) - .and_then(|fe| { - let guest_fd = wasi_ctx.insert_fd_entry(fe)?; - - trace!(" | *fd={:?}", guest_fd); - - enc_fd_byref(memory, fd_out_ptr, guest_fd) - }) - .or_else(|err| { - enc_fd_byref(memory, fd_out_ptr, wasm32::__wasi_fd_t::max_value())?; - Err(err) - }) + enc_fd_byref(memory, fd_out_ptr, guest_fd) } pub(crate) fn fd_readdir( @@ -676,10 +678,11 @@ pub(crate) fn path_readlink( let dirfd = wasi_ctx .get_fd_entry(dirfd, host::__WASI_RIGHT_PATH_READLINK, 0) .and_then(|fe| fe.fd_object.descriptor.as_file())?; + let resolved = path_get(dirfd, 0, &path, false)?; let mut buf = dec_slice_of_mut::(memory, buf_ptr, buf_len)?; - let host_bufused = hostcalls_impl::path_readlink(dirfd, &path, &mut buf)?; + let host_bufused = hostcalls_impl::path_readlink(resolved, &mut buf)?; trace!(" | (buf_ptr,*buf_used)={:?}", buf); trace!(" | *buf_used={:?}", host_bufused); @@ -723,8 +726,10 @@ pub(crate) fn path_rename( let new_dirfd = wasi_ctx .get_fd_entry(new_dirfd, host::__WASI_RIGHT_PATH_RENAME_TARGET, 0) .and_then(|fe| fe.fd_object.descriptor.as_file())?; + let resolved_old = path_get(old_dirfd, 0, old_path, false)?; + let resolved_new = path_get(new_dirfd, 0, new_path, false)?; - hostcalls_impl::path_rename(old_dirfd, old_path, new_dirfd, new_path) + hostcalls_impl::path_rename(resolved_old, resolved_new) } pub(crate) fn fd_filestat_get( @@ -825,8 +830,8 @@ pub(crate) fn path_filestat_get( let dirfd = wasi_ctx .get_fd_entry(dirfd, host::__WASI_RIGHT_PATH_FILESTAT_GET, 0) .and_then(|fe| fe.fd_object.descriptor.as_file())?; - - let host_filestat = hostcalls_impl::path_filestat_get(dirfd, dirflags, path)?; + let resolved = path_get(dirfd, dirflags, path, false)?; + let host_filestat = hostcalls_impl::path_filestat_get(resolved, dirflags)?; trace!(" | *filestat_ptr={:?}", host_filestat); @@ -867,8 +872,9 @@ pub(crate) fn path_filestat_set_times( let dirfd = wasi_ctx .get_fd_entry(dirfd, host::__WASI_RIGHT_PATH_FILESTAT_SET_TIMES, 0) .and_then(|fe| fe.fd_object.descriptor.as_file())?; + let resolved = path_get(dirfd, dirflags, path, false)?; - hostcalls_impl::path_filestat_set_times(dirfd, dirflags, path, st_atim, st_mtim, fst_flags) + hostcalls_impl::path_filestat_set_times(resolved, dirflags, st_atim, st_mtim, fst_flags) } pub(crate) fn path_symlink( @@ -901,8 +907,9 @@ pub(crate) fn path_symlink( let dirfd = wasi_ctx .get_fd_entry(dirfd, host::__WASI_RIGHT_PATH_SYMLINK, 0) .and_then(|fe| fe.fd_object.descriptor.as_file())?; + let resolved_new = path_get(dirfd, 0, new_path, false)?; - hostcalls_impl::path_symlink(dirfd, old_path, new_path) + hostcalls_impl::path_symlink(old_path, resolved_new) } pub(crate) fn path_unlink_file( @@ -927,8 +934,9 @@ pub(crate) fn path_unlink_file( let dirfd = wasi_ctx .get_fd_entry(dirfd, host::__WASI_RIGHT_PATH_UNLINK_FILE, 0) .and_then(|fe| fe.fd_object.descriptor.as_file())?; + let resolved = path_get(dirfd, 0, path, false)?; - hostcalls_impl::path_unlink_file(dirfd, path) + hostcalls_impl::path_unlink_file(resolved) } pub(crate) fn path_remove_directory( @@ -953,8 +961,9 @@ pub(crate) fn path_remove_directory( let dirfd = wasi_ctx .get_fd_entry(dirfd, host::__WASI_RIGHT_PATH_REMOVE_DIRECTORY, 0) .and_then(|fe| fe.fd_object.descriptor.as_file())?; + let resolved = path_get(dirfd, 0, path, false)?; - hostcalls_impl::path_remove_directory(dirfd, path) + hostcalls_impl::path_remove_directory(resolved) } pub(crate) fn fd_prestat_get( diff --git a/src/hostcalls_impl/fs_helpers.rs b/src/hostcalls_impl/fs_helpers.rs new file mode 100644 index 0000000000..1886681b66 --- /dev/null +++ b/src/hostcalls_impl/fs_helpers.rs @@ -0,0 +1,197 @@ +#![allow(non_camel_case_types)] +use crate::sys::hostcalls_impl::fs_helpers::*; +use crate::sys::{errno_from_host, host_impl}; +use crate::{host, Result}; +use std::fs::File; +use std::path::{Component, Path}; + +pub(crate) struct PathGet { + dirfd: File, + path: String, +} + +impl PathGet { + pub(crate) fn dirfd(&self) -> &File { + &self.dirfd + } + + pub(crate) fn path(&self) -> &str { + &self.path + } +} + +/// Normalizes a path to ensure that the target path is located under the directory provided. +/// +/// This is a workaround for not having Capsicum support in the OS. +pub(crate) fn path_get( + dirfd: &File, + dirflags: host::__wasi_lookupflags_t, + path: &str, + needs_final_component: bool, +) -> Result { + const MAX_SYMLINK_EXPANSIONS: usize = 128; + + if path.contains('\0') { + // if contains NUL, return EILSEQ + return Err(host::__WASI_EILSEQ); + } + + let dirfd = dirfd.try_clone().map_err(|err| { + err.raw_os_error() + .map_or(host::__WASI_EBADF, errno_from_host) + })?; + + // Stack of directory file descriptors. Index 0 always corresponds with the directory provided + // to this function. Entering a directory causes a file descriptor to be pushed, while handling + // ".." entries causes an entry to be popped. Index 0 cannot be popped, as this would imply + // escaping the base directory. + let mut dir_stack = vec![dirfd]; + + // Stack of paths left to process. This is initially the `path` argument to this function, but + // any symlinks we encounter are processed by pushing them on the stack. + let mut path_stack = vec![path.to_owned()]; + + // Track the number of symlinks we've expanded, so we can return `ELOOP` after too many. + let mut symlink_expansions = 0; + + // TODO: rewrite this using a custom posix path type, with a component iterator that respects + // trailing slashes. This version does way too much allocation, and is way too fiddly. + loop { + match path_stack.pop() { + Some(cur_path) => { + log::debug!("cur_path = {:?}", cur_path); + + let ends_with_slash = cur_path.ends_with("/"); + let mut components = Path::new(&cur_path).components(); + let head = match components.next() { + None => return Err(host::__WASI_ENOENT), + Some(p) => p, + }; + let tail = components.as_path(); + + if tail.components().next().is_some() { + let mut tail = host_impl::path_from_host(tail.as_os_str())?; + if ends_with_slash { + tail.push_str("/"); + } + path_stack.push(tail); + } + + match head { + Component::Prefix(_) | Component::RootDir => { + // path is absolute! + return Err(host::__WASI_ENOTCAPABLE); + } + Component::CurDir => { + // "." so skip + } + Component::ParentDir => { + // ".." so pop a dir + let _ = dir_stack.pop().ok_or(host::__WASI_ENOTCAPABLE)?; + + // we're not allowed to pop past the original directory + if dir_stack.is_empty() { + return Err(host::__WASI_ENOTCAPABLE); + } + } + Component::Normal(head) => { + let mut head = host_impl::path_from_host(head)?; + if ends_with_slash { + // preserve trailing slash + head.push_str("/"); + } + + if !path_stack.is_empty() || (ends_with_slash && !needs_final_component) { + match openat(dir_stack.last().ok_or(host::__WASI_ENOTCAPABLE)?, &head) { + Ok(new_dir) => { + dir_stack.push(new_dir); + } + Err(e) + if e == host::__WASI_ELOOP + || e == host::__WASI_EMLINK + || e == host::__WASI_ENOTDIR => + // Check to see if it was a symlink. Linux indicates + // this with ENOTDIR because of the O_DIRECTORY flag. + { + // attempt symlink expansion + let mut link_path = readlinkat( + dir_stack.last().ok_or(host::__WASI_ENOTCAPABLE)?, + &head, + )?; + + symlink_expansions += 1; + if symlink_expansions > MAX_SYMLINK_EXPANSIONS { + return Err(host::__WASI_ELOOP); + } + + if head.ends_with("/") { + link_path.push_str("/"); + } + + log::debug!( + "attempted symlink expansion link_path={:?}", + link_path + ); + + path_stack.push(link_path); + } + Err(e) => { + return Err(e); + } + } + + continue; + } else if ends_with_slash + || (dirflags & host::__WASI_LOOKUP_SYMLINK_FOLLOW) != 0 + { + // if there's a trailing slash, or if `LOOKUP_SYMLINK_FOLLOW` is set, attempt + // symlink expansion + match readlinkat( + dir_stack.last().ok_or(host::__WASI_ENOTCAPABLE)?, + &head, + ) { + Ok(mut link_path) => { + symlink_expansions += 1; + if symlink_expansions > MAX_SYMLINK_EXPANSIONS { + return Err(host::__WASI_ELOOP); + } + + if head.ends_with("/") { + link_path.push_str("/"); + } + + log::debug!( + "attempted symlink expansion link_path={:?}", + link_path + ); + + path_stack.push(link_path); + continue; + } + Err(e) => { + if e != host::__WASI_EINVAL && e != host::__WASI_ENOENT { + return Err(e); + } + } + } + } + + // not a symlink, so we're done; + return Ok(PathGet { + dirfd: dir_stack.pop().ok_or(host::__WASI_ENOTCAPABLE)?, + path: head, + }); + } + } + } + None => { + // no further components to process. means we've hit a case like "." or "a/..", or if the + // input path has trailing slashes and `needs_final_component` is not set + return Ok(PathGet { + dirfd: dir_stack.pop().ok_or(host::__WASI_ENOTCAPABLE)?, + path: String::from("."), + }); + } + } + } +} diff --git a/src/hostcalls_impl/mod.rs b/src/hostcalls_impl/mod.rs index 247c60cd10..f74be814b0 100644 --- a/src/hostcalls_impl/mod.rs +++ b/src/hostcalls_impl/mod.rs @@ -1,5 +1,7 @@ mod fs; +mod fs_helpers; mod misc; pub(crate) use self::fs::*; +pub(crate) use self::fs_helpers::PathGet; pub(crate) use self::misc::*; diff --git a/src/sys/mod.rs b/src/sys/mod.rs index 8a60ea664f..aad58cd230 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -4,14 +4,16 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(unix)] { mod unix; - pub use self::unix::*; + pub(crate) use self::unix::*; + pub use self::unix::preopen_dir; pub(crate) fn errno_from_host(err: i32) -> host::__wasi_errno_t { host_impl::errno_from_nix(nix::errno::from_i32(err)) } } else if #[cfg(windows)] { mod windows; - pub use self::windows::*; + pub(crate) use self::windows::*; + pub use self::windows::preopen_dir; pub(crate) fn errno_from_host(err: i32) -> host::__wasi_errno_t { host_impl::errno_from_win(winx::winerror::WinError::from_u32(err as u32)) diff --git a/src/sys/unix/hostcalls_impl/fs.rs b/src/sys/unix/hostcalls_impl/fs.rs index 13cdbed3b5..7c33262548 100644 --- a/src/sys/unix/hostcalls_impl/fs.rs +++ b/src/sys/unix/hostcalls_impl/fs.rs @@ -1,10 +1,8 @@ #![allow(non_camel_case_types)] #![allow(unused_unsafe)] use super::fs_helpers::*; -use crate::ctx::WasiCtx; -use crate::fdentry::FdEntry; use crate::helpers::systemtime_to_timestamp; -use crate::sys::fdentry_impl::determine_type_rights; +use crate::hostcalls_impl::PathGet; use crate::sys::host_impl; use crate::sys::{errno_from_host, errno_from_ioerror}; use crate::{host, wasm32, Result}; @@ -88,38 +86,30 @@ pub(crate) fn fd_advise( Ok(()) } -pub(crate) fn path_create_directory(dirfd: &File, path: &str) -> Result<()> { +pub(crate) fn path_create_directory(resolved: PathGet) -> Result<()> { use nix::libc::mkdirat; - - let (dir, path) = path_get(dirfd, 0, path, false)?; - let path_cstr = CString::new(path.as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; - + let path_cstr = CString::new(resolved.path().as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; // nix doesn't expose mkdirat() yet - match unsafe { mkdirat(dir.as_raw_fd(), path_cstr.as_ptr(), 0o777) } { + match unsafe { mkdirat(resolved.dirfd().as_raw_fd(), path_cstr.as_ptr(), 0o777) } { 0 => Ok(()), _ => Err(host_impl::errno_from_nix(nix::errno::Errno::last())), } } -pub(crate) fn path_link( - old_dirfd: &File, - new_dirfd: &File, - old_path: &str, - new_path: &str, -) -> Result<()> { +pub(crate) fn path_link(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> { use nix::libc::linkat; - let (old_dir, old_path) = path_get(old_dirfd, 0, old_path, false)?; - let (new_dir, new_path) = path_get(new_dirfd, 0, new_path, false)?; - let old_path_cstr = CString::new(old_path.as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; - let new_path_cstr = CString::new(new_path.as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; + let old_path_cstr = + CString::new(resolved_old.path().as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; + let new_path_cstr = + CString::new(resolved_new.path().as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; // Not setting AT_SYMLINK_FOLLOW fails on most filesystems let atflags = libc::AT_SYMLINK_FOLLOW; let res = unsafe { linkat( - old_dir.as_raw_fd(), + resolved_old.dirfd().as_raw_fd(), old_path_cstr.as_ptr(), - new_dir.as_raw_fd(), + resolved_new.dirfd().as_raw_fd(), new_path_cstr.as_ptr(), atflags, ) @@ -132,17 +122,12 @@ pub(crate) fn path_link( } pub(crate) fn path_open( - ctx: &WasiCtx, - dirfd: host::__wasi_fd_t, - dirflags: host::__wasi_lookupflags_t, - path: &str, - oflags: host::__wasi_oflags_t, + resolved: PathGet, read: bool, write: bool, - mut needed_base: host::__wasi_rights_t, - mut needed_inheriting: host::__wasi_rights_t, + oflags: host::__wasi_oflags_t, fs_flags: host::__wasi_fdflags_t, -) -> Result { +) -> Result { use nix::errno::Errno; use nix::fcntl::{openat, AtFlags, OFlag}; use nix::sys::stat::{fstatat, Mode, SFlag}; @@ -159,34 +144,17 @@ pub(crate) fn path_open( nix_all_oflags.insert(OFlag::O_NOFOLLOW); // convert open flags - let nix_oflags = host_impl::nix_from_oflags(oflags); - nix_all_oflags.insert(nix_oflags); - if nix_all_oflags.contains(OFlag::O_CREAT) { - needed_base |= host::__WASI_RIGHT_PATH_CREATE_FILE; - } - if nix_all_oflags.contains(OFlag::O_TRUNC) { - needed_base |= host::__WASI_RIGHT_PATH_FILESTAT_SET_SIZE; - } + nix_all_oflags.insert(host_impl::nix_from_oflags(oflags)); // convert file descriptor flags nix_all_oflags.insert(host_impl::nix_from_fdflags(fs_flags)); - if nix_all_oflags.contains(OFlag::O_DSYNC) { - needed_inheriting |= host::__WASI_RIGHT_FD_DATASYNC; - } - if nix_all_oflags.intersects(host_impl::O_RSYNC | OFlag::O_SYNC) { - needed_inheriting |= host::__WASI_RIGHT_FD_SYNC; - } - - let dirfe = ctx.get_fd_entry(dirfd, needed_base, needed_inheriting)?; - let dirfd = dirfe.fd_object.descriptor.as_file()?; - let (dir, path) = path_get(dirfd, dirflags, path, nix_oflags.contains(OFlag::O_CREAT))?; // Call openat. Use mode 0o666 so that we follow whatever the user's // umask is, but don't set the executable flag, because it isn't yet // meaningful for WASI programs to create executable files. let new_fd = match openat( - dir.as_raw_fd(), - path.as_str(), + resolved.dirfd().as_raw_fd(), + resolved.path(), nix_all_oflags, Mode::from_bits_truncate(0o666), ) { @@ -195,9 +163,11 @@ pub(crate) fn path_open( match e.as_errno() { // Linux returns ENXIO instead of EOPNOTSUPP when opening a socket Some(Errno::ENXIO) => { - if let Ok(stat) = - fstatat(dir.as_raw_fd(), path.as_str(), AtFlags::AT_SYMLINK_NOFOLLOW) - { + if let Ok(stat) = fstatat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + AtFlags::AT_SYMLINK_NOFOLLOW, + ) { if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFSOCK) { return Err(host::__WASI_ENOTSUP); } else { @@ -212,9 +182,11 @@ pub(crate) fn path_open( Some(Errno::ENOTDIR) if !(nix_all_oflags & (OFlag::O_NOFOLLOW | OFlag::O_DIRECTORY)).is_empty() => { - if let Ok(stat) = - fstatat(dir.as_raw_fd(), path.as_str(), AtFlags::AT_SYMLINK_NOFOLLOW) - { + if let Ok(stat) = fstatat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + AtFlags::AT_SYMLINK_NOFOLLOW, + ) { if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFLNK) { return Err(host::__WASI_ELOOP); } @@ -233,14 +205,7 @@ pub(crate) fn path_open( }; // Determine the type of the new file descriptor and which rights contradict with this type - let file = unsafe { File::from_raw_fd(new_fd) }; - determine_type_rights(&file).and_then(|(_ty, max_base, max_inheriting)| { - FdEntry::from(file).map(|mut fe| { - fe.rights_base &= max_base; - fe.rights_inheriting &= max_inheriting; - fe - }) - }) + Ok(unsafe { File::from_raw_fd(new_fd) }) } pub(crate) fn fd_readdir( @@ -297,11 +262,9 @@ pub(crate) fn fd_readdir( Ok(host_buf_len - left) } -pub(crate) fn path_readlink(dirfd: &File, path: &str, buf: &mut [u8]) -> Result { +pub(crate) fn path_readlink(resolved: PathGet, buf: &mut [u8]) -> Result { use nix::errno::Errno; - - let (dir, path) = path_get(dirfd, 0, path, false)?; - let path_cstr = CString::new(path.as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; + let path_cstr = CString::new(resolved.path().as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; // Linux requires that the buffer size is positive, whereas POSIX does not. // Use a fake buffer to store the results if the size is zero. @@ -311,7 +274,7 @@ pub(crate) fn path_readlink(dirfd: &File, path: &str, buf: &mut [u8]) -> Result< let buf_len = buf.len(); let len = unsafe { libc::readlinkat( - dir.as_raw_fd(), + resolved.dirfd().as_raw_fd(), path_cstr.as_ptr() as *const libc::c_char, if buf_len == 0 { fakebuf.as_mut_ptr() @@ -330,24 +293,18 @@ pub(crate) fn path_readlink(dirfd: &File, path: &str, buf: &mut [u8]) -> Result< } } -pub(crate) fn path_rename( - old_dirfd: &File, - old_path: &str, - new_dirfd: &File, - new_path: &str, -) -> Result<()> { +pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> { use nix::libc::renameat; - - let (old_dir, old_path) = path_get(old_dirfd, 0, old_path, false)?; - let (new_dir, new_path) = path_get(new_dirfd, 0, new_path, false)?; - let old_path_cstr = CString::new(old_path.as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; - let new_path_cstr = CString::new(new_path.as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; + let old_path_cstr = + CString::new(resolved_old.path().as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; + let new_path_cstr = + CString::new(resolved_new.path().as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; let res = unsafe { renameat( - old_dir.as_raw_fd(), + resolved_old.dirfd().as_raw_fd(), old_path_cstr.as_ptr(), - new_dir.as_raw_fd(), + resolved_new.dirfd().as_raw_fd(), new_path_cstr.as_ptr(), ) }; @@ -458,35 +415,31 @@ pub(crate) fn fd_filestat_set_size(fd: &File, st_size: host::__wasi_filesize_t) } pub(crate) fn path_filestat_get( - dirfd: &File, + resolved: PathGet, dirflags: host::__wasi_lookupflags_t, - path: &str, ) -> Result { use nix::fcntl::AtFlags; use nix::sys::stat::fstatat; - let (dir, path) = path_get(dirfd, dirflags, path, false)?; let atflags = match dirflags { 0 => AtFlags::empty(), _ => AtFlags::AT_SYMLINK_NOFOLLOW, }; - let filestat = fstatat(dir.as_raw_fd(), path.as_str(), atflags) + let filestat = fstatat(resolved.dirfd().as_raw_fd(), resolved.path(), atflags) .map_err(|err| host_impl::errno_from_nix(err.as_errno().unwrap()))?; host_impl::filestat_from_nix(filestat) } pub(crate) fn path_filestat_set_times( - dirfd: &File, + resolved: PathGet, dirflags: host::__wasi_lookupflags_t, - path: &str, st_atim: host::__wasi_timestamp_t, mut st_mtim: host::__wasi_timestamp_t, fst_flags: host::__wasi_fstflags_t, ) -> Result<()> { use nix::sys::time::{TimeSpec, TimeValLike}; - let (dir, path) = path_get(dirfd, dirflags, &path, false)?; let atflags = match dirflags { wasm32::__WASI_LOOKUP_SYMLINK_FOLLOW => 0, _ => libc::AT_SYMLINK_NOFOLLOW, @@ -519,10 +472,16 @@ pub(crate) fn path_filestat_set_times( let ts_mtime = *TimeSpec::nanoseconds(st_mtim as i64).as_ref(); let times = [ts_atime, ts_mtime]; - let path_cstr = CString::new(path.as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; + let path_cstr = CString::new(resolved.path().as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; - let res = - unsafe { libc::utimensat(dir.as_raw_fd(), path_cstr.as_ptr(), times.as_ptr(), atflags) }; + let res = unsafe { + libc::utimensat( + resolved.dirfd().as_raw_fd(), + path_cstr.as_ptr(), + times.as_ptr(), + atflags, + ) + }; if res != 0 { Err(host_impl::errno_from_nix(nix::errno::Errno::last())) } else { @@ -530,17 +489,17 @@ pub(crate) fn path_filestat_set_times( } } -pub(crate) fn path_symlink(dirfd: &File, old_path: &str, new_path: &str) -> Result<()> { +pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> Result<()> { use nix::libc::symlinkat; - let (dir, new_path) = path_get(dirfd, 0, new_path, false)?; let old_path_cstr = CString::new(old_path.as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; - let new_path_cstr = CString::new(new_path.as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; + let new_path_cstr = + CString::new(resolved.path().as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; let res = unsafe { symlinkat( old_path_cstr.as_ptr(), - dir.as_raw_fd(), + resolved.dirfd().as_raw_fd(), new_path_cstr.as_ptr(), ) }; @@ -551,15 +510,14 @@ pub(crate) fn path_symlink(dirfd: &File, old_path: &str, new_path: &str) -> Resu } } -pub(crate) fn path_unlink_file(dirfd: &File, path: &str) -> Result<()> { +pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> { use nix::errno; use nix::libc::unlinkat; - let (dir, path) = path_get(dirfd, 0, path, false)?; - let path_cstr = CString::new(path.as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; + let path_cstr = CString::new(resolved.path().as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; // nix doesn't expose unlinkat() yet - match unsafe { unlinkat(dir.as_raw_fd(), path_cstr.as_ptr(), 0) } { + match unsafe { unlinkat(resolved.dirfd().as_raw_fd(), path_cstr.as_ptr(), 0) } { 0 => Ok(()), _ => { let mut e = errno::Errno::last(); @@ -577,9 +535,11 @@ pub(crate) fn path_unlink_file(dirfd: &File, path: &str) -> Result<()> { use nix::sys::stat::{fstatat, SFlag}; if e == errno::Errno::EPERM { - if let Ok(stat) = - fstatat(dir.as_raw_fd(), path.as_str(), AtFlags::AT_SYMLINK_NOFOLLOW) - { + if let Ok(stat) = fstatat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + AtFlags::AT_SYMLINK_NOFOLLOW, + ) { if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFDIR) { e = errno::Errno::EISDIR; } @@ -594,15 +554,20 @@ pub(crate) fn path_unlink_file(dirfd: &File, path: &str) -> Result<()> { } } -pub(crate) fn path_remove_directory(dirfd: &File, path: &str) -> Result<()> { +pub(crate) fn path_remove_directory(resolved: PathGet) -> Result<()> { use nix::errno; use nix::libc::{unlinkat, AT_REMOVEDIR}; - let (dir, path) = path_get(dirfd, 0, path, false)?; - let path_cstr = CString::new(path.as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; + let path_cstr = CString::new(resolved.path().as_bytes()).map_err(|_| host::__WASI_EILSEQ)?; // nix doesn't expose unlinkat() yet - match unsafe { unlinkat(dir.as_raw_fd(), path_cstr.as_ptr(), AT_REMOVEDIR) } { + match unsafe { + unlinkat( + resolved.dirfd().as_raw_fd(), + path_cstr.as_ptr(), + AT_REMOVEDIR, + ) + } { 0 => Ok(()), _ => Err(host_impl::errno_from_nix(errno::Errno::last())), } diff --git a/src/sys/unix/hostcalls_impl/fs_helpers.rs b/src/sys/unix/hostcalls_impl/fs_helpers.rs index 97e50f913b..435da35f77 100644 --- a/src/sys/unix/hostcalls_impl/fs_helpers.rs +++ b/src/sys/unix/hostcalls_impl/fs_helpers.rs @@ -1,183 +1,44 @@ #![allow(non_camel_case_types)] #![allow(unused_unsafe)] - -use crate::sys::errno_from_host; use crate::sys::host_impl; use crate::{host, Result}; use nix::libc::{self, c_long}; use std::fs::File; -use std::path::{Component, Path}; -/// Normalizes a path to ensure that the target path is located under the directory provided. -/// -/// This is a workaround for not having Capsicum support in the OS. -pub(crate) fn path_get( - dirfd: &File, - dirflags: host::__wasi_lookupflags_t, - path: &str, - needs_final_component: bool, -) -> Result<(File, String)> { - const MAX_SYMLINK_EXPANSIONS: usize = 128; +pub(crate) fn path_open_rights( + rights_base: host::__wasi_rights_t, + rights_inheriting: host::__wasi_rights_t, + oflags: host::__wasi_oflags_t, + fs_flags: host::__wasi_fdflags_t, +) -> (host::__wasi_rights_t, host::__wasi_rights_t) { + use nix::fcntl::OFlag; - if path.contains("\0") { - // if contains NUL, return EILSEQ - return Err(host::__WASI_EILSEQ); + // which rights are needed on the dirfd? + let mut needed_base = host::__WASI_RIGHT_PATH_OPEN; + let mut needed_inheriting = rights_base | rights_inheriting; + + // convert open flags + let oflags = host_impl::nix_from_oflags(oflags); + if oflags.contains(OFlag::O_CREAT) { + needed_base |= host::__WASI_RIGHT_PATH_CREATE_FILE; + } + if oflags.contains(OFlag::O_TRUNC) { + needed_base |= host::__WASI_RIGHT_PATH_FILESTAT_SET_SIZE; } - let dirfd = dirfd.try_clone().map_err(|err| { - err.raw_os_error() - .map_or(host::__WASI_EBADF, errno_from_host) - })?; - - // Stack of directory file descriptors. Index 0 always corresponds with the directory provided - // to this function. Entering a directory causes a file descriptor to be pushed, while handling - // ".." entries causes an entry to be popped. Index 0 cannot be popped, as this would imply - // escaping the base directory. - let mut dir_stack = vec![dirfd]; - - // Stack of paths left to process. This is initially the `path` argument to this function, but - // any symlinks we encounter are processed by pushing them on the stack. - let mut path_stack = vec![path.to_owned()]; - - // Track the number of symlinks we've expanded, so we can return `ELOOP` after too many. - let mut symlink_expansions = 0; - - // TODO: rewrite this using a custom posix path type, with a component iterator that respects - // trailing slashes. This version does way too much allocation, and is way too fiddly. - loop { - match path_stack.pop() { - Some(cur_path) => { - // eprintln!("cur_path = {:?}", cur_path); - - let ends_with_slash = cur_path.ends_with("/"); - let mut components = Path::new(&cur_path).components(); - let head = match components.next() { - None => return Err(host::__WASI_ENOENT), - Some(p) => p, - }; - let tail = components.as_path(); - - if tail.components().next().is_some() { - let mut tail = host_impl::path_from_host(tail.as_os_str())?; - if ends_with_slash { - tail.push_str("/"); - } - path_stack.push(tail); - } - - match head { - Component::Prefix(_) | Component::RootDir => { - // path is absolute! - return Err(host::__WASI_ENOTCAPABLE); - } - Component::CurDir => { - // "." so skip - continue; - } - Component::ParentDir => { - // ".." so pop a dir - let _ = dir_stack.pop().ok_or(host::__WASI_ENOTCAPABLE)?; - - // we're not allowed to pop past the original directory - if dir_stack.is_empty() { - return Err(host::__WASI_ENOTCAPABLE); - } - } - Component::Normal(head) => { - let mut head = host_impl::path_from_host(head)?; - if ends_with_slash { - // preserve trailing slash - head.push_str("/"); - } - - if !path_stack.is_empty() || (ends_with_slash && !needs_final_component) { - match openat(dir_stack.last().ok_or(host::__WASI_ENOTCAPABLE)?, &head) { - Ok(new_dir) => { - dir_stack.push(new_dir); - continue; - } - Err(e) - if e == host::__WASI_ELOOP - || e == host::__WASI_EMLINK - || e == host::__WASI_ENOTDIR => - // Check to see if it was a symlink. Linux indicates - // this with ENOTDIR because of the O_DIRECTORY flag. - { - // attempt symlink expansion - match readlinkat( - dir_stack.last().ok_or(host::__WASI_ENOTCAPABLE)?, - &head, - ) { - Ok(mut link_path) => { - symlink_expansions += 1; - if symlink_expansions > MAX_SYMLINK_EXPANSIONS { - return Err(host::__WASI_ELOOP); - } - - if head.ends_with("/") { - link_path.push_str("/"); - } - - path_stack.push(link_path); - continue; - } - Err(e) => { - return Err(e); - } - } - } - Err(e) => { - return Err(e); - } - } - } else if ends_with_slash - || (dirflags & host::__WASI_LOOKUP_SYMLINK_FOLLOW) != 0 - { - // if there's a trailing slash, or if `LOOKUP_SYMLINK_FOLLOW` is set, attempt - // symlink expansion - match readlinkat( - dir_stack.last().ok_or(host::__WASI_ENOTCAPABLE)?, - &head, - ) { - Ok(mut link_path) => { - symlink_expansions += 1; - if symlink_expansions > MAX_SYMLINK_EXPANSIONS { - return Err(host::__WASI_ELOOP); - } - - if head.ends_with("/") { - link_path.push_str("/"); - } - - path_stack.push(link_path); - continue; - } - Err(e) => { - if e != host::__WASI_EINVAL && e != host::__WASI_ENOENT { - return Err(e); - } - } - } - } - - // not a symlink, so we're done; - return Ok((dir_stack.pop().ok_or(host::__WASI_ENOTCAPABLE)?, head)); - } - } - } - None => { - // no further components to process. means we've hit a case like "." or "a/..", or if the - // input path has trailing slashes and `needs_final_component` is not set - return Ok(( - dir_stack.pop().ok_or(host::__WASI_ENOTCAPABLE)?, - String::from("."), - )); - } - } + // convert file descriptor flags + let fdflags = host_impl::nix_from_fdflags(fs_flags); + if fdflags.contains(OFlag::O_DSYNC) { + needed_inheriting |= host::__WASI_RIGHT_FD_DATASYNC; } + if fdflags.intersects(host_impl::O_RSYNC | OFlag::O_SYNC) { + needed_inheriting |= host::__WASI_RIGHT_FD_SYNC; + } + + (needed_base, needed_inheriting) } -fn openat(dirfd: &File, path: &str) -> Result { +pub(crate) fn openat(dirfd: &File, path: &str) -> Result { use nix::fcntl::{self, OFlag}; use nix::sys::stat::Mode; use std::os::unix::prelude::{AsRawFd, FromRawFd}; @@ -192,7 +53,7 @@ fn openat(dirfd: &File, path: &str) -> Result { .map_err(|e| host_impl::errno_from_nix(e.as_errno().unwrap())) } -fn readlinkat(dirfd: &File, path: &str) -> Result { +pub(crate) fn readlinkat(dirfd: &File, path: &str) -> Result { use nix::fcntl; use std::os::unix::prelude::AsRawFd; @@ -204,21 +65,21 @@ fn readlinkat(dirfd: &File, path: &str) -> Result { } #[cfg(not(target_os = "macos"))] -pub fn utime_now() -> c_long { +pub(crate) fn utime_now() -> c_long { libc::UTIME_NOW } #[cfg(target_os = "macos")] -pub fn utime_now() -> c_long { +pub(crate) fn utime_now() -> c_long { -1 } #[cfg(not(target_os = "macos"))] -pub fn utime_omit() -> c_long { +pub(crate) fn utime_omit() -> c_long { libc::UTIME_OMIT } #[cfg(target_os = "macos")] -pub fn utime_omit() -> c_long { +pub(crate) fn utime_omit() -> c_long { -2 } diff --git a/src/sys/unix/hostcalls_impl/mod.rs b/src/sys/unix/hostcalls_impl/mod.rs index bee80ebdf8..75f8df76c7 100644 --- a/src/sys/unix/hostcalls_impl/mod.rs +++ b/src/sys/unix/hostcalls_impl/mod.rs @@ -1,7 +1,7 @@ //! Unix-specific hostcalls that implement //! [WASI](https://github.com/CraneStation/wasmtime-wasi/blob/wasi/docs/WASI-overview.md). mod fs; -mod fs_helpers; +pub(crate) mod fs_helpers; mod misc; pub(crate) use self::fs::*; diff --git a/src/sys/windows/fdentry_impl.rs b/src/sys/windows/fdentry_impl.rs index 0a0bfdaf0f..3d68f8ed96 100644 --- a/src/sys/windows/fdentry_impl.rs +++ b/src/sys/windows/fdentry_impl.rs @@ -23,19 +23,18 @@ pub(crate) fn determine_type_and_access_rights( host::__wasi_rights_t, host::__wasi_rights_t, )> { - use winx::file::{get_file_access_rights, AccessRight}; + use winx::file::{get_file_access_mode, AccessMode}; let (file_type, mut rights_base, rights_inheriting) = determine_type_rights(handle)?; match file_type { host::__WASI_FILETYPE_DIRECTORY | host::__WASI_FILETYPE_REGULAR_FILE => { - let rights = get_file_access_rights(handle.as_raw_handle()) - .map_err(host_impl::errno_from_win)?; - let rights = AccessRight::from_bits_truncate(rights); - if rights.contains(AccessRight::FILE_GENERIC_READ) { + let mode = + get_file_access_mode(handle.as_raw_handle()).map_err(host_impl::errno_from_win)?; + if mode.contains(AccessMode::FILE_GENERIC_READ) { rights_base |= host::__WASI_RIGHT_FD_READ; } - if rights.contains(AccessRight::FILE_GENERIC_WRITE) { + if mode.contains(AccessMode::FILE_GENERIC_WRITE) { rights_base |= host::__WASI_RIGHT_FD_WRITE; } } diff --git a/src/sys/windows/host_impl.rs b/src/sys/windows/host_impl.rs index f05c1c808a..c53f29bc25 100644 --- a/src/sys/windows/host_impl.rs +++ b/src/sys/windows/host_impl.rs @@ -4,7 +4,10 @@ #![allow(unused)] use crate::{host, Result}; use std::ffi::OsStr; +use std::fs::OpenOptions; use std::os::windows::ffi::OsStrExt; +use std::os::windows::fs::OpenOptionsExt; +use winx::file::{AccessMode, Attributes, CreationDisposition, Flags}; pub(crate) fn errno_from_win(error: winx::winerror::WinError) -> host::__wasi_errno_t { // TODO: implement error mapping between Windows and WASI @@ -12,55 +15,36 @@ pub(crate) fn errno_from_win(error: winx::winerror::WinError) -> host::__wasi_er match error { ERROR_SUCCESS => host::__WASI_ESUCCESS, ERROR_BAD_ENVIRONMENT => host::__WASI_E2BIG, - ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND => host::__WASI_ENOENT, + ERROR_FILE_NOT_FOUND => host::__WASI_ENOENT, + ERROR_PATH_NOT_FOUND => host::__WASI_ENOENT, ERROR_TOO_MANY_OPEN_FILES => host::__WASI_ENFILE, - ERROR_ACCESS_DENIED | ERROR_SHARING_VIOLATION => host::__WASI_EACCES, - ERROR_INVALID_HANDLE | ERROR_INVALID_NAME => host::__WASI_EBADF, - ERROR_NOT_ENOUGH_MEMORY | ERROR_OUTOFMEMORY => host::__WASI_ENOMEM, + ERROR_ACCESS_DENIED => host::__WASI_EACCES, + ERROR_SHARING_VIOLATION => host::__WASI_EACCES, + ERROR_PRIVILEGE_NOT_HELD => host::__WASI_ENOTCAPABLE, // TODO is this the correct mapping? + ERROR_INVALID_HANDLE => host::__WASI_EBADF, + ERROR_INVALID_NAME => host::__WASI_EINVAL, + ERROR_NOT_ENOUGH_MEMORY => host::__WASI_ENOMEM, + ERROR_OUTOFMEMORY => host::__WASI_ENOMEM, ERROR_DIR_NOT_EMPTY => host::__WASI_ENOTEMPTY, - ERROR_DEV_NOT_EXIST => host::__WASI_EINVAL, - ERROR_NOT_READY | ERROR_BUSY => host::__WASI_EBUSY, + ERROR_NOT_READY => host::__WASI_EBUSY, + ERROR_BUSY => host::__WASI_EBUSY, ERROR_NOT_SUPPORTED => host::__WASI_ENOTSUP, ERROR_FILE_EXISTS => host::__WASI_EEXIST, ERROR_BROKEN_PIPE => host::__WASI_EPIPE, ERROR_BUFFER_OVERFLOW => host::__WASI_ENAMETOOLONG, - ERROR_DISK_FULL => host::__WASI_ENOSPC, - ERROR_SHARING_BUFFER_EXCEEDED => host::__WASI_ENFILE, + ERROR_NOT_A_REPARSE_POINT => host::__WASI_EINVAL, + ERROR_NEGATIVE_SEEK => host::__WASI_EINVAL, _ => host::__WASI_ENOTSUP, } } -pub(crate) fn win_from_fdflags( - fdflags: host::__wasi_fdflags_t, -) -> (winx::file::AccessRight, winx::file::FlagsAndAttributes) { - use winx::file::{AccessRight, FlagsAndAttributes}; - // TODO verify this! - let mut win_rights = AccessRight::empty(); - let mut win_flags_attrs = FlagsAndAttributes::empty(); - - if fdflags & host::__WASI_FDFLAG_NONBLOCK != 0 { - win_flags_attrs.insert(FlagsAndAttributes::FILE_FLAG_OVERLAPPED); - } - if fdflags & host::__WASI_FDFLAG_APPEND != 0 { - win_rights.insert(AccessRight::FILE_APPEND_DATA); - } - if fdflags & host::__WASI_FDFLAG_DSYNC != 0 - || fdflags & host::__WASI_FDFLAG_RSYNC != 0 - || fdflags & host::__WASI_FDFLAG_SYNC != 0 - { - win_rights.insert(AccessRight::SYNCHRONIZE); - } - (win_rights, win_flags_attrs) -} - -pub(crate) fn fdflags_from_win(rights: winx::file::AccessRight) -> host::__wasi_fdflags_t { - use winx::file::AccessRight; +pub(crate) fn fdflags_from_win(mode: AccessMode) -> host::__wasi_fdflags_t { let mut fdflags = 0; // TODO verify this! - if rights.contains(AccessRight::FILE_APPEND_DATA) { + if mode.contains(AccessMode::FILE_APPEND_DATA) { fdflags |= host::__WASI_FDFLAG_APPEND; } - if rights.contains(AccessRight::SYNCHRONIZE) { + if mode.contains(AccessMode::SYNCHRONIZE) { fdflags |= host::__WASI_FDFLAG_DSYNC; fdflags |= host::__WASI_FDFLAG_RSYNC; fdflags |= host::__WASI_FDFLAG_SYNC; @@ -77,31 +61,39 @@ pub(crate) fn fdflags_from_win(rights: winx::file::AccessRight) -> host::__wasi_ fdflags } -pub(crate) fn win_from_oflags( - oflags: host::__wasi_oflags_t, -) -> ( - winx::file::CreationDisposition, - winx::file::FlagsAndAttributes, -) { - use winx::file::{CreationDisposition, FlagsAndAttributes}; +pub(crate) fn win_from_fdflags(fdflags: host::__wasi_fdflags_t) -> (AccessMode, Flags) { + let mut access_mode = AccessMode::empty(); + let mut flags = Flags::empty(); - let win_flags_attrs = if oflags & host::__WASI_O_DIRECTORY != 0 { - FlagsAndAttributes::FILE_FLAG_BACKUP_SEMANTICS - } else { - FlagsAndAttributes::FILE_ATTRIBUTE_NORMAL - }; + // TODO verify this! + if fdflags & host::__WASI_FDFLAG_NONBLOCK != 0 { + flags.insert(Flags::FILE_FLAG_OVERLAPPED); + } + if fdflags & host::__WASI_FDFLAG_APPEND != 0 { + access_mode.insert(AccessMode::FILE_APPEND_DATA); + } + if fdflags & host::__WASI_FDFLAG_DSYNC != 0 + || fdflags & host::__WASI_FDFLAG_RSYNC != 0 + || fdflags & host::__WASI_FDFLAG_SYNC != 0 + { + access_mode.insert(AccessMode::SYNCHRONIZE); + } - let win_disp = if oflags & host::__WASI_O_CREAT != 0 && oflags & host::__WASI_O_EXCL != 0 { - CreationDisposition::CREATE_NEW - } else if oflags & host::__WASI_O_CREAT != 0 { - CreationDisposition::CREATE_ALWAYS + (access_mode, flags) +} + +pub(crate) fn win_from_oflags(oflags: host::__wasi_oflags_t) -> CreationDisposition { + if oflags & host::__WASI_O_CREAT != 0 { + if oflags & host::__WASI_O_EXCL != 0 { + CreationDisposition::CREATE_NEW + } else { + CreationDisposition::CREATE_ALWAYS + } } else if oflags & host::__WASI_O_TRUNC != 0 { CreationDisposition::TRUNCATE_EXISTING } else { CreationDisposition::OPEN_EXISTING - }; - - (win_disp, win_flags_attrs) + } } /// Creates owned WASI path from OS string. diff --git a/src/sys/windows/hostcalls_impl/fs.rs b/src/sys/windows/hostcalls_impl/fs.rs index e1b4736e3b..1d354eb65c 100644 --- a/src/sys/windows/hostcalls_impl/fs.rs +++ b/src/sys/windows/hostcalls_impl/fs.rs @@ -4,15 +4,17 @@ use super::fs_helpers::*; use crate::ctx::WasiCtx; use crate::fdentry::FdEntry; use crate::helpers::systemtime_to_timestamp; -use crate::sys::{errno_from_ioerror, errno_from_host}; +use crate::hostcalls_impl::PathGet; use crate::sys::fdentry_impl::determine_type_rights; use crate::sys::host_impl; +use crate::sys::{errno_from_host, errno_from_ioerror}; use crate::{host, Result}; use std::convert::TryInto; -use std::fs::{File, Metadata}; +use std::fs::{File, Metadata, OpenOptions}; use std::io::{self, Seek, SeekFrom}; -use std::os::windows::fs::FileExt; +use std::os::windows::fs::{FileExt, OpenOptionsExt}; use std::os::windows::prelude::{AsRawHandle, FromRawHandle}; +use std::path::{Path, PathBuf}; fn read_at(mut file: &File, buf: &mut [u8], offset: u64) -> io::Result { // get current cursor position @@ -49,13 +51,10 @@ pub(crate) fn fd_pwrite(file: &File, buf: &[u8], offset: host::__wasi_filesize_t } pub(crate) fn fd_fdstat_get(fd: &File) -> Result { - use winx::file::AccessRight; - match winx::file::get_file_access_rights(fd.as_raw_handle()) - .map(AccessRight::from_bits_truncate) - { - Ok(rights) => Ok(host_impl::fdflags_from_win(rights)), - Err(e) => Err(host_impl::errno_from_win(e)), - } + use winx::file::AccessMode; + winx::file::get_file_access_mode(fd.as_raw_handle()) + .map(host_impl::fdflags_from_win) + .map_err(host_impl::errno_from_win) } pub(crate) fn fd_fdstat_set_flags(fd: &File, fdflags: host::__wasi_fdflags_t) -> Result<()> { @@ -71,87 +70,90 @@ pub(crate) fn fd_advise( unimplemented!("fd_advise") } -pub(crate) fn path_create_directory(dirfd: &File, path: &str) -> Result<()> { - unimplemented!("path_create_directory") +pub(crate) fn path_create_directory(resolved: PathGet) -> Result<()> { + let path = concatenate(resolved.dirfd(), Path::new(resolved.path()))?; + std::fs::create_dir(&path).map_err(errno_from_ioerror) } -pub(crate) fn path_link( - old_dirfd: &File, - new_dirfd: &File, - old_path: &str, - new_path: &str, -) -> Result<()> { +pub(crate) fn path_link(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> { unimplemented!("path_link") } pub(crate) fn path_open( - ctx: &WasiCtx, - dirfd: host::__wasi_fd_t, - dirflags: host::__wasi_lookupflags_t, - path: &str, - oflags: host::__wasi_oflags_t, + resolved: PathGet, read: bool, write: bool, - mut needed_base: host::__wasi_rights_t, - mut needed_inheriting: host::__wasi_rights_t, - fs_flags: host::__wasi_fdflags_t, -) -> Result { - use winx::file::{AccessRight, CreationDisposition, FlagsAndAttributes, ShareMode}; + oflags: host::__wasi_oflags_t, + fdflags: host::__wasi_fdflags_t, +) -> Result { + use winx::file::{AccessMode, CreationDisposition, Flags}; - let mut win_rights = AccessRight::READ_CONTROL; + let mut access_mode = AccessMode::READ_CONTROL; if read { - win_rights.insert(AccessRight::FILE_GENERIC_READ); + access_mode.insert(AccessMode::FILE_GENERIC_READ); } if write { - win_rights.insert(AccessRight::FILE_GENERIC_WRITE); + access_mode.insert(AccessMode::FILE_GENERIC_WRITE); } + let mut flags = Flags::FILE_FLAG_BACKUP_SEMANTICS; + // convert open flags - let (win_create_disp, mut win_flags_attrs) = host_impl::win_from_oflags(oflags); - if win_create_disp == CreationDisposition::CREATE_NEW { - needed_base |= host::__WASI_RIGHT_PATH_CREATE_FILE; - } else if win_create_disp == CreationDisposition::CREATE_ALWAYS { - needed_base |= host::__WASI_RIGHT_PATH_CREATE_FILE; - } else if win_create_disp == CreationDisposition::TRUNCATE_EXISTING { - needed_base |= host::__WASI_RIGHT_PATH_FILESTAT_SET_SIZE; + let mut opts = OpenOptions::new(); + match host_impl::win_from_oflags(oflags) { + CreationDisposition::CREATE_ALWAYS => { + opts.create(true).append(true); + } + CreationDisposition::CREATE_NEW => { + opts.create_new(true).write(true); + } + CreationDisposition::TRUNCATE_EXISTING => { + opts.truncate(true); + } + _ => {} } // convert file descriptor flags - let win_fdflags_res = host_impl::win_from_fdflags(fs_flags); - win_rights.insert(win_fdflags_res.0); - win_flags_attrs.insert(win_fdflags_res.1); - if win_rights.contains(AccessRight::SYNCHRONIZE) { - needed_inheriting |= host::__WASI_RIGHT_FD_DATASYNC; - needed_inheriting |= host::__WASI_RIGHT_FD_SYNC; + let (add_access_mode, add_flags) = host_impl::win_from_fdflags(fdflags); + access_mode.insert(add_access_mode); + flags.insert(add_flags); + + let path = concatenate(resolved.dirfd(), Path::new(resolved.path()))?; + + match path.symlink_metadata().map(|metadata| metadata.file_type()) { + Ok(file_type) => { + // check if we are trying to open a symlink + if file_type.is_symlink() { + return Err(host::__WASI_ELOOP); + } + // check if we are trying to open a file as a dir + if file_type.is_file() && oflags & host::__WASI_O_DIRECTORY != 0 { + return Err(host::__WASI_ENOTDIR); + } + } + Err(e) => match e.raw_os_error() { + Some(e) => { + use winx::winerror::WinError; + log::debug!("path_open at symlink_metadata error code={:?}", e); + let e = WinError::from_u32(e as u32); + + if e != WinError::ERROR_FILE_NOT_FOUND { + return Err(host_impl::errno_from_win(e)); + } + // file not found, let it proceed to actually + // trying to open it + } + None => { + log::debug!("Inconvertible OS error: {}", e); + return Err(host::__WASI_EIO); + } + }, } - let dirfe = ctx.get_fd_entry(dirfd, needed_base, needed_inheriting)?; - let dirfd = dirfe.fd_object.descriptor.as_file()?; - let (dir, path) = path_get( - dirfd, - dirflags, - path, - !win_flags_attrs.contains(FlagsAndAttributes::FILE_FLAG_BACKUP_SEMANTICS), - )?; - - let new_handle = winx::file::openat( - dir.as_raw_handle(), - path.as_str(), - win_rights, - win_create_disp, - win_flags_attrs, - ) - .map_err(host_impl::errno_from_win)?; - - // Determine the type of the new file descriptor and which rights contradict with this type - let file = unsafe { File::from_raw_handle(new_handle) }; - determine_type_rights(&file).and_then(|(_ty, max_base, max_inheriting)| { - FdEntry::from(file).map(|mut fe| { - fe.rights_base &= max_base; - fe.rights_inheriting &= max_inheriting; - fe - }) - }) + opts.access_mode(access_mode.bits()) + .custom_flags(flags.bits()) + .open(&path) + .map_err(errno_from_ioerror) } pub(crate) fn fd_readdir( @@ -162,16 +164,45 @@ pub(crate) fn fd_readdir( unimplemented!("fd_readdir") } -pub(crate) fn path_readlink(dirfd: &File, path: &str, buf: &mut [u8]) -> Result { - unimplemented!("path_readlink") +pub(crate) fn path_readlink(resolved: PathGet, buf: &mut [u8]) -> Result { + use winx::file::get_path_by_handle; + + let path = concatenate(resolved.dirfd(), Path::new(resolved.path()))?; + let target_path = path.read_link().map_err(errno_from_ioerror)?; + + // since on Windows we are effectively emulating 'at' syscalls + // we need to strip the prefix from the absolute path + // as otherwise we will error out since WASI is not capable + // of dealing with absolute paths + let dir_path = + get_path_by_handle(resolved.dirfd().as_raw_handle()).map_err(host_impl::errno_from_win)?; + let dir_path = PathBuf::from(strip_extended_prefix(dir_path)); + let target_path = target_path + .strip_prefix(dir_path) + .map_err(|_| host::__WASI_ENOTCAPABLE) + .and_then(|path| path.to_str().map(String::from).ok_or(host::__WASI_EILSEQ))?; + + if buf.len() > 0 { + let mut chars = target_path.chars(); + let mut nread = 0usize; + + for i in 0..buf.len() { + match chars.next() { + Some(ch) => { + buf[i] = ch as u8; + nread += 1; + } + None => break, + } + } + + Ok(nread) + } else { + Ok(0) + } } -pub(crate) fn path_rename( - old_dirfd: &File, - old_path: &str, - new_dirfd: &File, - new_path: &str, -) -> Result<()> { +pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> { unimplemented!("path_rename") } @@ -250,17 +281,15 @@ pub(crate) fn fd_filestat_set_size(fd: &File, st_size: host::__wasi_filesize_t) } pub(crate) fn path_filestat_get( - dirfd: &File, + resolved: PathGet, dirflags: host::__wasi_lookupflags_t, - path: &str, ) -> Result { unimplemented!("path_filestat_get") } pub(crate) fn path_filestat_set_times( - dirfd: &File, + resolved: PathGet, dirflags: host::__wasi_lookupflags_t, - path: &str, st_atim: host::__wasi_timestamp_t, mut st_mtim: host::__wasi_timestamp_t, fst_flags: host::__wasi_fstflags_t, @@ -268,14 +297,78 @@ pub(crate) fn path_filestat_set_times( unimplemented!("path_filestat_set_times") } -pub(crate) fn path_symlink(dirfd: &File, old_path: &str, new_path: &str) -> Result<()> { - unimplemented!("path_symlink") +pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> Result<()> { + use std::os::windows::fs::{symlink_dir, symlink_file}; + use winx::winerror::WinError; + + let old_path = concatenate(resolved.dirfd(), Path::new(old_path))?; + let new_path = concatenate(resolved.dirfd(), Path::new(resolved.path()))?; + + // try creating a file symlink + symlink_file(&old_path, &new_path).or_else(|e| { + match e.raw_os_error() { + Some(e) => { + log::debug!("path_symlink at symlink_file error code={:?}", e); + match WinError::from_u32(e as u32) { + WinError::ERROR_NOT_A_REPARSE_POINT => { + // try creating a dir symlink instead + symlink_dir(old_path, new_path).map_err(errno_from_ioerror) + } + e => Err(host_impl::errno_from_win(e)), + } + } + None => { + log::debug!("Inconvertible OS error: {}", e); + Err(host::__WASI_EIO) + } + } + }) } -pub(crate) fn path_unlink_file(dirfd: &File, path: &str) -> Result<()> { - unimplemented!("path_unlink_file") +pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> { + use std::fs; + use winx::winerror::WinError; + + let path = concatenate(resolved.dirfd(), Path::new(resolved.path()))?; + let file_type = path + .symlink_metadata() + .map(|metadata| metadata.file_type()) + .map_err(errno_from_ioerror)?; + + // check if we're unlinking a symlink + // NB this will get cleaned up a lot when [std::os::windows::fs::FileTypeExt] + // stabilises + // + // [std::os::windows::fs::FileTypeExt]: https://doc.rust-lang.org/std/os/windows/fs/trait.FileTypeExt.html + if file_type.is_symlink() { + fs::remove_file(&path).or_else(|e| { + match e.raw_os_error() { + Some(e) => { + log::debug!("path_unlink_file at symlink_file error code={:?}", e); + match WinError::from_u32(e as u32) { + WinError::ERROR_ACCESS_DENIED => { + // try unlinking a dir symlink instead + fs::remove_dir(path).map_err(errno_from_ioerror) + } + e => Err(host_impl::errno_from_win(e)), + } + } + None => { + log::debug!("Inconvertible OS error: {}", e); + Err(host::__WASI_EIO) + } + } + }) + } else if file_type.is_dir() { + Err(host::__WASI_EISDIR) + } else if file_type.is_file() { + fs::remove_file(path).map_err(errno_from_ioerror) + } else { + Err(host::__WASI_EINVAL) + } } -pub(crate) fn path_remove_directory(dirfd: &File, path: &str) -> Result<()> { - unimplemented!("path_remove_directory") +pub(crate) fn path_remove_directory(resolved: PathGet) -> Result<()> { + let path = concatenate(resolved.dirfd(), Path::new(resolved.path()))?; + std::fs::remove_dir(&path).map_err(errno_from_ioerror) } diff --git a/src/sys/windows/hostcalls_impl/fs_helpers.rs b/src/sys/windows/hostcalls_impl/fs_helpers.rs index 57399d1638..b2c96d9e42 100644 --- a/src/sys/windows/hostcalls_impl/fs_helpers.rs +++ b/src/sys/windows/hostcalls_impl/fs_helpers.rs @@ -1,120 +1,141 @@ #![allow(non_camel_case_types)] -#![allow(unused_unsafe)] - -use crate::sys::errno_from_host; use crate::sys::host_impl; use crate::{host, Result}; +use std::ffi::{OsStr, OsString}; use std::fs::File; -use std::os::windows::prelude::{AsRawHandle, FromRawHandle}; -use std::path::{Component, Path}; +use std::os::windows::ffi::{OsStrExt, OsStringExt}; +use std::os::windows::prelude::AsRawHandle; +use std::path::{Path, PathBuf}; -/// Normalizes a path to ensure that the target path is located under the directory provided. -pub(crate) fn path_get( - dirfd: &File, - _dirflags: host::__wasi_lookupflags_t, - path: &str, - needs_final_component: bool, -) -> Result<(File, String)> { - if path.contains("\0") { - // if contains NUL, return EILSEQ - return Err(host::__WASI_EILSEQ); +pub(crate) fn path_open_rights( + rights_base: host::__wasi_rights_t, + rights_inheriting: host::__wasi_rights_t, + oflags: host::__wasi_oflags_t, + fdflags: host::__wasi_fdflags_t, +) -> (host::__wasi_rights_t, host::__wasi_rights_t) { + // which rights are needed on the dirfd? + let mut needed_base = host::__WASI_RIGHT_PATH_OPEN; + let mut needed_inheriting = rights_base | rights_inheriting; + + // convert open flags + if oflags & host::__WASI_O_CREAT != 0 { + needed_base |= host::__WASI_RIGHT_PATH_CREATE_FILE; + } else if oflags & host::__WASI_O_TRUNC != 0 { + needed_base |= host::__WASI_RIGHT_PATH_FILESTAT_SET_SIZE; } - let dirfd = dirfd.try_clone().map_err(|err| { - err.raw_os_error() - .map_or(host::__WASI_EBADF, errno_from_host) - })?; + // convert file descriptor flags + if fdflags & host::__WASI_FDFLAG_DSYNC != 0 + || fdflags & host::__WASI_FDFLAG_RSYNC != 0 + || fdflags & host::__WASI_FDFLAG_SYNC != 0 + { + needed_inheriting |= host::__WASI_RIGHT_FD_DATASYNC; + needed_inheriting |= host::__WASI_RIGHT_FD_SYNC; + } - // Stack of directory handles. Index 0 always corresponds with the directory provided - // to this function. Entering a directory causes a handle to be pushed, while handling - // ".." entries causes an entry to be popped. Index 0 cannot be popped, as this would imply - // escaping the base directory. - let mut dir_stack = vec![dirfd]; + (needed_base, needed_inheriting) +} - // Stack of paths left to process. This is initially the `path` argument to this function, but - // any symlinks we encounter are processed by pushing them on the stack. - let mut path_stack = vec![path.to_owned()]; +pub(crate) fn openat(dirfd: &File, path: &str) -> Result { + use std::fs::OpenOptions; + use std::os::windows::fs::OpenOptionsExt; + use winx::file::Flags; + use winx::winerror::WinError; - loop { - match path_stack.pop() { - Some(cur_path) => { - // dbg!(&cur_path); - let ends_with_slash = cur_path.ends_with("/"); - let mut components = Path::new(&cur_path).components(); - let head = match components.next() { - None => return Err(host::__WASI_ENOENT), - Some(p) => p, - }; - let tail = components.as_path(); - - if tail.components().next().is_some() { - let mut tail = host_impl::path_from_host(tail.as_os_str())?; - if ends_with_slash { - tail.push_str("/"); - } - path_stack.push(tail); - } - - match head { - Component::Prefix(_) | Component::RootDir => { - // path is absolute! - return Err(host::__WASI_ENOTCAPABLE); - } - Component::CurDir => { - // "." so skip - continue; - } - Component::ParentDir => { - // ".." so pop a dir - let _ = dir_stack.pop().ok_or(host::__WASI_ENOTCAPABLE)?; - - // we're not allowed to pop past the original directory - if dir_stack.is_empty() { - return Err(host::__WASI_ENOTCAPABLE); - } - } - Component::Normal(head) => { - let mut head = host_impl::path_from_host(head)?; - if ends_with_slash { - // preserve trailing slash - head.push_str("/"); - } - // should the component be a directory? it should if there is more path left to process, or - // if it has a trailing slash and `needs_final_component` is not set - if !path_stack.is_empty() || (ends_with_slash && !needs_final_component) { - match winx::file::openat( - dir_stack - .last() - .ok_or(host::__WASI_ENOTCAPABLE)? - .as_raw_handle(), - head.as_str(), - winx::file::AccessRight::FILE_GENERIC_READ, - winx::file::CreationDisposition::OPEN_EXISTING, - winx::file::FlagsAndAttributes::FILE_FLAG_BACKUP_SEMANTICS, - ) { - Ok(new_dir) => { - dir_stack.push(unsafe { File::from_raw_handle(new_dir) }); - continue; - } - Err(e) => { - return Err(host_impl::errno_from_win(e)); - } - } - } else { - // we're done - return Ok((dir_stack.pop().ok_or(host::__WASI_ENOTCAPABLE)?, head)); - } - } + let path = concatenate(dirfd, Path::new(path))?; + OpenOptions::new() + .read(true) + .custom_flags(Flags::FILE_FLAG_BACKUP_SEMANTICS.bits()) + .open(&path) + .map_err(|e| match e.raw_os_error() { + Some(e) => { + log::debug!("openat error={:?}", e); + match WinError::from_u32(e as u32) { + WinError::ERROR_INVALID_NAME => host::__WASI_ENOTDIR, + e => host_impl::errno_from_win(e), } } None => { - // no further components to process. means we've hit a case like "." or "a/..", or if the - // input path has trailing slashes and `needs_final_component` is not set - return Ok(( - dir_stack.pop().ok_or(host::__WASI_ENOTCAPABLE)?, - String::from("."), - )); + log::debug!("Inconvertible OS error: {}", e); + host::__WASI_EIO } + }) +} + +pub(crate) fn readlinkat(dirfd: &File, s_path: &str) -> Result { + use winx::file::get_path_by_handle; + use winx::winerror::WinError; + + let path = concatenate(dirfd, Path::new(s_path))?; + match path.read_link() { + Ok(target_path) => { + // since on Windows we are effectively emulating 'at' syscalls + // we need to strip the prefix from the absolute path + // as otherwise we will error out since WASI is not capable + // of dealing with absolute paths + let dir_path = + get_path_by_handle(dirfd.as_raw_handle()).map_err(host_impl::errno_from_win)?; + let dir_path = PathBuf::from(strip_extended_prefix(dir_path)); + target_path + .strip_prefix(dir_path) + .map_err(|_| host::__WASI_ENOTCAPABLE) + .and_then(|path| path.to_str().map(String::from).ok_or(host::__WASI_EILSEQ)) } + Err(e) => match e.raw_os_error() { + Some(e) => { + log::debug!("readlinkat error={:?}", e); + match WinError::from_u32(e as u32) { + WinError::ERROR_INVALID_NAME => { + if s_path.ends_with("/") { + // strip "/" and check if exists + let path = concatenate(dirfd, Path::new(s_path.trim_end_matches("/")))?; + if path.exists() && !path.is_dir() { + Err(host::__WASI_ENOTDIR) + } else { + Err(host::__WASI_ENOENT) + } + } else { + Err(host::__WASI_ENOENT) + } + } + e => Err(host_impl::errno_from_win(e)), + } + } + None => { + log::debug!("Inconvertible OS error: {}", e); + Err(host::__WASI_EIO) + } + }, } } + +pub(crate) fn strip_extended_prefix>(path: P) -> OsString { + let path: Vec = path.as_ref().encode_wide().collect(); + if &[92, 92, 63, 92] == &path[0..4] { + OsString::from_wide(&path[4..]) + } else { + OsString::from_wide(&path) + } +} + +pub(crate) fn concatenate>(dirfd: &File, path: P) -> Result { + use winx::file::get_path_by_handle; + + // WASI is not able to deal with absolute paths + // so error out if absolute + if path.as_ref().is_absolute() { + return Err(host::__WASI_ENOTCAPABLE); + } + + let dir_path = get_path_by_handle(dirfd.as_raw_handle()).map_err(host_impl::errno_from_win)?; + // concatenate paths + let mut out_path = PathBuf::from(dir_path); + out_path.push(path.as_ref()); + // strip extended prefix; otherwise we will error out on any relative + // components with `out_path` + let out_path = PathBuf::from(strip_extended_prefix(out_path)); + + log::debug!("out_path={:?}", out_path); + + Ok(out_path) +} diff --git a/src/sys/windows/hostcalls_impl/mod.rs b/src/sys/windows/hostcalls_impl/mod.rs index 94af56073f..9bf4b539a5 100644 --- a/src/sys/windows/hostcalls_impl/mod.rs +++ b/src/sys/windows/hostcalls_impl/mod.rs @@ -1,7 +1,7 @@ //! Windows-specific hostcalls that implement //! [WASI](https://github.com/CraneStation/wasmtime-wasi/blob/wasi/docs/WASI-overview.md). mod fs; -mod fs_helpers; +pub(crate) mod fs_helpers; mod misc; pub(crate) use self::fs::*; diff --git a/winx/src/file.rs b/winx/src/file.rs index de7f7ed00e..20172ee75a 100644 --- a/winx/src/file.rs +++ b/winx/src/file.rs @@ -1,9 +1,9 @@ #![allow(non_camel_case_types)] use crate::{winerror, Result}; -use std::ffi::{c_void, OsStr, OsString}; +use std::ffi::{c_void, OsString}; use std::fs::File; use std::io; -use std::os::windows::prelude::{AsRawHandle, OsStrExt, OsStringExt, RawHandle}; +use std::os::windows::prelude::{AsRawHandle, OsStringExt, RawHandle}; use winapi::shared::minwindef::{self, DWORD}; use winapi::um::{fileapi, fileapi::GetFileType, minwinbase, winbase, winnt}; @@ -55,28 +55,6 @@ pub fn get_file_type(handle: RawHandle) -> Result { } } -bitflags! { - pub struct ShareMode: minwindef::DWORD { - /// Prevents other processes from opening a file or device if they request delete, read, or write access. - const NO_SHARE = 0x0; - /// Enables subsequent open operations on a file or device to request read access. - /// Otherwise, other processes cannot open the file or device if they request read access. - /// If this flag is not specified, but the file or device has been opened for read access, the function fails. - const FILE_SHARE_READ = winnt::FILE_SHARE_READ; - /// Enables subsequent open operations on a file or device to request write access. - /// Otherwise, other processes cannot open the file or device if they request write access. - /// If this flag is not specified, but the file or device has been opened for write access or has a file mapping with write access, the function fails. - const FILE_SHARE_WRITE = winnt::FILE_SHARE_WRITE; - /// Enables subsequent open operations on a file or device to request delete access. - /// Otherwise, other processes cannot open the file or device if they request delete access. - /// If this flag is not specified, but the file or device has been opened for delete access, the function fails. - const FILE_SHARE_DELETE = winnt::FILE_SHARE_DELETE; - const ALL = ShareMode::FILE_SHARE_READ.bits - | ShareMode::FILE_SHARE_WRITE.bits - | ShareMode::FILE_SHARE_DELETE.bits; - } -} - #[derive(Debug, Clone, Copy, Eq, PartialEq)] #[repr(u32)] pub enum CreationDisposition { @@ -130,7 +108,7 @@ impl CreationDisposition { } bitflags! { - pub struct FlagsAndAttributes: minwindef::DWORD { + pub struct Attributes: minwindef::DWORD { /// A file or directory that is an archive file or directory. /// Applications typically use this attribute to mark files for backup or removal. const FILE_ATTRIBUTE_ARCHIVE = winnt::FILE_ATTRIBUTE_ARCHIVE; @@ -189,6 +167,11 @@ bitflags! { const FILE_ATTRIBUTE_TEMPORARY = winnt::FILE_ATTRIBUTE_TEMPORARY; /// This value is reserved for system use. const FILE_ATTRIBUTE_VIRTUAL = winnt::FILE_ATTRIBUTE_VIRTUAL; + } +} + +bitflags! { + pub struct Flags: minwindef::DWORD { /// The file is being opened or created for a backup or restore operation. /// The system ensures that the calling process overrides file security checks when the process has SE_BACKUP_NAME and SE_RESTORE_NAME privileges. /// You must set this flag to obtain a handle to a directory. A directory handle can be passed to some functions instead of a file handle. @@ -238,7 +221,7 @@ bitflags! { bitflags! { /// [Access mask]: https://docs.microsoft.com/en-us/windows/desktop/SecAuthZ/access-mask - pub struct AccessRight: minwindef::DWORD { + pub struct AccessMode: minwindef::DWORD { /// For a file object, the right to read the corresponding file data. /// For a directory object, the right to read the corresponding directory data. const FILE_READ_DATA = winnt::FILE_READ_DATA; @@ -307,31 +290,31 @@ bitflags! { const GENERIC_READ = winnt::GENERIC_READ; /// Provides read access. /// This flag is a union of: FILE_READ_ATTRIBUTES, FILE_READ_DATA, FILE_READ_EA, READ_CONTROL, SYNCHRONIZE - const FILE_GENERIC_READ = AccessRight::FILE_READ_ATTRIBUTES.bits - | AccessRight::FILE_READ_DATA.bits - | AccessRight::FILE_READ_EA.bits - | AccessRight::READ_CONTROL.bits - | AccessRight::SYNCHRONIZE.bits; + const FILE_GENERIC_READ = AccessMode::FILE_READ_ATTRIBUTES.bits + | AccessMode::FILE_READ_DATA.bits + | AccessMode::FILE_READ_EA.bits + | AccessMode::READ_CONTROL.bits + | AccessMode::SYNCHRONIZE.bits; /// Provides write access. /// This flag is a union of: FILE_WRITE_ATTRIBUTES, FILE_WRITE_DATA, FILE_WRITE_EA, READ_CONTROL, SYNCHRONIZE - const FILE_GENERIC_WRITE = AccessRight::FILE_WRITE_ATTRIBUTES.bits - | AccessRight::FILE_WRITE_DATA.bits - | AccessRight::FILE_WRITE_EA.bits - | AccessRight::READ_CONTROL.bits - | AccessRight::SYNCHRONIZE.bits; + const FILE_GENERIC_WRITE = AccessMode::FILE_WRITE_ATTRIBUTES.bits + | AccessMode::FILE_WRITE_DATA.bits + | AccessMode::FILE_WRITE_EA.bits + | AccessMode::READ_CONTROL.bits + | AccessMode::SYNCHRONIZE.bits; /// Provides execute access. /// This flag is a union of: FILE_WRITE_ATTRIBUTES, FILE_WRITE_DATA, FILE_WRITE_EA, READ_CONTROL, SYNCHRONIZE - const FILE_GENERIC_EXECUTE = AccessRight::FILE_EXECUTE.bits - | AccessRight::FILE_READ_ATTRIBUTES.bits - | AccessRight::READ_CONTROL.bits - | AccessRight::SYNCHRONIZE.bits; + const FILE_GENERIC_EXECUTE = AccessMode::FILE_EXECUTE.bits + | AccessMode::FILE_READ_ATTRIBUTES.bits + | AccessMode::READ_CONTROL.bits + | AccessMode::SYNCHRONIZE.bits; /// Provides all accesses. /// This flag is a union of: FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE - const FILE_GENERIC_ALL = AccessRight::FILE_GENERIC_READ.bits | AccessRight::FILE_GENERIC_WRITE.bits | AccessRight::FILE_GENERIC_EXECUTE.bits; + const FILE_GENERIC_ALL = AccessMode::FILE_GENERIC_READ.bits | AccessMode::FILE_GENERIC_WRITE.bits | AccessMode::FILE_GENERIC_EXECUTE.bits; } } -pub fn get_file_access_rights(handle: RawHandle) -> Result { +pub fn get_file_access_mode(handle: RawHandle) -> Result { use winapi::shared::minwindef::FALSE; use winapi::um::accctrl; use winapi::um::aclapi::GetSecurityInfo; @@ -368,18 +351,13 @@ pub fn get_file_access_rights(handle: RawHandle) -> Result { // TODO: check for PACCESS_ALLOWED_ACE in Ace before accessing // let header = (*(ace as winnt::PACCESS_ALLOWED_ACE)).Header.AceType; - Ok((*(ace as winnt::PACCESS_ALLOWED_ACE)).Mask) + Ok(AccessMode::from_bits_truncate( + (*(ace as winnt::PACCESS_ALLOWED_ACE)).Mask, + )) } } -/// Converts OS string reference to Windows wide UTF-16 format. -pub fn str_to_wide>(s: S) -> Vec { - let mut win_unicode: Vec = s.as_ref().encode_wide().collect(); - win_unicode.push(0); - win_unicode -} - -fn get_path_by_handle(handle: RawHandle) -> Result { +pub fn get_path_by_handle(handle: RawHandle) -> Result { use winapi::um::fileapi::GetFinalPathNameByHandleW; let mut raw_path: Vec = Vec::with_capacity(WIDE_MAX_PATH as usize); @@ -402,64 +380,6 @@ fn get_path_by_handle(handle: RawHandle) -> Result { Ok(OsString::from_wide(&raw_path)) } -fn strip_extended_prefix>(path: P) -> OsString { - let path = str_to_wide(path); - if &[92, 92, 63, 92] == &path[0..4] { - OsString::from_wide(&path[4..]) - } else { - OsString::from_wide(&path) - } -} - -/// Opens a `path` relative to a directory handle `dir_handle`, and returns a handle to the -/// newly opened file. The newly opened file will have the specified `AccessRight` `rights`. -/// -/// If the `path` is absolute, then the directory handle `dir_handle` is ignored. -pub fn openat>( - dir_handle: RawHandle, - path: S, - rights: AccessRight, - disposition: CreationDisposition, - flags_attrs: FlagsAndAttributes, -) -> Result { - use std::path::PathBuf; - use winapi::um::fileapi::CreateFileW; - use winapi::um::handleapi::INVALID_HANDLE_VALUE; - - // check if specified path is absolute - let path = PathBuf::from(path.as_ref()); - let out_path = if path.is_absolute() { - path - } else { - let dir_path = get_path_by_handle(dir_handle)?; - // concatenate paths - let mut out_path = PathBuf::from(&dir_path); - out_path.push(path); - out_path.into() - }; - - // this is needed so that we can use relative paths - let raw_out_path = strip_extended_prefix(out_path); - let raw_out_path = str_to_wide(raw_out_path); - let handle = unsafe { - CreateFileW( - raw_out_path.as_ptr(), - rights.bits(), - ShareMode::ALL.bits(), - std::ptr::null_mut(), - disposition as minwindef::DWORD, - flags_attrs.bits(), - std::ptr::null_mut(), - ) - }; - - if handle == INVALID_HANDLE_VALUE { - Err(winerror::WinError::last()) - } else { - Ok(handle) - } -} - // Taken from Rust libstd, file libstd/sys/windows/fs.rs fn cvt(i: winapi::shared::minwindef::BOOL) -> io::Result<()> { if i == 0 { diff --git a/winx/src/winerror.rs b/winx/src/winerror.rs index 107e7bf20e..e6237267db 100644 --- a/winx/src/winerror.rs +++ b/winx/src/winerror.rs @@ -78,6 +78,12 @@ win_error_expand! { ERROR_INVALID_NAME, /// The process cannot access the file because it is being used by another process. ERROR_SHARING_VIOLATION, + /// A required privilege is not held by the client. + ERROR_PRIVILEGE_NOT_HELD, + /// The file or directory is not a reparse point. + ERROR_NOT_A_REPARSE_POINT, + /// An attempt was made to move the file pointer before the beginning of the file. + ERROR_NEGATIVE_SEEK, } impl WinError {