Rename pmdepend_t to alpm_depend_t
[pacman-ng.git] / scripts / repo-add.sh.in
blob17b32aab1e85d8091cfe65c02c725cdb053a06e1
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 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/$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) TAR_OPT="" ;;
232 *) error "$(gettext "'%s' does not have a valid archive extension.")" \
233 "$repofile"
234 exit 1 ;;
235 esac
237 printf '%s' "$TAR_OPT"
240 # write an entry to the pacman database
241 # arg1 - path to package
242 db_write_entry() {
243 # blank out all variables
244 local pkgfile="$1"
245 local -a _groups _licenses _replaces _depends _conflicts _provides _optdepends
246 local pkgname pkgver pkgdesc csize size url arch builddate packager \
247 md5sum sha256sum pgpsig
249 # read info from the zipped package
250 local line var val
251 while read -r line; do
252 [[ ${line:0:1} = '#' ]] && continue
253 IFS=' =' read -r var val < <(printf '%s\n' "$line")
255 # normalize whitespace with an extglob
256 declare "$var=${val//+([[:space:]])/ }"
257 case "$var" in
258 group) _groups+=("$group") ;;
259 license) _licenses+=("$license") ;;
260 replaces) _replaces+=("$replaces") ;;
261 depend) _depends+=("$depend") ;;
262 conflict) _conflicts+=("$conflict") ;;
263 provides) _provides+=("$provides") ;;
264 optdepend) _optdepends+=("$optdepend") ;;
265 esac
266 done< <(bsdtar -xOqf "$pkgfile" .PKGINFO)
268 csize=$(@SIZECMD@ "$pkgfile")
270 # compute checksums
271 msg2 "$(gettext "Computing checksums...")"
272 md5sum="$(openssl dgst -md5 "$pkgfile")"
273 md5sum="${md5sum##* }"
274 sha256sum="$(openssl dgst -sha256 "$pkgfile")"
275 sha256sum="${sha256sum##* }"
277 # compute base64'd PGP signature
278 if [[ -f "$pkgfile.sig" ]]; then
279 pgpsig=$(openssl base64 -in "$pkgfile.sig" | tr -d '\n')
282 # ensure $pkgname and $pkgver variables were found
283 if [[ -z $pkgname || -z $pkgver ]]; then
284 error "$(gettext "Invalid package file '%s'.")" "$pkgfile"
285 return 1
288 pushd "$tmpdir" >/dev/null
289 if [[ -d $pkgname-$pkgver ]]; then
290 warning "$(gettext "An entry for '%s' already existed")" "$pkgname-$pkgver"
291 else
292 if (( DELTA )); then
293 pkgentry=$(find_pkgentry $pkgname)
294 if [[ -n $pkgentry ]]; then
295 local oldfilename=$(grep -A1 FILENAME $pkgentry/desc | tail -n1)
296 local oldfile="$(dirname $1)/$oldfilename"
301 # remove an existing entry if it exists, ignore failures
302 db_remove_entry "$pkgname"
304 # create package directory
305 mkdir "$pkgname-$pkgver"
306 pushd "$pkgname-$pkgver" >/dev/null
308 # restore an eventual deltas file
309 [[ -f ../$pkgname.deltas ]] && mv "../$pkgname.deltas" deltas
311 # create desc entry
312 msg2 "$(gettext "Creating '%s' db entry...")" 'desc'
314 format_entry "FILENAME" "${1##*/}"
315 format_entry "NAME" "$pkgname"
316 format_entry "BASE" "$pkgbase"
317 format_entry "VERSION" "$pkgver"
318 format_entry "DESC" "$pkgdesc"
319 format_entry "GROUPS" "${_groups[@]}"
320 format_entry "CSIZE" "$csize"
321 format_entry "ISIZE" "$size"
323 # add checksums
324 format_entry "MD5SUM" "$md5sum"
325 format_entry "SHA256SUM" "$sha256sum"
327 # add PGP sig
328 format_entry "PGPSIG" "$pgpsig"
330 format_entry "URL" "$url"
331 format_entry "LICENSE" "${_licenses[@]}"
332 format_entry "ARCH" "$arch"
333 format_entry "BUILDDATE" "$builddate"
334 format_entry "PACKAGER" "$packager"
335 format_entry "REPLACES" "${_replaces[@]}"
336 } >'desc'
338 # create depends entry
339 msg2 "$(gettext "Creating '%s' db entry...")" 'depends'
341 format_entry "DEPENDS" "${_depends[@]}"
342 format_entry "CONFLICTS" "${_conflicts[@]}"
343 format_entry "PROVIDES" "${_provides[@]}"
344 format_entry "OPTDEPENDS" "${_optdepends[@]}"
345 } >'depends'
347 popd >/dev/null
348 popd >/dev/null
350 # create files file if wanted
351 if (( WITHFILES )); then
352 msg2 "$(gettext "Creating '%s' db entry...")" 'files'
353 local files_path="$tmpdir/$pkgname-$pkgver/files"
354 echo "%FILES%" >$files_path
355 bsdtar --exclude='^.*' -tf "$pkgfile" >>$files_path
358 # create a delta file
359 if (( DELTA )); then
360 if [[ -n $oldfilename ]]; then
361 if [[ -f $oldfile ]]; then
362 delta=$(pkgdelta -q $oldfile $1)
363 if [[ -f $delta ]]; then
364 db_write_delta $delta
366 else
367 warning "$(gettext "Old package file not found: %s")" "$oldfilename"
372 return 0
373 } # end db_write_entry
375 # remove existing entries from the DB
376 # arg1 - package name
377 db_remove_entry() {
378 local pkgname=$1
379 local notfound=1
380 local pkgentry=$(find_pkgentry $pkgname)
381 while [[ -n $pkgentry ]]; do
382 notfound=0
383 if [[ -f $pkgentry/deltas ]]; then
384 mv "$pkgentry/deltas" "$tmpdir/$pkgname.deltas"
386 msg2 "$(gettext "Removing existing entry '%s'...")" \
387 "${pkgentry##*/}"
388 rm -rf $pkgentry
389 pkgentry=$(find_pkgentry $pkgname)
390 done
391 return $notfound
392 } # end db_remove_entry
394 elephant() {
395 case $(( RANDOM % 2 )) in
396 0) printf '%s\n' "H4sIAL3qBE4CAyWLwQ3AMAgD/0xh5UPzYiFUMgjq7LUJsk7yIQNAQTAikFUDnqkr" \
397 "OQFOUm0Wd9pHCi13ONjBpVdqcWx+EdXVX4vXvGv5cgztB9+fJxZ7AAAA"
400 1) printf '%s\n' "H4sIAJVWBU4CA21RMQ7DIBDbeYWrDgQJ7rZ+IA/IB05l69alcx5fc0ASVXUk4jOO" \
401 "7yAAUWtorygwJ4hlMii0YkJKKRKGvsMsiykl1SalvrMD1gUXyXRkGZPx5OPft81K" \
402 "tNAiAjyGjYO47h1JjizPkJrCWbK/4C+uLkT7bzpGc7CT9bmOzNSW5WLSO5vexjmH" \
403 "ZL9JFFZeAa0a2+lKjL2anpYfV+0Zx9LJ+/MC8nRayuDlSNy2rfAPibOzsiWHL0jL" \
404 "SsjFAQAA"
406 esac | openssl base64 -d | gzip -d
409 check_repo_db() {
410 local repodir
412 # ensure the path to the DB exists
413 if [[ "$LOCKFILE" == /* ]]; then
414 repodir=${LOCKFILE%/*}/
415 else
416 repodir=$PWD/$LOCKFILE
417 repodir=${repodir%/*}/
420 if [[ ! -d "$repodir" ]]; then
421 error "$(gettext "%s does not exist or is not a directory.")" "$repodir"
422 exit 1
425 # check lock file
426 if ( set -o noclobber; echo "$$" > "$LOCKFILE") 2> /dev/null; then
427 CLEAN_LOCK=1
428 else
429 error "$(gettext "Failed to acquire lockfile: %s.")" "$LOCKFILE"
430 [[ -f $LOCKFILE ]] && error "$(gettext "Held by process %s")" "$(cat $LOCKFILE)"
431 exit 1
434 if [[ -f $REPO_DB_FILE ]]; then
435 # there are two situations we can have here- a DB with some entries,
436 # or a DB with no contents at all.
437 if ! bsdtar -tqf "$REPO_DB_FILE" '*/desc' >/dev/null 2>&1; then
438 # check empty case
439 if [[ -n $(bsdtar -tqf "$REPO_DB_FILE" '*' 2>/dev/null) ]]; then
440 error "$(gettext "Repository file '%s' is not a proper pacman database.")" "$REPO_DB_FILE"
441 exit 1
444 verify_signature "$REPO_DB_FILE"
445 msg "$(gettext "Extracting database to a temporary location...")"
446 bsdtar -xf "$REPO_DB_FILE" -C "$tmpdir"
447 else
448 case "$cmd" in
449 repo-remove)
450 error "$(gettext "Repository file '%s' was not found.")" "$REPO_DB_FILE"
451 exit 1
453 repo-add)
454 # check if the file can be created (write permission, directory existence, etc)
455 if ! touch "$REPO_DB_FILE"; then
456 error "$(gettext "Repository file '%s' could not be created.")" "$REPO_DB_FILE"
457 exit 1
459 rm -f "$REPO_DB_FILE"
461 esac
465 add() {
466 if [[ ! -f $1 ]]; then
467 error "$(gettext "File '%s' not found.")" "$1"
468 return 1
471 if [[ ${1##*.} == "delta" ]]; then
472 deltafile=$1
473 msg "$(gettext "Adding delta '%s'")" "$deltafile"
474 if ! type xdelta3 &>/dev/null; then
475 error "$(gettext "Cannot find the xdelta3 binary! Is xdelta3 installed?")"
476 exit 1
478 if db_write_delta "$deltafile"; then
479 return 0
480 else
481 return 1
485 pkgfile=$1
486 if ! bsdtar -tqf "$pkgfile" .PKGINFO >/dev/null 2>&1; then
487 error "$(gettext "'%s' is not a package file, skipping")" "$pkgfile"
488 return 1
491 msg "$(gettext "Adding package '%s'")" "$pkgfile"
493 db_write_entry "$pkgfile"
496 remove() {
497 if [[ ${1##*.} == "delta" ]]; then
498 deltafile=$1
499 msg "$(gettext "Searching for delta '%s'...")" "$deltafile"
500 if db_remove_delta "$deltafile"; then
501 return 0
502 else
503 error "$(gettext "Delta matching '%s' not found.")" "$deltafile"
504 return 1
508 pkgname=$1
509 msg "$(gettext "Searching for package '%s'...")" "$pkgname"
511 if db_remove_entry "$pkgname"; then
512 rm -f "$tmpdir/$pkgname.deltas"
513 return 0
514 else
515 error "$(gettext "Package matching '%s' not found.")" "$pkgname"
516 return 1
520 trap_exit() {
521 echo
522 error "$@"
523 exit 1
526 clean_up() {
527 local exit_code=$?
529 [[ -d $tmpdir ]] && rm -rf "$tmpdir"
530 (( CLEAN_LOCK )) && [[ -f $LOCKFILE ]] && rm -f "$LOCKFILE"
532 exit $exit_code
535 # PROGRAM START
537 # determine whether we have gettext; make it a no-op if we do not
538 if ! type gettext &>/dev/null; then
539 gettext() {
540 echo "$@"
544 case "$1" in
545 -h|--help) usage; exit 0;;
546 -V|--version) version; exit 0;;
547 esac
549 # figure out what program we are
550 cmd=${0##*/}
551 if [[ $cmd == "repo-elephant" ]]; then
552 elephant
553 exit 0
556 if [[ $cmd != "repo-add" && $cmd != "repo-remove" ]]; then
557 error "$(gettext "Invalid command name '%s' specified.")" "$cmd"
558 exit 1
561 tmpdir=$(mktemp -d /tmp/repo-tools.XXXXXXXXXX) || (\
562 error "$(gettext "Cannot create temp directory for database building.")"; \
563 exit 1)
565 trap 'clean_up' EXIT
566 trap 'trap_exit "$(gettext "TERM signal caught. Exiting...")"' TERM HUP QUIT
567 trap 'trap_exit "$(gettext "Aborted by user! Exiting...")"' INT
568 trap 'trap_exit "$(gettext "An unknown error has occured. Exiting...")"' ERR
570 declare -a args
571 success=0
572 # parse arguments
573 while (( $# )); do
574 case "$1" in
575 -q|--quiet) QUIET=1;;
576 -d|--delta) DELTA=1;;
577 -f|--files) WITHFILES=1;;
578 -s|--sign)
579 check_gpg
580 SIGN=1
581 if ! gpg --list-key ${GPGKEY} &>/dev/null; then
582 if [[ ! -z $GPGKEY ]]; then
583 error "$(gettext "The key ${GPGKEY} does not exist in your keyring.")"
584 else
585 error "$(gettext "There is no key in your keyring.")"
587 exit 1
590 -k|--key)
591 check_gpg
592 shift
593 GPGKEY="$1"
594 if ! gpg --list-key ${GPGKEY} &>/dev/null; then
595 error "$(gettext "The key ${GPGKEY} does not exist in your keyring.")"
596 exit 1
599 -v|--verify)
600 check_gpg
601 VERIFY=1
604 args+=("$1")
606 esac
607 shift
608 done
611 REPO_DB_FILE=${args[0]}
612 LOCKFILE=$REPO_DB_FILE.lck
614 verify_repo_extension "$REPO_DB_FILE" >/dev/null
615 check_repo_db
617 for arg in "${args[@]:1}"; do
618 case "$cmd" in
619 repo-add) add "$arg" ;;
620 repo-remove) remove "$arg" ;;
621 esac && success=1
622 done
624 # if at least one operation was a success, re-zip database
625 if (( success )); then
626 msg "$(gettext "Creating updated database file '%s'")" "$REPO_DB_FILE"
628 TAR_OPT=$(verify_repo_extension "$REPO_DB_FILE")
629 filename=${REPO_DB_FILE##*/}
631 pushd "$tmpdir" >/dev/null
632 # strip the './' off filenames; this also allows us to tar an empty dir
633 bsdtar -s %^./%% -c${TAR_OPT}f "$REPO_DB_FILE" ./
634 create_signature "$filename"
635 popd >/dev/null
637 [[ -f $REPO_DB_FILE ]] && mv -f "$REPO_DB_FILE" "${REPO_DB_FILE}.old"
638 [[ -f $REPO_DB_FILE.sig ]] && rm -f "$REPO_DB_FILE.sig"
639 [[ -f $tmpdir/$filename ]] && mv "$tmpdir/$filename" "$REPO_DB_FILE"
640 [[ -f $tmpdir/$filename.sig ]] && mv "$tmpdir/$filename.sig" "$REPO_DB_FILE.sig"
641 dblink="${REPO_DB_FILE%.tar*}"
642 target=${REPO_DB_FILE##*/}
643 rm -f "$dblink"
644 ln -s "$target" "$dblink" 2>/dev/null || \
645 ln "$target" "$dblink" 2>/dev/null || \
646 cp "$REPO_DB_FILE" "$dblink"
647 if [[ -f "$target.sig" ]]; then
648 rm -f "$dblink.sig"
649 ln -s "$target.sig" "$dblink.sig" 2>/dev/null || \
650 ln "$target.sig" "$dblink.sig" 2>/dev/null || \
651 cp "$REPO_DB_FILE.sig" "$dblink.sig"
653 else
654 msg "$(gettext "No packages modified, nothing to do.")"
655 exit 1
658 exit 0
659 # vim: set ts=2 sw=2 noet: