[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

RFC: preventing accidental deletion of system directories



(Please CC me on your reply)

Having recently deleted my /usr/lib by mistake (and gone through the pain of
reinstalling all of my packages), I wrote a little Perl script which I have
now aliased to "rm" in my .bashrc.

Basically, the wrapper (see attached file) has a blacklist which contains
directories like /usr/lib, /home, /etc and removes those before passing its
arguments to the real 'rm' command.

I'm probably not the only person to have made this mistake and who wants to
avoid doing it again.  So I'm thinking of turning it into something that's
useful to other people (probably packaging it in some form).  If you have
some ideas regarding things like:

- how to get this script to be picked up before 'rm' in the PATH (including
  when using sudo) or whether it should be an alias in all of the shell
  global config files (like /etc/bash.bashrc)

- where to find a good list of directories which should never be deleted

- how to effectively disable it if one really wants to delete a system dir
  (for an alias, '\rm' does the trick, for a command in the path, maybe an
  environment variable?)

- ways to detect directories expressed like  "../../../usr/lib/../../usr/bin"

- any other comments/suggestions you may have about the idea or the script

Francois

P.S. I realize that 'rm' is a low-level command which should do what it's
told, but the reality is that a lot of people use it directly on a daily
basis and can accidently hose their system.  I don't want to implement a
"command-line trashcan", but I'm looking for a way to prevent me from doing
things I should never ask for (like 'rm -rf /usr/lib/').
#!/usr/bin/perl

=head1 NAME

safe-rm - safety wrapper around the rm command

=head1 SYNOPSIS

safe-rm [ ... ]
(same arguments as rm

=head1 DESCRIPTION

safe-rm is a wrapper around 'rm' that checks the given arguments
against a blacklist of directories which should never be removed.

It is meant to be used instead of rm by aliasing rm to it.  For
example, in bash:
  alias rm='safe-rm'

=head1 AUTHOR

Francois Marier <francois@debian.org>

=head1 SEE ALSO

rm(1)

=head1 COPYRIGHT

Copyright (C) 2007 Francois Marier <francois@debian.org>

safe-rm is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 3 of the
License, or (at your option) any later version.

safe-rm is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with Email-Reminder; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.

=cut

use warnings;
use strict;

my %protected_dirs = ( '/bin' => 1,
                       '/boot' => 1,
                       '/dev' => 1,
                       '/etc' => 1,
                       '/home' => 1,
                       '/initrd' => 1,
                       '/lib' => 1,
                       '/proc' => 1,
                       '/root' => 1,
                       '/sbin' => 1,
                       '/sys' => 1,
                       '/usr' => 1,
                       '/usr/bin' => 1,
                       '/usr/include' => 1,
                       '/usr/lib' => 1,
                       '/usr/local' => 1,
                       '/usr/local/bin' => 1,
                       '/usr/local/include' => 1,
                       '/usr/local/sbin' => 1,
                       '/usr/local/share' => 1,
                       '/usr/sbin' => 1,
                       '/usr/share' => 1,
                       '/usr/src' => 1,
                       '/var' => 1,
    );

my @allowed_args = ();
for (my $i = 0; $i <= $#ARGV; $i++) {
    my $pathname = $ARGV[$i];

    # Normalize the pathname
    my $normalized_pathname = $pathname;
    if ($pathname =~ m|^(.*?)/+$|) {
        # Trim leading slashes
        $normalized_pathname = $1;
    }
    # TODO: Get the absolute name (to catch things like ../../usr/lib/ from /usr/lib/ or ./etc from /)

    # Check against the blacklist
    if (exists($protected_dirs{$normalized_pathname})) {
        print STDERR "Skipping $pathname\n";
    } else {
        push @allowed_args, $pathname;
    }
}

# Run rm if there is at least one argument left
my $errcode = 0;
if (@allowed_args > 0) {
    my $status = system('rm', @allowed_args);
    $errcode = $status >> 8;
}
exit $errcode;

Reply to: