Skip to content

Commit

Permalink
Add -L / --dereference option (#36)
Browse files Browse the repository at this point in the history
Co-authored-by: Noam Yorav-Raphael <[email protected]>
Co-authored-by: Alex Saveau <[email protected]>
  • Loading branch information
3 people authored Jun 30, 2024
1 parent ef296f1 commit 3e6e5f7
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 32 deletions.
3 changes: 3 additions & 0 deletions cpz/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ Options:
-t, --reverse-args
Reverse the argument order so that it becomes `cpz <TO> <FROM>...`

-L, --dereference
Follow symlinks in the files to be copied rather than copying the symlinks themselves

-h, --help
Print help (use `-h` for a summary)

Expand Down
2 changes: 2 additions & 0 deletions cpz/command-reference-short.golden
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ Arguments:
Options:
-f, --force Overwrite existing files
-t, --reverse-args Reverse the argument order so that it becomes `cpz <TO> <FROM>...`
-L, --dereference Follow symlinks in the files to be copied rather than copying the symlinks
themselves
-h, --help Print help (use `--help` for more detail)
-V, --version Print version
3 changes: 3 additions & 0 deletions cpz/command-reference.golden
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ Options:
-t, --reverse-args
Reverse the argument order so that it becomes `cpz <TO> <FROM>...`

-L, --dereference
Follow symlinks in the files to be copied rather than copying the symlinks themselves

-h, --help
Print help (use `-h` for a summary)

Expand Down
9 changes: 9 additions & 0 deletions cpz/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ struct Cpz {
#[arg(short = 't', long, default_value_t = false)]
reverse_args: bool,

/// Follow symlinks in the files to be copied rather than copying the
/// symlinks themselves
#[arg(short = 'L', long, default_value_t = false)]
#[arg(aliases = ["follow-symlinks"])]
dereference: bool,

#[arg(short, long, short_alias = '?', global = true)]
#[arg(action = ArgAction::Help, help = "Print help (use `--help` for more detail)")]
#[arg(long_help = "Print help (use `-h` for a summary)")]
Expand Down Expand Up @@ -203,6 +209,7 @@ fn copy(
mut to,
force,
reverse_args,
dereference,
help: _,
}: Cpz,
) -> Result<(), Error> {
Expand Down Expand Up @@ -243,6 +250,7 @@ fn copy(
(path, to)
}))
.force(force)
.follow_symlinks(dereference)
.build()
.run()
} else {
Expand All @@ -261,6 +269,7 @@ fn copy(
(from, to)
}])
.force(force)
.follow_symlinks(dereference)
.build()
.run()
}
Expand Down
2 changes: 1 addition & 1 deletion fuc_engine/api.golden
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub struct fuc_engine::CopyOp<'a, 'b, I1: core::convert::Into<alloc::borrow::Cow
impl<'a, 'b, I1: core::convert::Into<alloc::borrow::Cow<'a, std::path::Path>> + 'a, I2: core::convert::Into<alloc::borrow::Cow<'b, std::path::Path>> + 'b, F: core::iter::traits::collect::IntoIterator<Item = (I1, I2)>> fuc_engine::CopyOp<'a, 'b, I1, I2, F>
pub fn fuc_engine::CopyOp<'a, 'b, I1, I2, F>::run(self) -> core::result::Result<(), fuc_engine::Error>
impl<'a, 'b, I1: core::convert::Into<alloc::borrow::Cow<'a, std::path::Path>> + 'a, I2: core::convert::Into<alloc::borrow::Cow<'b, std::path::Path>> + 'b, F: core::iter::traits::collect::IntoIterator<Item = (I1, I2)>> fuc_engine::CopyOp<'a, 'b, I1, I2, F>
pub fn fuc_engine::CopyOp<'a, 'b, I1, I2, F>::builder() -> CopyOpBuilder<'a, 'b, I1, I2, F, ((), (), (), ())>
pub fn fuc_engine::CopyOp<'a, 'b, I1, I2, F>::builder() -> CopyOpBuilder<'a, 'b, I1, I2, F, ((), (), (), (), ())>
impl<'a, 'b, I1: core::fmt::Debug + core::convert::Into<alloc::borrow::Cow<'a, std::path::Path>> + 'a, I2: core::fmt::Debug + core::convert::Into<alloc::borrow::Cow<'b, std::path::Path>> + 'b, F: core::fmt::Debug + core::iter::traits::collect::IntoIterator<Item = (I1, I2)>> core::fmt::Debug for fuc_engine::CopyOp<'a, 'b, I1, I2, F>
pub fn fuc_engine::CopyOp<'a, 'b, I1, I2, F>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
impl<'a, 'b, I1, I2, F> core::marker::Freeze for fuc_engine::CopyOp<'a, 'b, I1, I2, F> where F: core::marker::Freeze
Expand Down
97 changes: 68 additions & 29 deletions fuc_engine/src/ops/copy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub struct CopyOp<
files: F,
#[builder(default = false)]
force: bool,
#[builder(default = false)]
follow_symlinks: bool,
#[builder(default)]
_marker1: PhantomData<&'a I1>,
#[builder(default)]
Expand All @@ -50,7 +52,7 @@ impl<
///
/// Returns the underlying I/O errors that occurred.
pub fn run(self) -> Result<(), Error> {
let copy = compat::copy_impl();
let copy = compat::copy_impl(self.follow_symlinks);
let result = schedule_copies(self, &copy);
copy.finish().and(result)
}
Expand All @@ -70,6 +72,7 @@ fn schedule_copies<
CopyOp {
files,
force,
follow_symlinks,
_marker1: _,
_marker2: _,
}: CopyOp<'a, 'b, I1, I2, F>,
Expand All @@ -94,9 +97,12 @@ fn schedule_copies<
}
}

let from_metadata = from
.symlink_metadata()
.map_io_err(|| format!("Failed to read metadata for file: {from:?}"))?;
let from_metadata = if follow_symlinks {
from.metadata()
} else {
from.symlink_metadata()
}
.map_io_err(|| format!("Failed to read metadata for file: {from:?}"))?;

if let Some(parent) = to.parent() {
fs::create_dir_all(parent)
Expand All @@ -106,14 +112,7 @@ fn schedule_copies<
#[cfg(unix)]
if from_metadata.is_dir() {
use std::os::unix::fs::{DirBuilderExt, MetadataExt};
match fs::DirBuilder::new()
.mode(
fs::symlink_metadata(&from)
.map_io_err(|| format!("Failed to stat directory: {from:?}"))?
.mode(),
)
.create(&to)
{
match fs::DirBuilder::new().mode(from_metadata.mode()).create(&to) {
Err(e) if force && e.kind() == io::ErrorKind::AlreadyExists => {}
r => r.map_io_err(|| format!("Failed to create directory: {to:?}"))?,
};
Expand Down Expand Up @@ -178,10 +177,15 @@ mod compat {
scheduling: LazyCell<(Sender<TreeNode>, JoinHandle<Result<(), Error>>), LF>,
}

pub fn copy_impl<'a, 'b>() -> impl DirectoryOp<(Cow<'a, Path>, Cow<'b, Path>)> {
let scheduling = LazyCell::new(|| {
pub fn copy_impl<'a, 'b>(
follow_symlinks: bool,
) -> impl DirectoryOp<(Cow<'a, Path>, Cow<'b, Path>)> {
let scheduling = LazyCell::new(move || {
let (tx, rx) = crossbeam_channel::unbounded();
(tx, thread::spawn(|| root_worker_thread(rx)))
(
tx,
thread::spawn(move || root_worker_thread(rx, follow_symlinks)),
)
});

Impl { scheduling }
Expand Down Expand Up @@ -222,7 +226,7 @@ mod compat {
}

#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(tasks)))]
fn root_worker_thread(tasks: Receiver<TreeNode>) -> Result<(), Error> {
fn root_worker_thread(tasks: Receiver<TreeNode>, follow_symlinks: bool) -> Result<(), Error> {
unshare_files()?;

let mut available_parallelism = thread::available_parallelism()
Expand Down Expand Up @@ -266,7 +270,7 @@ mod compat {
available_parallelism -= 1;
threads.push(scope.spawn({
let tasks = tasks.clone();
move || worker_thread(tasks, root_to_inode)
move || worker_thread(tasks, root_to_inode, follow_symlinks)
}));
}
};
Expand All @@ -275,6 +279,7 @@ mod compat {
copy_dir(
node,
root_to_inode,
follow_symlinks,
&mut buf,
&symlink_buf_cache,
maybe_spawn,
Expand All @@ -290,13 +295,24 @@ mod compat {
}

#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(tasks)))]
fn worker_thread(tasks: Receiver<TreeNode>, root_to_inode: u64) -> Result<(), Error> {
fn worker_thread(
tasks: Receiver<TreeNode>,
root_to_inode: u64,
follow_symlinks: bool,
) -> Result<(), Error> {
unshare_files()?;

let mut buf = [MaybeUninit::<u8>::uninit(); 8192];
let symlink_buf_cache = Cell::new(Vec::new());
for node in tasks {
copy_dir(node, root_to_inode, &mut buf, &symlink_buf_cache, || {})?;
copy_dir(
node,
root_to_inode,
follow_symlinks,
&mut buf,
&symlink_buf_cache,
|| {},
)?;
}
Ok(())
}
Expand All @@ -308,14 +324,21 @@ mod compat {
fn copy_dir(
TreeNode { from, to, messages }: TreeNode,
root_to_inode: u64,
follow_symlinks: bool,
buf: &mut [MaybeUninit<u8>],
symlink_buf_cache: &Cell<Vec<u8>>,
mut maybe_spawn: impl FnMut(),
) -> Result<(), Error> {
let from_dir = openat(
CWD,
&from,
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::NOFOLLOW,
OFlags::RDONLY
| OFlags::DIRECTORY
| if follow_symlinks {
OFlags::empty()
} else {
OFlags::NOFOLLOW
},
Mode::empty(),
)
.map_io_err(|| format!("Failed to open directory: {from:?}"))?;
Expand All @@ -341,10 +364,12 @@ mod compat {
}
}

let file_type = match file.file_type() {
FileType::Unknown => get_file_type(&from_dir, file.file_name(), &from)?,
t => t,
};
let mut file_type = file.file_type();
if file_type == FileType::Unknown || (follow_symlinks && file_type == FileType::Symlink)
{
file_type = get_file_type(&from_dir, file.file_name(), &from, follow_symlinks)?;
}
let file_type = file_type;
if file_type == FileType::Directory {
let from = concat_cstrs(&from, file.file_name());
let to = concat_cstrs(&to, file.file_name());
Expand Down Expand Up @@ -586,10 +611,15 @@ mod compat {
Error,
};

struct Impl;
struct Impl {
#[allow(dead_code)]
follow_symlinks: bool,
}

pub fn copy_impl<'a, 'b>() -> impl DirectoryOp<(Cow<'a, Path>, Cow<'b, Path>)> {
Impl
pub fn copy_impl<'a, 'b>(
follow_symlinks: bool,
) -> impl DirectoryOp<(Cow<'a, Path>, Cow<'b, Path>)> {
Impl { follow_symlinks }
}

impl DirectoryOp<(Cow<'_, Path>, Cow<'_, Path>)> for Impl {
Expand All @@ -598,6 +628,8 @@ mod compat {
&from,
to,
#[cfg(unix)]
self.follow_symlinks,
#[cfg(unix)]
None,
)
.map_io_err(|| format!("Failed to copy directory: {from:?}"))
Expand All @@ -611,6 +643,7 @@ mod compat {
fn copy_dir<P: AsRef<Path>, Q: AsRef<Path>>(
from: P,
to: Q,
#[cfg(unix)] follow_symlinks: bool,
#[cfg(unix)] root_to_inode: Option<u64>,
) -> Result<(), io::Error> {
let to = to.as_ref();
Expand All @@ -636,11 +669,17 @@ mod compat {
}

let to = to.join(dir_entry.file_name());
let file_type = dir_entry.file_type()?;
#[allow(unused_mut)]
let mut file_type = dir_entry.file_type()?;
#[cfg(unix)]
if follow_symlinks && file_type.is_symlink() {
file_type = fs::metadata(dir_entry.path())?.file_type();
}
let file_type = file_type;

#[cfg(unix)]
if file_type.is_dir() {
copy_dir(dir_entry.path(), to, root_to_inode)?;
copy_dir(dir_entry.path(), to, follow_symlinks, root_to_inode)?;
} else if file_type.is_symlink() {
std::os::unix::fs::symlink(fs::read_link(dir_entry.path())?, to)?;
} else {
Expand Down
8 changes: 7 additions & 1 deletion fuc_engine/src/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,14 @@ mod linux {
dir: impl AsFd,
file_name: &CStr,
path: &CString,
follow_symlinks: bool,
) -> Result<FileType, Error> {
statx(dir, file_name, AtFlags::SYMLINK_NOFOLLOW, StatxFlags::TYPE)
let flags = if follow_symlinks {
AtFlags::empty()
} else {
AtFlags::SYMLINK_NOFOLLOW
};
statx(dir, file_name, flags, StatxFlags::TYPE)
.map_io_err(|| {
format!(
"Failed to stat file: {:?}",
Expand Down
4 changes: 3 additions & 1 deletion fuc_engine/src/ops/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,9 @@ mod compat {
}

let file_type = match file.file_type() {
FileType::Unknown => get_file_type(&dir, file.file_name(), &node.as_ref().path)?,
FileType::Unknown => {
get_file_type(&dir, file.file_name(), &node.as_ref().path, false)?
}
t => t,
};
if file_type == FileType::Directory {
Expand Down
Loading

0 comments on commit 3e6e5f7

Please sign in to comment.