obliterate 1.0.0

Force-remove Files and Directories on Linux Including Paths with 000 Permissions.
Documentation
//! Recursive file and directory removal with permission and mount-point management.
//!
//! This module coordinates the destruction of directory trees. It handles Linux-specific
//! challenges such as nested mounts and restrictive permissions by detaching filesystems
//! and elevating access modes before deletion.

use crate::error::Error;
use crate::error::Result;
use crate::unmount;
use std::fs;
use std::io;
use std::io::ErrorKind;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};

/// Force-removes a file or directory and all its descendants.
///
/// # Arguments
///
/// * `path` - The target path to be removed.
///
/// # Returns
///
/// Returns `Ok(())` if the removal was successful.
pub fn remove<P: AsRef<Path>>(path: P) -> Result<()> {
    let path = path.as_ref();

    let normalized: PathBuf = path
        .components()
        .filter(|c| !matches!(c, std::path::Component::CurDir))
        .collect();

    let last_segment = normalized.components().last().ok_or_else(|| {
        Error::InvalidTarget("Invalid path, cannot get last file path component".to_string())
    })?;

    if matches!(last_segment, std::path::Component::ParentDir) {
        return Err(Error::InvalidTarget(
            "Invalid path, last path segment cannot be \"..\"".to_string(),
        ));
    }

    match normalized.symlink_metadata() {
        Ok(_) => {
            unmount_nested(&normalized)?;
            recursive_remove(&normalized).map_err(Error::IoError)
        }
        Err(err) => match err.kind() {
            ErrorKind::NotFound => Err(Error::NotFound),
            _ => Err(Error::IoError(err)),
        },
    }
}

/// Idempotent version of [`remove`], succeeding even if the target does not exist.
///
/// # Arguments
///
/// * `path` - The path to ensure is removed from the filesystem.
///
/// # Returns
///
/// Returns `Ok(())` if the path is absent or was successfully deleted.
pub fn ensure_removed<P: AsRef<Path>>(path: P) -> Result<()> {
    if let Err(err) = path.as_ref().symlink_metadata() {
        if err.kind() == ErrorKind::NotFound {
            return Ok(());
        }
    };
    remove(path)
}

/// Lazily unmounts every mount-point nested under the given path.
///
/// # Arguments
///
/// * `path` - The root directory to check for nested filesystems.
///
/// # Returns
///
/// Returns `Ok(())` after detaching all discovered mount-points.
fn unmount_nested(path: &Path) -> Result<()> {
    for mp in unmount::mounts_under(path)? {
        unmount::unmount_lazy(&mp)?;
    }
    Ok(())
}

/// Performs a recursive deletion of the filesystem tree.
///
/// # Arguments
///
/// * `path` - The current file or directory to remove.
///
/// # Returns
///
/// Returns a [`std::io::Result<()>`] indicating the outcome of the operation.
fn recursive_remove(path: &Path) -> io::Result<()> {
    fix_permissions(path)?;
    let metadata = path.symlink_metadata()?;
    if !metadata.is_dir() {
        fs::remove_file(path)
    } else if fs::remove_dir(path).is_ok() {
        Ok(())
    } else {
        for child in fs::read_dir(&path)? {
            let child = child?;
            let path = child.path();
            stacker::maybe_grow(4 * 1024, 16 * 1024, || recursive_remove(&path))?;
        }
        fs::remove_dir(path)
    }
}

/// Ensures the current process has enough permissions to delete the target.
///
/// # Arguments
///
/// * `path` - The filesystem entry that needs permission adjustment.
///
/// # Returns
///
/// Returns `Ok(())` if the permissions are correctly set to `0o700`.
fn fix_permissions(path: &Path) -> io::Result<()> {
    let metadata = fs::symlink_metadata(path)?;
    let mut perms = metadata.permissions();
    let mode = perms.mode();

    if mode & 0o700 != 0o700 {
        perms.set_mode(mode | 0o700);
        fs::set_permissions(path, perms)?;
    }
    Ok(())
}