2 # Copyright 1999-2007 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
6 # Author Brandon Low <lostlogic@gentoo.org>
8 # Previous version (from which I've borrowed a few bits) by:
9 # Jochem Kossen <j.kossen@home.nl>
10 # Leo Lipelis <aeoo@gentoo.org>
11 # Karl Trygve Kalleberg <karltk@gentoo.org>
15 if type -P gsed >/dev/null ; then
21 # - strip off comments
22 # - match lines that set item in question
23 # - delete the "item =" part
24 # - store the actual value into the hold space
25 # - on the last line, restore the hold space and print it
26 # If there's more than one of the same configuration item, then
27 # the store to the hold space clobbers previous value so the last
28 # setting takes precedence.
31 -e 's:[[:space:]]*#.*$::' \
32 -e "/^[[:space:]]*$item[[:space:]]*=/{s:[^=]*=[[:space:]]*\([\"']\{0,1\}\)\(.*\)\1:\2:;h}" \
34 "${PORTAGE_CONFIGROOT}"etc/etc-update.conf)
38 local cmd=${diff_command//%file1/$1}
43 echo "Scanning Configuration files..."
44 rm -rf ${TMP}/files > /dev/null 2>&1
45 mkdir ${TMP}/files || die "Failed mkdir command!" 1
51 for path in ${CONFIG_PROTECT} ; do
53 # Do not traverse hidden directories such as .svn or .git.
54 find_opts="-name .* -type d -prune -o -name ._cfg????_*"
55 if [ ! -d "${path}" ]; then
56 [ ! -f "${path}" ] && continue
57 my_basename="${path##*/}"
59 find_opts="-maxdepth 1 -name ._cfg????_${my_basename}"
63 # The below set -f turns off file name globbing in the ${find_opts} expansion.
64 for file in $(set -f ; find ${path}/ ${find_opts} \
65 ! -name '.*~' ! -iname '.*.bak' -print |
66 sed -e "s:\(^.*/\)\(\._cfg[0-9]*_\)\(.*$\):\1\2\3\%\2\%\3:" |
67 sort -t'%' -k3 -k2 | LANG=POSIX LC_ALL=POSIX cut -f1 -d'%'); do
69 rpath=$(echo "${file/\/\///}" | sed -e "s:/[^/]*$::")
70 rfile=$(echo "${file/\/\///}" | sed -e "s:^.*/::")
71 for mpath in ${CONFIG_PROTECT_MASK}; do
72 mpath="${ROOT}${mpath}"
73 mpath=$(echo "${mpath/\/\///}")
74 if [[ "${rpath}" == "${mpath}"* ]]; then
75 mv ${rpath}/${rfile} ${rpath}/${rfile:10}
79 if [[ ! -f ${file} ]] ; then
80 echo "Skipping non-file ${file} ..."
84 if [[ "${ofile:10}" != "${rfile:10}" ]] ||
85 [[ ${opath} != ${rpath} ]]; then
87 if [[ "${EU_AUTOMERGE}" == "yes" ]]; then
88 if [ ! -e "${rpath}/${rfile}" ] || [ ! -e "${rpath}/${rfile:10}" ]; then
91 diff -Bbua ${rpath}/${rfile} ${rpath}/${rfile:10} | egrep '^[+-]' | egrep -v '^[+-][\t ]*#|^--- |^\+\+\+ ' | egrep -qv '^[-+][\t ]*$'
94 elif [[ -z $(diff -Nua ${rpath}/${rfile} ${rpath}/${rfile:10}|
95 grep "^[+-][^+-]"|grep -v '# .Header:.*') ]]; then
98 if [[ "${MATCHES}" == "1" ]]; then
99 echo "Automerging trivial changes in: ${rpath}/${rfile:10}"
100 mv ${rpath}/${rfile} ${rpath}/${rfile:10}
104 echo "${rpath}/${rfile:10}" > ${TMP}/files/${count}
105 echo "${rpath}/${rfile}" >> ${TMP}/files/${count}
112 if [[ -z $(diff -Nua ${rpath}/${rfile} ${rpath}/${ofile}|
113 grep "^[+-][^+-]"|grep -v '# .Header:.*') ]]; then
114 mv ${rpath}/${rfile} ${rpath}/${ofile}
117 echo "${rpath}/${rfile}" >> ${TMP}/files/${count}
128 until [[ -f ${TMP}/files/${input} ]] || \
129 [[ ${input} == -1 ]] || \
132 local numfiles=$(ls ${TMP}/files|wc -l)
133 local numwidth=${#numfiles}
134 for file in $(ls ${TMP}/files|sort -n); do
135 if [[ ${isfirst} == 0 ]] ; then
138 numshow=$(printf "%${numwidth}i${PAR} " ${file})
139 numupdates=$(( $(wc -l <${TMP}/files/${file}) - 1 ))
141 if [[ ${mode} == 0 ]] ; then
142 echo "$(head -n1 ${TMP}/files/${file}) (${numupdates})"
144 head -n1 ${TMP}/files/${file}
146 done > ${TMP}/menuitems
148 if [ "${OVERWRITE_ALL}" == "yes" ]; then
150 elif [ "${DELETE_ALL}" == "yes" ]; then
153 if [[ ${mode} == 0 ]] ; then
154 echo "The following is the list of files which need updating, each
155 configuration file is followed by a list of possible replacement files."
157 local my_title="Please select a file to update"
160 if [[ ${mode} == 0 ]] ; then
162 echo "Please select a file to edit by entering the corresponding number."
163 echo " (don't use -3, -5, -7 or -9 if you're unsure what to do)"
164 echo " (-1 to exit) (-3 to auto merge all remaining files)"
165 echo " (-5 to auto-merge AND not use 'mv -i')"
166 echo " (-7 to discard all updates)"
167 echo -n " (-9 to discard all updates AND not use 'rm -i'): "
170 dialog --title "${title}" --menu "${my_title}" \
171 0 0 0 $(echo -e "-1 Exit\n$(<${TMP}/menuitems)") \
172 2> ${TMP}/input || die "User termination!" 0
173 input=$(<${TMP}/input)
175 if [[ ${input} == -9 ]]; then
176 read -p "Are you sure that you want to delete all updates (type YES):" reply
177 if [[ ${reply} != "YES" ]]; then
184 if [[ ${input} == -7 ]]; then
186 export DELETE_ALL="yes"
188 if [[ ${input} == -5 ]] ; then
190 export mv_opts=" ${mv_opts} "
191 mv_opts="${mv_opts// -i / }"
193 if [[ ${input} == -3 ]] ; then
195 export OVERWRITE_ALL="yes"
198 if [[ -z ${input} ]] || [[ ${input} == 0 ]] ; then
205 if [ -r ${PORTAGE_CONFIGROOT}etc/etc-update.special ]; then
207 echo "ERROR: user_special() called without arguments"
211 echo ${1} | grep "${pat}" > /dev/null && return 0
212 done < ${PORTAGE_CONFIGROOT}etc/etc-update.special
218 # Read an integer from stdin. Continously loops until a valid integer is
219 # read. This is a workaround for odd behavior of bash when an attempt is
220 # made to store a value such as "1y" into an integer-only variable.
224 # failed integer conversions will break a loop unless they're enclosed
226 echo "${my_input}" | ( declare -i x; read x) && break
232 interactive_echo() { [ "${OVERWRITE_ALL}" != "yes" ] && [ "${DELETE_ALL}" != "yes" ] && echo; }
236 until (( $(wc -l < ${TMP}/files/${input}) < 2 )); do
238 if (( $(wc -l < ${TMP}/files/${input}) == 2 )); then
241 until (( ${my_input} > 0 )) && (( ${my_input} < $(wc -l < ${TMP}/files/${input}) )); do
244 if [ "${OVERWRITE_ALL}" == "yes" ]; then
246 elif [ "${DELETE_ALL}" == "yes" ]; then
249 for line in $(<${TMP}/files/${input}); do
250 if (( ${fcount} > 0 )); then
251 echo -n "${fcount}${PAR} "
254 if [[ ${mode} == 0 ]] ; then
255 echo "Below are the new config files for ${line}:"
257 local my_title="Please select a file to process for ${line}"
261 done > ${TMP}/menuitems
263 if [[ ${mode} == 0 ]] ; then
265 echo -n "Please select a file to process (-1 to exit this file): "
268 dialog --title "${title}" --menu "${my_title}" \
269 0 0 0 $(echo -e "$(<${TMP}/menuitems)\n${fcount} Exit") \
270 2> ${TMP}/input || die "User termination!" 0
271 my_input=$(<${TMP}/input)
275 if [[ ${my_input} == 0 ]] ; then
277 elif [[ ${my_input} == -1 ]] ; then
280 elif [[ ${my_input} == ${fcount} ]] ; then
284 if [[ ${my_input} == ${fcount} ]] ; then
290 file=$(sed -e "${fcount}p;d" ${TMP}/files/${input})
291 ofile=$(head -n1 ${TMP}/files/${input})
293 do_cfg "${file}" "${ofile}"
295 sed -e "${fcount}!p;d" ${TMP}/files/${input} > ${TMP}/files/sed
296 mv ${TMP}/files/sed ${TMP}/files/${input}
298 if [[ ${my_input} == -1 ]] ; then
303 rm ${TMP}/files/${input}
313 until (( ${my_input} == -1 )) || [ ! -f ${file} ]; do
314 if [[ "${OVERWRITE_ALL}" == "yes" ]] && ! user_special "${ofile}"; then
316 elif [[ "${DELETE_ALL}" == "yes" ]] && ! user_special "${ofile}"; then
319 if [ "${using_editor}" == 0 ]; then
321 echo "Showing differences between ${ofile} and ${file}"
322 diff_command "${ofile}" "${file}"
325 echo "Beginning of differences between ${ofile} and ${file}"
326 diff_command "${ofile}" "${file}"
327 echo "End of differences between ${ofile} and ${file}"
329 if [ -L "${file}" ]; then
331 echo "-------------------------------------------------------------"
332 echo "NOTE: File is a symlink to another file. REPLACE recommended."
333 echo " The original file may simply have moved. Please review."
334 echo "-------------------------------------------------------------"
337 echo -n "File: ${file}
338 1) Replace original with update
339 2) Delete update, keeping original as is
340 3) Interactively merge original with update
341 4) Show differences again
342 Please select from the menu above (-1 to ignore this update): "
347 1) echo "Replacing ${ofile} with ${file}"
348 mv ${mv_opts} ${file} ${ofile}
349 [ -n "${OVERWRITE_ALL}" ] && my_input=-1
352 2) echo "Deleting ${file}"
353 rm ${rm_opts} ${file}
354 [ -n "${DELETE_ALL}" ] && my_input=-1
357 3) do_merge "${file}" "${ofile}"
359 # [ ${my_input} == 255 ] && my_input=-1
371 # make sure we keep the merged file in the secure tempdir
372 # so we dont leak any information contained in said file
373 # (think of case where the file has 0600 perms; during the
374 # merging process, the temp file gets umask perms!)
378 local mfile="${TMP}/${2}.merged"
380 echo "${file} ${ofile} ${mfile}"
382 if [[ -e ${mfile} ]] ; then
383 echo "A previous version of the merged file exists, cleaning..."
384 rm ${rm_opts} "${mfile}"
387 # since mfile will be like $TMP/path/to/original-file.merged, we
388 # need to make sure the full /path/to/ exists ahead of time
389 mkdir -p "${mfile%/*}"
391 until (( ${my_input} == -1 )); do
392 echo "Merging ${file} and ${ofile}"
393 $(echo "${merge_command}" |
394 sed -e "s:%merged:${mfile}:g" \
395 -e "s:%orig:${ofile}:g" \
396 -e "s:%new:${file}:g")
397 until (( ${my_input} == -1 )); do
398 echo -n "1) Replace ${ofile} with merged file
399 2) Show differences between merged file and original
400 3) Remerge original with update
402 5) Return to the previous menu
403 Please select from the menu above (-1 to exit, losing this merge): "
406 1) echo "Replacing ${ofile} with ${mfile}"
407 if [[ ${USERLAND} == BSD ]] ; then
408 chown "$(stat -f %Su:%Sg "${ofile}")" "${mfile}"
409 chmod $(stat -f %Mp%Lp "${ofile}") "${mfile}"
411 chown --reference="${ofile}" "${mfile}"
412 chmod --reference="${ofile}" "${mfile}"
414 mv ${mv_opts} "${mfile}" "${ofile}"
415 rm ${rm_opts} "${file}"
418 2) if [ "${using_editor}" == 0 ]; then
420 echo "Showing differences between ${ofile} and ${mfile}"
421 diff_command "${ofile}" "${mfile}"
424 echo "Beginning of differences between ${ofile} and ${mfile}"
425 diff_command "${ofile}" "${mfile}"
426 echo "End of differences between ${ofile} and ${mfile}"
432 4) ${EDITOR:-nano -w} "${mfile}"
435 5) rm ${rm_opts} "${mfile}"
443 rm ${rm_opts} "${mfile}"
451 if [ ${2} -eq 0 ]; then
454 [ ${count} -gt 0 ] && echo "NOTE: ${count} updates remaining"
465 etc-update: Handle configuration file updates
467 Usage: etc-update [options]
470 -d, --debug Enable shell debugging
471 -h, --help Show help and run away
472 -V, --version Show version and trundle away
475 [[ -n ${*:2} ]] && printf "\nError: %s\n" "${*:2}" 1>&2
485 while [[ -n $1 ]] ; do
487 -d|--debug) SET_X=true;;
489 -V|--version) echo '$Id$'; exit 0;;
490 *) usage 1 "Invalid option '$1'";;
496 type portageq > /dev/null || exit $?
497 eval $(portageq envvar -v CONFIG_PROTECT \
498 CONFIG_PROTECT_MASK PORTAGE_CONFIGROOT PORTAGE_TMPDIR ROOT USERLAND)
499 export PORTAGE_TMPDIR
501 TMP="${PORTAGE_TMPDIR}/etc-update-$$"
504 [ -w ${PORTAGE_CONFIGROOT}etc ] || die "Need write access to ${PORTAGE_CONFIGROOT}etc" 1
505 #echo $PORTAGE_TMPDIR
506 #echo $CONFIG_PROTECT
507 #echo $CONFIG_PROTECT_MASK
508 #export PORTAGE_TMPDIR=$(/usr/lib/portage/bin/portageq envvar PORTAGE_TMPDIR)
510 rm -rf "${TMP}" 2> /dev/null
511 mkdir "${TMP}" || die "failed to create temp dir" 1
512 # make sure we have a secure directory to work in
513 chmod 0700 "${TMP}" || die "failed to set perms on temp dir" 1
514 chown ${UID:-0}:${GID:-0} "${TMP}" || die "failed to set ownership on temp dir" 1
516 # I need the CONFIG_PROTECT value
517 #CONFIG_PROTECT=$(/usr/lib/portage/bin/portageq envvar CONFIG_PROTECT)
518 #CONFIG_PROTECT_MASK=$(/usr/lib/portage/bin/portageq envvar CONFIG_PROTECT_MASK)
520 # load etc-config's configuration
521 EU_AUTOMERGE=$(get_config eu_automerge)
522 rm_opts=$(get_config rm_opts)
523 mv_opts=$(get_config mv_opts)
524 cp_opts=$(get_config cp_opts)
525 pager=$(get_config pager)
526 diff_command=$(get_config diff_command)
527 using_editor=$(get_config using_editor)
528 merge_command=$(get_config merge_command)
529 declare -i mode=$(get_config mode)
530 [[ -z ${mode} ]] && mode=0
531 [[ -z ${pager} ]] && pager="cat"
533 if [ "${using_editor}" == 0 ]; then
534 # Sanity check to make sure diff exists and works
535 echo > "${TMP}"/.diff-test-1
536 echo > "${TMP}"/.diff-test-2
538 if ! diff_command "${TMP}"/.diff-test-1 "${TMP}"/.diff-test-2 ; then
539 die "'${diff_command}' does not seem to work, aborting" 1
542 if ! type ${diff_command%% *} >/dev/null; then
543 die "'${diff_command}' does not seem to work, aborting" 1
547 if [[ ${mode} == "1" ]] ; then
548 if ! type dialog >/dev/null || ! dialog --help >/dev/null ; then
549 die "mode=1 and 'dialog' not found or not executable, aborting" 1
553 #echo "rm_opts: $rm_opts, mv_opts: $mv_opts, cp_opts: $cp_opts"
554 #echo "pager: $pager, diff_command: $diff_command, merge_command: $merge_command"
556 if (( ${mode} == 0 )); then
564 declare title="Gentoo's etc-update tool!"
568 until (( ${input} == -1 )); do
569 if (( ${count} == 0 )); then
570 die "Nothing left to do; exiting. :)" 0
573 if (( ${input} != -1 )); then
578 die "User termination!" 0