use crate::handle::{Fdflags, Filestat, Fstflags, Handle, HandleRights, Oflags, Rights};
use crate::sched::Timestamp;
use crate::sys::osdir::OsDir;
use crate::sys::{fd, AsFile};
use crate::{Error, Result};
use std::convert::TryFrom;
use std::ffi::{OsStr, OsString};
use std::fs::{self, Metadata, OpenOptions};
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::os::windows::fs::OpenOptionsExt;
use std::path::{Path, PathBuf};
use winapi::shared::winerror;
use winx::file::AccessMode;
fn strip_trailing_slashes_and_concatenate(dirfd: &OsDir, path: &str) -> Result<Option<PathBuf>> {
if path.ends_with('/') {
let suffix = path.trim_end_matches('/');
concatenate(dirfd, Path::new(suffix)).map(Some)
} else {
Ok(None)
}
}
fn strip_extended_prefix<P: AsRef<OsStr>>(path: P) -> OsString {
let path: Vec<u16> = path.as_ref().encode_wide().collect();
if &[92, 92, 63, 92] == &path[0..4] {
OsString::from_wide(&path[4..])
} else {
OsString::from_wide(&path)
}
}
fn concatenate<P: AsRef<Path>>(file: &OsDir, path: P) -> Result<PathBuf> {
use winx::file::get_file_path;
if path.as_ref().is_absolute() {
return Err(Error::Notcapable);
}
let dir_path = get_file_path(&*file.as_file()?)?;
let mut out_path = PathBuf::from(dir_path);
out_path.push(path.as_ref());
let out_path = PathBuf::from(strip_extended_prefix(out_path));
tracing::debug!(out_path = tracing::field::debug(&out_path));
Ok(out_path)
}
fn file_access_mode_from_fdflags(fdflags: Fdflags, read: bool, write: bool) -> AccessMode {
let mut access_mode = AccessMode::READ_CONTROL;
access_mode.insert(AccessMode::FILE_WRITE_ATTRIBUTES);
if read {
access_mode.insert(AccessMode::FILE_GENERIC_READ);
}
if write {
access_mode.insert(AccessMode::FILE_GENERIC_WRITE);
}
if fdflags.contains(&Fdflags::APPEND) {
access_mode.insert(AccessMode::FILE_APPEND_DATA);
access_mode.remove(AccessMode::FILE_WRITE_DATA);
}
access_mode
}
pub(crate) fn from_host<S: AsRef<OsStr>>(s: S) -> Result<String> {
let vec: Vec<u16> = s.as_ref().encode_wide().collect();
let s = String::from_utf16(&vec)?;
Ok(s)
}
pub(crate) fn open_rights(
input_rights: &HandleRights,
oflags: Oflags,
fdflags: Fdflags,
) -> HandleRights {
let mut needed_base = Rights::PATH_OPEN;
let mut needed_inheriting = input_rights.base | input_rights.inheriting;
if oflags.contains(&Oflags::CREAT) {
needed_base |= Rights::PATH_CREATE_FILE;
} else if oflags.contains(&Oflags::TRUNC) {
needed_base |= Rights::PATH_FILESTAT_SET_SIZE;
}
if fdflags.contains(&Fdflags::DSYNC)
|| fdflags.contains(&Fdflags::RSYNC)
|| fdflags.contains(&Fdflags::SYNC)
{
needed_inheriting |= Rights::FD_DATASYNC;
needed_inheriting |= Rights::FD_SYNC;
}
HandleRights::new(needed_base, needed_inheriting)
}
pub(crate) fn readlinkat(dirfd: &OsDir, s_path: &str) -> Result<String> {
use winx::file::get_file_path;
let path = concatenate(dirfd, Path::new(s_path))?;
let err = match path.read_link() {
Ok(target_path) => {
let dir_path = get_file_path(&*dirfd.as_file()?)?;
let dir_path = PathBuf::from(strip_extended_prefix(dir_path));
let target_path = target_path
.strip_prefix(dir_path)
.map_err(|_| Error::Notcapable)?;
let target_path = target_path.to_str().ok_or(Error::Ilseq)?;
return Ok(target_path.to_owned());
}
Err(e) => e,
};
if let Some(code) = err.raw_os_error() {
tracing::debug!("readlinkat error={:?}", code);
if code as u32 == winerror::ERROR_INVALID_NAME {
if s_path.ends_with('/') {
let path = concatenate(dirfd, Path::new(s_path.trim_end_matches('/')))?;
if path.exists() && !path.is_dir() {
return Err(Error::Notdir);
}
}
}
}
Err(err.into())
}
pub(crate) fn create_directory(file: &OsDir, path: &str) -> Result<()> {
let path = concatenate(file, path)?;
std::fs::create_dir(&path)?;
Ok(())
}
pub(crate) fn link(
old_dirfd: &OsDir,
old_path: &str,
new_dirfd: &OsDir,
new_path: &str,
follow_symlinks: bool,
) -> Result<()> {
use std::fs;
let mut old_path = concatenate(old_dirfd, old_path)?;
let new_path = concatenate(new_dirfd, new_path)?;
if follow_symlinks {
tracing::debug!(
old_path = tracing::field::display(old_path.display()),
"Following symlinks"
);
old_path = fs::canonicalize(&old_path).map_err(|e| match e.raw_os_error() {
Some(code) if code as u32 == winerror::ERROR_CANT_RESOLVE_FILENAME => Error::Loop,
_ => e.into(),
})?;
}
let err = match fs::hard_link(&old_path, &new_path) {
Ok(()) => return Ok(()),
Err(e) => e,
};
if let Some(code) = err.raw_os_error() {
tracing::debug!("path_link at fs::hard_link error code={:?}", code);
if code as u32 == winerror::ERROR_ACCESS_DENIED {
if fs::metadata(&old_path).map(|m| m.is_dir()).unwrap_or(false) {
return Err(Error::Perm);
}
}
}
Err(err.into())
}
pub(crate) fn open(
dirfd: &OsDir,
path: &str,
read: bool,
write: bool,
oflags: Oflags,
fdflags: Fdflags,
) -> Result<Box<dyn Handle>> {
use winx::file::{AccessMode, CreationDisposition, Flags};
let is_trunc = oflags.contains(&Oflags::TRUNC);
if is_trunc {
if fdflags.contains(&Fdflags::APPEND) {
return Err(Error::Notsup);
}
}
let mut opts = OpenOptions::new();
match oflags.into() {
CreationDisposition::CREATE_ALWAYS => {
opts.create(true).truncate(true).write(true);
}
CreationDisposition::CREATE_NEW => {
opts.create_new(true).write(true);
}
CreationDisposition::TRUNCATE_EXISTING => {
opts.truncate(true).write(true);
}
_ => {}
}
let path = concatenate(dirfd, path)?;
match path.symlink_metadata().map(|metadata| metadata.file_type()) {
Ok(file_type) => {
if file_type.is_symlink() {
return Err(Error::Loop);
}
if file_type.is_file() && oflags.contains(&Oflags::DIRECTORY) {
return Err(Error::Notdir);
}
}
Err(err) => match err.raw_os_error() {
Some(code) => {
tracing::debug!("path_open at symlink_metadata error code={:?}", code);
match code as u32 {
winerror::ERROR_FILE_NOT_FOUND => {
}
winerror::ERROR_INVALID_NAME => {
return Err(Error::Notdir);
}
_ => return Err(err.into()),
};
}
None => {
tracing::debug!("Inconvertible OS error: {}", err);
return Err(Error::Io);
}
},
}
let mut access_mode = file_access_mode_from_fdflags(fdflags, read, write);
if is_trunc {
access_mode |= AccessMode::GENERIC_WRITE;
}
let flags: Flags = fdflags.into();
let file = opts
.access_mode(access_mode.bits())
.custom_flags(flags.bits())
.open(&path)?;
let handle = <Box<dyn Handle>>::try_from(file)?;
Ok(handle)
}
pub(crate) fn readlink(dirfd: &OsDir, path: &str, buf: &mut [u8]) -> Result<usize> {
use winx::file::get_file_path;
let path = concatenate(dirfd, path)?;
let target_path = path.read_link()?;
let dir_path = get_file_path(&*dirfd.as_file()?)?;
let dir_path = PathBuf::from(strip_extended_prefix(dir_path));
let target_path = target_path
.strip_prefix(dir_path)
.map_err(|_| Error::Notcapable)
.and_then(|path| path.to_str().map(String::from).ok_or(Error::Ilseq))?;
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 rename(
old_dirfd: &OsDir,
old_path_: &str,
new_dirfd: &OsDir,
new_path_: &str,
) -> Result<()> {
use std::fs;
let old_path = concatenate(old_dirfd, old_path_)?;
let new_path = concatenate(new_dirfd, new_path_)?;
if old_path.is_dir() && new_path.is_file() {
return Err(Error::Notdir);
}
if old_path.is_file() && new_path_.ends_with('/') {
return Err(Error::Notdir);
}
let err = match fs::rename(&old_path, &new_path) {
Ok(()) => return Ok(()),
Err(e) => e,
};
match err.raw_os_error() {
Some(code) => {
tracing::debug!("path_rename at rename error code={:?}", code);
match code as u32 {
winerror::ERROR_ACCESS_DENIED => {
if old_path.is_file() {
return Err(Error::Isdir);
} else {
fs::remove_dir(&new_path)?;
fs::rename(old_path, new_path)?;
return Ok(());
}
}
winerror::ERROR_INVALID_NAME => {
if let Some(path) =
strip_trailing_slashes_and_concatenate(old_dirfd, old_path_)?
{
if path.is_file() {
return Err(Error::Notdir);
}
}
}
_ => {}
}
Err(err.into())
}
None => {
tracing::debug!("Inconvertible OS error: {}", err);
Err(Error::Io)
}
}
}
pub(crate) fn symlink(old_path: &str, new_dirfd: &OsDir, new_path_: &str) -> Result<()> {
use std::os::windows::fs::{symlink_dir, symlink_file};
let old_path = concatenate(new_dirfd, Path::new(old_path))?;
let new_path = concatenate(new_dirfd, new_path_)?;
let use_dir_symlink = fs::metadata(&new_path)
.as_ref()
.map(Metadata::is_dir)
.unwrap_or(false);
let res = if use_dir_symlink {
symlink_dir(&old_path, &new_path)
} else {
symlink_file(&old_path, &new_path)
};
let err = match res {
Ok(()) => return Ok(()),
Err(e) => e,
};
match err.raw_os_error() {
Some(code) => {
tracing::debug!("path_symlink at symlink_file error code={:?}", code);
match code as u32 {
winerror::ERROR_INVALID_NAME => {
if let Some(path) =
strip_trailing_slashes_and_concatenate(new_dirfd, new_path_)?
{
if path.exists() {
return Err(Error::Exist);
}
}
}
_ => {}
}
Err(err.into())
}
None => {
tracing::debug!("Inconvertible OS error: {}", err);
Err(Error::Io)
}
}
}
pub(crate) fn unlink_file(dirfd: &OsDir, path: &str) -> Result<()> {
use std::fs;
let path = concatenate(dirfd, path)?;
let file_type = path
.symlink_metadata()
.map(|metadata| metadata.file_type())?;
if file_type.is_symlink() {
let err = match fs::remove_file(&path) {
Ok(()) => return Ok(()),
Err(e) => e,
};
match err.raw_os_error() {
Some(code) => {
tracing::debug!("path_unlink_file at symlink_file error code={:?}", code);
if code as u32 == winerror::ERROR_ACCESS_DENIED {
return fs::remove_dir(path).map_err(Into::into);
}
Err(err.into())
}
None => {
tracing::debug!("Inconvertible OS error: {}", err);
Err(Error::Io)
}
}
} else if file_type.is_dir() {
Err(Error::Isdir)
} else if file_type.is_file() {
fs::remove_file(path).map_err(Into::into)
} else {
Err(Error::Inval)
}
}
pub(crate) fn remove_directory(dirfd: &OsDir, path: &str) -> Result<()> {
let path = concatenate(dirfd, path)?;
std::fs::remove_dir(&path).map_err(Into::into)
}
pub(crate) fn filestat_get_at(dirfd: &OsDir, path: &str, follow: bool) -> Result<Filestat> {
use winx::file::Flags;
let path = concatenate(dirfd, path)?;
let mut opts = OpenOptions::new();
if !follow {
opts.custom_flags(Flags::FILE_FLAG_OPEN_REPARSE_POINT.bits());
}
let file = opts.read(true).open(path)?;
let stat = fd::filestat_get(&file)?;
Ok(stat)
}
pub(crate) fn filestat_set_times_at(
dirfd: &OsDir,
path: &str,
atim: Timestamp,
mtim: Timestamp,
fst_flags: Fstflags,
follow: bool,
) -> Result<()> {
use winx::file::{AccessMode, Flags};
let path = concatenate(dirfd, path)?;
let mut opts = OpenOptions::new();
if !follow {
opts.custom_flags(Flags::FILE_FLAG_OPEN_REPARSE_POINT.bits());
}
let file = opts
.access_mode(AccessMode::FILE_WRITE_ATTRIBUTES.bits())
.open(path)?;
fd::filestat_set_times(&file, atim, mtim, fst_flags)?;
Ok(())
}