mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
parent
392ae87a9f
commit
9d285e953d
6 changed files with 243 additions and 218 deletions
|
@ -351,7 +351,7 @@ fn cat_path(
|
|||
|
||||
if let Some(out_info) = out_info {
|
||||
if out_info.file_size() != 0
|
||||
&& FileInformation::from_file(&file).as_ref() == Some(out_info)
|
||||
&& FileInformation::from_file(&file).ok().as_ref() == Some(out_info)
|
||||
{
|
||||
return Err(CatError::OutputIsInput);
|
||||
}
|
||||
|
@ -367,7 +367,7 @@ fn cat_path(
|
|||
}
|
||||
|
||||
fn cat_files(files: &[String], options: &OutputOptions) -> UResult<()> {
|
||||
let out_info = FileInformation::from_file(&std::io::stdout());
|
||||
let out_info = FileInformation::from_file(&std::io::stdout()).ok();
|
||||
|
||||
let mut state = OutputState {
|
||||
line_number: 1,
|
||||
|
|
|
@ -19,10 +19,6 @@ extern crate uucore;
|
|||
use uucore::display::Quotable;
|
||||
use uucore::format_usage;
|
||||
use uucore::fs::FileInformation;
|
||||
#[cfg(windows)]
|
||||
use winapi::um::fileapi::CreateFileW;
|
||||
#[cfg(windows)]
|
||||
use winapi::um::fileapi::GetFileInformationByHandle;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
|
@ -35,22 +31,17 @@ use std::collections::HashSet;
|
|||
use std::env;
|
||||
#[cfg(not(windows))]
|
||||
use std::ffi::CString;
|
||||
#[cfg(windows)]
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io;
|
||||
use std::io::{stdin, stdout, Write};
|
||||
use std::mem;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::{FileTypeExt, PermissionsExt};
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
use std::os::unix::io::AsRawFd;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::path::{Path, PathBuf, StripPrefixError};
|
||||
use std::str::FromStr;
|
||||
use std::string::ToString;
|
||||
|
@ -793,60 +784,30 @@ fn preserve_hardlinks(
|
|||
#[cfg(not(target_os = "redox"))]
|
||||
{
|
||||
if !source.is_dir() {
|
||||
unsafe {
|
||||
let inode: u64;
|
||||
let nlinks: u64;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let src_path = CString::new(source.as_os_str().to_str().unwrap()).unwrap();
|
||||
let mut stat = mem::zeroed();
|
||||
if libc::lstat(src_path.as_ptr(), &mut stat) < 0 {
|
||||
return Err(format!(
|
||||
"cannot stat {}: {}",
|
||||
source.quote(),
|
||||
std::io::Error::last_os_error()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
inode = stat.st_ino as u64;
|
||||
nlinks = stat.st_nlink as u64;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let src_path: Vec<u16> = OsStr::new(source).encode_wide().collect();
|
||||
#[allow(deprecated)]
|
||||
let stat = mem::uninitialized();
|
||||
let handle = CreateFileW(
|
||||
src_path.as_ptr(),
|
||||
winapi::um::winnt::GENERIC_READ,
|
||||
winapi::um::winnt::FILE_SHARE_READ,
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
if GetFileInformationByHandle(handle, stat) != 0 {
|
||||
return Err(format!(
|
||||
"cannot get file information {:?}: {}",
|
||||
source,
|
||||
std::io::Error::last_os_error()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
inode = ((*stat).nFileIndexHigh as u64) << 32 | (*stat).nFileIndexLow as u64;
|
||||
nlinks = (*stat).nNumberOfLinks as u64;
|
||||
let info = match FileInformation::from_path(source, false) {
|
||||
Ok(info) => info,
|
||||
Err(e) => {
|
||||
return Err(format!("cannot stat {}: {}", source.quote(), e,).into());
|
||||
}
|
||||
};
|
||||
|
||||
for hard_link in hard_links.iter() {
|
||||
if hard_link.1 == inode {
|
||||
std::fs::hard_link(hard_link.0.clone(), dest).unwrap();
|
||||
*found_hard_link = true;
|
||||
}
|
||||
}
|
||||
if !(*found_hard_link) && nlinks > 1 {
|
||||
hard_links.push((dest.to_str().unwrap().to_string(), inode));
|
||||
#[cfg(unix)]
|
||||
let inode = info.inode();
|
||||
|
||||
#[cfg(windows)]
|
||||
let inode = info.file_index();
|
||||
|
||||
let nlinks = info.number_of_links();
|
||||
|
||||
for hard_link in hard_links.iter() {
|
||||
if hard_link.1 == inode {
|
||||
std::fs::hard_link(hard_link.0.clone(), dest).unwrap();
|
||||
*found_hard_link = true;
|
||||
}
|
||||
}
|
||||
if !(*found_hard_link) && nlinks > 1 {
|
||||
hard_links.push((dest.to_str().unwrap().to_string(), inode));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -1227,7 +1188,7 @@ fn symlink_file(
|
|||
{
|
||||
std::os::windows::fs::symlink_file(source, dest).context(context)?;
|
||||
}
|
||||
if let Some(file_info) = FileInformation::from_path(dest, false) {
|
||||
if let Ok(file_info) = FileInformation::from_path(dest, false) {
|
||||
symlinked_files.insert(file_info);
|
||||
}
|
||||
Ok(())
|
||||
|
@ -1245,7 +1206,8 @@ fn backup_dest(dest: &Path, backup_path: &Path) -> CopyResult<PathBuf> {
|
|||
}
|
||||
|
||||
fn handle_existing_dest(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
|
||||
if paths_refer_to_same_file(source, dest)? {
|
||||
let dereference_to_compare = options.dereference || !is_symlink(source);
|
||||
if paths_refer_to_same_file(source, dest, dereference_to_compare) {
|
||||
return Err(format!("{}: same file", context_for(source, dest)).into());
|
||||
}
|
||||
|
||||
|
@ -1253,7 +1215,7 @@ fn handle_existing_dest(source: &Path, dest: &Path, options: &Options) -> CopyRe
|
|||
|
||||
let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix);
|
||||
if let Some(backup_path) = backup_path {
|
||||
if paths_refer_to_same_file(source, &backup_path)? {
|
||||
if paths_refer_to_same_file(source, &backup_path, true) {
|
||||
return Err(format!(
|
||||
"backing up {} might destroy source; {} not copied",
|
||||
dest.quote(),
|
||||
|
@ -1313,7 +1275,8 @@ fn copy_file(
|
|||
dest.display()
|
||||
)));
|
||||
}
|
||||
if options.dereference && !dest.exists() {
|
||||
let copy_contents = options.dereference || !is_symlink(source);
|
||||
if copy_contents && !dest.exists() {
|
||||
return Err(Error::Error(format!(
|
||||
"not writing through dangling symlink '{}'",
|
||||
dest.display()
|
||||
|
@ -1542,7 +1505,7 @@ fn copy_link(
|
|||
} else {
|
||||
// we always need to remove the file to be able to create a symlink,
|
||||
// even if it is writeable.
|
||||
if dest.is_file() {
|
||||
if is_symlink(dest) || dest.is_file() {
|
||||
fs::remove_file(dest)?;
|
||||
}
|
||||
dest.into()
|
||||
|
@ -1685,12 +1648,15 @@ pub fn localize_to_target(root: &Path, source: &Path, target: &Path) -> CopyResu
|
|||
Ok(target.join(&local_to_root))
|
||||
}
|
||||
|
||||
pub fn paths_refer_to_same_file(p1: &Path, p2: &Path) -> io::Result<bool> {
|
||||
pub fn paths_refer_to_same_file(p1: &Path, p2: &Path, dereference: bool) -> bool {
|
||||
// We have to take symlinks and relative paths into account.
|
||||
let pathbuf1 = canonicalize(p1, MissingHandling::Normal, ResolveMode::Logical)?;
|
||||
let pathbuf2 = canonicalize(p2, MissingHandling::Normal, ResolveMode::Logical)?;
|
||||
let res1 = FileInformation::from_path(p1, dereference);
|
||||
let res2 = FileInformation::from_path(p2, dereference);
|
||||
|
||||
Ok(pathbuf1 == pathbuf2)
|
||||
match (res1, res2) {
|
||||
(Ok(info1), Ok(info2)) => info1 == info2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path_has_prefix(p1: &Path, p2: &Path) -> io::Result<bool> {
|
||||
|
|
|
@ -17,12 +17,14 @@ use libc::{
|
|||
S_IXUSR,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
#[cfg(unix)]
|
||||
use std::collections::HashSet;
|
||||
use std::collections::VecDeque;
|
||||
use std::env;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fs;
|
||||
use std::hash::Hash;
|
||||
use std::io::Error as IOError;
|
||||
use std::io::Result as IOResult;
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::io::{Error, ErrorKind, Result as IOResult};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::{fs::MetadataExt, io::AsRawFd};
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
@ -46,29 +48,23 @@ pub struct FileInformation(
|
|||
impl FileInformation {
|
||||
/// Get information from a currently open file
|
||||
#[cfg(unix)]
|
||||
pub fn from_file(file: &impl AsRawFd) -> Option<Self> {
|
||||
if let Ok(x) = nix::sys::stat::fstat(file.as_raw_fd()) {
|
||||
Some(Self(x))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
pub fn from_file(file: &impl AsRawFd) -> IOResult<Self> {
|
||||
let stat = nix::sys::stat::fstat(file.as_raw_fd())?;
|
||||
Ok(Self(stat))
|
||||
}
|
||||
|
||||
/// Get information from a currently open file
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn from_file(file: &impl AsHandleRef) -> Option<Self> {
|
||||
if let Ok(x) = winapi_util::file::information(file.as_handle_ref()) {
|
||||
Some(Self(x))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
pub fn from_file(file: &impl AsHandleRef) -> IOResult<Self> {
|
||||
let info = winapi_util::file::information(file.as_handle_ref())?;
|
||||
Ok(Self(info))
|
||||
}
|
||||
|
||||
/// Get information for a given path.
|
||||
///
|
||||
/// If `path` points to a symlink and `dereference` is true, information about
|
||||
/// the link's target will be returned.
|
||||
pub fn from_path(path: impl AsRef<Path>, dereference: bool) -> Option<Self> {
|
||||
pub fn from_path(path: impl AsRef<Path>, dereference: bool) -> IOResult<Self> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let stat = if dereference {
|
||||
|
@ -76,11 +72,7 @@ impl FileInformation {
|
|||
} else {
|
||||
nix::sys::stat::lstat(path.as_ref())
|
||||
};
|
||||
if let Ok(stat) = stat {
|
||||
Some(Self(stat))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Ok(Self(stat?))
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
|
@ -90,11 +82,8 @@ impl FileInformation {
|
|||
if !dereference {
|
||||
open_options.custom_flags(winapi::um::winbase::FILE_FLAG_OPEN_REPARSE_POINT);
|
||||
}
|
||||
open_options
|
||||
.read(true)
|
||||
.open(path.as_ref())
|
||||
.ok()
|
||||
.and_then(|file| Self::from_file(&file))
|
||||
let file = open_options.read(true).open(path.as_ref())?;
|
||||
Self::from_file(&file)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,6 +98,23 @@ impl FileInformation {
|
|||
self.0.file_size()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn file_index(&self) -> u64 {
|
||||
self.0.file_index()
|
||||
}
|
||||
|
||||
pub fn number_of_links(&self) -> u64 {
|
||||
#[cfg(unix)]
|
||||
return self.0.st_nlink as u64;
|
||||
#[cfg(windows)]
|
||||
return self.0.number_of_links() as u64;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn inode(&self) -> u64 {
|
||||
self.0.st_ino as u64
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
|
@ -233,47 +239,45 @@ pub fn is_symlink<P: AsRef<Path>>(path: P) -> bool {
|
|||
fs::symlink_metadata(path).map_or(false, |m| m.file_type().is_symlink())
|
||||
}
|
||||
|
||||
fn resolve<P: AsRef<Path>>(original: P) -> Result<PathBuf, (bool, PathBuf, IOError)> {
|
||||
const MAX_LINKS_FOLLOWED: u32 = 255;
|
||||
let mut followed = 0;
|
||||
let mut result = original.as_ref().to_path_buf();
|
||||
let mut symlink_is_absolute = false;
|
||||
let mut first_resolution = None;
|
||||
fn resolve_symlink<P: AsRef<Path>>(path: P) -> IOResult<Option<PathBuf>> {
|
||||
let result = if fs::symlink_metadata(&path)?.file_type().is_symlink() {
|
||||
Some(fs::read_link(&path)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
loop {
|
||||
if followed == MAX_LINKS_FOLLOWED {
|
||||
return Err((
|
||||
symlink_is_absolute,
|
||||
// When we hit MAX_LINKS_FOLLOWED we should return the first resolution (that's what GNU does - for whatever reason)
|
||||
first_resolution.unwrap(),
|
||||
Error::new(ErrorKind::InvalidInput, "maximum links followed"),
|
||||
));
|
||||
}
|
||||
enum OwningComponent {
|
||||
Prefix(OsString),
|
||||
RootDir,
|
||||
CurDir,
|
||||
ParentDir,
|
||||
Normal(OsString),
|
||||
}
|
||||
|
||||
match fs::symlink_metadata(&result) {
|
||||
Ok(meta) => {
|
||||
if !meta.file_type().is_symlink() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => return Err((symlink_is_absolute, result, e)),
|
||||
}
|
||||
|
||||
followed += 1;
|
||||
match fs::read_link(&result) {
|
||||
Ok(path) => {
|
||||
result.pop();
|
||||
symlink_is_absolute = path.is_absolute();
|
||||
result.push(path);
|
||||
}
|
||||
Err(e) => return Err((symlink_is_absolute, result, e)),
|
||||
}
|
||||
|
||||
if first_resolution.is_none() {
|
||||
first_resolution = Some(result.clone());
|
||||
impl OwningComponent {
|
||||
fn as_os_str(&self) -> &OsStr {
|
||||
match self {
|
||||
Self::Prefix(s) => s.as_os_str(),
|
||||
Self::RootDir => Component::RootDir.as_os_str(),
|
||||
Self::CurDir => Component::CurDir.as_os_str(),
|
||||
Self::ParentDir => Component::ParentDir.as_os_str(),
|
||||
Self::Normal(s) => s.as_os_str(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Component<'a>> for OwningComponent {
|
||||
fn from(comp: Component<'a>) -> Self {
|
||||
match comp {
|
||||
Component::Prefix(_) => Self::Prefix(comp.as_os_str().to_os_string()),
|
||||
Component::RootDir => Self::RootDir,
|
||||
Component::CurDir => Self::CurDir,
|
||||
Component::ParentDir => Self::ParentDir,
|
||||
Component::Normal(s) => Self::Normal(s.to_os_string()),
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Return the canonical, absolute form of a path.
|
||||
|
@ -307,7 +311,7 @@ pub fn canonicalize<P: AsRef<Path>>(
|
|||
miss_mode: MissingHandling,
|
||||
res_mode: ResolveMode,
|
||||
) -> IOResult<PathBuf> {
|
||||
// Create an absolute path
|
||||
const SYMLINKS_TO_LOOK_FOR_LOOPS: i32 = 256;
|
||||
let original = original.as_ref();
|
||||
let original = if original.is_absolute() {
|
||||
original.to_path_buf()
|
||||
|
@ -315,86 +319,72 @@ pub fn canonicalize<P: AsRef<Path>>(
|
|||
let current_dir = env::current_dir()?;
|
||||
dunce::canonicalize(current_dir)?.join(original)
|
||||
};
|
||||
|
||||
let path = if res_mode == ResolveMode::Logical {
|
||||
normalize_path(&original)
|
||||
} else {
|
||||
original
|
||||
};
|
||||
let mut parts: VecDeque<OwningComponent> = path.components().map(|part| part.into()).collect();
|
||||
let mut result = PathBuf::new();
|
||||
let mut parts = vec![];
|
||||
|
||||
// Split path by directory separator; add prefix (Windows-only) and root
|
||||
// directory to final path buffer; add remaining parts to temporary
|
||||
// vector for canonicalization.
|
||||
for part in original.components() {
|
||||
let mut followed_symlinks = 0;
|
||||
#[cfg(unix)]
|
||||
let mut visited_files = HashSet::new();
|
||||
while let Some(part) = parts.pop_front() {
|
||||
match part {
|
||||
Component::Prefix(_) | Component::RootDir => {
|
||||
result.push(part.as_os_str());
|
||||
}
|
||||
Component::CurDir => (),
|
||||
Component::ParentDir => {
|
||||
if res_mode == ResolveMode::Logical {
|
||||
parts.pop();
|
||||
} else {
|
||||
parts.push(part.as_os_str());
|
||||
}
|
||||
}
|
||||
Component::Normal(_) => {
|
||||
parts.push(part.as_os_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the symlinks where possible
|
||||
if !parts.is_empty() {
|
||||
for part in parts[..parts.len() - 1].iter() {
|
||||
result.push(part);
|
||||
|
||||
//resolve as we go to handle long relative paths on windows
|
||||
if res_mode == ResolveMode::Physical {
|
||||
result = normalize_path(&result);
|
||||
}
|
||||
|
||||
if res_mode == ResolveMode::None {
|
||||
OwningComponent::Prefix(s) => {
|
||||
result.push(s);
|
||||
continue;
|
||||
}
|
||||
OwningComponent::RootDir | OwningComponent::Normal(..) => {
|
||||
result.push(part.as_os_str());
|
||||
}
|
||||
OwningComponent::CurDir => {}
|
||||
OwningComponent::ParentDir => {
|
||||
result.pop();
|
||||
}
|
||||
}
|
||||
if res_mode == ResolveMode::None {
|
||||
continue;
|
||||
}
|
||||
match resolve_symlink(&result) {
|
||||
Ok(Some(link_path)) => {
|
||||
for link_part in link_path.components().rev() {
|
||||
parts.push_front(link_part.into());
|
||||
}
|
||||
if followed_symlinks < SYMLINKS_TO_LOOK_FOR_LOOPS {
|
||||
followed_symlinks += 1;
|
||||
} else {
|
||||
#[cfg(unix)]
|
||||
let has_loop = {
|
||||
let file_info =
|
||||
FileInformation::from_path(&result.parent().unwrap(), false).unwrap();
|
||||
let mut path_to_follow = PathBuf::new();
|
||||
for part in &parts {
|
||||
path_to_follow.push(part.as_os_str());
|
||||
}
|
||||
!visited_files.insert((file_info, path_to_follow))
|
||||
};
|
||||
|
||||
match resolve(&result) {
|
||||
Err((_, path, e)) => {
|
||||
if miss_mode == MissingHandling::Missing {
|
||||
result = path;
|
||||
} else {
|
||||
return Err(e);
|
||||
#[cfg(not(unix))]
|
||||
let has_loop = true;
|
||||
|
||||
if has_loop {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
"Too many levels of symbolic links",
|
||||
)); // TODO use ErrorKind::FilesystemLoop when stable
|
||||
}
|
||||
}
|
||||
Ok(path) => {
|
||||
result = path;
|
||||
}
|
||||
result.pop();
|
||||
}
|
||||
}
|
||||
|
||||
result.push(parts.last().unwrap());
|
||||
|
||||
if res_mode == ResolveMode::None {
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
match resolve(&result) {
|
||||
Err((is_absolute, path, err)) => {
|
||||
// If the resolved symlink is an absolute path and non-existent,
|
||||
// `realpath` throws no such file error.
|
||||
Err(e) => {
|
||||
if miss_mode == MissingHandling::Existing
|
||||
|| (err.kind() == ErrorKind::NotFound
|
||||
&& is_absolute
|
||||
&& miss_mode == MissingHandling::Normal)
|
||||
|| (miss_mode == MissingHandling::Normal && !parts.is_empty())
|
||||
{
|
||||
return Err(err);
|
||||
} else {
|
||||
result = path;
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
Ok(path) => {
|
||||
result = path;
|
||||
}
|
||||
}
|
||||
if res_mode == ResolveMode::Physical {
|
||||
result = normalize_path(&result);
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
|
|
|
@ -1492,13 +1492,12 @@ fn test_copy_through_just_created_symlink() {
|
|||
at.mkdir("a");
|
||||
at.mkdir("b");
|
||||
at.mkdir("c");
|
||||
#[cfg(unix)]
|
||||
fs::symlink("../t", at.plus("a/1")).unwrap();
|
||||
#[cfg(target_os = "windows")]
|
||||
symlink_file("../t", at.plus("a/1")).unwrap();
|
||||
at.relative_symlink_file("../t", "a/1");
|
||||
at.touch("b/1");
|
||||
at.write("b/1", "hello");
|
||||
if create_t {
|
||||
at.touch("t");
|
||||
at.write("t", "world");
|
||||
}
|
||||
ucmd.arg("--no-dereference")
|
||||
.arg("a/1")
|
||||
|
@ -1510,6 +1509,9 @@ fn test_copy_through_just_created_symlink() {
|
|||
} else {
|
||||
"cp: will not copy 'b/1' through just-created symlink 'c\\1'"
|
||||
});
|
||||
if create_t {
|
||||
assert_eq!(at.read("a/1"), "world");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1536,6 +1538,16 @@ fn test_copy_through_dangling_symlink_no_dereference() {
|
|||
.no_stdout();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_through_dangling_symlink_no_dereference_2() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.touch("file");
|
||||
at.symlink_file("nonexistent", "target");
|
||||
ucmd.args(&["-P", "file", "target"])
|
||||
.fails()
|
||||
.stderr_only("cp: not writing through dangling symlink 'target'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_cp_archive_on_nonexistent_file() {
|
||||
|
@ -1658,3 +1670,52 @@ fn test_cp_overriding_arguments() {
|
|||
s.fixtures.remove("file2");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_no_dereference_1() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.mkdir("a");
|
||||
at.mkdir("b");
|
||||
at.touch("a/foo");
|
||||
at.write("a/foo", "bar");
|
||||
at.relative_symlink_file("../a/foo", "b/foo");
|
||||
ucmd.args(&["-P", "a/foo", "b"]).fails();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_abuse_existing() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.mkdir("a");
|
||||
at.mkdir("b");
|
||||
at.mkdir("c");
|
||||
at.relative_symlink_file("../t", "a/1");
|
||||
at.touch("b/1");
|
||||
at.write("b/1", "hello");
|
||||
at.relative_symlink_file("../t", "c/1");
|
||||
at.touch("t");
|
||||
at.write("t", "i");
|
||||
ucmd.args(&["-dR", "a/1", "b/1", "c"])
|
||||
.fails()
|
||||
.stderr_contains(format!(
|
||||
"will not copy 'b/1' through just-created symlink 'c{}1'",
|
||||
if cfg!(windows) { "\\" } else { "/" }
|
||||
));
|
||||
assert_eq!(at.read("t"), "i");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_same_symlink_no_dereference() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.relative_symlink_file("t", "a");
|
||||
at.relative_symlink_file("t", "b");
|
||||
at.touch("t");
|
||||
ucmd.args(&["-d", "a", "b"]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_same_symlink_no_dereference_dangling() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.relative_symlink_file("t", "a");
|
||||
at.relative_symlink_file("t", "b");
|
||||
ucmd.args(&["-d", "a", "b"]).succeeds();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::common::util::*;
|
||||
|
||||
use std::path::Path;
|
||||
use std::path::{Path, MAIN_SEPARATOR};
|
||||
|
||||
static GIBBERISH: &str = "supercalifragilisticexpialidocious";
|
||||
|
||||
|
@ -155,8 +155,8 @@ fn test_realpath_dangling() {
|
|||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.symlink_file("nonexistent-file", "link");
|
||||
ucmd.arg("link")
|
||||
.fails()
|
||||
.stderr_contains("realpath: link: No such file or directory");
|
||||
.succeeds()
|
||||
.stdout_contains(at.plus_as_string("nonexistent-file\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -166,8 +166,8 @@ fn test_realpath_loop() {
|
|||
at.symlink_file("3", "2");
|
||||
at.symlink_file("1", "3");
|
||||
ucmd.arg("1")
|
||||
.succeeds()
|
||||
.stdout_only(at.plus_as_string("2\n"));
|
||||
.fails()
|
||||
.stderr_contains("Too many levels of symbolic links");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -241,7 +241,6 @@ fn test_realpath_when_symlink_is_absolute_and_enoent() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "issue #3669"]
|
||||
fn test_realpath_when_symlink_part_is_missing() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
|
@ -254,10 +253,13 @@ fn test_realpath_when_symlink_part_is_missing() {
|
|||
at.relative_symlink_file("../dir2/baz", "dir1/foo3");
|
||||
at.symlink_file("dir3/bar", "dir1/foo4");
|
||||
|
||||
let expect1 = format!("dir2{}bar", MAIN_SEPARATOR);
|
||||
let expect2 = format!("dir2{}baz", MAIN_SEPARATOR);
|
||||
|
||||
ucmd.args(&["dir1/foo1", "dir1/foo2", "dir1/foo3", "dir1/foo4"])
|
||||
.run()
|
||||
.stdout_contains(at.plus_as_string("dir2/bar") + "\n")
|
||||
.stdout_contains(at.plus_as_string("dir2/baz") + "\n")
|
||||
.stdout_contains(expect1 + "\n")
|
||||
.stdout_contains(expect2 + "\n")
|
||||
.stderr_contains("realpath: dir1/foo2: No such file or directory\n")
|
||||
.stderr_contains("realpath: dir1/foo4: No such file or directory\n");
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ use std::io::{Read, Result, Write};
|
|||
use std::os::unix::fs::{symlink as symlink_dir, symlink as symlink_file};
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::fs::{symlink_dir, symlink_file};
|
||||
#[cfg(windows)]
|
||||
use std::path::MAIN_SEPARATOR;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::rc::Rc;
|
||||
|
@ -702,11 +704,13 @@ impl AtPath {
|
|||
}
|
||||
|
||||
pub fn relative_symlink_file(&self, original: &str, link: &str) {
|
||||
#[cfg(windows)]
|
||||
let original = original.replace('/', &MAIN_SEPARATOR.to_string());
|
||||
log_info(
|
||||
"symlink",
|
||||
&format!("{},{}", original, &self.plus_as_string(link)),
|
||||
&format!("{},{}", &original, &self.plus_as_string(link)),
|
||||
);
|
||||
symlink_file(original, &self.plus(link)).unwrap();
|
||||
symlink_file(&original, &self.plus(link)).unwrap();
|
||||
}
|
||||
|
||||
pub fn symlink_dir(&self, original: &str, link: &str) {
|
||||
|
@ -722,11 +726,13 @@ impl AtPath {
|
|||
}
|
||||
|
||||
pub fn relative_symlink_dir(&self, original: &str, link: &str) {
|
||||
#[cfg(windows)]
|
||||
let original = original.replace('/', &MAIN_SEPARATOR.to_string());
|
||||
log_info(
|
||||
"symlink",
|
||||
&format!("{},{}", original, &self.plus_as_string(link)),
|
||||
&format!("{},{}", &original, &self.plus_as_string(link)),
|
||||
);
|
||||
symlink_dir(original, &self.plus(link)).unwrap();
|
||||
symlink_dir(&original, &self.plus(link)).unwrap();
|
||||
}
|
||||
|
||||
pub fn is_symlink(&self, path: &str) -> bool {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue