#!/bin/sh
# NAME
#    thin - thin down executables to specified architectures
#
# SYNOPSIS
#    thin [-R -g -q -9] [-arch <arch1> [-arch <arch2>]...] files
#
# DESCRIPTION
#    The thin command takes a list of files as parameters and thins them
#    down to the specified architectures, if they are executable.
#    Non-executable files are left alone. With the -R option directories
#    in the list are thinned recursively and archives are unpacked, thinned
#    and packed again. If no -arch option is given, the file ~/.architectures
#    is read instead. This file contains a list of architectures separated by
#    whitespace. The following extensions are recognized for archives:
#       .tar        packed with tar or gnutar
#       .gz, .z     compressed with gzip
#       .Z          compressed with compress
#       .tgz        equivalent to .tar.gz
#       .compressed equivalent to .tar.Z
#
# OPTIONS
#    -arch <arch_type>
#       Leave this architecture in the executables. This option may be given
#       multiple times.
#
#    -d, -dd
#       These options enable debugging. -d enables normal debugging infos,
#       -dd enables the extended ones (and implies -d).
#
#    -q
#       Quiet mode -- do not print out informal messages.
#
#    -R
#       Go through all subdirectories recursively. Archive files ending in
#       .tar, .gz, .tgz, ... and combinations of these extensions are
#       unpacked and fed into thin recursively. The result is packed in the
#       same way as the original was (except for the -g option).
#
#    -g
#       Use gzip to pack archives that were originally packed with compress.
#       This option is useful if you want to transfer archives containing
#       compressed files (like packages) efficiently. The gzipped archive
#       will still be named after the compressed one.
#
#    -9
#       Pass the '-9' option to gzip when compressing. This results in best
#       compression but may take a considerable amount of time.
#
#    -h, -help
#       Print out this help-page to stdout and terminate.
#
# FILES
#    ~/.architectures
#       If this file exists, it should contain the list of architectures
#       you are interested in. For example, if you have NEXTSTEP for Intel,
#       your file should consist of the line
#           i386 i486
#
# BUGS
#    Always make a backup copy of your original data, because this script
#    may contain serious bugs. These are known until now:
#    - File permissions are not always preserved.
#    - Filenames containing whitespace will probably not work.
#    - The .tar.Z file in some packages can not be unpacked with gnutar.
#    - I don't know whether .compressed always means .tar.Z
#
# COPYRIGHT
#    Copyright (C) 1995 by Christian Starkjohann <cs@hal.kph.tuwien.ac.at>
#    You may redistribute this script freely as long as you keep this
#    copyright message. 
#


LIPO_COMMANDS="qlipo lipo"
# on NEXTSTEP machines we can use lipo, on non-NEXTSTEP machines we must use
# qlipo (available at ftp.cs.orst.edu or from ftp.informatik.uni-muenchen.de,
# written by Darcy Brockbank <samurai@hasc.ca>).

TMPDIR="/tmp/`whoami`-thin"
# set this to echo if you want debugging info
DEBUG=false
NOISE=false
INFO=echo

SELF=`which $0`
RECURSIVE=no
ALWAYS_GZIP=no
gzip_option=    # this can be set to -9 for most efficient compression


Usage="Usage: `basename $0` [-R -g -q -9] [-arch <arch1> [-arch <arch2>]...] files or `basename $0` -h"

###############################################################################

init_directories()
{
    rm -rf "$TMPDIR"
    mkdir "$TMPDIR"
    echo 0 >"$TMPDIR/counter"
}

###############################################################################

cleanup_directories()
{
    rm -rf "$TMPDIR"
}

###############################################################################
# the following function is not used any more
#remove_architecture()
#{
#    rm -f "$TMPDIR/exec"
#    $LIPO $1 -remove $2 -output "$TMPDIR/exec" 2>/dev/null
#    if [ -f "$TMPDIR/exec" ]; then
#        mv "$TMPDIR/exec" $1
#        $DEBUG "thinned out $2 from $1"
#    else
#        $DEBUG "could not thin out $2 from $1"
#    fi
#    rm -f "$TMPDIR/exec"
#}
#
###############################################################################

unique()
{
    counter=`cat "$TMPDIR/counter"`
    counter=`expr $counter + 1`
    echo $counter >"$TMPDIR/counter"
    echo $counter
}

###############################################################################

reduce_to_architectures()
{
    rm -f $TMPDIR/exec*
    file="$1"; shift
    while [ $# -gt 0 ]; do
        $LIPO "$file" -extract $1 -output "$TMPDIR/exec.$1" 2>/dev/null
        if [ ! -f "$TMPDIR/exec.$1" ]; then
            echo "warning: file $file does not contain architecture $1" 1>&2
        fi
        shift
    done
    if [ "X`ls $TMPDIR/exec* 2>/dev/null`" != X ]; then
        $DEBUG "debug: there are thinned files"
        $LIPO $TMPDIR/exec* -create -output $TMPDIR/exec
        if [ -f $TMPDIR/exec ]; then
            $DEBUG "debug: created new fat file"
            mv -f $TMPDIR/exec "$file"
        else
            echo "warning: error creating fat file $file (leaving it unchanged)" 1>&2
        fi
    else
        echo "warning: none of the architectures found in $file (leaving it unchanged)" 1>&2
    fi
}

###############################################################################

do_job_on_files()
{
    $NOISE "noise: thinning files $*"
    for FILE in $*; do
        if [ $RECURSIVE = yes ]; then
            if [ -d $FILE ]; then
                (do_job_on_files $FILE/*)
            else
                case $FILE in
                    *.gz)           (operate_on_gz $FILE) ;;
                    *.tgz)          (operate_on_tgz $FILE) ;;
                    *.Z)            (operate_on_Z $FILE) ;;
                    *.z)            (operate_on_z $FILE) ;;
                    *.tar)          (operate_on_tar $FILE) ;;
                    *.compressed)   (operate_on_compressed $FILE) ;;
                    *)          if [ -x $FILE ]; then
                                    (reduce_to_architectures "$FILE" $THINTO)
                                else
                                    $NOISE "noise: $FILE can not be stripped"
                                fi;;
                esac
            fi
        else
            if [ -f $FILE -a -x $FILE ]; then
                (reduce_to_architectures "$FILE" $THINTO)
            else
                $NOISE "noise: $FILE can not be stripped"
            fi
        fi
    done
}

###############################################################################

operate_on_gz()
{
    base=`basename $1 | sed 's/.gz$//'`
    uncomp="$TMPDIR/uz`unique`.$base"
    file=$1
    $INFO "info: gunzipping $file" 1>&2
    gzip -d -c $file > $uncomp
    if [ -f $uncomp ]; then
        if [ -x $file ]; then
            chmod a+x $uncomp
        fi
        (do_job_on_files $uncomp)
        $INFO "info: gzipping $file" 1>&2
        chmod u+rw $file
        gzip -n $gzip_option -c $uncomp > $file
        rm -f $uncomp
    else
        echo "warning: error uncompressing $file (leaving it unchanged)" 1>&2
    fi
}

###############################################################################

operate_on_tgz()
{
    base=`basename $1 | sed 's/.tgz$//'`
    file=$1
    moved="$TMPDIR/mv`unique`.$base.tar.gz"
    $DEBUG "debug: moving $file to $moved"
    mv -f $file $moved
    (do_job_on_files $moved)
    $DEBUG "debug: moving $moved to $file"
    mv -f $moved $file
}

###############################################################################

operate_on_Z()
{
    base=`basename $1 | sed 's/.Z$//'`
    uncomp="$TMPDIR/uc`unique`.$base"
    file=$1
    $INFO "info: decompressing $file" 1>&2
    uncompress -c $file > $uncomp
    if [ -f $uncomp ]; then
        if [ -x $file ]; then
            chmod a+x $uncomp
        fi
        (do_job_on_files $uncomp)
        chmod u+rw $file
        if [ $ALWAYS_GZIP = yes ]; then
            $INFO "info: gzipping $file" 1>&2
            gzip -n $gzip_option -c $uncomp > $file
        else
            $INFO "info: compressing $file" 1>&2
            compress -f -c $uncomp > $file
        fi
        rm -f $uncomp
    else
        echo "warning: error uncompressing $file (leaving it unchanged)" 1>&2
    fi
}

###############################################################################

operate_on_z()
{
    base=`basename $1 | sed 's/.z$//'`
    uncomp="$TMPDIR/z`unique`.$base"
    file=$1
    $INFO "info: gunzipping $file" 1>&2
    gzip -d -c $file > $uncomp
    if [ -f $uncomp ]; then
        if [ -x $file ]; then
            chmod a+x $uncomp
        fi
        (do_job_on_files $uncomp)
        $INFO "info: gzipping $file" 1>&2
        chmod u+rw $file
        gzip -n $gzip_option -c $uncomp > $file
        rm -f $uncomp
    else
        echo "warning: error uncompressing $file (leaving it unchanged)" 1>&2
    fi
}

###############################################################################

operate_on_tar()
{
    base=`basename $1 | sed 's/.tar$//'`
    untar="$TMPDIR/ut`unique`.$base"
    file=$1
    $INFO "info: untaring $file" 1>&2
    mkdir $untar
    (cd $untar; gnutar xf -) <$file
    if [ "X`ls -A $untar`" != "X" ]; then
        (do_job_on_files $untar)
        $INFO "info: taring $file" 1>&2
        chmod a+rw $file
        (cd $untar; gnutar cf - `ls -A`) > $file
        rm -rf $untar
    else
        echo "warning: error untaring $file (leaving it unchanged)" 1>&2
    fi
}

###############################################################################

operate_on_compressed()
{
    base=`basename $1 | sed 's/.compressed$//'`
    file=$1
    moved="$TMPDIR/mv`unique`.$base.tar.Z"
    $DEBUG "debug: moving $file to $moved"
    mv -f $file $moved
    (do_job_on_files $moved)
    $DEBUG "debug: moving $moved to $file"
    mv -f $moved $file
}

###############################################################################
# here starts the main program
###############################################################################

THINTO=
FILES=
while [ $# -gt 0 ]; do
    case $1 in
    -arch)      shift; THINTO="$THINTO ${1?$Usage}" ;;
    -R)         RECURSIVE=yes ;;
    -g)         ALWAYS_GZIP=yes ;;
    -d)         DEBUG=echo ;;
    -dd)        NOISE=echo; DEBUG=echo ;;
    -q)         INFO=false ;;
    -9)         gzip_option='-9' ;;
    -help)      sed -n -e '/^$/q' -e '2,$s/^#//p' $SELF; exit ;;
    -h)         sed -n -e '/^$/q' -e '2,$s/^#//p' $SELF; exit ;;
    -*)         echo $Usage 1>&2 ; exit 1 ;;
    *)          FILES="$FILES $1" ;;
    esac
    shift
done

LIPO=
for i in $LIPO_COMMANDS; do
    if [ `which $i | awk '{print $1}'` != no ]; then
        LIPO=$i
        break
    fi
    $NOISE "noise: ->$i<- command not available on this machine"
done
if [ "X$LIPO" = X ]; then
    echo "fatal: none of the comands ->$LIPO_COMMANDS<- is available" 1>&2
fi
$DEBUG "debug: using the lipo command ->$LIPO<-"

if [ "X$THINTO" = "X" ]; then
    THINTO=`cat ~/.architectures 2>/dev/null`
fi

$NOISE "    THINTO = $THINTO"
$NOISE "    RECURSIVE = $RECURSIVE"
$NOISE "    FILES = $FILES"

if [ '(' "X$THINTO" = X ')' -o '(' "X$FILES" = X ')' ]; then
    echo $Usage 1>&2
    exit 1
fi

$INFO "info: thinning to architectures: $THINTO" 1>&2

init_directories

do_job_on_files $FILES

cleanup_directories

