kumquat-buildroot/support/download/check-hash
Yann E. MORIN 145e8e7406 support/download/check-hash: accept hash files without terminating \n
Lots of people are using broken text editors that 1. do not naturally
terminate text files with a final \n as is customary in UNIX text files,
and 2. do not respect our .editorconfig settings, which explicitly
require adding that final newline. See this nice summary of what a text
file is (with references to applicable standards):

    https://stackoverflow.com/questions/12916352/shell-script-read-missing-last-line/12916758#12916758

So, it is not surprising that read does not read the last "line" of a
file, when said "line" does not end with a newline, because it is thus
not really a line.

Even though we do mandate actual text files, let's be a little bit lax
in this respect, because people may write packages, and their hash
files, in a br2-external tree, and they may not have our .editorconfig
in the directory heierarchy (e.g. if buildroot is a submodule of their
br2-external tree, or whatever).

mapfile does not suffer from this limitation, though, and correctly
reads all lines from a file, even the final line-that-is-not-a-line.

mapfile was introduced in bash 4.0, released on 2009-01-20, more than
15 years ago. Debian squeeze, released in 2011 already had bash 4.1.
Those are really ancient. So, it means we can indeed expect bash
version 4.0 or later; which means mapfile is available.

"It should be fine!"

Fixes: #15976

Reported-by: masonwardle@gmail.com
Signed-off-by: Yann E. MORIN <yann.morin.1998@free.fr>
Signed-off-by: Arnout Vandecappelle <arnout@mind.be>
(cherry picked from commit ac2e6b392791085bc29fa21901265a8eed4ae0ee)
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
2024-08-20 19:20:00 +02:00

117 lines
3.4 KiB
Bash
Executable File

#!/usr/bin/env bash
set -e
# Helper to check a file matches its known hash
# Call it with:
# $1: the full path to the temporary file that was downloaded, and
# that is to be checked
# $2: the final basename of the file, to which it will be ultimately
# saved as, to be able to match it to the corresponding hashes
# in the .hash file
# $*: the paths of the files containing all the expected hashes
#
# Exit codes:
# 0: the hash file exists and the file to check matches all its hashes,
# or the hash file does not exist
# 1: unknown command-line option
# 2: the hash file exists and the file to check does not match at least
# one of its hashes
# 3: the hash file exists and there was no hash to check the file against
# 4: the hash file exists and at least one hash type is unknown
while getopts :q OPT; do
case "${OPT}" in
q) exec >/dev/null;;
\?) exit 1;;
esac
done
shift $((OPTIND-1))
file="${1}"
base="${2}"
shift 2
declare -a h_files=( "${@}" )
# Check one hash for a file
# $1: algo hash
# $2: known hash
# $3: file (full path)
# $4: hash file (full path)
check_one_hash() {
_h="${1}"
_known="${2}"
_file="${3}"
_h_file="${4}"
# Note: md5 is supported, but undocumented on purpose.
# Note: sha3 is not supported, since there is currently no implementation
# (the NIST has yet to publish the parameters).
case "${_h}" in
md5|sha1) ;;
sha224|sha256|sha384|sha512) ;;
*) # Unknown hash, exit with error
printf "ERROR: unknown hash '%s' for '%s'\n" \
"${_h}" "${base}" >&2
exit 4
;;
esac
# Do the hashes match?
_hash="$( "${_h}sum" "${_file}" |cut -d ' ' -f 1 )"
if [ "${_hash}" = "${_known}" ]; then
printf "%s: OK (%s: %s)\n" "${base}" "${_h}" "${_hash}"
return 0
fi
printf "ERROR: while checking hashes from %s\n" "${_h_file}" >&2
printf "ERROR: %s has wrong %s hash:\n" "${base}" "${_h}" >&2
printf "ERROR: expected: %s\n" "${_known}" >&2
printf "ERROR: got : %s\n" "${_hash}" >&2
printf "ERROR: Incomplete download, or man-in-the-middle (MITM) attack\n" >&2
exit 2
}
# Do we know one or more hashes for that file?
nb_h_files=0
nb_checks=0
for h_file in "${h_files[@]}"; do
[ -f "${h_file}" ] || continue
: $((nb_h_files++))
# mapfile reads all lines, even the last one if it is missing a \n
mapfile -t hash_lines <"${h_file}"
for hash_line in "${hash_lines[@]}"; do
read -r t h f <<<"${hash_line}"
case "${t}" in
''|'#'*)
# Skip comments and empty lines
continue
;;
*)
if [ "${f}" = "${base}" ]; then
check_one_hash "${t}" "${h}" "${file}" "${h_file}"
: $((nb_checks++))
fi
;;
esac
done
done
# shellcheck disable=SC2086 # nb_h_files is a non-empty int
if [ ${nb_h_files} -eq 0 ]; then
printf "WARNING: no hash file for %s\n" "${base}" >&2
exit 0
fi
# shellcheck disable=SC2086 # nb_checks is a non-empty int
if [ ${nb_checks} -eq 0 ]; then
case " ${BR_NO_CHECK_HASH_FOR} " in
*" ${base} "*)
# File explicitly has no hash
exit 0
;;
esac
printf "ERROR: No hash found for %s\n" "${base}" >&2
exit 3
fi