[comp.unix.wizards] Making rm undoable

bernsten@phoenix.Princeton.EDU (Dan Bernstein) (03/03/89)

The lack of protection against completely unlinking UNIX files, methods
of setting up a ``trash can,'' etc., are frequent subjects of discussion
here. Consider the following idea: If every file has an extra link in a
special directory meant only for such links, then one doesn't even need
to change how rm works in order to preserve files; removing a file or
moving another on top of it will not destroy it.

To be more specific, in your home directory have a .Copies directory,
inaccessible except at select times. In .Copies are files COPY000000,
COPY000001, etc., and a file, linklist, that maps each copy number
to one or more pathnames. Every COPY file is originally a hard link to
a file outside .Copies; if a COPY file has only a single link, the
original must have been deleted.

The ``emptytrash'' command thus finds and unlink any COPY file with
a single link. ``setuptrash'' recursively finds all files within your
home directory (except in .Copies) and links each to a COPY file,
checking inode numbers to determine whether it's repeating itself.
``unrm'' will probably be the most difficult command to write, because
even if each of the above makes a valiant effort to maintain the linklist,
/bin/mv and the rename() call don't. Intelligently finding the correct
file could be quite a chore when someone just says ``unrm core.'' But
simply assuming that ``core'' means the current working directory slash
core would be good enough for most cases.

It is hoped that the extra links themselves within .Copies do not take
up enough file space that they must be removed often, for setuptrash is
naturally continuous from the previous setuptrash, doing no extra ln's.
(It would be dangerous to have two links within .Copies to the same
file, for then that file will never be removed automatically; it is
perhaps worth an extra program to check for this.) setuptrash doesn't
really need to be done in the foreground, so even with large directories
the user need not perceive the delay. emptytrash needs only search a
single (though humungous) directory, so it could probably be done in
every .logout.

Dealing with files moving around and directories moving around makes
automatic unrm operation more difficult, unless mv and mvdir are
rewritten to maintain linklist. If setuptrash were running continuously
this would not be a problem, but more realistically, it is for unrm to
figure out which file you mean and give you a choice if necessary.

A more serious problem is that a new file created after the latest
setuptrash (e.g., during a session if setuptrash is run by .login)
will not be preserved. It seems to me that this is just something to
warn the user about---that new files are considered in some sense
temporary until you run setuptrash.

Note that setting this up would change the perceived behavior of rm
slightly: at least on this system, rm asks for confirmation if the file
has any strange bits set, but only if the file has exactly one link left.
With all the .Copies COPY files running around, rm would never ask for
this confirmation. (Which makes sense as it's so easy to unrm.)

Mail me any corrections, questions, comments, or implementations. I will
post a summary in a week.

---Dan Bernstein, bernsten@phoenix.princeton.edu

bernsten@phoenix.Princeton.EDU (Dan Bernstein) (03/24/89)

I did promise a few weeks ago to summarize this in a week...

For those who missed the original posting, I was proposing that rather
than somehow convert every rm into an mv (unlink() into rename()), you
could prepare beforehand for losing files by making an extra link
somewhere safe with ln (link()). (That's what I meant to say, anyway.)
After all, link() followed by unlink() is almost the same as to rename().

I received several responses, each of which I summarize (in detail)
below. Skip to GENERAL THOUGHTS at the end if you don't want to read
eighty lines of discussion...

Stephen C. North (hector!north, ulysses!north, hector.homer.nh.att.com)
prefers the ``old alias "rm" trick'' for its simplicity. I'd say that
there is less to think about for the user, but often-noted problems
include: 1. If the file preservation is too invisible, you'll be too
careless under a system or shell without it. 2. How do you make sure
that all unlink()s use the alias? 3. How do you make sure that shell
scripts that really should delete a file actually use the real rm?
Nevertheless, the ``old alias "rm" trick'' does in practice prove quite
useful.

James R. Drinkwater (jd@csd4.milw.wisc.edu) sees the problem that every
file in the file system would take up an extra inode. He said that when
he wanted a file back, it was because of accidental deletion (e.g., rm a*
without remembering attn.important) rather than later deciding he needed
a file. He made a general proposal that the trash directory contain not
only the deleted files but also soft links to their original positions;
this is an excellent idea that applies to all trash directory methods.
He also proposed that deleted files could be dumped to tape in the end
rather than really erased; I think this would require superuser support
and also that everybody use the same trash method---jd proposed a global
trash directory. He pointed out that files should remain at least a fixed
(though user-defined) time; I'd say that if the trash is emptied
automatically, this had better be true, whil if the trash is emptied
manually, it shouldn't.

Christopher J. Calabrese (ulysses!cjc@research, cjc@ulysses.att.com)
also pointed out that ``you really want to emptytrash only files over
a certain age.'' He criticized my proposal, saying it would require
too much overhead and too many ``huge and unnecessary directories''
to maintain, as well as time; this is basically correct. He brought
up the problem of distinguishing between deleting when you want a
copy preserved and deleting when you don't. He said that people most
often delete files that they just created, and that for this reason
changing rm's behavior is better than my proposal.

Paul English (ileaf!io!speed!pme@eddie.mit.edu, pme@speed.io.uucp)
also prefers the idea of changing rm's behavior. He proposed that
rather than doing mv, safe rm should make a hard link to the file
and then remove the original file. This is, like my method, more
restricted than mv, which (on newer systems) can transfer files
across filesystems; forcing a physical transfer of a potentially
gigantic file is dubious, so I agree that an rm alias should
understand the necessity of staying within a filesystem.

Eli ? (echarne@orion.cf.uci.edu) mentioned that on his system, file
names beginning with a comma are automatically removed after a few
days, and that thus a safe way of removing files is to rename them
to ,-files. I've observed this elsewhere (# files are also commonly
removed); renaming files that way seems to me a very good solution.

Barry Shein (bzs@xenna.encore.com) also observed that you usually
delete what you're currently working on. He pointed out again the
fundamental problem of convincing all programs to unlink()
safely---except those shell scripts that should really erase the
file (aargh)... He proposed that if UNIX supported real event
signals (wake me up when a process does X, and pause that process
in the meantime) one could easily trap all unlink()s, and noted
that one can effectively do this by using NFS. He mentioned that
some editors and other utilities unlink and then recreate the file,
which deserves some discussion: The more common action (shell >,
vi, most other programs) is to simply write over the file. This
means that trapping unlink() won't stop most changes, and brings
to light the fact that version numbering in UNIX is a very very
tricky subject. What do you do if a process keeps a file open?
Do you say the version number increases on each write() (very
inefficient) or on each close()? How do you distinguish between
files that should not be version numbered and files that should,
and what about disk space? I am tempted to say that because of
the unified UNIX philosophy for dealing with everything as just
some type of file, version numbering is impossible---but I
remember hearing someone mention it is possible, and if I do
make my claim, Murphy will insure that I am publicly proven wrong.

Carl Witty (cwitty@csli.stanford.edu) wondered what mvdir is
(it's a general term covering whatever you have to do to move
a directory---on BSD, mv can do mvdir, within filesystems...).
He reminds us that ``the only cost for an extra hard link is
the space in the directory file, which is certainly manageable.''
Of course, this is the opposite view to jd, who worries about all
the extra inodes needed. I agree with cwitty; I've never seen more
than half the inodes used, on any filesystem.

Jerry Peek (jdpeek@rodan.acs.syr.edu) supports my idea and has been
looking forward to this summary. Well, now you have it.

Kevin Braunsdorf (ksb@j.cc.purdue.edu) said that at Purdue there are
three entombing schemes, of which the best one, maintained by
Matt Bradburn (mjb@staff.cc.purdue.edu), is a library redefining
unlink(), link(), and rename() to safer versions. ``It works.''

Larry Wall (lwall@devvax.jpl.nasa.gov, lwall@jpl-devvax.jpl.nasa.gov)
criticized my scheme since it doesn't work across filesystems, and
thus doesn't work over his account. He would rather see a trashcan
in each subdirectory; this is an interesting idea.

GENERAL THOUGHTS

The first person who reads this far wins a ... :-)

If UNIX were the type of system where version numbering were possible
(oops, I mean common, really I do) then the problem of file deletion
would be trivial. But version numbering is not possible (oops, common)
in UNIX.

Changing the low-down behavior of at least unlink() and possibly
link() and rename(), by (slow) NFS trickery or by a safe-rm library,
would completely solve the problem of files being accidentally
deleted. Perhaps the kernel should support this. However, this
leaves the problem of files that you really want deleted, or
the fact that this is not (yet?) the standard and thus programs
will be written for the old standard, or shell scripts that only
want a temporary file, or ... .

So it's not a simple problem. As for the idea of a more long-term
link() to make unlink() more safe, the responses have convinced me
that without kernel support this is not an appropriate use of
resources for all files. However, it would be useful as a
``preserve'' program that you explicitly invoke upon files that
you do not want deleted at any cost. preserve would not stop any
changes, and it would have to list all those programs that unlink()
and recreate files as ``preserve will not work with these, sorry,''
but it would prevent accidental deletion of the named files. So
you would just preserve your most important files, as a last resort.

There could be advantages to writing preserve as simply a process
that keeps the file open. This is a shorter-term solution, giving
Murphy a great excuse to crash the machine; but it would not require
an extra filesystem entry, and it would be trivial to include
automatic warnings every so often if the file is accidentally
removed. ``Mail from username... Subject: preserving "foo". To
recover "foo", type "unrm foo"...'' Or I suppose the file could
be re-instated in a trash directory by that process...

---Dan Bernstein, bernsten@phoenix.princeton.edu

ekrell@hector.UUCP (Eduardo Krell) (03/25/89)

In article <7360@phoenix.Princeton.EDU> bernsten@phoenix.Princeton.EDU (Dan Bernstein) writes:

>If UNIX were the type of system where version numbering were possible
>(oops, I mean common, really I do) then the problem of file deletion
>would be trivial. But version numbering is not possible (oops, common)
>in UNIX.

It's not common, but it has been done.

Dave Korn and/or I are going to present a paper called "The 3-D File System"
at the next summer Usenix conference in Baltimore. If you listen to
the presentation or read the paper in the proceedings, I think you'll
realize it adds version numbering and more to the Unix file system.
    
Eduardo Krell                   AT&T Bell Laboratories, Murray Hill, NJ

UUCP: {att,decvax,ucbvax}!ulysses!ekrell  Internet: ekrell@ulysses.att.com

jik@athena.mit.edu (Jonathan I. Kamens) (03/26/89)

In article <7360@phoenix.Princeton.EDU> bernsten@phoenix.Princeton.EDU
(Dan Bernstein) writes:

>For those who missed the original posting, I was proposing that rather
>than somehow convert every rm into an mv (unlink() into rename()), you
>could prepare beforehand for losing files by making an extra link
>somewhere safe with ln (link()). (That's what I meant to say, anyway.)
>After all, link() followed by unlink() is almost the same as to rename().

(I had missed the original posting.... Oh, well :-)

There are two problems with using link() or with keeping a file open
in order to prevent it from being lost.

Both of these mechanisms are useless under NFS (and possibly other
protocols, but I only know for sure about NFS) -- soft links are
meaningless because an NFS filesystem can be mounted anywhere and
paths are therefore meaningless, and hard links won't work very well
either because the trash directory has to be on the same filesystem as
the deleted file.  If I mount "/foo/bar/baz" from another machine onto
"/mnt" on my machine, and I use an rm that uses a link() call to
delete a file, where is it going to put the link?

Keeping a file open also does nothing under NFS -- there's no state,
so the NFS server doesn't realize that the file is open and it will go
away.  I hear that the next version of NFS supplies the capability to
do file locking, but I don't know if this includes keeping track of
busy files.

This discussion came up some time last year in comp.unix.wizards.  At
that point, I was working on a suite of file-deletion (and undeletion)
utilities for Project Athena.  That set of programs has been finished
and is going in the next Project Athena software release.  The
particular attractiveness of the "delete" program is that it can
exactly emulate the behavior of rm and rmdir if that is desired, with
the result that you can do a couple of aliases and then never realize
that you're using delete (unless you need to undelete something)!

If people are interested in getting the sources to the programs
(delete, undelete, expunge, purge, and lsdel), I can probably send
people sources (after I check with my boss and get a copy of the
copyright notice that has to appear at the top of each file :-).  It's
only been tested on BSD, but I assume that it will work with minimal
modification on other Unix platforms as well.

Jonathan Kamens			              USnail:
MIT Project Athena				410 Memorial Drive, No. 223F
jik@Athena.MIT.EDU				Cambridge, MA 02139-4318
Office: 617-253-4261			      Home: 617-225-8218

john@anasaz.UUCP (John Moore) (03/26/89)

In article <10113@bloom-beacon.MIT.EDU> jik@athena.mit.edu (Jonathan I. Kamens) writes:
]If people are interested in getting the sources to the programs
](delete, undelete, expunge, purge, and lsdel), I can probably send
]people sources (after I check with my boss and get a copy of the

Better yet, why not post them to unix.sources?
-- 
John Moore (NJ7E)           mcdphx!anasaz!john asuvax!anasaz!john
(602) 861-7607 (day or eve) 
The opinions expressed here are obviously not mine, so they must be
someone else's. :-)

guy@auspex.UUCP (Guy Harris) (03/27/89)

>Keeping a file open also does nothing under NFS -- there's no state,
>so the NFS server doesn't realize that the file is open and it will go
>away.  I hear that the next version of NFS supplies the capability to
>do file locking, but I don't know if this includes keeping track of
>busy files.

I'm not sure what you mean by "the next version of NFS".  The *current*
version of the ONC/NFS source as distributed by Sun includes a network
service to perform byte-span locking (which subsumes file locking) over
the network; said service is different from the file access service that
NFS refers to in the strict sense.  I think the previous version of the
ONC/NFS source had it as well. 

The next version of the NFS *protocol* doesn't include support for
byte-span locking; that's still left up to a separate service and
protocol.

Since the locking is a separate service, and since it's accessed on
typical UNIX implementations only through the "fcntl" system call
(POSIX/SVID-style), neither Version 2 (the current version of the
protocol) nor Version 3 (the next version of the protocol) of the NFS
service keeps track of files that a client has open, so no, this doesn't
include keeping track of busy files.

hedrick@geneva.rutgers.edu (Charles Hedrick) (03/27/89)

> soft links are
> meaningless because an NFS filesystem can be mounted anywhere and
> paths are therefore meaningless

There are two ways to make soft links work across NFS.  One is to use
a mounting convention that gives the same path names on all systems.
We often use /machinename as the top level directory, even on the
machine itself.  So a link to /machine/usr/bin/foo works anywhere.
The other is for all of your symbolic links to be relative.  E.g.  if
you want /usr/bin/foo to be a link to /usr/ucb/bar, define the link as
../ucb/bar.  If you want it to be a link to /bin/bar, define the link
as ../../bin/bar.  This presupposes that you mount all file systems
from a given system at the same point in the local file system, of
course.

> I hear that the next version of NFS supplies the capability to
> do file locking, but I don't know if this includes keeping track of
> busy files.

File locking (actually System V record locking) has been present in
NFS on Suns since at least release 3.2.  It appears that the
implementation in 4.0.2 actually works.  (4.0.1 is the current
released version, but you can get 4.0.2 of the lock daemon from the
Sun software support people.)  NFS does manage to keep files that are
still open around.  Try the following:
   tail -f foo &
   rm foo
   ls .nfs*
You'll see that foo has been renamed to .nfsXXX.  Now if you kill the
tail, .nfsXXX will go away.  I'm not sure quite how that interacts
with statelessness.  It's possible that if you open a file, remove it,
and then crash before closing it, that the .nfsXXX file will stay on
the the file server.  I haven't looked at the code that carefully.

Despite all the comments about how NFS violates "Unix semantics",
we've not run into anything that failed across NFS, aside from bugs in
implementations.

jik@athena.mit.edu (Jonathan I. Kamens) (03/27/89)

In article <Mar.26.19.51.41.1989.8372@geneva.rutgers.edu>
hedrick@geneva.rutgers.edu (Charles Hedrick) writes:
>
>There are two ways to make soft links work across NFS.  One is to use
>a mounting convention that gives the same path names on all systems.
>We often use /machinename as the top level directory, even on the
>machine itself.  So a link to /machine/usr/bin/foo works anywhere.
>The other is for all of your symbolic links to be relative.  E.g.  if
>you want /usr/bin/foo to be a link to /usr/ucb/bar, define the link as
>../ucb/bar.  If you want it to be a link to /bin/bar, define the link
>as ../../bin/bar.  This presupposes that you mount all file systems
>from a given system at the same point in the local file system, of
>course.

Both of these will work fine when you're in a relatively small system
and NFS is only exported to a small number of hosts.

However, when you have more than 1000 workstations and more machines
of other types, all of which can NFS mount nearly any directory off of
any fileserver on campus on any mount point, it stops being feasible
to force directories always to get mounted in the same place and
always at the same level from the root.  In fact, the capability to
mount in different locations is used a lot here at Athena.

Both of the points you made will help make links *more* reliable, but
none of them will make the links completely reliable, and (in my
opinion) that's what you need when you are going to be using the links
for file removal.

When we first started going through design reviews for delete et al, a
system of ".deleted" directories storing symbolic links to deleted
files was proposed.  However, as we talked more and more about it, it
degenerated into something like this: "When a file is deleted, it is
renamed from foo to .#foo so that it becomes invisible to the user and
will be cleaned up in the nightly filesystem sweeps after a few days.
Then, the delete program starts walking up the directory tree until it
encounters either (a) a directory named .deleted or (b) the top of the
filesystem (NFS or whatever); while doing this it keeps track of how
many levels up it's gone and all of the directories it's transversed
up in order to be able to do a link back to the deleted file.  If it
gets to the top of the filesystem and doesn't find a .deleted
directory, it tries to create one.  If it fails, it starts walking
back *down* the directory chain until it finds a directory in which it
can create a .deleted directory, coming, at worst, back to the
original directory from which it started.  By this point, it should
have been able to create a .deleted directory, and it makes a link in
that directory to the deleted file."

If you think about it, you'll realize that this is the only way to
guarantee that a link will be created when working with NFS, unless
you have all filesystems mounted on all machines at all times,
something which we most definitely do not.

So, as you can see there are certain things that are *not* feasible
with NFS in certain environments.

Jonathan Kamens			              USnail:
MIT Project Athena				410 Memorial Drive, No. 223F
jik@Athena.MIT.EDU				Cambridge, MA 02139-4318
Office: 617-253-4261			      Home: 617-225-8218

P.S. Oh, so that's what all those .nfsXXX files are from :-)

jpn@genrad.uucp (John P. Nelson) (03/28/89)

>NFS does manage to keep files that are still open around.  Try the following:
>   tail -f foo &
>   rm foo
>   ls .nfs*
>You'll see that foo has been renamed to .nfsXXX.  Now if you kill the
>tail, .nfsXXX will go away.

This is only true if the 'rm' and the 'tail' occur on the same machine.
Try it with two different machines:  No .nfsXXX file is created.  You get
assorted bizzare behaviors when this happens.  Actually,  I discovered this
because we had an application where this behavior was very annoying.

>I'm not sure quite how that interacts
>with statelessness.  It's possible that if you open a file, remove it,
>and then crash before closing it, that the .nfsXXX file will stay on
>the the file server.

Yes, it does.

>Despite all the comments about how NFS violates "Unix semantics",
>we've not run into anything that failed across NFS, aside from bugs in
>implementations.

We had a ascii "database" with both readers and writers:  The readers
simply open the file, but the writers use locking and always create a
new file, then rename the new file as the old file (expecting old readers
to keep their descriptors to the old file).  Unfortunately, this doesn't
work under NFS (at least not in sun 3.x:  I don't know about sun 4.x).

     john nelson

UUCP:	{decvax,mit-eddie}!genrad!jpn
smail:	jpn@lightning.genrad.com

dgk@ulysses.homer.nj.att.com (David Korn[drew]) (03/29/89)

I implemented a version of unlink(name) that did a rename() to a file with
the same name in a sub-directory called .trashcan, if this directory
exists.  I modified rm to use this unlink, and I modified rmdir to
consider a directory empty if the only thing it it was a .trashcan
directory.

This scheme has the advantage of having the link() always on the
same file system.  Also, only files that have a .trashcan subdirectory
are saved.  Thus, you can have a single rm command that works on
directories that you want files saved, and to remove trash from
the .trashcan directory.

Of course, I added a line to cron to clean out .trashcan directories
every day.