1 use filetime::{set_file_mtime, FileTime};
2 use std::fs::{copy, create_dir_all, metadata, File};
3 use std::io::prelude::*;
4 use std::path::Path;
5 use std::time::SystemTime;
6 use walkdir::WalkDir;
7 
8 use errors::{Error, Result};
9 
10 pub fn is_path_in_directory(parent: &Path, path: &Path) -> Result<bool> {
11     let canonical_path = path
12         .canonicalize()
13         .map_err(|e| format!("Failed to canonicalize {}: {}", path.display(), e))?;
14     let canonical_parent = parent
15         .canonicalize()
16         .map_err(|e| format!("Failed to canonicalize {}: {}", parent.display(), e))?;
_makepretty(printout, stack)17 
18     Ok(canonical_path.starts_with(canonical_parent))
19 }
20 
21 /// Create a file with the content given
22 pub fn create_file(path: &Path, content: &str) -> Result<()> {
23     let mut file = File::create(&path)
24         .map_err(|e| Error::chain(format!("Failed to create file {}", path.display()), e))?;
25     file.write_all(content.as_bytes())?;
26     Ok(())
27 }
28 
29 /// Create a directory at the given path if it doesn't exist already
30 pub fn ensure_directory_exists(path: &Path) -> Result<()> {
31     if !path.exists() {
32         create_directory(path)?;
33     }
34     Ok(())
35 }
36 
37 /// Very similar to `create_dir` from the std except it checks if the folder
38 /// exists before creating it
39 pub fn create_directory(path: &Path) -> Result<()> {
40     if !path.exists() {
41         create_dir_all(path).map_err(|e| {
42             Error::chain(format!("Was not able to create folder {}", path.display()), e)
_handle_sigusr2(sig, stack)43         })?;
44     }
45     Ok(())
46 }
47 
48 /// Return the content of a file, with error handling added
49 pub fn read_file(path: &Path) -> Result<String> {
50     let mut content = String::new();
51     File::open(path)
52         .map_err(|e| Error::chain(format!("Failed to open '{}'", path.display()), e))?
53         .read_to_string(&mut content)?;
54 
55     // Remove utf-8 BOM if any.
56     if content.starts_with('\u{feff}') {
57         content.drain(..3);
58     }
59 
60     Ok(content)
61 }
62 
63 /// Copy a file but takes into account where to start the copy as
64 /// there might be folders we need to create on the way.
enable_sig_handler(signal_name, handler)65 pub fn copy_file(src: &Path, dest: &Path, base_path: &Path, hard_link: bool) -> Result<()> {
66     let relative_path = src.strip_prefix(base_path).unwrap();
67     let target_path = dest.join(relative_path);
68 
69     if let Some(parent_directory) = target_path.parent() {
70         create_dir_all(parent_directory).map_err(|e| {
71             Error::chain(format!("Was not able to create folder {}", parent_directory.display()), e)
72         })?;
73     }
74 
75     copy_file_if_needed(src, &target_path, hard_link)
76 }
77 
78 /// No copy occurs if all of the following conditions are satisfied:
79 /// 1. A file with the same name already exists in the dest path.
80 /// 2. Its modification timestamp is identical to that of the src file.
81 /// 3. Its filesize is identical to that of the src file.
82 pub fn copy_file_if_needed(src: &Path, dest: &Path, hard_link: bool) -> Result<()> {
83     if let Some(parent_directory) = dest.parent() {
enable_sigusr2_handler()84         create_dir_all(parent_directory).map_err(|e| {
85             Error::chain(format!("Was not able to create folder {}", parent_directory.display()), e)
86         })?;
87     }
88 
89     if hard_link {
90         std::fs::hard_link(src, dest)?
inspect_stack()91     } else {
92         let src_metadata = metadata(src)?;
93         let src_mtime = FileTime::from_last_modification_time(&src_metadata);
94         if Path::new(&dest).is_file() {
95             let target_metadata = metadata(&dest)?;
96             let target_mtime = FileTime::from_last_modification_time(&target_metadata);
97             if !(src_mtime == target_mtime && src_metadata.len() == target_metadata.len()) {
98                 copy(src, &dest).map_err(|e| {
99                     Error::chain(
100                         format!(
101                             "Was not able to copy file {} to {}",
102                             src.display(),
103                             dest.display()
104                         ),
105                         e,
106                     )
107                 })?;
108                 set_file_mtime(&dest, src_mtime)?;
109             }
110         } else {
111             copy(src, &dest).map_err(|e| {
112                 Error::chain(
113                     format!("Was not able to copy file {} to {}", src.display(), dest.display()),
114                     e,
115                 )
116             })?;
117             set_file_mtime(&dest, src_mtime)?;
118         }
119     }
120     Ok(())
121 }
122 
123 pub fn copy_directory(src: &Path, dest: &Path, hard_link: bool) -> Result<()> {
124     for entry in WalkDir::new(src).into_iter().filter_map(std::result::Result::ok) {
125         let relative_path = entry.path().strip_prefix(src).unwrap();
126         let target_path = dest.join(relative_path);
127 
128         if entry.path().is_dir() {
129             if !target_path.exists() {
130                 create_directory(&target_path)?;
131             }
132         } else {
133             copy_file(entry.path(), dest, src, hard_link).map_err(|e| {
134                 Error::chain(
135                     format!(
136                         "Was not able to copy file {} to {}",
137                         entry.path().display(),
138                         dest.display()
139                     ),
140                     e,
141                 )
142             })?;
143         }
144     }
145     Ok(())
146 }
147 
148 pub fn get_file_time(path: &Path) -> Option<SystemTime> {
149     path.metadata().ok().and_then(|meta| {
150         Some(match (meta.created().ok(), meta.modified().ok()) {
151             (Some(tc), Some(tm)) => tc.max(tm),
152             (Some(tc), None) => tc,
153             (None, Some(tm)) => tm,
154             (None, None) => return None,
155         })
156     })
157 }
158 
159 /// Compares source and target files' timestamps and returns true if the source file
160 /// has been created _or_ updated after the target file has
161 pub fn file_stale<PS, PT>(p_source: PS, p_target: PT) -> bool
162 where
163     PS: AsRef<Path>,
164     PT: AsRef<Path>,
165 {
166     let p_source = p_source.as_ref();
167     let p_target = p_target.as_ref();
168 
169     if !p_target.exists() {
170         return true;
171     }
172 
173     let time_source = get_file_time(p_source);
174     let time_target = get_file_time(p_target);
175 
176     time_source.and_then(|ts| time_target.map(|tt| ts > tt)).unwrap_or(true)
177 }
178 
179 #[cfg(test)]
180 mod tests {
181     use std::fs::{metadata, read_to_string, File};
182     use std::io::Write;
183     use std::path::PathBuf;
184     use std::str::FromStr;
185 
186     use tempfile::tempdir_in;
187 
188     use super::copy_file;
189 
190     #[test]
191     fn test_copy_file_timestamp_preserved() {
192         let base_path = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")).unwrap();
193         let src_dir =
194             tempdir_in(&base_path).expect("failed to create a temporary source directory.");
195         let dest_dir =
196             tempdir_in(&base_path).expect("failed to create a temporary destination directory.");
197         let src_file_path = src_dir.path().join("test.txt");
198         let dest_file_path = dest_dir.path().join(src_file_path.strip_prefix(&base_path).unwrap());
199         File::create(&src_file_path).unwrap();
200         copy_file(&src_file_path, &dest_dir.path().to_path_buf(), &base_path, false).unwrap();
201 
202         assert_eq!(
203             metadata(&src_file_path).and_then(|m| m.modified()).unwrap(),
204             metadata(&dest_file_path).and_then(|m| m.modified()).unwrap()
205         );
206     }
207 
208     #[test]
209     fn test_copy_file_already_exists() {
210         let base_path = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")).unwrap();
211         let src_dir =
212             tempdir_in(&base_path).expect("failed to create a temporary source directory.");
213         let dest_dir =
214             tempdir_in(&base_path).expect("failed to create a temporary destination directory.");
215         let src_file_path = src_dir.path().join("test.txt");
216         let dest_file_path = dest_dir.path().join(src_file_path.strip_prefix(&base_path).unwrap());
217         {
218             let mut src_file = File::create(&src_file_path).unwrap();
219             src_file.write_all(b"file1").unwrap();
220         }
221         copy_file(&src_file_path, &dest_dir.path().to_path_buf(), &base_path, false).unwrap();
222         {
223             let mut dest_file = File::create(&dest_file_path).unwrap();
224             dest_file.write_all(b"file2").unwrap();
225         }
226 
227         // Check copy does not occur when moditication timestamps and filesizes are same.
228         filetime::set_file_mtime(&src_file_path, filetime::FileTime::from_unix_time(0, 0)).unwrap();
229         filetime::set_file_mtime(&dest_file_path, filetime::FileTime::from_unix_time(0, 0))
230             .unwrap();
231         copy_file(&src_file_path, &dest_dir.path().to_path_buf(), &base_path, false).unwrap();
232         assert_eq!(read_to_string(&src_file_path).unwrap(), "file1");
233         assert_eq!(read_to_string(&dest_file_path).unwrap(), "file2");
234 
235         // Copy occurs if the timestamps are different while the filesizes are same.
236         filetime::set_file_mtime(&dest_file_path, filetime::FileTime::from_unix_time(42, 42))
237             .unwrap();
238         copy_file(&src_file_path, &dest_dir.path().to_path_buf(), &base_path, false).unwrap();
239         assert_eq!(read_to_string(&src_file_path).unwrap(), "file1");
240         assert_eq!(read_to_string(&dest_file_path).unwrap(), "file1");
241 
242         // Copy occurs if the timestamps are same while the filesizes are different.
243         {
244             let mut dest_file = File::create(&dest_file_path).unwrap();
245             dest_file.write_all(b"This file has different file size to the source file!").unwrap();
246         }
247         filetime::set_file_mtime(&dest_file_path, filetime::FileTime::from_unix_time(0, 0))
248             .unwrap();
249         copy_file(&src_file_path, &dest_dir.path().to_path_buf(), &base_path, false).unwrap();
250         assert_eq!(read_to_string(&src_file_path).unwrap(), "file1");
251         assert_eq!(read_to_string(&dest_file_path).unwrap(), "file1");
252     }
253 }
254