#! /bin/sh
# cdlndir --- make symbolic links to a source tree residing on an
#             ISO9660 or Rock Ridge CD-ROM

# Copyright (C) 1992-1998 Free Software Foundation, Inc.

# 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; see the file COPYING.  If not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

# Commentary:

# $Id: cdlndir,v 2.21 1998/08/14 22:57:58 kwzh Exp $

# Author: Noah Friedman <friedman@gnu.org>
#	  Daniel Hagerty <hag@ai.mit.edu>
# Created: 1992-10-29

# Please report bugs to bug-gnu-cdrom@gnu.org.  Include the
# version information obtained from `cdlndir --version'.

# This script makes some assumptions about the CDROM filesystem layout
# in order to guess at the correct options to use (most of these
# heuristics can be overriden by command line options):
#
#   * It assumes that the ISO9660<-->unix filename map file is called
#     `TRANS.TBL' (it may appear in upper or lower case and
#     optionally have a semicolon and file version number, depending
#     on how the CDROM driver presents the file), though that can be
#     changed with the --map-file option.  This file should be in the
#     format generated by the `mkisofs' program by Eric Youngdale
#     <ericy@gnu.org>.  The mkisofs program is available via
#     anonymous FTP from tsx-11.mit.edu in the directory
#     /pub/linux/packages/mkisofs/.
#
#   * There is a file called `.RockRidge' at the top of the source
#     tree to help determine how the filesystem is mounted.  If the
#     filesystems is mounted with Rock Ridge extensions enabled, the
#     file will be present with exactly that name.  If it is mounted
#     as a vanilla ISO9660 filesystem, the file won't be there, at
#     least not with that name.
#
#   * There is at least one top-level non-directory file whose name
#     does not contain a dot.  (Typically this will be `MANIFEST',
#     `README', `INSTALL', etc.)  When a filename has no extension,
#     some ISO9660 drivers will strip the trailing `.'; others won't.
#     Inspecting the name of this file determines which is the case.
#
# The top of the CDROM source tree is determined by walking up the
# directory hierarchy until it finds the topmost directory that has a
# TRANS.TBL file.  If you NFS-mount the CDROM starting at some
# subdirectory (rather than the root of the CDROM filesystem), some of
# the heuristics are likely to fail.  If that happens, use whichever
# options are necessary to do the right thing.

# Code:

###

umask 000

# Name by which this script was invoked.
fullprogname=$0
progname=`echo "$0" | sed -e 's/[^\/]*\///g'`

# This string is expanded by rcs automatically when this file is checked out.
rcs_revision='$Revision: 2.21 $'
revision=`set - $rcs_revision; echo $2`

usage="Usage: $progname [options] source-directory destination-directory
(Version $revision)

Options are:
-D, --debug           Turn on shell debugging (\`\`set -x'').
-c, --copy            Copy files, don't make symlinks (by default, symlinks
                      are made if target filesystem supports them).
-h, --help            You're looking at it.
-i, --iso9660         Assume CDROM file system is in vanilla ISO9660 format.
-m, --map-file FILE   Name of file on CDROM in each directory which
                      provides the mapping between ISO9660 and unix file
                      names.  By default it is \`TRANS.TBL'.
-n, --numbered        Assume all files on CDROM must be referenced with
                      version numbers even if they appear unnecessary.
-N, --no-numbered     Never use version numbers in file names.
-r, --rockridge       Assume CDROM file system is in Rock Ridge format.
-s, --symbolic-link   Assume target filesystem supports symbolic links.
-t, --trailing-dot    File names lacking an extension have a trailing \`.'
-T, --no-trailing-dot File names lacking an extension have no trailing \`.'
-v, --verbose         Show shell commands as they are being run to make tree.
-V, --version         Show version number of this program ($revision) & exit.

All switches are optional; if they are not specified the proper values
are guessed.
"

# For systems that don't have the #! kernel hack.
SHELL=/bin/sh
export SHELL

# Initialize variables.
# Don't use `unset' since old bourne shells don't have this command.
# Instead, assign them an empty value.
copy=
debug=
fstype=
subargs=
trailing_dot=
transtab_basename=
transtab_search=
verbose=
vnumbers=

# Usage: eval "$getopt"; value=$optarg
# or     optarg_optional=t; eval "$getopt"; value=$optarg
#
# This function automatically shifts the positional args as appropriate.
# The argument to an option is optional if the variable `optarg_optional'
# is non-empty.  Otherwise, the argument is required and getopt will cause
# the program to exit on an error.  optarg_optional is reset to be empty
# after every call to getopt.  The argument (if any) is stored in the
# variable `optarg'.
#
# Long option syntax is `--foo=bar' or `--foo bar'.  2nd argument
# won't get used if first long option syntax was used.
#
# Note: because Bourne shell command substitution (`...`) is required
# to strip trailing newlines, using --foo=bar syntax can actually screw
# the quoting of args that end with newlines.  Backslashes are also
# unsafe, since some versions of echo treat backslash literally, while
# others use it for C-like escapes.
getopt='
  {
    optarg=
    case "$1" in
      --*=* )
        optarg=`echo "$1" | sed -e "1s/^[^=]*=//"`
        shift
       ;;
      * )
        case ${2+set} in
          set )
            optarg="$2"
            shift
            shift
           ;;
          * )
            case "$optarg_optional" in
              "" )
                case "$1" in
                  --*=* ) option=`echo "$1" | sed -e "1s/=.*//;q"` ;;
                  * ) option="$1" ;;
                esac
                exec 1>&2
                echo "$progname: option $bq$option$eq requires argument."
                echo "$progname: use $bq--help$eq to list option syntax."
                exit 1
               ;;
           esac
           shift
         ;;
        esac
     ;;
    esac
    optarg_optional=
  }'

# Parse command line arguments.
# Make sure that all wildcarded options are long enough to be unambiguous.
# It's a good idea to document the full long option name in each case.
# Long options which take arguments will need a `*' appended to the
# canonical name to match the value appended after the `=' character.
posargs=
while test $# -ne 0 ; do
  case "$1" in
    -c | --copy | --c* )
      subargs="$subargs --copy"
      copy=t
      shift
     ;;
    -D | --debug | --d* )
      subargs="$subargs --debug"
      debug=t
      shift
     ;;
    -h | --help | --h* )
      echo "$usage"
      exit 0
     ;;
    -i | --iso9660 | --i* )
      fstype=iso9660
      subargs="$subargs --$fstype"
      shift
     ;;
    -m | --map-file* | --m* )
      eval "$getopt"
      transtab_basename="$optarg"
      transtab_search=t
     ;;
    -n | --numbered | --nu* )
      vnumbers=t
      subargs="$subargs --numbered"
      shift
     ;;
    -N | --no-numbered | --no-n* )
      vnumbers=f
      subargs="$subargs --no-numbered"
      shift
     ;;
    -r | --rock-ridge | --rockridge | --r* )
      fstype=rockridge
      subargs="$subargs --$fstype"
      shift
     ;;
    -s | --symbolic-link | --symlink | --s* )
      subargs="$subargs --symbolic-link"
      copy=f
      shift
     ;;
    -t | --trailing-dot | --t* )
      subargs="$subargs --trailing-dot"
      trailing_dot=t
      shift
     ;;
    -T | --no-trailing-dot | --no-t* )
      subargs="$subargs --no-trailing-dot"
      trailing_dot=f
      shift
     ;;
    -v | --verbose | --verb* )
      subargs="$subargs --verbose"
      verbose="-x"
      shift
     ;;
    -V | --version | --vers* )
      echo "$progname version $revision" 1>&2
      exit 1
     ;;
    -- )     # Stop option processing
      shift
      break
     ;;
    -? | --* )
      case "$1" in
        --*=* ) arg=`echo "$1" | sed -e 's/=.*//'` ;;
        * )     arg="$1" ;;
      esac
      exec 1>&2
      echo "$progname: unknown or ambiguous option $bq$arg$eq"
      echo "$progname: Use $bq--help$eq for a list of options."
      exit 1
     ;;
    -??* )
      # Split grouped single options into separate args and try again
      optarg="$1"
      shift
      set fnord `echo "x$optarg" | sed -e 's/^x-//;s/\(.\)/-\1 /g'` ${1+"$@"}
      shift
     ;;
    * )
      # We'll accept positional arguments and named options in any order.
      posargs="$posargs $1"
      shift
     ;;
  esac
done
set fnord $posargs; shift

###

case "$debug" in t ) set -x ;; esac

case $# in
   0 | 1 )
      exec 1>&2
      echo "$progname: you must provide source and target directory names."
      echo "$progname: Use $bq--help$eq for argument syntax."
      exit 1
     ;;
   2 )
     ;;
   * )
      exec 1>&2
      echo "$progname: Too many arguments."
      echo "$progname: You must specify exactly two directory names (source and target)."
      echo "$progname: Use $bq--help$eq for argument syntax."
      exit 1
     ;;
esac

srcdir=$1
dstdir=$2

# The user might be following instructions that specify a Unixy name
# that doesn't actually appear in the filesystem, due to ISO9660 name
# mangling.  If this happens, walk up to the top of the tree and
# keep a list of the Unixy directory names as we extract them.

srcmore=
while test ! -d "$srcdir" ; do
   srcmore=`expr "/$srcdir" : '.*/\([^/][^/]*\)/*$'`" $srcmore"
   tmp=`expr "$srcdir/" : '\(.*[^/]\)//*[^/][^/]*//*$'`
   if test -z "$tmp" ; then
      exec 1>&2
      echo "$progname: The directory $srcdir doesn't exist."
      echo "Please check to see where the CD-ROM is mounted."
      exit 1
   fi
   srcdir=$tmp
done

if test -d "$dstdir" || mkdir "$dstdir" ; then
   :
else
   echo "$progname: $dstdir: Couldn't create directory." 1>&2
   exit 1
fi

###

# Canonicalize the source directory, stripping `..' and `.' items from it.
# If user didn't specify an absolute pathname as the location of the CDROM
# directory, we must canonicalize it or else the symlinks won't point anywhere
# useful.  For example, if you are in directory /foo/whatever and you do
#
#        $ cdlndir ../cdrom/gcc20 /src/local/gcc-2.0
#
# The srcdir (which is relative) may otherwise be completely meaningless if
# the links are in /src/local/gcc-2.0.
#
# Also, deeply relocated directories are passed from the parent
# cdlndir with many embedded `..' in them.  Its nicer for several
# reasons to strip these out of the symlink tree that we build.
#
# This pathname canonicalization doesn't resolve symbolic links to make a
# truly canonical pathname, but that's simply too expensive to do in shell
# script, even though it's possible.

orig_pwd=`pwd`

# Get rid of cruft like /tmp_mnt.  It can cause references to
# symlinked files not to invoke the automounter.  What lossage.
case "$orig_pwd" in
   /tmp_mnt/* )
      tpwd=`echo "$orig_pwd" | sed -e 's=^/tmp_mnt=='`
      if test -d "$tpwd" ; then
         orig_pwd=$tpwd
      fi
     ;;
esac

# Quote orig_pwd so that sed will match exactly that string (i.e. quote
# special characters so that they lose their specialness).
regexp_quote='s/\([][*.\\\/?|^$]\)/\\\1/g'

orig_pwd_quoted=`echo "$orig_pwd" | sed -e "$regexp_quote"`

# Regexp for converting paths to absolute form with . and ..
# references resolved.
canonicalize_pathname='/^\.$/{s/\./'"$orig_pwd_quoted"'/;q;}
                       /^[^\/]/s/^\([^\/]\)/'"$orig_pwd_quoted"'\/\1/
                       s/\/\/*/\//g
                       : cde
                       s/\/\.\//\//g
                       t cde
                       : pde
                       s/\/[^\/][^\/]*\/\.\.\//\//
                       t pde
                       s/\/[^\/][^\/]*\/\.\.$//
                       s/^\/\.\.\//\//g'

# Don't need to canonicalize dstdir.
#dstdir=`echo "$dstdir" | sed -e "$canonicalize_pathname"`
srcdir=`echo "$srcdir" | sed -e "$canonicalize_pathname"`

###

l_cmd_args='-s'  # Use symbolic links for L-type entries
f_cmd='ln -s'    # Command to symlink files

case "$copy" in
   t )
      #l_cmd_args=      # Use hard links for L-type entries
      f_cmd='cp'       # Command to copy files
     ;;
   f ) : ;;
   * )
      # If user didn't specify -c or -s on the command line, check whether
      # dstdir filesystem actually supports symlinks.  Fail if they don't.
      # (this is done because copying the files is likely to take a lot of
      # space and the user should explicitly state that this is what
      # s/he wants).
      (
        cd "$dstdir"
        > "$progname$$"
        ln -s "$progname$$" "$progname-$$"
        exitstat=$?
        rm -f "$progname$$" "$progname-$$"
        exit $exitstat;
      )
      exitstat=$?
      if test $exitstat -ne 0 ; then
         echo "$progname: $dstdir: This filesystem does not appear to support symbolic links." 1>&2
         echo "$progname: Invoke this script with the --copy option to copy files instead." 1>&2
         exit 1
      fi

      subargs="$subargs --symbolic-link"
     ;;
esac

###

case "$transtab_basename" in
  '' )
    transtab_basename=TRANS.TBL
    transtab_search=t
   ;;
esac

case "$transtab_search" in
  t )
    # Generate list of possible file names for the table of
    # mutated-filename to original-filename list.  This will vary depending
    # on how the CDROM driver presents file names.  Even a plain ISO9660
    # driver can force everything to lower case, show version numbers, etc.
    # From a file like `foo' this generates the list `foo', `foo.',
    # `foo;1', `foo.;1', `FOO', `FOO.', `FOO;1', and `FOO.;1'.  (The names
    # with trailing dots aren't produced if the provided name has a dot in
    # it already.)
    names=`echo "$transtab_basename" \
           | sed -ne '
               /\/$/s/\/\/*$//
               /\//s/.*\///

               /;/s/;.*//
               /\.$/s/\.$//

               y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/
               x;s/.*/up/;x
               b sub

               :up
               y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
               x;s/.*/end/;x
               b sub

               :sub
               p
               /\./!{
                 s/$/./p
                 s/\.$//
               }
               s/$/;1/p
               s/;.*//
               /\./!{
                 s/$/.;1/p
                 s/\.;1$//
               }
               x
               /up/{x; b up
               }
             '`

    for file in $names ; do
      if test -f "$srcdir/$file" ; then
        transtab_basename=$file
        export transtab_basename
        break
      fi
    done
   ;;
esac

if test ! -f "$srcdir/$transtab_basename"; then
   exec 1>&2
   echo "$progname: $srcdir exists, but it doesn't seem to be a CD-ROM"
   echo "filesystem, since there's no $transtab_basename file in that directory."
   echo "Please check to see where the CD-ROM is mounted."
   exit 1
fi

###

# Find CDROM mountpoint.  This basically walks up the directory tree
# specified by $srcdir until it can no longer find a translation table
# file.
case "$cdrom_mountpoint" in
   '' )
      tdir=$srcdir
      pdir=$tdir
      while test -f "$tdir/$transtab_basename" ; do
         pdir=$tdir
         tdir=`echo "$tdir" | sed -e 's=/[^/]*$=='`
      done
      cdrom_mountpoint=$pdir
      # For now it seems simpler to export this than to add hair to cdlndir
      # to parse option arguments.
      export cdrom_mountpoint
     ;;
esac

# Guess whether CDROM is in vanilla ISO9660 or Rock Ridge format, unless
# told to assume one or the other.  In the root of the CDROM filesystem
# there is a file ".RockRidge".  Since this isn't legal ISO9660, if the
# file is there we can safely assume the driver is presenting the CDROM in
# Rock Ridge format.  If we see "_ROCKRID" instead, we'll take that to be
# an ISO9660 mangling of it.  If we don't see either one (which probably
# means that someone goofed when creating the CD), we'll check to see if
# some other top-level file gives us an answer that we trust -- either a
# long file name, or both upper and lower case, implies Rock Ridge; and
# we'll assume that version numbers imply ISO9660.
case "$fstype" in
   iso9660 | rockridge ) : ;;
   * )
      if test -f "$cdrom_mountpoint/.RockRidge" ; then
        fstype=rockridge
      elif ls $cdrom_mountpoint | \
           egrep "^_(ROCKRID|rockrid)\\.?(;1)?$" >/dev/null ; then
        fstype=iso9660
      elif (c="[^.;]"; \
            ls $cdrom_mountpoint | grep "$c$c$c$c$c$c$c$c$c" || \
            ls $cdrom_mountpoint | grep "[.]$c$c$c$c" || \
            (ls $cdrom_mountpoint | grep '[A-Z]' && \
             ls $cdrom_mountpoint | grep '[a-z]')) >/dev/null; then
        fstype=rockridge
      elif ls $cdrom_mountpoint | grep ';1$' >/dev/null; then
        fstype=iso9660
      else
        echo "$progname: heuristic failed" >&2
        echo "you must specify --iso9660 or --rockridge" >&2
        exit 1
      fi
      subargs="$subargs --$fstype"
     ;;
esac

###

# For ISO9660, guess what kind of name mangling applies.

case "$transtab_basename" in
  *[a-z]* )
    sed_case_fold='y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'
    ;;
  * )
    sed_case_fold=
    ;;
esac

if test -z "$vnumbers" ; then
  # See if some top-level file has a version number.
  # Note: it is NOT reliable to just check TRANS.TBL for a version number.
  # Some versions of mkisofs do the wrong thing with that file.
  if ls $cdrom_mountpoint | grep ';1$' >/dev/null; then
    vnumbers=t
    subargs="$subargs --numbered"
  elif (set fnord `ls $cdrom_mountpoint | egrep '^[^;]+$'`; shift; \
        for i do test -f $cdrom_mountpoint/$i -a $i != $transtab_basename && exit 0; done; \
        exit 1); then
    vnumbers=f
    subargs="$subargs --no-numbered"
  else
    echo "$progname: heuristic failed" >&2
    echo "you must specify --numbered or --no-numbered" >&2
    exit 1
  fi
fi
case "$vnumbers" in
  t ) sed_strip_version= ;;
  f ) sed_strip_version='s/;.*$//' ;;
esac

if test -z "$trailing_dot"; then
  if ls $cdrom_mountpoint | egrep '\.(;1)?$' >/dev/null; then
    trailing_dot=t
    subargs="$subargs --trailing-dot"
  elif (set fnord `ls $cdrom_mountpoint | egrep '^[^.]+$'`; shift; \
        for i do test -f $cdrom_mountpoint/$i && exit 0; done; \
        exit 1); then
    trailing_dot=f
    subargs="$subargs --no-trailing-dot"
  else
    echo "$progname: heuristic failed" >&2
    echo "you must specify --trailing-dot or --no-trailing-dot" >&2
    exit 1
  fi
fi
case $trailing_dot in
  t )
    sed_strip_trailing_dot=
   ;;
  f )
    sed_strip_trailing_dot='
      s/\.;1$/;1/
      s/\.$//
    '
   ;;
esac

###

# If we previously had to peel off Unixy directory names from $srcdir,
# then we'll put them back now in mangled form (to match the filesystem).

for dirname in $srcmore ; do
   mangle=`sed -n -e "
      s/[ 	][ 	]*/ /g
      s/^[DM] \([^ ][^ ]*\) $dirname *\$/\1/p" <$srcdir/$transtab_basename`
   if test -z "$mangle" ; then
      echo "$srcdir/$dirname: No such directory" 1>&2
      exit 1
   fi
   if test -n "$sed_case_fold$sed_strip_version$sed_strip_trailing_dot"; then
      mangle=`echo $mangle | sed -e "
         $sed_case_fold
         $sed_strip_version
         $sed_strip_trailing_dot"`
   fi
   if test ! -d $srcdir/$mangle ; then
      echo "$srcdir/$dirname: No directory \"$mangle\" in filesystem??" >&2
      exit 1
   fi
   srcdir=$srcdir/$mangle
done

###

case "$fstype" in
   iso9660 )
      sed -n -e '
         s/[ 	][ 	]*/ /g
         /^[^BCDFLPM]/{s?^\(.*\)$?echo "'"$progname: $srcdir/$transtab_basename"': unsupported entry \\\`\1'\\\''" 1>\&2?p;d;}
         h
         s/^\([^ ]*\) .*/ \1/
         y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/
         x
         s/^[^ ]* //
         G
         s/\n//g
         h
         s/^\([^ ][^ ]*\) .*/\1/
         '"$sed_case_fold"'
         '"$sed_strip_version"'
         '"$sed_strip_trailing_dot"'
         x
         s/	//g
         s/^ *[^ ]* *\([^ ].*\)$/ \1/
         H
         g
         s/\n//g
         s/^\(.*\) \([^ ][^ ]*\)$/\2 \1/
         /^f /{s?^[^ ]* \([^ ]*\) \([^ ]*\) *.*?'"$f_cmd"' '\'"$srcdir/"'\1'\'' '\'"$dstdir/"'\2'\''?p;d;}
         /^d /{s?^[^ ]* \([^ ]*\) \([^ ]*\) *.*?'\'"$fullprogname"\'' '"$subargs"' '\'"$srcdir"/'\1'\'' '\'"$dstdir"/'\2'\''?p;d;}
         /^m /{h;s/.* / /;'"$sed_case_fold"';x;s/ [^ ]*$//;G;s/\n//;s?^[^ ]* [^ ]* \([^ ]*\) \([^ ]*\) *.*?'\'"$fullprogname"\'' '"$subargs"' '\'"$srcdir"/'\2'\'' '\'"$dstdir"/'\1'\''?p;d;}
         /^l /{s?^[^ ]* [^ ]* \([^ ]*\) \([^ ]*\) *.*?(cd '\'"$dstdir"\''; ln '"$l_cmd_args"' '\''\2'\'' '\''\1'\'')?p;d;}
         /^[cb] /{s/^\([^ ]*\) \([^ ]*\) \([^ ]*\) \([^ ]*\) \([^ ]*\) *.*/mknod '\''\3'\'' \1 \4 \5 ;/p;d;}
         /^p /{s/^\([^ ]*\) \([^ ]*\) \([^ ]*\) *.*/mknod '\''\3'\'' \1 ;/p;d;}
         ' "$srcdir/$transtab_basename" \
       | sh $verbose
     ;;
   rockridge )
      (cd "$srcdir"; find . -name "$transtab_basename" -print) \
       | sed -e 's/^\.\///
                 s/'"$transtab_basename"'$//' \
       | sort \
       | while read dirname ; do
            sed -n -e '
               s/[ 	][ 	]*/ /g
               /^[FSBCP] /{s?^[^ ]* [^ ]* \([^ ]*\) *.*?'"$f_cmd"' '\'"$srcdir/$dirname"'\1'\'' '\'"$dstdir/$dirname"'\1'\'' ;?p;d;}
               /^[DM] /{s?^[^ ]* [^ ]* \([^ ]*\) *.*?mkdir '\'"$dstdir/$dirname"'\1'\'' ;?p;d;}
               /^L /{s?^[^ ]* [^ ]* \([^ ]*\) \([^ ]*\) *.*?(cd '\'"$dstdir/$dirname"\''; ln '"$l_cmd_args"' '\''\2'\'' '\''\1'\'');?p;d;}
              ' "$srcdir/$dirname/$transtab_basename" \
             | sh $verbose
         done
     ;;
esac

###

# local variables:
# page-delimiter: "^###"
# vc-make-backup-files: t
# eval: (auto-fill-mode -1)
# end:

# cdlndir ends here
