#!/bin/sh # contains functions used by more than one script # shellcheck disable=SC2039,SC2119,SC2120,SC3043 if [ -z "${base_dir}" ]; then # just to make shellcheck happy . '../lib/load-configuration' fi # ls_master_mirror $path # list content of $path on the master mirror (via rsync) ls_master_mirror() { local path="$1" ${master_mirror_rsync_command} \ "${master_mirror_rsync_directory}/${path}/" | \ grep -v '\s\.$' | \ awk '{print $5}' | \ grep -v build-support-manual } # remove_old_package_versions # removes all older versions of the packages given at stdin (by bpir.id) # from all repositories less[1] stable than the current repository, as # well as any different version of the same package from equally[2] # stable repositories # 1] determined by `repository_stability_relations` # 2] identical `repositories`.`stability` remove_old_package_versions() { ( # the new shell is intentional tmp_dir=$(mktemp -d 'tmp.common-functions.remove_old_package_versions.XXXXXXXXXX' --tmpdir) trap 'rm -rf --one-file-system "${tmp_dir}"' EXIT while read -r bpir_id; do # shellcheck disable=SC2016 { printf 'SELECT ' printf '`d_bpir`.`id`,' printf 'IF(`d_r`.`stability`=`o_r`.`stability` AND `d_bpir`.`id`!=`o_bpir`.`id`,1,0),' printf 'CONCAT(' printf 'IF(`d_bp`.`epoch`=0,"",CONCAT(`d_bp`.`epoch`,":")),' printf '`d_bp`.`pkgver`,"-",' printf '`d_bp`.`pkgrel`,' printf 'IF(`d_bp`.`sub_pkgrel_omitted`,"",CONCAT(".",`d_bp`.`sub_pkgrel`))' printf '),' printf 'IF(`d_r`.`id`=`o_r`.`id`,1,0),' printf '`d_ra`.`name`,' printf '`d_r`.`name`,' printf '`d_bp`.`pkgname`,' printf 'CONCAT(`d_ra`.`name`,"/",' printf '`d_r`.`name`,"/",' mysql_package_name_query 'd_bp' 'd_bpa' 'd_bpc' printf ')' printf ' FROM `binary_packages_in_repositories` AS `d_bpir`' mysql_join_binary_packages_in_repositories_binary_packages 'd_bpir' 'd_bp' mysql_join_binary_packages_in_repositories_repositories 'd_bpir' 'd_r' printf ' AND `d_r`.`is_on_master_mirror`' mysql_join_repositories_architectures 'd_r' 'd_ra' mysql_join_binary_packages_architectures 'd_bp' 'd_bpa' printf ' LEFT' mysql_join_binary_packages_compressions 'd_bp' 'd_bpc' printf ' JOIN `binary_packages` AS `o_bp`' printf ' ON `d_bp`.`pkgname`=`o_bp`.`pkgname`' mysql_join_binary_packages_binary_packages_in_repositories 'o_bp' 'o_bpir' mysql_join_binary_packages_in_repositories_repositories 'o_bpir' 'o_r' printf ' AND `o_r`.`is_on_master_mirror`' printf ' AND `o_r`.`architecture`=`d_r`.`architecture`' printf ' JOIN `repository_stability_relations`' printf ' ON `repository_stability_relations`.`less_stable`=`d_r`.`stability`' printf ' AND `repository_stability_relations`.`more_stable`=`o_r`.`stability`' printf ' WHERE `o_bpir`.`id`=from_base64("%s")' \ "$( printf '%s' "${bpir_id}" | \ base64 -w0 )" printf ';\n' } | \ mysql_run_query | \ tr '\t' ' ' | \ expand_version 3 | \ sort -k3V,3 -k2r,2 | \ shrink_version 3 | \ sed ' s/^/'"${bpir_id}"' / $ a ' done | \ sed -n ' /^\([0-9]\+\) \1 /,/^$/ d /^$/ d s/^[0-9]\+ \([0-9]\+ \)\(\S\+ \)\{2\}/\1/ h /^[0-9]\+ 0 / { s/^\(\S\+ \)\{2\}// s/ \S\+$// w'"${tmp_dir}"'/repo-removes g } s/^\(\S\+ \)\{5\}// w'"${tmp_dir}"'/sftp-removes g s/ .*$// w'"${tmp_dir}"'/db-removes ' for file in 'repo-removes' 'sftp-remove' 'db-removes'; do if [ -s "${tmp_dir}/${file}" ]; then sort -u "${tmp_dir}/${file}" | \ sponge "${tmp_dir}/${file}" fi done # repo-remove packages while read -r arch repo pkgname; do mkdir "${tmp_dir}/transit" failsafe_rsync \ "${master_mirror_rsync_directory}/${arch}/${repo}/${repo}.db."* \ "${master_mirror_rsync_directory}/${arch}/${repo}/${repo}.files."* \ "${tmp_dir}/transit/" repo-remove "${tmp_dir}/transit/${repo}.db.tar.gz" "${pkgname}" failsafe_rsync \ "${tmp_dir}/transit/${repo}.db."* \ "${tmp_dir}/transit/${repo}.files."* \ "${master_mirror_rsync_directory}/${arch}/${repo}/" rm -rf --one-file-system "${tmp_dir}/transit" done < \ "${tmp_dir}/repo-removes" # db-remove packages if [ -s "${tmp_dir}/db-removes" ]; then # shellcheck disable=SC2016 { printf 'CREATE TEMPORARY TABLE `del` (`id` BIGINT NOT NULL AUTO_INCREMENT, PRIMARY KEY (`id`));\n' printf 'LOAD DATA LOCAL INFILE "%s" INTO TABLE `del` (`id`);\n' \ "${tmp_dir}/db-removes" printf 'DELETE `binary_packages_in_repositories`' printf ' FROM `binary_packages_in_repositories`' printf ' JOIN `del`' printf ' ON `binary_packages_in_repositories`.`id`=`del`.`id`;\n' mysql_query_and_delete_unneeded_binary_packages } | \ mysql_run_query | \ sort -u >> \ "${tmp_dir}/sftp-removes" fi # sftp-remove packages if [ -s "${tmp_dir}/sftp-removes" ]; then sed ' p s/$/.sig/ ' "${tmp_dir}/sftp-removes" | \ sed ' s|^|rm "| s|$|"| ' | \ failsafe_sftp fi ) } # wait_some_time $minimum $diff # wait between minimum and minimum+diff seconds (diff defaults to 30) wait_some_time() { local minimum="$1" local diff="$2" local random if [ -z "${diff}" ]; then diff=30 fi random=$( dd if='/dev/urandom' count=1 2> /dev/null | \ cksum | \ cut -d' ' -f1 ) sleep $((minimum + random % diff)) } # str_to_regex $string # escape dots for use in regex str_to_regex() { echo "$1" | \ sed ' s|[.[]|\\\0|g ' } make_source_info_using_cache() { local package="$1" local repository="$2" local git_revision="$3" local mod_git_revision="$4" local output="$5" curl -LSs "https://buildmaster.archlinux32.org/pkginfo/${package}=${repository}=${git_revision}=${mod_git_revision}" \ >"${output}" } # make_source_info $package $repository $git_revision $mod_git_revision $output # create .SRCINFO from PKGBUILD within git repositories, output to $output make_source_info() { local package="$1" local repository="$2" local git_revision="$3" local mod_git_revision="$4" local output="$5" ( # the new shell is intentional local epoch local pkgver tmp_dir=$(mktemp -d "${work_dir}/tmp.make_source_info.XXXXXX") trap 'rm -rf --one-file-system "${tmp_dir}"' EXIT extract_source_directory "${package}" "${repository}" "${git_revision}" "${mod_git_revision}" "${tmp_dir}" '0' { cd "${tmp_dir}" makepkg --printsrcinfo cd .. } > \ "${tmp_dir}/SRCINFO" unset epoch unset pkgver eval "$( sed -n ' s/^\t\(epoch\|pkgver\) = /\1=/ T p ' "${tmp_dir}/SRCINFO" )" # shellcheck disable=SC2031 sed -i ' /^pkgname = /! b /= gtk-doc$/ b /= linux-api-headers$/ b s/= \(openjdk[0-9]\+\)-doc$/\0\n\tdepends = \1-src/ t append_version s/= \(qt5\)-doc$/\0\n\tdepends = \1-base/ t append_version s/= \(\S\+\)-i18n-\S\+$/\0\n\tdepends = \1/ t append_version s/= \(linux\(-\S\+\)\?\)-\(docs\|headers\)$/\0\n\tdepends = \1/ t append_version b :append_version s/$/='"${epoch}${epoch+:}${pkgver}"'/ ' "${tmp_dir}/SRCINFO" cat "${tmp_dir}/SRCINFO" > \ "${output}" ) } # recursively_umount_and_rm $dir # umount all mountpoints in $dir which are also in $dir's # filesystem, possibly also $dir itself and then # rm -rf --one-file-system $dir recursively_umount_and_rm() { local dir="$1" if [ -z "${dir}" ]; then >&2 echo 'ERROR: recursively_umount_and_rm requires an argument' exit 42 fi if [ ! -d "${dir}" ]; then >&2 printf 'ERROR: recursively_umount_and_rm requires a directory as argument - "%s" is not\n' \ "${dir}" exit 42 fi # the sh -c '...' construct branch below is borrowed from # archlinux/devtools lib/archroot.sh: is_subvolume(), is_same_fs() and # subvolume_delete_recursive() # shellcheck disable=SC1004,SC2016 find "${dir}" \ -depth \ -xdev \ -type d \ -exec 'mountpoint' '-q' '{}' ';' \ -exec 'sudo' 'umount' '-l' '{}' ';' \ -prune \ , \ -inum 256 \ -exec sh -c ' [ "$(stat -f -c %T "$1")" = btrfs ] && \ [ "$(stat -c %d "$1")" = "$2" ] ' {} "$(stat -c %d "${dir}")" \; \ -exec btrfs subvolume delete {} \; \ -prune || true rm -rf --one-file-system "${dir}" || true } # mangle_pkgbuild $PKGBUILD [$sub_pkgrel] # mangle $arch in PKBUILDs to contain i486, i686, pentium4 # append $sub_pkgrel to the pkgrel # remove "lib32-" and "gcc-multilib" from {make,check,opt,}depends # remove $pkgrel from {make,check,opt,}depends mangle_pkgbuild() { local PKGBUILD="$1" local sub_pkgrel="$2" if [ -n "${sub_pkgrel}" ]; then sub_pkgrel=".${sub_pkgrel}" fi if grep -q '^\s*pkgname=["'"'"']\?lib32-' "${PKGBUILD}"; then sed -i ' s/^\(\s*pkgrel=\)['"'"'"]\?\([0-9]\+\)\.[0-9]*['"'"'"]\?\s*\(#.*\)\?$/\1"\2"/ ' "${PKGBUILD}" fi sed -i ' /^arch=[^#]*any/!{ /^arch=(/s/(/(i486 i686 pentium4 / } s/^\(\s*pkgrel=\)['"'"'"]\?\([0-9]\+\)\(\.[0-9.]*\)\?['"'"'"]\?\s*\(#.*\)\?$/\1"\2'"${sub_pkgrel:-\\3}"'"/ ' "${PKGBUILD}" # shellcheck disable=SC2016 sed -i ' /^\s*\(make\|check\|opt\|\)depends\(_[^=[:space:]]\+\)\?=(/ { :a /^\s*\(\S[^=]*\)=(\(\([^()"'"'"']\|"[^"]*"\|['"'"'][^'"'"']*['"'"']\s*\)*\(#[^\n]*\n\)\?\)*)/! { $b N ba } :b s/\(=.*["'"'"'([:space:]]\)lib32-/\1/g s/\(=.*["'"'"'([:space:]]\)gcc-multilib\(["'"'"')[:space:]]\)/\1gcc\2/g s/\(=.*["'"'"'([:space:]][^[:space:]$]\+[<=>]\S\+\)-[^:"'"'"')[:space:]]\+\([:"'"'"')[:space:]]\)/\1\2/g tb } ' "${PKGBUILD}" } # find_package_repository_to_package $package $git_repository $git_commit # find the package repository a package from a given git repository # belongs to find_package_repository_to_package() { local package="$1" local git_repository="$2" local git_commit="$3" local repo_path local repo eval 'repo_path="${repo_paths__'"${git_repository}"'}"' if [ "${git_repository}" = 'archlinux32' ]; then repo=$( git -C "${repo_path}" archive "${git_commit}" -- | \ tar -t --wildcards "*/${package}/" | \ cut -d/ -f1 | \ grep -vxF blacklist | \ sort -u ) else repo=$( git -C "${repo_path}" archive "${git_commit}" -- "${package}/repos" 2> /dev/null | \ tar -t | \ cut -d/ -f3 | \ grep -vxF '' | \ grep -v 'staging\|testing\|-unstable' | \ grep -v -- '-i686$' | \ sed 's|-[^-]\+$||' | \ sort -u ) fi if [ -z "${repo}" ]; then >&2 printf 'find_package_repository_to_package %s %s %s: no repo found\n' \ "${package}" \ "${git_repository}" \ "${git_commit}" return 1 fi if [ "$( echo "${repo}" | \ wc -l )" -ne 1 ]; then >&2 printf 'find_package_repository_to_package %s %s %s: multiple repos found:\n' \ "${package}" \ "${git_repository}" \ "${git_commit}" >&2 printf '%s\n' "${repo}" >&2 printf 'trying to filter based on git\n' if ! repo=$( # shellcheck disable=SC2016 { printf 'SELECT `upstream_repositories`.`name`' printf ' FROM `upstream_repositories`' mysql_join_upstream_repositories_git_repositories printf ' WHERE `git_repositories`.`name`=from_base64("%s")' \ "$( printf '%s' "${git_repository}" \ | base64 -w0 )" } | \ mysql_run_query 'unimportant' | \ grep -xF "${repo}" ); then >&2 printf 'find_package_repository_to_package %s %s %s: no repo found after filtering\n' \ "${package}" \ "${git_repository}" \ "${git_commit}" return 1 fi if [ "$( echo "${repo}" | \ wc -l )" -ne 1 ]; then >&2 printf 'find_package_repository_to_package %s %s %s: still multiple repos found after filter:\n' \ "${package}" \ "${git_repository}" \ "${git_commit}" >&2 printf '%s\n' "${repo}" return 1 fi fi echo "${repo}" } # extract_source_directory $pkgbase $repository $revision $mod_revision $output $sub_pkgrel # extract files found in the svn/git source directories extract_source_directory() { local pkgbase="$1" local repository="$2" local revision="$3" local mod_revision="$4" local output="$5" local sub_pkgrel="$6" local res local tmptarfile local pkgtarfile if [ -n "${revision}" ] \ && [ "${revision}" != '0000000000000000000000000000000000000000' ] \ && [ "${repository}" != 'build-support' ]; then local pkgbase_translated pkgbase_translated=$(gitlab_project_name_to_path "${pkgbase}") tmptarfile=$(mktemp "${tmp_dir}"'/tmp.upstream-package.XXXXXXXXXX') url="https://buildmaster.archlinux32.org/upstream-packages/${pkgbase_translated}-${revision}.tar.gz" for i in {1..4}; do curl -o "$tmptarfile" -fLSs "${url}" res=$? if [ "$res" -ne 0 ]; then >&2 printf 'Download upstream-package "%s" from upstream via buildmaster cache failed. Curl exit code was %s.' \ "${url}" \ "${res}" return 1 fi # If zero bytes, try again if [ -s "${tmptarfile}" ]; then break fi sleep 5 done if [ -f "${tmptarfile}" ]; then tar -xz -f "${tmptarfile}" --strip-components=1 -C "${output}" -- "${pkgbase_translated}-${revision}" printf '\n' >> \ "${output}/PKGBUILD" else >&2 echo 'No upstream tarfile, assuming the package is a local one and has a full PKGBUILD not a diff-PKGBUILD.' fi fi if [ -n "${mod_revision}" ] \ && [ "${mod_revision}" != '0000000000000000000000000000000000000000' ]; then git -C "${repo_paths__archlinux32}" archive "${mod_revision}" -- "${repository}/${pkgbase}" \ |tar -x --overwrite --exclude 'PKGBUILD' --strip-components=2 -C "${output}" 2> /dev/null \ ||true git -C "${repo_paths__archlinux32}" archive "${mod_revision}" -- "${repository}/${pkgbase}/PKGBUILD" \ |tar -Ox "${repository}/${pkgbase}/PKGBUILD" \ >>"${output}/PKGBUILD" printf '\n' \ >>"${output}/PKGBUILD" fi # we do not want to update pkgver, so we just undefine it printf 'unset -f pkgver\n' >> \ "${output}/PKGBUILD" mangle_pkgbuild "${output}/PKGBUILD" "${sub_pkgrel}" # shellcheck disable=SC2016 sed -i '/^\$Id\$$/d' "${output}/PKGBUILD" # we don't want write permissions on the PKGBUILD - otherwise pkgver() # will change the version! (**HACK**) chmod oga-w "${output}/PKGBUILD" } # download_sources_by_hash # try to download all sources by their hash into the current directory # returns 0 if any source was downloaded and 1 otherwise download_sources_by_hash() { local return_value=1 local tmp_dir local sum_type local arch_suffix if [ ! -f 'PKGBUILD' ]; then >&2 echo 'No PKGBUILD found - download_sources_by_hash() must be run from the source directory.' return 1 fi tmp_dir=$(mktemp -d 'tmp.common-functions.download_sources_by_hash.XXXXXXXXXX' --tmpdir="$(pwd)") makepkg --printsrcinfo > "${tmp_dir}/.SRCINFO" for arch_suffix in '' '_i486' '_i686' '_pentium4'; do for sum_type in 'md5sum' 'sha1sum' 'sha256sum' 'sha512sum'; do grep '^\s*'"${sum_type}s${arch_suffix}"' = ' "${tmp_dir}/.SRCINFO" | \ sed 's|^.* = ||' | \ cat -n > \ "${tmp_dir}/sums" grep '^\s*source'"${arch_suffix}"' = ' "${tmp_dir}/.SRCINFO" | \ sed ' s|^.* = || s|::.*$|| s|.*/|| ' | \ cat -n > \ "${tmp_dir}/urls" if [ "$(wc -l < "${tmp_dir}/sums")" -eq "$(wc -l < "${tmp_dir}/urls")" ]; then join -1 1 -2 1 -o 1.2,2.2 "${tmp_dir}/sums" "${tmp_dir}/urls" > \ "${tmp_dir}/joined" while read -r sum file; do if [ "${sum}" = 'SKIP' ]; then continue fi if echo "${sum} ${file}" | \ ${sum_type} -c > /dev/null 2>&1; then # the correct source is already there continue fi if wget -O "${tmp_dir}/transfer" "${source_by_hash_mirror}${sum_type}/${sum}"; then mv "${tmp_dir}/transfer" "${file}" return_value=0 fi done < \ "${tmp_dir}/joined" fi done done rm -rf --one-file-system "${tmp_dir}" return ${return_value} } # expand_version $column_num # add "0:" to version in $colum_num-th column if no ":" is there (epoch) # add "+0" to version in $colum_num-th column if no "+" is there (git count/hash) expand_version() { local column_num column_num="$1" sed ' /^\(\S\+\s\+\)\{'"$((column_num-1))"'\}\S*+/! s/^\(\(\S\+\s\+\)\{'"$((column_num-1))"'\}\S*\)-/\1+0-/ /^\(\S\+\s\+\)\{'"$((column_num-1))"'\}\S*:/! s/^\(\(\S\+\s\+\)\{'"$((column_num-1))"'\}\)/\10:/ ' } # shrink_version $column_num # remove "0:" from version in $colum_num-th column (epoch) # remove "+0" from version in $colum_num-th column (git count/hash) shrink_version() { local column_num column_num="$1" sed ' s/^\(\(\S\+\s\+\)\{'"$((column_num-1))"'\}\S*\)+0-/\1-/ s/^\(\(\S\+\s\+\)\{'"$((column_num-1))"'\}\)0:/\1/ ' } # sort_square_bracket_content $file # sort the content of [] in $file, print to stdout sort_square_bracket_content() { local file local line local token local token_list local rest file="$1" while read -r line; do printf '%s ' "${line}" | \ tr ' ' '\n' | \ while read -r token; do if echo "${token}" | \ grep -qF '['; then printf '%s[' "${token%[*}" token="${token##*[}" token_list="${token%,}" while ! echo "${token_list}" | \ grep -qF ']'; do read -r token token_list=$( printf '%s\n' \ "${token_list}" \ "${token%,}" ) done rest="]${token_list#*]}" token_list="${token_list%%]*}" token=$( printf '%s' "${token_list}" | \ sort | \ sed 's|$|,|' printf '%s' "${rest}" ) fi printf '%s\n' "${token}" done | \ tr '\n' ' ' | \ sed ' s|, ]|]|g s| $|| ' printf '\n' done < \ "${file}" } # smoothen_namcap_log $file # remove unneccesary differences from namcap-logs: # - remove architecture specific information # - sort lines # - sort content of square brackets smoothen_namcap_log() { local file file="$1" # shellcheck disable=SC2016 sort_square_bracket_content "${file}" | \ sed ' # normalize architecture specific information s|i[34567]86|$ARCH|g s|x86\([-_]64\)\?|$ARCH|g # remove haskell hashes s|\('"'"'[^'"'"']*-[0-9.]\+\)-[a-zA-Z0-9]\{1,22\}\(-ghc[^'"'"']*'"'"'\)|\1\2|g ' | \ sort | \ sponge "${file}" } # trigger_mirror_refreshs # trigger a refresh of capable tier 1 mirrors (as backup for master mirror) trigger_mirror_refreshs() { local tmp_file tmp_file=$(mktemp 'tmp.common-functions.trigger_mirror_refreshs.XXXXXXXXXX' --tmpdir) date '+%s' > \ "${tmp_file}" failsafe_rsync \ "${tmp_file}" \ "${master_mirror_rsync_directory}/lastupdate" rm "${tmp_file}" screen -wipe || true for trigger_url in ${mirror_refresh_trigger_urls}; do screen -S trigger-mirror-update -d -m curl -L --connect-timeout 10 "${trigger_url}" done } # extract_pkgname_epoch_pkgver_pkgrel_sub_pkgrel_arch_from_package_name extract_pkgname_epoch_pkgver_pkgrel_sub_pkgrel_arch_from_package_name() { pkgname="$1" pkgname="${pkgname%.pkg.tar*}" arch="${pkgname##*-}" pkgname="${pkgname%-*}" sub_pkgrel="${pkgname##*-}" pkgname="${pkgname%-*}" pkgrel="${sub_pkgrel%.*}" if [ "${pkgrel}" = "${sub_pkgrel}" ]; then sub_pkgrel='0' else sub_pkgrel="${sub_pkgrel##*.}" fi epoch="${pkgname##*-}" pkgname="${pkgname%-*}" pkgver="${epoch#*:}" if [ "${pkgver}" = "${epoch}" ]; then epoch='0' else epoch="${epoch%%:*}" fi } # irc_say $channel [copy] # say content of stdin in irc channel $channel (default: #archlinux32-devops) # and print copy to stdout if 'copy' is given # shellcheck disable=SC2120 irc_say() { local channel local content if [ -z "$1" ]; then channel='#archlinux32-devops' else channel="$1" fi if [ -s "${work_dir}/irc-shut-up" ] && \ [ "$(date '+%s')" -gt "$(cat "${work_dir}/irc-shut-up")" ]; then rm "${work_dir}/irc-shut-up" fi content=$(cat) if [ "$2" = 'copy' ]; then printf '%s\n' "${content}" fi if [ -s "${work_dir}/irc-shut-up" ] && \ [ -z "${channel%%#*}" ]; then return fi if [ -p "${irc_dir}/${channel}/in" ]; then printf '%s\n' "${content}" \ | sponge "${irc_dir}/${channel}/in" else printf '%s\n' "${content}" \ | sed 's@^@/j '"${channel}"' @' \ | sponge "${irc_dir}/in" fi } # calculate_script_checksum # calculate and print a checksum of the main script and all scripts in lib/ calculate_script_checksum() { { sha512sum "$0" find "${base_dir}/lib" -type f \ -exec sha512sum '{}' \; } | \ sort | \ awk '{print $1}' | \ sha512sum | \ awk '{print $1}' } # verbose_flock # flock wrapper with some informational output on error verbose_flock() { local err=0 flock "$@" || { err=$? lsof +c0 "/proc/$$/fd/$( printf '%s\n' "$@" | \ grep -vm1 '^-' )" >&2 2>/dev/null || true >&2 printf 'FYI: I am %s.\n' "$$" return ${err} } } # recompress_gz $tmp_dir $file1.gz $file2.gz ... # recompress the given file(s) to make them rsync friendly recompress_gz() { tmp_file=$(mktemp "$1/recompress_gz.XXXXXXXX") shift local file for file in "$@"; do if [ ! -f "${file}" ]; then continue fi mv "${file}" "${tmp_file}" zcat "${tmp_file}" | \ gzip --best --rsyncable > \ "${file}" done rm "${tmp_file}" } # failsafe_sftp # execute the commands from stdin on the master mirror, retrying if # unsuccessful # caveats: # - only checks for success of "rm", "ln", "rename" # - commands must be executable in arbitrary order failsafe_sftp() { ( # new shell is intentional temp_dir=$(mktemp -d 'tmp.common-functions.sftp_failsafe.XXXXXXXXXX' --tmpdir) trial_counter=20 trap 'rm -rf --one-file-system "${temp_dir}"' EXIT cat > "${temp_dir}/input" sed -n ' s/^rm "\([^"]\+\)"$/- \1/ s/^ln\( [^"]\S*\)* "[^"]\+" "\([^"]\+\)"$/+ \2/ s/^rename "\([^"]\+\)" "\([^"]\+\)"$/- \1\n+ \2/ T p ' "${temp_dir}/input" > \ "${temp_dir}/expectations" if sed ' 1 i cd "'"${master_mirror_sftp_root}"'" ' "${temp_dir}/input" \ | ${master_mirror_sftp_command}; then # we're done exit 0 fi # make failing sftp commands non-fatal - maybe some succeeded above? sed -i ' s/^[^-]/-\0/ ' "${temp_dir}/input" # we stop on exhausted $trial_counter while [ ${trial_counter} -gt 0 ]; do wait_some_time 30 # success requires 3 things: # - succeeding sftp executions (commands are non-critical already) # - succeeding sftp command to create listing (e.g. all expected # files are found) # - no unexpected files were found if sed ' 1 i cd "'"${master_mirror_sftp_root}"'" ' "${temp_dir}/input" \ | ${master_mirror_sftp_command} && \ sed ' s/^+ \(\S\+\)$/ls -1 "\1"/ s,^- \(\S\+/\)[^/]\+$,ls -1 "\1", ' "${temp_dir}/expectations" | \ sort -u | \ sed ' 1 i cd "'"${master_mirror_sftp_root}"'" ' "${temp_dir}/input" | \ ${master_mirror_sftp_command} > "${temp_dir}/check" && \ ! grep -qxF "$( sed -n ' s/^- // T p ' "${temp_dir}/expectations" )" "${temp_dir}/check"; then exit 0 fi trial_counter=$((trial_counter-1)) done exit 1 ) || \ return $? } # failsafe_rsync # execute rsync with the given parameters on the master mirror, retrying # if unsuccessful # caveats: # - output might be garbled # - error code will be projected into {0,1} failsafe_rsync() { local trial_counter trial_counter=20 if ${master_mirror_rsync_command} "$@"; then return 0 fi while [ ${trial_counter} -gt 0 ]; do wait_some_time 30 if ${master_mirror_rsync_command} "$@"; then return 0 fi trial_counter=$((trial_counter-1)) done return 1 } # update_blocked_packages_count # update the count how many packages are blocked by a build assignment update_blocked_packages_count() { ( # new shell is intentional temp_dir=$(mktemp -d 'tmp.sanity-check.XXXXXXXXXX' --tmpdir) trap 'rm -rf --one-file-system "${temp_dir}"' EXIT # shellcheck disable=SC2016 { printf 'SELECT ' printf '`a_bp`.`build_assignment`,' printf '`b_bp`.`build_assignment`' printf ' FROM `binary_packages` AS `a_bp`' mysql_join_binary_packages_binary_packages_in_repositories 'a_bp' 'a_bpir' printf ' AND `a_bpir`.`repository`=%s' \ "${repository_ids__any_build_list}" mysql_join_binary_packages_dependencies 'a_bp' mysql_join_dependencies_dependency_types printf ' AND `dependency_types`.`relevant_for_binary_packages`' mysql_join_dependencies_install_target_providers_with_versions mysql_join_install_target_providers_binary_packages '' 'b_bp' mysql_join_binary_packages_binary_packages_in_repositories 'b_bp' 'b_bpir' printf ' AND `a_bpir`.`repository`=%s' \ "${repository_ids__any_build_list}" printf ' WHERE `a_bp`.`architecture`=`b_bp`.`architecture`' printf ' OR `a_bp`.`architecture`=%s' \ "${architecture_ids__any}" printf ' OR `b_bp`.`architecture`=%s' \ "${architecture_ids__any}" } | \ mysql_run_query 'unimportant' | \ tr '\t' ' ' | \ sort -u | \ sort -k1,1 > \ "${temp_dir}/links.new" touch "${temp_dir}/links.1" "${temp_dir}/links.2" # new links (sorted by 1st column); old links (sorted by 1st / 2nd column) while [ -s "${temp_dir}/links.new" ]; do cat "${temp_dir}/links.1" "${temp_dir}/links.new" | \ sort -k2,2 > \ "${temp_dir}/links.2" sort -k1,1 "${temp_dir}/links.2" > \ "${temp_dir}/links.1" duplicates=$( uniq -d "${temp_dir}/links.1" ) if [ -n "${duplicates}" ]; then >&2 echo 'internal error: there are %s duplicate links' \ "$( printf '%s\n' "${duplicates}" | \ wc -l )" exit 42 fi { { join -1 2 -2 1 -o 1.1,2.2 "${temp_dir}/links.2" "${temp_dir}/links.new" sort -k2,2 "${temp_dir}/links.new" | \ join -1 2 -2 1 -o 1.1,2.2 - "${temp_dir}/links.1" sort -k2,2 "${temp_dir}/links.new" | \ join -1 2 -2 1 -o 1.1,2.2 - "${temp_dir}/links.new" } | \ sort -u sed 'p' "${temp_dir}/links.1" } | \ sort | \ uniq -u | \ sort -k1,1 | \ sponge "${temp_dir}/links.new" done cut -d' ' -f2 "${temp_dir}/links.2" | \ uniq -c | \ sed 's/^\s\+//' | \ tr ' ' '\t' > \ "${temp_dir}/link-counts" # shellcheck disable=SC2016 { printf 'CREATE TEMPORARY TABLE `lc` (' printf '`count` MEDIUMINT,' printf '`ba_id` BIGINT,' printf 'PRIMARY KEY (`ba_id`)' printf ');\n' printf 'LOAD DATA LOCAL INFILE "%s" INTO TABLE `lc`(`count`,`ba_id`);\n' \ "${temp_dir}/link-counts" printf 'UPDATE `build_assignments`' printf ' LEFT JOIN `lc`' printf ' ON `build_assignments`.`id`=`lc`.`ba_id`' printf ' SET `build_assignments`.`currently_blocking`=`lc`.`count`;\n' } | \ mysql_run_query 'unimportant' ) } # extract_dependencies_from_package $pkgfile # extract make-, run- and check-depends from $pkgfile # format: deptype install_target version_relation epoch version extract_dependencies_from_package() { { bsdtar -Oxf "$1" '.PKGINFO' \ | sed ' s/^checkdepend = /check / t s/^depend = /run / t s/^makedepend = /make / t d ' \ | sed ' s/\(=\|[<>]=\?\)\([^-]\+\)\(-[^-]\+\)\?$/ \1 \2/ t s/$/ >= '"${min_version}"'/ ' \ | sed ' s/:\(\S\+\)$/ \1/ t s/ \(\S\+\)$/ 0 \1/ ' # inject base & base-devel printf ' >= %s\n' \ "${min_version}" \ | tr ':' ' ' \ | sed ' s/^.*$/run base\0\nmake base-devel\0/ ' } \ | sort -u } # expand_blacklist_architectures $tmp_file # expand the architecture of the blacklisted packages given on stdin as # lines # $arch$pkgbase/$pkgname/$whatever expand_blacklist_architectures() { # shellcheck disable=SC2016 { printf 'SELECT `superior`.`name`,`inferior`.`name`' printf ' FROM `architectures` AS `inferior`' printf ' JOIN `architecture_compatibilities`' printf ' ON `architecture_compatibilities`.`built_for`=`inferior`.`id`' printf ' JOIN `architectures` AS `superior`' printf ' ON `architecture_compatibilities`.`runs_on`=`superior`.`id`' # "any" has a special role - it should be regarded as *inferior* to # all architectures printf ' WHERE `superior`.`name`!="any"' printf ' AND `inferior`.`name`!="any";\n' printf 'SELECT "any",`architectures`.`name`' printf ' FROM `architectures`;\n' } \ | mysql_run_query \ | sort -k1,1 \ > "$1" sort -k1,1 \ | join -1 1 -2 1 -o 1.2,2.2 "$1" - \ | sort -u rm "$1" } # taken verbatim from upstream archlinux: # https://gitlab.archlinux.org/archlinux/devtools/-/raw/master/src/lib/api/gitlab.sh # Convert arbitrary project names to GitLab valid path names. # # GitLab has several limitations on project and group names and also maintains # a list of reserved keywords as documented on their docs. # https://docs.gitlab.com/ee/user/reserved_names.html # # 1. replace single '+' between word boundaries with '-' # 2. replace any other '+' with literal 'plus' # 3. replace any special chars other than '_', '-' and '.' with '-' # 4. replace consecutive '_-' chars with a single '-' # 5. replace 'tree' with 'unix-tree' due to GitLab reserved keyword gitlab_project_name_to_path() { local name=$1 printf "%s" "${name}" \ | sed -E 's/([a-zA-Z0-9]+)\+([a-zA-Z]+)/\1-\2/g' \ | sed -E 's/\+/plus/g' \ | sed -E 's/[^a-zA-Z0-9_\-\.]/-/g' \ | sed -E 's/[_\-]{2,}/-/g' \ | sed -E 's/^tree$/unix-tree/g' }