source: src-sh/pc-updatemanager/etcmerge @ 5e3985e

releng/10.0
Last change on this file since 5e3985e was 5e3985e, checked in by Kris Moore <kris@…>, 8 months ago

Add missed file

  • Property mode set to 100755
File size: 14.7 KB
Line 
1#!/bin/sh
2# 02/20/2014 - Adapted from version in FreeBSD Ports Tree sysutils/etcmerge
3# Modified By: Kris Moore
4#
5# ex:ts=8
6# vim:sts=4:sw=4:tw=120
7#
8# etcmerge - a program to merge an old and a new copy of the FreeBSD /etc
9# directory
10#
11
12#
13# Exit on encountering an error or unknown variable
14#
15set -e -u
16
17usage() {
18    echo "Usage:" 1>&2
19    echo "  etcmerge [-d <workdir>] [-e <etcdir>] [-r <refdir>] [-s <srcdir>] \\" 1>&2
20    echo "               [init|install]" 1>&2
21    echo 1>&2
22    echo "    -d   Set work directory for merge.  Defaults to" 1>&2
23    echo "               ${HOME}/etc-work/$(date +%Y%m%d%H%M)" 1>&2
24    echo "    -e   Set etc directory to merge.  Defaults to /etc" 1>&2
25    echo "    -r   Reference copy of etc.  Defaults to /var/db/etc" 1>&2
26    echo "    -s   FreeBSD source directory.  Defaults to /usr/src" 1>&2
27    echo 1>&2
28    echo "  init:      Do full generation of a new etc directory, including merge from the" 1>&2
29    echo "             active etc." 1>&2
30    echo 1>&2
31    echo "  install:   Make the merged etc active, and the newly generated from source etc" 1>&2
32    echo "             the new reference.  This prepares for a new merge." 1>&2
33    echo 1>&2
34    echo 1>&2
35    echo "  IMPORTANT: Before running 'install', you should resolve any conflicts" 1>&2
36    echo "             reported." 1>&2
37    echo "             Any '.diff' files that are left in merged-* directories represent" 1>&2
38    echo "             changes that are LOST in the newly merged etc.  These should either" 1>&2
39    echo "             be hand-applied or deemed OK to loose." 1>&2
40}
41
42#
43# Where we store our work files
44#
45WORKDIR=${HOME}/etc-work/$(date +%Y%m%d%H%M)
46
47while getopts ":d:e:r:s:" ARGUMENT ; do
48    case "${ARGUMENT}" in
49        d) WORKDIR="${OPTARG}" ;;
50        e) ACTIVEETC="${OPTARG}" ;;
51        r) REFETC="${OPTARG}" ;;
52        *) usage; exit 1 ;;
53    esac
54done
55shift $(($OPTIND - 1))
56
57
58#
59# Where we store class files
60#
61CLASSDIR=${WORKDIR}/classes
62
63#
64# Where the new "root" is linked from
65#
66NEWROOT="${WORKDIR}/new-base"
67
68#
69# Where the new etc is fetched from
70#
71NEWETC="${WORKDIR}/etc-new"
72
73#
74# Where we store our backup copy of an unmodified etc
75#
76REFETC=/var/db/etc
77
78#
79# Where the active copy of etc is stored
80#
81ACTIVEETC=/etc
82
83#
84# Where does our main merged tree go?
85#
86MERGEDETC=${WORKDIR}/etc-merged
87
88#
89# How do we use CPIO for extract?
90#
91CPIO_EXTRACT="cpio -i -d -u --quiet"
92
93#
94# How do we use CPIO for archiving?
95#
96CPIO_ARCHIVE="cpio -o --quiet"
97
98#
99# Show number of conflicts for a particular class
100#
101conflictshow() {
102    id=$1
103    if [ -s "${WORKDIR}/${id}.conflicts" ]; then
104        echo "ETCMERGE: >>>>"
105        echo "ETCMERGE: >>>> Class ${id}: $(cat "${WORKDIR}/${id}.conflicts" | wc -l) conflict(s)"
106    fi
107}
108
109if [ "$#" -lt 1 ]; then
110    usage
111    exit 1
112fi
113case "$1" in
114    init)    ;;
115    install)
116        if ! [ -d "etc-merged" -a "etc-new" ]; then
117            echo "install attempted without standing in work directory" 1>&2
118            echo "cd to work directory (by default under ${HOME}/etc-work/) and try again." 1>&2
119            exit 1
120        fi
121        for i in $(cat *.conflicts 2> /dev/null); do
122            if [ ! -e "etc-merged/$i" ] ; then continue ; fi
123            if egrep -q '^(<<<<<<< |=======$|>>>>>>> )' etc-merged/$i; then
124                echo "Unresolved conflicts in ${i}" 1>&2
125                exit 1
126            fi
127        done
128        # XXX Check for need?
129        /usr/sbin/pwd_mkdb -d etc-merged -p etc-merged/master.passwd
130        /usr/bin/cap_mkdb etc-merged/login.conf
131        if diff -q /etc/mail/aliases etc-merged/mail/aliases > /dev/null; then
132            NEED_NEWALIASES=yes
133        else
134            NEED_NEWALIASES=no
135        fi
136        tmpetc=/etc.$(date +%Y%m%d)
137        # XXX The entire set of operations below should be transactional.
138        #     This could be achieved by doing the updates as a series of
139        #     ln operations, then syncing, then removing the extra files,
140        #     and ending with removing the temporary directories, at each
141        #     phase recording what phase we are in.
142        #
143        #     Instead, the system now just prays that error does not
144        #     happen in the tiny section where it does the actual rename
145        #     operations, and syncs around this.  This is probably still
146        #     quicker than doing it the safe way.
147        #
148        # FIXME Should check for existance beforehand
149        mv etc-merged ${tmpetc}
150        mv etc-new ${REFETC}.etcmerge
151        fsync /
152        fsync ${REFETC}.etcmerge
153        fsync /var/db
154        # Should get everything to disk, one would hope.
155        sync && sleep 0.5 && sync && sleep 0.5 && sync && sleep 0.5
156        mv /etc/ /etc.etcmergeold
157        mv ${tmpetc} /etc
158        fsync /
159        if [ "${NEED_NEWALIASES}" = "yes" ]; then
160            /usr/bin/newaliases
161        fi
162        mv ${REFETC} ${REFETC}.etcmergeold
163        mv ${REFETC}.etcmerge ${REFETC}
164        fsync /var/db
165        # Do a sync that can keep running after the program exists.
166        sync && sync && sync
167        echo "Install done - removing copies of old /etc and old reference." 1>&2
168        rm -rf /etc.etcmergeold ${REFETC}.etcmergeold
169        echo "Done." 1>&2
170        exit 0
171        ;;
172    *)
173      usage
174      exit 1
175      ;;
176esac
177
178
179# Also creates our base work directory
180mkdir -p ${CLASSDIR}
181
182# KPM 2-19-2014, remove this functionality, we already have new etc prepped
183#
184# XXX Make sure we have all needed users and groups before this
185#
186#if ! (mkdir -p "${NEWROOT}" && \
187#       cd ${USRSRC}/etc && \
188#       make DESTDIR="${NEWROOT}" distrib-dirs && \
189#       make DESTDIR="${NEWROOT}" distribution && \
190#       mv ${NEWROOT}/etc ${NEWETC}); then
191#    echo "Unable to create new etc directory" 1>& 2
192#    echo "MERGE FAILED" 1>&2
193#    exit 1
194#else
195#    rm -rf ${NEWROOT} 2> /dev/null || (chflags -R noschg ${NEWROOT} && rm -rf ${NEWROOT}) || \
196#    (echo "Unable to clean out temp root" 1>&2; echo "MERGE FAILED" 1>&2; exit 1)
197#fi
198
199echo "ETCMERGE: >>> Finding classes of files"
200echo "ETCMERGE: >>> Working from"
201echo "ETCMERGE: >>>     Active:    ${ACTIVEETC}"
202echo "ETCMERGE: >>>     Reference: ${REFETC}"
203echo "ETCMERGE: >>>     New:       ${NEWETC}"
204
205#
206# Find list of new files and list of old (reference) files
207#
208cd $WORKDIR
209(cd "${NEWETC}"    && find . -type f -print | sort > ${CLASSDIR}/newetc.files)
210(cd "${NEWETC}"    && find . -type d -links 2 -print | sort > ${CLASSDIR}/newetc.emptydirs)
211(cd "${NEWETC}"    && find . \! \( -type d -or -type f \) -print | sort > ${CLASSDIR}/newetc.others)
212(cd "${REFETC}"    && find . -type f -print | sort > ${CLASSDIR}/refetc.files)
213(cd "${REFETC}"    && find . -type d -links 2 -print | sort > ${CLASSDIR}/refetc.emptydirs)
214(cd "${REFETC}"    && find . \! \( -type d -or -type f \) -print | sort > ${CLASSDIR}/refetc.others)
215(cd "${ACTIVEETC}" && find . -type f -print | sort > ${CLASSDIR}/activeetc.files)
216(cd "${ACTIVEETC}"    && find . -type d -links 2 -print | sort > ${CLASSDIR}/activeetc.emptydirs)
217(cd "${ACTIVEETC}"    && find . \! \( -type d -or -type f \) -print | sort > ${CLASSDIR}/activeetc.others)
218
219#
220# Generate lists of differences on a file level, which effectively divides all
221# files into classes:
222#
223# Id    Ref     New     Active          Action
224#  0    Absent  Absent  Absent          (Irrelevant case)
225#  1    Absent  Absent  Present         Copy file over, with directory if necessary
226#  2    Absent  Present Absent          Copy file over, with directory if necessary     
227#  3    Absent  Present Present         Store NEW file
228#                                       If there are differences:
229#                                               Store diff
230#                                               Add to conflict list
231#  4    Present Absent  Absent          Ignore file
232#  5    Present Absent  Present         No differences: Ignore files
233#                                       With differences: Store in conflict
234#                                               directory, with separate diff file
235#  6    Present Present Absent          Store in conflict directory
236#  7    Present Present Present         Do a 3-way merge, with directory if
237#                                               necessary.
238cd ${CLASSDIR}
239for extension in files emptydirs others; do
240    cat refetc.${extension} newetc.${extension} activeetc.${extension} | sort -u > alletc.${extension}
241    cat refetc.${extension} newetc.${extension} | sort | uniq -d | cat - activeetc.${extension} | sort | uniq -d > ${CLASSDIR}/7.${extension}
242    cat alletc.${extension} refetc.${extension} newetc.${extension}     | sort | uniq -u > ${CLASSDIR}/1.${extension}
243    cat alletc.${extension} refetc.${extension} activeetc.${extension}  | sort | uniq -u > ${CLASSDIR}/2.${extension}
244    cat alletc.${extension} newetc.${extension} activeetc.${extension}  | sort | uniq -u > ${CLASSDIR}/4.${extension}
245    cat newetc.${extension} activeetc.${extension}              | sort | uniq -d | cat - refetc.${extension} refetc.${extension} | sort | uniq -u > ${CLASSDIR}/3.${extension}
246    cat refetc.${extension} activeetc.${extension}              | sort | uniq -d | cat - newetc.${extension} newetc.${extension} | sort | uniq -u > ${CLASSDIR}/5.${extension}
247    cat refetc.${extension} newetc.${extension}                 | sort | uniq -d | cat - activeetc.${extension} activeetc.${extension} | sort | uniq -u > ${CLASSDIR}/6.${extension}
248done
249
250for i in 1 2 3 4 5 6 7; do
251    echo "ETCMERGE: >>>> Class ${i}: $(cat ${CLASSDIR}/$i.files | wc -l) files, $(cat ${CLASSDIR}/$i.emptydirs | wc -l) empty dirs, $(cat ${CLASSDIR}/$i.others | wc -l) others"
252done
253
254#
255# Create directory for merged data
256#
257mkdir ${MERGEDETC}
258
259echo "ETCMERGE: >>>"
260echo "ETCMERGE: >>> Handling class 7 files - present everywhere"
261echo "ETCMERGE: >>>> Files are handled as an ascii 3-way merge."
262echo "ETCMERGE: >>>> Non-files get copied from the ACTIVE etc dir."
263
264#
265# Class 7 - present everywhere.  Create a merged directory tree.
266#
267cd ${MERGEDETC}
268(cd ${ACTIVEETC} && cat ${CLASSDIR}/7.files ${CLASSDIR}/7.emptydirs ${CLASSDIR}/7.others | ${CPIO_ARCHIVE}) | ${CPIO_EXTRACT}
269for i in $(cat ${CLASSDIR}/7.files); do
270    if ! merge -q $i ${REFETC}/$i ${NEWETC}/$i; then
271        echo ${i} >> ${WORKDIR}/7.conflicts
272    fi
273done
274conflictshow 7
275
276#
277# Class 1 - only present in active directory.  Copy over.
278#
279echo "ETCMERGE: >>>"
280echo "ETCMERGE: >>> Handling class 1 - only present in active directory"
281echo "ETCMERGE: >>>> Both files and non-files get copied."
282echo "ETCMERGE: >>>"
283cd ${MERGEDETC}
284(cd ${ACTIVEETC} && cat ${CLASSDIR}/1.files ${CLASSDIR}/1.emptydirs ${CLASSDIR}/1.others | ${CPIO_ARCHIVE}) | ${CPIO_EXTRACT}
285
286#
287# Class 2 - only present in new directory.  Copy over.
288#
289echo "ETCMERGE: >>>"
290echo "ETCMERGE: >>> Handling class 2 - only present in new directory"
291echo "ETCMERGE: >>>> Both files and non-files get copied."
292echo "ETCMERGE: >>>"
293cd ${MERGEDETC}
294(cd ${NEWETC} && cat ${CLASSDIR}/2.files ${CLASSDIR}/2.emptydirs ${CLASSDIR}/2.others | ${CPIO_ARCHIVE}) | ${CPIO_EXTRACT}
295
296#
297# Class 3 - present in new and active directory, but not ref.
298# Use the active directory permissions, but the new file.
299# If the files differ, store filename in 3.conflicts and the active version and a diff in merged-changed.
300#
301echo "ETCMERGE: >>>"
302echo "ETCMERGE: >>>> Handling class 3 - present in new and active directory only"
303echo "ETCMERGE: >>>> Files with differences get a copy of NEW file in both"
304echo "ETCMERGE: >>>> etc-merged and merged-changed, with a .diff from the NEW to"
305echo "ETCMERGE: >>>> the ACTIVE file in merged-changed."
306echo "ETCMERGE: >>>> Non-files are fetched from the ACTIVE directory."
307echo "ETCMERGE: >>>"
308cd ${MERGEDETC}
309(cd ${ACTIVEETC} && cat ${CLASSDIR}/3.files ${CLASSDIR}/3.emptydirs ${CLASSDIR}/3.others | ${CPIO_ARCHIVE}) | ${CPIO_EXTRACT}
310(cd ${NEWETC} && cat ${CLASSDIR}/3.files | ${CPIO_ARCHIVE}) | ${CPIO_EXTRACT}
311for i in $(cat ${CLASSDIR}/3.files); do
312    if ! diff -q ${ACTIVEETC}/$i ${REFETC}/$i > /dev/null; then
313        # Files differ
314        echo $i >> ${WORKDIR}/3.conflicts
315    fi
316done
317conflictshow 3
318#
319# Handle differing files (if any)
320#
321if [ -s ${WORKDIR}/3.conflicts ]; then
322    mkdir -p ${WORKDIR}/merged-changed
323    cd ${WORKDIR}/merged-changed
324    (cd ${ACTIVEETC} && cat ${WORKDIR}/3.conflicts | ${CPIO_ARCHIVE}) | ${CPIO_EXTRACT}
325    for i in $(cat ${WORKDIR}/3.conflicts); do
326        diff -u ${REFETC}/$i $i > $i.diff
327    done
328fi
329
330#
331# Class 4 - present in ref, removed in new and active
332#
333echo "ETCMERGE: >>>"
334echo "ETCMERGE: >>> Handling class 4 - present in reference only"
335echo "ETCMERGE: >>>> A copy of each file is stored in merged-removed."
336echo "ETCMERGE: >>>> Non-files get dropped."
337echo "ETCMERGE: >>>"
338if [ -s ${CLASSDIR}/4.files ]; then
339    mkdir -p ${WORKDIR}/merged-removed
340    cd ${WORKDIR}/merged-removed
341    (cd ${REFETC} && cat ${CLASSDIR}/4.files | ${CPIO_ARCHIVE}) | ${CPIO_EXTRACT}
342fi
343
344#
345# Class 5 - present in ref and active, removed in new
346#
347# For all files where the active is different from the reference, create a copy in merged-removed, with a .diff that
348# shows what the differences are.
349# For all unchanged files, just copy them over.
350#
351echo "ETCMERGE: >>>"
352echo "ETCMERGE: >>> Handling class 5 - present in reference and active only"
353echo "ETCMERGE: >>>> A copy of each ACTIVE file is stored in merged-removed."
354echo "ETCMERGE: >>>> If there is a difference between the ACTIVE and the"
355echo "ETCMERGE: >>>> REFERENCE file, a diff from REFERENCE to ACTIVE gets"
356echo "ETCMERGE: >>>> stored in merged-removed/"
357echo "ETCMERGE: >>>> Non-files get dropped."
358echo "ETCMERGE: >>>"
359if [ -s ${CLASSDIR}/5.files ]; then
360    mkdir -p ${WORKDIR}/merged-removed
361    cd ${WORKDIR}/merged-removed
362    (cd ${ACTIVEETC} && cat ${CLASSDIR}/5.files | ${CPIO_ARCHIVE}) | ${CPIO_EXTRACT}
363    for i in $(cat ${CLASSDIR}/5.files); do
364        if ! diff -q ${ACTIVEETC}/$i ${REFETC}/$i > /dev/null; then
365            # Files differ
366            echo $i >> ${WORKDIR}/5.conflicts
367            diff -u ${REFETC}/$i ${ACTIVEETC}/$i > $i.diff
368        fi
369    done
370fi
371conflictshow 5
372
373#
374# Class 6 - present in ref and new, but removed in active
375# Files are copied from new to merged-conflicts, and if the files differ, the filename is added to 6.conflicts,
376# and a .diff file with the changes that are lost is stored alongside the file.
377#
378echo "ETCMERGE: >>>"
379echo "ETCMERGE: >>> Handling class 6 - present in reference and new only"
380echo "ETCMERGE: >>>> A copy of the NEW version of each file is stored in"
381echo "ETCMERGE: >>>> merged-conflicts/"
382echo "ETCMERGE: >>>> If there are differences the REFERENCE and NEW file,"
383echo "ETCMERGE: >>>> a .diff file with these is also stored."
384echo "ETCMERGE: >>>> Non-files get dropped."
385echo "ETCMERGE: >>>"
386if [ -s ${CLASSDIR}/6.files ]; then
387    mkdir ${WORKDIR}/merged-conflicts
388    cd ${WORKDIR}/merged-conflicts
389    (cd ${NEWETC} && cat ${CLASSDIR}/6.files | ${CPIO_ARCHIVE}) | ${CPIO_EXTRACT}
390    for i in $(cat ${CLASSDIR}/6.files); do
391        if ! diff -q $i ${REFETC}/$i > /dev/null; then
392            # Files differ
393            echo $i >> ${WORKDIR}/6.conflicts
394            diff -u ${REFETC}/$i $i > $i.diff
395        fi
396    done
397fi
398conflictshow 6
399
400cat <<EOM
401
402Directories (only present if they would have contents)
403    etc-merged        Replaced etc/ directory, ready for use (potentially after
404                        conflict resolution)
405    etc-new           New etc, used to generate etc-merged.
406
407    merged-removed    Files that have been removed, along with .diff files if
408                        the active file was different from the reference file.
409    merged-changed    Files that have been replaced by the update, along with
410                        .diff files saying what changes this has resulted in.
411    merged-conflicts  Files that are present in new and reference, but not in
412                        the active etc.  If these are changed, a .diff is
413                        also stored here.
414    classes           Internal overview of what files belong to what classes
415
416Work directory: ${WORKDIR}
417EOM
Note: See TracBrowser for help on using the repository browser.