download callback: show decimal places in rate if we have room
[pacman-ng.git] / scripts / repo-add.sh.in
blob29b150c89e6aba302ab1d3086092e79a26db95e2
1 #!/bin/bash
3 # repo-add - add a package to a given repo database file
4 # repo-remove - remove a package entry from a given repo database file
5 # @configure_input@
7 # Copyright (c) 2006-2011 Pacman Development Team <pacman-dev@archlinux.org>
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 shopt -s extglob
24 # gettext initialization
25 export TEXTDOMAIN='pacman-scripts'
26 export TEXTDOMAINDIR='@localedir@'
28 myver='@PACKAGE_VERSION@'
29 confdir='@sysconfdir@'
31 QUIET=0
32 DELTA=0
33 WITHFILES=0
34 SIGN=0
35 VERIFY=0
36 REPO_DB_FILE=
37 LOCKFILE=
38 CLEAN_LOCK=0
40 # ensure we have a sane umask set
41 umask 0022
43 m4_include(library/output_format.sh)
45 # print usage instructions
46 usage() {
47 cmd=${0##*/}
48 printf "%s (pacman) %s\n\n" "$cmd" "$myver"
49 if [[ $cmd == "repo-add" ]] ; then
50 printf "$(gettext "Usage: repo-add [options] <path-to-db> <package|delta> ...\n")"
51 printf "$(gettext "\
52 repo-add will update a package database by reading a package file.\n\
53 Multiple packages to add can be specified on the command line.\n\n")"
54 printf "$(gettext "Options:\n")"
55 printf "$(gettext " -d, --delta generate and add delta for package update\n")"
56 printf "$(gettext " -f, --files update database's file list\n")"
57 elif [[ $cmd == "repo-remove" ]] ; then
58 printf "$(gettext "Usage: repo-remove [options] <path-to-db> <packagename|delta> ...\n\n")"
59 printf "$(gettext "\
60 repo-remove will update a package database by removing the package name\n\
61 specified on the command line from the given repo database. Multiple\n\
62 packages to remove can be specified on the command line.\n\n")"
63 printf "$(gettext "Options:\n")"
65 printf "$(gettext " -q, --quiet minimize output\n")"
66 printf "$(gettext " -s, --sign sign database with GnuPG after update\n")"
67 printf "$(gettext " -k, --key <key> use the specified key to sign the database\n")"
68 printf "$(gettext " -v, --verify verify database's signature before update\n")"
69 printf "$(gettext "\n\
70 See %s(8) for more details and descriptions of the available options.\n\n")" $cmd
71 if [[ $cmd == "repo-add" ]] ; then
72 echo "$(gettext "Example: repo-add /path/to/repo.db.tar.gz pacman-3.0.0-1-i686.pkg.tar.gz")"
73 elif [[ $cmd == "repo-remove" ]] ; then
74 echo "$(gettext "Example: repo-remove /path/to/repo.db.tar.gz kernel26")"
78 version() {
79 cmd=${0##*/}
80 printf "%s (pacman) %s\n\n" "$cmd" "$myver"
81 printf "$(gettext "\
82 Copyright (c) 2006-2011 Pacman Development Team <pacman-dev@archlinux.org>\n\n\
83 This is free software; see the source for copying conditions.\n\
84 There is NO WARRANTY, to the extent permitted by law.\n")"
87 # format a metadata entry
88 # arg1 - Entry name
89 # ... - value(s)
90 format_entry() {
91 local field=$1; shift
93 if [[ $1 ]]; then
94 printf '%%%s%%\n' "$field"
95 printf '%s\n' "$@"
96 printf '\n'
100 find_pkgentry() {
101 local pkgname=$1
102 local pkgentry
103 for pkgentry in $tmpdir/tree/$pkgname*; do
104 name=${pkgentry##*/}
105 if [[ ${name%-*-*} = $pkgname ]]; then
106 echo $pkgentry
107 return 0
109 done
110 return 1
113 # Get the package name from the delta filename
114 get_delta_pkgname() {
115 local tmp
117 tmp=${1##*/}
118 echo ${tmp%-*-*_to*}
121 # write a delta entry
122 # arg1 - path to delta file
123 db_write_delta() {
124 deltafile="$1"
125 pkgname="$(get_delta_pkgname $deltafile)"
127 pkgentry=$(find_pkgentry $pkgname)
128 if [[ -z $pkgentry ]]; then
129 error "$(gettext "No database entry for package '%s'.")" "$pkgname"
130 return 1
132 deltas="$pkgentry/deltas"
133 if [[ ! -f $deltas ]]; then
134 echo -e "%DELTAS%" >$deltas
136 # get md5sum and compressed size of package
137 md5sum="$(openssl dgst -md5 "$deltafile")"
138 md5sum="${md5sum##* }"
139 csize=$(@SIZECMD@ "$deltafile")
141 oldfile=$(xdelta3 printhdr $deltafile | grep "XDELTA filename (source)" | sed 's/.*: *//')
142 newfile=$(xdelta3 printhdr $deltafile | grep "XDELTA filename (output)" | sed 's/.*: *//')
144 if grep -q "$oldfile.*$newfile" $deltas; then
145 sed -i.backup "/$oldfile.*$newfile/d" $deltas && rm -f $deltas.backup
147 msg2 "$(gettext "Adding 'deltas' entry : %s -> %s")" "$oldfile" "$newfile"
148 echo ${deltafile##*/} $md5sum $csize $oldfile $newfile >> $deltas
150 return 0
151 } # end db_write_delta
153 # remove a delta entry
154 # arg1 - path to delta file
155 db_remove_delta() {
156 deltafile="$1"
157 filename=${deltafile##*/}
158 pkgname="$(get_delta_pkgname $deltafile)"
160 pkgentry=$(find_pkgentry $pkgname)
161 if [[ -z $pkgentry ]]; then
162 return 1
164 deltas="$pkgentry/deltas"
165 if [[ ! -f $deltas ]]; then
166 return 1
168 if grep -q "$filename" $deltas; then
169 sed -i.backup "/$filename/d" $deltas && rm -f $deltas.backup
170 msg2 "$(gettext "Removing existing entry '%s'...")" "$filename"
171 return 0
174 return 1
175 } # end db_remove_delta
177 check_gpg() {
178 if ! type -p gpg >/dev/null; then
179 error "$(gettext "Cannot find the gpg binary! Is GnuPG installed?")"
180 exit 1 # $E_MISSING_PROGRAM
184 # sign the package database once repackaged
185 create_signature() {
186 (( ! SIGN )) && return
187 local dbfile="$1"
188 local ret=0
189 msg "$(gettext "Signing database...")"
191 local SIGNWITHKEY=""
192 if [[ -n $GPGKEY ]]; then
193 SIGNWITHKEY="-u ${GPGKEY}"
195 gpg --detach-sign --use-agent ${SIGNWITHKEY} "$dbfile" &>/dev/null || ret=$?
197 if (( ! ret )); then
198 msg2 "$(gettext "Created signature file %s.")" "${dbfile##*/}.sig"
199 else
200 warning "$(gettext "Failed to sign package database.")"
204 # verify the existing package database signature
205 verify_signature() {
206 (( ! VERIFY )) && return
207 local dbfile="$1"
208 local ret=0
209 msg "$(gettext "Verifying database signature...")"
211 if [[ ! -f $dbfile.sig ]]; then
212 warning "$(gettext "No existing signature found, skipping verification.")"
213 return
215 gpg --verify "$dbfile.sig" || ret=$?
216 if (( ! ret )); then
217 msg2 "$(gettext "Database signature file verified.")"
218 else
219 error "$(gettext "Database signature was NOT valid!")"
220 exit 1
224 verify_repo_extension() {
225 local repofile=$1
227 case "$repofile" in
228 *.@(db|files).tar.gz) TAR_OPT="z" ;;
229 *.@(db|files).tar.bz2) TAR_OPT="j" ;;
230 *.@(db|files).tar.xz) TAR_OPT="J" ;;
231 *.@(db|files).tar.Z) TAR_OPT="Z" ;;
232 *.@(db|files).tar) TAR_OPT="" ;;
233 *) error "$(gettext "'%s' does not have a valid archive extension.")" \
234 "$repofile"
235 exit 1 ;;
236 esac
238 printf '%s' "$TAR_OPT"
241 # write an entry to the pacman database
242 # arg1 - path to package
243 db_write_entry() {
244 # blank out all variables
245 local pkgfile="$1"
246 local -a _groups _licenses _replaces _depends _conflicts _provides _optdepends
247 local pkgname pkgver pkgdesc csize size url arch builddate packager \
248 md5sum sha256sum pgpsig
250 # read info from the zipped package
251 local line var val
252 while read -r line; do
253 [[ ${line:0:1} = '#' ]] && continue
254 IFS=' =' read -r var val < <(printf '%s\n' "$line")
256 # normalize whitespace with an extglob
257 declare "$var=${val//+([[:space:]])/ }"
258 case "$var" in
259 group) _groups+=("$group") ;;
260 license) _licenses+=("$license") ;;
261 replaces) _replaces+=("$replaces") ;;
262 depend) _depends+=("$depend") ;;
263 conflict) _conflicts+=("$conflict") ;;
264 provides) _provides+=("$provides") ;;
265 optdepend) _optdepends+=("$optdepend") ;;
266 esac
267 done< <(bsdtar -xOqf "$pkgfile" .PKGINFO)
269 # ensure $pkgname and $pkgver variables were found
270 if [[ -z $pkgname || -z $pkgver ]]; then
271 error "$(gettext "Invalid package file '%s'.")" "$pkgfile"
272 return 1
275 if [[ -d $tmpdir/tree/$pkgname-$pkgver ]]; then
276 warning "$(gettext "An entry for '%s' already existed")" "$pkgname-$pkgver"
277 else
278 if (( DELTA )); then
279 pkgentry=$(find_pkgentry $pkgname)
280 if [[ -n $pkgentry ]]; then
281 local oldfilename=$(grep -A1 FILENAME $pkgentry/desc | tail -n1)
282 local oldfile="$(dirname $1)/$oldfilename"
287 csize=$(@SIZECMD@ "$pkgfile")
289 # compute checksums
290 msg2 "$(gettext "Computing checksums...")"
291 md5sum="$(openssl dgst -md5 "$pkgfile")"
292 md5sum="${md5sum##* }"
293 sha256sum="$(openssl dgst -sha256 "$pkgfile")"
294 sha256sum="${sha256sum##* }"
296 # compute base64'd PGP signature
297 if [[ -f "$pkgfile.sig" ]]; then
298 msg2 "$(gettext "Adding package signature...")"
299 pgpsig=$(openssl base64 -in "$pkgfile.sig" | tr -d '\n')
302 # remove an existing entry if it exists, ignore failures
303 db_remove_entry "$pkgname"
305 # create package directory
306 pushd "$tmpdir/tree" >/dev/null
307 mkdir "$pkgname-$pkgver"
308 pushd "$pkgname-$pkgver" >/dev/null
310 # restore an eventual deltas file
311 [[ -f ../$pkgname.deltas ]] && mv "../$pkgname.deltas" deltas
313 # create desc entry
314 msg2 "$(gettext "Creating '%s' db entry...")" 'desc'
316 format_entry "FILENAME" "${1##*/}"
317 format_entry "NAME" "$pkgname"
318 format_entry "BASE" "$pkgbase"
319 format_entry "VERSION" "$pkgver"
320 format_entry "DESC" "$pkgdesc"
321 format_entry "GROUPS" "${_groups[@]}"
322 format_entry "CSIZE" "$csize"
323 format_entry "ISIZE" "$size"
325 # add checksums
326 format_entry "MD5SUM" "$md5sum"
327 format_entry "SHA256SUM" "$sha256sum"
329 # add PGP sig
330 format_entry "PGPSIG" "$pgpsig"
332 format_entry "URL" "$url"
333 format_entry "LICENSE" "${_licenses[@]}"
334 format_entry "ARCH" "$arch"
335 format_entry "BUILDDATE" "$builddate"
336 format_entry "PACKAGER" "$packager"
337 format_entry "REPLACES" "${_replaces[@]}"
338 } >'desc'
340 # create depends entry
341 msg2 "$(gettext "Creating '%s' db entry...")" 'depends'
343 format_entry "DEPENDS" "${_depends[@]}"
344 format_entry "CONFLICTS" "${_conflicts[@]}"
345 format_entry "PROVIDES" "${_provides[@]}"
346 format_entry "OPTDEPENDS" "${_optdepends[@]}"
347 } >'depends'
349 popd >/dev/null
350 popd >/dev/null
352 # create files file if wanted
353 if (( WITHFILES )); then
354 msg2 "$(gettext "Creating '%s' db entry...")" 'files'
355 local files_path="$tmpdir/tree/$pkgname-$pkgver/files"
356 echo "%FILES%" >$files_path
357 bsdtar --exclude='^.*' -tf "$pkgfile" >>$files_path
360 # create a delta file
361 if (( DELTA )); then
362 if [[ -n $oldfilename ]]; then
363 if [[ -f $oldfile ]]; then
364 delta=$(pkgdelta -q $oldfile $1)
365 if [[ -f $delta ]]; then
366 db_write_delta $delta
368 else
369 warning "$(gettext "Old package file not found: %s")" "$oldfilename"
374 return 0
375 } # end db_write_entry
377 # remove existing entries from the DB
378 # arg1 - package name
379 db_remove_entry() {
380 local pkgname=$1
381 local notfound=1
382 local pkgentry=$(find_pkgentry $pkgname)
383 while [[ -n $pkgentry ]]; do
384 notfound=0
385 if [[ -f $pkgentry/deltas ]]; then
386 mv "$pkgentry/deltas" "$tmpdir/tree/$pkgname.deltas"
388 msg2 "$(gettext "Removing existing entry '%s'...")" \
389 "${pkgentry##*/}"
390 rm -rf $pkgentry
391 pkgentry=$(find_pkgentry $pkgname)
392 done
393 return $notfound
394 } # end db_remove_entry
396 elephant() {
397 case $(( RANDOM % 2 )) in
398 0) printf '%s\n' "H4sIAL3qBE4CAyWLwQ3AMAgD/0xh5UPzYiFUMgjq7LUJsk7yIQNAQTAikFUDnqkr" \
399 "OQFOUm0Wd9pHCi13ONjBpVdqcWx+EdXVX4vXvGv5cgztB9+fJxZ7AAAA"
402 1) printf '%s\n' "H4sIAJVWBU4CA21RMQ7DIBDbeYWrDgQJ7rZ+IA/IB05l69alcx5fc0ASVXUk4jOO" \
403 "7yAAUWtorygwJ4hlMii0YkJKKRKGvsMsiykl1SalvrMD1gUXyXRkGZPx5OPft81K" \
404 "tNAiAjyGjYO47h1JjizPkJrCWbK/4C+uLkT7bzpGc7CT9bmOzNSW5WLSO5vexjmH" \
405 "ZL9JFFZeAa0a2+lKjL2anpYfV+0Zx9LJ+/MC8nRayuDlSNy2rfAPibOzsiWHL0jL" \
406 "SsjFAQAA"
408 esac | openssl base64 -d | gzip -d
411 check_repo_db() {
412 local repodir
414 # ensure the path to the DB exists
415 if [[ "$LOCKFILE" == /* ]]; then
416 repodir=${LOCKFILE%/*}/
417 else
418 repodir=$PWD/$LOCKFILE
419 repodir=${repodir%/*}/
422 if [[ ! -d "$repodir" ]]; then
423 error "$(gettext "%s does not exist or is not a directory.")" "$repodir"
424 exit 1
427 # check lock file
428 if ( set -o noclobber; echo "$$" > "$LOCKFILE") 2> /dev/null; then
429 CLEAN_LOCK=1
430 else
431 error "$(gettext "Failed to acquire lockfile: %s.")" "$LOCKFILE"
432 [[ -f $LOCKFILE ]] && error "$(gettext "Held by process %s")" "$(cat $LOCKFILE)"
433 exit 1
436 if [[ -f $REPO_DB_FILE ]]; then
437 # there are two situations we can have here- a DB with some entries,
438 # or a DB with no contents at all.
439 if ! bsdtar -tqf "$REPO_DB_FILE" '*/desc' >/dev/null 2>&1; then
440 # check empty case
441 if [[ -n $(bsdtar -tqf "$REPO_DB_FILE" '*' 2>/dev/null) ]]; then
442 error "$(gettext "Repository file '%s' is not a proper pacman database.")" "$REPO_DB_FILE"
443 exit 1
446 verify_signature "$REPO_DB_FILE"
447 msg "$(gettext "Extracting database to a temporary location...")"
448 bsdtar -xf "$REPO_DB_FILE" -C "$tmpdir/tree"
449 else
450 case "$cmd" in
451 repo-remove)
452 error "$(gettext "Repository file '%s' was not found.")" "$REPO_DB_FILE"
453 exit 1
455 repo-add)
456 # check if the file can be created (write permission, directory existence, etc)
457 if ! touch "$REPO_DB_FILE"; then
458 error "$(gettext "Repository file '%s' could not be created.")" "$REPO_DB_FILE"
459 exit 1
461 rm -f "$REPO_DB_FILE"
463 esac
467 add() {
468 if [[ ! -f $1 ]]; then
469 error "$(gettext "File '%s' not found.")" "$1"
470 return 1
473 if [[ ${1##*.} == "delta" ]]; then
474 deltafile=$1
475 msg "$(gettext "Adding delta '%s'")" "$deltafile"
476 if ! type xdelta3 &>/dev/null; then
477 error "$(gettext "Cannot find the xdelta3 binary! Is xdelta3 installed?")"
478 exit 1
480 if db_write_delta "$deltafile"; then
481 return 0
482 else
483 return 1
487 pkgfile=$1
488 if ! bsdtar -tqf "$pkgfile" .PKGINFO >/dev/null 2>&1; then
489 error "$(gettext "'%s' is not a package file, skipping")" "$pkgfile"
490 return 1
493 msg "$(gettext "Adding package '%s'")" "$pkgfile"
495 db_write_entry "$pkgfile"
498 remove() {
499 if [[ ${1##*.} == "delta" ]]; then
500 deltafile=$1
501 msg "$(gettext "Searching for delta '%s'...")" "$deltafile"
502 if db_remove_delta "$deltafile"; then
503 return 0
504 else
505 error "$(gettext "Delta matching '%s' not found.")" "$deltafile"
506 return 1
510 pkgname=$1
511 msg "$(gettext "Searching for package '%s'...")" "$pkgname"
513 if db_remove_entry "$pkgname"; then
514 rm -f "$tmpdir/tree/$pkgname.deltas"
515 return 0
516 else
517 error "$(gettext "Package matching '%s' not found.")" "$pkgname"
518 return 1
522 trap_exit() {
523 echo
524 error "$@"
525 exit 1
528 clean_up() {
529 local exit_code=$?
531 [[ -d $tmpdir ]] && rm -rf "$tmpdir"
532 (( CLEAN_LOCK )) && [[ -f $LOCKFILE ]] && rm -f "$LOCKFILE"
534 exit $exit_code
537 # PROGRAM START
539 # determine whether we have gettext; make it a no-op if we do not
540 if ! type gettext &>/dev/null; then
541 gettext() {
542 echo "$@"
546 case "$1" in
547 -h|--help) usage; exit 0;;
548 -V|--version) version; exit 0;;
549 esac
551 # figure out what program we are
552 cmd=${0##*/}
553 if [[ $cmd == "repo-elephant" ]]; then
554 elephant
555 exit 0
558 if [[ $cmd != "repo-add" && $cmd != "repo-remove" ]]; then
559 error "$(gettext "Invalid command name '%s' specified.")" "$cmd"
560 exit 1
563 tmpdir=$(mktemp -d /tmp/repo-tools.XXXXXXXXXX) || (\
564 error "$(gettext "Cannot create temp directory for database building.")"; \
565 exit 1)
566 mkdir $tmpdir/tree
568 trap 'clean_up' EXIT
569 trap 'trap_exit "$(gettext "TERM signal caught. Exiting...")"' TERM HUP QUIT
570 trap 'trap_exit "$(gettext "Aborted by user! Exiting...")"' INT
571 trap 'trap_exit "$(gettext "An unknown error has occured. Exiting...")"' ERR
573 declare -a args
574 success=0
575 # parse arguments
576 while (( $# )); do
577 case "$1" in
578 -q|--quiet) QUIET=1;;
579 -d|--delta) DELTA=1;;
580 -f|--files) WITHFILES=1;;
581 -s|--sign)
582 check_gpg
583 SIGN=1
584 if ! gpg --list-key ${GPGKEY} &>/dev/null; then
585 if [[ ! -z $GPGKEY ]]; then
586 error "$(gettext "The key ${GPGKEY} does not exist in your keyring.")"
587 else
588 error "$(gettext "There is no key in your keyring.")"
590 exit 1
593 -k|--key)
594 check_gpg
595 shift
596 GPGKEY="$1"
597 if ! gpg --list-key ${GPGKEY} &>/dev/null; then
598 error "$(gettext "The key ${GPGKEY} does not exist in your keyring.")"
599 exit 1
602 -v|--verify)
603 check_gpg
604 VERIFY=1
607 args+=("$1")
609 esac
610 shift
611 done
613 REPO_DB_FILE=${args[0]}
614 if [[ -z $REPO_DB_FILE ]]; then
615 usage
616 exit 1
619 LOCKFILE=$REPO_DB_FILE.lck
621 verify_repo_extension "$REPO_DB_FILE" >/dev/null
622 check_repo_db
624 for arg in "${args[@]:1}"; do
625 case "$cmd" in
626 repo-add) add "$arg" ;;
627 repo-remove) remove "$arg" ;;
628 esac && success=1
629 done
631 # if at least one operation was a success, re-zip database
632 if (( success )); then
633 msg "$(gettext "Creating updated database file '%s'")" "$REPO_DB_FILE"
635 TAR_OPT=$(verify_repo_extension "$REPO_DB_FILE")
636 filename=${REPO_DB_FILE##*/}
638 pushd "$tmpdir/tree" >/dev/null
639 if ( shopt -s nullglob; files=(*); (( ${#files[*]} )) ); then
640 bsdtar -c${TAR_OPT}f "$tmpdir/$filename" *
641 else
642 # we have no packages remaining? zip up some emptyness
643 warning "$(gettext "No packages remain, creating empty database.")"
644 bsdtar -c${TAR_OPT}f "$tmpdir/$filename" -T /dev/null
646 popd >/dev/null
648 create_signature "$tmpdir/$filename"
650 [[ -f $REPO_DB_FILE ]] && mv -f "$REPO_DB_FILE" "${REPO_DB_FILE}.old"
651 if [[ -f $REPO_DB_FILE.sig ]]; then
652 mv -f "$REPO_DB_FILE.sig" "$REPO_DB_FILE.old.sig"
653 else
654 rm -f "$REPO_DB_FILE.old.sig"
656 [[ -f $tmpdir/$filename ]] && mv "$tmpdir/$filename" "$REPO_DB_FILE"
657 [[ -f $tmpdir/$filename.sig ]] && mv "$tmpdir/$filename.sig" "$REPO_DB_FILE.sig"
658 dblink="${REPO_DB_FILE%.tar*}"
659 target=${REPO_DB_FILE##*/}
660 rm -f "$dblink" "$dblink.sig"
661 ln -s "$target" "$dblink" 2>/dev/null || \
662 ln "$target" "$dblink" 2>/dev/null || \
663 cp "$REPO_DB_FILE" "$dblink"
664 if [[ -f "$REPO_DB_FILE.sig" ]]; then
665 ln -s "$target.sig" "$dblink.sig" 2>/dev/null || \
666 ln "$target.sig" "$dblink.sig" 2>/dev/null || \
667 cp "$REPO_DB_FILE.sig" "$dblink.sig"
669 else
670 msg "$(gettext "No packages modified, nothing to do.")"
671 exit 1
674 exit 0
675 # vim: set ts=2 sw=2 noet: