Implement path_link for Windows. (#1199)

Implement path_link for Windows.
This commit is contained in:
Marcin Mielniczuk
2020-04-03 21:24:38 +02:00
committed by GitHub
parent 7728703ec7
commit f6e3ab03a2
4 changed files with 51 additions and 9 deletions

View File

@@ -233,7 +233,6 @@ mod wasi_tests {
"dangling_symlink" => true, "dangling_symlink" => true,
"symlink_loop" => true, "symlink_loop" => true,
"truncation_rights" => true, "truncation_rights" => true,
"path_link" => true,
"dangling_fd" => true, "dangling_fd" => true,
// TODO: virtfs files cannot be poll_oneoff'd yet // TODO: virtfs files cannot be poll_oneoff'd yet
"poll_oneoff_virtualfs" => true, "poll_oneoff_virtualfs" => true,

View File

@@ -74,6 +74,8 @@ unsafe fn check_rights(orig_fd: wasi::Fd, link_fd: wasi::Fd) {
fdstats_assert_eq(orig_fdstat, link_fdstat); fdstats_assert_eq(orig_fdstat, link_fdstat);
} }
// Extra calls of fd_close are needed for Windows, which will not remove
// the directory until all handles are closed.
unsafe fn test_path_link(dir_fd: wasi::Fd) { unsafe fn test_path_link(dir_fd: wasi::Fd) {
// Create a file // Create a file
let file_fd = create_or_open(dir_fd, "file", wasi::OFLAGS_CREAT); let file_fd = create_or_open(dir_fd, "file", wasi::OFLAGS_CREAT);
@@ -81,17 +83,20 @@ unsafe fn test_path_link(dir_fd: wasi::Fd) {
// Create a link in the same directory and compare rights // Create a link in the same directory and compare rights
wasi::path_link(dir_fd, 0, "file", dir_fd, "link") wasi::path_link(dir_fd, 0, "file", dir_fd, "link")
.expect("creating a link in the same directory"); .expect("creating a link in the same directory");
let mut link_fd = open_link(dir_fd, "link"); let link_fd = open_link(dir_fd, "link");
check_rights(file_fd, link_fd); check_rights(file_fd, link_fd);
wasi::fd_close(link_fd).expect("Closing link_fd"); // needed for Windows
wasi::path_unlink_file(dir_fd, "link").expect("removing a link"); wasi::path_unlink_file(dir_fd, "link").expect("removing a link");
// Create a link in a different directory and compare rights // Create a link in a different directory and compare rights
wasi::path_create_directory(dir_fd, "subdir").expect("creating a subdirectory"); wasi::path_create_directory(dir_fd, "subdir").expect("creating a subdirectory");
let subdir_fd = create_or_open(dir_fd, "subdir", wasi::OFLAGS_DIRECTORY); let subdir_fd = create_or_open(dir_fd, "subdir", wasi::OFLAGS_DIRECTORY);
wasi::path_link(dir_fd, 0, "file", subdir_fd, "link").expect("creating a link in subdirectory"); wasi::path_link(dir_fd, 0, "file", subdir_fd, "link").expect("creating a link in subdirectory");
link_fd = open_link(subdir_fd, "link"); let link_fd = open_link(subdir_fd, "link");
check_rights(file_fd, link_fd); check_rights(file_fd, link_fd);
wasi::path_unlink_file(subdir_fd, "link").expect("removing a link"); wasi::path_unlink_file(subdir_fd, "link").expect("removing a link");
wasi::fd_close(subdir_fd).expect("Closing subdir_fd"); // needed for Windows
wasi::fd_close(link_fd).expect("Closing link_fd"); // needed for Windows
wasi::path_remove_directory(dir_fd, "subdir").expect("removing a subdirectory"); wasi::path_remove_directory(dir_fd, "subdir").expect("removing a subdirectory");
// Create a link to a path that already exists // Create a link to a path that already exists
@@ -188,7 +193,7 @@ unsafe fn test_path_link(dir_fd: wasi::Fd) {
"link", "link",
) )
.expect("creating a link to a file following symlinks"); .expect("creating a link to a file following symlinks");
link_fd = open_link(dir_fd, "link"); let link_fd = open_link(dir_fd, "link");
check_rights(file_fd, link_fd); check_rights(file_fd, link_fd);
wasi::path_unlink_file(dir_fd, "link").expect("removing a link"); wasi::path_unlink_file(dir_fd, "link").expect("removing a link");
wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink"); wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink");

View File

@@ -69,7 +69,7 @@ impl From<io::Error> for Errno {
winerror::ERROR_DIRECTORY => Self::Notdir, winerror::ERROR_DIRECTORY => Self::Notdir,
winerror::ERROR_ALREADY_EXISTS => Self::Exist, winerror::ERROR_ALREADY_EXISTS => Self::Exist,
x => { x => {
log::debug!("unknown error value: {}", x); log::debug!("winerror: unknown error value: {}", x);
Self::Io Self::Io
} }
}, },

View File

@@ -3,6 +3,7 @@ use crate::fd;
use crate::path::PathGet; use crate::path::PathGet;
use crate::sys::entry::OsHandle; use crate::sys::entry::OsHandle;
use crate::wasi::{types, Errno, Result}; use crate::wasi::{types, Errno, Result};
use log::debug;
use std::convert::TryInto; use std::convert::TryInto;
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
use std::fs::{File, Metadata, OpenOptions}; use std::fs::{File, Metadata, OpenOptions};
@@ -164,11 +165,48 @@ pub(crate) fn create_directory(file: &File, path: &str) -> Result<()> {
} }
pub(crate) fn link( pub(crate) fn link(
_resolved_old: PathGet, resolved_old: PathGet,
_resolved_new: PathGet, resolved_new: PathGet,
_follow_symlinks: bool, follow_symlinks: bool,
) -> Result<()> { ) -> Result<()> {
unimplemented!("path_link") use std::fs;
let mut old_path = resolved_old.concatenate()?;
let new_path = resolved_new.concatenate()?;
if follow_symlinks {
// in particular, this will return an error if the target path doesn't exist
debug!("Following symlinks for path: {:?}", old_path);
old_path = fs::canonicalize(&old_path).map_err(|e| match e.raw_os_error() {
// fs::canonicalize under Windows will return:
// * ERROR_FILE_NOT_FOUND, if it encounters a dangling symlink
// * ERROR_CANT_RESOLVE_FILENAME, if it encounters a symlink loop
Some(code) if code as u32 == winerror::ERROR_CANT_RESOLVE_FILENAME => Errno::Loop,
_ => e.into(),
})?;
}
fs::hard_link(&old_path, &new_path).or_else(|err| {
match err.raw_os_error() {
Some(code) => {
debug!("path_link at fs::hard_link error code={:?}", code);
match code as u32 {
winerror::ERROR_ACCESS_DENIED => {
// If an attempt is made to create a hard link to a directory, POSIX-compliant
// implementations of link return `EPERM`, but `ERROR_ACCESS_DENIED` is converted
// to `EACCES`. We detect and correct this case here.
if fs::metadata(&old_path).map(|m| m.is_dir()).unwrap_or(false) {
return Err(Errno::Perm);
}
}
_ => {}
}
Err(err.into())
}
None => {
log::debug!("Inconvertible OS error: {}", err);
Err(Errno::Io)
}
}
})
} }
pub(crate) fn open( pub(crate) fn open(