/usr/sbin/cleanse
1 #!/bin/bash
2
3 #---------------------------------------------------------------------
4 ## This script should clean up the sorcery files, particularly
5 ## the depends and packages files. It should validate the files
6 ## remove corrupted lines, and ensure the information all agrees
7 ## with itself. It is also to clean out unused lines and files.
8 ##
9 ## @Copyright Copyright 2004 by Paul Mahon for Source Mage
10 ## @Licence Relased under the GNU GPL version 2
11 ##
12 #---------------------------------------------------------------------
13
14 #---------------------------------------------------------------------
15 ## This software is free software; you can redistribute it and/or modify
16 ## it under the terms of the GNU General Public License as published by
17 ## the Free Software Foundation; either version 2 of the License, or
18 ## (at your option) any later version.
19 ##
20 ## This software is distributed in the hope that it will be useful,
21 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
22 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 ## GNU General Public License for more details.
24 ##
25 ## You should have received a copy of the GNU General Public License
26 ## along with this software; if not, write to the Free Software
27 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 ##
29 #---------------------------------------------------------------------
30
31 #--------------------
32 ## Gives the usage of this script
33 ## @param A switch that was not understood (optional)
34 ## @Stdout Usage statement
35 #--------------------
36 function usage()
37 {
38 local s="cleanse"
39 cat << EOF
40 Invoke $s with one of the arguments below.
41
42 Command Description
43 --delint Finds stale dependency entries
44 --delint full will delint dependencies that are off
45 --prune Finds dispeled spells that should be cast
46 --prune doit will cause it to actually cast and dispel
47 --packages find bad entries in the installed spells file
48
49 --fix attempts to detect errors in cast spells and recasts broken ones
50 --nofix attempts to detect errors in cast spells but does not recast
51 --nofix_quick like --nofix, but does not check dependent spells.
52
53 --tablet clean up the tablet
54 --tablet coalesce clean up the tablet and hardlink duplicate files
55 --tablet_spell clean up the tablet for one spell
56 --logs cleans old logs in the same way a system-update does
57
58 --sweep Clean out old downloaded sources and cached binaries
59 old is defined as not capable of being used by any
60 spell/version in any grimoire on the system.
61
62 --sweep_all Clean out old downloaded sources and cached binaries
63 old is defined as not capable of being used by any spell
64 installed on the system. Only the first version of the
65 spell found is used.
66
67 It is suggested that you try the following order the first time:
68 1) $s --packages
69 2) $s --delint
70 3) $s --tablet
71 4) $s --logs
72 5) $s --prune
73 6) $s --sweep
74 7) $s --fix
75 EOF
76 [[ $# -gt 0 ]] && echo "Unknown $*"
77 }
78
79
80 #---------------------
81 ## Interpret the arguments to this script
82 ## @Args --help Display help">help">help
83 ## @Args --delint [full] Find stale dependency entries, with full it'll look at off entries too
84 ## @Args --prune [doit] Find missing spells, do the actions of "doit" is specified
85 ## @Args --packages Find packages entries that are corrupted, or non-existant
86 ## @Args --tablet Clean up the tablet
87 ## @args --tablet_spell Clean up the tablet for one spell
88 ## @Args --logs Remove stale logs
89 ## @Args --sweep Remove uneeded spool and cache files
90 ## @Args --sweep_all Remove uneeded spool and cache files
91 ## @Args --fix Find binaries that are missing libs and files, and recast broken spells
92 ## @Args --nofix Same as --fix, but won't recast.
93 ## @Args --nofix_quick Same as --nofix, but doesn't check dependent spells
94 #---------------------
95 function args()
96 {
97 local rc
98 let rc=0
99 while [ $# -gt 0 ] ; do
100 arg=$1
101 shift
102
103 case $arg in
104 --help) usage ;;
105 --delint)
106 if [[ $1 == full ]] ; then
107 delint full
108 let rc+=$?
109 shift
110 else
111 delint
112 let rc+=$?
113 fi
114 ;;
115 --prune)
116 if [[ $1 == doit ]] ; then
117 prune_depends doit
118 let rc+=$?
119 shift
120 else
121 prune_depends
122 let rc+=$?
123 fi
124 ;;
125 --logs) clean_logs
126 let rc+=$?
127 ;;
128 --fix) cleanse_fix fix "$*"
129 let rc+=$?
130 shift $#
131 ;;
132 --nofix) cleanse_fix nofix "$*"
133 let rc+=$?
134 shift $#
135 ;;
136 --nofix_quick) cleanse_fix quick "$*"
137 let rc+=$?
138 shift $#
139 ;;
140 --sweep) prune
141 let rc+=$?
142 ;;
143 --sweep_all) prune installed_only
144 let rc+=$?
145 ;;
146 --packages) packages
147 let rc+=$?
148 ;;
149 --tablet)
150 tablet_cleanse_tablet $TABLET_PATH $BACKUPDIR
151 let rc+=$?
152 if [[ $1 == coalesce ]] ; then
153 tablet_coalesce_files $TABLET_PATH $BACKUPDIR
154 let rc+=$?
155 shift
156 fi
157 ;;
158 --tablet_spell)
159 tablet_cleanse_chapter $TABLET_PATH/$1 $BACKUPDIR yes
160 let rc+=$?
161 shift
162 ;;
163 *) usage $arg $* ; return 1 ;;
164 esac
165 done
166 return ${rc:-0}
167 }
168
169 function verify_parameters()
170 {
171 while [[ $# -gt 0 ]]; do
172 case $1 in
173 --delint|--prune|--tablet)
174 case $2 in
175 full|doit|coalesce) shift ;;
176 esac
177 ;;
178 --fix|--nofix|--nofix_quick) true ;;
179 --sweep_all|--sweep|--logs) true ;;
180 --tablet_spell|--packages) true ;;
181 -*) usage $*; exit 1 ;;
182 esac
183 shift
184 done
185 }
186
187 #-------------------
188 ## Find stale dependency entries, with full it'll look at off entries too
189 ## @param full if set, looks at off dependencies as well as on
190 ## @Stdout Misc output and questions
191 ## @Stdin Answers to questions
192 #-------------------
193 function delint()
194 {
195 message "Delinting..."
196 local cond='$3=="on"'
197 local options=()
198 local choice t lines regex
199 if [[ $1 == full ]] ; then
200 cond=''
201 fi
202
203 lock_file $DEPENDS_STATUS
204
205 message "Pass one (malformed depends lines)"
206 # This grep will pull out malformed lines from the depends file
207 regex='^[^:]*:[^:]*:(on|off):(optional|required|suggest|runtime):'
208 lines=$(grep -v -E "$regex" $DEPENDS_STATUS)
209 if [[ $lines ]] ; then
210 message "The following malformed lines were found"
211 message "and will cause problems with the following passes:"
212 message "$lines"
213 if query "Do you want them removed" "n" ; then
214 message "Removing lines..."
215 grep -E "$regex" $DEPENDS_STATUS > $TMP_DIR/delint.1
216 mv $TMP_DIR/delint.1 $DEPENDS_STATUS
217 t=$(date +%Y%m%d%s)
218 message "Saving bad lines to $BACKUPDIR/depends.malformed.$t"
219 echo "$lines" > $BACKUPDIR/depends.malformed.$t
220 message "done."
221 else
222 message "Not removing malformed lines, quitting."
223 return 1
224 fi
225 fi
226
227 generic_remove_stale "$DEPENDS_STATUS" depends two
228
229 message "Pass three (duplicate depends entries)"
230 for LINE in $( sed -n 's/^\([^:]*\):\([^:]*\):.*$/\1:\2/p' $DEPENDS_STATUS | sed 's/+/\\+/' | sort | uniq -d ) ; do
231 message
232 options=()
233 eval $(awk '/^'$LINE':/{
234 if(!ENTRIES[$0]) {
235 printf("options[%d]=\"%s\"\n", count++, $0); ENTRIES[$0]=1;
236 } }' $DEPENDS_STATUS)
237 if [ ${#options[@]} -gt 1 ] ; then
238 select_list choice "" "${options[@]}"
239 message "Removing other entries and keeping selected entry"
240 else
241 choice="${options[0]}"
242 message "Identical lines removed: $choice"
243 fi
244 grep -Ev "^$LINE:" $DEPENDS_STATUS > $TMP_DIR/delint.3
245 echo "$choice" >> $TMP_DIR/delint.3
246 mv $TMP_DIR/delint.3 $DEPENDS_STATUS
247 done
248
249 unlock_file $DEPENDS_STATUS
250
251 touch $SUB_DEPENDS_STATUS
252 lock_file $SUB_DEPENDS_STATUS
253 generic_remove_duplicate "$SUB_DEPENDS_STATUS" sub_depends four
254 generic_remove_stale "$SUB_DEPENDS_STATUS" sub_depends five
255 unlock_file $SUB_DEPENDS_STATUS
256
257 touch $TRIGGER_LIST
258 lock_file $TRIGGER_LIST
259 generic_remove_duplicate "$TRIGGER_LIST" trigger six
260 generic_remove_stale "$TRIGGER_LIST" trigger seven
261 unlock_file $TRIGGER_LIST
262
263 message "Delint done."
264 }
265
266 function generic_remove_duplicate() {
267 local LINE
268 local tmp=$TMP_DIR/delint.$2.dup
269 message "Pass $3 (duplicate $2 entries)"
270 for LINE in $(sort $1 | uniq -d ) ; do
271 if test $(grep -E "^$LINE$" "$1"|wc -l) -gt 1; then
272 message "Duplicated line removed: $LINE"
273 grep -Ev "^$LINE$" $1 > $tmp
274 echo "$LINE" >> $tmp
275 mv $tmp $1
276 fi
277 done
278 }
279
280 function generic_remove_stale() {
281 message "Pass $3 (stale $2 entries)"
282 local tmp=$TMP_DIR/delint.$2.stale
283 local t
284 awk -F: '
285 BEGIN {
286 while (getline < "'$SPELL_STATUS'")
287 Installed[$1] = 1;
288 }
289 {
290 if ( ! Installed[$1])
291 print $0;
292 }' "$1" |tee $tmp
293 t=$(wc -l $tmp | awk '{print $1;}')
294 message "\t$t bad lines, $(( $(wc -l $1| cut -f1 -d' ') - t))" \
295 "good lines in your $2 file."
296 if [[ $t -gt 0 ]] ; then
297 t=$(date +%Y%m%d%s)
298 message "\tSaving bad lines to $BACKUPDIR/$2.stale.$t"
299 cp $tmp $BACKUPDIR/$2.stale.$t
300 message "\tRemoving bad entries"
301 cat $1 >> $tmp
302 sort $tmp | uniq -u > $tmp.2
303 cp $tmp.2 $1
304 fi
305 }
306
307 #---------------------
308 ## Find missing spells, do the actions of "doit" is specified
309 ## @param if set to "doit" it'll actual cast and dispel
310 ## @Stdout questions about what to do about problems
311 ## @Stdin responses to queries
312 #---------------------
313 function prune_depends()
314 {
315 message "Pruning..."
316 local action=""
317 local LINE DISPEL_LIST CAST_LIST
318 local choice options provider spell
319
320 # actualy do the actions, or just output what should be done
321 if [[ $1 == doit ]] ; then
322 action="doit"
323 fi
324
325 # Lock the depends file so no one messes with it while it's being worked on
326 lock_file $DEPENDS_STATUS
327 lock_file $SPELL_STATUS
328
329 # Get a list of bad dependencies
330 # Output is in the form "SPELL DEPENDENCY optional/reqired PROVIDER"
331 bad=( $( awk -F: '
332 BEGIN {
333 while (getline < "'$SPELL_STATUS'")
334 Installed[$1] = 1;
335 }
336 $3=="on"{
337 if($2 ~ "[(].*[)]")
338 prov=gensub("^.*\\((.*)\\).*$", "\\1", 1, $2);
339 else
340 prov="__BLANK__";
341 gsub("\\(.*\\)", "", $2)
342 if ( Installed[$1] && ! Installed[$2] && $2!="")
343 printf("%s %s %s %s\n", $1, $2, $4, prov);
344 }' $DEPENDS_STATUS ) )
345
346 unlock_file $SPELL_STATUS
347 # Set the function args to the spells with problems
348 set -- ${bad[@]}
349 while [ $# -gt 0 ] ; do
350 options=()
351 message "$1 needs $2 ($3)."
352
353 # Check if the dependency is a provider. IF so, the SA may have switched
354 # to another provider and should be given the option to recast with the
355 # new provider
356 if [[ $4 != __BLANK__ ]] ; then
357 message -n "$2 used to provide $4. Now "
358 local CANDIDATES=$( find_providers $4 )
359 provider=""
360 for spell in $CANDIDATES; do
361 if spell_ok $spell; then
362 local provider=$spell
363 break
364 fi
365 done
366
367 if [[ ${provider} ]] ; then
368 message -n "$provider "
369 if [[ $3 == suggest ]] || [[ $3 == runtime ]]; then
370 options=( "Switch from $2 to ${provider}" )
371 else
372 options=( "Switch from $2 to ${provider} and recompile" )
373 fi
374 else
375 message -n "${PROBLEM_COLOR}no one${DEFAULT_COLOR} "
376 fi
377 message "provides it."
378 fi
379
380 # If the spell is optional, then the dependency can be removed and the spell recast
381 # If it's required, then it can't be removed
382 if [[ $3 == required ]] || [[ $3 == runtime ]]; then
383 options=( "Ignore" "Dispel $1" "Cast $2" "${options[@]}" )
384 elif [[ $3 == optional ]] ; then
385 options=( "Ignore" "Dispel $1" "Cast $2" "Recast $1 without $2" "${options[@]}" )
386 elif [[ $3 == suggest ]] ; then
387 options=( "Ignore" "Dispel $1" "Cast $2" "Disable $2 in $1" "${options[@]}" )
388 fi
389
390 #Get the user's choice of action
391 select_list choice "" "${options[@]}"
392 case ${choice} in
393 I*) message "Ignoring." ;;
394 Dispel*) echo $1 >> $TMP_DIR/prune.dispel ;;
395 C*) echo $2 >> $TMP_DIR/prune.cast ;;
396 R*) echo $1 >> $TMP_DIR/prune.cast
397 # Revise choice in the depends file
398 sed 's/^\('$1':'$2':\)on\(:optional:.*\)$/\1off\2/' $DEPENDS_STATUS > $TMP_DIR/prune.1
399 mv $TMP_DIR/prune.1 $DEPENDS_STATUS
400 ;;
401 Disable*)
402 sed 's/^\('$1':'$2':\)on\(:suggest:.*\)$/\1off\2/' $DEPENDS_STATUS > $TMP_DIR/prune.1
403 mv $TMP_DIR/prune.1 $DEPENDS_STATUS
404 ;;
405 S*) sed 's/^\('$1':\)'$2'\(('$4'):.*\)$/\1'$provider'\2/' $DEPENDS_STATUS > $TMP_DIR/prune.1
406 mv $TMP_DIR/prune.1 $DEPENDS_STATUS
407 if [[ $3 == required ]] || [[ $3 == optional ]]; then
408 echo $1 >>$TMP_DIR/prune.cast
409 fi
410 ;;
411 *) message "We should never get here."
412 exit 1
413 ;;
414 esac
415 echo
416 shift 4
417 done
418
419 unlock_file $DEPENDS_STATUS
420
421 # put the spells in two files for later perusal
422 if [[ $action ]] ; then
423 [ -s $TMP_DIR/prune.dispel ] &&
424 dispel --noqueue $(sort -u $TMP_DIR/prune.dispel)
425 [ -s $TMP_DIR/prune.cast ] &&
426 cast -c $(sort -u $TMP_DIR/prune.cast)
427 else
428 local prune_dispel prune_cast t
429 prune_dispel=$(sort -u $TMP_DIR/prune.dispel 2>/dev/null)
430 prune_cast=$(sort -u $TMP_DIR/prune.cast 2>/dev/null)
431 if [[ -z $prune_dispel$prune_cast ]]; then
432 message "Pruning done."
433 return 0
434 fi
435 t=$(date +%Y%m%d%s)
436 [ "$prune_dispel" ] &&
437 echo dispel $prune_dispel > $BACKUPDIR/prune.$t
438 [ "$prune_cast" ] &&
439 echo cast -c $prune_cast >> $BACKUPDIR/prune.$t
440 cat $BACKUPDIR/prune.$t
441 message "For future reference, lines are stored in $BACKUPDIR/prune.$t"
442 fi
443 message "Pruning done."
444 }
445
446 #----------------------
447 ## Find packages entries that are corrupted, or non-existant
448 ## @Stdout Misc status output
449 #----------------------
450 function packages()
451 {
452 local spell t LINE
453 message "Fixing packages..."
454 lock_file $SPELL_STATUS
455
456 message "Pass 1 (bad lines)"
457 sed -n '/^[^:]*:[^:]*:[^:]*:[^:]*$/!p' $SPELL_STATUS > $TMP_DIR/packages.bad
458 if [ -s $TMP_DIR/packages.bad ] ; then
459 sed -n '/^[^:]*:[^:]*:[^:]*:[^:]*$/p' $SPELL_STATUS > $TMP_DIR/packages.good
460 echo "$(wc -l $TMP_DIR/packages.bad) bad lines, $(wc -l $TMP_DIR/packages.good) good lines in your packages file."
461 t=$(date +%Y%m%d%s)
462 message "\tSaving bad lines to $BACKUPDIR/packages.1.bad.$t"
463 mv $TMP_DIR/packages.bad $BACKUPDIR/packages.1.bad.$t
464 message "\tRemoving bad entries"
465 mv $TMP_DIR/packages.good $SPELL_STATUS
466 fi
467
468 message "Pass 2 (non-existant spells)"
469 t=""
470 for spell in $( sed -n 's/^\([^:]*\):.*$/\1/p' $SPELL_STATUS ) ; do
471 if ! [[ $( codex_find_spell_by_name $spell ) ]] ; then
472 message "\t$spell"
473 t="found"
474 fi
475 done
476 if [[ $t ]] ; then
477 message "Entries for non-existant spells will NOT be automaticly removed."
478 message "You may have removed the grimoire with the spell"
479 message "or the name may have changed. You should figure out where"
480 message "these spells went. They may have been deprecated and then"
481 message "removed. You may want to dispel these if that is the case."
482 fi
483
484 message "Pass 3 (duplicate entries)"
485 t=$(
486 for LINE in $( sed -n 's/^\([^:]*\):\([^:]*\):.*$/\1/p' $SPELL_STATUS | sort | uniq -d ) ; do
487 grep "^$LINE:" $SPELL_STATUS | sed 's/^/\t/'
488 done
489 )
490 if [[ $t ]] && query "\tRemove all but one of each set of duplicates?" "n" ; then
491 awk -F: '{if(!LINES[$1]) { LINES[$1]=1; print $0; } }' $SPELL_STATUS > $TMP_DIR/packages.3
492 message "\tRemoving bad entries"
493 mv $TMP_DIR/packages.3 $SPELL_STATUS
494 fi
495
496 unlock_file $SPELL_STATUS
497 message "Done fixing packages."
498 }
499
500 #-----------------------------
501 ##
502 ## Checks and fixes a list of spells. If no spells
503 ## are specified, then all installed spells are checked.
504 ## If the first arg is 'nofix', then no fixing will be done
505 ## Just error detection
506 ##
507 ## @Args [nofix] [spell [spell [...]]]
508 ## @Returns May return non zero if there was a problem.
509 ##
510 #-----------------------------
511 function cleanse_fix()
512 {
513
514 local FIX=$1
515 shift 1
516 local SPELL
517 local SPELLS=$*
518
519 # find the binary executable make, we cant call it directly since make may
520 # be a function in this context
521 local REAL_MAKE
522 find_make REAL_MAKE || return $?
523
524 # given a list of spells to check
525 if [ -z "$SPELLS" ]; then
526 # I think the behavior will be to not check held spells by default
527 # because they are held, but if they are explicitly specified check them
528 SPELLS=$(get_all_spells_with_status installed|tr '\n' ' ')
529 else
530 local tmp_spells
531 # ensure they are all installed
532 for SPELL in $SPELLS ; do
533 spell_ok $SPELL &&
534 tmp_spells="$tmp_spells $SPELL" ||
535 message "${SPELL_COLOR}$SPELL${DEFAULT_COLOR}" \
536 "is not installed or held, not fixing"
537 done
538 SPELLS=$tmp_spells
539 if [ -z "$SPELLS" ] ; then
540 message "${PROBLEM_COLOR}no installed spells to fix${DEFAULT_COLOR}"
541 exit
542 fi
543 fi
544 # get the installed depends hash
545 compute_installed_depends depends_hash
546
547 # generate a makefile from the spells + depends hash
548 local MAKEFILE=$TMP_DIR/Makefile
549 (
550 for i in $( hash_get_table_fields depends_hash ) ; do
551 echo "$i : $(hash_get "depends_hash" "$i")"
552 echo -e "\t@echo $i"
553 echo
554 done
555 echo
556 echo %:
557 echo -e "\t@echo -n"
558 echo
559 echo "all : $SPELLS"
560 echo
561 echo ".PHONY : all $SPELLS"
562 ) > $MAKEFILE
563
564 # load our ldd hash with the spells we know we'll care about
565 # but no more then necessary
566 cleanse_fix_init_ldd_check $($REAL_MAKE -s -f $MAKEFILE all 2>/dev/null)
567
568 local PASSED FIXED HOPELESS TOTALNR CURNR unused
569
570 CURNR=0
571
572 if [ $FIX == quick ] ; then
573 TOTALNR=0 #counting them like this doesn't seem to take substantial time
574 for unused in $SPELLS ; do let TOTALNR++ ; done
575 for SPELL in $SPELLS ; do
576 let CURNR++
577 if cleanse_fix_run_checks $SPELL $CURNR $TOTALNR; then
578 PASSED="${PASSED}${SPELL}"$'\n'
579 else
580 message "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}" \
581 "is broken${DEFAULT_COLOR}\n"
582 HOPELESS="${HOPELESS}${SPELL}"$'\n'
583 fi
584 done
585 else
586 # non-quick mode implies we check from the bottom up, so get all
587 # the spells..
588 TOTALNR=$($REAL_MAKE -s -f $MAKEFILE all 2>/dev/null|wc -l)
589 cleanse_fix_depends all
590 fi
591
592 if [[ $PASSED ]] ; then
593 message "${MESSAGE_COLOR}The following spells passed the" \
594 "check:${SPELL_COLOR}"
595 echo "$PASSED"|sort -u|column
596 message "${DEFAULT_COLOR}"
597 fi
598 if [[ $FIXED ]] ; then
599 message "${MESSAGE_COLOR}The following spells were broken and" \
600 "fixed:${QUERY_COLOR}"
601 echo "$FIXED"|sort -u|column
602 message "${DEFAULT_COLOR}"
603 fi
604 if [[ $HOPELESS ]] ; then
605 if [[ "$FIX" == quick ]]; then
606 message "${MESSAGE_COLOR}The following spells were broken:" \
607 "${PROBLEM_COLOR}"
608 else
609 message "${MESSAGE_COLOR}The following spells were broken and" \
610 "could not be fixed:${PROBLEM_COLOR}"
611 fi
612 echo "$HOPELESS"|sort -u|column
613 message "${DEFAULT_COLOR}"
614 return 1
615 fi
616 return 0
617 }
618
619 function cleanse_fix_depends() {
620 local SPELL=$1
621 # run the makefile to get the proper order
622 local FIX_ORDER=$($REAL_MAKE -s -f $MAKEFILE $1 2>/dev/null| grep -v "^$SPELL\$")
623 #for each spell
624 for SPELL in ${FIX_ORDER}; do
625 # check it
626 let CURNR++
627 if cleanse_fix_run_checks $SPELL $CURNR $TOTALNR; then
628 PASSED="${PASSED}${SPELL}"$'\n'
629 else
630 if [ $FIX == fix ] ; then
631 # if its broken try to fix it
632 cleanse_fix_spell $SPELL
633 else
634 message "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}" \
635 "is broken${DEFAULT_COLOR} and not being fixed\n"
636 HOPELESS="${HOPELESS}${SPELL}"$'\n'
637 fi
638 fi
639 done
640 }
641
642 #----------------------------
643 ##
644 ## cast a spell and recheck it to see if its actually fixed
645 ## we assume in this case that all its dependents are fixed/hopeless
646 ##
647 ## if the spell cannot be fixed we complain and append it to the HOPELESS list
648 ##
649 ## @param Spell to check
650 ## @return 0 if spell is fixed 1 if not
651 ##
652 #----------------------------
653 function cleanse_fix_spell() {
654 local SPELL=$1
655 if spell_held $SPELL ; then
656 HOPELESS="${HOPELESS}${SPELL}"$'\n'
657 message "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}" \
658 "is broken broken, but also held, not fixing${DEFAULT_COLOR}"
659 return 1
660 fi
661
662 cast -c $SPELL &&
663 cleanse_fix_run_checks $SPELL &&
664 FIXED="${FIXED}${SPELL}"$'\n' &&
665 return 0
666
667 # if we get here the spell is deemed hopeless
668
669 HOPELESS="${HOPELESS}${SPELL}"$'\n'
670 message "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}" \
671 "is hopelessly broken and we can't fix it${DEFAULT_COLOR}"
672 return 1
673 }
674
675 #--------------------------
676 ##
677 ## Run the checks on a spell.
678 ## @args Spell [check nr,total nr]
679 ## @return Should return 0 if all the checks completed
680 ##
681 #--------------------------
682 function cleanse_fix_run_checks() {
683 local SPELL=$1
684 if [ $# -eq 1 ] ;then
685 set_term_title "Checking $SPELL"
686 message "Checking ${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}"
687 else
688 set_term_title "Checking $SPELL ($2 of $3)"
689 message "Checking ${SPELL_COLOR}${SPELL}${DEFAULT_COLOR} ($2 of $3)"
690 fi
691 if ! spell_ok $SPELL; then
692 message "${SPELL_COLOR}${SPELL}${MESSAGE_COLOR_COLOR}" \
693 "was dispelled in the meanwhile, skipping check!${DEFAULT_COLOR}"
694 return 0
695 fi
696 local VERSION=$(private_installed_version $SPELL)
697
698 cleanse_fix_find_check $SPELL $VERSION &&
699 cleanse_fix_ldd_check $SPELL $VERSION &&
700 cleanse_fix_sym_check $SPELL $VERSION &&
701 cleanse_fix_md5sum_check $SPELL $VERSION
702
703 }
704
705
706 #-----------------------
707 ##
708 ## Check that all files installed still exist.
709 ## @FIXME The skipped directories should be made configurable somewhere
710 ## @return 0 The files were all in existance
711 ## @return 1 At least one file was found missing
712 ##
713 #-----------------------
714 function cleanse_fix_find_check() {
715 if [ "$FIND_CHECK" == "off" ] ; then
716 return 0
717 fi
718
719 local SPELL=$1
720 local I_LOG="$INSTALL_LOGS/$SPELL-$2"
721 local BADLIST=""
722 local LINE
723
724 message "\tFile test"
725
726 if ! [ -f $I_LOG ] ; then
727 message "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}" \
728 "is missing an install log.${DEFAULT_COLOR}"
729 return 1;
730 fi
731
732 local size=$(wc -l < $I_LOG)
733 local count=0
734
735 local TMP_I_LOG=$TMP_DIR/${SPELL}/install.log
736 mkdir -p $TMP_DIR/${SPELL}
737 rm -f $TMP_I_LOG
738 log_adjuster $I_LOG /dev/stdout log filterable |
739 filter_volatiles |
740 log_adjuster /dev/stdin $TMP_I_LOG filterable root
741
742 while read LINE ; do
743 progress_bar $count $size 50
744 if [ ! -e "$LINE" ]; then
745 BADLIST="${BADLIST}${LINE}"$'\n'
746 fi
747 let count++
748 done < $TMP_I_LOG
749 rm -f $TMP_I_LOG
750 clear_line
751
752 if [[ $BADLIST ]] ; then
753 message "${PROBLEM_COLOR}The following files from" \
754 "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}" \
755 "${PROBLEM_COLOR}don't exist: "$'\n'"${DEFAULT_COLOR}"
756 message "$BADLIST"
757
758 return 1
759 fi
760
761 return 0
762
763 }
764
765 #-----------------------
766 ##
767 ## Initialize the ldd check, builds up a hash table in ldd_hash of
768 ## what library directories a spell has libraries in, this is because
769 ## some spells have their own private libraries and arent happy if
770 ## they dont know where they are
771 ##
772 #-----------------------
773 function cleanse_fix_init_ldd_check() {
774 if [ "$LDD_CHECK" == "off" ] ; then
775 return 0
776 fi
777 local SPELL
778 local I_LOG
779 local size count
780 let size=0
781 let count=0
782 message "Doing initialization for the ldd check"
783 for SPELL in $* ; do
784 let size++
785 done
786 for SPELL in $* ; do
787 I_LOG=$INSTALL_LOGS/$SPELL-$(private_installed_version $SPELL)
788 hash_put ldd_hash $SPELL "$(grep '\.so$' $I_LOG|get_dirnames|sort -u|tr '\n' :)"
789 progress_bar $count $size 50
790 let count++
791 done
792 clear_line
793 }
794
795 #-----------------------
796 ##
797 ## Check that all libraries the spell binaries need exist
798 ## @return 0 The libraries have their requirements met
799 ## @return 1 At least one library isnt happy
800 ##
801 #-----------------------
802 function cleanse_fix_ldd_check() { (
803 if [ "$LDD_CHECK" == "off" ] ; then
804 return 0
805 fi
806 message "\tLibrary test"
807
808 local SPELL=$1
809 local I_LOG=$INSTALL_LOGS/$SPELL-$2
810 local BADLIST=""
811 local LINE
812
813 if ! [ -f $I_LOG ] ; then
814 message "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}" \
815 "is missing an install log.${DEFAULT_COLOR}"
816 return 1;
817 fi
818
819 local tmp
820 for each in $($REAL_MAKE -s -f $MAKEFILE $1 2>/dev/null); do
821 hash_get_ref ldd_hash $each tmp
822 [[ $tmp ]] && LD_LIBRARY_PATH="$tmp:${LD_LIBRARY_PATH}"
823 done
824
825 local TMP_I_LOG=$TMP_DIR/${SPELL}/install.log
826 mkdir -p $TMP_DIR/${SPELL}
827 log_adjuster $I_LOG /dev/stdout filterable |
828 filter_volatiles |
829 log_adjuster /dev/stdin $TMP_I_LOG filterable root
830
831 local count size
832 let count=0
833 local size=$(wc -l < $TMP_I_LOG)
834 export LD_LIBRARY_PATH
835 while read LINE; do
836
837 progress_bar $count $size 50
838 let count++
839 if [ -f "$LINE" ] &&
840 file -b "$LINE" |
841 grep -q "ELF" &&
842 ldd "$LINE" 2>&1 |
843 grep -q "not found"
844 then
845 BADLIST="${BADLIST}${LINE}"$'\n'
846 fi
847 done < $TMP_I_LOG
848 rm -f $TMP_I_LOG
849 clear_line
850
851 if [[ $BADLIST ]] ; then
852 message "${PROBLEM_COLOR}The following files from" \
853 "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}" \
854 "${PROBLEM_COLOR}have missing library dependencies:" \
855 "${DEFAULT_COLOR}"
856 echo "$BADLIST"|while read LINE ; do
857 echo $LINE
858 ldd $LINE 2>&1|grep "not found"
859 done
860 return 1
861 fi
862
863 return 0
864
865 ) }
866
867 #-----------------------
868 ##
869 ## Check that all symlink targets exist
870 ## @return 0 The symlinks point to real files
871 ## @return 1 At least one symlink doesnt have its target
872 ##
873 #-----------------------
874 function cleanse_fix_sym_check() {
875 if [ "$SYM_CHECK" == "off" ] ; then
876 return 0
877 fi
878 local SPELL=$1
879 local I_LOG="$INSTALL_LOGS/$SPELL-$2"
880 local BADLIST=""
881 local LINE
882
883 message "\tSymlink test"
884
885 if ! [ -f $I_LOG ] ; then
886 message "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}" \
887 "is missing an install log.${DEFAULT_COLOR}"
888 return 1;
889 fi
890
891 local TMP_I_LOG=$TMP_DIR/${SPELL}/install.log
892 mkdir -p $TMP_DIR/${SPELL}
893 rm -f $TMP_I_LOG
894 log_adjuster $I_LOG /dev/stdout log filterable |
895 filter_volatiles |
896 log_adjuster /dev/stdin $TMP_I_LOG filterable root
897
898 local size=$(wc -l < $TMP_I_LOG)
899 local count=0
900
901 while read LINE ; do
902 progress_bar $count $size 50
903 # check that it is a symlink first, then if it is broken
904 # readlink -e requires all path elements to exist
905 if [[ -h $LINE ]] && ! readlink -q -e "$LINE" > /dev/null; then
906 BADLIST="${BADLIST}${LINE}"$'\n'
907 fi
908 let count++
909 done < $TMP_I_LOG
910 rm -f $TMP_I_LOG
911 clear_line
912
913 if [[ $BADLIST ]] ; then
914 message "${PROBLEM_COLOR}The following symlinks from" \
915 "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}" \
916 "${PROBLEM_COLOR}don't link to files it installed:" \
917 "${DEFAULT_COLOR}"
918 message "$BADLIST"
919
920 return 1
921 fi
922
923 return 0
924 }
925
926 #-----------------------
927 ##
928 ## Verify the md5sum of all the files
929 ## @return 0 The md5sum of a file is incorrect
930 ## @return 1 At least one md5sum is wrong
931 ##
932 #-----------------------
933 function cleanse_fix_md5sum_check() {
934
935 if [ "$MD5SUM_CHECK" == "off" ] ; then
936 return 0
937 fi
938 local SPELL=$1
939 local MD5_LOG="$MD5SUM_LOGS/$SPELL-$2"
940 local BADLIST
941 local TMP_MD5_LOG=$TMP_DIR/cleanse.md5sum.tmp.$SPELL.$$
942 local BROKEN1=$TMP_DIR/cleanse.md5sum.broken1.$SPELL.$$
943 local BROKEN2=$TMP_DIR/cleanse.md5sum.broken2.$SPELL.$$
944 local A=$'\a' # magic sed seperator, assume files dont have bells in the name
945
946 # Ignore standard paths we know will change, the install log, file log,
947 # compile log, and score files that live in /var/games (bug 7500)
948
949 message -n "\tChecking md5sums."
950
951 if ! [ -f $MD5_LOG ] ; then
952 message "${SPELL_COLOR}${SPELL} " \
953 "${PROBLEM_COLOR}is missing an md5 log.${DEFAULT_COLOR}"
954 return 1;
955 fi
956 touch $BROKEN1 $BROKEN2
957
958 message -n '.'
959
960 log_adjuster $MD5_LOG $TMP_MD5_LOG log root md5_log_filter
961
962
963 # for some reason stuffing the output of this in a variable loses the
964 # newlines and thus files with spaces in them get lost
965 LANG=C md5sum --check $TMP_MD5_LOG 2>/dev/null | grep ': FAILED$' |
966 sed "s${A}: FAILED\$${A}${A}" > $BROKEN1
967 # cant use awk above with : as a seperator due to files with :'s in their name
968
969 # filters dont work on md5sum logs, filter after md5sum is done
970 # this is lame but oh well, this function is ugly enough as it is
971 filter_volatiles < $BROKEN1 > $BROKEN2
972
973 message -n '.'
974
975 # the files with bad md5s may be owned by another spell (either legitimatly
976 # or by spell writer oversight) if so remove the entry from
977 # the spell's install and md5sum logs, and enter it in the possessed
978 # log. Someday dispel et. al. will use this for stuff, for now
979 # its for informational purposes
980
981 # we have to dump out to a file because I cant use message
982 # inside $( ), and variables are stupid about spaces
983 local FOUND_IN REAL_MD5 possessed INSTALL_LOG
984 rm $BROKEN1
985 while read LINE; do
986 message -n '.'
987 [[ ! $LINE ]] && continue
988 test -h $LINE && continue
989 REAL_MD5=$(md5sum $LINE)
990 # find the file in some log somewhere...
991 FOUND_IN=$(echo $MD5SUM_LOGS/* | xargs grep -l "^$REAL_MD5\$")
992
993 # remove anything from a non-current version of the spell
994 # this is ugly but the only reasonable way I know of to do this...
995 FOUND_IN=$(for each in $FOUND_IN; do
996 # make the assumption that spell versions dont contain dashes...
997 # this actually isnt a very good assumption but works enough
998 # of the time
999 each_spell=$(smgl_basename $each|awk -F- '{for (i=1;i<NF-1;i++) {printf "%s-",$i}printf "%s\n",$i}')
1000 each_version=$(private_installed_version $each_spell)
1001 [[ $each_version ]] &&
1002 [[ $MD5SUM_LOGS/${each_spell}-${each_version} == $each ]] &&
1003 echo $each
1004 done)
1005
1006 if [[ ! $FOUND_IN ]]; then
1007 echo $LINE >> $BROKEN1
1008 continue
1009 fi
1010
1011 message "\n${PROBLEM_COLOR}Incorrect md5sum for" \
1012 "${FILE_COLOR}$LINE${DEFAULT_COLOR}"
1013 message "but matches in:\n$FOUND_IN"
1014
1015 if [ $FIX == fix ] || [ $FIX == quick ]; then
1016 message "Removing from $SPELL's install and md5sum logs."
1017
1018 INSTALL_LOG=$INSTALL_LOGS/$SPELL-$2
1019 # there is a slight bug with all these transactions due to
1020 # the fact that we have no stdin, if a transaction fails the user
1021 # is prompted, but since we're in this funny loop, it'll read from
1022 # the wrong place a future improvement would be
1023 # to use iterate or some other method that is not naive to
1024 # filenames with spaces in them
1025 lock_start_transaction "$INSTALL_LOG" tfile
1026 sed "s${A}^$LINE\$${A}${A}" $INSTALL_LOG|tr -s '\n' > $tfile
1027 lock_commit_transaction $INSTALL_LOG
1028 message -n '.'
1029
1030 lock_start_transaction "$MD5_LOG" tfile
1031 sed "s$A.* $LINE\$${A}${A}" $MD5_LOG|tr -s '\n' > $tfile
1032 lock_commit_transaction $MD5_LOG
1033 message -n '.'
1034
1035 test -d $POSSESSED_LOGS || mkdir -p $POSSESSED_LOGS
1036
1037 possessed=$POSSESSED_LOGS/$SPELL-$2
1038 lock_start_transaction "$possessed" tfile
1039 echo $LINE >> $tfile
1040 # remove duplicates
1041 cp $tfile $TMP_DIR/$SPELL.possessed.$$
1042 sort -u $TMP_DIR/$SPELL.possessed.$$ > $tfile
1043 rm $TMP_DIR/$SPELL.possessed.$$
1044 lock_commit_transaction $possessed
1045
1046 message -n '.'
1047 fi
1048 done < $BROKEN2
1049
1050 message -n '.'
1051
1052 if test -s $BROKEN1 ; then
1053 BADLIST=$(cat $BROKEN1)
1054 else
1055 BADLIST=""
1056 fi
1057 rm $BROKEN1 $BROKEN2 &>/dev/null
1058
1059 message ''
1060
1061
1062 if [[ $BADLIST ]] ; then
1063 message "${PROBLEM_COLOR}The following files have been modified from" \
1064 "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}:"
1065 message "$BADLIST"
1066 return 1
1067 fi
1068 return 0
1069 }
1070
1071 source /etc/sorcery/config
1072
1073 if [ $UID -eq 0 ] ; then
1074
1075 # Make sure root is running this
1076 if [ $# -eq 0 ] ; then
1077 # IF there are no args, assume the full treatment is desired
1078 exec cleanse --packages --delint --logs --prune doit --tablet --sweep --fix
1079 fi
1080
1081 mk_tmp_dirs backup /tmp/cleanse
1082 BACKUPDIR=$TMP_DIR
1083 unset TMP_DIR
1084 mk_tmp_dirs cleanse
1085 if [ ! -d $TMP_DIR ] || [ ! -d $BACKUPDIR ] ; then
1086 message "Unable to make tmp dirs..."
1087 exit 1
1088 fi
1089 args "$@"
1090 rc=$?
1091 cleanup_tmp_dir $TMP_DIR
1092 message "Removed information is backed up to $BACKUPDIR."
1093 exit $rc
1094 else
1095 if [[ $1 == --help ]] ; then
1096 usage
1097 exit 0
1098 fi
1099 # validate the parameters before su-ing, since we may still drop out
1100 verify_parameters "$@"
1101
1102 message "You must be root to run this command."
1103 message "Switching to root user..."
1104 PARAMS=$(consolidate_params "$@")
1105 exec su -c "cleanse $PARAMS" root
1106 fi