diff --git a/Cargo.toml b/Cargo.toml index 28069d8..61050a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rembackup" version = "0.1.0" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/apply_indexchanges.rs b/src/apply_indexchanges.rs index 0d35b08..eb49c60 100755 --- a/src/apply_indexchanges.rs +++ b/src/apply_indexchanges.rs @@ -1,5 +1,5 @@ use std::{ - fs, io, + fs, path::{Path, PathBuf}, }; @@ -12,12 +12,25 @@ pub fn apply_indexchanges( source: &Path, index: &Path, target: &Option, - changes: &Vec, + changes: &[IndexChange], gib_total: Option, -) -> io::Result<()> { - let o = apply_indexchanges_int(source, index, target, changes, gib_total); +) -> usize { + // do symlinks last, as they cd, which can fail, + // and if it does, it would be fatal and stop the backup. + let (mut changes, symlink_additions) = + changes.into_iter().partition::, _>(|c| match c { + IndexChange::AddDir(..) + | IndexChange::AddFile(..) + | IndexChange::RemoveFile(..) + | IndexChange::RemoveDir(..) => true, + IndexChange::AddSymlink(..) => false, + }); + changes.extend(symlink_additions); + + let mut failures = changes.len(); + apply_indexchanges_int(source, index, target, &changes, gib_total, &mut failures); eprintln!(); - o + failures } fn eprint_constants(changes_total: usize, gib_total: f64) -> (usize, usize, usize) { @@ -63,7 +76,6 @@ fn eprint_status( let pending_prog = " ".repeat(rightpad); eprint!( "\r{changes_pad}{changes_applied}/{changes_total} | {gib_pad}{gib_transferred}/{gib_total:.1}GiB [{completed_prog_min}{completed_prog_max}>{pending_prog}]", - ); } @@ -71,9 +83,10 @@ pub fn apply_indexchanges_int( source: &Path, index: &Path, target: &Option, - changes: &Vec, + changes: &[&IndexChange], gib_total: Option, -) -> io::Result<()> { + failures: &mut usize, +) { let changes_total = changes.len(); let gib_total = gib_total.unwrap_or_else(|| { changes @@ -87,7 +100,7 @@ pub fn apply_indexchanges_int( }) .sum() }); - let (prog_width, changes_len_width, gib_len_width) = eprint_constants(changes.len(), gib_total); + let (prog_width, changes_len_width, gib_len_width) = eprint_constants(changes_total, gib_total); let mut gib_transferred = 0.0; eprint_status( 0, @@ -114,8 +127,14 @@ pub fn apply_indexchanges_int( true }; if ok { - fs::create_dir_all(&index.join(dir))?; + *failures -= 1; + let t = index.join(dir); + if let Err(e) = fs::create_dir_all(&t) { + eprintln!("\n[warn] couldn't create index directory {t:?}: {e}"); + } } + } else { + *failures -= 1; } } IndexChange::AddFile(file, index_file) => { @@ -133,25 +152,42 @@ pub fn apply_indexchanges_int( true }; if ok { - fs::write(&index.join(file), index_file.save())?; + *failures -= 1; + let t = index.join(file); + if let Err(e) = fs::write(&t, index_file.save()) { + eprintln!("\n[warn] couldn't save index file {t:?}: {e}"); + } } } IndexChange::AddSymlink(file, link_target) => { - let cwd = std::env::current_dir()?; + let cwd = match std::env::current_dir() { + Ok(cwd) => cwd, + Err(e) => { + eprintln!("\n[err] fatal: couldn't get cwd: {e}"); + return; + } + }; let ok = if let Some(target) = target { let t = target.join(file); if let Some(p) = t.parent() { let t = t.file_name().expect("a file should always have a filename"); - std::env::set_current_dir(&p)?; - let _ = std::fs::remove_file(t); - if let Err(e) = std::os::unix::fs::symlink(&link_target, t) { - eprintln!( - "\n[warn] couldn't set file {t:?} to be a symlink to {link_target:?}: {e}" - ); + if let Err(e) = std::env::set_current_dir(&p) { + eprintln!("\n[warn] couldn't cd to {p:?}: {e}"); false } else { - std::env::set_current_dir(&cwd)?; - true + let _ = std::fs::remove_file(t); + if let Err(e) = std::os::unix::fs::symlink(&link_target, t) { + eprintln!( + "\n[warn] couldn't set file {t:?} to be a symlink to {link_target:?}: {e}" + ); + false + } else { + if let Err(e) = std::env::set_current_dir(&cwd) { + eprintln!("\n[err] fatal: couldn't cd back to {cwd:?}: {e}"); + return; + } + true + } } } else { eprintln!("\n[warn] symlink path was empty"); @@ -161,27 +197,42 @@ pub fn apply_indexchanges_int( true }; if ok { + *failures -= 1; let index_file = index.join(file); if let Some(p) = index_file.parent() { - std::env::set_current_dir(&p)?; - std::os::unix::fs::symlink( - link_target, - index_file - .file_name() - .expect("a file should always have a filename"), - )?; + if let Err(e) = std::env::set_current_dir(&p) { + eprintln!("\n[warn] couldn't cd to {p:?}: {e}"); + } else { + if let Err(e) = std::os::unix::fs::symlink( + link_target, + index_file + .file_name() + .expect("a file should always have a filename"), + ) { + eprintln!( + "\n[warn] couldn't set index file {index_file:?} to be a symlink to {link_target:?}: {e}" + ); + } + } } else { - eprintln!("\n[warn] couldn't get parent for index file's path, so could not create the symlink"); + eprintln!( + "\n[warn] couldn't get parent for index file's path, so could not create the symlink" + ); } } - std::env::set_current_dir(&cwd)?; + if let Err(e) = std::env::set_current_dir(&cwd) { + eprintln!("\n[err] fatal: couldn't cd back to {cwd:?}: {e}"); + return; + } } IndexChange::RemoveFile(file) => { let i = index.join(file); let ok = if let Some(target) = target { let t = target.join(file); if let Err(e) = fs::remove_file(&t) { - eprintln!("\n[warn] couldn't remove file {t:?}, keeping index file {i:?}: {e:?}\n If this error keeps appearing, check if the file was deleted on the target system but still exists in the index. if yes, consider manually deleting it."); + eprintln!( + "\n[warn] couldn't remove file {t:?}, keeping index file {i:?}: {e:?}\n If this error keeps appearing, check if the file was deleted on the target system but still exists in the index. if yes, consider manually deleting it." + ); false } else { true @@ -190,7 +241,10 @@ pub fn apply_indexchanges_int( true }; if ok { - fs::remove_file(i)?; + *failures -= 1; + if let Err(e) = fs::remove_file(&i) { + eprintln!("\n[warn] couldn't remove index file {i:?}: {e:?}"); + } } } IndexChange::RemoveDir(dir) => { @@ -198,7 +252,9 @@ pub fn apply_indexchanges_int( let ok = if let Some(target) = target { let t = target.join(dir); if let Err(e) = fs::remove_dir_all(&t) { - eprintln!("\n[warn] couldn't remove directory {t:?}, keeping index files under {i:?}: {e:?}\n If this error keeps appearing, check if the directory was deleted on the target system but still exists in the index. if yes, consider manually deleting it."); + eprintln!( + "\n[warn] couldn't remove directory {t:?}, keeping index files under {i:?}: {e:?}\n If this error keeps appearing, check if the directory was deleted on the target system but still exists in the index. if yes, consider manually deleting it." + ); false } else { true @@ -207,14 +263,17 @@ pub fn apply_indexchanges_int( true }; if ok { - fs::remove_dir_all(i)?; + *failures -= 1; + if let Err(e) = fs::remove_dir_all(&i) { + eprintln!("\n[warn] couldn't remove index directory {i:?}: {e:?}"); + } } } } { eprint_status( i + 1, - changes.len(), + changes_total, gib_transferred, gib_total, prog_width, @@ -223,5 +282,4 @@ pub fn apply_indexchanges_int( ); } } - Ok(()) } diff --git a/src/args.rs b/src/args.rs index 128c7db..0a81f5c 100755 --- a/src/args.rs +++ b/src/args.rs @@ -4,6 +4,11 @@ use clap::Parser; use crate::update_index::Settings; +/// rembackup, +/// a simple backup tool for local or remote backups. +/// run with --help for more help. +/// +/// rembackup copies files from to using and storing information in . #[derive(Parser)] #[command(author, version)] pub struct Args { diff --git a/src/main.rs b/src/main.rs index a73c692..dffdc23 100755 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,8 @@ mod update_index; const EXIT_IGNORE_FAILED: u8 = 200; const EXIT_DIFF_FAILED: u8 = 20; -const EXIT_APPLY_FAILED: u8 = 30; +const EXIT_APPLY_FAILED_ONE: u8 = 100; +const EXIT_APPLY_FAILED_ALL: u8 = 200; fn main() { // get args @@ -27,7 +28,7 @@ fn main() { let cwd = match std::env::current_dir() { Ok(v) => Some(v), Err(e) => { - eprintln!("[WARN] Couldn't get current directory (CWD): {e}"); + eprintln!("[warn] Couldn't get current directory (CWD): {e}"); None } }; @@ -178,7 +179,7 @@ fn main() { if !args.noconfirm { loop { if args.target.is_none() { - eprintln!("[WARN] You didn't set a `target` directory!\n[WARN] Be careful not to update your index without actually applying the changes to the `target` filesystem!\nType 'Ok' and press enter to continue."); + eprintln!("[warn] You didn't set a `target` directory!\n[warn] Be careful not to update your index without actually applying the changes to the `target` filesystem!\nType 'Ok' and press enter to continue."); } else { eprintln!("Exclude unwanted directories/files using --ignore,\nor press enter to apply the changes."); } @@ -195,18 +196,19 @@ fn main() { } } } - match apply_indexchanges( + let failure_count = apply_indexchanges( &args.source, &args.index, &args.target, &changes, Some(add_file_total_size_gib), - ) { - Ok(()) => {} - Err(e) => { - eprintln!("Failed to apply: {e}"); - exit(EXIT_APPLY_FAILED as _); - } + ); + eprintln!("[info] encountered {failure_count} failures"); + if failure_count > 0 { + exit( + (EXIT_APPLY_FAILED_ONE as u64 + failure_count.ilog2() as u64) + .min(EXIT_APPLY_FAILED_ALL as u64) as _, + ); } } } diff --git a/src/update_index.rs b/src/update_index.rs index 0a51caa..b05ef82 100755 --- a/src/update_index.rs +++ b/src/update_index.rs @@ -14,10 +14,15 @@ use crate::{ #[derive(Clone, Default, Args)] pub struct Settings { - /// don't sort the changes that form the backup. disables sort options. + /// don't sort the changes that form the backup + /// + /// disables sort options. + /// symlinks will still be created last. #[arg(long)] pub dont_sort: bool, /// start with smaller directories rather than larger ones + /// + /// symlinks will still be created last. #[arg(long)] pub smallest_first: bool, /// show changes in the order in which they will be applied, not reversed