New feature request from blog comments
[safe-rm.git] / safe-rm
blob3e17a079d88a9d41c2248f2deefd530633b602c3
1 #!/usr/bin/perl
3 =head1 NAME
5 safe-rm - wrapper around the rm command to prevent accidental deletions
7 =head1 SYNOPSIS
9 safe-rm [ ... ]
10 (same arguments as rm)
12 =head1 DESCRIPTION
14 safe-rm prevents the accidental deletion of important files by
15 replacing rm with a wrapper which checks the given arguments against a
16 configurable blacklist of files and directories which should never be
17 removed.
19 Users who attempt to delete one of these protected files or
20 directories will not be able to do so and will be shown a warning
21 message instead.
23 safe-rm is meant to replace the rm command so you can achieve this by
24 putting a symbolic link with the name "rm" in a directory which sits
25 at the front of your path. For example, given this path:
27 PATH=/usr/local/bin:/bin:/usr/bin
29 You could create the following symlink:
31 ln -s /usr/local/bin/safe-rm /usr/local/bin/rm
33 =head1 CONFIGURATION FILES
35 Protected paths can be set both at the site and user levels.
37 Both of these configuation files can contain a list of important files
38 or directories (one per line):
40 /etc/safe-rm.conf
41 ~/.safe-rm
43 If both of these are empty, a default list of important paths will be
44 used.
46 =head1 AUTHOR
48 Francois Marier <francois@debian.org>
50 =head1 SEE ALSO
52 rm(1)
54 =head1 COPYRIGHT
56 Copyright (C) 2008 Francois Marier <francois@debian.org>
58 This program is free software: you can redistribute it and/or modify
59 it under the terms of the GNU General Public License as published by
60 the Free Software Foundation, either version 3 of the License, or
61 (at your option) any later version.
63 This program is distributed in the hope that it will be useful,
64 but WITHOUT ANY WARRANTY; without even the implied warranty of
65 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
66 GNU General Public License for more details.
68 You should have received a copy of the GNU General Public License
69 along with this program. If not, see <http://www.gnu.org/licenses/>.
71 =cut
73 use warnings;
74 use strict;
75 use Cwd 'realpath';
77 my %default_protected_dirs = ( '/bin' => 1,
78 '/boot' => 1,
79 '/dev' => 1,
80 '/etc' => 1,
81 '/home' => 1,
82 '/initrd' => 1,
83 '/lib' => 1,
84 '/proc' => 1,
85 '/root' => 1,
86 '/sbin' => 1,
87 '/sys' => 1,
88 '/usr' => 1,
89 '/usr/bin' => 1,
90 '/usr/include' => 1,
91 '/usr/lib' => 1,
92 '/usr/local' => 1,
93 '/usr/local/bin' => 1,
94 '/usr/local/include' => 1,
95 '/usr/local/sbin' => 1,
96 '/usr/local/share' => 1,
97 '/usr/sbin' => 1,
98 '/usr/share' => 1,
99 '/usr/src' => 1,
100 '/var' => 1,
103 my %protected_dirs = ();
105 # Read in system-wide configuration file
106 if (open(SYSTEMWIDE, '<', '/etc/safe-rm.conf')) {
107 while (<SYSTEMWIDE>) {
108 chomp;
109 $protected_dirs{$_} = 1;
111 close(SYSTEMWIDE);
114 # Read in user configuration file
115 my $homedir = $ENV{HOME} || '';
116 if (open(USERCONFIG, '<', "$homedir/.safe-rm")) {
117 while (<USERCONFIG>) {
118 chomp;
119 $protected_dirs{$_} = 1;
121 close(USERCONFIG);
124 if (0 == scalar keys %protected_dirs) {
125 %protected_dirs = %default_protected_dirs;
128 my @allowed_args = ();
129 for (my $i = 0; $i <= $#ARGV; $i++) {
130 my $pathname = $ARGV[$i];
132 # Normalize the pathname
133 my $normalized_pathname = $pathname;
134 if ($normalized_pathname =~ m|/| or -e "$normalized_pathname") {
135 # Convert to an absolute path (e.g. remove "..")
136 $normalized_pathname = realpath($normalized_pathname);
137 $normalized_pathname = $pathname unless $normalized_pathname;
139 if ($normalized_pathname =~ m|^(.+?)/+$|) {
140 # Trim trailing slashes
141 $normalized_pathname = $1;
144 # Check against the blacklist
145 if (exists($protected_dirs{$normalized_pathname})) {
146 print STDERR "Skipping $pathname\n";
147 } else {
148 push @allowed_args, $pathname;
152 # Run rm if there is at least one argument left
153 my $errcode = 0;
154 if (@allowed_args > 0) {
155 my $real_rm = '/bin/rm';
156 if (realpath($real_rm) eq realpath($0)) {
157 die 'Cannot find the real "rm" binary';
159 my $status = system($real_rm, @allowed_args);
160 $errcode = $status >> 8;
162 exit $errcode;