Вот как это rm -rf dir
работает:
- Он открывается
dir
и перечисляет его содержимое.
- Для каждой записи, если это каталог, повторите тот же процесс для него, если это не так, вызовите
unlink
его.
Если бы вы могли, для списка каталогов сначала вернуть специальное имя файла, и если бы вы могли вызвать процесс, выполняющий unlink
этот файл, умереть, это решило бы проблему. Это можно сделать с помощью файловой системы fuse.
Например, вы можете адаптировать loopback.pl
пример из модуля Perl Fuse, который просто реализует фиктивную файловую систему, которая является просто проходом к реальной файловой системе, как показано ниже (см. Также патч ниже):
- при перечислении каталога, если он содержит запись с именем
.{{do-not-delete}}.
, добавьте список записей с двумя файлами: .{{do-not-delete}}!error
и.{{do-not-delete}}!kill
- при попытке
unlink
первого верните EPERM
код так, чтобыrm
отобразилось сообщение об ошибке
- при попытке
unlink
второго, процесс убивается.
$ ls -Ff dir/test
./ .{{do-not-delete}}. foo/ ../ bar
$ ./rm-rf-killer dir
$ ls -Ff dir/test
.{{do-not-delete}}!error .{{do-not-delete}}!kill ./ .{{do-not-delete}}. foo/ ../ bar
$ rm -rf dir/test
rm: cannot remove `dir/test/.{{do-not-delete}}!error': Operation not permitted
zsh: terminated rm -rf dir/test
$ ls -Ff dir/test
.{{do-not-delete}}!error .{{do-not-delete}}!kill ./ .{{do-not-delete}}. foo/ ../ bar
Вот патч для применения поверх этого loopback.pl
примера в качестве доказательства концепции:
--- loopback.pl 2013-06-03 22:35:00.577316063 +0100
+++ rm-rf-killer 2013-06-03 22:33:41.523328427 +0100
@@ -7,2 +7,4 @@
my $has_threads = 0;
+my $flag = ".{{do-not-delete}}";
+
eval {
@@ -42,3 +44,4 @@
-use blib;
+#use blib;
+use File::Basename;
use Fuse;
@@ -49,3 +52,3 @@
-my %extraopts = ( 'threaded' => 0, 'debug' => 0 );
+my %extraopts = ( 'threaded' => 0, 'debug' => 0, 'mountopts' => 'nonempty' );
my($use_real_statfs, $pidfile);
@@ -64,3 +67,7 @@
-sub fixup { return "/tmp/fusetest-" . $ENV{LOGNAME} . shift }
+sub fixup {
+ my $f = shift;
+ $f =~ s#(/\Q$flag\E)!(error|kill)$#$1.#s;
+ return ".$f";
+}
@@ -78,3 +85,9 @@
}
- my (@files) = readdir(DIRHANDLE);
+ my @files;
+
+ while (my $f = readdir(DIRHANDLE)) {
+ unshift @files, "$flag!error", "$flag!kill"
+ if ($f eq "$flag.");
+ push @files, $f;
+ }
closedir(DIRHANDLE);
@@ -121,3 +134,12 @@
sub x_readlink { return readlink(fixup(shift)); }
-sub x_unlink { return unlink(fixup(shift)) ? 0 : -$!; }
+sub x_unlink {
+ my $f = shift;
+ if (basename($f) eq "$flag!error") {return -EPERM()}
+ if (basename($f) eq "$flag!kill") {
+ my $caller_pid = Fuse::fuse_get_context()->{"pid"};
+ kill("TERM", $caller_pid);
+ return -EPERM();
+ }
+ return unlink(".$f") ? 0 : -$!;
+}
@@ -203,3 +225,2 @@
sub daemonize {
- chdir("/") || die "can't chdir to /: $!";
open(STDIN, "< /dev/null") || die "can't read /dev/null: $!";
@@ -236,2 +257,3 @@
+chdir($mountpoint) or die("chdir: $!");
daemonize();
@@ -239,3 +261,3 @@
Fuse::main(
- 'mountpoint' => $mountpoint,
+ 'mountpoint' => '.',
'getattr' => 'main::x_getattr',
rm
дляrm -i
:> -i строка перед каждым удаление или> -I подскажи один раз перед удалением более трех файлов, или при удалении рекурсивно. Менее навязчив, чем -i, но при этом обеспечивает защиту от большинства ошибок. Вы можете в любое время переписать их с другими флагами.