3 # pacache - flexible pacman cache cleaning
5 # Copyright (C) 2011 Dave Reisner <dreisner@archlinux.org>
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; either version 2
10 # of the License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 declare -r myname
='paccache'
25 declare -a candidates
=() cmdopts
=() whitelist
=() blacklist
=()
26 declare -i delete
=0 dryrun
=0 filecount
=0 move
=0 needsroot
=0 totalsaved
=0 verbose
=0
27 declare cachedir
=@localstatedir@
/cache
/pacman
/pkg delim
=$
'\n' keep
=3 movedir
= scanarch
=
31 printf "==> $mesg\n" "$@"
36 printf "==> ERROR: $mesg\n" "$@"
44 # reads a list of files on stdin and prints out deletion candidates
46 # there's whitelist and blacklist parameters passed to this
47 # script after the block of awk.
49 awk -v keep
="$1" -v scanarch
="$2" '
50 function parse_filename(filename, parts, count, i, pkgname, arch) {
52 count = split(filename, parts, "-")
56 while (i <= count - 3) {
57 pkgname = pkgname "-" parts[i++]
60 arch = substr(parts[count], 1, index(parts[count], ".") - 1)
62 # filter on whitelist or blacklist
63 if (wlen && !whitelist[pkgname]) return
64 if (blen && blacklist[pkgname]) return
66 if ("" == packages[pkgname,arch]) {
67 packages[pkgname,arch] = filename
69 packages[pkgname,arch] = packages[pkgname,arch] SUBSEP filename
75 wlen = ARGV[1]; delete ARGV[1]
76 for (i = 2; i < 2 + wlen; i++) {
77 whitelist[ARGV[i]] = 1
82 blen = ARGV[i]; delete ARGV[i]
84 blacklist[ARGV[i]] = 1
88 # read package filenames
89 while (getline < "/dev/stdin") {
93 for (pkglist in packages) {
94 # idx[1,2] = idx[pkgname,arch]
95 split(pkglist, idx, SUBSEP)
97 # enforce architecture match if specified
98 if (!scanarch || scanarch == idx[2]) {
99 count = split(packages[idx[1], idx[2]], pkgs, SUBSEP)
100 for(i = 1; i <= count - keep; i++) {
118 while (size > 1024) {
123 sizestr = sprintf("%.2f", size)
124 sub(/\.?0+$/, "", sizestr)
125 printf("%s %s", sizestr, suffix[count])
130 if (( needsroot
)); then
131 msg
"Privilege escalation required"
132 if sudo
-v &>/dev
/null
&& sudo
-l &>/dev
/null
; then
136 su
-c "$(printf '%q ' "$@
")"
144 local -i filecount
=$1; shift
145 local seenarch
= seen
= arch
= name
=
146 local -r pkg_re
='(.+)-[^-]+-[0-9]+-([^.]+)\.pkg.*'
148 if (( delete
)); then
149 printf -v output
'finished: %d packages removed' "$filecount"
150 elif (( move
)); then
151 printf -v output
"finished: %d packages moved to \`%s'" "$filecount" "$movedir"
152 elif (( dryrun
)); then
153 if (( verbose
)); then
154 msg
"Candidate packages:"
155 while read -r pkg
; do
156 if (( verbose
>= 3 )); then
157 [[ $pkg =~
$pkg_re ]] && name
=${BASH_REMATCH[1]} arch
=${BASH_REMATCH[2]}
158 if [[ -z $seen ||
$seenarch != "$arch" ||
$seen != "$name" ]]; then
159 seen
=$name seenarch
=$arch
160 printf '%s (%s):\n' "$name" "$arch"
162 printf ' %s\n' "$pkg"
163 elif (( verbose
>= 2 )); then
164 printf "$PWD/%s$delim" "$pkg"
166 printf "%s$delim" "$pkg"
168 done < <(printf '%s\n' "$@" | pacsort
)
170 printf -v output
'finished dry run: %d candidates' "$filecount"
174 msg
"$output (diskspace saved: %s)" "$(size_to_human "$totalsaved")"
179 usage: $myname <operation> [options] [targets...]
181 $myname is a flexible pacman cache cleaning utility, which has numerous
182 options to help control how much, and what, is deleted from any directory
183 containing pacman package tarballs.
186 -d perform a dry run, only finding candidate packages.
187 -m <movedir> move candidate packages to 'movedir'.
188 -r remove candidate packages.
191 -a <arch> scan for 'arch' (default: all architectures).
192 -c <cachedir> scan 'cachedir' for packages (default: @localstatedir@/cache/pacman/pkg).
193 -f apply force to mv(1) and rm(1) operations.
194 -h display this help message.
195 -i <pkgs> ignore 'pkgs', which is a comma separated. Alternatively,
196 specify '-' to read package names from stdin, newline delimited.
197 -k <num> keep 'num' of each package in 'cachedir' (default: 3).
198 -u target uninstalled packages.
199 -v increase verbosity. specify up to 3 times.
200 -z use null delimiters for candidate names (only with -v and -vv)
206 error
"Do not run this script as root. You will be prompted for privilege escalation."
210 while getopts ':a:c:dfhi:k:m:rsuvz' opt
; do
212 a
) scanarch
=$OPTARG ;;
213 c
) cachedir
=$OPTARG ;;
218 i
) if [[ $OPTARG = '-' ]]; then
219 [[ ! -t 0 ]] && IFS
=$
'\n' read -r -d '' -a ign
221 IFS
=',' read -r -a ign
<<< "$OPTARG"
223 blacklist
+=("${ign[@]}")
226 if [[ -z $keep ||
-n ${keep//[0-9]/} ]]; then
227 die
'argument to option -k must be a non-negative integer'
231 m
) move
=1 movedir
=$OPTARG ;;
233 u
) IFS
=$
'\n' read -r -d '' -a ign
< <(pacman
-Qq)
234 blacklist
+=("${ign[@]}")
236 v
) (( ++verbose
)) ;;
238 :) die
"option '--%s' requires an argument" "$OPTARG" ;;
239 ?
) die
"invalid option -- '%s'" "$OPTARG" ;;
242 shift $
(( OPTIND
- 1 ))
244 # remaining args are a whitelist
248 case $
(( dryrun
+delete
+move
)) in
249 0) die
"no operation specified (use -h for help)" ;;
250 [^
1]) die
"only one operation may be used at a time" ;;
253 [[ -d $cachedir ]] ||
254 die
"cachedir \`%s' does not exist or is not a directory" "$cachedir"
256 [[ $movedir && ! -d $movedir ]] &&
257 die
"move-to directory \`%s' does not exist or is not a directory" "$movedir"
259 if (( move || delete
)); then
260 # make it an absolute path since we're about to chdir
261 [[ ${movedir:0:1} != '/' ]] && movedir
=$PWD/$movedir
262 [[ ! -w $cachedir ||
( $movedir && ! -w $movedir ) ]] && needsroot
=1
265 # unlikely that this will fail, but better make sure
266 cd "$cachedir" || die
"failed to chdir to \`%s'" "$cachedir"
268 # note that these results are returned in an arbitrary order from awk, but
269 # they'll be resorted (in summarize) iff we have a verbosity level set.
270 IFS
=$
'\n' read -r -d '' -a candidates
< \
271 <(printf '%s\n' *.pkg.
tar?
(.
+([^.
])) | pacsort |
272 pkgfilter
"$keep" "$scanarch" \
273 "${#whitelist[*]}" "${whitelist[@]}" \
274 "${#blacklist[*]}" "${blacklist[@]}")
276 if (( ! ${#candidates[*]} )); then
277 msg
'no candidate packages found for pruning'
281 # grab this prior to signature scavenging
282 pkgcount
=${#candidates[*]}
284 # copy the list, merging in any found sigs
285 for cand
in "${candidates[@]}"; do
287 [[ -f $cand.sig
]] && candtemp
+=("$cand.sig")
289 candidates
=("${candtemp[@]}")
292 # do this before we destroy anything
293 totalsaved
=$
(@SIZECMD@
"${candidates[@]}" |
awk '{ sum += $1 } END { print sum }')
295 # crush. kill. destroy.
296 (( verbose
)) && cmdopts
+=(-v)
297 if (( delete
)); then
298 runcmd
rm "${cmdopts[@]}" "${candidates[@]}"
299 elif (( move
)); then
300 runcmd
mv "${cmdopts[@]}" "${candidates[@]}" "$movedir"
303 summarize
"$pkgcount" "${candidates[@]}"
305 # vim: set ts=2 sw=2 noet: