use super::paths::{resolve, tree}; use super::*; fn overlaps(directory: &Dir, path: &Path) -> io::Result { let root = same_file::Handle::from_file(directory.try_clone()?.into_std_file())?; for ancestor in path.ancestors() { let candidate = parent(ancestor).and_then(|(parent, name)| parent.open_dir_nofollow(name)); match candidate { Ok(candidate) => { let candidate = same_file::Handle::from_file(candidate.into_std_file())?; if root == candidate { return Ok(true); } } Err(error) if error.kind() == io::ErrorKind::NotFound => (), Err(error) => return Err(error), } } Ok(false) } pub(super) fn ops(files: Arc) -> Vec { let ops = files.clone(); let ensure = files.clone(); let prepare = files.clone(); let read_dir = files.clone(); let dir_mode = files.clone(); vec![ ops.io_ops( "op_file_read_tree_dir", "op_file_read_tree_dir_sync", move |path: String| { let caps = read_dir.caps.clone(); let pending = read_dir .pool .run(move || super::paths::entries(&caps, &path, true)); async move { Ok::<_, Infallible>(receive(pending).await) } }, ), ops.io_ops( "op_file_copy_dir_mode", "op_file_copy_dir_mode_sync", move |(path, mode): (String, Option)| { let caps = dir_mode.caps.clone(); let pending = dir_mode.pool.run(move || { let target = resolve(&caps, &path, false, true)?; tree(&caps, &target, &path, true)?; let (directory, name) = parent(target.as_path())?; #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; if let Some(mode) = mode { if mode > 0o777 { return Err(io::Error::new( io::ErrorKind::InvalidInput, "Invalid copied directory mode", )); } use cap_std::fs::OpenOptionsExt; let mut flags = OpenOptions::new(); flags .read(true) .follow(FollowSymlinks::No) .nonblock(true) .custom_flags(libc::O_DIRECTORY); let file = directory.open_with(name, &flags)?; if !file.metadata()?.is_dir() { return Err(io::Error::new( io::ErrorKind::NotADirectory, "Copy destination is not a directory", )); } file.into_std() .set_permissions(std::fs::Permissions::from_mode(mode))?; } } #[cfg(not(unix))] let _ = (directory.open_dir_nofollow(name)?, mode); Ok(true) }); async move { Ok::<_, Infallible>(receive(pending).await) } }, ), vec![Op::new("op_file_tmp_dir", |(): ()| { let dir = std::env::temp_dir() .into_os_string() .into_string() .map_err(|_| { io::Error::new( io::ErrorKind::Unsupported, "not yet implemented: non-UTF-8 temp directory", ) }); Ok::<_, Infallible>(response(dir)) })], vec![Op::new("op_file_path_parent", move |path: String| { let parsed = filesystem_path(&files.caps, &path).and_then(|path| { path.parent() .map(|parent| { parent.to_str().map(str::to_owned).ok_or_else(|| { io::Error::new( io::ErrorKind::Unsupported, "not yet implemented: non-UTF-8 paths", ) }) }) .transpose()? .ok_or_else(|| { io::Error::new(io::ErrorKind::InvalidInput, "Filesystem root has no parent") }) }); Ok::<_, Infallible>(response(parsed)) })], vec![Op::new( "op_file_path_join", |(path, name): (String, String)| { let parsed = (|| { let mut components = Path::new(&name).components(); if !matches!(components.next(), Some(Component::Normal(_))) || components.next().is_some() { return Err(io::Error::new( io::ErrorKind::InvalidInput, "Expected a single entry name", )); } Path::new(&path) .join(name) .into_os_string() .into_string() .map_err(|_| { io::Error::new( io::ErrorKind::Unsupported, "not yet implemented: non-UTF-8 paths", ) }) })(); Ok::<_, Infallible>(response(parsed)) }, )], ops.io_ops( "op_file_ensure_dir", "op_file_ensure_dir_sync", move |(path, mode): (String, Option)| { let caps = ensure.caps.clone(); let pending = ensure.pool.run(move || { if mode.is_some_and(|mode| mode > 0o7777) { return Err(io::Error::new( io::ErrorKind::InvalidInput, "Invalid directory mode", )); } let target = resolve(&caps, &path, false, true)?; let (directory, name) = parent(target.as_path())?; let mut builder = cap_std::fs::DirBuilder::new(); #[cfg(unix)] { use cap_std::fs::DirBuilderExt; if let Some(mode) = mode { builder.mode(mode); } } match directory.create_dir_with(&name, &builder) { Ok(()) => (), Err(error) if error.kind() == io::ErrorKind::AlreadyExists => { directory.open_dir_nofollow(name)?; } Err(error) => return Err(error), } Ok(true) }); async move { Ok::<_, Infallible>(receive(pending).await) } }, ), ops.io_ops( "op_file_prepare_copy", "op_file_prepare_copy_sync", move |(source, destination, moving, dereference): (String, String, bool, bool)| { let caps = prepare.caps.clone(); let pending = prepare.pool.run(move || { let from = resolve(&caps, &source, !dereference, false)?; let (from_parent, name) = parent(from.as_path())?; let mut metadata = from_parent.symlink_metadata(&name)?; if dereference && metadata.is_symlink() { return Err(io::Error::new( io::ErrorKind::NotFound, "Symbolic link changed or has a missing target", )); } let to = resolve(&caps, &destination, metadata.is_symlink(), true)?; if !(metadata.is_symlink() || metadata.is_file() || metadata.is_dir()) { return Err(io::Error::new( io::ErrorKind::Unsupported, "not yet implemented: copying non-regular files", )); } if moving || metadata.is_dir() { tree(&caps, &from, &source, false)?; tree(&caps, &to, &destination, true)?; } if moving { tree(&caps, &from, &source, true)?; } let mut identity = None; if metadata.is_dir() { let source_dir = from_parent.open_dir_nofollow(name)?; metadata = source_dir.dir_metadata()?; { use cap_fs_ext::MetadataExt; identity = Some(format!("{:x}:{:x}", metadata.dev(), metadata.ino())); } if overlaps(&source_dir, to.as_path())? { return Err(io::Error::new( io::ErrorKind::InvalidInput, "Source and destination trees overlap", )); } let destination_dir = parent(to.as_path()) .and_then(|(parent, name)| parent.open_dir_nofollow(name)); match destination_dir { Ok(destination_dir) if overlaps(&destination_dir, from.as_path())? => { return Err(io::Error::new( io::ErrorKind::InvalidInput, "Source and destination trees overlap", )); } Ok(_) => (), Err(error) if error.kind() == io::ErrorKind::NotFound => (), Err(error) => return Err(error), } } #[cfg(unix)] let mode = { use cap_std::fs::MetadataExt; Some(metadata.mode() & 0o777) }; #[cfg(not(unix))] let mode: Option = None; let kind: u8 = if metadata.is_symlink() { 2 } else if metadata.is_dir() { 1 } else { 0 }; Ok((kind, mode, identity, super::links::text(from.into_path())?)) }); async move { Ok::<_, Infallible>(receive(pending).await) } }, ), ] .into_iter() .flatten() .collect() }