scripts: refactor output formatting functions
[pacman-ng.git] / scripts / repo-add.sh.in
blob6ccefd7babb379d5449d69867a3d095233b3a458
1 #!@BASH_SHELL@
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 # gettext initialization
23 export TEXTDOMAIN='pacman'
24 export TEXTDOMAINDIR='@localedir@'
26 myver='@PACKAGE_VERSION@'
27 confdir='@sysconfdir@'
29 QUIET=0
30 DELTA=0
31 WITHFILES=0
32 SIGN=0
33 VERIFY=0
34 REPO_DB_FILE=
35 LOCKFILE=
36 CLEAN_LOCK=0
38 # ensure we have a sane umask set
39 umask 0022
41 m4_include(library/output_format.sh)
43 # print usage instructions
44 usage() {
45 cmd="$(basename $0)"
46 printf "%s (pacman) %s\n\n" "$cmd" "$myver"
47 if [[ $cmd == "repo-add" ]] ; then
48 printf "$(gettext "Usage: repo-add [options] <path-to-db> <package|delta> ...\n")"
49 printf "$(gettext "\
50 repo-add will update a package database by reading a package file.\n\
51 Multiple packages to add can be specified on the command line.\n\n")"
52 printf "$(gettext "Options:\n")"
53 printf "$(gettext " -d, --delta generate and add delta for package update\n")"
54 printf "$(gettext " -f, --files update database's file list\n")"
55 elif [[ $cmd == "repo-remove" ]] ; then
56 printf "$(gettext "Usage: repo-remove [options] <path-to-db> <packagename|delta> ...\n\n")"
57 printf "$(gettext "\
58 repo-remove will update a package database by removing the package name\n\
59 specified on the command line from the given repo database. Multiple\n\
60 packages to remove can be specified on the command line.\n\n")"
61 printf "$(gettext "Options:\n")"
63 printf "$(gettext " -q, --quiet minimize output\n")"
64 printf "$(gettext " -s, --sign sign database with GnuPG after update\n")"
65 printf "$(gettext " -k, --key <key> use the specified key to sign the database\n")"
66 printf "$(gettext " -v, --verify verify database's signature before update\n")"
67 printf "$(gettext "\n\
68 See %s(8) for more details and descriptions of the available options.\n\n")" $cmd
69 if [[ $cmd == "repo-add" ]] ; then
70 echo "$(gettext "Example: repo-add /path/to/repo.db.tar.gz pacman-3.0.0-1-i686.pkg.tar.gz")"
71 elif [[ $cmd == "repo-remove" ]] ; then
72 echo "$(gettext "Example: repo-remove /path/to/repo.db.tar.gz kernel26")"
76 version() {
77 cmd="$(basename $0)"
78 printf "%s (pacman) %s\n\n" "$cmd" "$myver"
79 printf "$(gettext "\
80 Copyright (c) 2006-2011 Pacman Development Team <pacman-dev@archlinux.org>\n\n\
81 This is free software; see the source for copying conditions.\n\
82 There is NO WARRANTY, to the extent permitted by law.\n")"
85 # write a list entry
86 # arg1 - Entry name
87 # arg2 - List
88 # arg3 - File to write to
89 write_list_entry() {
90 if [[ -n $2 ]]; then
91 echo "%$1%" >>$3
92 echo -e $2 >>$3
96 find_pkgentry()
98 local pkgname=$1
99 local pkgentry
100 for pkgentry in $tmpdir/$pkgname*; do
101 name=${pkgentry##*/}
102 if [[ ${name%-*-*} = $pkgname ]]; then
103 echo $pkgentry
104 return 0
106 done
107 return 1
110 # Get the package name from the delta filename
111 get_delta_pkgname() {
112 local tmp
114 tmp=${1##*/}
115 echo ${tmp%-*-*_to*}
118 # write a delta entry
119 # arg1 - path to delta file
120 db_write_delta()
122 deltafile="$1"
123 pkgname="$(get_delta_pkgname $deltafile)"
125 pkgentry=$(find_pkgentry $pkgname)
126 if [[ -z $pkgentry ]]; then
127 error "$(gettext "No database entry for package '%s'.")" "$pkgname"
128 return 1
130 deltas="$pkgentry/deltas"
131 if [[ ! -f $deltas ]]; then
132 echo -e "%DELTAS%" >$deltas
134 # get md5sum and compressed size of package
135 md5sum="$(openssl dgst -md5 "$deltafile")"
136 md5sum="${md5sum##* }"
137 csize=$(@SIZECMD@ "$deltafile")
139 oldfile=$(xdelta3 printhdr $deltafile | grep "XDELTA filename (source)" | sed 's/.*: *//')
140 newfile=$(xdelta3 printhdr $deltafile | grep "XDELTA filename (output)" | sed 's/.*: *//')
142 if grep -q "$oldfile.*$newfile" $deltas; then
143 sed -i.backup "/$oldfile.*$newfile/d" $deltas && rm -f $deltas.backup
145 msg2 "$(gettext "Adding 'deltas' entry : %s -> %s")" "$oldfile" "$newfile"
146 echo ${deltafile##*/} $md5sum $csize $oldfile $newfile >> $deltas
148 return 0
149 } # end db_write_delta
151 # remove a delta entry
152 # arg1 - path to delta file
153 db_remove_delta()
155 deltafile="$1"
156 filename=${deltafile##*/}
157 pkgname="$(get_delta_pkgname $deltafile)"
159 pkgentry=$(find_pkgentry $pkgname)
160 if [[ -z $pkgentry ]]; then
161 return 1
163 deltas="$pkgentry/deltas"
164 if [[ ! -f $deltas ]]; then
165 return 1
167 if grep -q "$filename" $deltas; then
168 sed -i.backup "/$filename/d" $deltas && rm -f $deltas.backup
169 msg2 "$(gettext "Removing existing entry '%s'...")" "$filename"
170 return 0
173 return 1
174 } # end db_remove_delta
176 check_gpg() {
177 if ! type -p gpg >/dev/null; then
178 error "$(gettext "Cannot find the gpg binary! Is gnupg installed?")"
179 exit 1 # $E_MISSING_PROGRAM
183 # sign the package database once repackaged
184 create_signature() {
185 (( ! SIGN )) && return
186 local dbfile="$1"
187 local ret=0
188 msg "$(gettext "Signing database...")"
190 local SIGNWITHKEY=""
191 if [[ -n $GPGKEY ]]; then
192 SIGNWITHKEY="-u ${GPGKEY}"
194 gpg --detach-sign --use-agent ${SIGNWITHKEY} "$dbfile" &>/dev/null || ret=$?
196 if (( ! ret )); then
197 msg2 "$(gettext "Created signature file %s.")" "$dbfile.sig"
198 else
199 warning "$(gettext "Failed to sign package database.")"
203 # verify the existing package database signature
204 verify_signature() {
205 (( ! VERIFY )) && return
206 local dbfile="$1"
207 local ret=0
208 msg "$(gettext "Verifying database signature...")"
210 if [[ ! -f $dbfile.sig ]]; then
211 warning "$(gettext "No existing signature found, skipping verification.")"
212 return
214 gpg --verify "$dbfile.sig" || ret=$?
215 if (( ! ret )); then
216 msg2 "$(gettext "Database signature file verified.")"
217 else
218 error "$(gettext "Database signature was NOT valid!")"
219 exit 1
223 # write an entry to the pacman database
224 # arg1 - path to package
225 db_write_entry()
227 # blank out all variables
228 local pkgfile="$1"
229 local pkgname pkgver pkgdesc csize size url arch builddate packager \
230 _groups _licenses _replaces _depends _conflicts _provides _optdepends \
231 md5sum sha256sum pgpsig
233 local OLDIFS="$IFS"
234 # IFS (field separator) is only the newline character
235 IFS="
238 # read info from the zipped package
239 local line var val
240 for line in $(bsdtar -xOqf "$pkgfile" .PKGINFO |
241 grep -v '^#' | sed 's|\(\w*\)\s*=\s*\(.*\)|\1 \2|'); do
242 # bash awesomeness here- var is always one word, val is everything else
243 var=${line%% *}
244 val=${line#* }
245 declare $var="$val"
246 case "$var" in
247 group) _groups="$_groups$group\n" ;;
248 license) _licenses="$_licenses$license\n" ;;
249 replaces) _replaces="$_replaces$replaces\n" ;;
250 depend) _depends="$_depends$depend\n" ;;
251 conflict) _conflicts="$_conflicts$conflict\n" ;;
252 provides) _provides="$_provides$provides\n" ;;
253 optdepend) _optdepends="$_optdepends$optdepend\n" ;;
254 esac
255 done
257 IFS=$OLDIFS
259 csize=$(@SIZECMD@ "$pkgfile")
261 # compute checksums
262 msg2 "$(gettext "Computing checksums...")"
263 md5sum="$(openssl dgst -md5 "$pkgfile")"
264 md5sum="${md5sum##* }"
265 sha256sum="$(openssl dgst -sha256 "$pkgfile")"
266 sha256sum="${sha256sum##* }"
268 # compute base64'd PGP signature
269 if [[ -f "$pkgfile.sig" ]]; then
270 pgpsig=$(openssl base64 -in "$pkgfile.sig" | tr -d '\n')
273 # ensure $pkgname and $pkgver variables were found
274 if [[ -z $pkgname || -z $pkgver ]]; then
275 error "$(gettext "Invalid package file '%s'.")" "$pkgfile"
276 return 1
279 pushd "$tmpdir" >/dev/null
280 if [[ -d $pkgname-$pkgver ]]; then
281 warning "$(gettext "An entry for '%s' already existed")" "$pkgname-$pkgver"
282 else
283 if (( DELTA )); then
284 pkgentry=$(find_pkgentry $pkgname)
285 if [[ -n $pkgentry ]]; then
286 local oldfilename=$(grep -A1 FILENAME $pkgentry/desc | tail -n1)
287 local oldfile="$(dirname $1)/$oldfilename"
292 # remove an existing entry if it exists, ignore failures
293 db_remove_entry "$pkgname"
295 # create package directory
296 mkdir "$pkgname-$pkgver"
297 pushd "$pkgname-$pkgver" >/dev/null
299 # restore an eventual deltas file
300 [[ -f ../$pkgname.deltas ]] && mv "../$pkgname.deltas" deltas
302 # create desc entry
303 msg2 "$(gettext "Creating '%s' db entry...")" 'desc'
304 echo -e "%FILENAME%\n$(basename "$1")\n" >>desc
305 echo -e "%NAME%\n$pkgname\n" >>desc
306 [[ -n $pkgbase ]] && echo -e "%BASE%\n$pkgbase\n" >>desc
307 echo -e "%VERSION%\n$pkgver\n" >>desc
308 [[ -n $pkgdesc ]] && echo -e "%DESC%\n$pkgdesc\n" >>desc
309 write_list_entry "GROUPS" "$_groups" "desc"
310 [[ -n $csize ]] && echo -e "%CSIZE%\n$csize\n" >>desc
311 [[ -n $size ]] && echo -e "%ISIZE%\n$size\n" >>desc
313 # add checksums
314 echo -e "%MD5SUM%\n$md5sum\n" >>desc
315 echo -e "%SHA256SUM%\n$sha256sum\n" >>desc
317 # add PGP sig
318 [[ -n $pgpsig ]] && echo -e "%PGPSIG%\n$pgpsig\n" >>desc
320 [[ -n $url ]] && echo -e "%URL%\n$url\n" >>desc
321 write_list_entry "LICENSE" "$_licenses" "desc"
322 [[ -n $arch ]] && echo -e "%ARCH%\n$arch\n" >>desc
323 [[ -n $builddate ]] && echo -e "%BUILDDATE%\n$builddate\n" >>desc
324 [[ -n $packager ]] && echo -e "%PACKAGER%\n$packager\n" >>desc
325 write_list_entry "REPLACES" "$_replaces" "desc"
327 # create depends entry
328 msg2 "$(gettext "Creating '%s' db entry...")" 'depends'
329 # create the file even if it will remain empty
330 touch "depends"
331 write_list_entry "DEPENDS" "$_depends" "depends"
332 write_list_entry "CONFLICTS" "$_conflicts" "depends"
333 write_list_entry "PROVIDES" "$_provides" "depends"
334 write_list_entry "OPTDEPENDS" "$_optdepends" "depends"
336 popd >/dev/null
337 popd >/dev/null
339 # create files file if wanted
340 if (( WITHFILES )); then
341 msg2 "$(gettext "Creating '%s' db entry...")" 'files'
342 local files_path="$tmpdir/$pkgname-$pkgver/files"
343 echo "%FILES%" >$files_path
344 bsdtar --exclude='^.*' -tf "$pkgfile" >>$files_path
347 # create a delta file
348 if (( DELTA )); then
349 if [[ -n $oldfilename ]]; then
350 if [[ -f $oldfile ]]; then
351 delta=$(pkgdelta -q $oldfile $1)
352 if [[ -f $delta ]]; then
353 db_write_delta $delta
355 else
356 warning "$(gettext "Old package file not found: %s")" "$oldfilename"
361 return 0
362 } # end db_write_entry
364 # remove existing entries from the DB
365 # arg1 - package name
366 db_remove_entry() {
367 local pkgname=$1
368 local notfound=1
369 local pkgentry=$(find_pkgentry $pkgname)
370 while [[ -n $pkgentry ]]; do
371 notfound=0
372 if [[ -f $pkgentry/deltas ]]; then
373 mv "$pkgentry/deltas" "$tmpdir/$pkgname.deltas"
375 msg2 "$(gettext "Removing existing entry '%s'...")" \
376 "$(basename $pkgentry)"
377 rm -rf $pkgentry
378 pkgentry=$(find_pkgentry $pkgname)
379 done
380 return $notfound
381 } # end db_remove_entry
383 check_repo_db()
385 # check lock file
386 if ( set -o noclobber; echo "$$" > "$LOCKFILE") 2> /dev/null; then
387 CLEAN_LOCK=1
388 else
389 error "$(gettext "Failed to acquire lockfile: %s.")" "$LOCKFILE"
390 [[ -f $LOCKFILE ]] && error "$(gettext "Held by process %s")" "$(cat $LOCKFILE)"
391 exit 1
394 if [[ -f $REPO_DB_FILE ]]; then
395 # there are two situations we can have here- a DB with some entries,
396 # or a DB with no contents at all.
397 if ! bsdtar -tqf "$REPO_DB_FILE" '*/desc' >/dev/null 2>&1; then
398 # check empty case
399 if [[ -n $(bsdtar -tqf "$REPO_DB_FILE" '*' 2>/dev/null) ]]; then
400 error "$(gettext "Repository file '%s' is not a proper pacman database.")" "$REPO_DB_FILE"
401 exit 1
404 verify_signature "$REPO_DB_FILE"
405 msg "$(gettext "Extracting database to a temporary location...")"
406 bsdtar -xf "$REPO_DB_FILE" -C "$tmpdir"
407 else
408 case "$cmd" in
409 repo-remove)
410 error "$(gettext "Repository file '%s' was not found.")" "$REPO_DB_FILE"
411 exit 1
413 repo-add)
414 # check if the file can be created (write permission, directory existence, etc)
415 if ! touch "$REPO_DB_FILE"; then
416 error "$(gettext "Repository file '%s' could not be created.")" "$REPO_DB_FILE"
417 exit 1
419 rm -f "$REPO_DB_FILE"
421 esac
425 add()
427 if [[ ! -f $1 ]]; then
428 error "$(gettext "File '%s' not found.")" "$1"
429 return 1
432 if [[ ${1##*.} == "delta" ]]; then
433 deltafile=$1
434 msg "$(gettext "Adding delta '%s'")" "$deltafile"
435 if ! type xdelta3 &>/dev/null; then
436 error "$(gettext "Cannot find the xdelta3 binary! Is xdelta3 installed?")"
437 exit 1
439 if db_write_delta "$deltafile"; then
440 return 0
441 else
442 return 1
446 pkgfile=$1
447 if ! bsdtar -tqf "$pkgfile" .PKGINFO >/dev/null 2>&1; then
448 error "$(gettext "'%s' is not a package file, skipping")" "$pkgfile"
449 return 1
452 msg "$(gettext "Adding package '%s'")" "$pkgfile"
454 db_write_entry "$pkgfile"
457 remove()
459 if [[ ${1##*.} == "delta" ]]; then
460 deltafile=$1
461 msg "$(gettext "Searching for delta '%s'...")" "$deltafile"
462 if db_remove_delta "$deltafile"; then
463 return 0
464 else
465 error "$(gettext "Delta matching '%s' not found.")" "$deltafile"
466 return 1
470 pkgname=$1
471 msg "$(gettext "Searching for package '%s'...")" "$pkgname"
473 if db_remove_entry "$pkgname"; then
474 rm -f "$tmpdir/$pkgname.deltas"
475 return 0
476 else
477 error "$(gettext "Package matching '%s' not found.")" "$pkgname"
478 return 1
482 trap_exit()
484 echo
485 error "$@"
486 exit 1
489 clean_up() {
490 local exit_code=$?
492 [[ -d $tmpdir ]] && rm -rf "$tmpdir"
493 (( CLEAN_LOCK )) && [[ -f $LOCKFILE ]] && rm -f "$LOCKFILE"
495 exit $exit_code
498 # PROGRAM START
500 # determine whether we have gettext; make it a no-op if we do not
501 if ! type gettext &>/dev/null; then
502 gettext() {
503 echo "$@"
507 case "$1" in
508 -h|--help) usage; exit 0;;
509 -V|--version) version; exit 0;;
510 esac
512 # figure out what program we are
513 cmd="$(basename $0)"
514 if [[ $cmd != "repo-add" && $cmd != "repo-remove" ]]; then
515 error "$(gettext "Invalid command name '%s' specified.")" "$cmd"
516 exit 1
519 tmpdir=$(mktemp -d /tmp/repo-tools.XXXXXXXXXX) || (\
520 error "$(gettext "Cannot create temp directory for database building.")"; \
521 exit 1)
523 trap 'clean_up' EXIT
524 trap 'trap_exit "$(gettext "TERM signal caught. Exiting...")"' TERM HUP QUIT
525 trap 'trap_exit "$(gettext "Aborted by user! Exiting...")"' INT
526 trap 'trap_exit "$(gettext "An unknown error has occured. Exiting...")"' ERR
528 success=0
529 # parse arguments
530 while [[ $# > 0 ]]; do
531 case "$1" in
532 -q|--quiet) QUIET=1;;
533 -d|--delta) DELTA=1;;
534 -f|--files) WITHFILES=1;;
535 -s|--sign)
536 check_gpg
537 SIGN=1
538 if ! gpg --list-key ${GPGKEY} &>/dev/null; then
539 if [[ ! -z $GPGKEY ]]; then
540 error "$(gettext "The key ${GPGKEY} does not exist in your keyring.")"
541 else
542 error "$(gettext "There is no key in your keyring.")"
544 exit 1
547 -k|--key)
548 check_gpg
549 shift
550 GPGKEY="$1"
551 if ! gpg --list-key ${GPGKEY} &>/dev/null; then
552 error "$(gettext "The key ${GPGKEY} does not exist in your keyring.")"
553 exit 1
556 -v|--verify)
557 check_gpg
558 VERIFY=1
561 if [[ -z $REPO_DB_FILE ]]; then
562 REPO_DB_FILE="$1"
563 LOCKFILE="$REPO_DB_FILE.lck"
564 check_repo_db
565 else
566 case "$cmd" in
567 repo-add) add $1 && success=1 ;;
568 repo-remove) remove $1 && success=1 ;;
569 esac
572 esac
573 shift
574 done
576 # if at least one operation was a success, re-zip database
577 if (( success )); then
578 msg "$(gettext "Creating updated database file '%s'")" "$REPO_DB_FILE"
580 case "$REPO_DB_FILE" in
581 *tar.gz) TAR_OPT="z" ;;
582 *tar.bz2) TAR_OPT="j" ;;
583 *tar.xz) TAR_OPT="J" ;;
584 *) warning "$(gettext "'%s' does not have a valid archive extension.")" \
585 "$REPO_DB_FILE" ;;
586 esac
588 filename=$(basename "$REPO_DB_FILE")
590 pushd "$tmpdir" >/dev/null
591 if [[ -n $(ls) ]]; then
592 bsdtar -c${TAR_OPT}f "$filename" *
593 else
594 # we have no packages remaining? zip up some emptyness
595 warning "$(gettext "No packages remain, creating empty database.")"
596 bsdtar -c${TAR_OPT}f "$filename" -T /dev/null
598 create_signature "$filename"
600 popd >/dev/null
602 [[ -f $REPO_DB_FILE ]] && mv -f "$REPO_DB_FILE" "${REPO_DB_FILE}.old"
603 [[ -f $REPO_DB_FILE.sig ]] && rm -f "$REPO_DB_FILE.sig"
604 [[ -f $tmpdir/$filename ]] && mv "$tmpdir/$filename" "$REPO_DB_FILE"
605 [[ -f $tmpdir/$filename.sig ]] && mv "$tmpdir/$filename.sig" "$REPO_DB_FILE.sig"
606 dblink="${REPO_DB_FILE%.tar.*}"
607 target=${REPO_DB_FILE##*/}
608 ln -sf "$target" "$dblink" 2>/dev/null || \
609 ln -f "$target" "$dblink" 2>/dev/null || \
610 cp "$REPO_DB_FILE" "$dblink"
611 if [[ -f "$target.sig" ]]; then
612 ln -sf "$target.sig" "$dblink.sig" 2>/dev/null || \
613 ln -f "$target.sig" "$dblink.sig" 2>/dev/null || \
614 cp "$REPO_DB_FILE.sig" "$dblink.sig"
616 else
617 msg "$(gettext "No packages modified, nothing to do.")"
618 exit 1
621 exit 0
622 # vim: set ts=2 sw=2 noet: