#!/bin/bash

# Task: go through the files in $RPM_BUILD_ROOT and
# relink symbolic links so that:
# links crossing top level directory boundaries (/usr/* -> /etc/*)
#   are absolute links
# links below one top level directory (/usr/bin/* -> /usr/lib/*)
#   are relative links
# NOTE: we're not doing this to fix a problem (as no matter how you
#   do it you fix one problem by creating another). We're doing it
#   so we can document this as policy - so you can rely on it

# Additional Task: check some usual errors arround symlinks e.g.
#   dangling symlinks or symlinks to init scripts in wrong location

# Author: Stephan Kulow <coolo@suse.de>
#
# major speedup: Tomas Cech <sleep_walker@suse.cz> and Michal Kubecek <mkubecek@suse.cz>

# If using normal root, avoid changing anything.
if [ -z "$RPM_BUILD_ROOT" ]; then
	exit 0
fi

strip_slashes() {
    local loc="${!1}"
    local tmp="${loc//\/\///}"
    while [ "$loc" != "$tmp" ]; do
	loc="$tmp"
	tmp="${loc//\/\///}"
    done
    eval "$1"="'$loc'"
}

eat_component() {
    loc="${!1}"
    local rslt="${loc#/*/}"
    if [ "$rslt" != "$loc" ]; then
        rslt="/$rslt"
    else
        rslt="${loc##/*}"
    fi
    eval "$1"="'$rslt'"
}

top_dir () {
    local rslt
    if [ "${1:0:1}" = / ]; then
        rslt="${1:1}"
        rslt="/${rslt%%/*}"
    else
        rslt="$1"
    fi
    eval "$2"="'$rslt'"
}

LC_ALL=
LANG=
LC_TIME=POSIX

basename_int() {
    echo "${1##*/}"
}

dirname_int() {
    local rslt="${1%/*}"
    if [ -n "$rslt" ]; then
        echo "$rslt"
    elif [ "${1:0:1}" = / ]; then
        echo /
    else
        echo .
    fi
}

cd $RPM_BUILD_ROOT

had_errors=0

while IFS="|" read link link_dir link_dest
do
    link="${link#.}"
    link_dir="${link_dir#.}"
    orig_link_dest="$link_dest"

    new_link_dest=NONE
    link_dir=$(dirname_int "$link")

    case $link_dest in
	.|..|../..) # link to current dir
	  continue ;;
	.*) # relative links up
	  link_dest="$link_dir/$link_dest"
	  ;;
        /*) # absolute links
	  ;;
        */*) # relative links down
	  link_dest="$link_dir/$link_dest"
	  ;;
	*) # the rest
	  continue
    esac

    # cleaning out double slash
    strip_slashes link_dest
    link_dest="${link_dest%.}"
    if [ "$link_dest" != '/' ]; then
	link_dest="${link_dest%/}"
    fi

    # eliminating /./ components
    link_dest="${link_dest//\/.\///}"

    counter=0
    # eliminating back references
    while [[ $link_dest =~ /\.\. ]]; do
       link_dest="$(sed "s@/[^/]\+/\.\.@@" <<< "$link_dest")"
       case $link_dest in
	/..*) # this is very mean
	    echo "ERROR: $link points to illegal $link_dest"
	    exit 1
	    ;;
	esac
	counter=$((counter + 1))
	if test $counter -gt 10; then
	    echo "ERROR: more than 10 back references in $link?"
	    exit 1
	fi
    done

    if test "$link" = "$link_dest"; then
      echo "ERROR: $link points to itself (as $orig_link_dest)"
      exit 1
    fi

    # black list
    case "$link,$link_dest" in
	*,/var/lib/named*)
	    continue;;
	/usr/etc,*|/usr/tmp,*)
	    continue;;
	*/share/texmf/*|/usr/share/terminfo/*)
	     continue;;
	/sbin/init,*) # don't create absolute symlink for /sbin/init
	     continue;;
	/etc/os-release,*) # don't create absolute symlink for /etc/os-release (boo#1028408).
	     continue;;
        *,/etc/alternatives/*) # white list alternatives
             ;;
	*share/automake-*)
	     echo "ERROR: link target $link points into automake directory"
	     echo " You might want to add a -c to the automake call (or just"
	     echo " skip the files from packaging)"
	     exit 1
	     ;;
	*,/opt/kde3/share/doc*/HTML/*/common) # white listed for not existant
	     ;;
	*,/usr/share/doc/kde/HTML/*/common) # white listed for not existant
	     ;;
	*,/proc/*) # links pointing into /proc file system
	     ;;
	*)
	  if test ! -L ./"$link_dest" && test ! -e "$link_dest" && test ! -e ./"$link_dest"; then
	    echo "ERROR: link target doesn't exist (neither in build root nor in installed system):"
	    echo "  $link -> $link_dest"
	    echo "Add the package providing the target to BuildRequires and Requires"
	    test "$NO_BRP_STALE_LINK_ERROR" != "yes" && had_errors=1
	  fi
	  ;;
    esac

    forced_absolute=0
    for prefix in /usr/X11R6/lib/X11 /usr/X11R6/include/X11 /usr/X11R6/lib/X11/app-defaults ; do
      if [[ $link =~ ^$prefix/ ]]; then
	 if ! [[ $link_dest =~ ^$prefix/ ]]; then
	    # not ok
	    forced_absolute=1
         fi
      fi
    done

    dest_dir=$(dirname_int "$link_dest")

    # figuring out (currently) correct destination
    if [ "$link_dir" = "$dest_dir" -o  "$dest_dir" = "." ]; then
	new_link_dest=$(basename_int "$link_dest")
    else
	# figuring out top level directory
        top_dir "$link" top_link
        top_dir "$link_dest" top_dest
	if [ "$forced_absolute" = 0 -a "$top_link" = "$top_dest" ]; then # supposed to be relative

	    # first we need to cut out the common prefix
	    link_tmp=$link
	    while [ "$top_link" = "$top_dest" ]; do
		link_orig=$link_tmp
		dest_orig=$link_dest
		link_tmp="/${link_tmp#/*/}"
		link_dest="/${link_dest#/*/}"
		top_link="${link_tmp#/}"
		top_link="/${top_link%%/*}"
		top_dest="${link_dest#/}"
		top_dest="/${top_dest%%/*}"

		if [ "$top_dest" = "$dest_orig" -o "$top_link" = "$link_orig" ]; then
		    link_tmp="$link_orig"
		    link_dest="$dest_orig"
		    break
		fi
	    done

	    # now we add a .. for every directory component
	    link_tmp=$(dirname_int "$link_tmp")

	    if [ "$link_tmp" = "$link_dest" ]; then
		new_link_dest=.
	    elif [ "$link_tmp" != "/" ]; then # we have a directory component
		link_rel=

		while [ -n "$link_tmp" ]; do
                    eat_component link_tmp
		    link_rel="../$link_rel"
		done

		new_link_dest="$link_rel/$link_dest"
		strip_slashes new_link_dest

	    else
		# get rid of the slash
		link_dest="${link_dest#/}"
		new_link_dest="$link_dest"
	    fi
	else
	    new_link_dest="$link_dest"
	fi
    fi

    if [ "$new_link_dest" != NONE -a "$new_link_dest" != "$orig_link_dest" ]; then
	echo "INFO: relinking $link -> $new_link_dest (was $orig_link_dest)"
	rm ./"$link" && ln -s "$new_link_dest" ./"$link"
    fi
done < <(find . -type l -printf '%p|%h|%l\n')

if test "$had_errors" = 1; then
    exit 1
fi
