#!/bin/bash
# Script to check which tools built the specified binaries.
#
# Created by Nick Clifton.
# Copyright (c) 2016-2018 Red Hat.
#
# This is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published
# by the Free Software Foundation; either version 3, or (at your
# option) any later version.
# It is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# Usage:
# built-by [switches] file(s)
#
# This script does not handle directories. This is deliberate.
# It is intended that if recursion is needed then it will be
# invoked from find, like this:
#
# find . -type f -exec built-by.sh {} \;
# To Do:
#
# * Allow arguments to command line options to be separated from the
# the option name by a space. Eg: --before 20161212
version=3.0
help ()
{
# The following exec goop is so that we don't have to manually
# redirect every message to stderr in this function.
exec 4>&1 # save stdout fd to fd #4
exec 1>&2 # redirect stdout to stderr
cat <<__EOM__
This is a shell script to extract details of the
tool that was used to create the named files.
Usage: $prog {files|options}
{options} are:
-h --help Display this information.
-v --version Report the version number of this script.
-V --verbose Report on progress.
-s --silent Produce no output, just an exit status.
-i --ignore Silently ignore files where the builder cannot be found.
-r=<PATH> --readelf=<PATH> Path to version of readelf to use to read notes.
-t=<PATH> --tmpfile=<PATH> Temporary file to use.
-- Stop accumulating options.
The information reported can be made conditional by using the following options:
--tool=<NAME> Only report binaries built by <NAME>
--nottool=<NAME> Skip binaries built by <NAME>
--before=<DATE> Only report binaries built before <DATE>
--after=<DATE> Only report binaries built after <DATE>
--minver=<VER> Only report binaries built by version <VER> or higher
--maxver=<VER> Only report binaries built by version <VER> or lower
<NAME> is just a string, not a regular expression
<DATE> format is YYYYMMDD. For example: 20161230
<VER> is a version string in the form V.V.V For example: 6.1.2
The --before and --after options can be used together to specify a date
range which should be reported. Similarly the --minver and --maxver
options can be used together to specify a version range.
__EOM__
exec 1>&4 # Copy stdout fd back from temporary save fd, #4
}
main ()
{
init
parse_args ${1+"$@"}
scan_files
if [ $failed -ne 0 ];
then
exit 1
else
exit 0
fi
}
report ()
{
if [ $silent -eq 0 ]
then
echo $prog":" ${1+"$@"}
fi
}
fail ()
{
report "Internal error: " ${1+"$@"}
exit 1
}
verbose ()
{
if [ $verb -ne 0 ]
then
echo $prog":" ${1+"$@"}
fi
}
# Initialise global variables.
init ()
{
files[0]="";
# num_files is the number of files to be listed minus one.
# This is because we are indexing the files[] array from zero.
num_files=0;
failed=0
silent=0
verb=0
ignore_unknown=0
scanner=readelf
tmpfile=/dev/shm/built.by.delme
tool=""
nottool=""
before=""
after=""
minver=""
maxver=""
}
# Parse our command line
parse_args ()
{
prog=`basename $0`;
# Locate any additional command line switches
# Likewise accumulate non-switches to the files list.
while [ $# -gt 0 ]
do
optname="`echo $1 | sed 's,=.*,,'`"
optarg="`echo $1 | sed 's,^[^=]*=,,'`"
case "$optname" in
-v | --version)
report "version: $version"
exit 0
;;
-h | --help)
help
exit 0
;;
-s | --silent)
silent=1;
verb=0;
;;
-V | --verbose)
silent=0;
verb=1;
;;
-i | --ignore)
ignore_unknown=1;
;;
-r | --readelf)
scanner="$optarg"
;;
-t | --tmpfile)
tmpfile="$optarg"
;;
--tool)
nottool=""
tool=$optarg
;;
--nottool)
tool=""
nottool=$optarg
;;
--before)
before=$optarg
;;
--after)
after=$optarg
;;
--minver)
minver=$optarg
;;
--maxver)
maxver=$optarg
;;
--)
break;
;;
--*)
report "unrecognised option: $1"
help
exit 1
;;
*)
files[$num_files]="$1";
let "num_files++"
;;
esac
shift
done
# Accumulate any remaining arguments without processing them.
while [ $# -gt 0 ]
do
files[$num_files]="$1";
let "num_files++";
shift
done
if [ $num_files -gt 0 ];
then
# Remember that we are counting from zero not one.
let "num_files--"
else
report "must specify at least one file to scan"
exit 1
fi
}
scan_files ()
{
local i
i=0;
while [ $i -le $num_files ]
do
scan_file i
let "i++"
done
}
scan_file ()
{
local file
# Paranoia checks - the user should never encounter these.
if test "x$1" = "x" ;
then
fail "scan_file called without an argument"
fi
if test "x$2" != "x" ;
then
fail "scan_file called with too many arguments"
fi
# Use quotes when accessing files in order to preserve
# any spaces that might be in the directory name.
file="${files[$1]}";
# Catch names that start with a dash - they might confuse readelf
if test "x${file:0:1}" = "x-" ;
then
file="./$file"
fi
if ! [ -a "$file" ]
then
if [ $ignore_unknown -eq 0 ]; then
report "$file: file not found"
failed=1
fi
return
fi
if ! [ -f "$file" ]
then
if [ $ignore_unknown -eq 0 ]; then
report "$file: not an ordinary file"
failed=1
fi
return
fi
if ! [ -r "$file" ]
then
if [ $ignore_unknown -eq 0 ]; then
report "$file: not readable"
failed=1
fi
return
fi
file $file | grep --silent -e ELF
if [ $? != 0 ];
then
if [ $ignore_unknown -eq 0 ];
then
report "$file: not an ELF format file"
failed=1
fi
return
fi
$scanner --wide --notes $file > $tmpfile 2>&1
if [ $? != 0 ];
then
if [ $ignore_unknown -eq 0 ]; then
report "$file: scanner '$scanner' failed - see $tmpfile"
failed=1
fi
# Leave the tmpfile intact so that it can be examined by the user.
return
fi
local -a builder
local tool_index ver_index date_index tell=1
grep --silent -e "\$<tool>" $tmpfile
if [ $? == 0 ];
then
# Convert:
# $<tool>gcc 7.0.0 20161212 0x00000000 NT_GNU...
# or
# GA$<tool>gcc 7.0.0 20161212 0x00000000 NT_GNU...
# into:
# builder[0]=gcc
# builder[1]=7.0.0
# builder[2]=20161212
tool_index=0
ver_index=1
date_index=2
eval 'builder=($(grep -e tool $tmpfile | cut -d " " -f 3-5 | sort -u))'
verbose "build notes contain: ${builder[*]}"
if [ ${#builder[*]} -gt 3 ];
then
report "$file: contains multiple, different creator notes"
fi
if [ ${#builder[*]} -lt 3 ];
then
if [ $ignore_unknown -eq 0 ];
then
report "$file: contains truncated creator notes"
failed=1
fi
tell=0
fi
builder[0]=`echo ${builder[0]} | cut -d \> -f 2`
else
verbose "scan for build notes failed, trying debug information"
# Try examining the debug information in case -grecord-gcc-switches has been used.
$scanner --wide --debug-dump=info $file | grep -e DW_AT_producer > $tmpfile
eval 'builder=($(grep -e GNU $tmpfile))'
if [ ${#builder[*]} -ge 11 ];
then
# FIXME: We should grep for the right strings, rather than using
# builtin knowledge of the format of the DW_AT_producer contents
verbose "DW_AT_producer contains: ${builder[*]}"
tool_index=7
ver_index=9
date_index=10
builder[7]="${builder[7]} ${builder[8]}"
else
verbose "scan for debug information failed, trying .comment section"
# Alright - last chance. Check the .comment section
$scanner -p.comment $file > $tmpfile 2>&1
grep --silent -e "does not exist" $tmpfile
if [ $? != 0 ];
then
eval 'builder=($(grep -e GNU $tmpfile))'
verbose ".comment contains: ${builder[*]}"
# FIXME: We are using assumed knowledge of the layout of the builder comment.
if [ ${#builder[*]} -lt 5 ];
then
if [ $ignore_unknown -eq 0 ]; then
report "$file: could not parse .comment section"
failed=1
fi
tell=0
fi
tool_index=2
ver_index=4
date_index=5
builder[2]="${builder[2]} ${builder[3]}"
else
if [ $ignore_unknown -eq 0 ]; then
report "$file: creator unknown"
failed=1
fi
tell=0
fi
fi
fi
if [ $tell -eq 1 ];
then
if [ x$tool != x ];
then
if [ "${builder[$tool_index]}" == $tool ];
then
tell=0
fi
fi
if [ x$nottool != x ];
then
if [ "${builder[$tool_index]}" == $nottool ];
then
tell=0
fi
fi
if [ x$minver != x ];
then
if [[ ${builder[$ver_index]} < $minver ]];
then
tell=0
fi
fi
if [ x$maxver != x ];
then
if [[ ${builder[$ver_index]} > $maxver ]];
then
tell=0
fi
fi
if [ x$before != x ];
then
if [ ${builder[$date_index]} -ge $before ];
then
tell=0
fi
fi
if [ x$after != x ];
then
if [ ${builder[$date_index]} -le $after ];
then
tell=0
fi
fi
fi
if [ $tell -eq 1 ];
then
report "$file: created by: ${builder[$tool_index]} v${builder[$ver_index]} ${builder[$date_index]}"
fi
rm -f $tmpfile
}
# Invoke main
main ${1+"$@"}