#!/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