1use std::io;
2use std::path::Path;
3
4#[cfg(unix)]
5use std::os::unix::fs::MetadataExt;
6#[cfg(unix)]
7use std::os::unix::fs::PermissionsExt;
8
9#[cfg(target_os = "linux")]
16static FICLONE_UNSUPPORTED: std::sync::atomic::AtomicBool =
17 std::sync::atomic::AtomicBool::new(false);
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum DerefMode {
22 Never,
24 CommandLine,
26 Always,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum BackupMode {
33 Numbered,
35 Existing,
37 Simple,
39 None,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum ReflinkMode {
46 Auto,
48 Always,
50 Never,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum SparseMode {
57 Auto,
59 Always,
61 Never,
63}
64
65pub struct CpConfig {
67 pub recursive: bool,
68 pub force: bool,
69 pub interactive: bool,
70 pub no_clobber: bool,
71 pub verbose: bool,
72 pub preserve_mode: bool,
73 pub preserve_ownership: bool,
74 pub preserve_timestamps: bool,
75 pub dereference: DerefMode,
76 pub link: bool,
77 pub symbolic_link: bool,
78 pub update: bool,
79 pub one_file_system: bool,
80 pub backup: Option<BackupMode>,
81 pub suffix: String,
82 pub reflink: ReflinkMode,
83 pub target_directory: Option<String>,
84 pub no_target_directory: bool,
85 pub strip_trailing_slashes: bool,
86 pub attributes_only: bool,
87 pub parents: bool,
88 pub sparse: SparseMode,
89}
90
91impl Default for CpConfig {
92 fn default() -> Self {
93 Self {
94 recursive: false,
95 force: false,
96 interactive: false,
97 no_clobber: false,
98 verbose: false,
99 preserve_mode: false,
100 preserve_ownership: false,
101 preserve_timestamps: false,
102 dereference: DerefMode::CommandLine,
103 link: false,
104 symbolic_link: false,
105 update: false,
106 one_file_system: false,
107 backup: None,
108 suffix: "~".to_string(),
109 reflink: ReflinkMode::Auto,
110 target_directory: None,
111 no_target_directory: false,
112 strip_trailing_slashes: false,
113 attributes_only: false,
114 parents: false,
115 sparse: SparseMode::Auto,
116 }
117 }
118}
119
120pub fn parse_sparse_mode(s: &str) -> Result<SparseMode, String> {
122 match s {
123 "auto" => Ok(SparseMode::Auto),
124 "always" => Ok(SparseMode::Always),
125 "never" => Ok(SparseMode::Never),
126 _ => Err(format!("invalid argument '{}' for '--sparse'", s)),
127 }
128}
129
130pub fn apply_no_preserve(list: &str, config: &mut CpConfig) {
132 for attr in list.split(',') {
133 match attr.trim() {
134 "mode" => config.preserve_mode = false,
135 "ownership" => config.preserve_ownership = false,
136 "timestamps" => config.preserve_timestamps = false,
137 "links" | "context" | "xattr" => { }
138 "all" => {
139 config.preserve_mode = false;
140 config.preserve_ownership = false;
141 config.preserve_timestamps = false;
142 }
143 _ => {}
144 }
145 }
146}
147
148pub fn parse_backup_mode(s: &str) -> Result<BackupMode, String> {
150 match s {
151 "none" | "off" => Ok(BackupMode::None),
152 "numbered" | "t" => Ok(BackupMode::Numbered),
153 "existing" | "nil" => Ok(BackupMode::Existing),
154 "simple" | "never" => Ok(BackupMode::Simple),
155 _ => Err(format!("invalid backup type '{}'", s)),
156 }
157}
158
159pub fn parse_reflink_mode(s: &str) -> Result<ReflinkMode, String> {
161 match s {
162 "auto" => Ok(ReflinkMode::Auto),
163 "always" => Ok(ReflinkMode::Always),
164 "never" => Ok(ReflinkMode::Never),
165 _ => Err(format!("invalid reflink value '{}'", s)),
166 }
167}
168
169pub fn apply_preserve(list: &str, config: &mut CpConfig) {
173 for attr in list.split(',') {
174 match attr.trim() {
175 "mode" => config.preserve_mode = true,
176 "ownership" => config.preserve_ownership = true,
177 "timestamps" => config.preserve_timestamps = true,
178 "links" | "context" | "xattr" => { }
179 "all" => {
180 config.preserve_mode = true;
181 config.preserve_ownership = true;
182 config.preserve_timestamps = true;
183 }
184 _ => {}
185 }
186 }
187}
188
189fn make_backup(dst: &Path, config: &CpConfig) -> io::Result<()> {
194 let mode = match config.backup {
195 Some(m) => m,
196 None => return Ok(()),
197 };
198 if mode == BackupMode::None {
199 return Ok(());
200 }
201 if !dst.exists() {
202 return Ok(());
203 }
204
205 let backup_path = match mode {
206 BackupMode::Simple | BackupMode::None => {
207 let mut p = dst.as_os_str().to_os_string();
208 p.push(&config.suffix);
209 std::path::PathBuf::from(p)
210 }
211 BackupMode::Numbered => numbered_backup_path(dst),
212 BackupMode::Existing => {
213 let numbered = numbered_backup_candidate(dst, 1);
215 if numbered.exists() {
216 numbered_backup_path(dst)
217 } else {
218 let mut p = dst.as_os_str().to_os_string();
219 p.push(&config.suffix);
220 std::path::PathBuf::from(p)
221 }
222 }
223 };
224
225 std::fs::rename(dst, &backup_path)?;
226 Ok(())
227}
228
229fn numbered_backup_path(dst: &Path) -> std::path::PathBuf {
230 let mut n: u64 = 1;
231 loop {
232 let candidate = numbered_backup_candidate(dst, n);
233 if !candidate.exists() {
234 return candidate;
235 }
236 n += 1;
237 }
238}
239
240fn numbered_backup_candidate(dst: &Path, n: u64) -> std::path::PathBuf {
241 let mut p = dst.as_os_str().to_os_string();
242 p.push(format!(".~{}~", n));
243 std::path::PathBuf::from(p)
244}
245
246fn preserve_attributes_from_meta(
251 meta: &std::fs::Metadata,
252 dst: &Path,
253 config: &CpConfig,
254) -> io::Result<()> {
255 #[cfg(unix)]
258 if config.preserve_mode {
259 let mode = meta.mode();
260 std::fs::set_permissions(dst, std::fs::Permissions::from_mode(mode))?;
261 }
262
263 #[cfg(unix)]
264 if config.preserve_timestamps {
265 let atime_spec = libc::timespec {
266 tv_sec: meta.atime(),
267 tv_nsec: meta.atime_nsec(),
268 };
269 let mtime_spec = libc::timespec {
270 tv_sec: meta.mtime(),
271 tv_nsec: meta.mtime_nsec(),
272 };
273 let times = [atime_spec, mtime_spec];
274 let c_path = std::ffi::CString::new(dst.as_os_str().as_encoded_bytes())
276 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
277 let ret = unsafe { libc::utimensat(libc::AT_FDCWD, c_path.as_ptr(), times.as_ptr(), 0) };
279 if ret != 0 {
280 return Err(io::Error::last_os_error());
281 }
282 }
283
284 #[cfg(unix)]
285 if config.preserve_ownership {
286 let c_path = std::ffi::CString::new(dst.as_os_str().as_encoded_bytes())
288 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
289 let ret = unsafe { libc::lchown(c_path.as_ptr(), meta.uid(), meta.gid()) };
291 if ret != 0 {
292 let err = io::Error::last_os_error();
294 if err.raw_os_error() != Some(libc::EPERM) {
295 return Err(err);
296 }
297 }
298 }
299
300 #[cfg(not(unix))]
302 {
303 let _ = (meta, config);
304 }
305
306 Ok(())
307}
308
309#[cfg(not(target_os = "linux"))]
316fn copy_data_large_buf(src: &Path, dst: &Path, src_len: u64, src_mode: u32) -> io::Result<()> {
317 use std::cell::RefCell;
318 use std::io::{Read, Write};
319 const MAX_BUF: usize = 4 * 1024 * 1024; const SHRINK_THRESHOLD: usize = 512 * 1024; thread_local! {
325 static BUF: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
326 }
327
328 let buf_size = src_len.min(MAX_BUF as u64).max(8192) as usize;
330
331 let mut reader = std::fs::File::open(src)?;
332 let mut opts = std::fs::OpenOptions::new();
333 opts.write(true).create(true).truncate(true);
334 #[cfg(unix)]
335 {
336 use std::os::unix::fs::OpenOptionsExt;
337 opts.mode(src_mode);
338 }
339 #[cfg(not(unix))]
340 let _ = src_mode;
341 let mut writer = opts.open(dst)?;
342
343 BUF.with(|cell| {
344 let mut buf = cell.borrow_mut();
345 if buf.len() > SHRINK_THRESHOLD && buf_size < buf.len() / 4 {
347 buf.resize(buf_size, 0);
348 buf.shrink_to_fit();
349 } else if buf.len() < buf_size {
350 buf.resize(buf_size, 0);
351 }
352 loop {
353 let n = reader.read(&mut buf[..buf_size])?;
354 if n == 0 {
355 break;
356 }
357 writer.write_all(&buf[..n])?;
358 }
359 Ok(())
360 })
361}
362
363#[cfg(target_os = "linux")]
370fn copy_data_linux(src: &Path, dst: &Path, config: &CpConfig, create_mode: u32) -> io::Result<()> {
371 use std::os::unix::fs::OpenOptionsExt;
372 use std::os::unix::io::AsRawFd;
373
374 let src_file = std::fs::File::open(src)?;
375 let src_fd = src_file.as_raw_fd();
376
377 let fd_meta = src_file.metadata()?;
379 let len = fd_meta.len();
380
381 let dst_file = std::fs::OpenOptions::new()
382 .write(true)
383 .create(true)
384 .truncate(true)
385 .mode(create_mode)
386 .open(dst)?;
387 let dst_fd = dst_file.as_raw_fd();
388
389 unsafe {
392 let _ = libc::posix_fadvise(src_fd, 0, 0, libc::POSIX_FADV_SEQUENTIAL);
393 }
394
395 if matches!(config.reflink, ReflinkMode::Auto | ReflinkMode::Always) {
397 const FICLONE: libc::c_ulong = 0x40049409;
398 let should_try = config.reflink == ReflinkMode::Always
399 || !FICLONE_UNSUPPORTED.load(std::sync::atomic::Ordering::Relaxed);
400
401 if should_try {
402 let ret = unsafe { libc::ioctl(dst_fd, FICLONE, src_fd) };
404 if ret == 0 {
405 return Ok(());
406 }
407 let errno = io::Error::last_os_error().raw_os_error().unwrap_or(0);
408 if config.reflink == ReflinkMode::Always {
409 return Err(io::Error::new(
410 io::ErrorKind::Unsupported,
411 format!(
412 "failed to clone '{}' to '{}': {}",
413 src.display(),
414 dst.display(),
415 io::Error::from_raw_os_error(errno)
416 ),
417 ));
418 }
419 if matches!(errno, libc::EOPNOTSUPP | libc::ENOTTY | libc::ENOSYS) {
420 FICLONE_UNSUPPORTED.store(true, std::sync::atomic::Ordering::Relaxed);
421 }
422 if errno == libc::EXDEV {
423 return readwrite_with_buffer(src_file, dst_file, len);
426 }
427 }
429 }
430
431 let mut remaining = match i64::try_from(len) {
433 Ok(v) => v,
434 Err(_) => return readwrite_with_buffer(src_file, dst_file, len),
436 };
437 let mut cfr_failed = false;
438 while remaining > 0 {
439 let to_copy = (remaining as u64).min(isize::MAX as u64) as usize;
440 let ret = unsafe {
443 libc::syscall(
444 libc::SYS_copy_file_range,
445 src_fd,
446 std::ptr::null_mut::<libc::off64_t>(),
447 dst_fd,
448 std::ptr::null_mut::<libc::off64_t>(),
449 to_copy,
450 0u32,
451 )
452 };
453 if ret < 0 {
454 let err = io::Error::last_os_error();
455 if matches!(
456 err.raw_os_error(),
457 Some(libc::EINVAL | libc::ENOSYS | libc::EXDEV)
458 ) {
459 cfr_failed = true;
460 break;
461 }
462 return Err(err);
463 }
464 if ret == 0 {
465 if remaining > 0 {
466 return Err(io::Error::new(
468 io::ErrorKind::UnexpectedEof,
469 "source file shrank during copy",
470 ));
471 }
472 break;
473 }
474 remaining -= ret as i64;
475 }
476 if !cfr_failed {
477 return Ok(());
478 }
479
480 use std::io::Seek;
483 let mut src_file = src_file;
484 let mut dst_file = dst_file;
485 src_file.seek(std::io::SeekFrom::Start(0))?;
486 dst_file.seek(std::io::SeekFrom::Start(0))?;
487 dst_file.set_len(0)?;
488
489 readwrite_with_buffer(src_file, dst_file, len)
490}
491
492#[cfg(target_os = "linux")]
494fn readwrite_with_buffer(
495 mut src_file: std::fs::File,
496 mut dst_file: std::fs::File,
497 len: u64,
498) -> io::Result<()> {
499 use std::cell::RefCell;
500 use std::io::{Read, Write};
501
502 const MAX_BUF: usize = 4 * 1024 * 1024;
503 const SHRINK_THRESHOLD: usize = 512 * 1024;
505
506 let buf_size = (len.min(MAX_BUF as u64) as usize).max(8192);
508
509 thread_local! {
510 static BUF: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
511 }
512 BUF.with(|cell| {
513 let mut buf = cell.borrow_mut();
514 if buf.len() > SHRINK_THRESHOLD && buf_size < buf.len() / 4 {
515 buf.resize(buf_size, 0);
516 buf.shrink_to_fit();
517 } else if buf.len() < buf_size {
518 buf.resize(buf_size, 0);
519 }
520 loop {
521 let n = src_file.read(&mut buf[..buf_size])?;
522 if n == 0 {
523 break;
524 }
525 dst_file.write_all(&buf[..n])?;
526 }
527 Ok(())
528 })
529}
530
531pub fn copy_file(src: &Path, dst: &Path, config: &CpConfig) -> io::Result<()> {
535 let src_meta = if config.dereference == DerefMode::Always {
536 std::fs::metadata(src)?
537 } else {
538 std::fs::symlink_metadata(src)?
539 };
540
541 copy_file_with_meta(src, dst, &src_meta, config)
542}
543
544fn copy_file_with_meta(
546 src: &Path,
547 dst: &Path,
548 src_meta: &std::fs::Metadata,
549 config: &CpConfig,
550) -> io::Result<()> {
551 if src_meta.file_type().is_symlink() && config.dereference == DerefMode::Never {
553 let target = std::fs::read_link(src)?;
554 #[cfg(unix)]
555 {
556 std::os::unix::fs::symlink(&target, dst)?;
557 }
558 #[cfg(not(unix))]
559 {
560 let _ = target;
562 std::fs::copy(src, dst)?;
563 }
564 return Ok(());
565 }
566
567 if config.attributes_only {
569 if !dst.exists() {
570 std::fs::File::create(dst)?;
572 }
573 preserve_attributes_from_meta(src_meta, dst, config)?;
574 return Ok(());
575 }
576
577 if config.link {
579 std::fs::hard_link(src, dst)?;
580 return Ok(());
581 }
582
583 if config.symbolic_link {
585 #[cfg(unix)]
586 {
587 std::os::unix::fs::symlink(src, dst)?;
588 }
589 #[cfg(not(unix))]
590 {
591 return Err(io::Error::new(
592 io::ErrorKind::Unsupported,
593 "symbolic links are not supported on this platform",
594 ));
595 }
596 return Ok(());
597 }
598
599 #[cfg(unix)]
601 let create_mode: u32 = if config.preserve_mode {
602 src_meta.mode()
603 } else {
604 0o666
605 };
606
607 #[cfg(target_os = "linux")]
609 {
610 copy_data_linux(src, dst, config, create_mode)?;
611 preserve_attributes_from_meta(src_meta, dst, config)?;
612 return Ok(());
613 }
614
615 #[cfg(not(target_os = "linux"))]
617 {
618 #[cfg(not(unix))]
619 let create_mode = 0o666u32;
620 copy_data_large_buf(src, dst, src_meta.len(), create_mode)?;
621 preserve_attributes_from_meta(src_meta, dst, config)?;
622 Ok(())
623 }
624}
625
626fn copy_recursive(
630 src: &Path,
631 dst: &Path,
632 config: &CpConfig,
633 root_dev: Option<u64>,
634) -> io::Result<()> {
635 let src_meta = std::fs::symlink_metadata(src)?;
636
637 #[cfg(unix)]
638 if config.one_file_system {
639 if let Some(dev) = root_dev {
640 if src_meta.dev() != dev {
641 return Ok(());
642 }
643 }
644 }
645
646 if src_meta.is_dir() {
647 if !dst.exists() {
648 std::fs::create_dir_all(dst)?;
649 }
650
651 #[cfg(unix)]
652 let next_dev = Some(root_dev.unwrap_or(src_meta.dev()));
653 #[cfg(not(unix))]
654 let next_dev: Option<u64> = None;
655
656 let mut files: Vec<(std::path::PathBuf, std::path::PathBuf, std::fs::Metadata)> =
658 Vec::new();
659 let mut dirs: Vec<(std::path::PathBuf, std::path::PathBuf)> = Vec::new();
660
661 for entry in std::fs::read_dir(src)? {
662 let entry = entry?;
663 let child_src = entry.path();
664 let child_dst = dst.join(entry.file_name());
665 let meta = if config.dereference == DerefMode::Always {
667 std::fs::metadata(&child_src)?
668 } else {
669 std::fs::symlink_metadata(&child_src)?
670 };
671 #[cfg(unix)]
673 if config.one_file_system {
674 if let Some(dev) = root_dev {
675 if meta.dev() != dev {
676 continue;
677 }
678 }
679 }
680 if meta.is_dir() {
681 dirs.push((child_src, child_dst));
682 } else {
683 files.push((child_src, child_dst, meta));
684 }
685 }
686
687 const PARALLEL_FILE_THRESHOLD: usize = 8;
690
691 if files.len() >= PARALLEL_FILE_THRESHOLD {
693 use rayon::prelude::*;
694 let result: Result<(), io::Error> =
695 files
696 .par_iter()
697 .try_for_each(|(child_src, child_dst, meta)| {
698 copy_file_with_meta(child_src, child_dst, meta, config)
699 });
700 result?;
701 } else {
702 for (child_src, child_dst, meta) in &files {
703 copy_file_with_meta(child_src, child_dst, meta, config)?;
704 }
705 }
706
707 for (child_src, child_dst) in &dirs {
710 copy_recursive(child_src, child_dst, config, next_dev)?;
711 }
712
713 preserve_attributes_from_meta(&src_meta, dst, config)?;
715 } else {
716 if let Some(parent) = dst.parent() {
718 if !parent.exists() {
719 std::fs::create_dir_all(parent)?;
720 }
721 }
722 copy_file_with_meta(src, dst, &src_meta, config)?;
723 }
724 Ok(())
725}
726
727pub fn run_cp(
737 sources: &[String],
738 raw_dest: Option<&str>,
739 config: &CpConfig,
740) -> (Vec<String>, bool) {
741 let mut errors: Vec<String> = Vec::new();
742 let mut had_error = false;
743
744 let dest_dir: Option<std::path::PathBuf> = config
746 .target_directory
747 .as_deref()
748 .or(raw_dest)
749 .map(std::path::PathBuf::from);
750
751 let dest_dir = match dest_dir {
752 Some(d) => d,
753 None => {
754 errors.push("cp: missing destination operand".to_string());
755 return (errors, true);
756 }
757 };
758
759 let copy_into_dir = sources.len() > 1 || dest_dir.is_dir() || config.target_directory.is_some();
761
762 let copy_into_dir = copy_into_dir && !config.no_target_directory;
764
765 for source in sources {
766 let src_str = if config.strip_trailing_slashes {
767 source.trim_end_matches('/')
768 } else {
769 source.as_str()
770 };
771 let src = Path::new(src_str);
772
773 let dst = if config.parents {
774 let rel = src_str.trim_start_matches('/');
777 dest_dir.join(rel)
778 } else if copy_into_dir {
779 let name = src.file_name().unwrap_or(src.as_ref());
780 dest_dir.join(name)
781 } else {
782 dest_dir.clone()
783 };
784
785 if config.parents {
786 if let Some(parent) = dst.parent() {
788 if !parent.exists() {
789 if let Err(e) = std::fs::create_dir_all(parent) {
790 let inner = strip_os_error(&e);
791 errors.push(format!(
792 "cp: cannot create directory '{}': {}",
793 parent.display(),
794 inner
795 ));
796 had_error = true;
797 continue;
798 }
799 }
800 }
801 }
802
803 if let Err(e) = do_copy(src, &dst, config) {
804 let inner = strip_os_error(&e);
805 let msg = if inner.contains("are the same file") {
806 format!("cp: {}", inner)
808 } else if inner.contains("omitting directory") {
809 format!("cp: {}", inner)
810 } else {
811 format!(
812 "cp: cannot copy '{}' to '{}': {}",
813 src.display(),
814 dst.display(),
815 inner
816 )
817 };
818 errors.push(msg);
819 had_error = true;
820 } else if config.verbose {
821 println!("'{}' -> '{}'", src.display(), dst.display());
823 }
824 }
825
826 (errors, had_error)
827}
828
829fn do_copy(src: &Path, dst: &Path, config: &CpConfig) -> io::Result<()> {
831 let src_meta = if config.dereference == DerefMode::Always {
832 std::fs::metadata(src)?
833 } else {
834 std::fs::symlink_metadata(src)?
835 };
836
837 if src_meta.is_dir() && !config.recursive {
839 return Err(io::Error::new(
840 io::ErrorKind::Other,
841 format!("omitting directory '{}'", src.display()),
842 ));
843 }
844
845 #[cfg(unix)]
847 if src_meta.is_dir() && dst.exists() && !dst.is_dir() {
848 return Err(io::Error::new(
849 io::ErrorKind::Other,
850 format!(
851 "cannot overwrite non-directory '{}' with directory '{}'",
852 dst.display(),
853 src.display()
854 ),
855 ));
856 }
857
858 if config.no_clobber && dst.exists() {
860 return Ok(());
861 }
862
863 if config.update && dst.exists() {
865 if let (Ok(src_m), Ok(dst_m)) = (src.metadata(), dst.metadata()) {
866 if let (Ok(src_t), Ok(dst_t)) = (src_m.modified(), dst_m.modified()) {
867 if dst_t >= src_t {
868 return Ok(());
869 }
870 }
871 }
872 }
873
874 if config.interactive && dst.exists() {
876 eprint!("cp: overwrite '{}'? ", dst.display());
877 let mut response = String::new();
878 io::stdin().read_line(&mut response)?;
879 let r = response.trim().to_lowercase();
880 if !(r == "y" || r == "yes") {
881 return Ok(());
882 }
883 }
884
885 #[cfg(unix)]
889 if !src_meta.is_dir() && dst.exists() {
890 if let Ok(dst_meta) = std::fs::metadata(dst) {
891 if src_meta.dev() == dst_meta.dev() && src_meta.ino() == dst_meta.ino() {
892 let has_backup = matches!(
893 config.backup,
894 Some(BackupMode::Simple | BackupMode::Numbered | BackupMode::Existing)
895 );
896 if has_backup {
897 make_backup(dst, config)?;
899 let backup_src = match config.backup.unwrap() {
901 BackupMode::Simple | BackupMode::None => {
902 let mut p = dst.as_os_str().to_os_string();
903 p.push(&config.suffix);
904 std::path::PathBuf::from(p)
905 }
906 BackupMode::Numbered => {
907 let mut n: u64 = 1;
909 loop {
910 let candidate = numbered_backup_candidate(dst, n);
911 let next = numbered_backup_candidate(dst, n + 1);
912 if !next.exists() {
913 break candidate;
914 }
915 n += 1;
916 }
917 }
918 BackupMode::Existing => {
919 let numbered = numbered_backup_candidate(dst, 1);
920 if numbered.exists() {
921 let mut n: u64 = 1;
922 loop {
923 let candidate = numbered_backup_candidate(dst, n);
924 let next = numbered_backup_candidate(dst, n + 1);
925 if !next.exists() {
926 break candidate;
927 }
928 n += 1;
929 }
930 } else {
931 let mut p = dst.as_os_str().to_os_string();
932 p.push(&config.suffix);
933 std::path::PathBuf::from(p)
934 }
935 }
936 };
937 return copy_file(&backup_src, dst, config);
938 }
939 return Err(io::Error::new(
940 io::ErrorKind::Other,
941 format!(
942 "'{}' and '{}' are the same file",
943 src.display(),
944 dst.display()
945 ),
946 ));
947 }
948 }
949 }
950
951 if config.force && dst.exists() {
954 if config.link || config.symbolic_link {
955 if dst.is_dir() {
957 std::fs::remove_dir(dst).ok();
958 } else {
959 std::fs::remove_file(dst).ok();
960 }
961 } else if let Ok(m) = dst.metadata() {
962 if m.permissions().readonly() {
963 std::fs::remove_file(dst)?;
964 }
965 }
966 }
967
968 if !src_meta.is_dir() {
970 make_backup(dst, config)?;
971 }
972
973 if src_meta.is_dir() {
974 #[cfg(unix)]
975 let root_dev = Some(src_meta.dev());
976 #[cfg(not(unix))]
977 let root_dev: Option<u64> = None;
978 copy_recursive(src, dst, config, root_dev)
979 } else {
980 copy_file(src, dst, config)
981 }
982}
983
984fn strip_os_error(e: &io::Error) -> String {
986 if let Some(raw) = e.raw_os_error() {
987 let msg = format!("{}", e);
988 msg.replace(&format!(" (os error {})", raw), "")
989 } else {
990 format!("{}", e)
991 }
992}