@@ -10,25 +10,15 @@ use std::collections::HashSet;
10
10
use std:: env;
11
11
use std:: fs;
12
12
use std:: io;
13
- use std:: path:: Path ;
13
+ use std:: path:: { Path , PathBuf , StripPrefixError } ;
14
14
15
15
use uucore:: display:: Quotable ;
16
16
use uucore:: fs:: { canonicalize, FileInformation , MissingHandling , ResolveMode } ;
17
- use walkdir:: WalkDir ;
17
+ use walkdir:: { DirEntry , WalkDir } ;
18
18
19
19
use crate :: { copy_file, copy_link, preserve_hardlinks, CopyResult , Options , TargetSlice } ;
20
20
21
- /// Continue next iteration of loop if result of expression is error
22
- macro_rules! or_continue(
23
- ( $expr: expr) => ( match $expr {
24
- Ok ( temp) => temp,
25
- Err ( error) => {
26
- show_error!( "{}" , error) ;
27
- continue
28
- } ,
29
- } )
30
- ) ;
31
-
21
+ /// Ensure a Windows path starts with a `\\?`.
32
22
#[ cfg( target_os = "windows" ) ]
33
23
fn adjust_canonicalization ( p : & Path ) -> Cow < Path > {
34
24
// In some cases, \\? can be missing on some Windows paths. Add it at the
@@ -50,6 +40,181 @@ fn adjust_canonicalization(p: &Path) -> Cow<Path> {
50
40
}
51
41
}
52
42
43
+ /// Get a descendant path relative to the given parent directory.
44
+ ///
45
+ /// If `root_parent` is `None`, then this just returns the `path`
46
+ /// itself. Otherwise, this function strips the parent prefix from the
47
+ /// given `path`, leaving only the portion of the path relative to the
48
+ /// parent.
49
+ fn get_local_to_root_parent (
50
+ path : & Path ,
51
+ root_parent : Option < & Path > ,
52
+ ) -> Result < PathBuf , StripPrefixError > {
53
+ match root_parent {
54
+ Some ( parent) => {
55
+ // On Windows, some paths are starting with \\?
56
+ // but not always, so, make sure that we are consistent for strip_prefix
57
+ // See https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file for more info
58
+ #[ cfg( windows) ]
59
+ let ( path, parent) = (
60
+ adjust_canonicalization ( path) ,
61
+ adjust_canonicalization ( parent) ,
62
+ ) ;
63
+ let path = path. strip_prefix ( & parent) ?;
64
+ Ok ( path. to_path_buf ( ) )
65
+ }
66
+ None => Ok ( path. to_path_buf ( ) ) ,
67
+ }
68
+ }
69
+
70
+ /// Paths that are invariant throughout the traversal when copying a directory.
71
+ struct Context < ' a > {
72
+ /// The current working directory at the time of starting the traversal.
73
+ current_dir : PathBuf ,
74
+
75
+ /// The path to the parent of the source directory, if any.
76
+ root_parent : Option < PathBuf > ,
77
+
78
+ /// The target path to which the directory will be copied.
79
+ target : & ' a Path ,
80
+ }
81
+
82
+ impl < ' a > Context < ' a > {
83
+ fn new ( root : & ' a Path , target : & ' a Path ) -> std:: io:: Result < Self > {
84
+ let current_dir = env:: current_dir ( ) ?;
85
+ let root_path = current_dir. join ( root) ;
86
+ let root_parent = if target. exists ( ) {
87
+ root_path. parent ( ) . map ( |p| p. to_path_buf ( ) )
88
+ } else {
89
+ Some ( root_path)
90
+ } ;
91
+ Ok ( Self {
92
+ current_dir,
93
+ root_parent,
94
+ target,
95
+ } )
96
+ }
97
+ }
98
+
99
+ /// Data needed to perform a single copy operation while traversing a directory.
100
+ ///
101
+ /// For convenience while traversing a directory, the [`Entry::new`]
102
+ /// function allows creating an entry from a [`Context`] and a
103
+ /// [`walkdir::DirEntry`].
104
+ ///
105
+ /// # Examples
106
+ ///
107
+ /// For example, if the source directory structure is `a/b/c`, the
108
+ /// target is `d/`, a directory that already exists, and the copy
109
+ /// command is `cp -r a/b/c d`, then the overall set of copy
110
+ /// operations could be represented as three entries,
111
+ ///
112
+ /// ```rust,ignore
113
+ /// let operations = [
114
+ /// Entry {
115
+ /// source: "a".into(),
116
+ /// local_to_target: "d/a".into(),
117
+ /// target_is_file: false,
118
+ /// }
119
+ /// Entry {
120
+ /// source: "a/b".into(),
121
+ /// local_to_target: "d/a/b".into(),
122
+ /// target_is_file: false,
123
+ /// }
124
+ /// Entry {
125
+ /// source: "a/b/c".into(),
126
+ /// local_to_target: "d/a/b/c".into(),
127
+ /// target_is_file: false,
128
+ /// }
129
+ /// ];
130
+ /// ```
131
+ struct Entry {
132
+ /// The path to file or directory to copy.
133
+ source : PathBuf ,
134
+
135
+ /// The path to the destination, relative to the target.
136
+ local_to_target : PathBuf ,
137
+
138
+ /// Whether the destination is a file.
139
+ target_is_file : bool ,
140
+ }
141
+
142
+ impl Entry {
143
+ fn new ( context : & Context , direntry : & DirEntry ) -> Result < Self , StripPrefixError > {
144
+ let source = context. current_dir . join ( direntry. path ( ) ) ;
145
+ let descendant = get_local_to_root_parent ( & source, context. root_parent . as_deref ( ) ) ?;
146
+ let local_to_target = context. target . join ( descendant) ;
147
+ let target_is_file = context. target . is_file ( ) ;
148
+ Ok ( Self {
149
+ source,
150
+ local_to_target,
151
+ target_is_file,
152
+ } )
153
+ }
154
+ }
155
+
156
+ /// Copy a single entry during a directory traversal.
157
+ fn copy_direntry (
158
+ entry : Entry ,
159
+ options : & Options ,
160
+ symlinked_files : & mut HashSet < FileInformation > ,
161
+ preserve_hard_links : bool ,
162
+ hard_links : & mut Vec < ( String , u64 ) > ,
163
+ ) -> CopyResult < ( ) > {
164
+ let Entry {
165
+ source,
166
+ local_to_target,
167
+ target_is_file,
168
+ } = entry;
169
+
170
+ // If the source is a symbolic link and the options tell us not to
171
+ // dereference the link, then copy the link object itself.
172
+ if source. is_symlink ( ) && !options. dereference {
173
+ return copy_link ( & source, & local_to_target, symlinked_files) ;
174
+ }
175
+
176
+ // If the source is a directory and the destination does not
177
+ // exist, ...
178
+ if source. is_dir ( ) && !local_to_target. exists ( ) {
179
+ if target_is_file {
180
+ return Err ( "cannot overwrite non-directory with directory" . into ( ) ) ;
181
+ } else {
182
+ // TODO Since the calling code is traversing from the root
183
+ // of the directory structure, I don't think
184
+ // `create_dir_all()` will have any benefit over
185
+ // `create_dir()`, since all the ancestor directories
186
+ // should have already been created.
187
+ fs:: create_dir_all ( local_to_target) ?;
188
+ return Ok ( ( ) ) ;
189
+ }
190
+ }
191
+
192
+ // If the source is not a directory, then we need to copy the file.
193
+ if !source. is_dir ( ) {
194
+ if preserve_hard_links {
195
+ let mut found_hard_link = false ;
196
+ preserve_hardlinks ( hard_links, & source, & local_to_target, & mut found_hard_link) ?;
197
+ if found_hard_link {
198
+ return Ok ( ( ) ) ;
199
+ }
200
+ match copy_file ( & source, & local_to_target, options, symlinked_files, false ) {
201
+ Ok ( _) => return Ok ( ( ) ) ,
202
+ // silent the error with a symlink
203
+ // In case we do --archive, we might copy the symlink
204
+ // before the file itself
205
+ Err ( _) if source. is_symlink ( ) => return Ok ( ( ) ) ,
206
+ Err ( err) => return Err ( err) ,
207
+ }
208
+ } else {
209
+ return copy_file ( & source, & local_to_target, options, symlinked_files, false ) ;
210
+ }
211
+ }
212
+
213
+ // In any other case, there is nothing to do, so we just return to
214
+ // continue the traversal.
215
+ Ok ( ( ) )
216
+ }
217
+
53
218
/// Read the contents of the directory `root` and recursively copy the
54
219
/// contents to `target`.
55
220
///
@@ -87,96 +252,35 @@ pub(crate) fn copy_directory(
87
252
. into ( ) ) ;
88
253
}
89
254
90
- let current_dir =
91
- env:: current_dir ( ) . unwrap_or_else ( |e| crash ! ( 1 , "failed to get current directory {}" , e) ) ;
92
-
93
- let root_path = current_dir. join ( root) ;
94
-
95
- let root_parent = if target. exists ( ) {
96
- root_path. parent ( )
97
- } else {
98
- Some ( root_path. as_path ( ) )
99
- } ;
100
-
101
- #[ cfg( unix) ]
102
255
let mut hard_links: Vec < ( String , u64 ) > = vec ! [ ] ;
103
256
let preserve_hard_links = options. preserve_hard_links ( ) ;
104
257
105
- // This should be changed once Redox supports hardlinks
106
- #[ cfg( any( windows, target_os = "redox" ) ) ]
107
- let mut hard_links: Vec < ( String , u64 ) > = vec ! [ ] ;
258
+ // Collect some paths here that are invariant during the traversal
259
+ // of the given directory, like the current working directory and
260
+ // the target directory.
261
+ let context = match Context :: new ( root, target) {
262
+ Ok ( c) => c,
263
+ Err ( e) => return Err ( format ! ( "failed to get current directory {}" , e) . into ( ) ) ,
264
+ } ;
108
265
109
- for path in WalkDir :: new ( root)
266
+ // Traverse the contents of the directory, copying each one.
267
+ for direntry_result in WalkDir :: new ( root)
110
268
. same_file_system ( options. one_file_system )
111
269
. follow_links ( options. dereference )
112
270
{
113
- let p = or_continue ! ( path) ;
114
- let path = current_dir. join ( & p. path ( ) ) ;
115
-
116
- let local_to_root_parent = match root_parent {
117
- Some ( parent) => {
118
- #[ cfg( windows) ]
119
- {
120
- // On Windows, some paths are starting with \\?
121
- // but not always, so, make sure that we are consistent for strip_prefix
122
- // See https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file for more info
123
- let parent_can = adjust_canonicalization ( parent) ;
124
- let path_can = adjust_canonicalization ( & path) ;
125
-
126
- or_continue ! ( & path_can. strip_prefix( & parent_can) ) . to_path_buf ( )
127
- }
128
- #[ cfg( not( windows) ) ]
129
- {
130
- or_continue ! ( path. strip_prefix( & parent) ) . to_path_buf ( )
131
- }
132
- }
133
- None => path. clone ( ) ,
134
- } ;
135
-
136
- let local_to_target = target. join ( & local_to_root_parent) ;
137
- if p. path ( ) . is_symlink ( ) && !options. dereference {
138
- copy_link ( & path, & local_to_target, symlinked_files) ?;
139
- } else if path. is_dir ( ) && !local_to_target. exists ( ) {
140
- if target. is_file ( ) {
141
- return Err ( "cannot overwrite non-directory with directory" . into ( ) ) ;
142
- }
143
- fs:: create_dir_all ( local_to_target) ?;
144
- } else if !path. is_dir ( ) {
145
- if preserve_hard_links {
146
- let mut found_hard_link = false ;
147
- let source = path. to_path_buf ( ) ;
148
- let dest = local_to_target. as_path ( ) . to_path_buf ( ) ;
149
- preserve_hardlinks ( & mut hard_links, & source, & dest, & mut found_hard_link) ?;
150
- if !found_hard_link {
151
- match copy_file (
152
- path. as_path ( ) ,
153
- local_to_target. as_path ( ) ,
154
- options,
155
- symlinked_files,
156
- false ,
157
- ) {
158
- Ok ( _) => Ok ( ( ) ) ,
159
- Err ( err) => {
160
- if source. is_symlink ( ) {
161
- // silent the error with a symlink
162
- // In case we do --archive, we might copy the symlink
163
- // before the file itself
164
- Ok ( ( ) )
165
- } else {
166
- Err ( err)
167
- }
168
- }
169
- } ?;
170
- }
171
- } else {
172
- copy_file (
173
- path. as_path ( ) ,
174
- local_to_target. as_path ( ) ,
271
+ match direntry_result {
272
+ Ok ( direntry) => {
273
+ let entry = Entry :: new ( & context, & direntry) ?;
274
+ copy_direntry (
275
+ entry,
175
276
options,
176
277
symlinked_files,
177
- false ,
278
+ preserve_hard_links,
279
+ & mut hard_links,
178
280
) ?;
179
281
}
282
+ // Print an error message, but continue traversing the directory.
283
+ Err ( e) => show_error ! ( "{}" , e) ,
180
284
}
181
285
}
182
286
0 commit comments