#!/bin/bash

test "$1" = "--verbose" && { VERBOSE=true ; shift ; }
test "$1" = "--batchmode" && { BATCHMODE=true ; shift ; }
DIR_TO_CHECK="$1"
DESTINATIONDIR="$2"
OSC_MODE=""
test -n "$DIR_TO_CHECK" || DIR_TO_CHECK=`pwd`
BASE_DIR=$(dirname $0)
BASE_DIR=${BASE_DIR:-.}
HELPERS_DIR="$BASE_DIR/helpers"
$HELPERS_DIR/check_input_filename "$DIR_TO_CHECK" || exit 1
. $HELPERS_DIR/functions
test -z "$DESTINATIONDIR" -a -d "$DIR_TO_CHECK/.osc" && {
	if [ -d "$DIR_TO_CHECK/.git" ]; then
            DESTINATIONDIR="$DIR_TO_CHECK"
	else
            OSCLIBVER="$(<.osc/_osclib_version)"
            case "$OSCLIBVER" in
              1.0)
                DESTINATIONDIR="$DIR_TO_CHECK/.osc"
                DOTOSCDIR="$DIR_TO_CHECK/.osc"
                ;;
              2.0)
                DESTINATIONDIR="$DIR_TO_CHECK/.osc/sources"
                DOTOSCDIR="$DIR_TO_CHECK/.osc"
                ;;
              *)
                echo "osclib version $OSCLIBVER not yet supported by source_validator" && exit 2
                ;;
            esac
	fi
	OSC_MODE="true"
}

RETURN=0

#
#  cleanup_and_exit
#
cleanup_and_exit () {
    if [ -n "$TMPDIR" ];then
    	rm -rf $TMPDIR
    fi
    exit $1
}

#
# display a warning if the file is not in the spec file sources
#
warn_on_unmentioned_files () {
  grep -q -F -a -x -- $1 $TMPDIR/sources || echo "(W) Attention, \"$1\" is not mentioned in spec files as source or patch."
}

test "$VERBOSE" = true && echo -n "- checking if needed files are present and none stale "
#
# first make my TMPDIR
#
export TMPDIR=`mktemp -d -t check_if_valid_source_dir-XXXXXX 2>/dev/null || mktemp -d /var/tmp/check_if_valid_source_dir-XXXXXX` || cleanup_and_exit 1

#
# now create list of Sources.
#
MY_ARCH=`uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ -e s/arm.*/arm/ -e s/sa110/arm/`
case $MY_ARCH in
  i386)
    MY_ARCH="%ix86"
    ;;
  arm)
    MY_ARCH="%arm"
    ;;
esac

while read recipe flavor; do
	$HELPERS_DIR/spec_query --specfile "${DIR_TO_CHECK}/${recipe}" --print-sources --buildflavor "$flavor" \
	--no-conditionals --keep-name-conditionals --disambiguate-sources \
        >> "$TMPDIR/sources" 2>"$TMPDIR/sources.err"
        # ignore expand errors with macro scripts
	sed -i "/can't expand %(...)/d" "$TMPDIR/sources.err"
	# ignore macro nesting reported by build script
	sed -i "/^Warning: spec file parser  line [0-9]\+: macro too deeply nested$/d" "$TMPDIR/sources.err"
	if [ -s "$TMPDIR/sources.err" ]; then
	    echo "Unable to extract sources from spec - spec_query failed:"
	    cat "$TMPDIR/sources.err"
	    cleanup_and_exit 1
	fi
	$HELPERS_DIR/spec_query --specfile "${DIR_TO_CHECK}/${recipe}" --print-moveassets --buildflavor "$flavor" \
	--no-conditionals --keep-name-conditionals --disambiguate-sources \
        | sed 's,^.*/,,' \
        >> "$TMPDIR/moveassettargets" 2>"$TMPDIR/sources.err"
done < <(spec_build_flavors "$DIR_TO_CHECK")
for i in "$DIR_TO_CHECK"/*.dsc ; do
	test -f "$i" || continue
	( sed -ne '/^Files:/,$p' < "$i" | sed -e 1d | sed -e '/^[^ ]/,$d' | while read debchk debsize debfile ; do echo "$debfile" ; done ) >> $TMPDIR/sources
done

# Add debian only patches to the allowed sources list.
# but strip tailing -p1 arguments
test -f "$DIR_TO_CHECK/debian.series" && \
  sed -e '/^$/d' -e '/^#/d' -e 's,[ \t]*-p[0123456789]*$,,' \
  "$DIR_TO_CHECK/debian.series" >> $TMPDIR/sources

test -f $TMPDIR/sources || cleanup_and_exit

#
# check if all Sources, patches and the icon are present
#
touch $TMPDIR/sources.t

for i in `cat $TMPDIR/sources` ; do
	echo "${i##*/}" >> $TMPDIR/sources.t
done
mv $TMPDIR/sources.t $TMPDIR/sources

# XML validate files starting with _..
if [ -x $(type -p xmllint) ]; then
    for i in "$DIR_TO_CHECK/_service"; do
        test -f "$i" || continue
        BASE="${i##*/}"

        xmllint --format "$i" >/dev/null || {
            echo "ERROR: $(basename -- $i) is not valid XML"
            RETURN=2
            continue
        }

        # Check if _service is sane
        if [ "$BASE" = "_service" ]; then
            xmllint --format "$i" > $TMPDIR/_service

            if grep -E -q "service .*mode=." $TMPDIR/_service \
                    && ! grep -E -q "service .*mode=.(disabled|manual|buildtime|localonly)" \
                    $TMPDIR/_service; then
                echo "(W) openSUSE: projects only allow 'manual' or 'buildtime' services (localonly/disabled are deprecated)."
            fi
        fi

    done
fi

obscpio_file_list()
{
    if ! [ -e "$TMPDIR/obscpio_sources" ]; then
        touch "$TMPDIR/obscpio_sources"
        for i in "$DIR_TO_CHECK/"*.obscpio; do
            if [ -e "$i" ]; then
                cpio --list --quiet < "$i" >> "$TMPDIR/obscpio_sources"
            fi
        done
        for i in "$DIR_TO_CHECK/"*; do
            if [ -d "$i" ]; then
                echo "${i##*/}" >> "$TMPDIR/obscpio_sources"
            fi
        done
    fi

    cat "$TMPDIR/obscpio_sources"
}

check_tracked()
{
    local file=${1##*/}
    if grep -q "^$file" "$TMPDIR/moveassettargets"; then
        # it will generated at build time
        return 0
    fi
    if test "$OSC_MODE" = "true" ; then
        if test -x "$(type -p git)" && test -d "$DIR_TO_CHECK/.git"; then
            if git --git-dir="$DIR_TO_CHECK/.git" ls-files --error-unmatch "$file" > /dev/null; then
                return 0
            fi
            return 1
        fi
        if test -f "$DESTINATIONDIR/$file" && ! grep -qsFx "$file" "$DOTOSCDIR/_to_be_deleted"; then
            return 0
        fi
        if test -f "$DESTINATIONDIR/${file/\.tar*/}.obscpio"; then
            # assume it will generated on builtime based of the archive
            return 0
        fi
        if grep -qsFx "$file" "$DOTOSCDIR/_to_be_added"; then
            return 0
        fi
        if obscpio_file_list | grep -qFx "$file"; then
            return 0
        fi
        echo "ERROR: $file mentioned in spec file is not tracked."
        return 1
    fi
    if ! test -f "$DIR_TO_CHECK/$file"; then
        if test -f "$DIR_TO_CHECK/${file/\.tar*/}.obscpio"; then
            # assume it will generated on builtime based of the archive
            return 0
        fi
        if obscpio_file_list | grep -qFx "$file"; then
            return 0
        fi
        echo "ERROR: $file mentioned in spec file does not exist."
        return 1
    fi
}

if ! test -f "$DIR_TO_CHECK/_service"; then
    for HASTOBETHERE in $(<$TMPDIR/sources); do
        check_tracked "$HASTOBETHERE" || RETURN=2
    done
fi

for f in $(<$TMPDIR/sources); do
    if test ! -f "$DIR_TO_CHECK/$f"; then
        continue
    fi
    case $f in
        *.dif|*.diff|*.patch)
            # Check for empty or ill-formatted patches
            if ! test -s  "$DIR_TO_CHECK/$f"; then
                echo "ERROR: Patch file $f is zero bytes long, remove empty patch?"
                RETURN=2
            fi
            if test "$(type -p patch)"; then
                patch -s -t --dry-run -i "$DIR_TO_CHECK/$f" >/dev/null
                # example a file that only contains a commit header but does not actually
                # modify any files gives this error message. we treat it as an error
                # because it is most likely a failed quilt run/erratic vulnerability patch
                # backport
                if test $? = 2; then
                    echo "ERROR: Patch file $f does not validate."
                    RETURN=2
                fi
            fi
            ;;
      *.zip)
          # Check for encrypted zip files
          if ! unzip -qq -P '' -t "$DIR_TO_CHECK/$f"; then
              echo "ERROR: zip file $f failed to extract, may be corrupted or encrypted. This makes source review impossible and hence is forbidden."
              RETURN=2
          fi
          ;;
    esac
done

#
# now check if everything is marked in spec files.
#
find "$DIR_TO_CHECK" -mindepth 1 -maxdepth 1 | while read -s -r i; do
    BASE=${i##*/}
    # files to display first
    case "$BASE" in
	config-dist.sh | \
	get_version_number.sh | \
	get_release_number.sh | \
	check-build.sh | \
	baselibs.conf )
	    if test -n "$DESTINATIONDIR" -a -f "$DESTINATIONDIR/$BASE" && cmp -s "$DIR_TO_CHECK/$BASE" "$DESTINATIONDIR/$BASE" ; then
		echo "- package has $BASE: (unchanged)"
	    else
		echo "- package has $BASE: (new or modified)"
		echo "--------------------------------------------------------------"
		if test -f "$DESTINATIONDIR/$BASE" ; then
			diff -u "$DESTINATIONDIR/$BASE" "$DIR_TO_CHECK/$BASE"
		else
			cat "$DIR_TO_CHECK/$BASE"
		fi
		echo "--------------------------------------------------------------"
		if test "$BATCHMODE" != true ; then
		    echo -n "Is this correct? [N/y/d] (y to ignore) "
		    read ANSWER
		    test "$ANSWER" = y -o "$ANSWER" = Y || {
			if test "$ANSWER" = d -o "$ANSWER" = D ; then
			    rm -v -- "$DIR_TO_CHECK/$BASE"
			else
			    echo ok, please fix it...
			    test "$RETURN" != "2" && RETURN=1
			fi
		    }
	    	else
		    echo "###ASK $DIR_TO_CHECK/$BASE"
	    	fi
	    fi
            # we want baselibs.conf in the src.rpm
            if test "$BASE" = "baselibs.conf"; then
              warn_on_unmentioned_files "$BASE"
            fi

	    ;;
	*rpmlintrc)
	    if test -n "$DESTINATIONDIR" -a -f "$DESTINATIONDIR/$BASE" && cmp -s "$DIR_TO_CHECK/$BASE" "$DESTINATIONDIR/$BASE" ; then
		echo "- package has $BASE: (unchanged)"
	    else
		echo "- package has $BASE: (new or modified)"
		echo "--------------------------------------------------------------"
		if test -f "$DESTINATIONDIR/$BASE" ; then
			diff -u "$DESTINATIONDIR/$BASE" "$DIR_TO_CHECK/$BASE"
		else
			cat "$DIR_TO_CHECK/$BASE"
		fi
		echo "--------------------------------------------------------------"
		if test "$BATCHMODE" != true ; then
		    echo -n "Is this correct? [N/y/d] (y to ignore) "
		    read ANSWER
		    test "$ANSWER" = y -o "$ANSWER" = Y || {
			if test "$ANSWER" = d -o "$ANSWER" = D ; then
			    rm -v -- "$DIR_TO_CHECK/$BASE"
			else
			    echo ok, please fix it...
			    test "$RETURN" != "2" && RETURN=1
			fi
		    }
		else
			echo "###ASK $DIR_TO_CHECK/$BASE"
		fi
	    fi
            warn_on_unmentioned_files "$BASE"


	    LINE=$(grep -E "^[^#]*setBadness" "$DIR_TO_CHECK/$BASE")
	    if [ "$LINE" != "" ]; then
	        if test "$BATCHMODE" != true ; then
		    echo "ERROR: Found possibly illegal rpmlintrc line:"
		    echo "       $LINE"
		    echo -n "Is this correct? [N/y] (y to ignore) "
		    read ANSWER
		    test "$ANSWER" = y -o "$ANSWER" = Y || {
			echo ok, please fix it...
			test "$RETURN" != "2" && RETURN=1
		    }
		else
		    echo "###ASK $DIR_TO_CHECK/$BASE"
		fi
	    fi
	    ;;
	.*.spec)
	    rm -v -- "$DIR_TO_CHECK/$BASE"
	    ;;
	*.changes | \
	*.lsm | \
	*.spec | \
	*.spec.in | \
	*.changes.in | \
	*.test | \
	MD5SUMS | \
	MD5SUMS.meta | \
	Makefile | \
	README.autobuild | \
	bigpack | \
	prepare-build.sh | \
	minmem | \
	needed_space_in_mb | \
	pre_checkin.sh | \
	newestfile | \
	.osc | \
	.bsinfo | \
	.bsnote | \
	.check_if_valid_source_dir | \
	.setup | \
	*.dsc | \
	*.obscpio | \
	*.obsinfo | \
	ready | \
	_* | \
	*.orig | \
	*~ | \
	.git | \
	.gitattributes | \
	.gitmodules | \
	.gitignore | \
	.emacs.backup | \
	PKGBUILD | \
	appimage.yml | \
	debian.changelog | \
	debian.compat | \
	debian.control | \
	debian.copyright | \
	debian.lintian-overrides | \
	debian.manpages | \
	debian.postinst | \
	debian.postrm | \
	debian.preinst | \
	debian.prerm | \
	debian.rules | \
	debian.series | \
	debian.tar.gz | \
	debian.triggers | \
	debian.format | \
	debian.*.default | \
	debian.*.dirs | \
	debian.*.docs | \
	debian.*.files | \
	debian.*.init | \
	debian.*.install | \
	debian.*.logrotate | \
	debian.*.manpages | \
	debian.*.postinst | \
	debian.*.postrm | \
	debian.*.preinst | \
	debian.*.prerm | \
	debian.*.triggers | \
	debian.*.lintian-overrides )
	    ;;
	*)
            grep -q -F -a -x -- "$BASE" $TMPDIR/sources && continue
            test -f "$DIR_TO_CHECK/_service" && grep -E -q 'mode=.remoterun' "$DIR_TO_CHECK/_service" && continue
            test -f "$DIR_TO_CHECK/_service" && grep -E -q 'name=.product_converter' "$DIR_TO_CHECK/_service" && continue
            # be a bit more relaxed for osc, it won't upload directories anyway
            [ -d "$DIR_TO_CHECK/$BASE" ] && [ -d  "$DIR_TO_CHECK/.osc" ] && continue
            # and source services on server side
            [ -d "$DIR_TO_CHECK/$BASE" ] && [ -d "$DIR_TO_CHECK/.old" ] && continue

            warn_on_unmentioned_files "$BASE"

            if test "$RETURN" != "2" ; then
                if [ -d "$DIR_TO_CHECK/$BASE" ] ; then
                    # be a bit more relaxed for osc, it won't upload directories anyway
                    if [ ! -d "$DIR_TO_CHECK/.osc" ] ; then
                        echo "!! $BASE is a directory !!"
                        if test "$BATCHMODE" != true ; then
                            echo    "     remove subtree with 'r'"
                            echo    "ignore and continue with 'y'"
                            echo -n "Is this correct? [N/y/r] "
                            read ANSWER
                            test "$ANSWER" = y -o "$ANSWER" = Y || {
                            # r for remove is also accepted, to make it compatible with osc itself
                            if test "$ANSWER" = d -o "$ANSWER" = D -o "$ANSWER" = r -o "$ANSWER" = R; then
                                rm -Rfv -- "$DIR_TO_CHECK/$BASE"
                            else
                                echo ok, please fix it...
                                test "$RETURN" != "2" && RETURN=1
                            fi
                        }
                    else
                        echo "###ASK -r $DIR_TO_CHECK/$BASE"
                    fi
                fi
	      else
		if test "$BATCHMODE" != true ; then
		    echo -n "Is this correct? [N/y/d] (y to ignore) "
		    read ANSWER
		    test "$ANSWER" = y -o "$ANSWER" = Y || {
			if test "$ANSWER" = d -o "$ANSWER" = D ; then
			    rm -v "$DIR_TO_CHECK/$BASE"
			else
			    echo ok, please fix it...
			    test "$RETURN" != "2" && RETURN=1
			fi
		    }
		else
		    echo "###ASK $DIR_TO_CHECK/$BASE"
		fi
	      fi
	    fi
	    ;;
    esac
done

test "$VERBOSE" = true && echo done

cleanup_and_exit $RETURN
