#!/bin/sh
# do some basic sanity checks
# shellcheck source=conf/default.conf
. "${0%/*}/../conf/default.conf"
# TODO: read information from database
usage() {
>&2 echo ''
>&2 echo 'sanity-check [options] [checks]: check sanity of build master'
>&2 echo ''
>&2 echo 'possible options:'
>&2 echo ' -h|--help: Show this help and exit.'
>&2 echo ' -q|--quiet: Only print errors found.'
>&2 echo ' -r|--really-quiet: Do not print anything.'
>&2 echo ' -w|--webserver: Generate output suitable for webserver.'
[ -z "$1" ] && exit 1 || exit "$1"
}
i_am_insane() {
if [ ! -s "${work_dir}/build-master-sanity" ]; then
printf '\001ACTION goes insane.\001\n' | \
sponge "${irc_dir}/#archlinux-ports/in"
fi
echo 'build master is insane' > \
"${work_dir}/build-master-sanity"
echo 'SANITY CHECK FAILED' >> \
"${tmp_dir}/messages"
exit 1
}
eval set -- "$(
getopt -o hqrw \
--long help \
--long quiet \
--long really-quiet \
--long webserver \
-n "$(basename "$0")" -- "$@" || \
echo usage
)"
silence=0
repos="${standalone_package_repositories} ${stable_package_repositories} ${testing_package_repositories} ${staging_package_repositories}"
web=false
while true
do
case "$1" in
-h|--help)
usage 0
;;
-q|--quiet)
silence=1
;;
-r|--really-quiet)
silence=2
;;
-w|--webserver)
web=true
;;
--)
shift
break
;;
*)
>&2 echo 'Whoops, forgot to implement option "'"$1"'" internally.'
exit 42
;;
esac
shift
done
exec 9> "${sanity_check_lock_file}"
if ! flock -n 9; then
>&2 echo 'Sanity check skipped, cannot acquire lock.'
exit
fi
finish() {
if ${web}; then
{
printf '%s\n' \
'' \
'
' \
'result of archlinux32 build master'"'"'s sanity check' \
'' \
''
printf '%s
\n' "$(date)"
sed 's|$|
|' "${tmp_dir}/messages"
printf '%s\n' \
'' \
''
} > \
"${webserver_directory}/master-sanity.html"
else
cat "${tmp_dir}/messages" >&2
fi
rm -rf --one-file-system "${tmp_dir}"
}
tmp_dir=$(mktemp -d 'tmp.sanity-check.XXXXXXXXXX' --tmpdir)
touch "${tmp_dir}/messages"
trap 'finish' EXIT
if [ $# -eq 0 ]; then
set -- git-repositories build-list repos package-database state-files
fi
while [ $# -gt 0 ]; do
case "$1" in
git-repositories)
[ ${silence} -gt 0 ] || \
printf 'checking git repositories ...' >> \
"${tmp_dir}/messages"
for repo in ${repo_names}; do
eval 'repo_path="${repo_paths__'"${repo}"'}"'
repo_revision=$(
cat "${work_dir}/${repo}.revision"
)
if ! git -C "${repo_path}" archive "${repo_revision}" -- | \
tar -t > /dev/null; then
if [ ${silence} -le 1 ]; then
printf '\nThe repository %s (%s) cannot archive the current revision %s.\n' \
"${repo}" "${repo_path}" "${repo_revision}" >> \
"${tmp_dir}/messages"
fi
i_am_insane
fi
done
[ ${silence} -gt 0 ] || \
echo ' passed.' >> \
"${tmp_dir}/messages"
;;
build-list)
[ ${silence} -gt 0 ] || \
printf 'checking build-list ...' >> \
"${tmp_dir}/messages"
errors=$(
grep -vn '^\S\+ [0-9a-f]\{40\} [0-9a-f]\{40\} \S\+$' "${work_dir}/build-list"
) || true
if [ -n "${errors}" ]; then
if [ ${silence} -le 1 ]; then
printf '\nThe following build orders are wrongly formatted:\n%s\n' \
"${errors}" >> \
"${tmp_dir}/messages"
fi
i_am_insane
fi
errors=$(
cut -d' ' -f1 < \
"${work_dir}/build-list" | \
sort | \
uniq -d
)
if [ -n "${errors}" ]; then
if [ ${silence} -le 1 ]; then
printf '\nThe following packages have duplicate build orders:\n%s\n' \
"${errors}" >> \
"${tmp_dir}/messages"
fi
i_am_insane
fi
errors=$(
{
cut -d' ' -f1 < \
"${work_dir}/build-list"
cat "${work_dir}/deletion-list"
} | \
sort | \
uniq -d
)
if [ -n "${errors}" ]; then
if [ ${silence} -le 1 ]; then
printf '\nThe following packages appear on the build- and deletion-list:\n%s\n' \
"${errors}" >> \
"${tmp_dir}/messages"
fi
i_am_insane
fi
[ ${silence} -gt 0 ] || \
echo ' passed.' >> \
"${tmp_dir}/messages"
;;
repos)
[ ${silence} -gt 0 ] || \
printf 'checking repos on master mirror ...' >> \
"${tmp_dir}/messages"
errors=$(
{
# shellcheck disable=SC2086
printf 'expected %s\n' ${repos}
ls_master_mirror 'i686' | \
sed 's|^|found |'
} | \
sort -k2 | \
uniq -uf1
)
if [ -n "${errors}" ]; then
if [ ${silence} -le 1 ]; then
printf '\nThe following repos are missing or obsolete on the mirror:\n%s\n' \
"${errors}" >> \
"${tmp_dir}/messages"
fi
i_am_insane
fi
[ ${silence} -gt 0 ] || \
echo ' passed.' >> \
"${tmp_dir}/messages"
;;
package-database)
for repo in ${repos}; do
[ ${silence} -gt 0 ] || \
printf 'checking consistency of repository "%s" on the master mirror ...' "${repo}" >> \
"${tmp_dir}/messages"
packages=$(
ls_master_mirror "i686/${repo}" | \
grep '\.pkg\.tar\.xz\(\.sig\)\?$'
) || true
errors=$(
echo "${packages}" | \
grep '\S' | \
sed '
s|^\(.*\.pkg\.tar\.xz\)$|package \1|
s|^\(.*\.pkg\.tar\.xz\)\.sig$|signature \1|
' | \
sort -k2 | \
uniq -cf1 | \
grep -v '^\s*2\s' | \
awk '{print $2 " " $3}'
) || true
if [ -n "${errors}" ]; then
if [ ${silence} -le 1 ]; then
printf '\nThe following packages in %s are missing a signature or vice versa:\n%s\n' \
"${repo}" \
"${errors}" >> \
"${tmp_dir}/messages"
fi
i_am_insane
fi
${master_mirror_rsync_command} \
"${master_mirror_rsync_directory}/i686/${repo}/${repo}.db.tar.gz" \
"${master_mirror_rsync_directory}/i686/${repo}/${repo}.files.tar.gz" \
"${tmp_dir}/"
errors=$(
{
tar -Oxzf "${tmp_dir}/${repo}.db.tar.gz" --wildcards '*/desc' 2>/dev/null | \
sed -n '
/^%FILENAME%$/ {
N
s/^.*\n/in_database /
p
}
'
echo "${packages}" | \
sed '
/\.pkg\.tar\.xz$/ !d
s/^/in_repository /
' | \
sort -u
} | \
sort -k2 | \
uniq -uf1
)
if [ -n "${errors}" ]; then
if [ ${silence} -le 1 ]; then
printf '\nThe following packages in %s are missing from the database or vice versa:\n%s\n' \
"${repo}" \
"${errors}" >> \
"${tmp_dir}/messages"
fi
i_am_insane
fi
errors=$(
{
tar -tzf "${tmp_dir}/${repo}.files.tar.gz" | \
grep '/$' | \
sed '
s|/$||
s|^|in_database |
'
echo "${packages}" | \
grep '\S' | \
sed '
s|-[^-]\+$||
s|^|in_repository |
' | \
sort -u
} | \
sort -k2 | \
uniq -uf1
)
if [ -n "${errors}" ]; then
if [ ${silence} -le 1 ]; then
printf '\nThe following packages in %s are missing from the file-database or vice versa:\n%s\n' \
"${repo}" \
"${errors}" >> \
"${tmp_dir}/messages"
fi
i_am_insane
fi
find "${tmp_dir:?}" -mindepth 1 \( -not -name 'messages' \) -delete
[ ${silence} -gt 0 ] || \
echo ' passed.' >> \
"${tmp_dir}/messages"
done
;;
state-files)
for status in 'staging' 'testing'; do
[ ${silence} -gt 0 ] || \
printf 'checking state-files of "%s" ...' "${status}" >> \
"${tmp_dir}/messages"
errors=$(
{
if [ "${status}" = 'staging' ]; then
find "${work_dir}/package-states" -name '*.done' \
-exec sed 's|^|package-state-file |' '{}' \;
else
find "${work_dir}/package-states" \( -name '*.testing' -o -name '*.tested' \) \
-exec sed 's|^|package-state-file |' '{}' \;
fi
ls_master_mirror 'i686' | \
grep "${status}\$" | \
while read -r repo; do
ls_master_mirror "i686/${repo}"
done | \
grep '\.pkg\.tar\.xz$' | \
sed 's|^|package-file |'
} | \
sort -k2 | \
uniq -cf1 | \
grep -v '^\s*2\s' | \
awk '{print $2 " " $3}'
)
if [ -n "${errors}" ]; then
if [ ${silence} -le 1 ]; then
printf '\nThe following %s packages do not have state files or vice versa:\n%s\n' \
"${status}" \
"${errors}" >> \
"${tmp_dir}/messages"
fi
i_am_insane
fi
[ ${silence} -gt 0 ] || \
echo ' passed.' >> \
"${tmp_dir}/messages"
done
;;
*)
[ ${silence} -gt 1 ] || \
>&2 printf 'unknown sanity-check "%s".\n' "$1"
exit 2
;;
esac
shift
done
if [ -f "${work_dir}/build-master-sanity" ]; then
rm "${work_dir}/build-master-sanity"
printf '\001ACTION resumes sanity.\001\n' | \
sponge "${irc_dir}/#archlinux-ports/in"
fi