#!/bin/bash
#
# qpkg - query portage package system for various information
#
# Copyright (c) Vitaly Kushneriuk <vitaly_kushneriuk@yahoo.com>
# This program is distributed under the terms of GPL version 2.
#
# Maintainer: Brandon Low <lostlogic@gentoo.org>
# Additional code thanks to:
#	      Josh Goebel <dreamer@firesedge.org>
#
# $Header: /var/cvsroot/gentoo-x86/app-portage/gentoolkit/files/scripts/qpkg,v 1.7 2003/11/30 20:26:55 genone Exp $
ID='$Id: qpkg,v 1.7 2003/11/30 20:26:55 genone Exp $'
VERSION=0.`echo ${ID} | cut -d\  -f3`

PROG=`basename ${0}`

# Parse args
verb=0
group="*"
params=${#}
while [ ${#} -gt 0 ]
do
	a=${1}
	shift
	case "${a}" in

	-h|--help)
		usage=y
		break
		;;

	-i|--info)
		info=y
		;;

	-d|--dups)
		dups=y
		inst=y
		;;

	-q|--query-deps)
		query=y
		;;

	-s|--slot)
		slot=y
		;;

	-f|--find-file)
		ffind=y
		inst=y
		;;

	-fp|--find-pattern)
		ffind=y
		fpat=y
		inst=y
		;;

	-I|--installed)
		inst=y
		;;

	-m|--masked)
		grepmask="-L"
		;;

	-n|--non-masked)
		grepmask="-l"
		;;

	-U|--uninstalled)
		uninst=y
		;;

	-g|--group)
		group=$1
		shift
		;;

	-l|--list)
		list=y
		inst=y
		;;

	-ct|--check-time|-tc|--time-check)
		tcheck=y
		inst=y
		;;

	-cm|--check-md5|-mc|--md5-check)
		mcheck=y
		inst=y
		;;

	-c|--check)
		mcheck=y
		tcheck=y
		inst=y
		;;

	-v|--verbose)
		let $((verb++))
		;;

	-vv)
		let $((verb++))
		let $((verb++))
		;;

	-nc|--no-color|--nocolor|--no-colors|--nocolors)
		nocolor=y
		;;

	-*)
		echo -e ${CY}${PROG}${NO}:${YL} Invalid option ${RD}$a 1>&2
		usage=y
		break
		;;
	*)
		if [ -n "${arg}" ]; then
			echo -e ${CY}${PROG}: ${YL}Only one argument supported
			usage=y
			break
		fi
		arg=$a
		;;
		
	esac
done

#This is a dumb way to handle things, take it out next time
T="\t"

#Set up colors
if [ ! "${nocolor}" ]; then
	NO="\x1b[0;0m"
	BR="\x1b[0;01m"
	CY="\x1b[36;01m"
	RD="\x1b[31;01m"
	GR="\x1b[32;01m"
	YL="\x1b[33;01m"
	BL="\x1b[34;01m"
	STAR=" *"
elif [ ! "${inst}" ] && [ ! "${uninst}" ]; then
	STAR=" *"
fi


# check for option conflicts
if [ "${inst}" -a "${uninst}" \
	-o \( "${ffind}" -o "${list}" -o "${tcheck}" -o "${mcheck}" \) \
	-a "${uninst}"  ]; then
	echo -e ${CY}${PROG}${NO}:${YL} conflicting options/modes${NO}
	usage=y
fi

if [ "${usage}" ]; then
	echo -e "${CY}${PROG} v. ${VERSION}${NO}

${CY}${PROG}${NO} is GenToolKit's \"query package\" tool, using it, you can
find packages owning files on your filesystem, check the integrity
of installed packages, and do other queries against installed or
uninstalled packages.

${BR}Usage:
${T}${CY}${PROG}${NO} [${BR}options${NO}] [${YL}pkgname${NO}] [${BL}-g${YL} group${NO}] [${BL}-f${YL} <file>${NO}|${BL}-fp${YL} <pattern>${NO}]
${T}${CY}${PROG}${NO} ${BL}--dups${NO} [${BL}--slot${NO}]
${T}${CY}${PROG}${NO} ${BL}--help${NO}

${BR}Duplicate Locating:
  ${BL}-d,  --dups${NO}${T}${T}print packages that have multiple versions installed
  ${BL}-s,  --slot${NO}${T}${T}make ${BL}-d${NO} SLOT only print dups of the same SLOT

${BR}Package Selection:
  ${BL}-f,  --find-file${NO}${T}finds package that owns file <file>
  ${BL}-fp, --find-pattern${NO}${T}finds to package that owns file matching *<pattern>*
  ${BL}-m,  --masked${NO}${T}${T}Include${YL} only${NO} masked packages
  ${BL}-n,  --non-masked${NO}${T}Include${YL} only${NO} non-masked packages
  ${BL}-I,  --installed${NO}${T}Include${YL} only${NO} installed packages
  ${BL}-U,  --uninstalled${NO}${T}Include${YL} only${NO} uninstalled packages
  ${BL}-g,  --group${NO}${T}${T}Find by group (can be combined with other searches)

${BR}Information Selection:
  ${BL}-l,  --list${NO}${T}${T}List package content
  ${BL}-i,  --info${NO}${T}${T}Get package description and home page.
  ${BL}-ct, --check-time${NO}
  ${BL}-tc, --time-check${NO}${T}Verify package files timestamps
  ${BL}-cm, --check-md5${NO}
  ${BL}-mc, --md5-check${NO}${T}Verify package files md5
  ${BL}-c,  --check${NO}${T}${T}Verify mtimes${YL} and${NO} md5.
  ${BL}-q,  --query-deps${NO}${T}display all installed packages 
${T}${T}${T}depending on selected packages

${BR}Operation Modifiers:
  ${BL}-nc, --no-color${NO}${T}don't use colors
  ${BL}-v,  --verbose${NO}${T}Be more verbose [ can be repeated twice ]
  ${BL}-vv${NO}${T}${T}${T}Same as ${BL}-v -v${NO}

${YL}Notes${NO}: 
${YL}*${NO} ${BL}-f${NO}, ${BL}-fp, ${BL}-d${NO}, ${BL}-l${NO}, ${BL}-ct${NO}, ${BL}-cm${NO}, and ${BL}-c${NO} apply only to installed packages.
${YL}*${NO} Short options may not be combined on the command-line, yet.
${YL}*${NO} The operation of some flags has been changed by the
  stripping of version numbers from some output to see
  the version numbers play with ${BL}-v${NO} and ${BL}-vv${NO}.
${YL}*${NO} When using${BL} -f${NO} with ${BL}-l${NO} or ${BL}--check.. -v${NO} options, only
  matching files will be displayed, unless ${BL}-v${NO} is doubled, 
  (yet more verbose) or ${BL}-vv${NO} is used.


${YL}Examples${NO}:
  ${PROG} --dups		print duplicates oldest first
  ${PROG} --dups -v	.. with versions
  ${PROG}			print list of installed packages
  ${PROG} porta -I		print versions of installed portage
  ${PROG} porta -i		.. + versions in portage tree + descriptions 
			and homepages
  ${PROG} gawk -c -v	check integrity of all installed versions of gawk
			the older ones will have \"damaged\" files.
  ${PROG} -f /bin/ls	print package(s) that own /bin/ls
"
	exit
fi

#For the --dups switch only
if [ "${dups}" ]; then
if [ "${grepmask}" ]; then
	mask=`python -c 'import portage; print portage.settings["ACCEPT_KEYWORDS"];' 2> /dev/null`
	echo -e "Currently accepted keywords: ${BL}${mask}${NO}"
	echo -e
	mask=`echo ${mask} | perl -pe 's/\s+/|/'`
fi

	#First dig out the list of packages with duplicates
	find /var/db/pkg/ -iname "*${arg}*.ebuild" 2> /dev/null > /tmp/qpkg.lst
	dups=`cat /tmp/qpkg.lst | cut -f7 -d/ |
		sed -e 's:\.ebuild$::; s:-r[0-9]*$::; s:-[^-]*$::; /^$/d' |
		sort | 
		uniq -d`

	#Next get all the exact versions
	duppak=`cat /tmp/qpkg.lst | fgrep "${dups}"`

	#Now cut that down to the directory name so we can be smart
	dirs=`sed -e 's:/[^/]*$::' /tmp/qpkg.lst`

	#Go through each package's DB and create a sortable file
	#to play with
	declare -i defcount=`cat /var/cache/edb/counter`
	for DIR in ${dirs}
	do	#Package COUNTER
		NUM=`cat "${DIR}/COUNTER" 2> /dev/null`
		[ -z "${NUM}" ] && NUM=defcount
		#Package slot if requested
		[ ${slot} ] && SLOT=`cat "${DIR}/SLOT"`
		#Package fullname
		PKG=`ls --color=no -1 ${DIR}/*.ebuild|cut -f5,7 -d"/"`
		#Package basename
		NAME=`echo "${PKG}"|sed -e 's:\.ebuild$::; s:-r[0-9]\+$::; s:-[0-9].*$::'`
		echo "${NUM} ${PKG} ${NAME}${SLOT}"
	#Finish loop, and sort that nice sortable file based on 
	#installation order, and then based on package basename
	#bash hates me so I decided to use a temp file
	done |sort -t" " -k3 -k1g,2|uniq -D -f2 > /tmp/qpkg.lst
	duppak=`cat /tmp/qpkg.lst`
	rm /tmp/qpkg.lst

	#If max verbosity is set output with full path to each ebuild
	if [ "${verb}" -gt 1 ]; then
		echo -n "${duppak}"|cut -f2 -d" "| \
				    sed -e "s:^:${BL}/var/db/pkg/${BR}:" \
					-e "s:\(/\)\([^/]*\)\(.ebuild\):\1${CY}\2${NO}\1\2\3:"

	#If normal verbosity output package group, package name and package version
	elif [ "${verb}" -gt 0 ]; then
		echo -n "${duppak}"|cut -f2 -d" "| \
				    sed -e "s:\(^[^/]*/\)\(.*\)\(\.ebuild\):${BR}\1${CY}\2${NO}:"

	#Otherwise just output package group and package name
	else
		echo -n "${duppak}"|cut -f2 -d" "| \
				    sed -e "s:-r[0-9]\+$::" \
					-e "s:-[0-9].*$::" \
					-e "s:\(^[^/]*/\)\(.*\):${BR}\1${CY}\2${NO}:"|uniq
	fi
	exit
fi

# get list of ebuilds to work on
if [ "${ffind}" ]; then
	# file find mode - list all ebuilds for 
	# package/CONTENTS containing <arg>
	if [ "${fpat}" ]; then
		dirs=`ls /var/db/pkg/${group}/*/CONTENTS \
		| xargs grep -l "${arg}" \
		| xargs --no-run-if-empty -n 1 dirname`
	else
		# if the user didnt specify a full path assume they
		# want to check in the working dir #17331
		[ "${arg:0:1}" != "/" ] && arg="${PWD}/${arg}"

		dirs=`ls /var/db/pkg/${group}/*/CONTENTS \
		| xargs grep -l " ${arg}\( .*\)*$" \
		| xargs --no-run-if-empty -n 1 dirname`
	fi
	ipak=`(
	for d in ${dirs} -;do
		[ "-" = "$d" ] && break
		ls ${d}/*.ebuild
	done)`
else
	# normal mode - list ebuilds for ebuild name containing <arg>

	# installed packages
	if [ ! "${uninst}" ]; then
		ipak=`find /var/db/pkg/ -iname "*.ebuild" 2>/dev/null`
		if [[ ${group} != "*" ]]; then
		    ipak=`echo ${ipak}|sed -e "s: :\n:g"|grep ${group}`
		fi
		if [ ${arg} ]; then
			# avoid ${arg}="db" from pulling in every installed package
			temp="/var/db/pkg/.*${arg}"	
			ipak=`echo ${ipak}|sed -e "s: :\n:g"|grep -i ${temp}`
		fi
		if [ -n "${mask}" ]; then
		    ipak=`echo ${ipak}|xargs -r egrep ${grepmask} "^KEYWORDS=.*[[:space:]\"\'](${mask})[[:space:]\"\']"`
		fi
	fi
	# not installed packages (yet:-)
	if [ ! "${inst}" ]; then
		upak=`find /usr/portage/ -iname "*.ebuild" 2>/dev/null|grep -v --regex="/usr/portage/[^/]*\.ebuild"`
		if [[ ${group} != "*" ]]; then
		    upak=`echo ${upak}|sed -e "s: :\n:g"|grep ${group}`
		fi
		if [ ${arg} ]; then
		    upak=`echo ${upak}|sed -e "s: :\n:g"|grep -i ${arg}`
		fi
		if [ -n "${mask}" ]; then
		    upak=`echo ${upak}|xargs -r egrep ${grepmask} "^KEYWORDS=.*[[:space:]\"\'](${mask})[[:space:]\"\']"`
		fi
	fi
fi

X="\([^/]*\)"

for p in ${ipak} ${upak} -;do
	[ "${p}" = "-" ] && break

	# cut common prefix from ebuild name and mark installed/uninstalled packages
	# Note: iii/uuu will be replaced by the pipe at the end
	n=${p%.ebuild}
	var_db_pkg="/var/db/pkg/"
	n=${n/${var_db_pkg}/iii }
	usr_portage="/usr/portage/"
	n=${n/${usr_portage}/uuu }
	n=${n/\/*\//\/}

	d=${p%\/*.ebuild} # faster d=`dirname ${p}`
	echo ${n}
	
	# if we have no passed parameters then 
	# we can skip the extra conditional checks
	[[ ${params} == 0 ]] && continue;

	if [ "${mask}" ]; then
		keywords=`grep KEYWORDS ${p}| cut -d\" -f2`
		echo -e "${T}Keywords: ${BL}${keywords}${NO}"
	fi
	
	if [ ${verb} -gt 1 ];then
		echo "vvv    ${p}"
	fi
	
	if [ "${info}" ]; then
		home=`grep HOMEPAGE ${p}| cut -d\" -f2`
		desc=`grep DESCRIPTION ${p}| cut -d\" -f2`
		echo -e "${T}${BL}${desc}${NO} [ ${YL}${home}${NO} ]"
		if [ ${verb} -gt 0 ]; then
			pdir=${p/$(basename ${p})/}
			if [[ -r ${pdir}/USE && -r ${pdir}/IUSE ]]; then
				echo -n "Compiled with USE Flags: "
				for flag in $(<${pdir}/IUSE)
				do
					use=$(grep -o ${flag} ${pdir}/USE | tr -d '\n')
					if [[ "${use}" == "" ]]; then
						echo -n "-"
					fi
					echo -n "${flag} "
				done
				echo
			fi
		fi
	fi
	
	if [ "${query}" ]; then
		echo -e "${BL}DEPENDED ON BY:${NO}"
		package="`echo ${n}|sed -e 's:-r[0-9]\+$::' \
					-e 's:-[0-9].*$::' \
					-e 's:^iii ::' \
					-e 's:^uuu ::'`"
		place="`echo ${n}|cut -f1 -d' '`"
		[[ "${place}" == "iii" ]] && color="${GR}" || color="${RD}"

		if [[ ${place} == "iii" ]]; then
			for deppkg in $(grep -R "${package}" /var/db/pkg/*/*/RDEPEND | sed 's/RDEPEND.*$//')
			do
				rdepend=$(< ${deppkg}/RDEPEND)

				for flag in $(< ${deppkg}/USE)
				do
					if [[ "${flag:0:1}" == "-" ]]; then
						rdepend=$(echo ${rdepend} | sed "s/${flag:1}? ( [[:alnum:][:punct:]]* )//")
					fi
				done

				if [[ $(echo ${rdepend} | grep -o ${package}) == ${package} ]]; then
					echo $'\t'$(< ${deppkg}/PF)
				fi
			done
		else
			grep -R "${package}" /var/db/pkg/*/*/RDEPEND | \
			cut -f5,6 -d"/" | sed -e "s:^:\t${color}:;s:$:${NO}:" | sort -u
			if [[ $(grep -R "*${package}" /etc/make.profile/packages) != "" ]]; then
				echo -e "\t${color}SYSTEM PROFILE${NO}"
			fi
		fi
	fi

	# cat package content, remove obj/sym/dir, md5 and mtime when not verbose
	# display only match in file-find mode unless extra verbose
	if [ "${list}" ]; then
		echo -e ${BL}CONTENTS:${NO}

		if [ ${verb} -gt 1 ]; then
			cat ${d}/CONTENTS
		else
			if [ "${ffind}" ]; then
				if [ "${fpat}" ]; then
					grep "${arg}" $d/CONTENTS
				else
					grep " ${arg}\( .*\)*$" $d/CONTENTS
				fi
			else
				cat $d/CONTENTS
			fi |
			if [ ${verb} -gt 0 ]; then
				cat
			else
				sed -e "s:\(^obj \)\([^ ]*\)\(.*$\):\1${BR}\2${NO}:;
				        s:\(^sym \)\([^ ]*\)\( -> \)\([^ ]*\)\(.*$\):\1${CY}\2${NO}\3\4:;
				        s:\(^dir \)\([^ ]*\)\(.*$\):\1${YL}\2${NO}:"
			fi
		fi

		echo
		
	# check files mtime and md5, display summary at the end
	elif [ "${tcheck}" -o "${mcheck}" ]; then
		# counters
		fe=0
		fs=0
		# read the CONTENTS file and check md5 and mtime if needed
		# process only matching files in find-file mode unless extra verbose
		cat ${d}/CONTENTS | 
			if [ "${ffind}" -a ${verb} -lt 2 ];then 
				if [ "${fpat}" ]; then
					grep "${arg}"
				else
					grep " ${arg} "
				fi
			else
				cat
			fi |
		(
		while read -a line
		do
			fs=$((fs + 1))
			
			unset md5
			unset _md5
			unset mtime
			unset _mtime
			unset err

			name=${line[1]}
			
			missing=
			[ ! -e ${name} ] && missing=1
			
			# colorize name and compute mtime/md5
			if [ "obj" = ${line[0]} ]; then
				[ -e ${name} ] && {
					[ "${tcheck}" ] && mtime=${line[3]}
					[ "${tcheck}" ] && _mtime=`date -r ${name} +%s`

					[ "${mcheck}" ] && md5=${line[2]}
					[ "${mcheck}" ] && _md5=`md5sum ${name}|cut -f1 -d" "`
				}

				name=${BR}${name}${NO}

			elif [ "sym" = ${line[0]} ]; then
				name=${CY}${name}${NO}

			elif [ "dir" = ${line[0]} ]; then
				name=${YL}${name}${NO}
			fi
			
			# compare
			if [ "$missing" ]; then
				err=1
				name="${name} ${RD}!not exist!${NO}"
			fi
			if [ "${md5}" != "${_md5}" ]; then
				#If the md5 fails the first time check it with
				#everything changed to lowercase :-D
				md5=`echo "${md5}"|tr A-Z a-z`
				if [ "${md5}" != "${_md5}" ]; then
					err=1
					name="${name} ${RD}!md5!${NO}"
				fi
			fi
			if [ "${mtime}" != "${_mtime}" ]; then
				err=1
				name="${name} ${RD}!mtime!${NO}"
			fi

			[ ${verb} -gt 1 ] && echo -e ${name}
			[[ ${verb} -eq 1 ]] && [[ $err -eq 1 ]] && echo -e ${name}

			fe=$((fe + err))
		done
		if [ "$fe" = "0" ]; then
			echo -e ${YL}$fe${CY}/$fs${NO}
		else
			echo -e ${RD}$fe${CY}/$fs${NO}
		fi
		echo
		)
	fi
	
done | (
	if [ ! \( "${tcheck}" -o "${mcheck}" -o "${info}" -o "${list}" -o "${query}" -o "${mask}" -o ${verb} -gt 0 \) ]; then
		sed -e "s:-r[0-9]\+$::" -e "s:-[0-9][^-]*$::"|sort -k2|uniq -f1
	elif [ ! \( "${tcheck}" -o "${mcheck}" -o "${info}" -o "${list}" -o "${query}" -o "${mask}" -o ${verb} -lt 2 \) ]; then
		sort -k2|uniq -f1
	else
		cat
	fi | sed \
		-e "s:^iii ${X}/${X}:${BR}\1/${CY}\2${STAR}${NO}:" \
		-e "s:^uuu ${X}/${X}:${BR}\1/${YL}\2${NO}:" \
		-e "s:^vvv \(.*\)$:${BL}\1${NO}:" \
		-e "s:^obj ::;s:^sym ::;s:^dir ::"

)
