2 # Copyright 1999-2011 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
5 # Author Brandon Low <lostlogic@gentoo.org>
7 # Previous version (from which I've borrowed a few bits) by:
8 # Jochem Kossen <j.kossen@home.nl>
9 # Leo Lipelis <aeoo@gentoo.org>
10 # Karl Trygve Kalleberg <karltk@gentoo.org>
14 if type -P gsed >/dev/null ; then
20 # - strip off comments
21 # - match lines that set item in question
22 # - delete the "item =" part
23 # - store the actual value into the hold space
24 # - on the last line, restore the hold space and print it
25 # If there's more than one of the same configuration item, then
26 # the store to the hold space clobbers previous value so the last
27 # setting takes precedence.
30 -e 's:[[:space:]]*#.*$::' \
31 -e "/^[[:space:]]*$item[[:space:]]*=/{s:[^=]*=[[:space:]]*\([\"']\{0,1\}\)\(.*\)\1:\2:;h}" \
33 "${PORTAGE_CONFIGROOT}"etc/etc-update.conf)
37 # return true if the first whitespace-separated token contained
38 # in "${1}" is an executable file, false otherwise
39 [[ -x $(type -P ${1%%[[:space:]]*}) ]]
43 local cmd=${diff_command//%file1/$1}
48 echo "Scanning Configuration files..."
49 rm -rf ${TMP}/files > /dev/null 2>&1
50 mkdir ${TMP}/files || die "Failed mkdir command!" 1
56 for path in ${CONFIG_PROTECT} ; do
57 path="${EROOT}${path}"
58 # Do not traverse hidden directories such as .svn or .git.
59 find_opts="-name .* -type d -prune -o -name ._cfg????_*"
60 if [ ! -d "${path}" ]; then
61 [ ! -f "${path}" ] && continue
62 my_basename="${path##*/}"
64 find_opts="-maxdepth 1 -name ._cfg????_${my_basename}"
68 # The below set -f turns off file name globbing in the ${find_opts} expansion.
69 for file in $(set -f ; find ${path}/ ${find_opts} \
70 ! -name '.*~' ! -iname '.*.bak' -print |
71 sed -e "s:\(^.*/\)\(\._cfg[0-9]*_\)\(.*$\):\1\2\3\%\1%\2\%\3:" |
72 sort -t'%' -k2,2 -k4,4 -k3,3 | LANG=POSIX LC_ALL=POSIX cut -f1 -d'%'); do
74 rpath=$(echo "${file/\/\///}" | sed -e "s:/[^/]*$::")
75 rfile=$(echo "${file/\/\///}" | sed -e "s:^.*/::")
76 for mpath in ${CONFIG_PROTECT_MASK}; do
77 mpath="${EROOT}${mpath}"
78 mpath=$(echo "${mpath/\/\///}")
79 if [[ "${rpath}" == "${mpath}"* ]]; then
80 mv ${rpath}/${rfile} ${rpath}/${rfile:10}
84 if [[ ! -f ${file} ]] ; then
85 echo "Skipping non-file ${file} ..."
89 if [[ "${ofile:10}" != "${rfile:10}" ]] ||
90 [[ ${opath} != ${rpath} ]]; then
92 if [[ "${EU_AUTOMERGE}" == "yes" ]]; then
93 if [ ! -e "${rpath}/${rfile}" ] || [ ! -e "${rpath}/${rfile:10}" ]; then
96 diff -Bbua ${rpath}/${rfile} ${rpath}/${rfile:10} | egrep '^[+-]' | egrep -v '^[+-][\t ]*#|^--- |^\+\+\+ ' | egrep -qv '^[-+][\t ]*$'
99 elif [[ -z $(diff -Nua ${rpath}/${rfile} ${rpath}/${rfile:10}|
100 grep "^[+-][^+-]"|grep -v '# .Header:.*') ]]; then
103 if [[ "${MATCHES}" == "1" ]]; then
104 echo "Automerging trivial changes in: ${rpath}/${rfile:10}"
105 mv ${rpath}/${rfile} ${rpath}/${rfile:10}
109 echo "${rpath}/${rfile:10}" > ${TMP}/files/${count}
110 echo "${rpath}/${rfile}" >> ${TMP}/files/${count}
117 if [[ -z $(diff -Nua ${rpath}/${rfile} ${rpath}/${ofile}|
118 grep "^[+-][^+-]"|grep -v '# .Header:.*') ]]; then
119 mv ${rpath}/${rfile} ${rpath}/${ofile}
122 echo "${rpath}/${rfile}" >> ${TMP}/files/${count}
133 until [[ -f ${TMP}/files/${input} ]] || \
134 [[ ${input} == -1 ]] || \
137 local numfiles=$(ls ${TMP}/files|wc -l)
138 local numwidth=${#numfiles}
139 for file in $(ls ${TMP}/files|sort -n); do
140 if [[ ${isfirst} == 0 ]] ; then
143 numshow=$(printf "%${numwidth}i${PAR} " ${file})
144 numupdates=$(( $(wc -l <${TMP}/files/${file}) - 1 ))
146 if [[ ${mode} == 0 ]] ; then
147 echo "$(head -n1 ${TMP}/files/${file}) (${numupdates})"
149 head -n1 ${TMP}/files/${file}
151 done > ${TMP}/menuitems
153 if [ "${OVERWRITE_ALL}" == "yes" ]; then
155 elif [ "${DELETE_ALL}" == "yes" ]; then
158 [[ $CLEAR_TERM == yes ]] && clear
159 if [[ ${mode} == 0 ]] ; then
160 echo "The following is the list of files which need updating, each
161 configuration file is followed by a list of possible replacement files."
163 local my_title="Please select a file to update"
166 if [[ ${mode} == 0 ]] ; then
168 echo "Please select a file to edit by entering the corresponding number."
169 echo " (don't use -3, -5, -7 or -9 if you're unsure what to do)"
170 echo " (-1 to exit) (-3 to auto merge all remaining files)"
171 echo " (-5 to auto-merge AND not use 'mv -i')"
172 echo " (-7 to discard all updates)"
173 echo -n " (-9 to discard all updates AND not use 'rm -i'): "
176 dialog --title "${title}" --menu "${my_title}" \
177 0 0 0 $(echo -e "-1 Exit\n$(<${TMP}/menuitems)") \
178 2> ${TMP}/input || die "User termination!" 0
179 input=$(<${TMP}/input)
181 if [[ ${input} == -9 ]]; then
182 read -p "Are you sure that you want to delete all updates (type YES):" reply
183 if [[ ${reply} != "YES" ]]; then
190 if [[ ${input} == -7 ]]; then
192 export DELETE_ALL="yes"
194 if [[ ${input} == -5 ]] ; then
196 export mv_opts=" ${mv_opts} "
197 mv_opts="${mv_opts// -i / }"
199 if [[ ${input} == -3 ]] ; then
201 export OVERWRITE_ALL="yes"
204 if [[ -z ${input} ]] || [[ ${input} == 0 ]] ; then
211 if [ -r ${PORTAGE_CONFIGROOT}etc/etc-update.special ]; then
213 echo "ERROR: user_special() called without arguments"
216 while read -r pat; do
217 echo ${1} | grep "${pat}" > /dev/null && return 0
218 done < ${PORTAGE_CONFIGROOT}etc/etc-update.special
224 # Read an integer from stdin. Continously loops until a valid integer is
225 # read. This is a workaround for odd behavior of bash when an attempt is
226 # made to store a value such as "1y" into an integer-only variable.
230 # failed integer conversions will break a loop unless they're enclosed
232 echo "${my_input}" | ( declare -i x; read x) 2>/dev/null && break
233 echo -n "Value '$my_input' is not valid. Please enter an integer value:" >&2
239 interactive_echo() { [ "${OVERWRITE_ALL}" != "yes" ] && [ "${DELETE_ALL}" != "yes" ] && echo; }
243 until (( $(wc -l < ${TMP}/files/${input}) < 2 )); do
245 if (( $(wc -l < ${TMP}/files/${input}) == 2 )); then
248 until (( ${my_input} > 0 )) && (( ${my_input} < $(wc -l < ${TMP}/files/${input}) )); do
251 if [ "${OVERWRITE_ALL}" == "yes" ]; then
253 elif [ "${DELETE_ALL}" == "yes" ]; then
256 for line in $(<${TMP}/files/${input}); do
257 if (( ${fcount} > 0 )); then
258 echo -n "${fcount}${PAR} "
261 if [[ ${mode} == 0 ]] ; then
262 echo "Below are the new config files for ${line}:"
264 local my_title="Please select a file to process for ${line}"
268 done > ${TMP}/menuitems
270 if [[ ${mode} == 0 ]] ; then
272 echo -n "Please select a file to process (-1 to exit this file): "
275 dialog --title "${title}" --menu "${my_title}" \
276 0 0 0 $(echo -e "$(<${TMP}/menuitems)\n${fcount} Exit") \
277 2> ${TMP}/input || die "User termination!" 0
278 my_input=$(<${TMP}/input)
282 if [[ ${my_input} == 0 ]] ; then
284 elif [[ ${my_input} == -1 ]] ; then
287 elif [[ ${my_input} == ${fcount} ]] ; then
291 if [[ ${my_input} == ${fcount} ]] ; then
297 file=$(sed -e "${fcount}p;d" ${TMP}/files/${input})
298 ofile=$(head -n1 ${TMP}/files/${input})
300 do_cfg "${file}" "${ofile}"
302 sed -e "${fcount}!p;d" ${TMP}/files/${input} > ${TMP}/files/sed
303 mv ${TMP}/files/sed ${TMP}/files/${input}
305 if [[ ${my_input} == -1 ]] ; then
310 rm ${TMP}/files/${input}
320 until (( ${my_input} == -1 )) || [ ! -f ${file} ]; do
321 if [[ "${OVERWRITE_ALL}" == "yes" ]] && ! user_special "${ofile}"; then
323 elif [[ "${DELETE_ALL}" == "yes" ]] && ! user_special "${ofile}"; then
326 [[ $CLEAR_TERM == yes ]] && clear
327 if [ "${using_editor}" == 0 ]; then
329 echo "Showing differences between ${ofile} and ${file}"
330 diff_command "${ofile}" "${file}"
333 echo "Beginning of differences between ${ofile} and ${file}"
334 diff_command "${ofile}" "${file}"
335 echo "End of differences between ${ofile} and ${file}"
337 if [ -L "${file}" ]; then
339 echo "-------------------------------------------------------------"
340 echo "NOTE: File is a symlink to another file. REPLACE recommended."
341 echo " The original file may simply have moved. Please review."
342 echo "-------------------------------------------------------------"
345 echo -n "File: ${file}
346 1) Replace original with update
347 2) Delete update, keeping original as is
348 3) Interactively merge original with update
349 4) Show differences again
350 5) Save update as example config
351 Please select from the menu above (-1 to ignore this update): "
356 1) echo "Replacing ${ofile} with ${file}"
357 mv ${mv_opts} ${file} ${ofile}
358 [ -n "${OVERWRITE_ALL}" ] && my_input=-1
361 2) echo "Deleting ${file}"
362 rm ${rm_opts} ${file}
363 [ -n "${DELETE_ALL}" ] && my_input=-1
366 3) do_merge "${file}" "${ofile}"
368 # [ ${my_input} == 255 ] && my_input=-1
373 5) do_distconf "${file}" "${ofile}"
382 # make sure we keep the merged file in the secure tempdir
383 # so we dont leak any information contained in said file
384 # (think of case where the file has 0600 perms; during the
385 # merging process, the temp file gets umask perms!)
389 local mfile="${TMP}/${2}.merged"
391 echo "${file} ${ofile} ${mfile}"
393 if [[ -e ${mfile} ]] ; then
394 echo "A previous version of the merged file exists, cleaning..."
395 rm ${rm_opts} "${mfile}"
398 # since mfile will be like $TMP/path/to/original-file.merged, we
399 # need to make sure the full /path/to/ exists ahead of time
400 mkdir -p "${mfile%/*}"
402 until (( ${my_input} == -1 )); do
403 echo "Merging ${file} and ${ofile}"
404 $(echo "${merge_command}" |
405 sed -e "s:%merged:${mfile}:g" \
406 -e "s:%orig:${ofile}:g" \
407 -e "s:%new:${file}:g")
408 until (( ${my_input} == -1 )); do
409 echo -n "1) Replace ${ofile} with merged file
410 2) Show differences between merged file and original
411 3) Remerge original with update
413 5) Return to the previous menu
414 Please select from the menu above (-1 to exit, losing this merge): "
417 1) echo "Replacing ${ofile} with ${mfile}"
418 if [[ ${USERLAND} == BSD ]] ; then
419 chown "$(stat -f %Su:%Sg "${ofile}")" "${mfile}"
420 chmod $(stat -f %Mp%Lp "${ofile}") "${mfile}"
422 chown --reference="${ofile}" "${mfile}"
423 chmod --reference="${ofile}" "${mfile}"
425 mv ${mv_opts} "${mfile}" "${ofile}"
426 rm ${rm_opts} "${file}"
430 [[ $CLEAR_TERM == yes ]] && clear
431 if [ "${using_editor}" == 0 ]; then
433 echo "Showing differences between ${ofile} and ${mfile}"
434 diff_command "${ofile}" "${mfile}"
437 echo "Beginning of differences between ${ofile} and ${mfile}"
438 diff_command "${ofile}" "${mfile}"
439 echo "End of differences between ${ofile} and ${mfile}"
445 4) ${EDITOR:-nano -w} "${mfile}"
448 5) rm ${rm_opts} "${mfile}"
456 rm ${rm_opts} "${mfile}"
461 # search for any previously saved distribution config
462 # files and number the current one accordingly
471 for ((count = 0; count <= 9999; count++)); do
473 for ((fill = 4 - ${#count}; fill > 0; fill--)); do
477 efile="${ofile}${suffix}"
478 if [[ ! -f ${efile} ]]; then
479 mv ${mv_opts} "${file}" "${efile}"
481 elif diff_command "${file}" "${efile}" &> /dev/null; then
482 # replace identical copy
483 mv "${file}" "${efile}"
493 if [ "$2" -eq 0 ]; then
496 [ ${count} -gt 0 ] && echo "NOTE: ${count} updates remaining"
507 etc-update: Handle configuration file updates
509 Usage: etc-update [options]
512 -d, --debug Enable shell debugging
513 -h, --help Show help and run away
514 -V, --version Show version and trundle away
517 [[ -n ${*:2} ]] && printf "\nError: %s\n" "${*:2}" 1>&2
527 while [[ -n $1 ]] ; do
529 -d|--debug) SET_X=true;;
531 -V|--version) emerge --version ; exit 0;;
532 *) usage 1 "Invalid option '$1'";;
538 type portageq > /dev/null || exit $?
539 eval $(portageq envvar -v CONFIG_PROTECT \
540 CONFIG_PROTECT_MASK PORTAGE_CONFIGROOT PORTAGE_INST_GID PORTAGE_INST_UID \
541 PORTAGE_TMPDIR EROOT USERLAND)
542 export PORTAGE_TMPDIR
544 TMP="${PORTAGE_TMPDIR}/etc-update-$$"
545 trap "die terminated 1" SIGTERM
546 trap "die interrupted 1" SIGINT
548 [ -w ${PORTAGE_CONFIGROOT}etc ] || die "Need write access to ${PORTAGE_CONFIGROOT}etc" 1
549 #echo $PORTAGE_TMPDIR
550 #echo $CONFIG_PROTECT
551 #echo $CONFIG_PROTECT_MASK
552 #export PORTAGE_TMPDIR=$(/usr/lib/portage/bin/portageq envvar PORTAGE_TMPDIR)
554 rm -rf "${TMP}" 2> /dev/null
555 mkdir "${TMP}" || die "failed to create temp dir" 1
556 # make sure we have a secure directory to work in
557 chmod 0700 "${TMP}" || die "failed to set perms on temp dir" 1
558 chown ${PORTAGE_INST_UID:-0}:${PORTAGE_INST_GID:-0} "${TMP}" || \
559 die "failed to set ownership on temp dir" 1
561 # I need the CONFIG_PROTECT value
562 #CONFIG_PROTECT=$(/usr/lib/portage/bin/portageq envvar CONFIG_PROTECT)
563 #CONFIG_PROTECT_MASK=$(/usr/lib/portage/bin/portageq envvar CONFIG_PROTECT_MASK)
565 # load etc-config's configuration
566 CLEAR_TERM=$(get_config clear_term)
567 EU_AUTOMERGE=$(get_config eu_automerge)
568 rm_opts=$(get_config rm_opts)
569 mv_opts=$(get_config mv_opts)
570 cp_opts=$(get_config cp_opts)
571 pager=$(get_config pager)
572 diff_command=$(get_config diff_command)
573 using_editor=$(get_config using_editor)
574 merge_command=$(get_config merge_command)
575 declare -i mode=$(get_config mode)
576 [[ -z ${mode} ]] && mode=0
577 if ! cmd_var_is_valid "${pager}" ; then
579 cmd_var_is_valid "${pager}" || pager=cat
582 if [ "${using_editor}" == 0 ]; then
583 # Sanity check to make sure diff exists and works
584 echo > "${TMP}"/.diff-test-1
585 echo > "${TMP}"/.diff-test-2
587 if ! diff_command "${TMP}"/.diff-test-1 "${TMP}"/.diff-test-2 ; then
588 die "'${diff_command}' does not seem to work, aborting" 1
591 if ! type ${diff_command%% *} >/dev/null; then
592 die "'${diff_command}' does not seem to work, aborting" 1
596 if [[ ${mode} == "1" ]] ; then
597 if ! type dialog >/dev/null || ! dialog --help >/dev/null ; then
598 die "mode=1 and 'dialog' not found or not executable, aborting" 1
602 #echo "rm_opts: $rm_opts, mv_opts: $mv_opts, cp_opts: $cp_opts"
603 #echo "pager: $pager, diff_command: $diff_command, merge_command: $merge_command"
605 if (( ${mode} == 0 )); then
613 declare title="Gentoo's etc-update tool!"
617 until (( ${input} == -1 )); do
618 if (( ${count} == 0 )); then
619 die "Nothing left to do; exiting. :)" 0
622 if (( ${input} != -1 )); then
627 die "User termination!" 0