#!/bin/bash ################################################## # Imports ################################################## stdinFD=16 stdoutFD=17 eval "exec $stdinFD<&0" eval "exec $stdoutFD<&1" . ScriptFunctions Import OptionParser Import Log Import File Import String Import Array Import GoboLinux Import Versions Import Hints Import MetadataInstallation ################################################## # Default Configuration ################################################## defaultMode='interactive' versionCheckingMethod='mtime' hintsEnabled='no' unset hintsAllowed updateOrder=( create dont_delete prompt_user auto_merge fail ) mergeChecker="${goboScripts}/../lib/ruby/site_ruby/1.8/gobo/MergeSucceeded.rb" mergeFuzz=1 Parse_Conf UpdateSettings.conf [ "$mergeChecker" = "disable" ] && unset mergeChecker ################################################## # Options ################################################## eval "${defaultMode}IsDefault=' (default)'" helpOnNoArguments=yes scriptDescription="Interactively update settings from defaults in Resources/Defaults/Settings" scriptCredits="(C)2005 by David Smith. Released under the GNU GPL. Maintained by Dan Charney and the GoboLinux team" scriptUsage="[] [] []" Add_Option_Boolean "d" "diffs" "When a file has been modified, show differences." Add_Option_Boolean "c" "check" "Only check whether current settings differ from the defaults." Add_Option_Boolean "r" "report" "Only report whether current settings and defaults differ." Add_Option_Boolean "l" "list" "List files that differ between current settings and defaults." Add_Option_Boolean "u" "update" "Update settings (default) Update Modes:" # this is an evil hack. --Dan Add_Option_Boolean "a" "auto" "Update automatically, without prompting the user at any point$autoIsDefault" Add_Option_Boolean "i" "interactive" "Update interactively, prompting the user before each change$interactiveIsDefault" Add_Option_Boolean "q" "quick" "Quickly update, prompting only when there are conflicts$quickIsDefault" Parse_Options "$@" if ! Boolean "list" && ! Boolean "check" && ! Boolean "report" then Set_Boolean "update" fi ################################################## # Initialization Code ################################################## # Determine the update mode updateMode='' Boolean "update" && updateMode="$defaultMode" Boolean "quick" && updateMode="quick" Boolean "auto" && updateMode="auto" Boolean "interactive" && updateMode="interactive" if [ $updateMode ]; then Set_Boolean "list" Set_Boolean "report" Set_Boolean "update" fi # Determine the directory and version of the program being updated appName="`GuessProgramCase $(Arg 1)`" pdir="${goboPrograms}/${appName}" if [ "$(Arg 2)" ] then version="$(Arg 2)" else version="Current" fi Is_Directory "$pdir" || Die "$pdir not found or is not a directory" Is_Directory "$pdir/$version" || Die "$pdir/$version not found" if Boolean "update" && ! Is_Writable "$pdir/Settings"; then Verify_Superuser fi # Try to guess the previously installed version of the program being updated, # if possible oldVersion="`Previous_Version "${appName}" $versionCheckingMethod`" [ "$?" = "0" ] || unset oldVersion EDITOR=${EDITOR:-vi} PAGER=${PAGER:-less} SKIP_ALL_MAGIC_NUMBER=123 InstructionSource='' ################################################## # Helper functions ################################################## function input_option() { echo -e "${colorNormal}${colorNormal}$*" >&$questionFD while true do read REPLY <&$stdinFD [ "$REPLY" != "" ] && break done REPLY="`Downcase "$REPLY"`" } function out() { # Echo to the user but without prepending the script name like LogNNN does echo -e "${colorNormal}${colorNormal}$@" >&$questionFD } function expand_dirs() { local difftype file while read difftype file; do if Is_Directory "$file" && Is_Nonempty_Directory "$file" || Quiet expr match "$file" '.*.svn' || Quiet expr match "$file" '.*CVS'; then find "$file" -type f | grep -v "/CVS|.svn/" |sed "s/^/$difftype /" else echo "$difftype $file" fi done } function list_diffs() { # List differing files in ./Settings and ./$version/Resources/ # Defaults/Settings [ "$1" = "-s" ] && echo if Is_Directory Settings && ! Is_Directory $version/Resources/Defaults/Settings; then find Settings -name '*CVS' -or -name '*.svn' -prune -o -print \ | sed 's,^,del: ,'\ | cat -n\ | sed 's/^ \{4\}//;s/ / /' elif ! Is_Directory Settings && Is_Directory $version/Resources/Defaults/Settings; then find $version/Resources/Defaults/Settings -name '*CVS' -or -name '*.svn' -prune -o -print \ | sed 's,^,new: ,'\ | cat -n\ | sed 's/^ \{4\}//;s/ / /' else # Differing files are presented as "#no new|del|mod " # 1 new: $version/Resources/Defaults/Settings/ # 2 del: Settings/ # 3 mod: Settings/ LANG=C LC_ALL=C diff -rq Settings $version/Resources/Defaults/Settings 2> /dev/null \ | sed ' s,Only in \('$version'[^:]*\): \(.*\),new: \1/\2, s,Only in \(Settings[^:]*\): \(.*\),del: \1/\2, s,Files \([^ ]*\) and.*differ,mod: \1,'\ | expand_dirs\ | grep -v '.svn/' \ | cat -n\ | sed "s/^ \{4\}//;s/ / /" fi [ "$1" = "-s" ] && echo } function files_differ() { ! Quiet diff -w "$1" "$2" } function dirs_differ() { Quiet diff -x CVS -x .svn -wr "$1" "$2" } function merge_and_edit() { Parameters "$@" cur default # Output from diff -D # #ifndef ____GOBO_magic____ # ...old... # #else /* ____GOBO_magic____ */ # ...new... # #endif /* ____GOBO_magic____ */ local marker="____GOBO_diff_marker____" local dat="($(date +%Y-%m-%d))" diff -D$marker $cur $default 2> /dev/null \ | sed ' s,#ifndef '$marker',#----- GOBO DIFF: current version '$dat' -----, s,#else /\* '$marker' \*/,#----- GOBO DIFF: new version '$dat' -----, s,#else /\* ! '$marker' \*/,#----- GOBO DIFF: current version '$dat' -----, s,#endif /\* [! ]*'$marker' \*/,#----- GOBO DIFF: end block '$dat' -----, s,#ifdef '$marker',#----- GOBO DIFF: new version '$dat' -----,'\ > $cur.new cp $cur.new $cur.new.copy $EDITOR $cur.new >&$stdoutFD <&$stdinFD if ! files_differ $cur.new.copy $cur.new; then Log_Normal "Not changed: $cur" rm $cur.new rm $cur.new.copy return 1 else Log_Normal "Edited: $cur" mv $cur.new $file rm $cur.new.copy return 0 fi } # Figures out what to do with a given file. In --auto and --quick modes, an # attempt is made based on a hints file. In --interactive mode (and in --quick # mode in the case of conflicts), the user is prompted for an action with the # input_action function repeatQuestion=no function update_one_file() { local num difftype file opts_requested opts_available hints hint instruction Parameters "$@" num difftype cur default local file_to_show=${cur:-$default} local file="${file_to_show}" # First, we need to figure out what our options are if [ "$updateMode" = "interactive" ]; then opts_available=( 'prompt_user' ) else opts_available=( 'skip' 'fail' ) if [ "$updateMode" != 'auto' ]; then Append_To_Array opts_available 'prompt_user' fi case $difftype in new*) Append_To_Array opts_available 'create' 'dont_create' [ "$oldVersion" ] && Append_To_Array opts_available 'check_delete' ;; del*) Append_To_Array opts_available 'delete' if [ "$oldVersion" ]; then Append_To_Array opts_available 'check_create' 'dont_delete' fi ;; mod*) Append_To_Array opts_available 'overwrite' if [ "$oldVersion" ]; then Append_To_Array opts_available 'auto_merge' 'dont_change' fi ;; esac # esac is ridiculous fi # ok, now that we have a list of things we /can/ do, it's time to start # figuring out what we /should/ do opts_requested=() if [ "$usingHints" = yes ]; then # hints are enabled, so check Resources/Hints for information about the # file we're looking at. for hint in `Hints_For "$file"`; do if Array_Contains opts_available "$hint"; then if [ "${hintsAllowed[@]}" ]; then if Array_Contains hintsAllowed "$hint"; then Append_To_Array opts_requested "${hint}:hint" fi else Append_To_Array opts_requested "${hint}:hint" fi fi done fi Append_To_Array opts_requested `Array_Intersect updateOrder opts_available` Append_To_Array opts_requested 'fail' # And now, opts_requested has a list of things that the user (or the author # of the hints file) wants us to do, in the order that the user wants us # to try them. for instruction in "${opts_requested[@]}"; do ACTION='' if [ "${instruction##*:}" = hint ]; then InstructionSource='hint:' else InstructionSource='auto:' fi case "${instruction%%:hint}" in # I'll start with the simpler behaviors. create|overwrite) ACTION=u ;; delete) ACTION=d ;; skip|dont_*) ACTION=s ;; # auto_merge sets action to whatever we're supposed to do next, and # returns nonzero if it fails. auto_merge|check_delete|check_create) if ! auto_merge "$num" "$difftype" "$cur" "$default"; then [ "$?" = 1 ] && Log_Terse "$instruction failed on $file" continue fi ;; # Ask the user what they think. prompt_user) InstructionSource='user:' input_action "${num}" "${difftype}" "${file}" ;; # Something somewhere along the line told us to fail. Warn user, and # move on to the next file fail) Log_Error "$InstructionSource ${file} could not be updated." Log_Error "Please examine this file manually." ACTION=s ;; # Treat unrecognized instructions as a 'skip,' but warn the user. # Because of opts_available, this shouldn't actually be possible, but # you never know. *) Log_Terse "Unsupported instruction '${instruction}'" ACTION=s ;; esac # ridiculous attempt_action "$num" "$difftype" "$cur" "$default" "${ACTION}" local result="$?" [ "$repeatQuestion" == yes ] && repeatQuestion=no && update_one_file $@ [ "$result" == "$SKIP_ALL_MAGIC_NUMBER" ] && return "$SKIP_ALL_MAGIC_NUMBER" [ "$result" == "0" ] && return 0 done } function input_action() { # Show the file and ask the user what to do. # New, deleted, and modified files give different alternatives. # Returns the selected option in $ACTION. Parameters "$@" num difftype file function show_changes() { echo "" >&$questionFD echo " $num $difftype $file" >&$questionFD echo "" >&$questionFD } local action="" while [ "$action" = "" ]; do REPLY="" case $difftype in new*) show_changes input_option "[C]heck delete/[V]iew/[U]se/[S]kip/[L]ist/[SA]Skip all " case $REPLY in c) auto_merge "$num" "$difftype" "$file" "$default" "user" action="$ACTION" ;; v) action=vn ;; l|vn|u|s|sa) action=$REPLY;; esac ;; del*) show_changes input_option "[C]heck create/[V]iew/[D]elete/[S]kip/[L]ist/[SA]Skip all" case $REPLY in c) auto_merge "$num" "$difftype" "$file" "$default" "user" action="$ACTION" ;; v) action=vc ;; l|vc|d|s|sa) action=$REPLY;; esac ;; mod*) if Boolean "diffs"; then diff -u "${cur}" "${default}" >&$questionFD fi show_changes input_option "[A]uto-merge/[V]iew/[U]se new/[S]kip/[L]ist/[M]erge and edit/[SA]Skip all " case $REPLY in a) auto_merge "$num" "$difftype" "$file" "$default" "user" action="$ACTION" ;; v) input_option "View new or current? [N]ew/[C]urrent " ;; esac case $REPLY in l|u|m|s|sa) action=$REPLY;; n|c) action=v$REPLY;; esac ;; esac done ACTION=$action } function auto_merge() { Parameters "$@" num difftype cur default action local oldFile oldExists curExists defExists t fileToPatch # If we couldn't figure out which version was installed before, give up. if [ -z "$oldVersion" ]; then [ "$action" = 'user' ] && Log_Error "Couldn't detect previous version" return 1 fi oldFile="${oldVersion}/Resources/Defaults/${cur}" ACTION='' mergeCheckerReportedFailure='' ! Exists "$oldFile" ; oldExists="$?" ! Exists "$cur" ; curExists="$?" ! Exists "$default" ; defExists="$?" case "${oldExists}${defExists}${curExists}" in 001) ACTION=s ;; # If the user created a file, keep it there 010) return 2 ;; # New file? Defer to the next requested action 011) return 1 ;; # File exists everywhere but old defaults. Can't merge 100) return 1 ;; # In theory, this one's impossible. 101) # File that the new Defaults want us to delete if files_differ "${oldFile}" "${cur}"; then return 1 fi ACTION=d ;; 110) ACTION=d ;; # If the user deleted a file, don't create it again. 111) if ! files_differ "${oldFile}" "${default}"; then ACTION=s # default configuration hasn't changed. don't update. return 0 fi if ! files_differ "${oldFile}" "${cur}"; then ACTION=u # user hasn't changed configuration, update. return 0 fi for diffOptions in -w -cw -uw; do local outpath="`Unique_Name "${cur}.$$-${scriptName}"`" diff "$diffOptions" "$oldFile" "$default" > "${outpath}.d1" diff "$diffOptions" "$oldFile" "$cur" > "${outpath}.d2" comm -23 "${outpath}.d1" "${outpath}.d2" > "${outpath}.d3" # Try to patch stuff for t in 1 2 3; do case "$t" in 2) whichFile="${default}" ;; *) whichFile="${cur}" ;; esac # is ridiculous Quiet patch -lfso "${outpath}" "$whichFile" < "${outpath}.d$t" if [ "$?" != 0 ] || Exists "${outpath}.rej"; then rm -f "${outpath}.rej" elif [ "$mergeChecker" ] && [ -x "$mergeChecker" ]; then "$mergeChecker" "$oldFile" "$cur" "$default" "$outpath" \ "$mergeFuzz" if [ "$?" != 0 ]; then mergeCheckerReportedFailure=yes else ACTION=s # Yaay. We've finished this file! break fi else ACTION=s break fi done # Remove diff files for t in 1 2 3; do rm -f "${outpath}.d$t" done if [ "$ACTION" ]; then mv "${outpath}" "${cur}" break else rm -f "${outpath}" fi done if [ -z "$ACTION" ]; then if [ "$mergeCheckerReportedFailure" ]; then Log_Terse "`basename "$mergeChecker"` reports failed merge" fi return 1 fi lastSuccessfulMerge="${cur}" ;; esac # yep } function attempt_action() { Parameters "$@" num difftype cur default action case "$action" in s) if [ "${lastSuccessfulMerge}" = "${cur}" ]; then Log_Normal "$InstructionSource Successfully patched ${cur}" else case "$difftype" in mod*) Log_Normal "$InstructionSource Not updating $cur" ;; del*) Log_Normal "$InstructionSource Not deleting $cur" ;; new*) Log_Normal "$InstructionSource Not using $default" ;; *) Log_Normal "$InstructionSource Leaving $cur alone" ;; esac fi ;; sa)return $SKIP_ALL_MAGIC_NUMBER ;; u) Log_Normal "$InstructionSource Using $default" mkdir -p "$(dirname "$cur")" if ! Is_Directory "$default" then cp "$default" "$cur" else mkdir -p "$cur" fi ;; m) merge_and_edit "$cur" "$default" if [ $? -gt 0 ]; then repeatQuestion=yes;fi ;; d) if Exists "$cur"; then Log_Normal "$InstructionSource Deleting $cur" else Log_Normal "$InstructionSource Not creating $cur" fi rm -f "$cur" ;; vn)out "Viewing $default..." out "-------------------------------------------------" $PAGER "$default" <&$stdinFD >&$stdoutFD repeatQuestion=yes ;; vc) out "Viewing $cur...." out "-------------------------------------------------" $PAGER "$cur" <&$stdinFD >&$stdoutFD repeatQuestion=yes ;; l) list_diffs -s >&$questionFD repeatQuestion=yes ;; esac } function update_files() { # Show all files. ask the user and update each one. local new default num type file # Parse the hints file if [ "$hintsEnabled" = yes ]; then Load_Hints "$version/Resources/Hints" && usingHints=yes fi # Loop through all files and handle each in turn. list_diffs | while read num type file; do if [ "$type" == "new:" ]; then # From list_diffs: "#no new: $version/Resources/Defaults/Settings/" # get current (though yet unexistant) file by removing C/R/D/S. default="$file" cur=$(echo "$file" | sed 's,'$version'/Resources/Defaults/,,') else # From list_diffs: "#no del/mod: Settings/" default="$version/Resources/Defaults/$file" cur="$file" fi # Handle one file update_one_file $num $type "$cur" "$default" local result=$? [ "$result" == "$SKIP_ALL_MAGIC_NUMBER" ] && return 0 [ "$result" == "0" ] || return $result done } ############################################## # Look for differences ############################################## curdir="$pdir/Settings" defaultdir="$pdir/$version/Resources/Defaults/Settings" if find "$curdir" -not -type d -and -not -path "*CVS/*" -and -not -path "*.svn/*" &> /dev/null then has_current=yes; else has_current=no; fi if find "$defaultdir" -not -type d -and -not -path "*CVS/*" -and -not -path "*.svn/*" &> /dev/null then has_default=yes; else has_default=no; fi diffs_found=yes; if [ "$has_default" = "$has_current" ]; then if [ "$has_default" = no ] || dirs_differ "$curdir" "$defaultdir"; then diffs_found=no; fi fi ############################################## # Report differences ############################################## if Boolean "check"; then # Exit with status telling if any differences were found [ $diffs_found = yes ] exit "$?" fi if [ $has_current = yes -a $has_default = no ] then # do not complain if recipe isn't shipped with a default settings exit 0 fi if Boolean "report"; then exitStatus=1 if [ $has_current = no -a $has_default = no ]; then Log_Normal "No settings exist" exitStatus=0 elif [ $has_current = no ]; then Log_Normal "Settings differ (no current settings exist)" elif [ $has_default = no ]; then Log_Normal "Settings differ (no default settings exist)" elif [ $diffs_found = yes ]; then Log_Normal "Current and default settings are different" else Log_Normal "Current and default settings match" exitStatus=0 fi fi [ $diffs_found = no ] && exit 0 cd "$pdir" if Boolean "list"; then # list differing files list_diffs -s fi ############################################## # Update settings ############################################## if Boolean "update"; then if [ "$updateMode" = "quick" ]; then Log_Normal "Quick-updating settings in $pdir" elif [ "$updateMode" = "auto" ]; then Log_Normal "Automatically updating settings in $pdir" elif [ "$updateMode" = "interactive" ]; then if [ $has_current = yes ]; then while true; do Ask "Update settings in $pdir?" case "$REPLY" in y|Y) break ;; n|N) exit 0 ;; esac # easc is ridiculous. done fi fi if Is_Directory "$pdir/Settings" && [ $has_current = yes ]; then mkdir -p "$pdir/$version/Resources" backup=$(Unique_Name "$pdir/$version/Resources/SettingsBackup") Log_Normal "Backing up settings in $backup" cp -a "$pdir/Settings" "$backup" || Die "Failed to backup settings" else Log_Normal "No current settings. No backup made." fi # we need to uninstall GConf schemas before we update the settings Is_Real_Nonempty_Directory "${goboPrograms}/${appName}/Settings/gconf/schemas" && Uninstall_GConf_Schemas "$appName" update_files exitStatus="$?" # then we install the GConf schemas when we have updated the settings Is_Real_Nonempty_Directory "${goboPrograms}/${appName}/Settings/gconf/schemas" && Install_GConf_Schemas "$appName" fi exit $exitStatus