#!/bin/bash
#
# asmcheck - scan RPM source packages for use of assembly language
# Copyright (C) 2012 Jon Masters <jcm@redhat.com>
#
# This is a simple proof of concept. A more complete solution would include
# tokenizing of actual source files and recognition of assembly mnemonics so
# as to handle the use of assembly in unusual places (e.g. header file arrays)
# but the current solution ought to catch the vast majority of obvious stuff.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 (only) of the GNU General
# Public License as published by the Free Software Foundation.

unpack_package() {
# We unpack the source package and stash it into a temporary directory
# which is set as an environment variable (package_dir) that can be used
# later on during processing of the package contents.

	source_package=$1
	if [ "z$source_package" == "z" ]
	then
		echo "Source package not specified"
		return 1
	fi

	package_name=$(rpm -qp --queryformat '%{name}' $source_package \
			2>/dev/null)
	ret=$?
	if [ $ret -ne 0 ]
	then
		echo "Could not determine package name"
		return 1
	fi

	tmp_dir=$(mktemp -d --tmpdir=`pwd` $package_name.XXXXXXXXXX \
			2>/dev/null)
	ret=$?
	if [ $ret -ne 0 ]
	then
		echo "Could not create temporary directory"
		return 1
	fi

	rpm --define="%_topdir $tmp_dir" -ivh $source_package >/dev/null 2>&1
	ret=$?
	if [ $ret -ne 0 ]
	then
		echo "Could not extract source package"
		return 1
	fi

	rpmbuild --define="%_topdir $tmp_dir" --nodeps -bp \
		$tmp_dir/SPECS/$package_name.spec >/dev/null 2>&1
	ret=$?
	if [ $ret -ne 0 ]
	then
		echo "Could not prepare source package"
		return 1
	fi

	package_dir="$tmp_dir/BUILD"

	return 0
}

do_scan() {
# Takes the name of the package we are scanning and directory to scan
# We're going to populate some arrays here with quantities of assembly usage
# 	files_ext 	- file names with known assembly extenstions
#	files_ext_idx	- number of files with known assembly extensions
#	files_asm_names - file names with known asm tags used
#	files_asm_count	- occurances of the asm tags in names
#	files_asm_idx	- number of files with known asm tags used
# We also set total_asm to the number of files containing assembly language

	input_package_name=$1
	input_directory_name=$2

	if [ "z$input_package_name" == "z" ]
	then
		echo "Source package name not specified"
		return 1
	fi
	if [ "z$input_directory_name" == "z" ]
	then
		echo "Source directory name not specified"
		return 1
	fi

	unset files_ext files_ext_idx files_tmp file
	unset match_count_gcc match_count_llvm match_count_total
	unset files_asm_idx files_asm_names files_asm_count total_asm

	pushd $input_directory_name >/dev/null 2>&1

	# scan for known assembly file extensions
	files_ext=($(find . -iname '*.s'))
	files_ext_idx=${#files_ext[@]}

	# scan for gcc or LLVM inline assembly tags
	files_tmp=($(find . \( -iname '*.h' -o -iname '*.hpp' -o -iname '*.c' \
    	  		    -o -iname '*.cc' -o -iname '*.cpp' \)))

	files_asm_idx=0
	for file in ${files_tmp[@]};
	do
		match_count_gcc=$(egrep -c \
		   '.*[_]*asm[_]*[ ]*\([ ]*".*|.*[_]*asm[_]*[ ]+.*\([ ]*".*' \
		   $file);
		match_count_llvm=$(egrep -c '.*module[ ]*asm[ ]".*' $file);
		match_count_total=$(($match_count_gcc+$match_count_llvm))
		if [ $match_count_total -ne 0 ]
		then
			files_asm_idx=${#files_asm_names[@]}
			files_asm_names[$files_asm_idx]=$file
			files_asm_count[$files_asm_idx]=$match_count_total
		fi
	done

	total_asm=$(($files_ext_idx+$files_asm_idx))

	popd >/dev/null 2>&1

	printf "Summary for package: $input_package_name\n"
	printf "\tAssembly source files: $files_ext_idx\n"
	printf "\tInline assembly usage: $files_asm_idx\n"
	printf "\tTotal number of files: $total_asm\n"
	printf "\t\n"

	if [ $files_ext_idx -ne 0 ]
	then
		printf "\tAssembly source files:\n"
		for file in ${files_ext[@]}
		do
			printf "\t\t$file\n"
		done
		printf "\t\n"
	fi
	if [ $files_asm_idx -ne 0 ]
	then
		tmp_idx=0;
		printf "\tInline assembly usage:\n"
		for file in ${files_asm_names[@]}
		do
			tmp_count=${files_asm_count[$tmp_idx]}
			printf "\t\t$file ($tmp_count)\n"
			tmp_idx=$((tmp_idx+1))
		done
	fi

	return 0
}

cleanup()
{
	if [ "z$tmp_dir" != "z" ]
	then
		if [ -d "$tmp_dir" ]
		then
			rm -rf "$tmp_dir"
		fi
	fi
}

usage()
{
	echo "Usage: $0 source_package"
	echo ""
	echo "Scan source_package for use of assembly language"
	echo ""
}

##########################
# Main program starts here
##########################

trap cleanup EXIT SIGINT SIGTERM

source_package=$1
if [ "z$source_package" == "z" ] || \
   [ "z$source_package" == "z-h" ] || [ "z$source_package" == "z--help" ]
then
	usage
	exit 0
fi

unpack_package $source_package # package_dir is set
ret=$?
if [ $ret -ne 0 ]
then
	echo "$?"
	echo "Aborting due to error preparing package"
	exit 1
fi

do_scan $source_package $package_dir
ret=$?
if [ $ret -ne 0 ]
then
	echo "Aborting due to error during package scan"
	exit 1
fi
