move files
This commit is contained in:
Executable
+611
@@ -0,0 +1,611 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
REPO_ROOT=$(cd -- "$SCRIPT_DIR/.." && pwd)
|
||||
NEWSET_C="$REPO_ROOT/reimplement/newset.c"
|
||||
NEWSET_COMPAT="$SCRIPT_DIR/newset_compat.h"
|
||||
NEWSET_WRAPPER="$SCRIPT_DIR/newset_mkset.c"
|
||||
PACKAGE_PARSER="$SCRIPT_DIR/parse_sisyphus_packages.awk"
|
||||
RESUME_PARSER="$SCRIPT_DIR/completed_set_versions.awk"
|
||||
PAYLOAD_HELPERS="$SCRIPT_DIR/rpm_payload_safety.sh"
|
||||
|
||||
# shellcheck source=scripts/rpm_payload_safety.sh
|
||||
source "$PAYLOAD_HELPERS"
|
||||
|
||||
MIRROR=https://ftp.altlinux.org/pub/distributions/ALTLinux
|
||||
REPORT=sisyphus-set-compare.tsv
|
||||
LIMIT=
|
||||
ALL=0
|
||||
RESUME=0
|
||||
KEEP_WORK=0
|
||||
BUILD_MKSET=
|
||||
PACKAGES=()
|
||||
|
||||
usage()
|
||||
{
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
compare_sisyphus_set_versions.sh --all [OPTIONS]
|
||||
compare_sisyphus_set_versions.sh --limit N [OPTIONS]
|
||||
compare_sisyphus_set_versions.sh --package NAME [--package NAME ...] [OPTIONS]
|
||||
compare_sisyphus_set_versions.sh --build-mkset PATH
|
||||
|
||||
Scopes:
|
||||
--all Process every x86_64/noarch package record.
|
||||
--limit N Process only the first N records (testing).
|
||||
--package NAME Process one named package; may be repeated (testing).
|
||||
|
||||
Options:
|
||||
--report FILE Streaming TSV report (default: sisyphus-set-compare.tsv).
|
||||
--resume Skip completed package/architecture/version records.
|
||||
--keep-work Preserve the temporary working directory.
|
||||
--mirror URL ALT repository root.
|
||||
--build-mkset PATH Build only the mkset-compatible newset.c wrapper and exit.
|
||||
-h, --help Show this help.
|
||||
|
||||
The report gets a START row before processing and DEPENDENCY/SUMMARY rows are
|
||||
appended after every package, so it can be monitored while the script runs.
|
||||
No repository-wide run is possible without the explicit --all option.
|
||||
EOF
|
||||
}
|
||||
|
||||
die()
|
||||
{
|
||||
printf 'error: %s\n' "$*" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
--all) ALL=1; shift ;;
|
||||
--limit) (($# >= 2)) || die '--limit requires N'; LIMIT=$2; shift 2 ;;
|
||||
--package) (($# >= 2)) || die '--package requires NAME'; PACKAGES+=("$2"); shift 2 ;;
|
||||
--report) (($# >= 2)) || die '--report requires FILE'; REPORT=$2; shift 2 ;;
|
||||
--resume) RESUME=1; shift ;;
|
||||
--keep-work) KEEP_WORK=1; shift ;;
|
||||
--mirror) (($# >= 2)) || die '--mirror requires URL'; MIRROR=${2%/}; shift 2 ;;
|
||||
--build-mkset) (($# >= 2)) || die '--build-mkset requires PATH'; BUILD_MKSET=$2; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) die "unknown option: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
build_mkset()
|
||||
(
|
||||
{
|
||||
local output=$1 build
|
||||
build=$(mktemp -d "${TMPDIR:-/tmp}/arsv-newset-build.XXXXXX")
|
||||
trap 'rm -rf "$build"' EXIT
|
||||
: >"$build/rpmlib.h"
|
||||
: >"$build/system.h"
|
||||
mkdir -p "$(dirname -- "$output")"
|
||||
|
||||
"${CC:-cc}" -O2 -std=gnu11 -D_GNU_SOURCE \
|
||||
-I"$build" -include "$NEWSET_COMPAT" \
|
||||
-c "$NEWSET_C" -o "$build/newset.o"
|
||||
"${CC:-cc}" -O2 -std=gnu11 -D_GNU_SOURCE \
|
||||
"$NEWSET_WRAPPER" "$build/newset.o" -o "$output"
|
||||
chmod 755 "$output"
|
||||
}
|
||||
)
|
||||
|
||||
if [[ -n $BUILD_MKSET ]]; then
|
||||
build_mkset "$BUILD_MKSET"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[[ $MIRROR == http://* || $MIRROR == https://* ]] || die '--mirror must use http:// or https://'
|
||||
[[ $MIRROR != *[$'\t\r\n ']* ]] || die '--mirror must not contain whitespace'
|
||||
for package in "${PACKAGES[@]}"; do
|
||||
[[ $package =~ ^[A-Za-z0-9][A-Za-z0-9+_.-]*$ ]] || die "invalid package name: $package"
|
||||
done
|
||||
|
||||
scope_count=$ALL
|
||||
[[ -n $LIMIT ]] && scope_count=$((scope_count + 1))
|
||||
((${#PACKAGES[@]} > 0)) && scope_count=$((scope_count + 1))
|
||||
((scope_count == 1)) || die 'use --all, --limit, or --package (exactly one scope)'
|
||||
if [[ -n $LIMIT && ! $LIMIT =~ ^[1-9][0-9]*$ ]]; then
|
||||
die '--limit must be a positive integer'
|
||||
fi
|
||||
|
||||
for command in apt-get apt-cache rpm rpmquery rpm2cpio cpio curl md5sum awk sed sort find cp cc realpath; do
|
||||
command -v "$command" >/dev/null || die "required command not found: $command"
|
||||
done
|
||||
|
||||
rpmlibdir=$(rpm --eval '%_rpmlibdir')
|
||||
[[ -x $rpmlibdir/find-provides ]] || die "$rpmlibdir/find-provides is missing (install rpm-build)"
|
||||
[[ -x $rpmlibdir/find-requires ]] || die "$rpmlibdir/find-requires is missing (install rpm-build)"
|
||||
|
||||
case $REPORT in
|
||||
/*) ;;
|
||||
*) REPORT="$PWD/$REPORT" ;;
|
||||
esac
|
||||
mkdir -p "$(dirname -- "$REPORT")"
|
||||
if [[ ! -s $REPORT ]]; then
|
||||
printf 'timestamp\trecord\tpackage\tarchitecture\tstatus\tside\tcapability\toperator\texpected\tgenerated\tdetail\n' >"$REPORT"
|
||||
fi
|
||||
|
||||
clean_field()
|
||||
{
|
||||
local value=${1-}
|
||||
value=${value//$'\t'/ }
|
||||
value=${value//$'\r'/ }
|
||||
value=${value//$'\n'/ }
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
report_row()
|
||||
{
|
||||
local fields=() field row
|
||||
(($# == 11)) || {
|
||||
printf 'internal error: report row has %s fields, expected 11\n' "$#" >&2
|
||||
return 1
|
||||
}
|
||||
for field in "$@"; do
|
||||
fields+=("$(clean_field "$field")")
|
||||
done
|
||||
if [[ ${fields[1]} == SUMMARY ]]; then
|
||||
case ${fields[10]} in
|
||||
'') fields[10]='complete=1' ;;
|
||||
*'; ') fields[10]="${fields[10]}complete=1" ;;
|
||||
*) fields[10]="${fields[10]}; complete=1" ;;
|
||||
esac
|
||||
fi
|
||||
row=${fields[0]}
|
||||
for field in "${fields[@]:1}"; do
|
||||
printf -v row '%s\t%s' "$row" "$field"
|
||||
done
|
||||
printf '%s\n' "$row" >>"$REPORT"
|
||||
}
|
||||
|
||||
timestamp()
|
||||
{
|
||||
date -u '+%Y-%m-%dT%H:%M:%SZ'
|
||||
}
|
||||
|
||||
WORK=$(mktemp -d "${TMPDIR:-/tmp}/arsv-sisyphus-sets.XXXXXX")
|
||||
cleanup()
|
||||
{
|
||||
if ((KEEP_WORK)); then
|
||||
printf 'work directory preserved: %s\n' "$WORK" >&2
|
||||
else
|
||||
rm -rf "$WORK"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'exit 130' INT
|
||||
trap 'exit 143' TERM
|
||||
|
||||
mkdir -p "$WORK/apt/lists/partial" "$WORK/apt/archives/partial" "$WORK/tools"
|
||||
: >"$WORK/apt/apt.conf"
|
||||
: >"$WORK/apt/status"
|
||||
printf '%s\n' \
|
||||
"rpm [alt] $MIRROR Sisyphus/x86_64 classic" \
|
||||
"rpm [alt] $MIRROR Sisyphus/noarch classic" >"$WORK/apt/sources.list"
|
||||
|
||||
APT_OPTIONS=(
|
||||
-o 'Dir::Etc::main=-'
|
||||
-o 'Dir::Etc::parts=-'
|
||||
-o "Dir::Etc::sourcelist=$WORK/apt/sources.list"
|
||||
-o 'Dir::Etc::sourceparts=-'
|
||||
-o 'Dir::Etc::preferences=-'
|
||||
-o 'Dir::Etc::preferencesparts=-'
|
||||
-o "Dir::State::lists=$WORK/apt/lists"
|
||||
-o "Dir::State::status=$WORK/apt/status"
|
||||
-o "Dir::Cache::archives=$WORK/apt/archives"
|
||||
-o "Dir::Cache::pkgcache=$WORK/apt/pkgcache.bin"
|
||||
-o "Dir::Cache::srcpkgcache=$WORK/apt/srcpkgcache.bin"
|
||||
)
|
||||
|
||||
apt_get()
|
||||
{
|
||||
APT_CONFIG="$WORK/apt/apt.conf" apt-get "${APT_OPTIONS[@]}" "$@"
|
||||
}
|
||||
|
||||
apt_cache()
|
||||
{
|
||||
APT_CONFIG="$WORK/apt/apt.conf" apt-cache "${APT_OPTIONS[@]}" "$@"
|
||||
}
|
||||
|
||||
NEW_MKSET="$WORK/new-mkset"
|
||||
build_mkset "$NEW_MKSET"
|
||||
cp -as "$rpmlibdir"/. "$WORK/tools/"
|
||||
rm -f "$WORK/tools/mkset"
|
||||
ln -s "$NEW_MKSET" "$WORK/tools/mkset"
|
||||
|
||||
report_row "$(timestamp)" RUN - - start - - - - - "updating isolated Sisyphus indexes"
|
||||
if ! apt_get update >"$WORK/apt-update.log" 2>&1; then
|
||||
detail=$(sed -n '/./{p;q;}' "$WORK/apt-update.log")
|
||||
report_row "$(timestamp)" RUN - - apt_update_error - - - - - "$detail"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
QUEUE="$WORK/packages.tsv"
|
||||
if ! apt_cache dumpavail |
|
||||
awk -f "$PACKAGE_PARSER" |
|
||||
LC_ALL=C sort -t$'\034' -k1,1 -k2,2 >"$QUEUE"; then
|
||||
report_row "$(timestamp)" RUN - - metadata_error - - - - - 'unable to parse apt-cache dumpavail'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SELECTED="$WORK/selected.tsv"
|
||||
if ((${#PACKAGES[@]})); then
|
||||
: >"$SELECTED"
|
||||
for package in "${PACKAGES[@]}"; do
|
||||
if ! awk -F '\034' -v package="$package" '$1 == package { print; found=1 } END { exit !found }' \
|
||||
"$QUEUE" >>"$SELECTED"; then
|
||||
die "package not found in Sisyphus x86_64/noarch: $package"
|
||||
fi
|
||||
done
|
||||
elif [[ -n $LIMIT ]]; then
|
||||
sed -n "1,${LIMIT}p" "$QUEUE" >"$SELECTED"
|
||||
else
|
||||
cp "$QUEUE" "$SELECTED"
|
||||
fi
|
||||
|
||||
selected_count=$(wc -l <"$SELECTED")
|
||||
report_row "$(timestamp)" RUN - - ready - - - - - "selected packages: $selected_count"
|
||||
printf 'selected packages: %s; report: %s\n' "$selected_count" "$REPORT"
|
||||
|
||||
declare -A COMPLETED=()
|
||||
if ((RESUME)); then
|
||||
while IFS=$'\t' read -r package architecture version; do
|
||||
COMPLETED["$package"$'\t'"$architecture"$'\t'"$version"]=1
|
||||
done < <(awk -f "$RESUME_PARSER" "$REPORT")
|
||||
fi
|
||||
|
||||
first_log_line()
|
||||
{
|
||||
local file=$1
|
||||
[[ -s $file ]] || return 0
|
||||
sed -n '/./{p;q;}' "$file" | cut -c1-500
|
||||
}
|
||||
|
||||
last_log_line()
|
||||
{
|
||||
local file=$1
|
||||
[[ -s $file ]] || return 0
|
||||
awk 'NF { line=$0 } END { print line }' "$file" | cut -c1-500
|
||||
}
|
||||
|
||||
extract_rpm()
|
||||
{
|
||||
local package_file=$1 root=$2 log=$3 mode=$4
|
||||
local metadata="$root/../.arsv-file-metadata"
|
||||
local patterns="$root/../.arsv-nonlink-patterns"
|
||||
local symlinks="$root/../.arsv-symlinks"
|
||||
local field_separator=$'\034' record_separator=$'\035' record
|
||||
local filename link_target relative archive_name
|
||||
local query_format="[%{FILENAMES}${field_separator}%{FILELINKTOS}${record_separator}]"
|
||||
|
||||
if ! rpmquery -p --qf "$query_format" \
|
||||
"$package_file" >"$metadata" 2>>"$log"; then
|
||||
return 1
|
||||
fi
|
||||
case $mode in
|
||||
symlinks) : >"$symlinks" ;;
|
||||
files) : >"$patterns" ;;
|
||||
*) printf 'internal error: invalid RPM extraction mode: %s\n' "$mode" >>"$log"; return 1 ;;
|
||||
esac
|
||||
|
||||
while IFS= read -r -d "$record_separator" record; do
|
||||
if ! rpm_split_file_record "$record"; then
|
||||
printf 'unsupported RPM filename or symlink record\n' >>"$log"
|
||||
return 1
|
||||
fi
|
||||
filename=$RPM_FILENAME
|
||||
link_target=$RPM_LINK_TARGET
|
||||
[[ -n $filename && $filename == /* ]] || {
|
||||
printf 'unsafe RPM filename: %q\n' "$filename" >>"$log"
|
||||
return 1
|
||||
}
|
||||
relative=${filename#/}
|
||||
if [[ $relative == '..' || $relative == ../* || $relative == */../* || $relative == */.. ]]; then
|
||||
printf 'unsafe RPM filename: %q\n' "$filename" >>"$log"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -n $link_target && $link_target != '(none)' ]]; then
|
||||
if [[ $mode == symlinks ]]; then
|
||||
printf '%s\t%s\n' "$relative" "$link_target" >>"$symlinks"
|
||||
fi
|
||||
elif [[ $mode == files ]]; then
|
||||
archive_name=./$relative
|
||||
if ! rpm_cpio_literal_pattern "$archive_name" >>"$patterns"; then
|
||||
printf 'unsupported RPM filename: %q\n' "$filename" >>"$log"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
done <"$metadata"
|
||||
|
||||
if [[ $mode == files ]]; then
|
||||
if ! rpm2cpio "$package_file" |
|
||||
cpio -it --quiet --no-absolute-filenames 2>>"$log" |
|
||||
while IFS= read -r member; do
|
||||
case $member in
|
||||
/*|../*|*/../*|*/..)
|
||||
printf 'unsafe cpio member: %q\n' "$member" >>"$log"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -s $patterns ]]; then
|
||||
if ! rpm2cpio "$package_file" |
|
||||
(cd "$root" && cpio -idm --quiet --no-absolute-filenames -E "$patterns") \
|
||||
2>>"$log"; then
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
cat "$symlinks"
|
||||
}
|
||||
|
||||
normalize_expected()
|
||||
{
|
||||
local side=$1 package_file=$2 output=$3
|
||||
if [[ $side == provides ]]; then
|
||||
rpmquery -p --qf \
|
||||
'[%{PROVIDENAME}\t%{PROVIDEFLAGS:depflags}\t%{PROVIDEVERSION}\n]' \
|
||||
"$package_file"
|
||||
else
|
||||
rpmquery -p --qf \
|
||||
'[%{REQUIRENAME}\t%{REQUIREFLAGS:depflags}\t%{REQUIREVERSION}\n]' \
|
||||
"$package_file"
|
||||
fi | awk -F '\t' '$3 ~ /^set:/ { print $1 "\t" $2 "\t" $3 }' |
|
||||
LC_ALL=C sort -u >"$output"
|
||||
}
|
||||
|
||||
normalize_generated()
|
||||
{
|
||||
local side=$1 input=$2 output=$3
|
||||
if [[ $side == provides ]]; then
|
||||
awk '$2 == "=" && $3 ~ /^set:/ { print $1 "\t" $2 "\t" $3 }' "$input"
|
||||
else
|
||||
awk '$2 == ">=" && $3 ~ /^set:/ { print $1 "\t" $2 "\t" $3 }' "$input"
|
||||
fi | LC_ALL=C sort -u >"$output"
|
||||
}
|
||||
|
||||
compare_side()
|
||||
{
|
||||
local side=$1 package=$2 architecture=$3 expected=$4 generated=$5 output=$6
|
||||
if ! awk -F '\t' -v OFS='\034' '
|
||||
FILENAME == ARGV[1] { key=$1 "\034" $2; expected[key]=$3; keys[key]=1; next }
|
||||
{ key=$1 "\034" $2; generated[key]=$3; keys[key]=1 }
|
||||
END {
|
||||
for (key in keys) {
|
||||
split(key, parts, "\034")
|
||||
e=expected[key]; g=generated[key]
|
||||
if (e == "") status="extra_generated"
|
||||
else if (g == "") status="missing_generated"
|
||||
else if (e == g) status="match"
|
||||
else status="mismatch"
|
||||
print status, parts[1], parts[2], e, g
|
||||
}
|
||||
}
|
||||
' "$expected" "$generated" |
|
||||
LC_ALL=C sort -t$'\034' -k2,2 -k3,3 >"$output"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# A non-whitespace separator preserves empty expected/generated fields.
|
||||
while IFS=$'\034' read -r status capability operator expected_set generated_set; do
|
||||
[[ -n $status ]] || continue
|
||||
report_row "$(timestamp)" DEPENDENCY "$package" "$architecture" "$status" \
|
||||
"$side" "$capability" "$operator" "$expected_set" "$generated_set" -
|
||||
done <"$output"
|
||||
}
|
||||
|
||||
process_package()
|
||||
{
|
||||
local package=$1 architecture=$2 version=$3 filename=$4 md5=$5
|
||||
local has_provides=$6 has_requires=$7
|
||||
local key=$package$'\t'$architecture$'\t'$version
|
||||
if [[ -n ${COMPLETED[$key]-} ]]; then
|
||||
printf 'skip completed: %s.%s\n' "$package" "$architecture"
|
||||
return 0
|
||||
fi
|
||||
|
||||
report_row "$(timestamp)" START "$package" "$architecture" processing - - - - - "$version"
|
||||
printf 'process: %s.%s\n' "$package" "$architecture"
|
||||
|
||||
if ((has_provides == 0 && has_requires == 0)); then
|
||||
report_row "$(timestamp)" SUMMARY "$package" "$architecture" no_set_metadata \
|
||||
- - - - - 'APT metadata contains no set: Provides/Requires'
|
||||
return 0
|
||||
fi
|
||||
|
||||
local package_work="$WORK/package"
|
||||
rm -rf "$package_work"
|
||||
mkdir -p "$package_work/root" "$package_work/archives/partial"
|
||||
local target="$package_work/target.rpm"
|
||||
local url="$MIRROR/Sisyphus/files/$architecture/RPMS/$filename"
|
||||
local log="$package_work/package.log"
|
||||
|
||||
if ! curl --silent --show-error -fL --retry 3 --retry-delay 2 \
|
||||
-o "$target" "$url" >"$log" 2>&1; then
|
||||
report_row "$(timestamp)" SUMMARY "$package" "$architecture" download_error \
|
||||
- - - - - "$(first_log_line "$log")"
|
||||
return 0
|
||||
fi
|
||||
if [[ -n $md5 ]]; then
|
||||
local actual_md5
|
||||
if ! actual_md5=$(md5sum "$target" | awk '{print $1}'); then
|
||||
report_row "$(timestamp)" SUMMARY "$package" "$architecture" checksum_error \
|
||||
- - - "$md5" - "$filename"
|
||||
return 0
|
||||
fi
|
||||
if [[ $actual_md5 != "$md5" ]]; then
|
||||
report_row "$(timestamp)" SUMMARY "$package" "$architecture" checksum_error \
|
||||
- - - "$md5" "$actual_md5" "$filename"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
local -a payload_rpms=("$target")
|
||||
if ((has_requires)); then
|
||||
mkdir -p "$package_work/root/var/lib/rpm"
|
||||
if ! rpm --root "$package_work/root" --initdb >>"$log" 2>&1; then
|
||||
report_row "$(timestamp)" SUMMARY "$package" "$architecture" rpmdb_error \
|
||||
- - - - - "$(last_log_line "$log")"
|
||||
return 0
|
||||
fi
|
||||
if ! apt_get -y -d \
|
||||
-o "Dir::Cache::archives=$package_work/archives" \
|
||||
-o "RPM::RootDir=$package_work/root" \
|
||||
install "$target" >>"$log" 2>&1; then
|
||||
report_row "$(timestamp)" SUMMARY "$package" "$architecture" dependency_download_error \
|
||||
- - - - - "$(last_log_line "$log")"
|
||||
return 0
|
||||
fi
|
||||
local dependency_rpm
|
||||
for dependency_rpm in "$package_work"/archives/*.rpm; do
|
||||
[[ -f $dependency_rpm ]] || continue
|
||||
payload_rpms+=("$dependency_rpm")
|
||||
done
|
||||
fi
|
||||
|
||||
local payload_rpm extraction_status
|
||||
local symlink_manifest="$package_work/symlinks.tsv"
|
||||
: >"$symlink_manifest"
|
||||
for payload_rpm in "${payload_rpms[@]}"; do
|
||||
if ! extract_rpm "$payload_rpm" "$package_work/root" "$log" symlinks \
|
||||
>>"$symlink_manifest"; then
|
||||
if [[ $payload_rpm == "$target" ]]; then
|
||||
extraction_status=extract_error
|
||||
else
|
||||
extraction_status=dependency_extract_error
|
||||
fi
|
||||
report_row "$(timestamp)" SUMMARY "$package" "$architecture" "$extraction_status" \
|
||||
- - - - - "$(last_log_line "$log")"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
if ! rpm_install_symlink_manifest "$package_work/root" "$symlink_manifest" "$log"; then
|
||||
if ((has_requires)); then
|
||||
extraction_status=dependency_extract_error
|
||||
else
|
||||
extraction_status=extract_error
|
||||
fi
|
||||
report_row "$(timestamp)" SUMMARY "$package" "$architecture" "$extraction_status" \
|
||||
- - - - - "$(last_log_line "$log")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
for payload_rpm in "${payload_rpms[@]}"; do
|
||||
if ! extract_rpm "$payload_rpm" "$package_work/root" "$log" files; then
|
||||
if [[ $payload_rpm == "$target" ]]; then
|
||||
extraction_status=extract_error
|
||||
else
|
||||
extraction_status=dependency_extract_error
|
||||
fi
|
||||
report_row "$(timestamp)" SUMMARY "$package" "$architecture" "$extraction_status" \
|
||||
- - - - - "$(last_log_line "$log")"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
if ! rpmquery -p --qf '[%{FILENAMES}\n]' "$target" |
|
||||
awk -v root="$package_work/root" \
|
||||
'{ if (substr($0,1,1)=="/") print root $0; else print root "/" $0 }' |
|
||||
while IFS= read -r path; do
|
||||
if [[ -e $path || -L $path ]]; then
|
||||
printf '%s\n' "$path"
|
||||
fi
|
||||
done >"$package_work/files"; then
|
||||
report_row "$(timestamp)" SUMMARY "$package" "$architecture" file_list_error \
|
||||
- - - - - 'rpmquery failed while reading package filenames'
|
||||
return 0
|
||||
fi
|
||||
|
||||
local scanner_env=(
|
||||
"RPM_BUILD_ROOT=$package_work/root"
|
||||
"RPM_SUBPACKAGE_NAME=$package"
|
||||
"RPM_PACKAGE_NAME=$package"
|
||||
"RPMB_TOOLS_DIR=$WORK/tools"
|
||||
"RPMB_LIB_DIR=$rpmlibdir"
|
||||
"RPMB_AUTODEPS_DIR=$rpmlibdir"
|
||||
'RPM_FINDPROV_METHOD=none,lib'
|
||||
'RPM_FINDREQ_METHOD=none,lib'
|
||||
'RPM_SCRIPTS_DEBUG=0'
|
||||
)
|
||||
|
||||
local side generator expected generated comparison
|
||||
local total_mismatches=0 detail=''
|
||||
for side in provides requires; do
|
||||
[[ $side == provides && $has_provides == 1 ]] ||
|
||||
[[ $side == requires && $has_requires == 1 ]] || continue
|
||||
|
||||
generator="$package_work/generated.$side.raw"
|
||||
expected="$package_work/expected.$side.tsv"
|
||||
generated="$package_work/generated.$side.tsv"
|
||||
comparison="$package_work/comparison.$side.tsv"
|
||||
if ! normalize_expected "$side" "$target" "$expected"; then
|
||||
report_row "$(timestamp)" SUMMARY "$package" "$architecture" metadata_error \
|
||||
"$side" - - - - 'rpmquery failed while reading set dependencies'
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ $side == provides ]]; then
|
||||
if ! env "${scanner_env[@]}" "$rpmlibdir/find-provides" \
|
||||
<"$package_work/files" >"$generator" 2>"$package_work/$side.log"; then
|
||||
report_row "$(timestamp)" SUMMARY "$package" "$architecture" generator_error \
|
||||
"$side" - - - - "$(first_log_line "$package_work/$side.log")"
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
if ! env "${scanner_env[@]}" "$rpmlibdir/find-requires" \
|
||||
<"$package_work/files" >"$generator" 2>"$package_work/$side.log"; then
|
||||
report_row "$(timestamp)" SUMMARY "$package" "$architecture" generator_error \
|
||||
"$side" - - - - "$(first_log_line "$package_work/$side.log")"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! normalize_generated "$side" "$generator" "$generated"; then
|
||||
report_row "$(timestamp)" SUMMARY "$package" "$architecture" generator_output_error \
|
||||
"$side" - - - - 'unable to normalize generator output'
|
||||
return 0
|
||||
fi
|
||||
if ! compare_side "$side" "$package" "$architecture" "$expected" "$generated" "$comparison"; then
|
||||
report_row "$(timestamp)" SUMMARY "$package" "$architecture" comparison_error \
|
||||
"$side" - - - - 'unable to compare generated dependencies'
|
||||
return 0
|
||||
fi
|
||||
|
||||
local expected_count generated_count mismatch_count extra_count
|
||||
expected_count=$(wc -l <"$expected")
|
||||
generated_count=$(wc -l <"$generated")
|
||||
mismatch_count=$(awk -F '\034' \
|
||||
'$1 == "mismatch" || $1 == "missing_generated" { count++ } END { print count+0 }' \
|
||||
"$comparison")
|
||||
extra_count=$(awk -F '\034' '$1 == "extra_generated" { count++ } END { print count+0 }' \
|
||||
"$comparison")
|
||||
total_mismatches=$((total_mismatches + mismatch_count + extra_count))
|
||||
detail+="$side expected=$expected_count generated=$generated_count differences=$mismatch_count extras=$extra_count; "
|
||||
done
|
||||
|
||||
local status=match
|
||||
((total_mismatches == 0)) || status=mismatch
|
||||
report_row "$(timestamp)" SUMMARY "$package" "$architecture" "$status" - - - - - "$detail"
|
||||
sync -f "$REPORT" 2>/dev/null || true
|
||||
}
|
||||
|
||||
while IFS=$'\034' read -r package architecture version filename md5 has_provides has_requires extra; do
|
||||
if [[ -n ${extra-} || -z $package || -z $architecture || -z $version || -z $filename ||
|
||||
! $has_provides =~ ^[01]$ || ! $has_requires =~ ^[01]$ ]]; then
|
||||
report_row "$(timestamp)" RUN "$package" "$architecture" queue_error - - - - - \
|
||||
'invalid package queue record'
|
||||
continue
|
||||
fi
|
||||
process_package "$package" "$architecture" "$version" "$filename" "$md5" \
|
||||
"$has_provides" "$has_requires"
|
||||
done <"$SELECTED"
|
||||
|
||||
report_row "$(timestamp)" RUN - - complete - - - - - "processed selection: $selected_count"
|
||||
printf 'complete; report: %s\n' "$REPORT"
|
||||
@@ -0,0 +1,10 @@
|
||||
BEGIN { FS = "\t" }
|
||||
|
||||
NF == 11 && $2 == "START" {
|
||||
version[$3 "\034" $4] = $11
|
||||
}
|
||||
|
||||
NF == 11 && $2 == "SUMMARY" && $11 ~ /(^|; )complete=1$/ && \
|
||||
version[$3 "\034" $4] != "" {
|
||||
print $3 "\t" $4 "\t" version[$3 "\034" $4]
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/* mkset-compatible wrapper around reimplement/newset.c.
|
||||
* The stdin/argv contract matches rpm-build/tools/mkset.c. */
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
struct set;
|
||||
struct set *set_new(void);
|
||||
void set_add(struct set *set, const char *symbol);
|
||||
const char *set_fini(struct set *set, int bpp);
|
||||
struct set *set_free(struct set *set);
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "usage: %s BPP\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
char *end = NULL;
|
||||
errno = 0;
|
||||
long parsed_bpp = strtol(argv[1], &end, 10);
|
||||
if (errno || !end || *end || parsed_bpp < 10 || parsed_bpp > 32) {
|
||||
fprintf(stderr, "invalid BPP: %s (expected 10..32)\n", argv[1]);
|
||||
return 2;
|
||||
}
|
||||
int bpp = (int)parsed_bpp;
|
||||
|
||||
struct set *set = set_new();
|
||||
char *line = NULL;
|
||||
size_t allocated = 0;
|
||||
ssize_t length;
|
||||
int added = 0;
|
||||
|
||||
while ((length = getline(&line, &allocated, stdin)) >= 0) {
|
||||
if (length > 0 && line[length - 1] == '\n')
|
||||
line[--length] = '\0';
|
||||
if (length == 0)
|
||||
continue;
|
||||
set_add(set, line);
|
||||
++added;
|
||||
}
|
||||
|
||||
if (!added) {
|
||||
fputs("mkset: no symbols on standard input\n", stderr);
|
||||
free(line);
|
||||
set_free(set);
|
||||
return 2;
|
||||
}
|
||||
const char *encoded = set_fini(set, bpp);
|
||||
if (!encoded) {
|
||||
fputs("mkset: unable to encode set\n", stderr);
|
||||
free(line);
|
||||
set_free(set);
|
||||
return 1;
|
||||
}
|
||||
printf("set:%s\n", encoded);
|
||||
|
||||
free((void *)encoded);
|
||||
free(line);
|
||||
set_free(set);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
BEGIN {
|
||||
RS = ""
|
||||
FS = "\n"
|
||||
OFS = "\034"
|
||||
}
|
||||
|
||||
{
|
||||
package = architecture = version = filename = md5 = ""
|
||||
has_provides = has_requires = 0
|
||||
dependency_field = ""
|
||||
|
||||
for (i = 1; i <= NF; ++i) {
|
||||
if ($i ~ /^Package: /)
|
||||
package = substr($i, 10)
|
||||
else if ($i ~ /^Architecture: /)
|
||||
architecture = substr($i, 15)
|
||||
else if ($i ~ /^Version: /)
|
||||
version = substr($i, 10)
|
||||
else if ($i ~ /^Filename: /)
|
||||
filename = substr($i, 11)
|
||||
else if ($i ~ /^MD5Sum: /)
|
||||
md5 = substr($i, 9)
|
||||
|
||||
if ($i ~ /^Provides: /)
|
||||
dependency_field = "provides"
|
||||
else if ($i ~ /^(Pre-Depends|Depends): /)
|
||||
dependency_field = "requires"
|
||||
else if ($i !~ /^[[:space:]]/)
|
||||
dependency_field = ""
|
||||
|
||||
if (dependency_field == "provides" && index($i, "set:"))
|
||||
has_provides = 1
|
||||
if (dependency_field == "requires" && index($i, "set:"))
|
||||
has_requires = 1
|
||||
}
|
||||
|
||||
if (package != "" && architecture != "" && version != "" && filename != "")
|
||||
print package, architecture, version, filename, md5, has_provides, has_requires
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
#!/bin/bash
|
||||
|
||||
rpm_safe_symlink_target()
|
||||
{
|
||||
local root=$1 relative_path=$2 link_target=$3
|
||||
local canonical_root link_directory resolved_target
|
||||
|
||||
canonical_root=$(realpath -m -- "$root") || return 1
|
||||
link_directory=$(realpath -m -- "$canonical_root/$(dirname -- "$relative_path")") || return 1
|
||||
|
||||
if [[ $link_target == /* ]]; then
|
||||
resolved_target=$(realpath -m -- "$canonical_root/${link_target#/}") || return 1
|
||||
else
|
||||
resolved_target=$(realpath -m -- "$link_directory/$link_target") || return 1
|
||||
fi
|
||||
|
||||
[[ $resolved_target == "$canonical_root" || $resolved_target == "$canonical_root"/* ]] || return 1
|
||||
|
||||
if [[ $link_target == /* ]]; then
|
||||
realpath -m --relative-to="$link_directory" -- "$resolved_target"
|
||||
else
|
||||
printf '%s\n' "$link_target"
|
||||
fi
|
||||
}
|
||||
|
||||
rpm_cpio_literal_pattern()
|
||||
{
|
||||
local input=$1 output= character index
|
||||
|
||||
[[ $input != *$'\n'* && $input != *$'\r'* ]] || return 1
|
||||
for ((index = 0; index < ${#input}; ++index)); do
|
||||
character=${input:index:1}
|
||||
case $character in
|
||||
'[') output+='[[]' ;;
|
||||
']') output+='[]]' ;;
|
||||
'*') output+='[*]' ;;
|
||||
'?') output+='[?]' ;;
|
||||
'\\') output+='[\\]' ;;
|
||||
*) output+=$character ;;
|
||||
esac
|
||||
done
|
||||
printf '%s\n' "$output"
|
||||
}
|
||||
|
||||
rpm_split_file_record()
|
||||
{
|
||||
local record=$1 separator=$'\034'
|
||||
|
||||
RPM_FILENAME=
|
||||
RPM_LINK_TARGET=
|
||||
[[ $record == *"$separator"* ]] || return 1
|
||||
RPM_FILENAME=${record%%"$separator"*}
|
||||
RPM_LINK_TARGET=${record#*"$separator"}
|
||||
[[ $RPM_LINK_TARGET != *"$separator"* ]] || return 1
|
||||
[[ $RPM_FILENAME != *$'\t'* && $RPM_FILENAME != *$'\n'* && $RPM_FILENAME != *$'\r'* ]] || return 1
|
||||
[[ $RPM_LINK_TARGET != *$'\t'* && $RPM_LINK_TARGET != *$'\n'* && $RPM_LINK_TARGET != *$'\r'* ]] || return 1
|
||||
}
|
||||
|
||||
rpm_install_symlink_manifest()
|
||||
{
|
||||
local root=$1 manifest=$2 log=$3 separator=$'\034'
|
||||
local canonical_root sorted row relative link_target safe_target destination parent existing
|
||||
|
||||
canonical_root=$(realpath -m -- "$root") || return 1
|
||||
|
||||
sorted=$(mktemp "${manifest}.sorted.XXXXXX") || return 1
|
||||
if ! awk -F '\t' -v separator="$separator" '
|
||||
NF >= 2 {
|
||||
path=$1
|
||||
depth=gsub(/\//, "/", path)
|
||||
printf "%08d%s%s\n", depth, separator, $0
|
||||
}
|
||||
' "$manifest" | LC_ALL=C sort -t "$separator" -k1,1n -k2,2 >"$sorted"; then
|
||||
rm -f -- "$sorted"
|
||||
return 1
|
||||
fi
|
||||
|
||||
while IFS=$separator read -r _depth row; do
|
||||
IFS=$'\t' read -r relative link_target <<<"$row"
|
||||
[[ -n $relative && -n $link_target ]] || continue
|
||||
destination=$canonical_root/$relative
|
||||
parent=$(realpath -m -- "$(dirname -- "$destination")") || {
|
||||
rm -f -- "$sorted"
|
||||
return 1
|
||||
}
|
||||
[[ $parent == "$canonical_root" || $parent == "$canonical_root"/* ]] || {
|
||||
printf 'escaping RPM symlink parent rejected: %q\n' "/$relative" >>"$log"
|
||||
rm -f -- "$sorted"
|
||||
return 1
|
||||
}
|
||||
if ! safe_target=$(rpm_safe_symlink_target "$canonical_root" "$relative" "$link_target"); then
|
||||
printf 'escaping RPM symlink rejected: %q -> %q\n' "/$relative" "$link_target" >>"$log"
|
||||
rm -f -- "$sorted"
|
||||
return 1
|
||||
fi
|
||||
mkdir -p -- "$parent" || {
|
||||
rm -f -- "$sorted"
|
||||
return 1
|
||||
}
|
||||
|
||||
if [[ -L $destination ]]; then
|
||||
existing=$(readlink -- "$destination") || {
|
||||
rm -f -- "$sorted"
|
||||
return 1
|
||||
}
|
||||
if [[ $existing == "$safe_target" ]]; then
|
||||
continue
|
||||
fi
|
||||
printf 'conflicting RPM symlinks: %q -> %q and %q\n' \
|
||||
"/$relative" "$existing" "$safe_target" >>"$log"
|
||||
rm -f -- "$sorted"
|
||||
return 1
|
||||
elif [[ -e $destination ]]; then
|
||||
printf 'RPM symlink conflicts with existing path: %q\n' "/$relative" >>"$log"
|
||||
rm -f -- "$sorted"
|
||||
return 1
|
||||
fi
|
||||
if ! ln -s -- "$safe_target" "$destination"; then
|
||||
printf 'unable to create RPM symlink: %q -> %q\n' "/$relative" "$safe_target" >>"$log"
|
||||
rm -f -- "$sorted"
|
||||
return 1
|
||||
fi
|
||||
done <"$sorted"
|
||||
|
||||
rm -f -- "$sorted"
|
||||
}
|
||||
Reference in New Issue
Block a user