#!/bin/bash # Copyright (C) 2007-2011 Matias A. Fonzo, Santiago del Estero, AR # # 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 . # Localización % Idioma: TEXTDOMAINDIR=/usr/share/locale TEXTDOMAIN=remove VERSION=2.6 # Funciones # # Una función para mostrar mensajes normales: msg() { local LC_ALL ; printf '%s\n' "$@"; } # Una función para mensajes de advertencia: warn() { local LC_ALL; printf '%b\n' "$@" >&2; } usage() { msg $"Removes one or more packages installed in your system." \ "" \ $"Usage: remove [options] package_name.tlz ..." \ "" \ $"Options:" \ $" -h, --help Show this help and exit." \ $" -v, --version Show the version of the program." \ $" -k, --keep Keep temporary files on the" \ $" temporary directory (debugging)." \ $" -q, --quiet Supress output messages." \ $" -w, --warn Warn about of the files and" \ $" directories that will be removed." \ "" } version() { msg "remove $VERSION" \ "Copyright (C) 2007-2011 Matias A. Fonzo ." \ "License GPLv3+: GNU GPL version 3 or later:" \ "" \ "This is free software: you are free to change and redistribute it." \ "There is NO WARRANTY, to the extent permitted by law." } # Opciones: while (( $# )); do case "$1" in -k|--keep) OPT=KEEP shift ;; -q|--quiet) OPT=QUIET shift ;; -w|--warn) OPT=WARN shift ;; -[h?]|--help) usage exit 0 ;; -[vV]|--version) version exit 0 ;; -*) warn $"${0##*/}: Invalid option: $1" exit 1 ;; *) break; esac done # Si no hay argumentos, llama a la función de ayuda: (( $# == 0 )) && { usage ; exit 0; } # Comprobamos la variable de entorno ROOT: if [[ -n $ROOT && ! -d $ROOT ]]; then warn $"${0##*/}: ROOT=${ROOT}: Invalid directory" exit 1; fi umask 022 # Chequeo de sanidad. # # Comprueba si el sistema de archivos # se encuentra en modo de sólo-lectura: if ! touch ${ROOT}/pkg-remove$$ ; then warn $"The filesystem (on $ROOT) is in read-only mode." exit 1; fi rm -f ${ROOT}/pkg-remove$$ # Base de datos por defecto: DB="${ROOT}/var/db" # Asegura la existencia de las partes de la base de datos: for directory in \ pkg/removed \ pkg/removed/pre-post \ pkg/removed/post-install \ pkg/removed/description do if [[ ! -d ${DB}/$directory ]]; then mkdir -p "${DB}/$directory" fi done # Creación de un directorio temporal seguro: TMP="${DB}/tmp" if [[ ! -d $TMP ]]; then mkdir -p -m 0700 "$TMP" fi # Crea los archivos temporales de forma segura: S=$RANDOM$$ # Hace que algunos comandos corran más rápido: export LC_ALL=C # Más Funciones # # Una función para reflejar el nombre base: _basename() { local name ; name=${1##*/} ; printf "${name%$2}"; } cat_except() { ( cd "$1" mapfile -n 2 -t lines < <(find . -maxdepth 1 -type f -print) if (( ${#lines[*]} == 2 )); then find . -maxdepth 1 -type f ! -name "$2" -exec cat '{}' + fi ) } move_install() { local dirName=$1 fileName=$2 if [[ -r ${DB}/pkg/${dirName}/$fileName ]]; then ( cd ${DB}/pkg/$dirName install -m 0644 $fileName -D ${DB}/pkg/removed/${dirName}/$fileName rm -f $fileName ) fi } empty_dir() { shopt -s nullglob dotglob files=( $@ ) shopt -u nullglob dotglob if (( ${#files[*]} == 0 )); then return 0; else return 1; fi } keep_lnk() { while read link ; do if [[ -L ${ROOT}/$link ]]; then if [[ $OPT != WARN ]]; then msg $"${ROOT}/${link}: Symlink found in another package. Dodging." else msg $"LINK -> ${ROOT}/$link" fi else warn $"WARNING: ${ROOT}/${link}: Symlink non-existent, found in another package. Dodging." fi done } keep_file() { while read file ; do [[ -f ${ROOT}/$file ]] || continue; if [[ -r ${ROOT}/$file ]]; then if [[ $OPT != WARN ]]; then msg $"${ROOT}/${file}: File found in another package. Dodging." else if [[ ${ROOT}/$file -nt ${DB}/pkg/$pkgname ]]; then warn $"WARNING: ${ROOT}/${file}: Changed after package installation." fi msg $"FILE -> ${ROOT}/$file" fi else warn $"WARNING: ${ROOT}/${file}: File non-existent, found in another package. Dodging." fi done } extract_lnk() { awk '$2 == "Link:" { print $3 }'; } delete_lnk() { while read link ; do if [[ -L ${ROOT}/$link ]]; then if [[ $OPT != WARN ]]; then if [[ $OPT != QUIET ]]; then rm -v -rf ${ROOT}/$link else rm -rf ${ROOT}/$link fi else msg $"LINK -> ${ROOT}/$link" fi else msg $"${ROOT}/${link}: Symlink no longer exists. Dodging." fi done } delete_file() { while read file ; do [[ -d ${ROOT}/$file ]] && continue; if [[ -r ${ROOT}/$file || -L ${ROOT}/$file ]]; then if [[ $OPT != WARN ]]; then if [[ $OPT != QUIET ]]; then rm -v -rf ${ROOT}/$file else rm -rf ${ROOT}/$file fi else if [[ ${ROOT}/$file -nt ${DB}/pkg/$pkgname ]]; then warn $"WARNING: ${ROOT}/${file}: Changed after package installation." fi msg $"FILE -> ${ROOT}/$file" fi else msg $"${ROOT}/${file}: No longer exists. Dodging." fi done } delete_dir() { sort -r | while read dir ; do [[ -d ${ROOT}/$dir ]] || continue; if [[ $OPT != WARN ]]; then if empty_dir "${ROOT}/${dir%/}/*" ; then if [[ $OPT != QUIET ]]; then rmdir -v ${ROOT}/$dir else rmdir ${ROOT}/$dir fi else warn $"rmdir: ${ROOT}/${dir}: Directory not empty." fi else msg $"DIR -> ${ROOT}/$dir" fi done } # Loop: for package in "$@" ; do pkgname=$(_basename $package .tlz) # Simple package_name notación. # # Comprueba la disponibilidad del paquete buscando # una coincidencia en la base de datos. Intentando # encontrar el paquete por su nombre largo, como # "name-version-arch-build": if [[ ! -e ${DB}/pkg/$pkgname ]]; then if ls ${DB}/pkg/${pkgname}* &>/dev/null ; then for long_name in ${DB}/pkg/${pkgname}* ; do short_name=${long_name##*/} if printf "$short_name" | grep -qwo "$pkgname" ; then pkgname=$short_name fi done fi fi # Si el paquete está disponible, manos a la obra: if [[ -f ${DB}/pkg/$pkgname ]]; then # Imprime un mensaje según la opción: if [[ $OPT != WARN ]]; then msg "" $"< Removing ${DB}/pkg/$pkgname ..." "" else msg "" "% Scanning ${DB}/pkg/$pkgname ..." "" fi cat_except ${DB}/pkg $pkgname | sort -u > ${TMP}/filelist$S if [[ -f ${DB}/pkg/post-install/$pkgname ]]; then cat_except ${DB}/pkg/post-install $pkgname | extract_lnk | sort -u > ${TMP}/linklist$S extract_lnk < ${DB}/pkg/post-install/$pkgname | sort -u > ${TMP}/deletelnk$S ( cd $TMP sort deletelnk$S linklist$S | uniq -d | keep_lnk sort deletelnk$S linklist$S linklist$S | uniq -u | delete_lnk ) else find ${DB}/pkg/post-install -maxdepth 1 -type f -exec cat '{}' + | \ extract_lnk | sort -u > ${TMP}/linklist$S mv ${TMP}/filelist$S ${TMP}/filelist$S.moved sort -u ${TMP}/linklist$S ${TMP}/filelist$S.moved > ${TMP}/filelist$S fi sort ${DB}/pkg/$pkgname ${TMP}/filelist$S | uniq -d | keep_file sort ${DB}/pkg/$pkgname ${TMP}/filelist$S ${TMP}/filelist$S | \ uniq -u > $TMP/uniq$S delete_file < ${TMP}/uniq$S delete_dir < ${TMP}/uniq$S # Si la opción KEEP no está activada, borramos los archivos temporales: if [[ $OPT != KEEP ]]; then rm -f \ ${TMP}/linklist$S ${TMP}/deletelnk$S ${TMP}/filelist$S* ${TMP}/uniq$S fi # Si la opción WARN no está activada, movemos los # archivos de pre y post instalación: if [[ $OPT != WARN ]]; then move_install pre-post $pkgname move_install post-install $pkgname # También, los archivos de descripción: if [[ -d ${DB}/pkg/description ]]; then ( cd ${DB}/pkg/description for file in ${pkgname}_?? ; do if [[ -f $file ]]; then mv $file ${DB}/pkg/removed/description lzip -fF ${DB}/pkg/removed/description/$file fi done ) fi # Mueve el registro del paquete al directorio de removidos: mv ${DB}/pkg/$pkgname ${DB}/pkg/removed/ fi else warn "${DB}/pkg/${pkgname}: No such package" CODE=1 continue; fi done exit $CODE