9

I just lost some data in folder A which was inside folder B after doing rm -rf B. Before I could realize what have I done, it was all over. Now a lesson is learnt, I wish to make some of my folder idiot-proof to avoid a next time when I do something similar and want to kill myself.

One way I can think of is to write a bash function and alias it to rm. This function will look into each sub-folder for a hidden file such as .dontdelete. When found, it would ask if I really want to continue. I can not make is write-protected since there is a process which constantly write to this folder. Is there any better way to do it?

5
  • 2
    Did you tried alias rm to rm -i: > -i prompt before every removal or > -I prompt once before removing more than three files, or when removing recursively. Less intrusive than -i, while still giving protection against most mistakes You can owerwrite those with other flags at any time.
    – IBr
    Commented Jun 1, 2013 at 9:33
  • 2
    Check out safe-rm
    – sr_
    Commented Jun 1, 2013 at 10:21
  • There are about a dozen ways to do it. You're going to need to go into more detail regarding your environment. Commented Jun 1, 2013 at 11:18
  • 1
    possible duplicates: How to forbid the execution of /bin/rm -f *? & Configure rm command
    – slm
    Commented Jun 1, 2013 at 11:49
  • Another idea is to alias it to a function that just moves it to a particular folder and then create a cronjob that runs tmpwatch to delete files out of this folder every hour.
    – Bratchley
    Commented Jun 1, 2013 at 12:07

5 Answers 5

14

In researching your question I came across this technique which might help you in the future.

You can apparently touch a file in the directory like so:

touch -- -i

Now when you run the command rm -fr * in a directory where the -i is present you'll be presented with the interactive prompt from rm.

$ ls
file1  file2  file3  file4  file5  -i

$ rm -fr *
rm: remove regular empty file `file1'? n
rm: remove regular empty file `file2'? n
rm: remove regular empty file `file3'? n
rm: remove regular empty file `file4'? n
rm: remove regular empty file `file5'? n

The same thing can be achieved by just leaving an alias in place for rm to always do rm -i. This can get annoying. So often what I've seen done is to have this alias in place, and then to disable it when you really want to delete without being prompted.

alias rm='rm -i'

Now in directories you'll be greeted like this:

$ ls
file1  file2  file3  file4  file5

$ rm -r *
rm: remove regular empty file `file1'?

To override the alias:

$ \rm -r *

This still doesn't stop a rm -fr however. But it does provide you with some protection.

References

3
  • 1
    This is a nice, elegant solution which works well if you only want/need to protect a small set of directories. And I may be old-fashioned but I still live under the impression that if you say --force to something, you really mean it.
    – user
    Commented Jun 1, 2013 at 21:18
  • Also note that rm -I (upper case i) might be useful in an alias, as it is a little less intrusive (according to the man page, only prompts if you remove more than three files or recursively).
    – user
    Commented Jun 1, 2013 at 21:20
  • Note that the touch ./-i trick only works with GNU rm and only if the POSIXLY_CORRECT variable is not set (POSIX commands don't recognise options after arguments). Commented Jun 3, 2013 at 22:03
5

Many possibilities:

  • alias rm='rm -i' - rm will ask - unless you specify -f...
  • chmod -w dir - protects files directly in that directory.
  • chattr +i if you really mean it
  • write your own wrapper around rm
  • etc...

But a better way is probably to have a good backup and keep important data in some kind of version control (like git), which brings a lot of other advantages, too.

3
  • 1
    Came here to mention this. I would suggest chattr +i to make it entirely safe.
    – JZeolla
    Commented Jun 1, 2013 at 14:09
  • I already have rm aliased to rm -i but as one would expect, it does not work with -f option. I use git for many other purpose, but what if I accidentally do rm -rf * in git too?
    – Dilawar
    Commented Jun 3, 2013 at 7:02
  • With git, if you remove just a subdirectory you can easily restore it from your local repository. If you remove the whole repository you can simply clone it again - given you pushed it to some other place before.
    – michas
    Commented Jun 3, 2013 at 7:42
1

Use a version control software like git to encapsulate your projects.

As long as you don't delete a whole project, you would have to purposefully type rm -rf .* to remove the .git directory and lose all the data needed for a rollback.

This has the added benefit that you can push backups of your stuff to a remote server like github or bitbucket.

1

Here's how rm -rf dir works:

  1. It opens dir, and list its content.
  2. For each entry, if it's a directory, repeat the same process for it, if it's not, call unlink on it.

If you could, for the directory listing, return a special filename first, and if you could cause a process doing an unlink on that file to die, that would solve the problem. That could be done using a fuse filesystem.

For instance, you could adapt the loopback.pl example from the perl Fuse module which just implements a dummy filesystem that is just a pass-through to a real file system underneath like so (see also patch below):

  • when listing a directory, if it contains an entry named .{{do-not-delete}}., prepend the list of entries with two files: .{{do-not-delete}}!error and .{{do-not-delete}}!kill
  • when trying to unlink the first one, return the EPERM code so that rm displays an error message
  • when trying to unlink the second one, the process gets killed.

$ 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

Here a patch to apply on top of that loopback.pl example as a proof of concept:

--- 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',
-1

I have created a script to make this task easier. The shell script modifies the read/write permission and chattr flag of a given list of folders interactively, without asking root password. You can download the zip file from the link given below.

https://drive.google.com/file/d/0B_3UYBZy2FVsMVpBSWdSWnFBYk0/edit?usp=sharing

I have also included installation script to make the setup easier. Instructions are enclosed in the zip file.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .