diff --git a/Cargo.toml b/Cargo.toml index 2d79760..5701865 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "rm-improved" -version = "0.10.2" +version = "0.11.0" authors = ["mail@nivekuil.com"] -description = "rip: a safety-focused alternative to rm." +description = "rip: a safe and ergonomic alternative to rm" repository = "https://github.com/nivekuil/rip" license = "GPL-3.0+" include = [ @@ -13,7 +13,7 @@ include = [ [dependencies] clap = "2" walkdir = "0.1" -libc = "0.2" +time = "0.1" [profile.release] lto = true diff --git a/README.org b/README.org index 8275721..25ee3ef 100644 --- a/README.org +++ b/README.org @@ -45,7 +45,7 @@ $ rip dir1/ file1 Undo the last deletion #+BEGIN_EXAMPLE $ rip -u -Returned /tmp/.graveyard/home/jack/file1 to /home/jack/file1 +Returned /tmp/graveyard-jack/home/jack/file1 to /home/jack/file1 #+END_EXAMPLE Print some info (size and first few lines in a file, total size and first few files in a directory) about the target and then prompt for deletion #+BEGIN_EXAMPLE @@ -58,28 +58,28 @@ Send file1 to the graveyard? (y/n) y Print files that were deleted from under the current directory #+BEGIN_EXAMPLE $ rip -s -/tmp/.graveyard/home/jack/file1 -/tmp/.graveyard/home/jack/dir1 +/tmp/graveyard-jack/home/jack/file1 +/tmp/graveyard-jack/home/jack/dir1 #+END_EXAMPLE Name conflicts are resolved #+BEGIN_EXAMPLE $ touch file1 $ rip file1 $ rip -s -/tmp/.graveyard/home/jack/dir1 -/tmp/.graveyard/home/jack/file1 -/tmp/.graveyard/home/jack/file1~1 +/tmp/graveyard-jack/home/jack/dir1 +/tmp/graveyard-jack/home/jack/file1 +/tmp/graveyard-jack/home/jack/file1~1 #+END_EXAMPLE -u also takes the path of a file in the graveyard #+BEGIN_EXAMPLE -$ rip -u /tmp/.graveyard/home/jack/file1 -Returned /tmp/.graveyard/home/jack/file1 to /home/jack/file1 +$ rip -u /tmp/graveyard-jack/home/jack/file1 +Returned /tmp/graveyard-jack/home/jack/file1 to /home/jack/file1 #+END_EXAMPLE Combine -u and -s to restore everything printed by -s #+BEGIN_EXAMPLE $ rip -su -Returned /tmp/.graveyard/home/jack/dir1 to /home/jack/dir1 -Returned /tmp/.graveyard/home/jack/file1~1 to /home/jack/file1~1 +Returned /tmp/graveyard-jack/home/jack/dir1 to /home/jack/dir1 +Returned /tmp/graveyard-jack/home/jack/file1~1 to /home/jack/file1~1 #+END_EXAMPLE ** ⚰ Notes - You probably shouldn't alias =rm= to =rip=. Unlearning muscle memory is hard, but it's harder to ensure that every =rm= you make (as different users, from different machines and application environments) is the aliased one. diff --git a/src/main.rs b/src/main.rs index 03902f0..58ae863 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,7 +36,7 @@ fn main() { .version(crate_version!()) .author(crate_authors!()) .about("Rm ImProved -Send files to the graveyard (/tmp/.graveyard by default) instead of unlinking them.") +Send files to the graveyard (/tmp/graveyard-$USER by default) instead of unlinking them.") .arg(Arg::with_name("TARGET") .help("File or directory to remove") .multiple(true) @@ -70,7 +70,7 @@ Send files to the graveyard (/tmp/.graveyard by default) instead of unlinking th let graveyard = &match (matches.value_of("graveyard"), env::var("GRAVEYARD")) { (Some(flag), _) => PathBuf::from(flag), (_, Ok(env)) => PathBuf::from(env), - _ => PathBuf::from(GRAVEYARD) + _ => PathBuf::from(format!("{}-{}", GRAVEYARD, get_user())) }; if matches.is_present("decompose") { @@ -84,10 +84,6 @@ Send files to the graveyard (/tmp/.graveyard by default) instead of unlinking th let record: &Path = &graveyard.join(RECORD); let cwd: PathBuf = env::current_dir().expect("Failed to get current dir"); - // Disable umask so rip can create a globally writable graveyard - unsafe { - libc::umask(0); - } if let Some(t) = matches.values_of("unbury") { // Vector to hold the grave path of items we want to unbury. @@ -262,10 +258,8 @@ fn bury, D: AsRef>(source: S, dest: D) -> io::Result<()> { } // If that didn't work, then copy and rm. - // All parent directories are created with open permissions so that - // other users can rip things into the same graveyard - let parent = dest.parent().expect("Trying to delete root?"); - fs::DirBuilder::new().mode(0o777).recursive(true).create(parent)?; + let parent = dest.parent().ok_or(io::Error::last_os_error())?; + fs::create_dir_all(parent)?; if fs::symlink_metadata(source)?.is_dir() { // Walk the source, creating directories and copying files as needed @@ -274,10 +268,7 @@ fn bury, D: AsRef>(source: S, dest: D) -> io::Result<()> { let orphan: &Path = entry.path().strip_prefix(source) .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; if entry.file_type().is_dir() { - let mode = entry.metadata()?.permissions().mode(); - if let Err(e) = fs::DirBuilder::new() - .mode(mode) - .create(dest.join(orphan)) { + if let Err(e) = fs::create_dir_all(dest.join(orphan)) { println!("Failed to create {} in {}", entry.path().display(), dest.join(orphan).display()); @@ -340,7 +331,7 @@ fn copy_file, D: AsRef>(source: S, dest: D) -> io::Result<( Ok(()) } -/// Return the path in the graveyard of the last file buried by the user. +/// Return the path in the graveyard of the last file to be buried. /// As a side effect, any valid last files that are found in the record but /// not on the filesystem are removed from the record. fn get_last_bury>(record: R) -> io::Result { @@ -351,21 +342,19 @@ fn get_last_bury>(record: R) -> io::Result { f.read_to_string(&mut contents)?; // This could be cleaned up more if/when for loops can return a value - for entry in contents.lines().rev() - .map(record_entry) - .filter(|x| x.user == get_user()) { - // Check that the file is still in the graveyard. - // If it is, return the corresponding line. - if symlink_exists(entry.dest) { - if !graves_to_exhume.is_empty() { - delete_lines_from_record(f, record, graves_to_exhume)?; - } - return Ok(String::from(entry.dest)) - } else { - // File is gone, mark the grave to be removed from the record - graves_to_exhume.push(String::from(entry.dest)); + for entry in contents.lines().rev().map(record_entry) { + // Check that the file is still in the graveyard. + // If it is, return the corresponding line. + if symlink_exists(entry.dest) { + if !graves_to_exhume.is_empty() { + delete_lines_from_record(f, record, graves_to_exhume)?; } + return Ok(String::from(entry.dest)) + } else { + // File is gone, mark the grave to be removed from the record + graves_to_exhume.push(String::from(entry.dest)); } + } if !graves_to_exhume.is_empty() { delete_lines_from_record(f, record, graves_to_exhume)?;