dbscripts/db-functions

550 lines
13 KiB
Bash

#!/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 <repo-name> <arch> [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-name> <arch>
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}"