@@ -89,8 +89,8 @@ fn skip_last<T>(mut iter: impl Iterator<Item = T>) -> impl Iterator<Item = T> {
8989
9090/// Paths that are invariant throughout the traversal when copying a directory.
9191struct Context < ' a > {
92- /// The current working directory at the time of starting the traversal.
93- current_dir : PathBuf ,
92+ /// The current working directory at the time of starting the traversal (if root is relative) .
93+ current_dir : Option < PathBuf > ,
9494
9595 /// The path to the parent of the source directory, if any.
9696 root_parent : Option < PathBuf > ,
@@ -107,20 +107,21 @@ struct Context<'a> {
107107
108108impl < ' a > Context < ' a > {
109109 fn new ( root : & ' a Path , target : & ' a Path ) -> io:: Result < Self > {
110- let current_dir = env:: current_dir ( ) ?;
111- let root_path = current_dir. join ( root) ;
112110 let target_is_file = target. is_file ( ) ;
113- let root_parent = if target. exists ( ) && !root. to_str ( ) . unwrap ( ) . ends_with ( "/." ) {
114- root_path. parent ( ) . map ( |p| p. to_path_buf ( ) )
115- } else if root == Path :: new ( "." ) && target. is_dir ( ) {
116- // Special case: when copying current directory (.) to an existing directory,
117- // we don't want to use the parent path as root_parent because we want to
118- // copy the contents of the current directory directly into the target directory,
119- // not create a subdirectory with the current directory's name.
120- None
111+
112+ // Only get the current directory if root is a relative path.
113+ // This avoids unnecessary getcwd() syscall failures when the current
114+ // directory has been deleted (issue #9105), and matches GNU cp behavior.
115+ let ( current_dir, root_path) = if root. is_absolute ( ) {
116+ ( None , root. to_path_buf ( ) )
121117 } else {
122- Some ( root_path)
118+ let cwd = env:: current_dir ( ) ?;
119+ let root_path = cwd. join ( root) ;
120+ ( Some ( cwd) , root_path)
123121 } ;
122+
123+ let root_parent = Self :: determine_root_parent ( root, target, & root_path) ;
124+
124125 Ok ( Self {
125126 current_dir,
126127 root_parent,
@@ -129,6 +130,26 @@ impl<'a> Context<'a> {
129130 root,
130131 } )
131132 }
133+
134+ /// Determine root_parent for path resolution during traversal.
135+ fn determine_root_parent ( root : & Path , target : & Path , root_path : & Path ) -> Option < PathBuf > {
136+ // Check if root path ends with "/." (e.g., "dir/.")
137+ let root_ends_with_dot = root. to_str ( ) . is_some_and ( |s| s. ends_with ( "/." ) ) ;
138+
139+ if target. exists ( ) && !root_ends_with_dot {
140+ // Normal case: use parent of resolved root path
141+ root_path. parent ( ) . map ( |p| p. to_path_buf ( ) )
142+ } else if root == Path :: new ( "." ) && target. is_dir ( ) {
143+ // Special case: when copying current directory (.) to an existing directory,
144+ // we don't want to use the parent path as root_parent because we want to
145+ // copy the contents of the current directory directly into the target directory,
146+ // not create a subdirectory with the current directory's name.
147+ None
148+ } else {
149+ // Use the root path itself when target doesn't exist yet
150+ Some ( root_path. to_path_buf ( ) )
151+ }
152+ }
132153}
133154
134155/// Data needed to perform a single copy operation while traversing a directory.
@@ -188,7 +209,11 @@ impl Entry {
188209 ) -> Result < Self , StripPrefixError > {
189210 let source = source. as_ref ( ) ;
190211 let source_relative = source. to_path_buf ( ) ;
191- let source_absolute = context. current_dir . join ( & source_relative) ;
212+ let source_absolute = if let Some ( ref current_dir) = context. current_dir {
213+ current_dir. join ( & source_relative)
214+ } else {
215+ source_relative. clone ( )
216+ } ;
192217 let mut descendant =
193218 get_local_to_root_parent ( & source_absolute, context. root_parent . as_deref ( ) ) ?;
194219 if no_target_dir {
@@ -217,9 +242,11 @@ impl Entry {
217242 // an extra level of nesting. For example, if we're in /home/user/source_dir
218243 // and copying . to /home/user/dest_dir, we want to copy source_dir/file.txt
219244 // to dest_dir/file.txt, not dest_dir/source_dir/file.txt.
220- if let Some ( current_dir_name) = context. current_dir . file_name ( ) {
221- if let Ok ( stripped) = descendant. strip_prefix ( current_dir_name) {
222- descendant = stripped. to_path_buf ( ) ;
245+ if let Some ( current_dir) = & context. current_dir {
246+ if let Some ( current_dir_name) = current_dir. file_name ( ) {
247+ if let Ok ( stripped) = descendant. strip_prefix ( current_dir_name) {
248+ descendant = stripped. to_path_buf ( ) ;
249+ }
223250 }
224251 }
225252 }
0 commit comments