#!/usr/bin/env bash # libredbdiff # # Copyright (C) 2014 Esteban Carnevale # Copyright (C) 2014, 2017 Luke Shumaker # # License: GNU GPLv3+ # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . . "$(librelib messages)" . "$(librelib conf)" load_files libredbdiff || exit 1 conffile_prbl="$statedir/pacman.conf.parabola" conffile_arch="$statedir/pacman.conf.archlinux" dbpath_prbl="$statedir/pacman.parabola" dbpath_arch="$statedir/pacman.archlinux" mirrorlist_prbl="$statedir/mirrorlist.parabola" mirrorlist_arch="$statedir/mirrorlist.archlinux" name="Libredbdiff" cmd="${0##*/}" field_pkgname_prbl=30 field_pkgname_arch=30 field_pkgname_total="$((field_pkgname_prbl + field_pkgname_arch))" printf_format="%s %-${field_pkgname_prbl}s%-${field_pkgname_arch}s %s | %s\n" printf_format_noarch="%s %-${field_pkgname_total}s %s\n" enablerepo() { local repo="$1" local conffile_arg="$2" msg2 "Enabling repo %q in %q" "$repo" "$conffile_arg" sed -i "s/\#\[$repo\]/[$repo]/" "$conffile_arg" sed -i "\/\[$repo\]/,+1 s/#Include/Include/" "$conffile_arg" } # Globals: # - init createdir() { local dir=$1 if [[ ! -e "$dir" ]] ; then msg "Creating directory %q" "$dir" mkdir -- "$dir" || die "Failed to create directory %q. Exiting." "$dir" elif $init; then warning "%q already exists. Skipping." "$dir" fi } initialize() { createdir "$statedir" if [[ ! -e "$conffile_prbl" ]] ; then msg "Generating %s %q" Parabola pacman.conf cp -T /usr/share/pacman/defaults/pacman.conf.x86_64 "$conffile_prbl" sed -r \ -e "s|/etc/pacman\.d/mirrorlist$|$mirrorlist_prbl|" \ -e "s|^#?DBPath\s*=.*|DBPath = $dbpath_prbl|" \ -e "s|^#?Architecture\s*=.*|Architecture = x86_64|" \ -i "$conffile_prbl" local repo for repo in multilib {libre,pcr,nonprism}{,-multilib}; do enablerepo "$repo" "$conffile_prbl" done else warning "%q already exists. Skipping." "$conffile_prbl" fi if [[ ! -e "$conffile_arch" ]] ; then msg "Generating %s %q" Arch pacman.conf wget -q \ -O "$conffile_arch" \ "https://git.archlinux.org/svntogit/packages.git/plain/pacman/repos/core-x86_64/pacman.conf.x86_64" || die "Failed to download %q. Exiting." "$conffile_arch" sed -r \ -e "s|/etc/pacman\.d/mirrorlist$|$mirrorlist_arch|" \ -e "s|^#?DBPath\s*=.*|DBPath = $dbpath_arch|" \ -e "s|^#?Architecture\s*=.*|Architecture = x86_64|" \ -i "$conffile_arch" enablerepo multilib "$conffile_arch" else warning "%q already exists. Skipping." "$conffile_arch" fi if [[ ! -e "$mirrorlist_prbl" ]] ; then printf 'Server = %s\n' "$mirror_prbl" > "$mirrorlist_prbl" else warning "%q already exists. Skipping." "$mirrorlist_prbl" fi if [[ ! -e "$mirrorlist_arch" ]] ; then printf 'Server = %s\n' "$mirror_arch" > "$mirrorlist_arch" else warning "%q already exists. Skipping." "$mirrorlist_arch" fi } # Globals: # - pkgname : the Parabola pkgname # - ver_prbl : a map of pkgname->arch_pkgver # - ver_arch : a map of pkgname->prbl_pkgver # - provides : a map of pkgname->provides compare_pkgs() { local cmp if [[ -n "${ver_arch[$pkgname]}" ]] ; then cmp=$(vercmp "${ver_prbl[$pkgname]}" "${ver_arch[$pkgname]}") if [[ "$cmp" -lt 0 ]]; then printf "$printf_format" \ '=' \ "$pkgname" \ "" \ "${ver_prbl[$pkgname]}" \ "${ver_arch[$pkgname]}" fi elif [[ -n "${provides[$pkgname]}" ]]; then local _provides provide read -r -a _provides <<<"${provides[$pkgname]}" for provide in "${_provides[@]}"; do if [[ -n "${ver_arch[$provide]}" ]]; then cmp=$(vercmp "${ver_prbl[$pkgname]}" "${ver_arch[$provide]}") if [[ "$cmp" -lt 0 ]]; then printf "$printf_format" \ 'p' \ "$pkgname" \ "$provide" \ "${ver_prbl[$pkgname]}" \ "${ver_arch[$provide]}" fi fi done else printf "$printf_format_noarch" \ 'o' \ "$pkgname" \ "${ver_prbl[$pkgname]}" fi } # Globals: # - prbl_packages_tmp # - ver_prbl # - ver_arch (transitively through compare_pkgs) # - provides print_cmp() { local repo="$1" < "$prbl_packages_tmp" \ awk -F/ -v repo="$repo" '$1 == repo {print $2}' | while read -a line ; do ver_prbl["${line[0]}"]="${line[1]}" provides["${line[0]}"]="${line[*]:2}" pkgname="${line[0]}" compare_pkgs done } usage() { print "Usage: sudo %q" "$cmd" print " or: %q -n [REPO]" "$cmd" print " or: %q -h" "$cmd" print 'Show packages that need to be updated from Arch repositories.' echo prose "Compares packages in Parabola repositories. Packages from all configured Parabola repositories are compared. A Parabola repository name can be specified as argument to compare only packages in that repository." echo prose "The default mode of operation is to download/update all necessary files for comparison, but not compare them. Specify the \`-n\` flag to not download anything, but to compare already downloaded files." echo prose "In the compare mode (if the \`-n\` flag is given), by default it will iterate over each of the configured repositories, prefixing each section with a line in the format \`[REPO]\`. However, if a REPO is specified on the command line, it scan for packages only in that repository, and no \`[REPO]\` line will be printed." echo print 'Options:' flag '-n' "Don't update anything, just compare already downloaded files." flag '-h' 'Show this message' echo print "Output format:" print "type_character parabola_pkgname (arch_pkgname) parabola_pkgver - (arch_pkgver)" echo print "Type characters:" flag '=' "Arch package with the same pkgname and greater pkgver was found" flag 'p' "Arch package with pkgname equal to a provide and greater pkgver was found In this case arch_pkgname is a provide of parabola_pkgname" flag 'o' "No Arch package with the same pkgname or with pkgname equal to a provide was found" } main() { local args arg mode=update badargs=false args="$(getopt -n "$cmd" -o nh -l noupdate,help -- "$@")" || badargs=true eval "set -- $args" while [[ $# -gt 0 ]]; do arg=$1 shift case "$arg" in -n|--noupdate) mode=compare ;; -h|--help) usage return 0 ;; --) break ;; esac done if $badargs; then usage >&2 return 2 fi case $mode in update) if [[ $# -gt 0 ]]; then print "%s: found non-flag arguments; a repo list may only be specified with the -n flag: %s" "$cmd" "$*" >&2 usage >&2 return 2 fi main_update ;; compare) if [[ $# -gt 1 ]]; then print "%s: found too many non-flag arguments; only one repo may be specified: %s" "$cmd" "$*" >&2 usage >&2 return 2 fi if [[ $# = 1 ]]; then if ! in_array "$1" "${repos[@]}"; then die "The specified Parabola repo \"%s\" cannot be compared. It's not in the list of repos in the configuration variable \"repos\"." "$1" fi fi main_compare "$@" ;; esac } main_update() { if true; then if [[ $EUID != 0 ]]; then die "To initialize %s or update %s pacman databases, %s must be run as root (without arguments). Nothing done." \ "$name" \ "$name" \ "$cmd" fi check_vars libredbdiff statedir mirror_prbl mirror_arch || exit 1 local init=false if ! [[ -e "$conffile_prbl" && \ -e "$conffile_arch" && \ -e "$mirrorlist_prbl" && \ -e "$mirrorlist_arch" ]]; then warning "At least one %s configuration file is missing." \ "$name" msg "Downloading and preparing missing configuration files." initialize init=true fi if ! [[ -d "$dbpath_prbl" && \ -d "$dbpath_arch" ]]; then warning "At least one %s pacman DB directory is missing. Synchronizing %s DB files." \ "$name" "$name" fi createdir "$dbpath_prbl" msg "Synchronizing %s pacman databases for %s" "$name" "Parabola" pacman --config "$conffile_prbl" -Sy || die "Failed to synchronize pacman database for %s. Exiting." Parabola createdir "$dbpath_arch" msg "Synchronizing %s pacman databases for %s" "$name" "Arch" pacman --config "$conffile_arch" -Sy || die "Failed to synchronize pacman database for %s. Exiting." Arch msg "%s pacman databases are updated. %s is ready. Run %q -n to print results." \ "$name" "$name" "$cmd" return 0 fi } main_compare() { if true; then check_vars libredbdiff statedir repos || exit 1 local conffiles=( "$dbpath_prbl/sync/libre.db" "$dbpath_arch/sync/core.db" "$conffile_prbl" "$conffile_arch" ) local file for file in "${conffiles[@]}"; do if ! [[ -f "$file" && -r "$file" ]]; then error "Could not read %q." "$file" die "Nothing done. It may be necessary to run %q without \ arguments as root to initialize %s." "$cmd" "$name" fi done local tmpdir tmpdir="$(mktemp --tmpdir -d "$cmd.XXXXXXXXXX")" || die "Could not create temporary working directory" trap "rm -rf -- $(printf %q "$tmpdir")" RETURN local arch_packages_tmp="$tmpdir/arch-packages" local prbl_packages_tmp="$tmpdir/parabola-packages" unset provides ver_prbl ver_arch declare -gA provides ver_prbl ver_arch pacman --config "$conffile_arch" -Ss | \ grep -v '^ ' | \ awk -F/ '{print $2}' \ > "$arch_packages_tmp" || \ die "pacman command to get Arch package data has failed. Exiting." while read -a line; do ver_arch["${line[0]}"]="${line[1]}" done < "$arch_packages_tmp" expac --config "$conffile_prbl" -S '%r/%n %v %S' \ > "$prbl_packages_tmp" || \ die "expac command to get Parabola package data has failed. Exiting." if [[ $# == 1 ]]; then print_cmp "$1" else for repo in "${repos[@]}" ; do echo "[$repo]" print_cmp "$repo" done fi fi } main "$@"