Use a different graveyard per user by default

This also obviates the need for umask/permissions juju.
This commit is contained in:
Kevin Liu 2016-11-04 13:19:07 -07:00
parent 208ca3db68
commit 43711e6fa1
3 changed files with 30 additions and 41 deletions

View file

@ -1,8 +1,8 @@
[package] [package]
name = "rm-improved" name = "rm-improved"
version = "0.10.2" version = "0.11.0"
authors = ["mail@nivekuil.com"] 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" repository = "https://github.com/nivekuil/rip"
license = "GPL-3.0+" license = "GPL-3.0+"
include = [ include = [
@ -13,7 +13,7 @@ include = [
[dependencies] [dependencies]
clap = "2" clap = "2"
walkdir = "0.1" walkdir = "0.1"
libc = "0.2" time = "0.1"
[profile.release] [profile.release]
lto = true lto = true

View file

@ -45,7 +45,7 @@ $ rip dir1/ file1
Undo the last deletion Undo the last deletion
#+BEGIN_EXAMPLE #+BEGIN_EXAMPLE
$ rip -u $ 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 #+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 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 #+BEGIN_EXAMPLE
@ -58,28 +58,28 @@ Send file1 to the graveyard? (y/n) y
Print files that were deleted from under the current directory Print files that were deleted from under the current directory
#+BEGIN_EXAMPLE #+BEGIN_EXAMPLE
$ rip -s $ rip -s
/tmp/.graveyard/home/jack/file1 /tmp/graveyard-jack/home/jack/file1
/tmp/.graveyard/home/jack/dir1 /tmp/graveyard-jack/home/jack/dir1
#+END_EXAMPLE #+END_EXAMPLE
Name conflicts are resolved Name conflicts are resolved
#+BEGIN_EXAMPLE #+BEGIN_EXAMPLE
$ touch file1 $ touch file1
$ rip file1 $ rip file1
$ rip -s $ rip -s
/tmp/.graveyard/home/jack/dir1 /tmp/graveyard-jack/home/jack/dir1
/tmp/.graveyard/home/jack/file1 /tmp/graveyard-jack/home/jack/file1
/tmp/.graveyard/home/jack/file1~1 /tmp/graveyard-jack/home/jack/file1~1
#+END_EXAMPLE #+END_EXAMPLE
-u also takes the path of a file in the graveyard -u also takes the path of a file in the graveyard
#+BEGIN_EXAMPLE #+BEGIN_EXAMPLE
$ rip -u /tmp/.graveyard/home/jack/file1 $ rip -u /tmp/graveyard-jack/home/jack/file1
Returned /tmp/.graveyard/home/jack/file1 to /home/jack/file1 Returned /tmp/graveyard-jack/home/jack/file1 to /home/jack/file1
#+END_EXAMPLE #+END_EXAMPLE
Combine -u and -s to restore everything printed by -s Combine -u and -s to restore everything printed by -s
#+BEGIN_EXAMPLE #+BEGIN_EXAMPLE
$ rip -su $ rip -su
Returned /tmp/.graveyard/home/jack/dir1 to /home/jack/dir1 Returned /tmp/graveyard-jack/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/file1~1 to /home/jack/file1~1
#+END_EXAMPLE #+END_EXAMPLE
** ⚰ Notes ** ⚰ 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. - 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.

View file

@ -36,7 +36,7 @@ fn main() {
.version(crate_version!()) .version(crate_version!())
.author(crate_authors!()) .author(crate_authors!())
.about("Rm ImProved .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") .arg(Arg::with_name("TARGET")
.help("File or directory to remove") .help("File or directory to remove")
.multiple(true) .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")) { let graveyard = &match (matches.value_of("graveyard"), env::var("GRAVEYARD")) {
(Some(flag), _) => PathBuf::from(flag), (Some(flag), _) => PathBuf::from(flag),
(_, Ok(env)) => PathBuf::from(env), (_, Ok(env)) => PathBuf::from(env),
_ => PathBuf::from(GRAVEYARD) _ => PathBuf::from(format!("{}-{}", GRAVEYARD, get_user()))
}; };
if matches.is_present("decompose") { 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 record: &Path = &graveyard.join(RECORD);
let cwd: PathBuf = env::current_dir().expect("Failed to get current dir"); 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") { if let Some(t) = matches.values_of("unbury") {
// Vector to hold the grave path of items we want to unbury. // Vector to hold the grave path of items we want to unbury.
@ -262,10 +258,8 @@ fn bury<S: AsRef<Path>, D: AsRef<Path>>(source: S, dest: D) -> io::Result<()> {
} }
// If that didn't work, then copy and rm. // If that didn't work, then copy and rm.
// All parent directories are created with open permissions so that let parent = dest.parent().ok_or(io::Error::last_os_error())?;
// other users can rip things into the same graveyard fs::create_dir_all(parent)?;
let parent = dest.parent().expect("Trying to delete root?");
fs::DirBuilder::new().mode(0o777).recursive(true).create(parent)?;
if fs::symlink_metadata(source)?.is_dir() { if fs::symlink_metadata(source)?.is_dir() {
// Walk the source, creating directories and copying files as needed // Walk the source, creating directories and copying files as needed
@ -274,10 +268,7 @@ fn bury<S: AsRef<Path>, D: AsRef<Path>>(source: S, dest: D) -> io::Result<()> {
let orphan: &Path = entry.path().strip_prefix(source) let orphan: &Path = entry.path().strip_prefix(source)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
if entry.file_type().is_dir() { if entry.file_type().is_dir() {
let mode = entry.metadata()?.permissions().mode(); if let Err(e) = fs::create_dir_all(dest.join(orphan)) {
if let Err(e) = fs::DirBuilder::new()
.mode(mode)
.create(dest.join(orphan)) {
println!("Failed to create {} in {}", println!("Failed to create {} in {}",
entry.path().display(), entry.path().display(),
dest.join(orphan).display()); dest.join(orphan).display());
@ -340,7 +331,7 @@ fn copy_file<S: AsRef<Path>, D: AsRef<Path>>(source: S, dest: D) -> io::Result<(
Ok(()) 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 /// As a side effect, any valid last files that are found in the record but
/// not on the filesystem are removed from the record. /// not on the filesystem are removed from the record.
fn get_last_bury<R: AsRef<Path>>(record: R) -> io::Result<String> { fn get_last_bury<R: AsRef<Path>>(record: R) -> io::Result<String> {
@ -351,21 +342,19 @@ fn get_last_bury<R: AsRef<Path>>(record: R) -> io::Result<String> {
f.read_to_string(&mut contents)?; f.read_to_string(&mut contents)?;
// This could be cleaned up more if/when for loops can return a value // This could be cleaned up more if/when for loops can return a value
for entry in contents.lines().rev() for entry in contents.lines().rev().map(record_entry) {
.map(record_entry) // Check that the file is still in the graveyard.
.filter(|x| x.user == get_user()) { // If it is, return the corresponding line.
// Check that the file is still in the graveyard. if symlink_exists(entry.dest) {
// If it is, return the corresponding line. if !graves_to_exhume.is_empty() {
if symlink_exists(entry.dest) { delete_lines_from_record(f, record, graves_to_exhume)?;
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));
} }
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() { if !graves_to_exhume.is_empty() {
delete_lines_from_record(f, record, graves_to_exhume)?; delete_lines_from_record(f, record, graves_to_exhume)?;