Oto jak rm -rf dir
działa:
- Otwiera się
dir
i wyświetla jego zawartość.
- Dla każdego wpisu, jeśli jest to katalog, powtórz dla niego ten sam proces, jeśli nie jest, wywołaj
unlink
go.
Jeśli możesz, na liście katalogów, najpierw zwrócić specjalną nazwę pliku, a jeśli możesz spowodować, że proces wykonujący polecenie unlink
dla tego pliku umrze, rozwiąże to problem. Można to zrobić za pomocą systemu plików bezpieczników.
Na przykład, możesz zaadaptować loopback.pl
przykład z modułu Perla Fuse, który po prostu implementuje fikcyjny system plików, który jest po prostu przejściem do prawdziwego systemu plików poniżej (patrz także łatka poniżej):
- przy wyświetlaniu katalogu, jeśli zawiera on wpis o nazwie
.{{do-not-delete}}.
, dołącz listę wpisów do dwóch plików: .{{do-not-delete}}!error
i.{{do-not-delete}}!kill
- podczas próby
unlink
przejścia do pierwszego zwróć EPERM
kod, aby rm
wyświetlał komunikat o błędzie
- podczas próby
unlink
drugiego proces zostaje zabity.
$ 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
Oto łatka do zastosowania na tym loopback.pl
przykładzie jako dowód koncepcji:
--- 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
dorm -i
:> -i każdy wiersz przed uprowadzeniem lub> -I szybka raz przed usunięciem więcej niż trzech plików, lub przy usuwaniu rekurencyjnie. Mniej uciążliwy niż -i, a jednocześnie zapewnia ochronę przed większością błędów. Możesz je w dowolnym momencie zapisać za pomocą innych flag.