[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: "readlink -f foo" fails if the target of foo does not exist

From: Jim Meyering
Subject: Re: "readlink -f foo" fails if the target of foo does not exist
Date: Fri, 09 Jan 2004 11:29:18 +0100

Thomas Hood <address@hidden> wrote:
> Currently,
>     readlink -f foo
> returns the null string if the target of foo does not exist.  This behavior
> differs from that of readlink in Debian woody.  I hope that this will be
> considered a bug because the new behavior is much less useful than the old
> behavior.  The new behavior can easily be emulated using the old readlink:
>     REALPATH="$(old-readlink -f "$FILE")"
>     [ -e "$REALPATH" ] || REALPATH=""
> but the old behavior cannot easily be emulated using the new readlink.
> With the new readlink there is no easy way to find out the ultimate
> target of a symlink chain if the target happens not to exist.

Thanks for persevering.

The problem with the previous implementation is that its behavior
wasn't well defined.  For example, it didn't say how it dealt with

  `readlink -f /tmp/symlink-to-nonexistent' or
  `readlink -f /tmp/symlink-to-nonexistent/..'

As it was implemented, it would output `/tmp/nonexistent' for both of those.
Ignoring the trailing `/..' (and succeeding!) in the second example is
unintuitive, to say the least.

Also, the old readlink man page said it was implemented
using realpath.  And POSIX's description of realpath describes
these errors:

  The realpath( ) function shall fail if:
    [EACCES]             Read or search permission was denied for a component
                           of file_name.
    [EINVAL]             Either the file_name or resolved_name argument is a
                           null pointer.
    [EIO]                An error occurred while reading from the file system.
    [ELOOP]              A loop exists in symbolic links encountered during
                           resolution of the path argument.
                         The length of the file_name argument exceeds {PATH_MAX}
                           or a pathname component is longer than {NAME_MAX}.
    [ENOENT]             A component of file_name does not name an existing file
                           or file_name points to an empty string.
    [ENOTDIR]            A component of the path prefix is not a directory.

  Which implies realpath actually calls lstat for each component.
  In particular, note the ENOENT part.  Makes me think that the old
  implementation of readlink didn't really use realpath.

How about this [see below for a better spec] :
If FILE exists, work exactly as now.
If FILE does not exist, then
  assume FILE can be divided into two parts, $d/$b
  where $d is the longest prefix for which r=$(readlink -f $d) succeeds
  and for which s=$(readlink $d/$(echo $b|sed s,/.*,,) (no -f option)
  also succeeds.

Then if $s is an absolute path name, output $s$suff where $suff
is the possibly empty suffix of $b removed via sed, above.
If $s is a relative path name, output $r/$s$suff.

But what if $suff contains ../ and/or ./ components.  `./' ones are easy.
Just remove them.  You might be tempted to process the `../' ones textually,
so that e.g.,

  readlink -f /tmp/a/b/c/dangle/../..
  (where dangle is a symlink to a nonexistent file, `nowhere')

would output


But imagine that rather than being a dangling link, `dangle' points
to `nowhere' which is now itself a symlink pointing to `/' (or any
directory not at the same depth).  Then the output would be `/' (or
different from `/tmp/a/b').

The point is that we can't resolve any ../ components beyond the first
component that is a dangling symlink.

So, I think the spec should say that the only component of FILE
that may be a dangling symlink is the last one.  Otherwise, `readlink -f'
must fail and give a diagnostic.

So here's a better spec:

  If canonicalize_file_name (FILE) succeeds, work exactly as now.
  else if lstat (FILE) succeeds,
    linkname = readlink (FILE)
    if linkname starts with `/', print linkname
    otherwise, print $(readlink -f $(dirname FILE))/linkname
  else, work exactly as now.

I think you get the idea.
I'll be happy to apply such a patch.  And if it comes with a documentation
patch describing the new behavior and giving a couple of instructive
examples, I'll be even happier -- with accompanying doc changes, the
code patch will probably go in a lot more promptly because I won't have
to find the time to write the documentation myself.

reply via email to

[Prev in Thread] Current Thread [Next in Thread]