bin/116477: rm behaves unexpectedly when using -r and relative paths

Bruce Evans brde at optusnet.com.au
Thu Sep 20 15:50:11 PDT 2007


On Wed, 19 Sep 2007, Justin Mitchell wrote:

>> Description:
> This is a bug with the 'rm' commandline utility that can cause data
> loss.

This is rm doing what you asked it to.

> When using 'rm -r' with relative paths and a trailing slash rm can
> delete the wrong item, or no item at all. Here is an example of rm
> deleting the wrong file.
>
> mac:~ $ mkdir tmp
> mac:~ $ cd tmp
> mac:~/tmp $ mkdir test
> mac:~/tmp $ ln -s test test2
> mac:~/tmp $ ls -l
> total 8
> drwxr-xr-x   2 justin  justin  68 Aug 10 01:41 test
> lrwxr-xr-x   1 justin  justin   4 Aug 10 01:42 test2 -> test
> mac:~/tmp $ cd ..
> mac:~ $ rm -r tmp/test2/
> mac:~ $ ls -l tmp
> total 8
> lrwxr-xr-x   1 justin  justin  4 Aug 10 01:42 test2 -> test
>
> Note that if you do you not use the trailing slash in 'rm -f tmp/
> test2/' that it will behave normally. It will also behave normally if
> you do not 'cd' to another directory.
>
> I confirmed this with another individual running OS X, but I do
> not know the version. For comparison, I tested this with an older
> version of Redhat, and it worked fine.

gnu/Linux rm or Linux could easily have a bug like that, but at least
the version in an old FreeBSD package (linux_base-8-8.0_4 with bin/rm
dated Sep 2 2002) works correctly under FreeBSD -- it removes the
directory.

> I submitted this bug report to Apple, but they said "This is how FreeBSD operates and rm is based on their sources."

This is how symbolic links work.  The trailing slash causes the symlink
to be followed.  POSIX spec: trailing slashes shall be resolved as if
a single dot character were appended to a pathname.  Hmm, this seems
to require a small part behaviour that you want -- the pathname ends
up with a trailing dot no matter where in the process of pathname
resolution that the dot is added.  Then rm(1)'s and/or rmdir(2)'s
(bogus) restriction on removing "." and ".." comes into play and
according to POSIX should prevent the removal of the directory pointed
to by the symlink (but not the contents of the directory -- see below).

What FreeBSD does is follow "test2/" to resolve to "test".  It doesn't
add the dot as required by POSIX.  POSIX doesn't seem to say clearly
when the dot should be added, but it would have to be added at the
beginning of pathname resolution to be useful, since then it would
cause "test2/" to be resolved to "test/.", while adding it after
resolving to "test" would give the name "test." where the dot doesn't
have anything to do with the directory entry "." and just messes up
the name "test".

After resolving to "test/.", rmdir() on the directory must fail due
to rmdir()'s bogus restriction on removing ".".  This restriction is
bogus because it is only by name -- you can easily rmdir() your current
directory (if it is empty) by spelling its name differently, e.g., as
the absolute path to th current directory, but you can't dubdirectories
if you spell their name with a ".".  Before I started writing this mail,
I thought that you could use the spelling of "<symlink>/" to remove
current and other directories, but the POSIX spec prevents that.

However, the POSIX spec doesn't affect removing the directories entire
contents using "rm -r <symlink>/".  rm has its own bogus restriction
on removing "." (and "..") by name.  This restriction doesn't apply
here since there is no dot in "<symlink>/".  The pathname resolution
that POSIX requires to add the dot occurs entirely in the kernel.

Thus the expected behaviour for a POSIX "rm -r test2/" is:

     follow the symlink to get "test/."
     remove the contents of "test/."
        rm will probably just apply rm -r to each file in "test/.".
        I verified that the Linux rm from 2002 when run under FreeBSD
        just chdir()'s into any subdirs to remove them recursively.
     fail with errno EINVAL trying to rmdir() "test/."
        (the Linux rm from 2002 just tries to remove "test/", and this
        succeeds under FreeBSD)
     don't remove the symlink

     This is not what you want -- it removes everything except the one
     thing that is easy to recover.

and the expected behaviour for a FreeBSD "rm -r test2/" is:

     same as above, except it won't fail on the top-level rmdir().

I know too much about this because I broke following of symlinks for
"<symlink>/" when fixing the handling of trailing slashes in general.

Some other cases of interest:

(1) when test2 is symlink to "test" and "test" doesn't exist at the start
     of each of the following operations:

     FreeBSD mkdir test2/    succeeds
     FreeBSD mkdir test2/.   fails with errno ENOENT (reasonable, since test2
 			    exists, but test2/ and test2/, don't)
     POSIX   mkdir test2/    must fail after adding dot

(2) when "test" doesn't exist at the start:

     FreeBSD mkdir test      succeeds, nothing special
     FreeBSD mkdir test/     succeeds (tricky to make this work)
     FreeBSD mkdir test/.    fails with ENOENT
     POSIX   mkdir test/     ?  POSIX says that trailing slashes shall be
 			    ignored in directory contexts.  FreeBSD
 			    considers this to be a directory context

(3) cases involving rename(2) are more complicated, and cases involving
     mv(1) are even more complicated, since mv to a directory has to manage
     pathnames before calling rename(2).

Bruce


More information about the freebsd-bugs mailing list