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]
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

View file

@ -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.

View file

@ -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<S: AsRef<Path>, D: AsRef<Path>>(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<S: AsRef<Path>, D: AsRef<Path>>(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<S: AsRef<Path>, D: AsRef<Path>>(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<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)?;
// 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)?;