/usr/sbin/dispel
1 #!/bin/bash
2 #---------------------------------------------------------------------
3 ## @Synopsis Dispel is the spell removal utility. It can be called by the user or by intone.
4 ##
5 ## @Copyright Original version Copyright 2001 by Kyle Sallee
6 ## @Copyright Additions/corrections Copyright 2002 by the Source Mage Team
7 ##
8 ## Dispel is the spell removal utility. It can be called by the user or by intone.
9 ##
10 #---------------------------------------------------------------------
11
12
13 help">help">help() {
14
15 cat << EOF
16
17 Dispel uninstalls single or multiple spells.
18
19 Example: dispel hdparm vim emacs
20 Usage: dispel [parameters] [spells]
21
22 Optional Parameters:
23
24 -e | --exile spell Removes spells and blocks them
25 from being automatically reinstalled.
26
27 -d | --downgrade spell [version]
28 Removes the spell and reinstalls
29 the selected version from cache
30
31 --notriggers Disables triggers for this dispel
32 --nosustain Turns off dispel protection for vital spells
33 (you usually don't want to do this)
34 --noreap Dispels spells but doesn't remove its files
35 --no-reap-depends Dont remove dependency information, dont use this
36 unless you know exactly what you are doing, and
37 dont complain if it breaks your box.
38
39 --total-dispel Remove everything including config files even
40 if they were modified.
41 --noqueue Don't try to remove the spell from the install queue
42
43 The following parameters effect dependency following, if they are not
44 specified the default action is to do no dependency following. For
45 a more in-depth explaination refer to the manual
46
47 The following 5 parameters accept one of the following
48 4 parameters:
49 ignore, ask-yes, ask-no, always
50
51 --orphan Default action for dispelling a newly orphaned child
52 spell.
53 --non-orphan Default action for dispelling a dependee of
54 another spell, that is not an orphan (doing this
55 will break the spells that still depend on it).
56
57 --child Default action for dispelling a child spell that
58 is either an orphan or non-orphan.
59
60 --recast-parent If a spell has a child removed from under it, the
61 spell is considered broken, if all of the removed
62 children are optional, the spell may be recast
63 without those depends. This option controls the
64 default for this action.
65 --dispel-parent Default action for dispelling broken parents,
66 the user is asked for recasting if possible first.
67
68 --user-deps Use defaults for dependency following from the
69 --user-child-deps sorcery menu for child and/or parent dependency
70 --user-parent-deps following.
71
72 EOF
73
74 exit 1
75
76 }
77
78 #-----
79 ## Downgrade a spell to a previously installed version
80 ## @param Spell
81 ## @param Previous version
82 #-----
83 function downgrade() {
84 if [[ $SUSTAIN == off ]]; then
85 resurrect --nosustain $1 -v "$2"
86 else
87 resurrect $1 -v "$2"
88 fi
89 DISPEL_EXIT_STATUS=$?
90 }
91
92 #-----
93 ## Parse the dispel script's parameters
94 ## @Args Parameters
95 #-----
96 function process_parameters">process_parameters">process_parameters() {
97 while [ -n "$1" ]; do
98 if echo "" $1 | grep -q "^ -"; then
99 case $1 in
100 -e|--exile)
101 if [[ -z $2 ]]; then
102 message "${PROBLEM_COLOR}Missing parameters!${DEFAULT_COLOR}"
103 help">help">help
104 fi
105 EXILE="yes"; shift 1
106 ;;
107 -d|--downgrade)
108 if [[ -z $2 ]]; then
109 message "${PROBLEM_COLOR}Missing parameters!${DEFAULT_COLOR}"
110 help">help">help
111 fi
112 DOWNGRADE_SPELL=$2
113 if [[ ${3:0:1} != - ]]; then
114 DOWNGRADE_VERSION=$3
115 shift
116 fi
117 shift 2
118 ;;
119 --noreap) REAP="off"; shift 1 ;;
120 --no-reap-depends) NO_REAP_DEPENDS="on"; shift 1 ;;
121 --nosustain) SUSTAIN="off"; shift 1 ;;
122 --notriggers) TRIGGER="off"; shift 1 ;;
123 --child) validate_param $2 ORPHAN_DEFAULT
124 validate_param $2 NONORPHAN_DEFAULT
125 shift ;;
126 --orphan) validate_param $2 ORPHAN_DEFAULT
127 shift ;;
128 --non-orphan) validate_param $2 NONORPHAN_DEFAULT
129 shift ;;
130 --user-deps) validate_param "$ORPHAN_MENU_DEFAULT" ORPHAN_DEFAULT
131 validate_param "$NONORPHAN_MENU_DEFAULT" NONORPHAN_DEFAULT
132 validate_param "$RECAST_PARENT_MENU_DEFAULT" \
133 RECAST_PARENT_DEFAULT
134 validate_param "$DISPEL_PARENT_MENU_DEFAULT" \
135 DISPEL_PARENT_DEFAULT
136 shift 1 ;;
137 --user-child-deps) validate_param "$ORPHAN_MENU_DEFAULT" ORPHAN_DEFAULT
138 validate_param "$NONORPHAN_MENU_DEFAULT" NONORPHAN_DEFAULT
139 shift ;;
140 --recast-parent) validate_param $2 RECAST_PARENT_DEFAULT
141 shift ;;
142 --dispel-parent) validate_param $2 DISPEL_PARENT_DEFAULT
143 shift ;;
144 --user-parent-deps) validate_param "$RECAST_PARENT_MENU_DEFAULT" \
145 RECAST_PARENT_DEFAULT
146 validate_param "$DISPEL_PARENT_MENU_DEFAULT" \
147 DISPEL_PARENT_DEFAULT
148 shift ;;
149 --debug-dispel) DEBUG_DISPEL=yes ; shift 1 ;;
150 --total-dispel) TOTAL_DISPEL=yes ; shift 1 ;;
151 --noqueue) DEQUEUE=off; shift 1 ;;
152 *) help">help">help ;;
153 esac
154 else
155 shift
156 fi
157 done
158 }
159
160 function validate_param() {
161 case $1 in
162 ask-yes|ask-no|ignore|always) eval "$2=\"$1\"" ;;
163 *) message "\"$1\" is not a valid option, use ask-yes, ask-no," \
164 "ignore or always"
165 help">help">help ;;
166 esac
167 }
168
169 #-----
170 ## Remove the parsed parameters
171 ## This is a silly way to do it
172 ## @Args Parameters
173 #-----
174 function strip_parameters">strip_parameters() {
175 while [ -n "$1" ]; do
176 if echo "" $1 | grep -q "^ -"; then
177 case $1 in
178 -e|--exile) shift 1 ;;
179 -d|--downgrade) shift 2; [[ ${3:0:1} == - ]] || shift ;;
180 --noreap) shift 1 ;;
181 --no-reap-depends) shift 1 ;;
182 --nosustain) shift 1 ;;
183 --notriggers) shift 1 ;;
184 --child) shift 2 ;;
185 --orphan) shift 2 ;;
186 --non-orphan) shift 2 ;;
187 --user-child-deps) shift 1 ;;
188 --recast-parent) shift 2 ;;
189 --dispel-parent) shift 2 ;;
190 --user-parent-deps) shift 1 ;;
191 --debug-dispel) shift 1 ;;
192 --total-dispel) shift 1 ;;
193 --noqueue) shift 1 ;;
194 *) shift 1 ;;
195 esac
196 else
197 echo -n "$1 "
198 shift
199 fi
200 done
201 }
202
203 #---------------------------------------------------------------------
204 ## Display what dependencies were removed
205 #---------------------------------------------------------------------
206 function dispel_parents_list_borkers() {
207 local parent=$1
208 local optional_borkers=$2
209 local runtime_borkers=$3
210 local suggested_borkers=$4
211 if [[ "$optional_borkers" ]] ; then
212 message "${SPELL_COLOR}${parent}${DEFAULT_COLOR}${MESSAGE_COLOR}" \
213 "had the following optional dependencies removed:${DEFAULT_COLOR}"
214 message "${SPELL_COLOR}"
215 echo $optional_borkers | col
216 message "${DEFAULT_COLOR}"
217 fi
218 if [[ "$runtime_borkers" ]] ; then
219 message "${SPELL_COLOR}${parent}${DEFAULT_COLOR}${MESSAGE_COLOR}" \
220 "had the following runtime dependencies removed:${DEFAULT_COLOR}"
221 message "${SPELL_COLOR}"
222 echo $runtime_borkers | col
223 message "${DEFAULT_COLOR}"
224 fi
225 if [[ "$suggested_borkers" ]] ; then
226 message "${SPELL_COLOR}${parent}${DEFAULT_COLOR}${MESSAGE_COLOR}" \
227 "had the following suggested dependencies removed:${DEFAULT_COLOR}"
228 message "${SPELL_COLOR}"
229 echo $suggested_borkers | col
230 message "${DEFAULT_COLOR}"
231 fi
232 }
233
234 #---------------------------------------------------------------------
235 ## @param spell to dispel
236 ##
237 ## Frontend to do common things for dispelling (keep a note of what
238 ## has already been dispelled.
239 #---------------------------------------------------------------------
240 function dispel_spell_wrapper() {
241 dispel_spell $1 &&
242 hash_put ALL_DISPELS $1 yes
243 }
244
245 #---------------------------------------------------------------------
246 ## @param list of spells to remove the children of
247 ## @param variable name to store list of newly dispelled spells
248 ##
249 ## Move one step down the depends tree for the given spells, return
250 ## list of spells removed.
251 ## The caller is expected to call this in a loop until nothing is returned.
252 #---------------------------------------------------------------------
253 function dispel_children() {
254 local spells=$1
255
256 local our_children
257 for spell in $spells; do
258 # this assumes list_add remains idempotent, which it is
259 local dependencies
260 hash_get_ref down_deps $spell dependencies
261 list_add our_children $dependencies
262 done
263 debug "children of $1 -> $our_children"
264
265 local is_orphan is_gone
266 local orphan_list non_orphan_list
267 for child in $our_children; do
268 hash_get_ref ALL_DISPELS $child is_gone
269 if [[ $is_gone == yes ]]; then
270 debug "someone depends on $child but it was already removed"
271 continue
272 fi
273 # determine if this child is becomming an orphan or not
274 is_orphan="yes"
275 local parents
276 hash_get_ref up_deps $child parents
277 for parent in $parents; do
278 if ! list_find "$spells" "$parent"; then
279 is_orphan=no
280 break
281 fi
282 done
283 if [[ "$is_orphan" == yes ]] ; then
284 list_add orphan_list $child
285 else
286 list_add non_orphan_list $child
287 fi
288 done
289
290 local removed_children
291 for child in $orphan_list; do
292 debug dispel "looking at orphan $child:"
293 #debug dispel "parents: $(hash_get up_deps $child)"
294 #debug "children: $(hash_get down_deps $child)"
295 if dispel_child_query "$child" "is an orphan" \
296 "Would you like to dispel it" \
297 "${ORPHAN_DEFAULT}" ; then
298 dispel_spell_wrapper $child &&
299 list_add removed_children $child
300 fi
301 done
302 for child in $non_orphan_list; do
303 debug "looking at non-orphan $child:"
304 #debug "parents: $(hash_get up_deps $child)"
305 #debug "children: $(hash_get down_deps $child)"
306 if dispel_child_query "$child" "is not an orphan" \
307 "Would you like to dispel it" \
308 "${NONORPHAN_DEFAULT}" ; then
309 dispel_spell_wrapper $child &&
310 list_add removed_children $child
311 fi
312 done
313
314 eval "$2=\"$removed_children\""
315 }
316
317 #---------------------------------------------------------------------
318 ## Common code for querying the user about children to remove
319 ## Prints a message and asks a query, if ask-yes or ask-no is the
320 ## provided action.
321 ##
322 ## @param Spell in question
323 ## @param Message a short message about the state of the spell
324 ## @param Question to ask the user
325 ## @param quad-option (always, ask-yes, ask-no, ignore)
326 #---------------------------------------------------------------------
327 function dispel_child_query() {
328 local SPELL=$1
329 local MESSAGE=$2
330 local QUERY=$3
331 local DEFAULT=$4
332 local query_default
333 if [[ ! $DEFAULT ]] ; then
334 return 1
335 fi
336 if [[ $DEFAULT == ignore ]] ; then
337 return 1
338 fi
339 if [[ $DEFAULT == always ]] ; then
340 return 0
341 fi
342 if [[ $DEFAULT == ask-yes ]] ; then
343 query_default=y
344 fi
345 if [[ $DEFAULT == ask-no ]] ; then
346 query_default=n
347 fi
348 message "${DEFAULT_COLOR}${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}" \
349 "${MESSAGE_COLOR}${MESSAGE}.${DEFAULT_COLOR}"
350 query "$QUERY" $query_default
351 }
352
353 #---------------------------------------------------------------------
354 ## Common code for querying the user about parents spells.
355 ## Asks a query, if ask-yes or ask-no is the provided action.
356 ##
357 ## @param Question to ask the user
358 ## @param quad-option (always, ask-yes, ask-no, ignore)
359 #---------------------------------------------------------------------
360 function dispel_parent_query() {
361 local QUERY=$1
362 local DEFAULT=$2
363 local query_default
364 if [[ ! $DEFAULT ]] ; then
365 return 1
366 fi
367 if [[ $DEFAULT == ignore ]] ; then
368 return 1
369 fi
370 if [[ $DEFAULT == always ]] ; then
371 return 0
372 fi
373 if [[ $DEFAULT == ask-yes ]] ; then
374 query_default=y
375 fi
376 if [[ $DEFAULT == ask-no ]] ; then
377 query_default=n
378 fi
379 query "$QUERY" $query_default
380 }
381
382
383 #---------------------------------------------------------------------
384 ## @param Spell in question
385 ## dis-associate with all its children (fix upward dep tree)
386 ## remove spell from the downward tree
387 #---------------------------------------------------------------------
388 function remove_from_dep_trees() {
389 debug dispel "$FUNCNAME $@"
390 local spell=$1
391 local down_dep_hash=$2
392 local up_dep_hash=$3
393 local foo
394 local children
395 hash_get_ref $down_dep_hash $spell children
396 for child in $children; do
397 hash_get_ref $up_dep_hash $child foo
398 list_remove foo $spell
399 hash_put $up_dep_hash $child "$foo"
400 done
401 hash_unset $down_dep_hash $spell
402 }
403
404 #---------------------------------------------------------------------
405 ## @param Spell to find borked parents for
406 ## @param Name of upward dependency hash
407 ## @param Name of hash to accumulate borked parents in
408 ##
409 ## Find the spells that the following spell borks.
410 ##
411 ## Bork is a technical term which in this context refers to the situation
412 ## where a spell is removed and spells that depend on it are broken. For
413 ## example xorg borks blackbox.
414 #---------------------------------------------------------------------
415 function get_borked_parents() {
416 local spell=$1
417 local up_dep_hash=$2
418 local parent parents
419 # get the spells that depend on us
420 hash_get_ref $up_dep_hash $spell parents
421 for parent in $parents; do
422 debug dispel "hash_append $3 $parent $spell"
423 hash_append $3 $parent $spell
424 done
425 }
426
427 #---------------------------------------------------------------------
428 ## Main dependency following engine. This will follow the dependency
429 ## tree downwards for all the provided spells, then repair one level of
430 ## borked parents. The caller is expected to call this repeatedly until
431 ## there are no borked parents to remove.
432 ##
433 ## @param List of spells to remove on this iteration.
434 #---------------------------------------------------------------------
435 function dispel_depends_engine() {
436
437 local CURR_SPELLS SPELL
438 for SPELL in $1; do
439 if dispel_spell_wrapper $SPELL ; then
440 list_add CURR_SPELLS $SPELL
441 else
442 DISPEL_EXIT_STATUS=${DISPEL_EXIT_STATUS:-$?}
443 fi
444 done
445
446
447 if [[ "$CHILD_DEPS" == yes ]] ; then
448 # go down the depends tree killing stuff
449 # each time dispel_children is called it appends the new children that
450 # can be dispelled
451 # dispel_children has its own policy of what will be picked, and may
452 # prompt the user for input
453 local new_children=$CURR_SPELLS
454 while [[ $new_children ]] ; do
455 curr_children=$new_children
456 unset new_children
457 debug dispel "in with $curr_children"
458 dispel_children "$curr_children" new_children
459 debug dispel "out with $new_children"
460 list_add CURR_SPELLS $new_children
461 for SPELL in $curr_children; do
462 remove_from_dep_trees $SPELL down_deps up_deps
463 done
464 done
465 fi
466
467 if [[ "$PARENT_DEPS" == yes ]] ; then
468 # figure out what parents are borked and whats borked them
469 hash_reset BORKED_PARENTS
470 for SPELL in $CURR_SPELLS; do
471 # maps broken spells to the spells that borked them
472 get_borked_parents $SPELL up_deps BORKED_PARENTS
473 done
474
475 local NEW_CASTS=""
476 local NEW_DISPELS=""
477 local borkers
478 # for all borked spells either recast, dispel, or ignore
479 for parent in $(hash_get_table_fields BORKED_PARENTS) ; do
480 hash_get_ref BORKED_PARENTS $parent borkers
481 message "$parent is borked because of $borkers"
482 local recastable=yes
483
484 local non_optional_borkers suggested_borkers runtime_borkers type
485 # if any of the removed children are required depends of the current
486 # spell, the spell is not recastable
487 # note: this doesnt take into account the spell changing out from
488 # under us. Oh well.
489 lock_file $DEPENDS_STATUS
490 for child in $borkers; do
491 type=$(grep -m 1 "$parent:$child\(([^:]*)\)\?:on" $DEPENDS_STATUS|cut -f4 -d:)
492 if [[ "$type" == required ]]; then
493 recastable=no
494 list_add non_optional_borkers $child
495 elif [[ "$type" == "runtime" ]] ; then
496 [[ "$recastable" != no ]] && recastable=NA
497 list_add runtime_borkers $child
498 elif [[ "$type" == "suggest" ]] ; then
499 [[ "$recastable" != no ]] && recastable=NA
500 list_add suggested_borkers $child
501 else
502 list_add optional_borkers $child
503 fi
504 done
505 unlock_file $DEPENDS_STATUS
506 if [[ $recastable == yes ]] ; then
507 dispel_parents_list_borkers $parent "$optional_borkers" \
508 "$runtime_borkers" "$suggested_borkers"
509 if [[ $optional_borkers ]] && dispel_parent_query \
510 "Re-cast $parent without these optional depends?" \
511 "$RECAST_PARENT_DEFAULT" ; then
512 # fix up the abandoned depends data for impending recompile
513 # this is a bit of a hack but should work:
514 # remove any uncommitted data, store our new data in a new abandonded
515 # dir with borked optionals disabled, remove those deps
516 # from master file so that recasting will see them
517 depends_file=$ABANDONED_DEPENDS/$SPELL
518 rm -f $UNCOMMITTED_DEPENDS/$parent $depends_file
519 touch $depends_file
520 local depline
521 search_depends_status $DEPENDS_STATUS $parent|while read depline; do
522 explode "$depline" : deparray
523 # note: we know that all $borkers are optional from
524 # the check above
525 if list_find "$borkers" "${deparray[1]}"; then
526 deparray[2]=off
527 # remove this rule so cast will look elsewhere
528 # (the abandoned file), the strange regexp at the end
529 # is to match providers: xorg(X11-LIBS)
530 remove_depends_status $DEPENDS_STATUS $parent \
531 "${deparray[1]}\(([^:]*)\)\?"
532 fi
533 add_depends $depends_file "${deparray[@]}"
534 done
535
536 list_add NEW_CASTS $parent
537 elif dispel_parent_query "Dispel $parent?" \
538 "$DISPEL_PARENT_DEFAULT" ; then
539
540 hash_get_ref down_deps $parent foo
541 list_remove foo $borkers
542 hash_put down_deps $parent $foo
543
544 list_add NEW_DISPELS $parent
545 else
546 message "Please fix broken dependencies with cleanse --prune"
547 fi
548 else
549 dispel_parents_list_borkers $parent "$optional_borkers" \
550 "$runtime_borkers" "$suggested_borkers"
551 if dispel_parent_query "Dispel $parent?" \
552 "$DISPEL_PARENT_DEFAULT"; then
553
554 hash_get_ref down_deps $parent foo
555 list_remove foo $borkers
556 hash_put down_deps $parent $foo
557
558 list_add NEW_DISPELS $parent
559 else
560 message "Please fix broken dependencies with cleanse --prune"
561 fi
562 fi
563 done
564 if [[ $NEW_CASTS ]] ; then
565 debug dispel "cast -c $NEW_CASTS"
566 cast -c $NEW_CASTS
567 fi
568 fi
569 debug dispel "NEW_DISPELS: $NEW_DISPELS"
570 debug dispel "eval $2=\"$NEW_DISPELS\""
571 eval "$2=\"$NEW_DISPELS\""
572
573 return ${DISPEL_EXIT_STATUS:-0}
574 }
575
576 #---------------------------------------------------------------------
577 ## Main loop for dispel
578 ## @Args Parameters
579 #---------------------------------------------------------------------
580 function main">main">main">main">main() {
581
582 process_parameters">process_parameters">process_parameters "$@"
583 # handle downgrading specially, since we also want to pass some of the flags
584 # but can't rely on input being ordered correctly
585 # having this block here allows process_parameters">process_parameters">process_parameters to be used for input verification
586 if [[ -n $DOWNGRADE_SPELL ]]; then
587 downgrade $DOWNGRADE_SPELL $DOWNGRADE_VERSION
588 fi
589 SPELLS=`strip_parameters">strip_parameters "$@"`
590
591 if [[ $NONORPHAN_DEFAULT ]] ||
592 [[ $ORPHAN_DEFAULT ]] ; then
593 CHILD_DEPS=yes
594 DO_DEPENDS=yes
595 fi
596
597 if [[ $RECAST_PARENT_DEFAULT ]] ||
598 [[ $DISPEL_PARENT_DEFAULT ]] ; then
599 PARENT_DEPS=yes
600 DO_DEPENDS=yes
601 fi
602 if [[ $DO_DEPENDS ]] ; then
603 while [[ $SPELLS ]] ; do
604 debug dispel "recomputing depends tree"
605 compute_installed_depends down_deps all
606 compute_reverse_installed_depends up_deps all
607 dispel_depends_engine "$SPELLS" SPELLS
608 debug dispel "SPELLS $SPELLS"
609 done
610 else
611 for SPELL in $SPELLS; do
612 dispel_spell_wrapper $SPELL ||
613 DISPEL_EXIT_STATUS=${DISPEL_EXIT_STATUS:-$?}
614 done
615 fi
616 return $DISPEL_EXIT_STATUS
617 }
618
619 . /etc/sorcery/config
620
621 if [ $# == 0 ]; then help">help">help | $PAGER
622 elif [[ $1 == -h ]] || [[ $1 == --help ]] ; then help">help">help
623 elif [ "$UID" == 0 ]; then
624 mk_tmp_dirs dispel
625 main">main">main">main">main "$@"
626 rc=$?
627 cleanup_tmp_dir $TMP_DIR
628 exit $rc
629 else
630 # validate the parameters before su-ing, since we may still drop out
631 process_parameters">process_parameters">process_parameters "$@"
632
633 echo "Enter the root password, please." 1>&2
634 PARAMS=$(consolidate_params "$@")
635 exec su -c "dispel $PARAMS" root
636 fi
637
638
639 #---------------------------------------------------------------------
640 ##=back
641 ##
642 ##=head1 LICENSE
643 ##
644 ## This software is free software; you can redistribute it and/or modify
645 ## it under the terms of the GNU General Public License as published by
646 ## the Free Software Foundation; either version 2 of the License, or
647 ## (at your option) any later version.
648 ##
649 ## This software is distributed in the hope that it will be useful,
650 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
651 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
652 ## GNU General Public License for more details.
653 ##
654 ## You should have received a copy of the GNU General Public License
655 ## along with this software; if not, write to the Free Software
656 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
657 ##
658 #---------------------------------------------------------------------