Download and verify package database signatures
[pacman-ng.git] / scripts / repo-add.sh.in
blob59e98cfe699d93c7e298ea0390cdf2a2b18c1254
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-2008 Aaron Griffin <aaron@archlinux.org>
8 # Copyright (c) 2007-2008 Dan McGee <dan@archlinux.org>
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 # gettext initialization
24 export TEXTDOMAIN='pacman'
25 export TEXTDOMAINDIR='@localedir@'
27 myver='@PACKAGE_VERSION@'
28 confdir='@sysconfdir@'
30 QUIET=0
31 DELTA=0
32 WITHFILES=0
33 SIGN=0
34 VERIFY=0
35 REPO_DB_FILE=
36 LOCKFILE=
37 CLEAN_LOCK=0
39 # ensure we have a sane umask set
40 umask 0022
42 msg() {
43 (( QUIET )) && return
44 local mesg=$1; shift
45 printf "==> ${mesg}\n" "$@" >&1
48 msg2() {
49 (( QUIET )) && return
50 local mesg=$1; shift
51 printf " -> ${mesg}\n" "$@" >&1
54 warning() {
55 local mesg=$1; shift
56 printf "==> $(gettext "WARNING:") ${mesg}\n" "$@" >&2
59 error() {
60 local mesg=$1; shift
61 printf "==> $(gettext "ERROR:") ${mesg}\n" "$@" >&2
64 # print usage instructions
65 usage() {
66 printf "repo-add, repo-remove (pacman) %s\n\n" "$myver"
67 printf "$(gettext "Usage: repo-add [-d] [-f] [-q] [-s] [-v] <path-to-db> <package|delta> ...\n")"
68 printf "$(gettext "Usage: repo-remove [-q] <path-to-db> <packagename|delta> ...\n\n")"
69 printf "$(gettext "\
70 repo-add will update a package database by reading a package file.\n\
71 Multiple packages to add can be specified on the command line.\n\n")"
72 printf "$(gettext "\
73 repo-remove will update a package database by removing the package name\n\
74 specified on the command line from the given repo database. Multiple\n\
75 packages to remove can be specified on the command line.\n\n")"
76 printf "$(gettext "\
77 Use the -q/--quiet flag to minimize output to basic messages, warnings,\n\
78 and errors.\n\n")"
79 printf "$(gettext "\
80 Use the -d/--delta flag to automatically generate and add a delta file\n\
81 between the old entry and the new one, if the old package file is found\n\
82 next to the new one.\n\n")"
83 printf "$(gettext "\
84 Use the -f/--files flag to update a database including file entries.\n\n
85 See repo-add(8) for more details and descriptions of the available options.\n\n")"
86 echo "$(gettext "Example: repo-add /path/to/repo.db.tar.gz pacman-3.0.0.pkg.tar.gz")"
87 echo "$(gettext "Example: repo-remove /path/to/repo.db.tar.gz kernel26")"
90 version() {
91 printf "repo-add, repo-remove (pacman) %s\n\n" "$myver"
92 printf "$(gettext "\
93 Copyright (C) 2006-2008 Aaron Griffin <aaron@archlinux.org>.\n\
94 Copyright (c) 2007-2008 Dan McGee <dan@archlinux.org>.\n\n\
95 This is free software; see the source for copying conditions.\n\
96 There is NO WARRANTY, to the extent permitted by law.\n")"
99 # write a list entry
100 # arg1 - Entry name
101 # arg2 - List
102 # arg3 - File to write to
103 write_list_entry() {
104 if [[ -n $2 ]]; then
105 echo "%$1%" >>$3
106 echo -e $2 >>$3
110 find_pkgentry()
112 local pkgname=$1
113 local pkgentry
114 for pkgentry in $tmpdir/$pkgname*; do
115 name=${pkgentry##*/}
116 if [[ ${name%-*-*} = $pkgname ]]; then
117 echo $pkgentry
118 return 0
120 done
121 return 1
124 # Get the package name from the delta filename
125 get_delta_pkgname() {
126 local tmp
128 tmp=${1##*/}
129 echo ${tmp%-*-*_to*}
132 # write a delta entry
133 # arg1 - path to delta file
134 db_write_delta()
136 deltafile="$1"
137 pkgname="$(get_delta_pkgname $deltafile)"
139 pkgentry=$(find_pkgentry $pkgname)
140 if [[ -z $pkgentry ]]; then
141 error "$(gettext "No database entry for package '%s'.")" "$pkgname"
142 return 1
144 deltas="$pkgentry/deltas"
145 if [[ ! -f $deltas ]]; then
146 echo -e "%DELTAS%" >$deltas
148 # get md5sum and compressed size of package
149 md5sum="$(openssl dgst -md5 "$deltafile")"
150 md5sum="${md5sum##* }"
151 csize=$(@SIZECMD@ "$deltafile")
153 oldfile=$(xdelta3 printhdr $deltafile | grep "XDELTA filename (source)" | sed 's/.*: *//')
154 newfile=$(xdelta3 printhdr $deltafile | grep "XDELTA filename (output)" | sed 's/.*: *//')
156 if grep -q "$oldfile.*$newfile" $deltas; then
157 sed -i.backup "/$oldfile.*$newfile/d" $deltas && rm -f $deltas.backup
159 msg2 "$(gettext "Adding 'deltas' entry : %s -> %s")" "$oldfile" "$newfile"
160 echo ${deltafile##*/} $md5sum $csize $oldfile $newfile >> $deltas
162 return 0
163 } # end db_write_delta
165 # remove a delta entry
166 # arg1 - path to delta file
167 db_remove_delta()
169 deltafile="$1"
170 filename=${deltafile##*/}
171 pkgname="$(get_delta_pkgname $deltafile)"
173 pkgentry=$(find_pkgentry $pkgname)
174 if [[ -z $pkgentry ]]; then
175 return 1
177 deltas="$pkgentry/deltas"
178 if [[ ! -f $deltas ]]; then
179 return 1
181 if grep -q "$filename" $deltas; then
182 sed -i.backup "/$filename/d" $deltas && rm -f $deltas.backup
183 msg2 "$(gettext "Removing existing entry '%s'...")" "$filename"
184 return 0
187 return 1
188 } # end db_remove_delta
190 # sign the package database once repackaged
191 create_signature() {
192 (( ! SIGN )) && return
193 local dbfile="$1"
194 local ret=0
195 msg "$(gettext "Signing database...")"
196 if [ ! $(type -p "gpg") ]; then
197 error "$(gettext "Cannot find the gpg binary! Is gnupg installed?")"
198 exit 1 # $E_MISSING_PROGRAM
200 gpg --detach-sign --use-agent "$dbfile" || ret=$?
201 if (( ! ret )); then
202 msg2 "$(gettext "Created signature file %s.")" "$dbfile.sig"
203 else
204 warning "$(gettext "Failed to sign package database.")"
208 # verify the existing package database signature
209 verify_signature() {
210 (( ! VERIFY )) && return
211 local dbfile="$1"
212 local ret=0
213 msg "$(gettext "Verifying database signature...")"
214 if [ ! $(type -p "gpg") ]; then
215 error "$(gettext "Cannot find the gpg binary! Is gnupg installed?")"
216 exit 1 # $E_MISSING_PROGRAM
218 if [[ ! -f $dbfile.sig ]]; then
219 warning "$(gettext "No existing signature found, skipping verification.")"
220 return
222 gpg --verify "$dbfile.sig" || ret=$?
223 if (( ! ret )); then
224 msg2 "$(gettext "Database signature file verified.")"
225 else
226 error "$(gettext "Database signature was NOT valid!")"
227 exit 1
231 # write an entry to the pacman database
232 # arg1 - path to package
233 db_write_entry()
235 # blank out all variables
236 local pkgfile="$1"
237 local pkgname pkgver pkgdesc csize size md5sum url arch builddate packager \
238 _groups _licenses _replaces _depends _conflicts _provides _optdepends
240 local OLDIFS="$IFS"
241 # IFS (field separator) is only the newline character
242 IFS="
245 # read info from the zipped package
246 local line var val
247 for line in $(bsdtar -xOqf "$pkgfile" .PKGINFO |
248 grep -v '^#' | sed 's|\(\w*\)\s*=\s*\(.*\)|\1 \2|'); do
249 # bash awesomeness here- var is always one word, val is everything else
250 var=${line%% *}
251 val=${line#* }
252 declare $var="$val"
253 case "$var" in
254 group) _groups="$_groups$group\n" ;;
255 license) _licenses="$_licenses$license\n" ;;
256 replaces) _replaces="$_replaces$replaces\n" ;;
257 depend) _depends="$_depends$depend\n" ;;
258 conflict) _conflicts="$_conflicts$conflict\n" ;;
259 provides) _provides="$_provides$provides\n" ;;
260 optdepend) _optdepends="$_optdepends$optdepend\n" ;;
261 esac
262 done
264 IFS=$OLDIFS
266 csize=$(@SIZECMD@ "$pkgfile")
268 # ensure $pkgname and $pkgver variables were found
269 if [[ -z $pkgname || -z $pkgver ]]; then
270 error "$(gettext "Invalid package file '%s'.")" "$pkgfile"
271 return 1
274 pushd "$tmpdir" >/dev/null
275 if [[ -d $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 # compute checksums
288 msg2 "$(gettext "Computing checksums...")"
289 md5sum="$(openssl dgst -md5 "$pkgfile")"
290 md5sum="${md5sum##* }"
291 sha256sum="$(openssl dgst -sha256 "$pkgfile")"
292 sha256sum="${sha256sum##* }"
294 # remove an existing entry if it exists, ignore failures
295 db_remove_entry "$pkgname"
297 # create package directory
298 mkdir "$pkgname-$pkgver"
299 pushd "$pkgname-$pkgver" >/dev/null
301 # restore an eventual deltas file
302 [[ -f ../$pkgname.deltas ]] && mv "../$pkgname.deltas" deltas
304 # create desc entry
305 msg2 "$(gettext "Creating '%s' db entry...")" 'desc'
306 echo -e "%FILENAME%\n$(basename "$1")\n" >>desc
307 echo -e "%NAME%\n$pkgname\n" >>desc
308 [[ -n $pkgbase ]] && echo -e "%BASE%\n$pkgbase\n" >>desc
309 echo -e "%VERSION%\n$pkgver\n" >>desc
310 [[ -n $pkgdesc ]] && echo -e "%DESC%\n$pkgdesc\n" >>desc
311 write_list_entry "GROUPS" "$_groups" "desc"
312 [[ -n $csize ]] && echo -e "%CSIZE%\n$csize\n" >>desc
313 [[ -n $size ]] && echo -e "%ISIZE%\n$size\n" >>desc
315 # add checksums
316 echo -e "%MD5SUM%\n$md5sum\n" >>desc
317 echo -e "%SHA256SUM%\n$sha256sum\n" >>desc
319 # add base64'd PGP signature
320 if [[ -f $startdir/$pkgfile.sig ]]; then
321 pgpsig=$(openssl base64 -in "$startdir/$pkgfile.sig" | tr -d '\n')
322 echo -e "%PGPSIG%\n$pgpsig\n" >>desc
325 [[ -n $url ]] && echo -e "%URL%\n$url\n" >>desc
326 write_list_entry "LICENSE" "$_licenses" "desc"
327 [[ -n $arch ]] && echo -e "%ARCH%\n$arch\n" >>desc
328 [[ -n $builddate ]] && echo -e "%BUILDDATE%\n$builddate\n" >>desc
329 [[ -n $packager ]] && echo -e "%PACKAGER%\n$packager\n" >>desc
330 write_list_entry "REPLACES" "$_replaces" "desc"
332 # create depends entry
333 msg2 "$(gettext "Creating '%s' db entry...")" 'depends'
334 # create the file even if it will remain empty
335 touch "depends"
336 write_list_entry "DEPENDS" "$_depends" "depends"
337 write_list_entry "CONFLICTS" "$_conflicts" "depends"
338 write_list_entry "PROVIDES" "$_provides" "depends"
339 write_list_entry "OPTDEPENDS" "$_optdepends" "depends"
341 popd >/dev/null
342 popd >/dev/null
344 # create files file if wanted
345 if (( WITHFILES )); then
346 msg2 "$(gettext "Creating '%s' db entry...")" 'files'
347 local files_path="$tmpdir/$pkgname-$pkgver/files"
348 echo "%FILES%" >$files_path
349 bsdtar --exclude='.*' -tf "$pkgfile" >>$files_path
352 # create a delta file
353 if (( DELTA )); then
354 if [[ -n $oldfilename ]]; then
355 if [[ -f $oldfile ]]; then
356 delta=$(pkgdelta -q $oldfile $1)
357 if [[ -f $delta ]]; then
358 db_write_delta $delta
360 else
361 warning "$(gettext "Old package file not found: %s")" "$oldfilename"
366 return 0
367 } # end db_write_entry
369 # remove existing entries from the DB
370 # arg1 - package name
371 db_remove_entry() {
372 local pkgname=$1
373 local notfound=1
374 local pkgentry=$(find_pkgentry $pkgname)
375 while [[ -n $pkgentry ]]; do
376 notfound=0
377 if [[ -f $pkgentry/deltas ]]; then
378 mv "$pkgentry/deltas" "$tmpdir/$pkgname.deltas"
380 msg2 "$(gettext "Removing existing entry '%s'...")" \
381 "$(basename $pkgentry)"
382 rm -rf $pkgentry
383 pkgentry=$(find_pkgentry $pkgname)
384 done
385 return $notfound
386 } # end db_remove_entry
388 check_repo_db()
390 # check lock file
391 if ( set -o noclobber; echo "$$" > "$LOCKFILE") 2> /dev/null; then
392 CLEAN_LOCK=1
393 else
394 error "$(gettext "Failed to acquire lockfile: %s.")" "$LOCKFILE"
395 [[ -f $LOCKFILE ]] && error "$(gettext "Held by process %s")" "$(cat $LOCKFILE)"
396 exit 1
399 if [[ -f $REPO_DB_FILE ]]; then
400 # there are two situations we can have here- a DB with some entries,
401 # or a DB with no contents at all.
402 if ! bsdtar -tqf "$REPO_DB_FILE" '*/desc' >/dev/null 2>&1; then
403 # check empty case
404 if [[ -n $(bsdtar -tqf "$REPO_DB_FILE" '*' 2>/dev/null) ]]; then
405 error "$(gettext "Repository file '%s' is not a proper pacman database.")" "$REPO_DB_FILE"
406 exit 1
409 verify_signature "$REPO_DB_FILE"
410 msg "$(gettext "Extracting database to a temporary location...")"
411 bsdtar -xf "$REPO_DB_FILE" -C "$tmpdir"
412 else
413 case "$cmd" in
414 repo-remove)
415 error "$(gettext "Repository file '%s' was not found.")" "$REPO_DB_FILE"
416 exit 1
418 repo-add)
419 # check if the file can be created (write permission, directory existence, etc)
420 if ! touch "$REPO_DB_FILE"; then
421 error "$(gettext "Repository file '%s' could not be created.")" "$REPO_DB_FILE"
422 exit 1
424 rm -f "$REPO_DB_FILE"
426 esac
430 add()
432 if [[ ! -f $1 ]]; then
433 error "$(gettext "File '%s' not found.")" "$1"
434 return 1
437 if [[ ${1##*.} == "delta" ]]; then
438 deltafile=$1
439 msg "$(gettext "Adding delta '%s'")" "$deltafile"
440 if ! type xdelta3 &>/dev/null; then
441 error "$(gettext "Cannot find the xdelta3 binary! Is xdelta3 installed?")"
442 exit 1
444 if db_write_delta "$deltafile"; then
445 return 0
446 else
447 return 1
451 pkgfile=$1
452 if ! bsdtar -tqf "$pkgfile" .PKGINFO >/dev/null 2>&1; then
453 error "$(gettext "'%s' is not a package file, skipping")" "$pkgfile"
454 return 1
457 msg "$(gettext "Adding package '%s'")" "$pkgfile"
459 db_write_entry "$pkgfile"
462 remove()
464 if [[ ${1##*.} == "delta" ]]; then
465 deltafile=$1
466 msg "$(gettext "Searching for delta '%s'...")" "$deltafile"
467 if db_remove_delta "$deltafile"; then
468 return 0
469 else
470 error "$(gettext "Delta matching '%s' not found.")" "$deltafile"
471 return 1
475 pkgname=$1
476 msg "$(gettext "Searching for package '%s'...")" "$pkgname"
478 if db_remove_entry "$pkgname"; then
479 rm -f "$tmpdir/$pkgname.deltas"
480 return 0
481 else
482 error "$(gettext "Package matching '%s' not found.")" "$pkgname"
483 return 1
487 trap_exit()
489 echo
490 error "$@"
491 exit 1
494 clean_up() {
495 local exit_code=$?
497 [[ -d $tmpdir ]] && rm -rf "$tmpdir"
498 (( CLEAN_LOCK )) && [[ -f $LOCKFILE ]] && rm -f "$LOCKFILE"
500 exit $exit_code
503 # PROGRAM START
505 # determine whether we have gettext; make it a no-op if we do not
506 if ! type gettext &>/dev/null; then
507 gettext() {
508 echo "$@"
512 case "$1" in
513 -h|--help) usage; exit 0;;
514 -V|--version) version; exit 0;;
515 esac
517 # figure out what program we are
518 cmd="$(basename $0)"
519 if [[ $cmd != "repo-add" && $cmd != "repo-remove" ]]; then
520 error "$(gettext "Invalid command name '%s' specified.")" "$cmd"
521 exit 1
524 tmpdir=$(mktemp -d /tmp/repo-tools.XXXXXXXXXX) || (\
525 error "$(gettext "Cannot create temp directory for database building.")"; \
526 exit 1)
528 trap 'clean_up' EXIT
529 trap 'trap_exit "$(gettext "TERM signal caught. Exiting...")"' TERM HUP QUIT
530 trap 'trap_exit "$(gettext "Aborted by user! Exiting...")"' INT
531 trap 'trap_exit "$(gettext "An unknown error has occured. Exiting...")"' ERR
533 success=0
534 # parse arguments
535 for arg in "$@"; do
536 case "$arg" in
537 -q|--quiet) QUIET=1;;
538 -d|--delta) DELTA=1;;
539 -f|--files) WITHFILES=1;;
540 -s|--sign) SIGN=1;;
541 -v|--verify) VERIFY=1;;
543 if [[ -z $REPO_DB_FILE ]]; then
544 REPO_DB_FILE="$arg"
545 LOCKFILE="$REPO_DB_FILE.lck"
546 check_repo_db
547 else
548 case "$cmd" in
549 repo-add) add $arg && success=1 ;;
550 repo-remove) remove $arg && success=1 ;;
551 esac
554 esac
555 done
557 # if at least one operation was a success, re-zip database
558 if (( success )); then
559 msg "$(gettext "Creating updated database file '%s'")" "$REPO_DB_FILE"
561 case "$REPO_DB_FILE" in
562 *tar.gz) TAR_OPT="z" ;;
563 *tar.bz2) TAR_OPT="j" ;;
564 *tar.xz) TAR_OPT="J" ;;
565 *) warning "$(gettext "'%s' does not have a valid archive extension.")" \
566 "$REPO_DB_FILE" ;;
567 esac
569 filename=$(basename "$REPO_DB_FILE")
571 pushd "$tmpdir" >/dev/null
572 if [[ -n $(ls) ]]; then
573 bsdtar -c${TAR_OPT}f "$filename" *
574 create_signature "$filename"
575 else
576 # we have no packages remaining? zip up some emptyness
577 warning "$(gettext "No packages remain, creating empty database.")"
578 bsdtar -c${TAR_OPT}f "$filename" -T /dev/null
580 popd >/dev/null
582 [[ -f $REPO_DB_FILE ]] && mv -f "$REPO_DB_FILE" "${REPO_DB_FILE}.old"
583 [[ -f $REPO_DB_FILE.sig ]] && rm -f "$REPO_DB_FILE.sig"
584 [[ -f $tmpdir/$filename ]] && mv "$tmpdir/$filename" "$REPO_DB_FILE"
585 [[ -f $tmpdir/$filename.sig ]] && mv "$tmpdir/$filename.sig" "$REPO_DB_FILE.sig"
586 dblink="${REPO_DB_FILE%.tar.*}"
587 target=${REPO_DB_FILE##*/}
588 ln -sf "$target" "$dblink" 2>/dev/null || \
589 ln -f "$target" "$dblink" 2>/dev/null || \
590 cp "$REPO_DB_FILE" "$dblink"
591 if [[ -f "$target.sig" ]]; then
592 ln -sf "$target.sig" "$dblink.sig" 2>/dev/null || \
593 ln -f "$target.sig" "$dblink.sig" 2>/dev/null || \
594 cp "$REPO_DB_FILE.sig" "$dblink.sig"
596 else
597 msg "$(gettext "No packages modified, nothing to do.")"
598 exit 1
601 exit 0
602 # vim: set ts=2 sw=2 noet: