Move important information up in -Si output
[pacman-ng.git] / contrib / paccache.sh.in
blobe813655966ddd3d155a60105047d526eb7610808
1 #!/bin/bash
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/>.
21 shopt -s extglob
23 declare -r myname='paccache'
24 declare -r myver='@PACKAGE_VERSION@'
26 declare -a candidates=() cmdopts=() whitelist=() blacklist=()
27 declare -i delete=0 dryrun=0 filecount=0 move=0 needsroot=0 totalsaved=0 verbose=0
28 declare cachedir=@localstatedir@/cache/pacman/pkg delim=$'\n' keep=3 movedir= scanarch=
30 msg() {
31 local mesg=$1; shift
32 printf "==> $mesg\n" "$@"
33 } >&2
35 error() {
36 local mesg=$1; shift
37 printf "==> ERROR: $mesg\n" "$@"
38 } >&2
40 die() {
41 error "$@"
42 exit 1
45 m4_include(../scripts/library/parseopts.sh)
47 # reads a list of files on stdin and prints out deletion candidates
48 pkgfilter() {
49 # there's whitelist and blacklist parameters passed to this
50 # script after the block of awk.
52 awk -v keep="$1" -v scanarch="$2" '
53 function parse_filename(filename, parts, count, i, pkgname, arch) {
55 count = split(filename, parts, "-")
57 i = 1
58 pkgname = parts[i++]
59 while (i <= count - 3) {
60 pkgname = pkgname "-" parts[i++]
63 arch = substr(parts[count], 1, index(parts[count], ".") - 1)
65 # filter on whitelist or blacklist
66 if (wlen && !whitelist[pkgname]) return
67 if (blen && blacklist[pkgname]) return
69 if ("" == packages[pkgname,arch]) {
70 packages[pkgname,arch] = filename
71 } else {
72 packages[pkgname,arch] = packages[pkgname,arch] SUBSEP filename
76 BEGIN {
77 # create whitelist
78 wlen = ARGV[1]; delete ARGV[1]
79 for (i = 2; i < 2 + wlen; i++) {
80 whitelist[ARGV[i]] = 1
81 delete ARGV[i]
84 # create blacklist
85 blen = ARGV[i]; delete ARGV[i]
86 while (i++ < ARGC) {
87 blacklist[ARGV[i]] = 1
88 delete ARGV[i]
91 # read package filenames
92 while (getline < "/dev/stdin") {
93 parse_filename($0)
96 for (pkglist in packages) {
97 # idx[1,2] = idx[pkgname,arch]
98 split(pkglist, idx, SUBSEP)
100 # enforce architecture match if specified
101 if (!scanarch || scanarch == idx[2]) {
102 count = split(packages[idx[1], idx[2]], pkgs, SUBSEP)
103 for(i = 1; i <= count - keep; i++) {
104 print pkgs[i]
108 }' "${@:3}"
111 m4_include(../scripts/library/size_to_human.sh)
113 runcmd() {
114 if (( needsroot && EUID != 0 )); then
115 msg "Privilege escalation required"
116 if sudo -v &>/dev/null && sudo -l &>/dev/null; then
117 sudo "$@"
118 else
119 printf '%s ' 'root'
120 su -c "$(printf '%q ' "$@")"
122 else
123 "$@"
127 summarize() {
128 local -i filecount=$1; shift
129 local seenarch= seen= arch= name=
130 local -r pkg_re='(.+)-[^-]+-[0-9]+-([^.]+)\.pkg.*'
132 if (( delete )); then
133 printf -v output 'finished: %d packages removed' "$filecount"
134 elif (( move )); then
135 printf -v output "finished: %d packages moved to \`%s'" "$filecount" "$movedir"
136 elif (( dryrun )); then
137 if (( verbose )); then
138 msg "Candidate packages:"
139 while read -r pkg; do
140 if (( verbose >= 3 )); then
141 [[ $pkg =~ $pkg_re ]] && name=${BASH_REMATCH[1]} arch=${BASH_REMATCH[2]}
142 if [[ -z $seen || $seenarch != "$arch" || $seen != "$name" ]]; then
143 seen=$name seenarch=$arch
144 printf '%s (%s):\n' "$name" "$arch"
146 printf ' %s\n' "$pkg"
147 elif (( verbose >= 2 )); then
148 printf "$PWD/%s$delim" "$pkg"
149 else
150 printf "%s$delim" "$pkg"
152 done < <(printf '%s\n' "$@" | pacsort)
154 printf -v output 'finished dry run: %d candidates' "$filecount"
157 printf '\n' >&2
158 msg "$output (diskspace saved: %s)" "$(size_to_human "$totalsaved")"
161 usage() {
162 cat <<EOF
163 usage: $myname <operation> [options] [targets...]
165 $myname is a flexible pacman cache cleaning utility, which has numerous
166 options to help control how much, and what, is deleted from any directory
167 containing pacman package tarballs.
169 Operations:
170 -d, --dryrun perform a dry run, only finding candidate packages.
171 -m, --move <dir> move candidate packages to 'movedir'.
172 -r, --remove remove candidate packages.
174 Options:
175 -a, --arch <arch> scan for 'arch' (default: all architectures).
176 -c, --cachedir <dir> scan 'cachedir' for packages (default: @localstatedir@/cache/pacman/pkg).
177 -f, --force apply force to mv(1) and rm(1) operations.
178 -h, --help display this help message and exit.
179 -i, --ignore <pkgs> ignore 'pkgs', comma separated. Alternatively, specify '-' to
180 read package names from stdin, newline delimited.
181 -k, --keep <num> keep 'num' of each package in 'cachedir' (default: 3).
182 -u, --uninstalled target uninstalled packages.
183 -v, --verbose increase verbosity. specify up to 3 times.
184 -z, --null use null delimiters for candidate names (only with -v and -vv)
189 version() {
190 printf "%s %s\n" "$myname" "$myver"
191 echo 'Copyright (C) 2011 Dave Reisner <dreisner@archlinux.org>'
194 OPT_SHORT=':a:c:dfhi:k:m:rsuVvz'
195 OPT_LONG=('arch:' 'cachedir:' 'dryrun' 'force' 'help' 'ignore:' 'keep:' 'move'
196 'remove' 'uninstalled' 'version' 'verbose' 'null')
198 if ! parseopts "$OPT_SHORT" "${OPT_LONG[@]}" -- "$@"; then
199 exit 1
201 set -- "${OPTRET[@]}"
202 unset OPT_SHORT OPT_LONG OPTRET
204 while :; do
205 case $1 in
206 -a|--arch)
207 scanarch=$2
208 shift ;;
209 -c|--cachedir)
210 cachedir=$2
211 shift ;;
212 -d|--dryrun)
213 dryrun=1 ;;
214 -f|--force)
215 cmdopts=(-f) ;;
216 -h|--help)
217 usage
218 exit 0 ;;
219 -i|--ignore)
220 if [[ $2 = '-' ]]; then
221 [[ ! -t 0 ]] && IFS=$'\n' read -r -d '' -a ign
222 else
223 IFS=',' read -r -a ign <<< "$2"
225 blacklist+=("${ign[@]}")
226 unset i ign
227 shift ;;
228 -k|--keep)
229 keep=$2
230 if [[ -z $keep || -n ${keep//[0-9]/} ]]; then
231 die 'argument to option -k must be a non-negative integer'
232 else
233 keep=$(( 10#$keep ))
235 shift ;;
236 -m|--move)
237 move=1 movedir=$2
238 shift ;;
239 -r|--remove)
240 delete=1 ;;
241 -u|--uninstalled)
242 IFS=$'\n' read -r -d '' -a ign < <(pacman -Qq)
243 blacklist+=("${ign[@]}")
244 unset ign ;;
245 -V|--version)
246 version
247 exit 0 ;;
248 -v|--verbose)
249 (( ++verbose )) ;;
250 -z|--null)
251 delim='\0' ;;
253 shift
254 break 2 ;;
255 esac
256 shift
257 done
259 # remaining args are a whitelist
260 whitelist=("$@")
262 # sanity checks
263 case $(( dryrun+delete+move )) in
264 0) die "no operation specified (use -h for help)" ;;
265 [^1]) die "only one operation may be used at a time" ;;
266 esac
268 [[ -d $cachedir ]] ||
269 die "cachedir \`%s' does not exist or is not a directory" "$cachedir"
271 [[ $movedir && ! -d $movedir ]] &&
272 die "move-to directory \`%s' does not exist or is not a directory" "$movedir"
274 if (( move || delete )); then
275 # make it an absolute path since we're about to chdir
276 [[ ${movedir:0:1} != '/' ]] && movedir=$PWD/$movedir
277 [[ ! -w $cachedir || ( $movedir && ! -w $movedir ) ]] && needsroot=1
280 # unlikely that this will fail, but better make sure
281 cd "$cachedir" >/dev/null || die "failed to chdir to \`%s'" "$cachedir"
283 # note that these results are returned in an arbitrary order from awk, but
284 # they'll be resorted (in summarize) iff we have a verbosity level set.
285 IFS=$'\n' read -r -d '' -a candidates < \
286 <(printf '%s\n' *.pkg.tar?(.+([^.])) | pacsort |
287 pkgfilter "$keep" "$scanarch" \
288 "${#whitelist[*]}" "${whitelist[@]}" \
289 "${#blacklist[*]}" "${blacklist[@]}")
291 if (( ! ${#candidates[*]} )); then
292 msg 'no candidate packages found for pruning'
293 exit 1
296 # grab this prior to signature scavenging
297 pkgcount=${#candidates[*]}
299 # copy the list, merging in any found sigs
300 for cand in "${candidates[@]}"; do
301 candtemp+=("$cand")
302 [[ -f $cand.sig ]] && candtemp+=("$cand.sig")
303 done
304 candidates=("${candtemp[@]}")
305 unset candtemp
307 # do this before we destroy anything
308 totalsaved=$(@SIZECMD@ "${candidates[@]}" | awk '{ sum += $1 } END { print sum }')
310 # crush. kill. destroy.
311 (( verbose )) && cmdopts+=(-v)
312 if (( delete )); then
313 runcmd rm "${cmdopts[@]}" "${candidates[@]}"
314 elif (( move )); then
315 runcmd mv "${cmdopts[@]}" "${candidates[@]}" "$movedir"
318 summarize "$pkgcount" "${candidates[@]}"
320 # vim: set ts=2 sw=2 noet: