obliterate 1.2.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();

    if normalized == Path::new("/") {
        return Err(io::Error::new(ErrorKind::InvalidInput, "refusing to remove root '/'").into());
    }

    let last_segment = normalized
        .components()
        .last()
        .ok_or_else(|| io::Error::new(ErrorKind::InvalidInput, "path has no components"))?;

    if matches!(last_segment, std::path::Component::ParentDir) {
        return Err(io::Error::new(
            ErrorKind::InvalidInput,
            "Invalid path, last path segment cannot be \"..\"",
        )
        .into());
    }

    match normalized.symlink_metadata() {
        Ok(_) => {
            unmount_nested(&normalized)?;
            recursive_remove(&normalized).map_err(Error::Io)
        }
        Err(e) => Err(Error::Io(e)),
    }
}

/// 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<()> {
    let metadata = path.symlink_metadata()?;

    if !metadata.is_symlink() {
        fix_permissions(path)?;
    }

    if !metadata.is_dir() {
        fs::remove_file(path)
    } else {
        match fs::remove_dir(path) {
            Ok(_) => Ok(()),
            Err(ref e)
                if e.kind() == ErrorKind::DirectoryNotEmpty
                    || e.raw_os_error() == Some(libc::ENOTEMPTY) =>
            {
                for child in fs::read_dir(path)? {
                    let child = child?;
                    let child_path = child.path();
                    stacker::maybe_grow(4 * 1024, 16 * 1024, || recursive_remove(&child_path))?;
                }
                fs::remove_dir(path)
            }
            Err(e) => Err(e),
        }
    }
}

/// 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);
        if let Err(e) = fs::set_permissions(path, perms) {
            if e.kind() != ErrorKind::PermissionDenied {
                return Err(e);
            }
        }
    }
    Ok(())
}