#!/hint/bash . /usr/share/makepkg/util.sh # global shell options for enhanced bash scripting shopt -s extglob globstar nullglob # Some PKGBUILDs need CARCH to be set CARCH="x86_64" # Useful functions UMASK="" set_umask () { export UMASK="${UMASK:-$(umask)}" umask 002 } restore_umask () { umask "$UMASK" >/dev/null } # Proxy function to check if a file exists. Using [[ -f ... ]] directly is not # always wanted because we might want to expand bash globs first. This way we # can pass unquoted globs to is_globfile() and have them expanded as function # arguments before being checked. is_globfile() { [[ -f $1 ]] } # just like mv -f, but we touch the file and then copy the content so # default ACLs in the target dir will be applied mv_acl() { rm -f "$2" touch "$2" cat "$1" >"$2" || return 1 rm -f "$1" } # set up general environment WORKDIR=$(mktemp -dt "${0##*/}.XXXXXXXXXX") LOCKS=() REPO_MODIFIED=0 script_lock() { local LOCKDIR="$TMPDIR/.scriptlock.${0##*/}" if ! mkdir "$LOCKDIR" >/dev/null 2>&1 ; then local _owner="$(/usr/bin/stat -c %U "$LOCKDIR")" error "Script %s is already locked by %s." "${0##*/}" "$_owner" exit 1 else set_umask return 0 fi } script_unlock() { local LOCKDIR="$TMPDIR/.scriptlock.${0##*/}" if [[ ! -d $LOCKDIR ]]; then warning "Script %s was not locked!" "${0##*/}" restore_umask return 1 else rmdir "$LOCKDIR" restore_umask return 0 fi } cleanup() { local l local repo local arch trap - EXIT INT QUIT TERM for l in "${LOCKS[@]}"; do repo=${l%.*} arch=${l#*.} if [[ -d $TMPDIR/.repolock.$repo.$arch ]]; then msg "Removing left over lock from [%s] (%s)" "$repo" "$arch" repo_unlock "$repo" "$arch" fi done if [[ -d $TMPDIR/.scriptlock.${0##*/} ]]; then msg "Removing left over lock from %s" "${0##*/}" script_unlock fi rm -rf "$WORKDIR" if (( REPO_MODIFIED )); then date +%s > "${FTP_BASE}/lastupdate" fi [[ -n $1 ]] && exit "$1" } abort() { msg 'Aborting...' cleanup 0 } die() { error "$@" cleanup 1 } trap abort INT QUIT TERM HUP trap cleanup EXIT #repo_lock [timeout] repo_lock() { local repo base=${1}; shift for repo in ${base} ${base}-debug; do _repo_lock ${repo} "${@}" done } _repo_lock () { local LOCKDIR="$TMPDIR/.repolock.$1.$2" local DBLOCKFILE="${FTP_BASE}/${1}/os/${2}/${1}${DBEXT}.lck" local _count local _trial local _timeout local _lockblock local _owner # This is the lock file used by repo-add and repo-remove if [[ -f ${DBLOCKFILE} ]]; then error "Repo [%s] (%s) is already locked by repo-{add,remove} process %s" "$1" "$2" "$(cat "$DBLOCKFILE")" return 1 fi if (( $# == 2 )); then _lockblock=true _trial=0 elif (( $# == 3 )); then _lockblock=false _timeout=$3 let _trial=$_timeout/$LOCK_DELAY fi _count=0 while (( _count <= _trial )) || [[ $_lockblock = true ]]; do if ! mkdir "$LOCKDIR" >/dev/null 2>&1 ; then _owner="$(/usr/bin/stat -c %U "$LOCKDIR")" warning "Repo [%s] (%s) is already locked by %s." "$1" "$2" "$_owner" msg2 "Retrying in %s seconds..." "$LOCK_DELAY" else LOCKS+=("$1.$2") set_umask return 0 fi sleep "$LOCK_DELAY" let _count=$_count+1 done error "Repo [%s] (%s) is already locked by %s. Giving up!" "$1" "$2" "$_owner" return 1 } #repo_unlock repo_unlock () { local repo base=${1}; shift for repo in ${base} ${base}-debug; do _repo_unlock ${repo} "${@}" done } _repo_unlock () { local LOCKDIR="$TMPDIR/.repolock.$1.$2" if [[ ! -d $LOCKDIR ]]; then warning "Repo lock [%s] (%s) was not locked!" "$1" "$2" restore_umask return 1 else rmdir "$LOCKDIR" restore_umask return 0 fi } # usage: _grep_all_info pkgfile infofile key _grep_all_info() { local _ret=() mapfile -t _ret < <(/usr/bin/bsdtar -xOqf "$1" "${2}" | grep "^${3} = ") printf '%s\n' "${_ret[@]#${3} = }" } # usage: _grep_pkginfo pkgfile pattern _grep_pkginfo() { _grep_all_info "${1}" .PKGINFO "${2}" | tail -1 } # usage: _grep_buildinfo pkgfile pattern _grep_buildinfo() { _grep_all_info "${1}" .BUILDINFO "${2}" | tail -1 } # Get the package base or name as fallback getpkgbase() { local _base _base="$(_grep_pkginfo "$1" "pkgbase")" if [[ -z $_base ]]; then getpkgname "$1" else echo "$_base" fi } # Get the package name getpkgname() { local _name _name="$(_grep_pkginfo "$1" "pkgname")" if [[ -z $_name ]]; then error "Package '%s' has no pkgname in the PKGINFO. Fail!" "$1" exit 1 fi echo "$_name" } # Get the pkgver-pkgrel of this package getpkgver() { local _ver _ver="$(_grep_pkginfo "$1" "pkgver")" if [[ -z $_ver ]]; then error "Package '%s' has no pkgver in the PKGINFO. Fail!" "$1" exit 1 fi echo "$_ver" } getpkgarch() { local _ver _ver="$(_grep_pkginfo "$1" "arch")" if [[ -z $_ver ]]; then error "Package '%s' has no arch in the PKGINFO. Fail!" "$1" exit 1 fi echo "$_ver" } getpkgdesc() { local _desc _desc="$(_grep_pkginfo "$1" "pkgdesc")" if [[ -z $_desc ]]; then error "Package '%s' has no pkgdesc in the PKGINFO. Fail!" "$1" exit 1 fi echo "$_desc" } # TODO: We need to not depend on pkgdesc # here be dragons is_debug_package() { local pkgfile=${1} local pkgbase="$(getpkgbase "${pkgfile}")" local pkgname="$(getpkgname "${pkgfile}")" local pkgdesc="$(getpkgdesc "${pkgfile}")" [[ ${pkgdesc} == "Detached debugging symbols for "* && ${pkgbase}-debug = ${pkgname} ]] } check_packager() { local _packager _packager=$(_grep_pkginfo "$1" "packager") [[ -n $_packager && $_packager != 'Unknown Packager' ]] } check_buildinfo() { /usr/bin/bsdtar -tqf "$1" .BUILDINFO >/dev/null 2>&1 } check_builddir() { local _builddir _builddir=$(_grep_buildinfo "$1" "builddir") [[ -n $_builddir && $_builddir = '/build' ]] } # Non fatal getpkgfile expanding globs maybe_getpkgfile() { if (( $# != 1 )); then exit 1 elif [[ ! -f ${1} ]]; then exit 1 elif [[ ! -f ${1}.sig ]]; then error "Package signature %s not found!" "$1.sig" exit 1 fi echo "${1}" } getpkgfile() { if (( $# != 1 )); then error 'No canonical package found!' exit 1 elif [[ ! -f ${1} ]]; then error "Package %s not found!" "$1" exit 1 elif [[ ! -f ${1}.sig ]]; then error "Package signature %s not found!" "$1.sig" exit 1 fi echo "${1}" } getpkgfiles() { local f files if ! printf '%s\n' "${@%\.*}" | awk 'a[$0]++{exit 1}'; then error 'Duplicate packages found!' exit 1 fi for f in "$@"; do files+=("$(getpkgfile "$f")") || exit 1 done echo "${files[@]}" } check_pkgfile() { local pkgfile=$1 local pkgname="$(getpkgname "${pkgfile}")" || return 1 local pkgver="$(getpkgver "${pkgfile}")" || return 1 local pkgarch="$(getpkgarch "${pkgfile}")" || return 1 in_array "${pkgarch}" "${ARCHES[@]}" 'any' || return 1 [[ ${pkgfile##*/} = "${pkgname}-${pkgver}-${pkgarch}"* ]] } # Check that the package file is consistent with the PKGBUILD in version control check_pkgvcs() { local pkgfile="${1}" local repo="${2}" local _pkgbase="$(getpkgbase "${pkgfile}")" || return 1 local _pkgname="$(getpkgname "${pkgfile}")" || return 1 local _pkgver="$(getpkgver "${pkgfile}")" || return 1 local _pkgarch="$(getpkgarch "${pkgfile}")" || return 1 in_array "${repo}" "${PKGREPOS[@]}" || return 1 local vcsver vcsnames=() read -rd'\n' vcsver vcsnames < <(source_pkgbuild "${_pkgbase}" "repos/${repo}-${_pkgarch}"; \ get_full_version; echo "${pkgname[@]}") read -ra vcsnames <<<"${vcsnames}" [[ "${vcsver}" = "${_pkgver}" ]] || return 1 in_array "${_pkgname}" "${vcsnames[@]}" "${_pkgbase}-debug" || return 1 return 0 } check_splitpkgs() { local repo="${1}" shift local pkgfiles=("${@}") local pkgfile local pkgdir local vcsname mkdir -p "${WORKDIR}/check_splitpkgs/" pushd "${WORKDIR}/check_splitpkgs" >/dev/null for pkgfile in "${pkgfiles[@]}"; do local _pkgbase="$(getpkgbase "${pkgfile}")" local _pkgname="$(getpkgname "${pkgfile}")" local _pkgarch="$(getpkgarch "${pkgfile}")" local vcsnames=($(source_pkgbuild "${_pkgbase}" "repos/${repo}-${_pkgarch}"; echo "${pkgname[@]}")) # not a split package (( ${#vcsnames[@]} > 1 )) || continue [[ ${_pkgbase}-debug = ${_pkgname} ]] && continue mkdir -p "${repo}/${_pkgarch}/${_pkgbase}" echo "${_pkgname}" >> "${repo}/${_pkgarch}/${_pkgbase}/staging" printf '%s\n' "${vcsnames[@]}" >> "${repo}/${_pkgarch}/${_pkgbase}/vcs" done popd >/dev/null for pkgdir in "${WORKDIR}/check_splitpkgs/${repo}"/*/*; do [[ ! -d ${pkgdir} ]] && continue sort -u "${pkgdir}/staging" -o "${pkgdir}/staging" sort -u "${pkgdir}/vcs" -o "${pkgdir}/vcs" if [[ ! -z "$(comm -13 "${pkgdir}/staging" "${pkgdir}/vcs")" ]]; then return 1 fi done return 0 } check_pkgrepos() { local pkgfile=$1 local pkgname="$(getpkgname "${pkgfile}")" || return 1 local pkgver="$(getpkgver "${pkgfile}")" || return 1 local pkgarch="$(getpkgarch "${pkgfile}")" || return 1 is_globfile "${FTP_BASE}/${PKGPOOL}/${pkgname}-${pkgver}-${pkgarch}"${PKGEXTS} && return 1 is_globfile "${FTP_BASE}/${PKGPOOL}/${pkgname}-${pkgver}-${pkgarch}"${PKGEXTS}.sig && return 1 [[ -f ${FTP_BASE}/${PKGPOOL}/${pkgfile##*/} ]] && return 1 [[ -f ${FTP_BASE}/${PKGPOOL}/${pkgfile##*/}.sig ]] && return 1 return 0 } check_stagingrepos() { local pkgfile=${1} local pkgrepo=${2} local pkgbase=$(getpkgbase "${pkgfile}") local pkgname=$(getpkgname "${pkgfile}") local pkgarch=$(getpkgarch "${pkgfile}") local candidate candidates=() if in_array "${pkgrepo}" "${STABLE_REPOS[@]}"; then candidates+=($(find_repo_for_package "${pkgbase}" "${pkgarch}" "${TESTING_REPOS[@]}")) fi if in_array "${pkgrepo}" "${STABLE_REPOS[@]}" "${TESTING_REPOS[@]}"; then candidates+=($(find_repo_for_package "${pkgbase}" "${pkgarch}" "${STAGING_REPOS[@]}")) fi (( ${#candidates[@]} == 0 )) && return 0 printf '%s\n' "${candidates[@]%-*}" for candidate in "${candidates[@]}"; do for candidate in "${STAGING}/${candidate%-*}"/*${PKGEXTS}; do [[ ${pkgname} = $(getpkgname "${candidate}" 2>/dev/null) ]] && return 0 done done return 1 } #usage: chk_license ${license[@]}" chk_license() { local l for l in "${@}"; do in_array "${l}" "${ALLOWED_LICENSES[@]}" && return 0 done return 1 } check_repo_configured() { local repo=$1 local count=$(printf '%s\n' "${PKGREPOS[@]}" | grep --count --line-regexp "$repo") [[ $count -gt 0 ]] && return 0 return 1 } check_repo_permission() { local repo=$1 (( ${#PKGREPOS[@]} == 0 )) && return 1 [[ -z "${PKGPOOL}" ]] && return 1 in_array "${repo}" "${PKGREPOS[@]}" "${DEBUGREPOS[@]}" || return 1 [[ -w $FTP_BASE/${PKGPOOL} ]] || return 1 local arch for arch in "${ARCHES[@]}"; do local dir="${FTP_BASE}/${repo}/os/${arch}/" [[ -w ${dir} ]] || return 1 [[ -f ${dir}${repo}${DBEXT} && ! -w ${dir}${repo}${DBEXT} ]] && return 1 [[ -f ${dir}${repo}${FILESEXT} && ! -w ${dir}${repo}${FILESEXT} ]] && return 1 done return 0 } set_repo_permission() { local repo=$1 local arch=$2 local dbfile="${FTP_BASE}/${repo}/os/${arch}/${repo}${DBEXT}" local filesfile="${FTP_BASE}/${repo}/os/${arch}/${repo}${FILESEXT}" if [[ -w ${dbfile} ]]; then local group=$(/usr/bin/stat --printf='%G' "$(dirname "${dbfile}")") chgrp "$group" "${dbfile}" || error "Could not change group of %s to %s" "$dbfile" "$group" chgrp "$group" "${filesfile}" || error "Could not change group of %s to %s" "$filesfile" "$group" chmod g+w "${dbfile}" || error "Could not set write permission for group %s to %s" "$group" "$dbfile" chmod g+w "${filesfile}" || error "Could not set write permission for group %s to %s" "$group" "$filesfile" else error "You don't have permission to change %s" "$dbfile" fi } arch_repo_modify() { local action=$1 local repo=$2 local arch=$3 local pkgs=("${@:4}") local dbfile="${FTP_BASE}/${repo}/os/${arch}/${repo}${DBEXT}" if [[ ${action} = remove && ! -f ${dbfile} ]]; then error "No database found at '%s'" "$dbfile" return 1 fi # package files for repo-add might be relative to repo dir pushd "${dbfile%/*}" >/dev/null /usr/bin/"repo-${action}" -q "${dbfile}" "${pkgs[@]}" \ || error '%s' "repo-${action} ${dbfile@Q} ${pkgs[*]@Q}" set_repo_permission "${repo}" "${arch}" popd >/dev/null REPO_MODIFIED=1 } # Verify the existence of dependent packages needed by a given pkgfile # usage: check_reproducible pkgfile check_reproducible() { local pkg dir pkgs=() pkgfile pkgfiles=() mapfile -t pkgs < <(_grep_all_info "${1}" .BUILDINFO installed) for pkg in "${pkgs[@]}"; do local pkgname=${pkg%-*-*-*} for dir in "${FTP_BASE}"/pool/* "${ARCHIVE_BASE}/packages/${pkgname:0:1}/${pkgname}" "${STAGING}"/**/; do if pkgfile="$(getpkgfile "${dir}/${pkg}"${PKGEXTS} 2>/dev/null)"; then pkgfiles+=("${pkgfile}") continue 2 fi done error "could not find existing or staged package for dependency %s" "${pkg}" return 1 done } . "$(dirname "$(readlink -e "${BASH_SOURCE[0]}")")/db-functions-${VCS}"