#!/bin/bash
set -e
APP_SVNID='$HeadURL: http://dione.no-ip.org/svn/ade/tags/1.7.6/bin/adeinst.sh $ $LastChangedRevision: 5986 $'

. $(ade-config ade_include_prefix)/ade.sh

ADEINST_DEFINED_ERRORS=(
    "KEY=ADEINST_ERR_MISC; FMT=\"%s\""
    "KEY=ADEINST_ERR_UNEXPECTED; FMT=\"%s: expected %s, got %s\""
    "KEY=ADEINST_ERR_ACCESS; FMT=\"%s: can't %s\""
    "KEY=ADEINST_ERR_OS; FMT=\"%s: failed\""
)

adeinst()
{
    local OPTVAL LISTPATHS RC TEMP ERRSTACK_REF
    local ISRELABELABLE_FLAG SANDPIT_DIR REF_FILE OUT_FILE PROCOPTS_CODEFRAG
    local -a DOLLARAT

    ERRSTACK_REF="$1"
    shift 

    ###########################################################################
    #
    #  SET ADE OPTIONS
    #
    ###########################################################################

    #  Register application-specific errors
    ade_err_registerdefderrs ADEINST_DEFINED_ERRORS

    ###########################################################################
    #
    #  PROCESS OPTIONS
    #
    ###########################################################################

    #  Defaults for options
    OPT_OPMODE=oneortwo
    OPT_SIMULATE=false
    OPT_PERMS=auto
    OPT_GROUP=nochange
    OPT_OWNER=nochange

    #  Register adeinst's options 
    ade_opt_register "$ERRSTACK_REF" -o ng:o:m:D --longoptions=simulate,group:,owner:,perms: --callback-template="adeinst_opt_handler_%s" || return $?
    ade_msg_register "$ERRSTACK_REF" adeinst_usage adeinst_version adeinst_listpaths || return $?

    #  Process options
    ade_opt_process "$ERRSTACK_REF" NEW_DOLLAR_AT "$@" || return $?
    set -- "${NEW_DOLLAR_AT[@]}" 

    ##########################################################################
    #
    #  ARGUMENT PROCESSING
    #
    ##########################################################################

    [ "X$1" != X ] || ade_msg_usage "$ERRSTACK_REF"

    ##########################################################################
    #
    #  FORKING AND LOCKING
    #
    ##########################################################################

    #ade_lck_lock "$ERRSTACK_REF" DUMMY ade_lck_getgenericlockfilename || {
    #    RC=$?
    #    ade_err_error "$ERRSTACK_REF" ADEINST_ERR_SEEABOVE
    #    return $RC
    #}

    ##########################################################################
    #
    #	GUTS START HERE
    #
    ##########################################################################

    #
    #  Sanity check and option rewrites
    #
    #  OPMODE 3 is specified by a command line option, but 1 and 2
    #  are determined by argument types, now check this.
    if [ $OPT_OPMODE = oneortwo ]; then
        #  If two args, then last may be file or non-existent.
        if [ $# = 2 ] && [ -f "$1" ] && [ -f "$2" -o ! -e "$2" ]; then
            OPT_OPMODE=one
        #  If two or more args, then last must be dir.
        elif [ $# -ge 2 ]; then
            for ((I=1; $I<=$#; I++)); do
                if [ $I -lt $# ]; then
                    eval "[ -f \"\${$I}\" ]" || ALL_FILES_EXIST=false
                else
                    eval "[ -d \"\${$I}\" ]" || LAST_IS_DIR=false
                fi
            done
            $LAST_IS_DIR && $ALL_FILES_EXIST && OPT_OPMODE=two
        fi
    fi
    #  If mode still not determined then subtle usage error.
    if [ $OPT_OPMODE = oneortwo ]; then
        ade_err_error "$ERRSTACK_REF" ADEINST_ERR_MISC $SRCFILE "non-existent or badly typed arguments"
        return $ADE_ERR_FAIL
    fi

    #  Decide directory permissions based on desired file permissions
    #  (if what is desired is a *directory* permission then this is
    #  superfluous but not dangerous).
    if [ "X$OPT_PERMS" = Xauto ]; then
        DIRMODE=auto
    else
        for I in `echo $OPT_PERMS | sed -e 's/^.\(...\)$/\1/' -e 's/\(.\)/\1 /g'`; do
            case $I in
                4) I=5 ;;
                6) I=7 ;;
                *) ;;
            esac
            DIRMODE="$DIRMODE$I"
        done
    fi

    [ $OPT_OPMODE = one   ] && [ "X$2" = X -o "X$3" != X ] && ade_msg_usage "$ERRSTACK_REF" 
    [ $OPT_OPMODE = two   ] && [ "X$2" = X               ] && ade_msg_usage "$ERRSTACK_REF"
    [ $OPT_OPMODE = three ] && [ "X$1" = X               ] && ade_msg_usage "$ERRSTACK_REF"

    #  Initially, directories will be created with permissions subject to the umask
    #  and files will be created (with the 'cp' command) with permissions which are the
    #  more restrictive of the the source file's original permissions and the umask.
    #  The permissions we want *here in this program* should will be either 755 or 644
    #  or something user-specified: umask is irrelevent and source permissions are relevent
    #  only in deciding whether to make something executable or not. fixperms() remains
    #  simplest if we decide the *final* permissions based on the target's initial
    #  permissions (rather than needing to tell fixperms() the name of the source file
    #  so that it could determine whether to grant executability. This means that the
    #  cp we're about to do should preserve the executability status, so that fixperms()
    #  can use it. To do that we merely need to make sure that the umask is not so
    #  restrictive as to *strip* executability, which means the umask's first digit should
    #  be 0; the other digits don't matter. Actually the other permissions *do* matter,
    #  but only because we don't want to risk temporarily exposing the contents of files
    #  that may have sensitive content and which could be copied in this brief time window.
    #  Hence the umask's other numbers being 7.
    umask 077
    case $OPT_OPMODE in
        one)   process_mode_one "$ERRSTACK_REF" "$@" ;;
        two)   process_mode_two "$ERRSTACK_REF" "$@" ;;
        three) process_mode_three "$ERRSTACK_REF" "$@" ;;
    esac
    RC=$?

    #  yes, of course, it does this anyway, but this is clearer.
    return $RC
}
    
adeinst_opt_handler_n()
{
    adeinst_opt_handler_simulate "$@"
}

adeinst_opt_handler_g()
{
    adeinst_opt_handler_group "$@"
}

adeinst_opt_handler_o()
{
    adeinst_opt_handler_owner "$@"
}

adeinst_opt_handler_m()
{
    adeinst_opt_handler_perms "$@"
}

adeinst_opt_handler_D()
{
    local ERRSTACK_REF="$1"; shift 

    OPT_OPMODE=three

    return $ADE_ERR_OK
}

adeinst_opt_handler_simulate()
{
    local ERRSTACK_REF="$1"; shift 

    OPT_SIMULATE="$1"

    return $ADE_ERR_OK
}

adeinst_opt_handler_group()
{
    local ERRSTACK_REF="$1"; shift 

    OPT_GROUP="$1"

    return $ADE_ERR_OK
}

adeinst_opt_handler_owner()
{
    local ERRSTACK_REF="$1"; shift 

    OPT_OWNER="$1"

    return $ADE_ERR_OK
}

adeinst_opt_handler_perms()
{
    local ERRSTACK_REF="$1"; shift 

    OPT_PERMS="$1"

    return $ADE_ERR_OK
}

adeinst_usage() 
{
    local ERRSTACK_REF="$1"; shift
    local USAGETEXT_REF="$1"; shift
    local PASSNO=$1; shift

    if [ $PASSNO = 1 ]; then
        eval "$USAGETEXT_REF=\"...\""
    elif [ $PASSNO = 2 ]; then
        eval "$USAGETEXT_REF=\"\
         -n        | --simulate              simulate
         -D                                  create directories
         -m        | --perms=<mode>          install with mode <mode>
         -o        | --owner=<owner>         install with owner <owner>
         -g        | --group=<group>         install with group <group>\
        \""
    fi

    return $ADE_ERR_OK
}

adeinst_listpaths() 
{
    local ERRSTACK_REF="$1"; shift
    local PATHLIST_REF=$1; shift

    eval "$PATHLIST_REF=\"\
    \""

    return $ADE_ERR_OK
} 

adeinst_version() 
{
    local ERRSTACK_REF="$1"; shift
    local VERSION_REF=$1; shift

    ade_smf_extractversionfromsvnstring "$ERRSTACK_REF" "$APP_SVNID" "$VERSION_REF"

    return $ADE_ERR_OK
} 

process_mode_one()
{
    local ERRSTACK_REF="$1"; shift
    local SRCFILE="$1"; shift
    local DSTFILE="$1"; shift
    local RC

    ade_err_debug "$ERRSTACK_REF" 4 "process_mode_one: sof (SRCFILE=$SRCFILE, DSTFILE=$DSTFILE)"

    #  source file must exist and a be a file
    [ -r $SRCFILE ] || {
        ade_err_error "$ERRSTACK_REF" ADEINST_ERR_ACCESS $SRCFILE "access"
        return $ADE_ERR_FAIL
    }
    [ -f $SRCFILE ] || {
        ade_err_error "$ERRSTACK_REF" ADEINST_ERR_UNEXPECTED $SRCFILE "file" "not file"
        return $ADE_ERR_FAIL
    }

    #  destination is a file, or doesn't exist so file can be written
    #  HP-UX doesn't have '-e', 'cos '\! -e' would be perfect here.
    [ -f $DSTFILE -o "X`ls $DSTFILE 2>/dev/null`" = X ] || {
        ade_err_error "$ERRSTACK_REF" ADEINST_ERR_UNEXPECTED $DSTFILE "file" "not a file"
        return $ADE_ERR_FAIL
    }

    #  make leading directory components
    DIR_COMPONENT=`dirname $DSTFILE`
    mkdir_with_fixes "$ERRSTACK_REF" $DIR_COMPONENT || {
        RC=$?
        ade_err_error "$ERRSTACK_REF" ADEINST_ERR_ACCESS "$DIR_COMPONENT" "create"
        return $RC
    }

    #  copy file in
    ade_err_debug "$ERRSTACK_REF" 4 "process_mode_one: OPT_PERMS=$OPT_PERMS"
    #  Fri Dec 26 11:41:16 CET 2008 - Alexis Huxley
    #  If an old version of the file is already present and has the wrong permissions
    #  (e.g. it is 644 and the new source to copy in is 755) and the user has not
    #  specified explicit permissions then the cp will preserve the old permissions
    #  and they won't be corrected. The simplest way to deal with this is to ensure
    #  that the destination file doesn't exist. (The test is to make sure the programmer
    #  hasn't overlooked something really stupid.)
    [ ! -f $DSTFILE ] || rm -f $DSTFILE
    cp $SRCFILE $DSTFILE || {
        ade_err_error "$ERRSTACK_REF" ADEINST_ERR_OS "cp" 
        return $ADE_ERR_FAIL
    }

    #  fix permissions of copied file
    fixperms "$ERRSTACK_REF" $DSTFILE "$OPT_OWNER" "$OPT_GROUP" "$OPT_PERMS" || {
        RC=$?
        ade_err_error "$ERRSTACK_REF" ADEINST_ERR_MISC "$DIR: can't fix owner/group/modes"
        return $RC
    }

    #
    #  If we get this far everything is ok.
    #

    return $ADE_ERR_OK
}

process_mode_two()
{
    local ERRSTACK_REF="$1"; shift
    local DSTDIR RC

    ade_err_debug "$ERRSTACK_REF" 4 "process_mode_two: sof"

    eval DSTDIR=\${$#}

    while :; do
        #  break out when we run out of files (i.e. args excl. last arg)
        [ "X$2" = X ] && break
    
        #  use the mode one processor
        ade_err_debug "$ERRSTACK_REF" 4 "process_mode_two: calling process_mode_one($1, $DSTDIR/$(basename "$1")) ..."
        process_mode_one "$ERRSTACK_REF" "$1" "$DSTDIR/$(basename "$1")" || {
            RC=$?
            ade_err_error "$ERRSTACK_REF" ADEINST_ERR_MISC "$1: can't process"
            return $RC
        }
	shift
    done
    
    #
    #  If we get this far everything is ok.
    #

    return $ADE_ERR_OK
}

process_mode_three()
{
    local ERRSTACK_REF="$1"; shift
    local DIR

    for DIR in "$@"; do
        mkdir_with_fixes "$ERRSTACK_REF" "$DIR" || {
            RC=$?
            ade_err_error "$ERRSTACK_REF" ADEINST_ERR_ACCESS "$DIR" "create"
            return $RC
        }
    done

    #
    #  If we get this far everything is ok.
    #

    return $ADE_ERR_OK
}

mkdir_with_fixes()
{
    local ERRSTACK_REF="$1"; shift
    local DIR="$1"; shift

    #  stop recursion when have worked up to something that exists
    [ -d $DIR ] && return 0

    #  make parents
    mkdir_with_fixes "$ERRSTACK_REF" `dirname $DIR` || {
        RC=$?
        ade_err_error "$ERRSTACK_REF" ADEINST_ERR_ACCESS "$DIR" "create"
        return $RC
    }

    #  make this directory ...
    mkdir $DIR 2>/dev/null || {
        ade_err_error "$ERRSTACK_REF" ADEINST_ERR_ACCESS $DIR "create"
        return $ADE_ERR_FAIL
    }

    #  ... and fix its perms
    ade_err_debug "$ERRSTACK_REF" 4 "mkdir_with_fixes: calling fixperms with DIRMODE=$DIRMODE"
    fixperms "$ERRSTACK_REF" $DIR "$OPT_OWNER" "$OPT_GROUP" "$DIRMODE" || {
        RC=$?
        ade_err_error "$ERRSTACK_REF" ADEINST_ERR_MISC "$DIR: can't fix owner/group/modes"
        return $RC
    }

    #
    #  If we get this far everything is ok.
    #

    return $ADE_ERR_OK
}
    
fixperms()
{
    local ERRSTACK_REF="$1"; shift
    local THING="$1"; shift
    local OWNER="$1"; shift
    local GROUP="$1"; shift
    local MODE="$1"; shift

    ade_err_debug "$ERRSTACK_REF" 4 "fixperms: THING=$THING, OWNER=$OWNER, GROUP=$GROUP, MODE=$MODE"

    [ $OPT_SIMULATE = true ] || {
        if [ "X$MODE" = Xauto -a -x "$THING" ]; then
             MODE=755
        elif [ "X$MODE" = Xauto -a ! -x "$THING" ]; then
             MODE=644
        fi
        chmod $MODE  "$THING" || {
            ade_err_error "$ERRSTACK_REF" ADEINST_ERR_ACCESS $THING "chmod"
            return $ADE_ERR_FAIL
        }
    }
    [ "X$GROUP" = Xnochange ] || [ $OPT_SIMULATE = true ] || chgrp $GROUP "$THING" || {
        ade_err_error "$ERRSTACK_REF" ADEINST_ERR_ACCESS $THING "chgrp"
        return $ADE_ERR_FAIL
    }
    #  This should be last to prevent problems with the other two changes
    [ "X$OWNER" = Xnochange ] || [ $OPT_SIMULATE = true ] || chown $OWNER "$THING" || {
        ade_err_error "$ERRSTACK_REF" ADEINST_ERR_ACCESS $THING "chown"
        return $ADE_ERR_FAIL
    }

    #
    #  If we get this far everything is ok.
    #

    return $ADE_ERR_OK
}

ade_gep_main adeinst "$@"
