Skip to content

rm -rf ./ and variants silently delete current directory contents #9749

@sylvestre

Description

@sylvestre

Component

rm -rf

Description

An issue with rm -rf: when deleting with rm -rf ., consistent with GNU rm, it refuses to delete the current directory, reports an error directly, and does not delete any contents.

❯ rm -rf .
rm: refusing to remove '.' or '..' directory: skipping '.'

However, when using rm -rf ./ or rm -rf .///, it reports an error as if the deletion failed (it should be consistent with rm -rf ., refusing directly without deleting anything), but in reality the contents of the current directory have already been deleted:

rm: cannot remove './': Invalid input

This is because the clean_trailing_slashes function collapses ./// into "./":

fn clean_trailing_slashes(path: &Path) -> &Path {
    ...
        // Checks if element at the end is a '/'
        if path_bytes[idx] == dir_separator {
            for i in (1..path_bytes.len()).rev() {
                if path_bytes[i - 1] != dir_separator {
                    idx = i;
                    break;
                }
            }
            #[cfg(unix)]
            return Path::new(OsStr::from_bytes(&path_bytes[0..=idx]));
            ...
        }
    }
    path
}

However, the path_is_current_or_parent_directory function only matches ".", "..", etc. It does not match equivalent paths like "./" or "../".

/// Checks if the path is referring to current or parent directory, if it is referring to current or any parent directory in the file tree e.g '/../..', '../..'
fn path_is_current_or_parent_directory(path: &Path) -> bool {
    let path_str = os_str_as_bytes(path.as_os_str());
    let dir_separator = MAIN_SEPARATOR as u8;
    if let Ok(path_bytes) = path_str {
        return path_bytes == ([b'.'])
            || path_bytes == ([b'.', b'.'])
            || path_bytes.ends_with(&[dir_separator, b'.'])
            || path_bytes.ends_with(&[dir_separator, b'.', b'.'])
            || path_bytes.ends_with(&[dir_separator, b'.', dir_separator])
            || path_bytes.ends_with(&[dir_separator, b'.', b'.', dir_separator]);
    }
    false
}

Test / Reproduction Steps

# Provide commands and setup to reproduce

Impact

This results in the contents of the current directory being recursively deleted.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions