#!/bin/bash # # Copyright 2003-2005 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 # $Header: $ # # yacleaner-0.3: Yet Another Cleaner tool for Gentoo # "Release early and often" # Author: Octavio Ruiz (Ta^3) # Original Idea: Konstantin Shaposhnikov # Parts of Code by: Ed Catmur # # http://gentoo.org.mx/yacleaner/ # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY. YOU USE AT YOUR OWN RISK. THE AUTHOR # WILL NOT BE LIABLE FOR DATA LOSS, DAMAGES, LOSS OF PROFITS OR ANY # OTHER KIND OF LOSS WHILE USING OR MISUSING THIS SOFTWARE. # See the GNU General Public License for more details. # # ToDo list # # * Don't use `portageq`, read config files prog_name="yacleaner" prog_version="0.3" release_name="Release early and often" fullprog="${prog_name}-${prog_version}" shopt -s extglob set_color() { NO=$'\x1b[0;0m' BR=$'\x1b[0;01m' RD=$'\x1b[31;01m' Rd=$'\x1b[00;31m' GR=$'\x1b[32;01m' Gr=$'\x1b[00;32m' CY=$'\x1b[36;01m' Cy=$'\x1b[00;36m' YL=$'\x1b[33;01m' Yl=$'\x1b[00;33m' } check_for_color() { if [[ ! ${option_color} == no ]]; then set_color fi } set_vars() { set_xterm_title "${fullprog}: Setting variables... (this could take a while)" portage_dirs=(`portageq portdir 2>/dev/null` `portageq portdir_overlay 2>/dev/null`) portage_db="/var/db/pkg" portage_distdir=`portageq distdir 2>/dev/null` varname_distdir='$DISTDIR' portage_binpkgdir=`portageq pkgdir 2>/dev/null` varname_binpkgdir='$PKGDIR' portage_worktmpdir="`portageq envvar PORTAGE_TMPDIR 2>/dev/null`/portage" varname_worktmpdir='$PORTAGE_TMPDIR/portage' portage_logdir=`portageq envvar PORT_LOGDIR 2>/dev/null` varname_logdir='$PORT_LOGDIR' set_xterm_title "${fullprog}: Setting variables... done!" } set_xterm_title() { [[ "$TERM" = @(@(x|E|a|k)term|rxvt|screen)* ]] && echo -n $'\x1b']"2;$*"$'\x07' >&2 [[ "$TERM" = screen* ]] && echo -n $'\x1b']"k$*"$'\x1b'"\\" >&2 } print_version() { cat < ${CY}${fullprog} --help${NO} ${BR}Options:${NO} [ ${GR}--ask${NO} | ${GR}--nocolor${NO} | ${GR}--pretend${NO} | ${GR}--verbose${NO} ] ${BR}Actions:${NO} [ ${GR}--delete${NO} ] END if [[ $help_more != 1 ]]; then cat <> ${tmp_dir}/status } upper() { echo "$*" | tr [[:lower:]] [[:upper:]] } query_database() { set_xterm_title "${fullprog}: Querying database..." set_ifs if [[ -z ${set_vars_once} ]]; then set_vars_once="1" set_vars fi if [[ -z ${query_db_once} ]]; then query_db_once="1" dbpkg_files="` find ${portage_db} -mindepth 2 -maxdepth 2 -type d | cut -c13-`" fi reset_ifs set_xterm_title "${fullprog}: Querying database... done!" } i_log() { echo $dbpkg_files | \ while read db do echo '*'${db##*/}.log done } c_log() { ls -1 ${portage_logdir} 2>/dev/null | while read c_log; do echo "${c_log/+([[:digit:]])-/*}" done } i_worktmp() { echo $dbpkg_files | \ while read db do echo ${db##*/} done } c_worktmp() { ls -1 ${portage_worktmpdir} 2>/dev/null | grep -v homedir } i_dist() { set_ifs echo $dbpkg_files | \ while read db do for (( N_portage_dirs=$((${#portage_dirs[@]} - 1)) ; N_portage_dirs>=0; N_portage_dirs-- )) do cat ${portage_dirs[$N_portage_dirs]}/${db/-[0-9]*/}/files/digest-${db##*/} 2>/dev/null done done | \ awk '{print $3}' reset_ifs } c_dist() { ls -1 ${portage_distdir} 2>/dev/null | grep -v '*-src$' } i_binpkg() { echo $dbpkg_files | \ while read db do echo All/${db##*/}.tbz2 echo ${db}.tbz2 done } c_binpkg() { find $portage_binpkgdir -type f -follow |\ while read binpkg do echo ${binpkg/$portage_binpkgdir\//} done } generate_filelist(){ if [[ "`eval_this portage ${1}`" == "" ]]; then set_status "${RD}*${NO} The ${RD}`eval_this varname ${1}`${NO} variable is not set." echo -e " [ ${Rd}${1}${NO} ]" elif [[ ! -d `eval_this portage ${1}` ]] && [[ ! -r `eval_this portage ${1}` ]]; then set_status "${RD}*${NO} The directory ${RD}`eval_this portage ${1}`${NO} do not exist." echo -e " [ ${Rd}${1}${NO} ]" else set_xterm_title "${fullprog}: Reading database... [${1}]" compare_lists i_${1} c_${1} set_xterm_title "${fullprog}: Reading database... [${1}] done!" if [ ${#files2delete[@]} == 0 ]; then [[ -z ${option_verbose} ]] && echo -e " [ ${Cy}${1}${NO} ] \t\t 0 B" || echo -e " [ ${Cy}${1}${NO} ]" set_status "${YL}*${NO} `eval_this portage ${1}` ${verb:-was} already clean." else set_ifs echo ${files2delete[@]} | \ while read files; do echo eval rm -rf \"`eval_this portage ${1}`/$files\" >> ${tmp_dir}/rmlist done reset_ifs cd `eval_this portage ${1}` eval tb_${1}dir=$(du -cb ${files2delete[@]} 2>/dev/null | tail -n 1 | cut -f1) [[ ! ${ask_pretend} == "pretend" ]] && \ set_status "${GR}*${NO} `eval_this portage ${1}` cleaned." if [[ -n ${option_verbose} ]]; then verbose_output ${1} else echo -e " [ ${Cy}${1}${NO} ] \t\t $(get_GMKB `eval_this tb ${1}`)" fi fi fi } verbose_output() { du -h --max-depth=0 ${files2delete[@]} 2>/dev/null | \ awk '{ print $2, s, $1}' | \ sed -r "s:.*\s:${Gr}\\0${NO}:" | \ while read i; do echo -e " [ ${Cy}${1}${NO} ]\t\t$i" done } compare_lists() { set_ifs ${1} | sort -u >| ${tmp_dir}/${1} ${2} | sort -u >| ${tmp_dir}/${2} files2delete=(`comm -1 -3 ${tmp_dir}/${1} ${tmp_dir}/${2}`) reset_ifs } get_GMKB() { if [[ "${#1}" -ge "10" ]]; then echo "$(( ${1}/1073741824 )) G" elif [[ "${#1}" -ge "7" ]]; then echo "$(( ${1}/1048576 )) M" elif [[ "${#1}" -ge "4" ]]; then echo "$(( ${1}/1024 )) K" else echo "${1} B" fi } print_totalbytes() { totalbytes=$(( ${tb_binpkgdir-0} + ${tb_distdir-0} + ${tb_logdir-0} + ${tb_worktmpdir-0} )) echo -e "\n [ ${GR}total${NO} ] \t\t +${BR}$(get_GMKB $totalbytes)${NO}" } query_user() { set_xterm_title "${fullprog}: ${1}" prompt="${BR}${1}${NO} " shift rprompt="[${GR}Yes${NO}/${RD}No${NO}] " responses=( Yes No ) echo -en "\n$prompt" # Ctrl-C trap "echo 'Interrupted.' >&2; exit 0" 2 while true; do if read -e -p "$rprompt"; then for ((i=0; i<${#responses[@]}; ++i)); do response="${responses[$i]}" [[ "$REPLY" == "${response:0:${#REPLY}}" ]] \ && return $i done for ((i=0; i<${#responses[@]}; ++i)); do response="${responses[$i]}" [[ "$(upper "$REPLY")" == "$(upper "${response:0:${#REPLY}}")" ]] \ && return $i done set_xterm_title "${fullprog}: Sorry, response '$REPLY' not understood." echo "Sorry, response '$REPLY' not understood." >&2 echo -en "\n$prompt" else # Ctrl-D echo "Interrupted." >&2 exit 0 fi done trap - 2 } ask_remove_files() { [[ ! -f ${tmp_dir}/rmlist ]] && ask_pretend="nothing" case $ask_pretend in nothing ) echo -e "${BR}\nNothing to delete${NO}" ; finish_clean;; pretend ) finish_clean;; ask ) while :; do query_user "Do you want me to delete these old${deleargs:- }files?" case $? in 0 ) remove_files && break ;; 1 ) exit 0 ;; esac done;; * ) remove_files;; esac } remove_files(){ trap "echo '${RD}X${NO}' >&2; exit 0" 2 echo -e "\n>>> Waiting 5 seconds before starting..." echo ">>> (Control-C + Enter to abort)..." echo -n ">>> Cleaning in " for seconds in $(seq 1 5 | tac); do set_xterm_title "${fullprog}: Waiting ${seconds} seconds before starting..." echo -n "${RD}${seconds}${NO} " sleep 1 done echo trap - 2 set_xterm_title "${fullprog}: Deleting old${deleargs:- }files..." echo -e "\n>>> Deleting old${deleargs:- }files..." while :; do [[ "$(whoami)" == "root" ]] || echo -en "\nPlease enter your root " su -c 'cat '"${tmp_dir}"'/rmlist | sh' case $? in 0) set_xterm_title "${fullprog}: Deleting old${deleargs:- }files... done!" finish_clean return 0;; 1) continue;; *) exit 1;; esac done } finish_clean() { echo set_status "" cat "${tmp_dir}/status" exit 0 } for ((i=$OPTIND; i<=$#; ++i)); do case ${!i} in -a | --ask ) ask_pretend="ask";; -d | --delete ) action_delete="yes";; -h | --help ) option_help="yes";; -nc | --nocolor ) option_color="no";; -p | --pretend ) verb="is"; ask_pretend="pretend";; -v | --verbose ) option_verbose="yes";; -V | --version ) option_version="yes";; binpkg ) delete_argument="binpkg ${delete_argument}" ;; dist ) delete_argument="dist ${delete_argument}" ;; log ) delete_argument="log ${delete_argument}" ;; worktmp ) delete_argument="worktmp ${delete_argument}" ;; all ) delete_argument="binpkg dist log worktmp" ;; * ) OPTIND=$i; should_exit="yes" ; show_help "${!i} is an invalid option"; break ;; esac done check_for_color tmp_dir="$(mktemp -t -d ${prog_name}.XXXXXXXX)" if [ $? -ne 0 ]; then echo -e "\n ${RD}*${NO} Could not set a valid temporary directory\n" exit 1 fi trap "rm -r $tmp_dir" 0 trap "rm -r $tmp_dir" 15 if [ $# -eq "0" ]; then show_help "please tell me what to do." elif [[ -n "${option_help}" ]]; then show_help elif [[ -n "${option_version}" ]]; then print_version fi if [[ -n "${action_delete}" ]]; then action_delete="yes" fi if [[ $# -ge "1" ]] && \ [[ -z "${delete_argument}" ]] && \ [[ -z "${option_help}" ]] && \ [[ ! "${should_exit}" = "yes" ]]; then show_help "please tell me what you want to delete." elif [[ -n "${option_help}" ]]; then show_help "" "extended" "1" else if [[ -z "${printed_help}" ]] && [[ ! "${should_exit}" = "yes" ]]; then delete_argument=($(for d_args in `echo ${delete_argument}`; do echo ${d_args}; done | sort -u)) [[ "${#delete_argument[@]}" == 1 ]] && deleargs=" ${delete_argument} " echo -e "\n${Gr}These are the old${deleargs:- }files that I would delete:${NO}\n" query_database for (( N_arguments=$((${#delete_argument[@]} - 1 )) ; N_arguments>=0 ; N_arguments-- )) do generate_filelist ${delete_argument[$N_arguments]} done print_totalbytes ask_remove_files fi fi if [[ -n "${printed_help}" ]]; then eval print_help ${display_help_args} exit 0 fi