Skip to content

Commit

Permalink
Merge pull request #8 from rustcoreutils/rm
Browse files Browse the repository at this point in the history
add util: rm
  • Loading branch information
jgarzik authored Mar 14, 2024
2 parents 18490fa + 2120e46 commit 10cb78c
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 7 deletions.
42 changes: 36 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ https://github.com/jgarzik/posixutils
- [ ] qstat (Batch cat.)
- [ ] qsub (Batch cat.)
- [x] renice
- [ ] rm
- [x] rm
- [ ] rmdel (SCCS)
- [x] rmdir
- [ ] sact (SCCS)
Expand Down
6 changes: 6 additions & 0 deletions tree/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ repository = "https://github.com/rustcoreutils/posixutils-rs.git"
plib = { path = "../plib" }
clap = { version = "4", features = ["derive"] }
gettext-rs = { version = "0.7", features = ["gettext-system"] }
walkdir = "2.5"
libc = "0.2"

[[bin]]
name = "rm"
path = "src/rm.rs"

[[bin]]
name = "rmdir"
Expand Down
153 changes: 153 additions & 0 deletions tree/src/rm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
//
// Copyright (c) 2024 Jeff Garzik
//
// This file is part of the posixutils-rs project covered under
// the MIT License. For the full license text, please see the LICENSE
// file in the root directory of this project.
// SPDX-License-Identifier: MIT
//

extern crate clap;
extern crate libc;
extern crate plib;

use clap::Parser;
use gettextrs::{bind_textdomain_codeset, gettext, textdomain};
use plib::PROJECT_NAME;
use std::ffi::OsStr;
use std::fs;
use std::io::{self, BufRead, Write};
use walkdir::WalkDir;

/// rm - remove directory entries
#[derive(Parser, Debug)]
#[command(author, version, about, long_about)]
struct Args {
/// Do not prompt for confirmation.
#[arg(short, long)]
force: bool,

/// Prompt for confirmation.
#[arg(short, long)]
interactive: bool,

/// Remove file hierarchies.
#[arg(short, short_alias = 'r', long)]
recurse: bool,

/// Filepaths to remove
files: Vec<String>,
}

struct RmConfig {
args: Args,
is_tty: bool,
}

fn prompt(cfg: &RmConfig, filepath: &OsStr, metadata: &fs::Metadata) -> bool {
let writable = !metadata.permissions().readonly();

let do_prompt = cfg.args.interactive || (!cfg.args.force && !writable && cfg.is_tty);
if !do_prompt {
return true;
}

let prompt = format!(
"{} {}? ",
gettext("remove read-only file"),
filepath.to_string_lossy()
);
io::stdout()
.write_all(prompt.as_bytes())
.expect("stdout failure");
io::stdout().flush().expect("stdout failure");

let mut line = String::new();
io::stdin()
.lock()
.read_line(&mut line)
.expect("stdin read failure");

line.starts_with("y") || line.starts_with("Y")
}

fn rm_file(cfg: &RmConfig, filepath: &OsStr, metadata: &fs::Metadata) -> io::Result<()> {
if prompt(cfg, filepath, metadata) {
fs::remove_file(filepath)
} else {
Ok(())
}
}

fn rm_dir_simple(cfg: &RmConfig, filepath: &OsStr, metadata: &fs::Metadata) -> io::Result<()> {
if prompt(cfg, filepath, metadata) {
fs::remove_dir(filepath)
} else {
Ok(())
}
}

fn rm_directory(cfg: &RmConfig, filepath: &OsStr) -> io::Result<()> {
if !cfg.args.recurse {
eprintln!(
"{} {}",
filepath.to_string_lossy(),
gettext("is a directory; not following")
);
return Ok(());
}

for entry in WalkDir::new(filepath)
.contents_first(true)
.follow_links(false)
{
let entry = entry?;
let subname = entry.path().as_os_str();
let sub_metadata = entry.metadata()?;
if sub_metadata.is_dir() {
rm_dir_simple(cfg, subname, &sub_metadata)?;
} else {
rm_file(cfg, subname, &sub_metadata)?;
}
}

Ok(())
}

fn rm_path(cfg: &RmConfig, filepath: &OsStr) -> io::Result<()> {
let metadata = fs::metadata(filepath)?;

if metadata.is_dir() {
rm_directory(cfg, filepath)
} else {
rm_file(cfg, filepath, &metadata)
}
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
// parse command line arguments
let args = Args::parse();

textdomain(PROJECT_NAME)?;
bind_textdomain_codeset(PROJECT_NAME, "UTF-8")?;

let is_tty = unsafe { libc::isatty(libc::STDIN_FILENO) != 0 };
let cfg = RmConfig { args, is_tty };

let mut exit_code = 0;

for filepath_str in &cfg.args.files {
let filepath = OsStr::new(filepath_str);
match rm_path(&cfg, &filepath) {
Ok(()) => {}
Err(e) => {
exit_code = 1;
if !cfg.args.force {
eprintln!("{}: {}", filepath.to_string_lossy(), e);
}
}
}
}

std::process::exit(exit_code)
}

0 comments on commit 10cb78c

Please sign in to comment.