mirror of
https://github.com/moparisthebest/pacman
synced 2024-12-22 07:48:50 -05:00
8be08f7cae
su is terribad. In addition to reverting, this also removes support for
privilege escalation via su. If you want to use paccache as root and
fail to comprehend how much better sudo is than su, then run paccache
directly via su.
Fixes FS#35173.
This reverts commit 597286eb25
.
Signed-off-by: Dave Reisner <dreisner@archlinux.org>
Signed-off-by: Allan McRae <allan@archlinux.org>
318 lines
8.5 KiB
Bash
318 lines
8.5 KiB
Bash
#!/bin/bash
|
|
#
|
|
# pacache - flexible pacman cache cleaning
|
|
#
|
|
# Copyright (C) 2011 Dave Reisner <dreisner@archlinux.org>
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
shopt -s extglob
|
|
|
|
declare -r myname='paccache'
|
|
declare -r myver='@PACKAGE_VERSION@'
|
|
|
|
declare -a candidates=() cmdopts=() whitelist=() blacklist=()
|
|
declare -i delete=0 dryrun=0 filecount=0 move=0 needsroot=0 totalsaved=0 verbose=0
|
|
declare cachedir=@localstatedir@/cache/pacman/pkg delim=$'\n' keep=3 movedir= scanarch=
|
|
|
|
USE_COLOR='y'
|
|
|
|
m4_include(../scripts/library/output_format.sh)
|
|
m4_include(../scripts/library/parseopts.sh)
|
|
|
|
die() {
|
|
error "$@"
|
|
exit 1
|
|
}
|
|
|
|
# reads a list of files on stdin and prints out deletion candidates
|
|
pkgfilter() {
|
|
# there's whitelist and blacklist parameters passed to this
|
|
# script after the block of awk.
|
|
|
|
awk -v keep="$1" -v scanarch="$2" '
|
|
function parse_filename(filename, parts, count, i, pkgname, arch) {
|
|
|
|
count = split(filename, parts, "-")
|
|
|
|
i = 1
|
|
pkgname = parts[i++]
|
|
while (i <= count - 3) {
|
|
pkgname = pkgname "-" parts[i++]
|
|
}
|
|
|
|
arch = substr(parts[count], 1, index(parts[count], ".") - 1)
|
|
|
|
# filter on whitelist or blacklist
|
|
if (wlen && !whitelist[pkgname]) return
|
|
if (blen && blacklist[pkgname]) return
|
|
|
|
if ("" == packages[pkgname,arch]) {
|
|
packages[pkgname,arch] = filename
|
|
} else {
|
|
packages[pkgname,arch] = packages[pkgname,arch] SUBSEP filename
|
|
}
|
|
}
|
|
|
|
BEGIN {
|
|
# create whitelist
|
|
wlen = ARGV[1]; delete ARGV[1]
|
|
for (i = 2; i < 2 + wlen; i++) {
|
|
whitelist[ARGV[i]] = 1
|
|
delete ARGV[i]
|
|
}
|
|
|
|
# create blacklist
|
|
blen = ARGV[i]; delete ARGV[i]
|
|
while (i++ < ARGC) {
|
|
blacklist[ARGV[i]] = 1
|
|
delete ARGV[i]
|
|
}
|
|
|
|
# read package filenames
|
|
while (getline < "/dev/stdin") {
|
|
parse_filename($0)
|
|
}
|
|
|
|
for (pkglist in packages) {
|
|
# idx[1,2] = idx[pkgname,arch]
|
|
split(pkglist, idx, SUBSEP)
|
|
|
|
# enforce architecture match if specified
|
|
if (!scanarch || scanarch == idx[2]) {
|
|
count = split(packages[idx[1], idx[2]], pkgs, SUBSEP)
|
|
for(i = 1; i <= count - keep; i++) {
|
|
print pkgs[i]
|
|
}
|
|
}
|
|
}
|
|
}' "${@:3}"
|
|
}
|
|
|
|
m4_include(../scripts/library/size_to_human.sh)
|
|
|
|
runcmd() {
|
|
if (( needsroot && EUID != 0 )); then
|
|
msg "Privilege escalation required"
|
|
if sudo -v &>/dev/null && sudo -l &>/dev/null; then
|
|
sudo "$@"
|
|
else
|
|
die 'Unable to escalate privileges using sudo'
|
|
fi
|
|
else
|
|
"$@"
|
|
fi
|
|
}
|
|
|
|
summarize() {
|
|
local -i filecount=$1; shift
|
|
local seenarch= seen= arch= name=
|
|
local -r pkg_re='(.+)-[^-]+-[0-9]+-([^.]+)\.pkg.*'
|
|
|
|
if (( delete )); then
|
|
printf -v output 'finished: %d packages removed' "$filecount"
|
|
elif (( move )); then
|
|
printf -v output "finished: %d packages moved to '%s'" "$filecount" "$movedir"
|
|
elif (( dryrun )); then
|
|
if (( verbose )); then
|
|
msg "Candidate packages:"
|
|
while read -r pkg; do
|
|
if (( verbose >= 3 )); then
|
|
[[ $pkg =~ $pkg_re ]] && name=${BASH_REMATCH[1]} arch=${BASH_REMATCH[2]}
|
|
if [[ -z $seen || $seenarch != "$arch" || $seen != "$name" ]]; then
|
|
seen=$name seenarch=$arch
|
|
printf '%s (%s):\n' "$name" "$arch"
|
|
fi
|
|
printf ' %s\n' "$pkg"
|
|
elif (( verbose >= 2 )); then
|
|
printf "$PWD/%s$delim" "$pkg"
|
|
else
|
|
printf "%s$delim" "$pkg"
|
|
fi
|
|
done < <(printf '%s\n' "$@" | pacsort --files)
|
|
fi
|
|
printf -v output 'finished dry run: %d candidates' "$filecount"
|
|
fi
|
|
|
|
printf '\n' >&2
|
|
msg "$output (diskspace saved: %s)" "$(size_to_human "$totalsaved")"
|
|
}
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
usage: $myname <operation> [options] [targets...]
|
|
|
|
$myname is a flexible pacman cache cleaning utility, which has numerous
|
|
options to help control how much, and what, is deleted from any directory
|
|
containing pacman package tarballs.
|
|
|
|
Operations:
|
|
-d, --dryrun perform a dry run, only finding candidate packages.
|
|
-m, --move <dir> move candidate packages to 'movedir'.
|
|
-r, --remove remove candidate packages.
|
|
|
|
Options:
|
|
-a, --arch <arch> scan for 'arch' (default: all architectures).
|
|
-c, --cachedir <dir> scan 'cachedir' for packages (default: @localstatedir@/cache/pacman/pkg).
|
|
-f, --force apply force to mv(1) and rm(1) operations.
|
|
-h, --help display this help message and exit.
|
|
-i, --ignore <pkgs> ignore 'pkgs', comma separated. Alternatively, specify '-' to
|
|
read package names from stdin, newline delimited.
|
|
-k, --keep <num> keep 'num' of each package in 'cachedir' (default: 3).
|
|
--nocolor remove color from output.
|
|
-u, --uninstalled target uninstalled packages.
|
|
-v, --verbose increase verbosity. specify up to 3 times.
|
|
-z, --null use null delimiters for candidate names (only with -v and -vv)
|
|
|
|
EOF
|
|
}
|
|
|
|
version() {
|
|
printf "%s %s\n" "$myname" "$myver"
|
|
echo 'Copyright (C) 2011 Dave Reisner <dreisner@archlinux.org>'
|
|
}
|
|
|
|
OPT_SHORT=':a:c:dfhi:k:m:rsuVvz'
|
|
OPT_LONG=('arch:' 'cachedir:' 'dryrun' 'force' 'help' 'ignore:' 'keep:' 'move'
|
|
'nocolor' 'remove' 'uninstalled' 'version' 'verbose' 'null')
|
|
|
|
if ! parseopts "$OPT_SHORT" "${OPT_LONG[@]}" -- "$@"; then
|
|
exit 1
|
|
fi
|
|
set -- "${OPTRET[@]}"
|
|
unset OPT_SHORT OPT_LONG OPTRET
|
|
|
|
while :; do
|
|
case $1 in
|
|
-a|--arch)
|
|
scanarch=$2
|
|
shift ;;
|
|
-c|--cachedir)
|
|
cachedir=$2
|
|
shift ;;
|
|
-d|--dryrun)
|
|
dryrun=1 ;;
|
|
-f|--force)
|
|
cmdopts=(-f) ;;
|
|
-h|--help)
|
|
usage
|
|
exit 0 ;;
|
|
-i|--ignore)
|
|
if [[ $2 = '-' ]]; then
|
|
[[ ! -t 0 ]] && IFS=$'\n' read -r -d '' -a ign
|
|
else
|
|
IFS=',' read -r -a ign <<< "$2"
|
|
fi
|
|
blacklist+=("${ign[@]}")
|
|
unset i ign
|
|
shift ;;
|
|
-k|--keep)
|
|
keep=$2
|
|
if [[ -z $keep || -n ${keep//[0-9]/} ]]; then
|
|
die 'argument to option -k must be a non-negative integer'
|
|
else
|
|
keep=$(( 10#$keep ))
|
|
fi
|
|
shift ;;
|
|
--nocolor)
|
|
USE_COLOR='n' ;;
|
|
-m|--move)
|
|
move=1 movedir=$2
|
|
shift ;;
|
|
-r|--remove)
|
|
delete=1 ;;
|
|
-u|--uninstalled)
|
|
IFS=$'\n' read -r -d '' -a ign < <(pacman -Qq)
|
|
blacklist+=("${ign[@]}")
|
|
unset ign ;;
|
|
-V|--version)
|
|
version
|
|
exit 0 ;;
|
|
-v|--verbose)
|
|
(( ++verbose )) ;;
|
|
-z|--null)
|
|
delim='\0' ;;
|
|
--)
|
|
shift
|
|
break 2 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
m4_include(../scripts/library/term_colors.sh)
|
|
|
|
# remaining args are a whitelist
|
|
whitelist=("$@")
|
|
|
|
# sanity checks
|
|
case $(( dryrun+delete+move )) in
|
|
0) die "no operation specified (use -h for help)" ;;
|
|
[^1]) die "only one operation may be used at a time" ;;
|
|
esac
|
|
|
|
[[ -d $cachedir ]] ||
|
|
die "cachedir '%s' does not exist or is not a directory" "$cachedir"
|
|
|
|
[[ $movedir && ! -d $movedir ]] &&
|
|
die "move-to directory '%s' does not exist or is not a directory" "$movedir"
|
|
|
|
if (( move || delete )); then
|
|
# make it an absolute path since we're about to chdir
|
|
[[ ${movedir:0:1} != '/' ]] && movedir=$PWD/$movedir
|
|
[[ ! -w $cachedir || ( $movedir && ! -w $movedir ) ]] && needsroot=1
|
|
fi
|
|
|
|
# unlikely that this will fail, but better make sure
|
|
cd "$cachedir" >/dev/null || die "failed to chdir to '%s'" "$cachedir"
|
|
|
|
# note that these results are returned in an arbitrary order from awk, but
|
|
# they'll be resorted (in summarize) iff we have a verbosity level set.
|
|
IFS=$'\n' read -r -d '' -a candidates < \
|
|
<(printf '%s\n' *.pkg.tar?(.+([^.])) | pacsort --files |
|
|
pkgfilter "$keep" "$scanarch" \
|
|
"${#whitelist[*]}" "${whitelist[@]}" \
|
|
"${#blacklist[*]}" "${blacklist[@]}")
|
|
|
|
if (( ! ${#candidates[*]} )); then
|
|
msg 'no candidate packages found for pruning'
|
|
exit 1
|
|
fi
|
|
|
|
# grab this prior to signature scavenging
|
|
pkgcount=${#candidates[*]}
|
|
|
|
# copy the list, merging in any found sigs
|
|
for cand in "${candidates[@]}"; do
|
|
candtemp+=("$cand")
|
|
[[ -f $cand.sig ]] && candtemp+=("$cand.sig")
|
|
done
|
|
candidates=("${candtemp[@]}")
|
|
unset candtemp
|
|
|
|
# do this before we destroy anything
|
|
totalsaved=$(@SIZECMD@ "${candidates[@]}" | awk '{ sum += $1 } END { print sum }')
|
|
|
|
# crush. kill. destroy.
|
|
(( verbose )) && cmdopts+=(-v)
|
|
if (( delete )); then
|
|
printf '%s\0' "${candidates[@]}" | runcmd xargs -0 rm "${cmdopts[@]}"
|
|
elif (( move )); then
|
|
printf '%s\0' "${candidates[@]}" | runcmd xargs -0 mv "${cmdopts[@]}" -t "$movedir"
|
|
fi
|
|
|
|
summarize "$pkgcount" "${candidates[@]}"
|
|
|
|
# vim: set ts=2 sw=2 noet:
|