#!/bin/sh
# symlink-resolve --- dereference symbolic links

# Copyright (C) 1993, 1995, 1998, 2000 Noah S. Friedman

# Author: Noah Friedman <friedman@splode.com>
# Created: 1993-02-07

# $Id: symlink-resolve,v 1.10 2000/01/13 19:48:00 friedman Exp $

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, you can either send email to this
# program's maintainer or write to: The Free Software Foundation,
# Inc.; 59 Temple Place, Suite 330; Boston, MA 02111-1307, USA.

# Commentary:

# Resolve pathnames until there are no more symbolic links.  Resulting
# pathname may not be a file that actually exists (depending on whether
# symlinks point to nonexistent files).

# Code:

exec ${PERL-perl} -Sx $0 ${1+"$@"}
#!perl		  [perl will skip all lines in this file before this line]

use Getopt::Long;
use Cwd;

my $cwd;

# Args: basename, default-directory
#
# Convert basename to absolute, and canonicalize it.  Second arg
# default-directory is directory to start with if basename is relative
# (does not start with slash); if default-directory is undefined, the
# current working directory is used.  File name components that are `.' are
# removed, and so are file name components followed by `..', along with the
# `..' itself; note that these simplifications are done without checking
# that the file name actually exists in the file system.
sub expand_file_name ($;$)
{
  local $_ = shift;

  if ($_ !~ m|^/|o)
    {
      $cwd = getcwd() unless (defined $cwd);
      $_ = join ('/', (shift || $cwd), $_);
    }

  # These substitutions must be done in loops to handle overlapping `/'
  # characters in adjacent patterns.
  s|/\./|/|o         while (m|/\./|o);
  s|//|/|o           while (m|//|o);
  s|/[^/]+/\.\./|/|o while (m|/[^/]+/\.\./|o);
  s|/[^/]+/\.\.$||o;
  s|/.$||go;
  # Eliminate leading `..'.
  # It may be harmful to do it if the filesystem interprets `/..' as
  # something not equivalent to `/'.
  #s|^/\.\./|/|o     while (m|^/\.\./|o);
  return $_;
}

sub dereference_links ($)
{
  my $file = shift;
  my @p = split (m|/|, $file);
  my $link_count = 0;
  for (my $j = 0; $j <= $#p; $j++)
    {
      my $k = join ("/", @p[0 .. $j]);
      my $orig_component = $k;
      while (my $l = readlink ($k))
        {
          $k = $l;
          # Simple way of detecting symlink loops (it unfortunately causes
          # the system to give up when there are simply too many levels,
          # even if resolution would eventually occur).  This parameter is
          # adjustable, of course.  Most unix kernels allow a depth of 8.
          if ($link_count++ == 64)
            {
              print STDERR "$0: $file: Too many levels of symbolic links\n";
              return undef;
            }
        }
      next if ($k eq $orig_component);
      if (substr ($k, 0, 1) eq "/")
        {
          # Absolute link.  Trash $p[0]-$p[$j+1] and replace with readlinked
          # path components.  Set $j to -1 so that next iteration of loop
          # will check array @p from start.
          splice (@p, 0, $j + 1, split (m|/|, $k));
          $j = -1;
        }
      else
        {
          # Insert partial (relative) path component into array in place of
          # current element $p[$j]
          splice (@p, $j, 1, split (m|/|, $k));
          $j--;
        }
    }
  join ("/", @p);
}

sub show_links ($)
{
  my $filelist = shift;
  my $multip = ($#$filelist > 0);
  for my $file (@$filelist)
    {
      my $linkto = dereference_links ($file);
      if ($file eq $linkto)
        {
          print "$file\n";
        }
      else
        {
          $linkto = expand_file_name ($linkto) unless ($nocanonicalizep);
          print ($multip? "$file -> $linkto\n" : "$linkto\n");
        }
    }
}

sub usage ()
{
  print "Usage: $0 {options} [path1] {path2} {...}

Options are:
-h, --help                   You're looking at it.
-n, --no-canonicalize        Do not canonicalize pathnames by resolving
                             references to \`..', etc.\n";
  exit (1);
}

sub main ()
{
  $0 =~ s|.*/||;

  $nocanonicalizep = 0;
  Getopt::Long::config ('bundling', 'autoabbrev');
  GetOptions ("n|no-canonicalize", \$nocanonicalizep,
              "h|help", \&usage);
  show_links (\@ARGV);
}

main();

# local variables:
# mode: perl
# eval: (auto-fill-mode 1)
# end:

# symlink-resolve ends here
