summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoshless2018-07-17 01:46:39 +0200
committerRoshless2018-07-17 01:46:39 +0200
commit48da19f479c079e62e2c4b3886841866228f6b01 (patch)
tree2c91342d03441e81ec8c625dde59b463e83e3431
downloadaur-48da19f479c079e62e2c4b3886841866228f6b01.tar.gz
first version
-rw-r--r--.SRCINFO18
-rw-r--r--PKGBUILD23
-rw-r--r--etc-update895
-rw-r--r--etc-update.conf82
4 files changed, 1018 insertions, 0 deletions
diff --git a/.SRCINFO b/.SRCINFO
new file mode 100644
index 000000000000..ce300cec3049
--- /dev/null
+++ b/.SRCINFO
@@ -0,0 +1,18 @@
+pkgbase = etc-update-nogithub
+ pkgdesc = CLI to interactively merge .pacnew in /etc. I don't trust gentoo's github.
+ pkgver = 20180717
+ pkgrel = 1
+ url = https://wiki.gentoo.org/wiki/Handbook:X86/Portage/Tools#etc-update
+ arch = any
+ license = GPL
+ makedepends = git
+ depends = bash
+ provides = etc-update
+ conflicts = etc-update
+ source = etc-update
+ source = etc-update.conf
+ md5sums = 1de63bd8e07fa78779eecf864b6021e4
+ md5sums = ca8d58262382a7ead33b69469f661f5e
+
+pkgname = etc-update-nogithub
+
diff --git a/PKGBUILD b/PKGBUILD
new file mode 100644
index 000000000000..6b22797686af
--- /dev/null
+++ b/PKGBUILD
@@ -0,0 +1,23 @@
+# Maintainer: Roshless <aur@roshless.com>
+_appname_=etc-update
+pkgname=${_appname_}-nogithub
+pkgdesc="CLI to interactively merge .pacnew in /etc. I don't trust gentoo's github."
+pkgver=20180717
+pkgrel=1
+arch=('any')
+url="https://wiki.gentoo.org/wiki/Handbook:X86/Portage/Tools#etc-update"
+license=('GPL')
+depends=('bash')
+makedepends=('git')
+provides=("${_appname_}")
+conflicts=("${_appname_}")
+source=("etc-update"
+ "etc-update.conf")
+md5sums=('1de63bd8e07fa78779eecf864b6021e4'
+ 'ca8d58262382a7ead33b69469f661f5e')
+
+package() {
+ install -Dm 0755 "${_appname_}" "$pkgdir/usr/bin/${pkgname}"
+ install -Dm 0644 "${_appname_}.conf" "$pkgdir/etc/${pkgname}.conf"
+}
+
diff --git a/etc-update b/etc-update
new file mode 100644
index 000000000000..850f6a21b88e
--- /dev/null
+++ b/etc-update
@@ -0,0 +1,895 @@
+#!/bin/bash
+# Copyright 1999-2012 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+# Author Brandon Low <lostlogic@gentoo.org>
+# Mike Frysinger <vapier@gentoo.org>
+#
+# Previous version (from which I've borrowed a few bits) by:
+# Jochem Kossen <j.kossen@home.nl>
+# Leo Lipelis <aeoo@gentoo.org>
+# Karl Trygve Kalleberg <karltk@gentoo.org>
+
+cd /
+
+type -P gsed >/dev/null && sed() { gsed "$@"; }
+
+get_config() {
+ # the sed here does:
+ # - strip off comments
+ # - match lines that set item in question
+ # - delete the "item =" part
+ # - store the actual value into the hold space
+ # - on the last line, restore the hold space and print it
+ # If there's more than one of the same configuration item, then
+ # the store to the hold space clobbers previous value so the last
+ # setting takes precedence.
+ local match=$1
+ eval $(sed -n -r \
+ -e 's:[[:space:]]*#.*$::' \
+ -e "/^[[:space:]]*${match}[[:space:]]*=/{s:^([^=]*)=[[:space:]]*([\"']{0,1})(.*)\2:\1=\2\3\2:;H}" \
+ -e '${g;p}' \
+ "${PORTAGE_CONFIGROOT}"etc/etc-update.conf)
+}
+
+OS_RELEASE_ID=$(cat /etc/os-release 2>/dev/null | grep '^ID=' | cut -d'=' -f2 | sed -e 's/"//g')
+
+case $OS_RELEASE_ID in
+ suse|opensuse|opensuse-leap|opensuse-tumbleweed) OS_FAMILY='rpm' ;;
+ fedora|rhel) OS_FAMILY='rpm' ;;
+ arch|archarm|manjaro|antergos) OS_FAMILY='arch' NEW_EXT='pacnew';;
+ *) OS_FAMILY='gentoo' ;;
+esac
+
+if [[ $OS_FAMILY == 'gentoo' ]]; then
+ get_basename() {
+ printf -- '%s\n' "${1:10}"
+ }
+ get_basename_find_opt() {
+ echo "._cfg????_${1}"
+ }
+ get_scan_regexp() {
+ echo "s:\(^.*/\)\(\._cfg[0-9]*_\)\(.*$\):\1\2\3$b\1$b\2$b\3:"
+ }
+ get_live_file() {
+ echo "${rpath}/${rfile:10}"
+ }
+elif [[ $OS_FAMILY == 'arch' ]]; then
+ get_basename() {
+ printf -- '%s\n' "${1%.${NEW_EXT}}"
+ }
+ get_basename_find_opt() {
+ printf -- '%s\n' "${1}.${NEW_EXT}"
+ }
+ get_scan_regexp() {
+ echo "s:\(^.*/\)\(.*\)\(\.${NEW_EXT}\):\1\2\3$b\1$b\3$b\2:"
+ }
+ get_live_file() {
+ printf -- '%s\n' "${cfg_file%.${NEW_EXT}}"
+ }
+# In rpm we have rpmsave, rpmorig, and rpmnew.
+elif [[ $OS_FAMILY == 'rpm' ]]; then
+ get_basename() {
+ printf -- '%s\n' "${1}" |sed -e 's/\.rpmsave$//' -e 's/\.rpmnew$//' -e 's/\.rpmorig$//'
+ }
+ get_basename_find_opt() {
+ printf -- '%s\n' "${1}.rpm???*"
+ }
+ get_scan_regexp() {
+ echo "s:\(^.*/\)\(.*\)\(\.\(rpmnew|rpmsave|rpmorig\)\):\1\2\3$b\1$b\3$b\2:"
+ }
+ get_live_file() {
+ printf -- '%s\n' "${cfg_file}" |sed -e 's/\.rpmsave$//' -e 's/\.rpmnew$//' -e 's/\.rpmorig$//'
+ }
+fi
+
+cmd_var_is_valid() {
+ # return true if the first whitespace-separated token contained
+ # in "${1}" is an executable file, false otherwise
+ [[ -x $(type -P ${1%%[[:space:]]*}) ]]
+}
+
+diff_command() {
+ local cmd=${diff_command//%file1/$1}
+ ${cmd//%file2/$2}
+}
+
+# Usage: do_mv_ln [options] <src> <dst>
+# Files have to be the last two args, and has to be
+# files so we can handle symlinked target sanely.
+do_mv_ln() {
+ local opts=( ${@:1:$(( $# - 2 ))} )
+ local src=${@:$(( $# - 1 )):1}
+ local dst=${@:$(( $# - 0 )):1}
+
+ if [[ ! -L ${src} && -L ${dst} ]] ; then #330221
+ local lfile=$(readlink "${dst}")
+ [[ ${lfile} == /* ]] || lfile="${dst%/*}/${lfile}"
+ echo " Target is a symlink; replacing ${lfile}"
+ dst=${lfile}
+ elif [[ -d ${dst} && ! -L ${dst} ]] ; then
+ # If ${dst} is a directory, do not move the file
+ # inside of it if this fails.
+ rmdir "${dst}" || return
+ fi
+
+ mv "${opts[@]}" "${src}" "${dst}"
+}
+
+scan() {
+ ${QUIET} || echo "Scanning Configuration files..."
+ rm -rf "${TMP}"/files > /dev/null 2>&1
+ mkdir "${TMP}"/files || die "Failed mkdir command!"
+ count=0
+ input=0
+ local find_opts
+ local path
+
+ for path in ${SCAN_PATHS} ; do
+ path="${EROOT%/}${path}"
+
+ if [[ ! -d ${path} ]] ; then
+ # Protect files that don't exist (bug #523684). If the
+ # parent directory doesn't exist, we can safely skip it.
+ path=${path%/}
+ [[ -d ${path%/*} ]] || continue
+ local name_opt=$(get_basename_find_opt "${path##*/}")
+ path="${path%/*}"
+ find_opts=( -maxdepth 1 )
+ else
+ # Do not traverse hidden directories such as .svn or .git.
+ local name_opt=$(get_basename_find_opt '*')
+ find_opts=( -name '.*' -type d -prune -o )
+ fi
+ ${case_insensitive} && \
+ find_opts+=( -iname ) || find_opts+=( -name )
+ find_opts+=( "$name_opt" )
+ find_opts+=( ! -name '.*~' ! -iname '.*.bak' -print )
+
+ if [[ ! -w ${path} ]] ; then
+ [[ -e ${path} ]] || continue
+ die "Need write access to ${path}"
+ fi
+
+ local file ofile b=$'\001'
+ local scan_regexp=$(get_scan_regexp)
+ for file in $(find "${path}"/ "${find_opts[@]}" |
+ sed \
+ -e 's://*:/:g' \
+ -e "${scan_regexp}" |
+ sort -t"$b" -k2,2 -k4,4 -k3,3 |
+ LC_ALL=C cut -f1 -d"$b")
+ do
+ local rpath rfile cfg_file live_file
+ rpath=${file%/*}
+ rfile=${file##*/}
+ cfg_file="${rpath}/${rfile}"
+ live_file=$(get_live_file)
+
+ local mpath
+ for mpath in ${CONFIG_PROTECT_MASK}; do
+ mpath="${EROOT%/}${mpath}"
+ if [[ "${rpath}" == "${mpath}"* ]] ; then
+ ${QUIET} || echo "Updating masked file: ${live_file}"
+ mv "${cfg_file}" "${live_file}"
+ continue 2
+ fi
+ done
+ if [[ -L ${file} ]] ; then
+ if [[ -L ${live_file} && \
+ $(readlink "${live_file}") == $(readlink "${file}") ]]
+ then
+ rm -f "${file}"
+ continue
+ fi
+ if [[ $(get_basename "${ofile}") != $(get_basename "${rfile}") ]] ||
+ [[ ${opath} != ${rpath} ]]
+ then
+ : $(( ++count ))
+ echo "${live_file}" > "${TMP}"/files/${count}
+ fi
+ echo "${cfg_file}" >> "${TMP}"/files/${count}
+ ofile="${rfile}"
+ opath="${rpath}"
+ continue
+ fi
+ if [[ ! -f ${file} ]] ; then
+ ${QUIET} || echo "Skipping non-file ${file} ..."
+ continue
+ fi
+
+ if [[ $(get_basename "${ofile}") != $(get_basename "${rfile}") ]] ||
+ [[ ${opath} != ${rpath} ]]
+ then
+ MATCHES=0
+ if ! [[ -f ${cfg_file} && -f ${live_file} ]] ; then
+ MATCHES=0
+ elif [[ ${eu_automerge} == "yes" ]] ; then
+ if [[ ! -e ${cfg_file} || ! -e ${live_file} ]] ; then
+ MATCHES=0
+ else
+ diff -Bbua "${cfg_file}" "${live_file}" | \
+ sed -n -r \
+ -e '/^[+-]/{/^([+-][\t ]*(#|$)|-{3} |\+{3} )/d;q1}'
+ : $(( MATCHES = ($? == 0) ))
+ fi
+
+ else
+ diff -Nbua "${cfg_file}" "${live_file}" |
+ sed -n \
+ -e '/# .Header:/d' \
+ -e '/^[+-][^+-]/q1'
+ : $(( MATCHES = ($? == 0) ))
+ fi
+
+ if [[ ${MATCHES} == 1 ]] ; then
+ ${QUIET} || echo "Automerging trivial changes in: ${live_file}"
+ do_mv_ln "${cfg_file}" "${live_file}"
+ continue
+ else
+ : $(( ++count ))
+ echo "${live_file}" > "${TMP}"/files/${count}
+ echo "${cfg_file}" >> "${TMP}"/files/${count}
+ ofile="${rfile}"
+ opath="${rpath}"
+ continue
+ fi
+ fi
+
+ if ! diff -Nbua "${cfg_file}" "${rpath}/${ofile}" |
+ sed -n \
+ -e '/# .Header:/d' \
+ -e '/^[+-][^+-]/q1'
+ then
+ echo "${cfg_file}" >> "${TMP}"/files/${count}
+ ofile="${rfile}"
+ opath="${rpath}"
+ else
+ mv "${cfg_file}" "${rpath}/${ofile}"
+ continue
+ fi
+ done
+ done
+}
+
+parse_automode_flag() {
+ case $1 in
+ -9)
+ local reply
+ read -p "Are you sure that you want to delete all updates (type YES): " reply
+ if [[ ${reply} != "YES" ]] ; then
+ echo "Did not get a 'YES', so ignoring request"
+ return 1
+ else
+ parse_automode_flag -7
+ export rm_opts=""
+ fi
+ ;;
+ -7)
+ input=0
+ export DELETE_ALL="yes"
+ ;;
+ -5)
+ parse_automode_flag -3
+ export mv_opts=" ${mv_opts} "
+ mv_opts="${mv_opts// -i / }"
+ NONINTERACTIVE_MV=true
+ ;;
+ -3)
+ input=0
+ export OVERWRITE_ALL="yes"
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ return 0
+}
+
+sel_file() {
+ local -i isfirst=0
+ until [[ -f ${TMP}/files/${input} ]] || \
+ [[ ${input} == -1 ]] || \
+ [[ ${input} == -3 ]]
+ do
+ local allfiles=( $(cd "${TMP}"/files/ && printf '%s\n' * | sort -n) )
+ local isfirst=${allfiles[0]}
+
+ # Optimize: no point in building the whole file list if
+ # we're not actually going to talk to the user.
+ if [[ ${OVERWRITE_ALL} == "yes" || ${DELETE_ALL} == "yes" ]] ; then
+ input=0
+ else
+ local numfiles=${#allfiles[@]}
+ local numwidth=${#numfiles}
+ local file fullfile line
+ for file in "${allfiles[@]}" ; do
+ fullfile="${TMP}/files/${file}"
+ line=$(head -n1 "${fullfile}")
+ printf '%*i%s %s' ${numwidth} ${file} "${PAR}" "${line}"
+ if [[ ${mode} == 0 ]] ; then
+ local numupdates=$(( $(wc -l <"${fullfile}") - 1 ))
+ echo " (${numupdates})"
+ else
+ echo
+ fi
+ done > "${TMP}"/menuitems
+
+ clear
+
+ if [[ ${mode} == 0 ]] ; then
+ cat <<-EOF
+ The following is the list of files which need updating, each
+ configuration file is followed by a list of possible replacement files.
+ $(<"${TMP}"/menuitems)
+ Please select a file to edit by entering the corresponding number.
+ (don't use -3, -5, -7 or -9 if you're unsure what to do)
+ (-1 to exit) (${_3_HELP_TEXT})
+ (${_5_HELP_TEXT})
+ (${_7_HELP_TEXT})
+ EOF
+ printf " (${_9_HELP_TEXT}): "
+ input=$(read_int)
+ else
+ dialog \
+ --title "${title}" \
+ --menu "Please select a file to update" \
+ 0 0 0 $(<"${TMP}"/menuitems) \
+ 2> "${TMP}"/input \
+ || die "$(<"${TMP}"/input)\n\nUser termination!" 0
+ input=$(<"${TMP}"/input)
+ fi
+ : ${input:=0}
+
+ if [[ ${input} != 0 ]] ; then
+ parse_automode_flag ${input} || continue
+ fi
+ fi # -3 automerge
+ if [[ ${input} == 0 ]] ; then
+ input=${isfirst}
+ fi
+ done
+}
+
+user_special() {
+ local special="${PORTAGE_CONFIGROOT}etc/etc-update.special"
+
+ if [[ -r ${special} ]] ; then
+ if [[ -z $1 ]] ; then
+ error "user_special() called without arguments"
+ return 1
+ fi
+ local pat
+ while read -r pat ; do
+ echo "$1" | grep -q "${pat}" && return 0
+ done < "${special}"
+ fi
+ return 1
+}
+
+read_int() {
+ # Read an integer from stdin. Continously loops until a valid integer is
+ # read. This is a workaround for odd behavior of bash when an attempt is
+ # made to store a value such as "1y" into an integer-only variable.
+ local my_input
+ while : ; do
+ read my_input
+ # failed integer conversions will break a loop unless they're enclosed
+ # in a subshell.
+ echo "${my_input}" | (declare -i x; read x) 2>/dev/null && break
+ printf 'Value "%s" is not valid. Please enter an integer value: ' "${my_input}" >&2
+ done
+ echo ${my_input}
+}
+
+do_file() {
+ interactive_echo() { [[ ${OVERWRITE_ALL} != yes ]] && [[ ${DELETE_ALL} != yes ]] && echo; }
+ interactive_echo
+ local -i my_input
+ local -i linecnt
+ local fullfile="${TMP}/files/${input}"
+ local ofile=$(head -n1 "${fullfile}")
+
+ # Walk through all the pending updates for this one file.
+ linecnt=$(wc -l <"${fullfile}")
+ while (( linecnt > 1 )) ; do
+ if (( linecnt == 2 )) ; then
+ # Only one update ... keeps things simple.
+ my_input=1
+ else
+ my_input=0
+ fi
+
+ # Optimize: no point in scanning the file list when we know
+ # we're just going to consume all the ones available.
+ if [[ ${OVERWRITE_ALL} == "yes" || ${DELETE_ALL} == "yes" ]] ; then
+ my_input=1
+ fi
+
+ # Figure out which file they wish to operate on.
+ while (( my_input <= 0 || my_input >= linecnt )) ; do
+ local fcount=0
+ for line in $(<"${fullfile}"); do
+ if (( fcount > 0 )); then
+ printf '%i%s %s\n' ${fcount} "${PAR}" "${line}"
+ fi
+ : $(( ++fcount ))
+ done > "${TMP}"/menuitems
+
+ if [[ ${mode} == 0 ]] ; then
+ echo "Below are the new config files for ${ofile}:"
+ cat "${TMP}"/menuitems
+ echo -n "Please select a file to process (-1 to exit this file): "
+ my_input=$(read_int)
+ else
+ dialog \
+ --title "${title}" \
+ --menu "Please select a file to process for ${ofile}" \
+ 0 0 0 $(<"${TMP}"/menuitems) \
+ 2> "${TMP}"/input \
+ || die "$(<"${TMP}"/input)\n\nUser termination!" 0
+ my_input=$(<"${TMP}"/input)
+ fi
+
+ if [[ ${my_input} == 0 ]] ; then
+ # Auto select the first file.
+ my_input=1
+ elif [[ ${my_input} == -1 ]] ; then
+ input=0
+ return
+ fi
+ done
+
+ # First line is the old file while the rest are the config files.
+ : $(( ++my_input ))
+ local file=$(sed -n -e "${my_input}p" "${fullfile}")
+ do_cfg "${file}" "${ofile}"
+
+ sed -i -e "${my_input}d" "${fullfile}"
+
+ : $(( --linecnt ))
+ done
+
+ interactive_echo
+ rm "${fullfile}"
+ : $(( --count ))
+}
+
+show_diff() {
+ clear
+ local file1=$1 file2=$2 files=("$1" "$2") \
+ diff_files=() file i tmpdir
+
+ if [[ -L ${file1} && ! -L ${file2} &&
+ -f ${file1} && -f ${file2} ]] ; then
+ # If a regular file replaces a symlink to a regular file, then
+ # show the diff between the regular files (bug #330221).
+ diff_files=("${file1}" "${file2}")
+ else
+ for i in 0 1 ; do
+ if [[ ! -L ${files[$i]} && -f ${files[$i]} ]] ; then
+ diff_files[$i]=${files[$i]}
+ continue
+ fi
+ [[ -n ${tmpdir} ]] || \
+ tmpdir=$(mktemp -d "${TMP}/symdiff-XXX")
+ diff_files[$i]=${tmpdir}/${i}
+ if [[ ! -L ${files[$i]} && ! -e ${files[$i]} ]] ; then
+ echo "/dev/null" > "${diff_files[$i]}"
+ elif [[ -L ${files[$i]} ]] ; then
+ echo "SYM: ${file1} -> $(readlink "${files[$i]}")" > \
+ "${diff_files[$i]}"
+ elif [[ -d ${files[$i]} ]] ; then
+ echo "DIR: ${file1}" > "${diff_files[$i]}"
+ elif [[ -p ${files[$i]} ]] ; then
+ echo "FIF: ${file1}" > "${diff_files[$i]}"
+ else
+ echo "DEV: ${file1}" > "${diff_files[$i]}"
+ fi
+ done
+ fi
+
+ if [[ ${using_editor} == 0 ]] ; then
+ (
+ echo "Showing differences between ${file1} and ${file2}"
+ diff_command "${diff_files[0]}" "${diff_files[1]}"
+ ) | ${pager}
+ else
+ echo "Beginning of differences between ${file1} and ${file2}"
+ diff_command "${diff_files[0]}" "${diff_files[1]}"
+ echo "End of differences between ${file1} and ${file2}"
+ fi
+
+ [[ -n ${tmpdir} ]] && rm -rf "${tmpdir}"
+}
+
+do_cfg() {
+ local file=$1
+ local ofile=$2
+ local -i my_input=0
+
+ until (( my_input == -1 )) || [[ ! -f ${file} && ! -L ${file} ]] ; do
+ if [[ "${OVERWRITE_ALL}" == "yes" ]] && ! user_special "${ofile}"; then
+ my_input=1
+ elif [[ "${DELETE_ALL}" == "yes" ]] && ! user_special "${ofile}"; then
+ my_input=2
+ else
+ show_diff "${ofile}" "${file}"
+ if [[ -L ${file} && ! -L ${ofile} ]] ; then
+ cat <<-EOF
+
+ -------------------------------------------------------------
+ NOTE: File is a symlink to another file. REPLACE recommended.
+ The original file may simply have moved. Please review.
+ -------------------------------------------------------------
+
+ EOF
+ fi
+ cat <<-EOF
+
+ File: ${file}
+ 1) Replace original with update
+ 2) Delete update, keeping original as is
+ 3) Interactively merge original with update
+ 4) Show differences again
+ 5) Save update as example config
+ EOF
+ printf 'Please select from the menu above (-1 to ignore this update): '
+ my_input=$(read_int)
+ fi
+
+ case ${my_input} in
+ 1) echo "Replacing ${ofile} with ${file}"
+ do_mv_ln ${mv_opts} "${file}" "${ofile}"
+ [[ -n ${OVERWRITE_ALL} ]] && my_input=-1
+ continue
+ ;;
+ 2) echo "Deleting ${file}"
+ rm ${rm_opts} "${file}"
+ [[ -n ${DELETE_ALL} ]] && my_input=-1
+ continue
+ ;;
+ 3) do_merge "${file}" "${ofile}"
+ my_input=${?}
+# [[ ${my_input} == 255 ]] && my_input=-1
+ continue
+ ;;
+ 4) continue
+ ;;
+ 5) do_distconf "${file}" "${ofile}"
+ ;;
+ *) continue
+ ;;
+ esac
+ done
+}
+
+do_merge() {
+ # make sure we keep the merged file in the secure tempdir
+ # so we dont leak any information contained in said file
+ # (think of case where the file has 0600 perms; during the
+ # merging process, the temp file gets umask perms!)
+
+ local file="${1}"
+ local ofile="${2}"
+ local mfile="${TMP}/${2#/}.merged"
+ local -i my_input=0
+
+ if [[ -L ${file} && -L ${ofile} ]] ; then
+ echo "Both files are symlinks, so they will not be merged."
+ return 0
+ elif [[ ! -f ${file} ]] ; then
+ echo "Non-regular file cannot be merged: ${file}"
+ return 0
+ elif [[ ! -f ${ofile} ]] ; then
+ echo "Non-regular file cannot be merged: ${ofile}"
+ return 0
+ fi
+
+
+ echo "${file} ${ofile} ${mfile}"
+
+ if [[ -e ${mfile} ]] ; then
+ echo "A previous version of the merged file exists, cleaning..."
+ rm ${rm_opts} "${mfile}"
+ fi
+
+ # since mfile will be like $TMP/path/to/original-file.merged, we
+ # need to make sure the full /path/to/ exists ahead of time
+ mkdir -p "${mfile%/*}"
+
+ until (( my_input == -1 )); do
+ echo "Merging ${file} and ${ofile}"
+ $(echo "${merge_command}" |
+ sed -e "s:%merged:${mfile}:g" \
+ -e "s:%orig:${ofile}:g" \
+ -e "s:%new:${file}:g")
+ until (( my_input == -1 )); do
+ cat <<-EOF
+ 1) Replace ${ofile} with merged file
+ 2) Show differences between merged file and original
+ 3) Remerge original with update
+ 4) Edit merged file
+ 5) Return to the previous menu
+ EOF
+ printf 'Please select from the menu above (-1 to exit, losing this merge): '
+ my_input=$(read_int)
+ case ${my_input} in
+ 1) echo "Replacing ${ofile} with ${mfile}"
+ if [[ ${USERLAND} == BSD ]] ; then
+ chown "$(stat -f %Su:%Sg "${ofile}")" "${mfile}"
+ chmod $(stat -f %Mp%Lp "${ofile}") "${mfile}"
+ else
+ chown --reference="${ofile}" "${mfile}"
+ chmod --reference="${ofile}" "${mfile}"
+ fi
+ do_mv_ln ${mv_opts} "${mfile}" "${ofile}"
+ rm ${rm_opts} "${file}"
+ return 255
+ ;;
+ 2) show_diff "${ofile}" "${mfile}"
+ continue
+ ;;
+ 3) break
+ ;;
+ 4) ${EDITOR:-nano -w} "${mfile}"
+ continue
+ ;;
+ 5) rm ${rm_opts} "${mfile}"
+ return 0
+ ;;
+ *) continue
+ ;;
+ esac
+ done
+ done
+ rm ${rm_opts} "${mfile}"
+ return 255
+}
+
+do_distconf() {
+ # search for any previously saved distribution config
+ # files and number the current one accordingly
+
+ local file=$1 ofile=$2
+ local -i count
+ local suffix
+ local efile
+
+ for (( count = 0; count <= 9999; ++count )) ; do
+ suffix=$(printf ".dist_%04i" ${count})
+ efile="${ofile}${suffix}"
+ if [[ ! -f ${efile} && ! -L ${efile} ]] ; then
+ mv ${mv_opts} "${file}" "${efile}"
+ break
+ elif [[ -L ${efile} && -L ${file} ]] ; then
+ if [[ $(readlink "${efile}") == $(readlink "${file}") ]] ; then
+ # replace identical copy
+ mv "${file}" "${efile}"
+ break
+ fi
+ elif [[ -L ${efile} || -L ${file} ]] ; then
+ # not the same file types
+ continue
+ else
+ local ret=
+ if [[ ${using_editor} == 0 ]] ; then
+ diff_command "${file}" "${efile}" &> /dev/null
+ ret=$?
+ else
+ # fall back to plain diff
+ diff -q "${file}" "${efile}" &> /dev/null
+ ret=$?
+ fi
+ if [[ ${ret} == 0 ]] ; then
+ # replace identical copy
+ mv "${file}" "${efile}"
+ break
+ fi
+ fi
+ done
+}
+
+error() { echo "etc-update: ERROR: $*" 1>&2 ; return 1 ; }
+die() {
+ trap SIGTERM
+ trap SIGINT
+ local msg=$1 exitcode=${2:-1}
+
+ if [[ ${exitcode} -eq 0 ]] ; then
+ ${QUIET} || printf 'Exiting: %b\n' "${msg}"
+ scan > /dev/null
+ ! ${QUIET} && [[ ${count} -gt 0 ]] && echo "NOTE: ${count} updates remaining"
+ else
+ error "${msg}"
+ fi
+
+ rm -rf "${TMP}"
+ exit ${exitcode}
+}
+
+_3_HELP_TEXT="-3 to auto merge all files"
+_5_HELP_TEXT="-5 to auto-merge AND not use 'mv -i'"
+_7_HELP_TEXT="-7 to discard all updates"
+_9_HELP_TEXT="-9 to discard all updates AND not use 'rm -i'"
+usage() {
+ cat <<-EOF
+ etc-update: Handle configuration file updates
+
+ Usage: etc-update [options] [paths to scan]
+
+ If no paths are specified, then \${CONFIG_PROTECT} will be used.
+
+ Options:
+ -d, --debug Enable shell debugging
+ -h, --help Show help and run away
+ -p, --preen Automerge trivial changes only and quit
+ -q, --quiet Show only essential output
+ -v, --verbose Show settings and such along the way
+ -V, --version Show version and trundle away
+
+ --automode <mode>
+ ${_3_HELP_TEXT}
+ ${_5_HELP_TEXT}
+ ${_7_HELP_TEXT}
+ ${_9_HELP_TEXT}
+ EOF
+
+ [[ $# -gt 1 ]] && printf "\nError: %s\n" "${*:2}" 1>&2
+
+ exit ${1:-0}
+}
+
+#
+# Run the script
+#
+
+declare -i count=0
+declare input=0
+declare title="Gentoo's etc-update tool!"
+
+PREEN=false
+SET_X=false
+QUIET=false
+VERBOSE=false
+NONINTERACTIVE_MV=false
+while [[ -n $1 ]] ; do
+ case $1 in
+ -d|--debug) SET_X=true;;
+ -h|--help) usage;;
+ -p|--preen) PREEN=true;;
+ -q|--quiet) QUIET=true;;
+ -v|--verbose) VERBOSE=true;;
+ -V|--version) emerge --version; exit 0;;
+ --automode) parse_automode_flag $2 && shift || usage 1 "Invalid mode '$2'";;
+ -*) usage 1 "Invalid option '$1'";;
+ *) break;;
+ esac
+ shift
+done
+${SET_X} && set -x
+
+if [[ $OS_FAMILY == 'rpm' ]]; then
+ PORTAGE_CONFIGROOT='/'
+ PORTAGE_TMPDIR='/tmp'
+ CONFIG_PROTECT='/etc /usr/share'
+ CONFIG_PROTECT_MASK=''
+ [[ -f /etc/sysconfig/etc-update ]] && . /etc/sysconfig/etc-update
+elif [[ $OS_FAMILY == 'arch' ]]; then
+ PORTAGE_CONFIGROOT='/'
+ PORTAGE_TMPDIR='/tmp'
+ CONFIG_PROTECT='/etc /usr/lib /usr/share/config'
+ CONFIG_PROTECT_MASK=''
+fi
+
+portage_vars=(
+ CONFIG_PROTECT{,_MASK}
+ FEATURES
+ PORTAGE_CONFIGROOT
+ PORTAGE_INST_{G,U}ID
+ PORTAGE_TMPDIR
+ EROOT
+ USERLAND
+ NOCOLOR
+)
+
+if type -P portageq > /dev/null; then
+ eval $(${PORTAGE_PYTHON:+"${PORTAGE_PYTHON}"} "$(type -P portageq)" envvar -v "${portage_vars[@]}")
+else
+ [[ $OS_FAMILY == 'gentoo' ]] && die "missing portageq"
+fi
+
+export PORTAGE_TMPDIR
+SCAN_PATHS=${*:-${CONFIG_PROTECT}}
+[[ " ${FEATURES} " == *" case-insensitive-fs "* ]] && \
+ case_insensitive=true || case_insensitive=false
+
+TMP="${PORTAGE_TMPDIR}/etc-update-$$"
+trap "die terminated" SIGTERM
+trap "die interrupted" SIGINT
+
+rm -rf "${TMP}" 2>/dev/null
+mkdir "${TMP}" || die "failed to create temp dir"
+# make sure we have a secure directory to work in
+chmod 0700 "${TMP}" || die "failed to set perms on temp dir"
+chown ${PORTAGE_INST_UID:-0}:${PORTAGE_INST_GID:-0} "${TMP}" || \
+ die "failed to set ownership on temp dir"
+
+# Get all the user settings from etc-update.conf
+cfg_vars=(
+ clear_term
+ eu_automerge
+ rm_opts
+ mv_opts
+ pager
+ diff_command
+ using_editor
+ merge_command
+ mode
+)
+# default them all to ""
+eval "${cfg_vars[@]/%/=}"
+# then extract them all from the conf in one shot
+# (ugly var at end is due to printf appending a '|' to last item)
+get_config "($(printf '%s|' "${cfg_vars[@]}")NOVARFOROLDMEN)"
+
+# finally setup any specific defaults
+: ${mode:="0"}
+if ! cmd_var_is_valid "${pager}" ; then
+ pager=${PAGER}
+ cmd_var_is_valid "${pager}" || pager=cat
+fi
+
+[[ ${clear_term} == "yes" ]] || clear() { :; }
+
+if [[ ${using_editor} == "0" ]] ; then
+ # Sanity check to make sure diff exists and works
+ echo > "${TMP}"/.diff-test-1
+ echo > "${TMP}"/.diff-test-2
+
+ if ! diff_command "${TMP}"/.diff-test-1 "${TMP}"/.diff-test-2 ; then
+ die "'${diff_command}' does not seem to work, aborting"
+ fi
+else
+ # NOTE: cmd_var_is_valid doesn't work with diff_command="eval emacs..."
+ # because it uses type -P.
+ if ! type ${diff_command%%[[:space:]]*} >/dev/null; then
+ die "'${diff_command}' does not seem to work, aborting"
+ fi
+fi
+
+if [[ ${mode} == "0" ]] ; then
+ PAR=")"
+else
+ PAR=""
+ if ! type dialog >/dev/null || ! dialog --help >/dev/null ; then
+ die "mode=1 and 'dialog' not found or not executable, aborting"
+ fi
+fi
+
+if ${NONINTERACTIVE_MV} ; then
+ export mv_opts=" ${mv_opts} "
+ mv_opts="${mv_opts// -i / }"
+fi
+
+if ${VERBOSE} ; then
+ for v in "${portage_vars[@]}" "${cfg_vars[@]}" TMP SCAN_PATHS ; do
+ echo "${v}=${!v}"
+ done
+fi
+
+scan
+
+${PREEN} && exit 0
+
+until (( input == -1 )); do
+ if (( count == 0 )); then
+ die "Nothing left to do; exiting. :)" 0
+ fi
+ sel_file
+ if (( input != -1 )); then
+ do_file
+ fi
+done
+
+die "User termination!" 0
diff --git a/etc-update.conf b/etc-update.conf
new file mode 100644
index 000000000000..970986251111
--- /dev/null
+++ b/etc-update.conf
@@ -0,0 +1,82 @@
+# /etc/etc-update.conf: config file for `etc-update` utility
+# edit the lines below to your liking
+
+# mode - 0 for text, 1 for menu (support incomplete)
+# note that you need dev-util/dialog installed
+mode="0"
+
+# Whether to clear the term prior to each display
+#clear_term="yes"
+clear_term="no"
+
+# Whether trivial/comment changes should be automerged
+eu_automerge="yes"
+
+# arguments used whenever rm is called
+rm_opts="-i"
+
+# arguments used whenever mv is called
+mv_opts="-i"
+
+# arguments used whenever cp is called
+cp_opts="-i"
+
+# set the pager for use with diff commands (this will
+# cause the PAGER environment variable to be ignored)
+#pager="less"
+
+# For emacs-users (see NOTE_2)
+# diff_command="eval emacs -nw --eval=\'\(ediff\ \"%file1\"\ \"%file2\"\)\'"
+#using_editor=1
+
+# vim-users: you CAN use vimdiff for diff_command. (see NOTE_1 and NOTE_2)
+#diff_command="vim -d %file1 %file2"
+#using_editor=1
+
+# If using colordiff instead of diff, the less -R option may be required
+# for correct display (see 'pager' setting above).
+diff_command="diff -uN %file1 %file2"
+using_editor=0
+
+
+# vim-users: don't use vimdiff for merging (see NOTE_1)
+merge_command="sdiff -s -o %merged %orig %new"
+
+# EXPLANATION
+#
+# pager:
+#
+# Examples of pager usage:
+# pager="cat" # don't use a pager
+# pager="less -E" # less
+# pager="more" # more
+#
+#
+# diff_command:
+#
+# Arguments:
+# %file1 [REQUIRED]
+# %file2 [REQUIRED]
+#
+# Examples of diff_command:
+# diff_command="diff -uN %file1 %file2" # diff
+# diff_command="vim -d %file1 %file2" # vimdiff
+#
+#
+# merge_command:
+#
+# Arguments:
+# %orig [REQUIRED]
+# %new [REQUIRED]
+# %merged [REQUIRED]
+#
+# Examples of merge_command:
+# merge_command="sdiff -s -o %merged %old %new" # sdiff
+#
+
+# NOTE_1: Editors such as vim/vimdiff are not usable for the merge_command
+# because it is not known what filenames the produced files have (the user can
+# choose while using those programs)
+
+# NOTE_2: Make sure using_editor is set to "1" when using an editor as
+# diff_command!