*stat()-ing symlinks with trailing slashes

Bruce Evans brde at optusnet.com.au
Sat May 23 10:34:29 UTC 2009


On Fri, 22 May 2009, Vlad GALU wrote:

> On 5/22/09, Vlad GALU <dudu at dudu.ro> wrote:
>> -- cut here --
>>  root at goofy / # rm -f passwd
>>  root at goofy / # ln -s /etc/passwd passwd
>>  root at goofy / # stat passwd
>>  74 3 lrwxr-xr-x 1 root wheel 1668572463 11 "May 22 19:34:17 2009" "May
>>  22 19:34:17 2009" "May 22 19:34:17 2009" "May 22 19:34:17 2009" 4096 0
>>  0 passwd
>>  root at goofy / # stat passwd/
>>  74 95688 -rw-r--r-- 1 root wheel 393192 2158 "May 21 09:27:10 2009"
>>  "May 21 09:27:10 2009" "May 22 17:25:49 2009" "Apr  7 13:05:32 2008"
>>  4096 8 0 passwd/
>>  root at goofy / #
>>  -- and here --
>>
>>  stat(1) is smart enough to figure out that my /passwd is a symlink
>>  then calls lstat() on it, thus returning the struct stat corresponding
>>  to /etc/passwd

stat(1) is not smart.  It uses lstat(2) in both cases (stat -L would use
stat(2) in both cases).  This gives the symlink in the first case.  In the
second case, the trailing slash forces the kernel to follow the link.
The pathname is apparently resolved to /etc/passwd.  This is wrong.  The
trailing slash must be preserved in some way.

At least the 2001 version of POSIX says that trailing slashes slashes
shall be resolved as if a single "." were added to the pathname, but
this is wrong too, since it breaks thinks like "mkdir foo/" by turning
it into "mkdir foo/." which must fail either because the directory foo
doesn't exist or because creating the "." directory is invalid.  The
POSIX 2001 rationale says that older versions of POSIX didn't require
this, and justifies changing this because:
- there were 2 main types of implementation: ones that always ignored
   trailing slashes, and ones that permitted them only on _existing_
   directories.  POSIX doesn't know about (at least in 2001) the better
   implementation in FreeBSD and now disallows it :-(: modulo bugs,
   FreeBSD permits them only on existing directories, directories
   that will be created by mkdir(2), and directories that will be
   created by rename(2).
- applications couldn't rely on any particular behaviour.

FreeBSD attempts to implement the 1990 version of POSIX with extensions
for symlinks.  (POSIX.1-1990 doesn't support symlinks, but the problem
is more for directories than for symlinks, with symlinks just providing
non-literal examples and possibilities for bugs).  In FreeBSD, "foo/"
is supposed to mean the same as "foo" (not the same as "foo/.") in
directory contexts but not in other contexts.  This works right when
"foo" is "/etc/passwd" (lstat() on "/etc/passwd/" gives ENOTDIR), but
it is apparently broken when "foo" is a symlink to a non-directory
(the trailing slash is apparently lost).  (When "foo" is a symlink to
a directory, the trailing slash is probably lost too, but this shouldn't
matter since the result is a directory and the trailing slash is
supposed to have no effect for directories.)

>>  However, there's http://www.freebsd.org/cgi/query-pr.cgi?pr=kern/21768
>>
>>  vfs_lookup.c has this piece of code:
>>  -- cut here --
>>         /*
>>          * Check for bogus trailing slashes.
>>          */
>>         if (trailing_slash && dp->v_type != VDIR) {
>>                 error = ENOTDIR;
>>                 goto bad2;
>>         }
>>  -- and here --

That is what is supposed to give an error for trailing slashes on
non-directories (except for when the files are non-directories only
because they don't yet exist).

I'm fairly sure that the patch in the PR is wrong for symlinks, because
I broke trailing slashes on symlinks (to directories) in a pre-commit
version, probably similarly.  However, my main commit for trailing slashes
(vfs_lookup.c 1.8) already points out the bug for symlinks to non-directories
and says that it is to be fixed later.  Apparently this was forgotten.

>>  I've CC-ed my friend Mircea Danila, who noticed this behavior with lighttpd.
>>  As my friend Mircea Danila, who I've CC-ed found out, lighttpd mistakenly treats
>
> So, to finish my idea, since I wasn't previously able to write a fully
> coherent mail, the behavior is that lighttpd returns the full source
> of scripts, instead of executing them, when they're symlinks and when
> the GET requests has a trailing "/". When there's no trailing slash,
> they get executed, as expected. The lighttpd devs say that, due to
> stat() not returning ENOTDIR, they simply try to list the content.

ENOTDIR is correct for following a symlink to non-directory (with a
trailing slash in the pathname).

Why does GET give trailing slashes for non-directories?  Trailing
slashes are most useful interactively for getting symlinks followed
and for avoiding getting file contents when you "know" that the file
is a directory (and that the OS handles trailing slashes reasonably).
Applications shouldn't need this hack since they can use lstat() to
get more details, and it is still unportable.

Bruce


More information about the freebsd-fs mailing list