#!/bin/bash
#
# --- NO-T2-COPYRIGHT-NOTE ---
#
# Found in the debian boot-floppies source package from:
# 	http://www.kernel.org/debian/dists/potato/main/source/admin/
#
# mklibs.sh: An automated way to create a minimal /lib/ directory.
#
# Copyright 1999 by Marcus Brinkmann <Marcus.Brinkmann@ruhr-uni-bochum.de>
#
#   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 of the License, 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, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Introduction:
# When creating boot floppies, there is never enough room on the disks.
# So it is important not to waste bytes on unnecessary code.
# Shared Libraries contain many functions that are probably not used in the
# binaries included in the boot disks, and copying the whole library is a
# waste of space.
# This utilitiy helps to reduce the necessary libraries to only include the
# symbols needed to run a given set of executables.
#
# Features:
# * Automatic detection of all necessary libraries, even for inter-library
#   dependencies, for a given set of executables.
# * Automatic installation of all needed libraries and soname links.
# * Automatic reduction of all libraries to minimal size for which PIC
#   libraries are provided.
#
# Requirements:
# * Beside the shared libraries, you need libfoo_pic.a files for all
#   libraries you want to reduce.
# * You need binutils (notably objdump and objcopy) installed.

# A GENERAL NOTE ABOUT LANGUAGE ABUSE
#
# If you believe this program had better not been written in shell script
# language, I invite you to reimplement it in the language of your
# preference.
# The reasons I chose shell are:
# * Shell scripts are very portable and available even on minimal systems as
#   well as boot disks.
# * Shell scripts can be run without compilation.
# * Shell scripts provide a very easy interface to the various system
#   commands I need to get the library dependencies and sort through them.
# Perl is lacking good data types, so implementing this would be equally
# cumbersome in perl (I need trees and queues, for example).
# C and C++ are lacking easy access to the system commands.
#
# Of course, shell scripting has many problems:
# * Use of temporary files for string arrays.
# * Slow.
# * Hard to debug.
#
# I think for hand written code, I hit a limit with the size and execution
# time of this program of what is still acceptable as a shell script. I also
# tried to improve the situation with many comments.

# TODO:
#  * Make sure versioned symbols get correct version number.
#    This seems to work now, however, we always include
#    all versions of a symbol. This is not a problem. To do
#    it properly, we had to parse the version information in
#    objdump, which is hard.
#  * Use --dynamic-syms on so lib instead --syms on pic file.
#  * Autodetect that libc needs ld (should be possible from
#    output of objdump --privat-headers| grep NEEDD).
#  * Code to create libs in cycles !!!

# HISTORY:
#
# 1999-09-13  Marcus Brinkmann <brinkmd@debian.org>
#
#	* Initial release (v0.1).
#

# STATIC DATA SECTION
#

usage="Usage: $0 [OPTION]... -d DEST FILE ..."
try="Try "\`"$0 --help' for more information"
version="$0 0.1, Copyright 1999 Marcus Brinkmann"

PATH=/bin:/usr/bin

default_src_path=/lib:/usr/lib
dest=""
exec=""
action=""
verbose="false"

gcc=${GCC-gcc}
objdump=${OBJDUMP-objdump}
objcopy=${OBJCOPY-objcopy}

# =================
# GRAPH ABSTRACTION
# =================
#
# Because we do some hairy graph operations, we provide some
# abstractions of them. Some functions here are very simple, but
# the source is much more readable this way.

# check-node NODE ...
#   checks if all NODEs are valid node names.
# Used internally for verificaton only.
# Return 0 if all NODEs are valid.
# Currently, a node is valid if it does not contain a space.

check-node () {
  local node
  for node in "$@" ; do
    if [ "x`echo $node | sed -e '/ /d'`" = x ] ; then
      echo 1>&2 $0: check-node: invalid node \"$node\"
      exit 1
    fi
  done
  return 0
}

# is-graph FILE ...
#   provides a very simple type assertion
# Turns FILE into a graph if it isn't already and returns 0.

is-graph () {
  local file
  for file in "$@" ; do
    if [ ! -e "$file" ] ; then
      touch "$qfile"
    fi
  done
}

# add-node FILE NODE
#   add a node NODE to graph FILE.
# This is useful if you need to make sure that a node appears
# in the graph without actually connecting it to an arrow.
# You don't need to add nodes that are part of an arrow.

add-node () {
  if [ $# != 2 ] ; then
    echo 1>&2 $0: add-node: internal error: called with invalid number of arguments
    exit 1
  fi
  check-node "$2"
  echo "$2 $2" >> "$1"
  return 0
}

# add-arrow FILE NODE1 NODE2
#   add an arrow from NODE1 to NODE2 to graph FILE.

add-arrow () {
  if [ $# != 3 ] ; then
    echo 1>&2 $0: add-arrow: internal error: called with invalid number of arguments
    exit 1
  fi
  check-node "$2" "$3"
  echo "$2 $3" >> "$1"
  return 0
}

# find-cycle FILE
#   finds a cycle in a graph FILE.
# If a cycle is found, it is printed out at stdin, one node each line,
# and 0 is returned. Otherwise, nothing is printed  on stdout and exit
# status is 1.

find-cycle () {
  if [ $# != 1 ] ; then
    echo 1>&2 $0: find-cycle: internal error: called with invalid number of arguments
    exit 1
  fi
  tsort "$1" 2> "$fl_dir/find-cycle" > /dev/null
  if [ "x`cat $fl_dir/find-cycle`" = x ] ; then
    return 1
  else
    if [ "x`head -n 1 $fl_dir/find-cycle`" != "xtsort: cycle in data" ] ; then
      echo 1>&2 $0: find-cycle: internal error: tsort has invalid output format
      exit 1
    fi
    cat "$fl_dir/find-cycle" | sed -e '1d' -e '/tsort: cycle in data/,$d' -e 's/^tsort: //'
  fi
}

# shrink-nodes FILE NODE1 ...
#   shrinks several nodes NODE1 ... to a single node in graph FILE.
# To hide cycles, we treat a cycle as a single node and replace
# each occurence of a node in the cycle with a new node
# [NODE1,...] . This change is destructive and can not be undone!
# (You would need to store the entry point to the cycle for each arrow
# pointing to/from it).
# This function does not check if the the nodes NODE1 ... exist.
# However, if none of these nodes exists already, the new node will
# not appear either. This makes this function sort of idem potent.
# It does not check if NODE1 ... are a cycle. We will assume this
# later in the library dependency analysis, but nothing in the code
# relies on it.
# Always shrink all cycles, or you may get unresolved symbols.
#
# Example:
#  N1  --->  N2                         N1 -------> /------------\
#  |             "shrink-nodes N2 N4"   |        _  | [N2,N4]    |
#  v             ------------------->   v   _____/| \------------/
#  N3  --->  N4                         N3 /

# A small helper function will aid us...
# equal-match STRING STRING1 ...
#   return 0 if STRING is among STRING1 ..., 1 otherwise.
equal-match () {
  local string
  local stringk
  string="$1"
  shift
  for stringk in "$@" ; do
    if [ "x$string" = "x$stringk" ] ; then
      return 0
    fi
  done
  return 1
}

shrink-nodes () {
  local head
  local lnode
  local rnode
  local graph="$1"
  shift
  is-graph "$graph"
  check-node "$@"
  local cnode="[`echo "$@" | sed 's/ /,/g'`]"
  # Okay, it's a hack. We treat the graph as a queue. I am just too
  # lazy to copy the relevant code here. Of course, we exploit several
  # properties of the graph and queue file format here (for example,
  # that graphs never can contain a QUEUE_SEPERATOR, and that a graph is
  # really a simple file with "a b" entries).
  cat /dev/null > "$fl_dir/shrink-cycle"
  while head=`get-top-of-queue "$graph"` ; do
    lnode=`echo $head|sed 's/ [^ ]*$//'`
    if equal-match "$lnode" "$@" ; then
      lnode="$cnode"
    fi
    rnode=`echo $head|sed 's/^[^ ]* //'`
    if equal-match "$rnode" "$@" ; then
      rnode="$cnode"
    fi
    echo "$lnode $rnode" >> "$fl_dir/shrink-cycle"
  done
  cat "$fl_dir/shrink-cycle" | sort -u > "$graph"
}

# =================
# QUEUE ABSTRACTION
# =================
#
# I added an abstract interface for queues to make the code more readable.
# Queue operations usually consist of several atomic file operations, which
# can get quite messy.
#
# You can use queues to simply loop through all lines of a file, but you
# also can add stuff to the queue while processing it.
#
# Implementation: All queues consist of a QUEUE_FILE which has two parts:
# the remaining entries in the queue (QUEUE) and the already processed
# entries (BUCKET).
# The two parts are seperated by a line containing only QUEUE_SEPERATOR.

QUEUE_SEPERATOR=SEPERATOR___ABOVE_IS_QUEUE__BELOW_IS_BUCKET___SEPERATOR

# check-queue-entry QENTRY ...
#   checks if all queue entries QENTRY are valid.
# Used internally for verificaton only.
# Return 0 if all QENTRYs are valid.
# Currently, a node is valid if it does not match the QUEUE_SEPERATOR.

check-queue-entry () {
  local qentry
  for qentry in "$@" ; do
    if [ "x`echo $qentry | sed "/^$QUEUE_SEPERATOR$/d"`" = x ] ; then
      echo 1>&2 $0: check-queue-entry: invalid qentry name \"$qentry\"
      exit 1
    fi
  done
  return 0
}

# is-queue QUEUE_FILE ...
#   provides a very simple type assertion
# Turns QUEUE_FILE into a queue if it isn't already and returns 0.

is-queue () {
  local qfile
  for qfile in "$@" ; do
    if [ ! -e "$qfile" ] ; then
      echo "$QUEUE_SEPERATOR" > "$qfile"
    else
      if ! grep -q "^$QUEUE_SEPERATOR$" "$qfile" ; then
        echo "$QUEUE_SEPERATOR" >> "$qfile";
      fi
    fi
  done
}

# get-top-of-queue QUEUE_FILE
#   processes a queue one more time.
# If QUEUE of QUEUE_FILE is empty, exit status is 1 and no output is given.
# Otherwise, top of QUEUE is removed, returned on stdout and
# appended to the end of the BUCKET part of QUEUE_FILE.

get-top-of-queue () {
  if [ $# != 1 ] ; then
    echo 1>&2 $0: get-top-of-queue: internal error: called with invalid number of arguments
    exit 1
  fi
  is-queue "$1"
  local head=`head -n 1 "$1"`
  if [ "x$head" = "x$QUEUE_SEPERATOR" ] ; then
    return 1
  else
    sed -e 1d "$1" > "$fl_dir/get-top-of-queue"
    echo "$head" | tee --append "$fl_dir/get-top-of-queue"
    cat "$fl_dir/get-top-of-queue" > "$1"
    return 0
  fi
}

# add-to-queue-if-not-there QUEUE_FILE QENTRY ...
#   add queue entries QENTRY ... to the beginning of the
# QUEUE of QUEUE_FILE if it is neither in QUEUE nor in BUCKET
# of QUEUE_FILE.
# Return with exit status 0.
# Note: If you want to add QENTRY to the *end* of QUEUE, you would do
# something like the following:
# sed -e s/^$QUEUE_SEPERATOR$/$head"'\
# '"$QUEUE_SEPERATOR/" 
# which is necessary to pass the newline to sed. I think we can take the
# easy way out.

add-to-queue-if-not-there () {
  local qentry
  local qfile="$1"
  shift
  check-queue-entry "$@"
  is-queue "$qfile"
  for qentry in "$@" ; do
    if ! grep -q "^$qentry\$" "$qfile" ; then
      echo "$qentry" > "$fl_dir/add-to-queue-if-not-there"
      cat "$qfile" >> "$fl_dir/add-to-queue-if-not-there"
      cat "$fl_dir/add-to-queue-if-not-there" > "$qfile" 
    fi
  done
  return 0
}

# ==================
# LIBRARY PROCESSING
# ==================
#
# The following helper functions mess around with the actual
# processing and installation of libraries.
#

# get-library-depends OBJ1 ...
#   get all libraries the objects OBJ1 ... depend on.
# OBJs can be binaries or shared libraries.
# The list is neither sort'ed nor uniq'ed.

get-library-depends () {
  if [ $# = 0 ] ; then
    echo 1>&2 $0: get-library-depends: internal error: no arguments
    exit 1
  fi
  $objdump --private-headers "$@" 2> /dev/null \
    | sed -n 's/^ *NEEDED *\([^ ]*\)$/\1/p'
}

# get-undefined-symbols OBJ1 ...
#   get all unresolved symbols in OBJ1 ...
# The list is neither sort'ed nor uniq'ed.

get-undefined-symbols () {
  if [ $# = 0 ] ; then
    echo 1>&2 $0: get-undefined-symbols: internal error: no arguments
    exit 1
  fi
  # ash has undefined reference to sys_siglist if .bss is not mentioned
  # here. Reported by Joel Klecker.
  # All symbols are epxosed, so we just catch all. Suggested by Roland
  # McGrath. Another thing to try is to investigate --dynamic-reloc.
  $objdump --dynamic-syms "$@" 2> /dev/null \
    | sed -n 's/^.* \([^ ]*\)$/\1/p'
#   | sed -n 's/^.*[\*UND\*|.bss].* \([^ ]*\)$/\1/p'
}

# get-provided-symbols LIB1 LIB2 ...
#   get all symbols available from libraries LIB1 ... .
# Does only work for pic libraries.
#
#                       v Watch the tab stop here.
# 00000000  w    F .text	00000000              syscall_device_write_request
# 00000000 g     F .text	0000056c              __strtoq_internal

get-provided-symbols () {
  if [ $# = 0 ] ; then
    echo 1>&2 $0: get-provided-symbols: internal error: no arguments
    exit 1
  fi
  $objdump --syms "$@" 2>/dev/null | grep -v '\*UND\*' \
    | sed -n 's/^[0-9a-f]\+ \(g \| w\)   .. .*	[0-9a-f]\+ \(0x8[08]\)\? *\([^ ]*\)$/\3/p'
}

# Crude hack (?) only used for diagnostic.

get-provided-symbols-of-so-lib () {
  if [ $# = 0 ] ; then
    echo 1>&2 $0: get-provided-symbols: internal error: no arguments
    exit 1
  fi
  $objdump --dynamic-syms "$@" 2>/dev/null \
    | sed -e '/\*UND\*/d' | sed -n 's/^.* \([^ ]*\)$/\1/p'
}

# get-common-symbols FILE1 FILE2
#   returns a list of all symbols in FILE1 that appear also in FILE2
# Note: When get-common-symbols returns, FILE1 and FILE2 are "sort -u"'ed.
# Note: Version Information in FILE1 is ignored when comparing.

get-common-symbols () {
  if [ $# != 2 ] ; then
    echo 1>&2 $0: get-common-symbols: internal error: called with invalid number of arguments
    exit 1
  fi
  # Not needed anymore, but we go for compatibility.
  # (Somewhere we HAVE to clean FILE2 up).
  sort -u "$1" > $fl_dir/get-common-symbols
  cat $fl_dir/get-common-symbols > "$1"
  sort -u "$2" > $fl_dir/get-common-symbols
  cat $fl_dir/get-common-symbols > "$2"

  local symbol=
  while symbol=`get-top-of-queue $fl_dir/get-common-symbols` ; do
    grep ^$symbol\$\\\|^$symbol@ "$1"
  done
}

# create-link TARGET LINK_NAME
#  creates a soft link if there isn't one already.

create-link () {
  if [ $# != 2 ] ; then
    echo 1>&2 $0: create-link: internal error: called with invalid number of arguments
    exit 1
  fi
  if [ ! -e "$2" ] ; then
    $action ln -s "$1" "$2"
  fi
}

# find-file PATH FILE
#   search all directories in PATH for file FILE, return absolute path
# FILE can be a relative path and a filename.
# PATH is a list, seperator is ':'.

find-file () {
  if [ $# != 2 ] ; then
    echo 1>&2 $0: find-file: internal error: exactly two arguments required
    exit 1
  fi
  local path=$1
  local dir=`echo $path | sed -e 's/:.*$//'`
  until [ "x$path" = x ] ; do
    if [ "x$dir" != x ] ; then
      if [ -e "$dir/$2" ] ; then
        echo "$dir/$2"
        return 0
      fi
    fi
    path=`echo $path | sed -e 's/^[^:]*:*//'`
    dir=`echo $path | sed -e 's/:.*$//'`
  done
  return 1
}

# find-files PATH FILE1 FILE2 ...
#   search all directories in PATH for file FILE1, FILE2...
# FILE can be a relative path and a filename.
# PATH is a list, seperator is ':'.
# Return value is a white space seperated list of absolute filenames.

find-files () {
  if [ $# -lt 2 ] ; then
    echo 1>&2 $0: find-files: internal error: too few arguments
    exit 1
  fi
  local path="$1" ; shift
  while [ $# != 0 ] ; do
    find-file $path $1
    shift
  done
}

# get-pic-file LIB
#   returns the filename of the pic archive for LIB.
# Note: There doesn't seem to be any convention, *ick*.

get-pic-file () {
  if [ $# != 1 ] ; then
    echo 1>&2 $0: get-pic-file: internal error: called with invalid number of arguments
    exit 1
  fi
  if [ "x$1" = "xlibc-2.0.7.so" ] ; then
    # Order does matter! First init, then lib, then fini!
    echo `find-files $src_path libc_pic/soinit.so libc_pic.a libc_pic/sofini.so`
    return 0
  fi
  if [ "x$1" = "xlibc-2.1.2.so" -o "x$1" = "xlibc-2.1.3.so" \
  	-o "x$1" = "xlibc-2.2.so" -o "x$1" = "xlibc-2.2.1.so" \
	-o "x$1" = "xlibc-2.2.2.so" ] ; then
    # Order does matter! First init, then lib, then fini!
    echo `find-files $src_path libc_pic/soinit.o libc_pic.a libc_pic/sofini.o libc_pic/interp.o`
    return 0
  fi
  if [ "x$1" = "xlibm-2.1.2.so" -o "x$1" = "xlibm-2.1.3.so" \
  	-o "x$1" = "xlibm-2.2.so" -o "x$1" = "xlibm-2.2.1.so" \
	-o "x$1" = "xlibm-2.2.2.so" ] ; then
    echo `find-file "$src_path" libm_pic.a`
    return 0
  fi
  if [ "x$1" = "xlibslang.so.1.3.9" ] ; then
    echo `find-file $src_path libslang1.3.9_pic.a`
    return 0
  fi
  if [ "x$1" = "xlibslang.so.1.4.1" ] ; then
    echo `find-file $src_path libslang1.4.1_pic.a`
    return 0
  fi
  local libname=`echo $1 | sed -e 's/^lib\(.*\).so.*/\1/'`
  echo `find-file "$src_path" lib${libname}_pic.a`
  return 0
}

get-extra-flags () {
  if [ $# != 1 ] ; then
    echo 1>&2 $0: get-extra-flags: internal error: called with invalid number of arguments
    exit 1
  fi
  if [ "x$1" = "xlibc-2.0.7.so" ] ; then
    echo `find-file $src_path ld-2.0.7.so` -lgcc 
    return 0
  fi
  if [ "x$1" = "xlibc-2.1.2.so" ] ; then
    echo "`find-file $src_path ld-2.1.2.so` -lgcc -Wl,--version-script=`find-file $src_path libc_pic.map`"
    return 0
  fi
  if [ "x$1" = "xlibm-2.1.2.so" -o "x$1" = "xlibm-2.1.3.so" ] ; then
    echo "-Wl,--version-script=`find-file $src_path libm_pic.map`"
    return 0
  fi
  if [ "x$1" = "xlibc-2.1.3.so" ] ; then
    echo "`find-file $src_path ld-2.1.3.so` -lgcc -Wl,--version-script=`find-file $src_path libc_pic.map`"
    return 0
  fi
  if [ "x$1" = "xlibc-2.2.so" ] ; then
    echo "`find-file $src_path ld-2.2.so` -lgcc -Wl,--version-script=`find-file $src_path libc_pic.map`"
    return 0
  fi
  if [ "x$1" = "xlibc-2.2.1.so" ] ; then
    echo "`find-file $src_path ld-2.2.1.so` -lgcc -Wl,--version-script=`find-file $src_path libc_pic.map`"
    return 0
  fi
  if [ "x$1" = "xlibc-2.2.2.so" ] ; then
    echo "`find-file $src_path ld-2.2.2.so` -lgcc -Wl,--version-script=`find-file $src_path libc_pic.map`"
    return 0
  fi
  return 0
}

# install-small-lib LIB_SONAME
#   makes a small version of library LIB_SONAME
#
# This happens the following way:
# 0. Make exception for the linker ld.
# 1. Try to figure out complete path of pic library.
# 2. If no found, copy the shared library, else:
#  a. Get shared libraries this lib depends on, transform into a
#     list of "-lfoo" options.
#  b. Get a list of symbols both provided by the lib and in the undefined
#     symbols list.
#  c. Make the library, strip it.
#  d. Add symbols that are still undefined to the undefined symbols list.
#  e. Put library into place.

install-small-lib () {
  if [ $# != 1 ] ; then
    echo 1>&2 $0: install-small-lib: internal error: called with invalid number of arguments
    exit 1
  fi
  local src_file=`find-file $src_path $1`
  if `echo "$1" | grep -q ^ld` ; then
    get-provided-symbols "$src_file" >> $fl_dir/provided-symbols
    $action $objcopy --strip-unneeded -R .note -R .comment "$src_file" "$dest/$1"
    return 0
  fi
  local pic_objects=`get-pic-file "$1"`
  local extra_flags=`get-extra-flags "$1"`
  local architecture=`dpkg --print-architecture`
  # some arm bins or libs are improperly linked, force -lgcc
  if [ "$architecture" = arm ]; then
    extra_flags="$extra_flags -lgcc"
  fi
  if [ "x$pic_objects" = x ] ; then
    $verbose 2>&1 No pic archive for library "$1" found, falling back to simple copy.
    get-provided-symbols-of-so-lib "$src_file" >> $fl_dir/provided-symbols
    get-undefined-symbols "$src_file" >> $fl_dir/undefined-symbols
    $action $objcopy --strip-unneeded -R .note -R .comment "$src_file" "$dest/$1"
  else
    $verbose 2>&1 Make small lib from "$pic_objects" in "$dest/$1".

    # XXX: If ld is NEEDED, we need to include it on the gcc command line
    get-library-depends "$src_file" \
      | sed -n -e 's/^lib\(.*\)\.so.*$/\1/p' > $fl_dir/lib-dependencies
    get-provided-symbols $pic_objects > $fl_dir/lib-provided-symbols
    # Argument order does matter:
    get-common-symbols $fl_dir/lib-provided-symbols \
      $fl_dir/undefined-symbols > $fl_dir/lib-symbols-to-include

    ${gcc} \
	-nostdlib -nostartfiles -shared \
	"-Wl,-soname=$1" \
	`cat $fl_dir/lib-symbols-to-include | sed 's/^/-u/'` \
	-o $fl_dir/lib-so \
	$pic_objects $extra_flags \
	"-L$dest" \
	-L`echo $src_path | sed -e 's/::*/:/g' -e 's/^://' -e 's/:$//' \
	-e 's/:/ -L/g'` \
	`cat $fl_dir/lib-dependencies | sed 's/^/-l/'` \
      && $objcopy --strip-unneeded -R .note -R .comment $fl_dir/lib-so $fl_dir/lib-so-stripped \
      || {
        echo 1>&2 $0: install-small-lib: $gcc or $objcopy failed.
        exit 1
      }
    get-undefined-symbols $fl_dir/lib-so-stripped \
      >> $fl_dir/undefined-symbols
    get-provided-symbols-of-so-lib $fl_dir/lib-so-stripped >> $fl_dir/provided-symbols
    $action cp $fl_dir/lib-so-stripped "$dest/$1"
  fi
}

# install-libs-in-sphere [LIB1,...]
#   extracts the libs in a shrinked node and cycles through them until all
# possible symbols are resolved.
# Always make sure this can be called recursively (from install-libs)!

install-libs-in-sphere () {
  if [ $# != 1 ] ; then
    echo 1>&2 $0: install-libs-in-sphere: internal error: called with invalid number of arguments
    exit 1
  fi
  # Unfortunately, we need a small parser here to do the right thing when
  # spheres are within spheres etc. RegEx simply can't count brackets. :(
  local string=`echo "$1" | sed -e 's/^\[//' -e 's/\]$//'`
  local char
  local result=
  local depth=0
  while [ "x$string" != x ] ; do
    # Jump to next special char for faster operation.
    # Don't be confused by the regex, it matches everything but ],[
    char=`echo $string | sed -e 's/^\([^],[]*\).*$/\1/'`
    string=`echo $string | sed -e 's/^[^],[]*//'`
    result="$result$char";
    # Read special char
    char=`echo $string | sed -e 's/^\(.\).*$/\1/'`
    string=`echo $string | sed -e 's/^.//'`
    case "$char" in
      [) depth=$(($depth+1));;
      ]) depth=$(($depth-1));;
      ,) if [ $depth = 0 ] ; then
           char=' ';
         fi;;
    esac
    result="$result$char";
  done
  $verbose 2>&1 "RESOLVING LOOP...`echo $result | md5sum`"
  echo XXX: CODE NOT FINISHED
  install-libs $result
  $verbose 2>&1 "END OF LOOP... `echo $result | md5sum`"
}

# install-libs LIB1 ...
#   goes through an ordered list of libraries and installs them.
# Make sure this can be called recursively, or hell breaks loose.
# Note that the code is (almost) tail-recursive. I wish I could
# write this in Scheme ;)

install-libs () {
  local cur_lib
  local lib
  for cur_lib in "$@" ; do
    if echo "$cur_lib" | grep -q '^\[' ; then
      install-libs-in-sphere "$cur_lib"
    else
      lib=`find-file $src_path $cur_lib`
      if [ -L "$lib" ] ; then
        lib=`basename \`readlink $lib\``
        create-link $lib $dest/$cur_lib
      else
        install-small-lib $cur_lib
      fi
    fi
  done
}

#
#  MAIN PROGRAM
#
#  1. Option Processing
#  2. Data Initialization
#  3. Graph Construction and Reduction
#  4. Library Installation

#  Global Files:
#  $fl_dir/undefined-symbols
#     Holds all undefined symbols we consider for inclusion.
#     Only grows. Does not to be sort'ed and uniq'ed, but will
#     get occasionally.
#  $fl_dir/provided-symbols
#     Holds all defined symbols we included.
#     Only grows. Should later be a superset of undefined-symbols.
#     But some weak symbols may be missing!
#  $fl_dir/library-depends
#     Queue of all libraries to consider.

#
# 1. Option Processing
#

while :; do
  case "$1" in
    -L) src_path="$src_path:$2"; shift 2;;
    -d|--dest-dir) dest=$2; shift 2;;
    -n|--dry-run) action="echo"; shift;;
    -v|--verbose) verbose="echo"; shift;;
    -V|--version) echo "$version"; exit 1;;
    -h|--help)
      echo "$usage"
      echo "Make a set of minimal libraries for FILE ... in directory DEST."
      echo ''
      echo "\
Options:
  -L DIRECTORY               Add DIRECTORY to library search path.
  -n, --dry-run              Don't actually run any commands; just print them.
  -v, --verbose              Print additional progress information.
  -V, --version              Print the version number and exit.
  -h, --help                 Print this help and exit.

  -d, --dest-dir DIRECTORY   Create libraries in DIRECTORY.

Required arguments for long options are also mandatory for the short options."
      exit 0;;
    -*) echo 1>&2 $0: $1: unknown flag; echo 1>&2 "$usage"; echo 1>&2 "$try"; exit 1;;
    ?*) exec="$exec $1"; shift;;
    *)  break;;
  esac
done

src_path=${src_path-$default_src_path}

if [ "x$exec" = x ] ; then
  exit 0
fi
if [ "x$dest" = x ] ; then
  echo 1>&2 $0: no destination directory given; echo 1>&2 "$usage"; exit 1
fi


#
# 2. Data Initialization
#

$verbose -n 2>&1 "Initializing data objects... "

# Temporary directory.
fl_dir="/tmp/,mklibs.$$"
set -e
mkdir $fl_dir
set +e

trap "rm -fr $fl_dir" EXIT

# Intialize our symbol array and library queue with the information
# from the executables.

get-undefined-symbols $exec > $fl_dir/undefined-symbols
add-to-queue-if-not-there $fl_dir/library-depends `get-library-depends $exec`

$verbose 2>&1 "done."

#
# 3.a Graph Construction
#
# Build the dependency graph, add new library dependencies to the queue on
# the way.
#   If the soname is a link, add the target to the end of the queue and
#   add a simple arrow to the graph.
#   If the soname is a real lib, get its dependencies and add them to
#   the queue. Furthermore, add arrows to the graph. If the lib is not
#   dependant on any other lib, add the node to make sure it is mentioned
#   at least once in the graph.

$verbose -n 2>&1 "Constructing dependency graph... ("

while cur_lib=`get-top-of-queue $fl_dir/library-depends`
do
  lib=`find-file $src_path $cur_lib`
  if [ -L "$lib" ] ; then
    $verbose -n 2>&1 L
    lib=`basename \`readlink $lib\``
    add-to-queue-if-not-there $fl_dir/library-depends "$lib" 
    add-arrow $fl_dir/dependency-graph "$cur_lib" "$lib"
  else
    get-library-depends "$lib" > $fl_dir/backup
    if [ "x`head -n 1 $fl_dir/backup`" = x ] ; then
      $verbose -n 2>&1 N
      add-node $fl_dir/dependency-graph "$cur_lib"
    else
      $verbose -n 2>&1 A
      for lib in `cat $fl_dir/backup` ; do
        add-to-queue-if-not-there $fl_dir/library-depends "$lib"
        add-arrow $fl_dir/dependency-graph "$cur_lib" "$lib"
      done
    fi
  fi
done

$verbose 2>&1 ") done."

#
# 3.b Graph Reduction
#
# Find and shrink cycles in the graph.

$verbose -n 2>&1 "Eliminating cycles... ("

while cycle=`find-cycle "$fl_dir/dependency-graph"` ; do
  $verbose -n 2>&1 C
  shrink-nodes "$fl_dir/dependency-graph" $cycle
done

$verbose 2>&1 ") done."

#
# 4. Library Installation
#
# Let tsort(1) do the actual work on the cycle-free graph.

tsort $fl_dir/dependency-graph > $fl_dir/backup

# Now the ordered list of libraries (or cycles of them)
# can be processed by install-libs. This is indeed the last step.

install-libs `cat $fl_dir/backup`

#sort -u $fl_dir/provided-symbols > $fl_dir/diag1
#sort -u $fl_dir/undefined-symbols > $fl_dir/diag2
#cat $fl_dir/diag1 $fl_dir/diag2 | sort | uniq -u > $fl_dir/diag3
## diag3 has now the symmetric difference.
#cat $fl_dir/diag3 $fl_dir/diag2 | sort | uniq -d > $fl_dir/diag1
## diag1 has now all undefined symbols that are not provided.
##cat $fl_dir/diag1 | wc
## Note that some of these symbols are weak and not having them is probably
## not an error.

exit 0	

