1#!/bin/sh
2#
3# Copyright (c) 2010 Hudson River Trading LLC
4# Written by: John H. Baldwin <jhb@FreeBSD.org>
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11#    notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright
13#    notice, this list of conditions and the following disclaimer in the
14#    documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26# SUCH DAMAGE.
27#
28
29# Various regression tests to test the -A flag to the 'update' command.
30
31FAILED=no
32WORKDIR=work
33
34usage()
35{
36	echo "Usage: always.sh [-s script] [-w workdir]"
37	exit 1
38}
39
40# Allow the user to specify an alternate work directory or script.
41COMMAND=etcupdate
42while getopts "s:w:" option; do
43	case $option in
44		s)
45			COMMAND="sh $OPTARG"
46			;;
47		w)
48			WORKDIR=$OPTARG
49			;;
50		*)
51			echo
52			usage
53			;;
54	esac
55done
56shift $((OPTIND - 1))
57if [ $# -ne 0 ]; then
58	usage
59fi
60
61CONFLICTS=$WORKDIR/conflicts
62OLD=$WORKDIR/old
63NEW=$WORKDIR/current
64TEST=$WORKDIR/test
65
66# The various states of the comparison of a file between two trees.
67states="equal first second difftype difflinks difffiles"
68
69# These tests deal with ignoring certain patterns of files.  We run
70# the test multiple times forcing the install of different patterns.
71build_trees()
72{
73	local i
74
75	rm -rf $OLD $NEW $TEST $CONFLICTS
76
77	for i in $states; do
78		for j in $states; do
79			for k in $states; do
80				mkdir -p $OLD/$i/$j/$k $NEW/$i/$j/$k \
81				    $TEST/$i/$j/$k
82			done
83		done
84	done
85
86	# What follows are the various warning/conflict cases from the
87	# larger regression tests.  These results of many of these
88	# tests should be changed when installation is forced.  The
89	# cases when these updates should still fail even when forced
90	# are: 1) it should not force the removal of a modified file
91	# and 2) it should not remove a subdirectory that contains a
92	# modified or added file.
93
94	# /first/difftype/second: File with different local type
95	# removed.  Should generate a warning.
96	mkfifo $OLD/first/difftype/second/fifo
97	mkdir $TEST/first/difftype/second/fifo
98
99	# /first/difflinks/second: Modified link removed.  Should
100	# generate a warning.
101	ln -s "old link" $OLD/first/difflinks/second/link
102	ln -s "test link" $TEST/first/difflinks/second/link
103
104	# /first/difffiles/second: Modified file removed.  Should
105	# generate a warning.
106	echo "foo" > $OLD/first/difffiles/second/file
107	echo "bar" > $TEST/first/difffiles/second/file
108
109	# /second/second/difftype: Newly added file conflicts with
110	# existing file in test tree of a different type.  Should
111	# generate a warning.
112	mkdir $NEW/second/second/difftype/dir
113	mkfifo $TEST/second/second/difftype/dir
114
115	# /second/second/difflinks: Newly added link conflicts with
116	# existing link in test tree.  Should generate a warning.
117	ln -s "new link" $NEW/second/second/difflinks/link
118	ln -s "test link" $TEST/second/second/difflinks/link
119
120	# /second/second/difffiles: Newly added file conflicts with
121	# existing file in test tree.  Should generate a warning.
122	echo "new" > $NEW/second/second/difffiles/file
123	echo "test" > $TEST/second/second/difffiles/file
124
125	# /difftype/first/first: A removed file has changed type.
126	# This should generate a warning.
127	mkfifo $OLD/difftype/first/first/fifo
128	mkdir $NEW/difftype/first/first/fifo
129
130	# /difftype/difftype/difftype: All three files (old, new, and
131	# test) are different types from each other.  This should
132	# generate a warning.
133	mkfifo $OLD/difftype/difftype/difftype/one
134	mkdir $NEW/difftype/difftype/difftype/one
135	echo "foo" > $TEST/difftype/difftype/difftype/one
136	mkdir $OLD/difftype/difftype/difftype/two
137	echo "baz" > $NEW/difftype/difftype/difftype/two
138	ln -s "bar" $TEST/difftype/difftype/difftype/two
139
140	# /difftype/difftype/difflinks: A file has changed from a
141	# non-link to a link in both the new and test trees, but the
142	# target of the new and test links differ.  This should
143	# generate a new link conflict.
144	mkfifo $OLD/difftype/difftype/difflinks/link
145	ln -s "new" $NEW/difftype/difftype/difflinks/link
146	ln -s "test" $TEST/difftype/difftype/difflinks/link
147
148	# /difftype/difftype/difffile: A file has changed from a
149	# non-regular file to a regular file in both the new and test
150	# trees, but the contents in the new and test files differ.
151	# This should generate a new file conflict.
152	ln -s "old" $OLD/difftype/difftype/difffiles/file
153	echo "foo" > $NEW/difftype/difftype/difffiles/file
154	echo "bar" > $TEST/difftype/difftype/difffiles/file
155
156	# /difflinks/first/first: A modified link is missing in the
157	# test tree.  This should generate a warning.
158	ln -s "old" $OLD/difflinks/first/first/link
159	ln -s "new" $NEW/difflinks/first/first/link
160
161	# /difflinks/difftype/difftype: An updated link has been
162	# changed to a different file type in the test tree.  This
163	# should generate a warning.
164	ln -s "old" $OLD/difflinks/difftype/difftype/link
165	ln -s "new" $NEW/difflinks/difftype/difftype/link
166	echo "test" > $TEST/difflinks/difftype/difftype/link
167
168	# /difflinks/difflinks/difflinks: An updated link has been
169	# modified in the test tree and doesn't match either the old
170	# or new links.  This should generate a warning.
171	ln -s "old" $OLD/difflinks/difflinks/difflinks/link
172	ln -s "new" $NEW/difflinks/difflinks/difflinks/link
173	ln -s "test" $TEST/difflinks/difflinks/difflinks/link
174
175	# /difffiles/first/first: A removed file has been changed in
176	# the new tree.  This should generate a warning.
177	echo "foo" > $OLD/difffiles/first/first/file
178	echo "bar" > $NEW/difffiles/first/first/file
179
180	# /difffiles/difftype/difftype: An updated regular file has
181	# been changed to a different file type in the test tree.
182	# This should generate a warning.
183	echo "old" > $OLD/difffiles/difftype/difftype/file
184	echo "new" > $NEW/difffiles/difftype/difftype/file
185	mkfifo $TEST/difffiles/difftype/difftype/file
186
187	# /difffiles/difffiles/difffiles: A modified regular file was
188	# updated in the new tree.  The changes should be merged into
189	# to the new file if possible.  If the merge fails, a conflict
190	# should be generated.  For this test we just include the
191	# conflict case.
192	cat > $OLD/difffiles/difffiles/difffiles/conflict <<EOF
193this is an old file
194EOF
195	cat > $NEW/difffiles/difffiles/difffiles/conflict <<EOF
196this is a new file
197EOF
198	cat > $TEST/difffiles/difffiles/difffiles/conflict <<EOF
199this is a test file
200EOF
201
202	## Tests for adding directories
203	mkdir -p $OLD/adddir $NEW/adddir $TEST/adddir
204
205	# /adddir/conflict: Add a new file in a directory that already
206	# exists as a file.  This should generate two warnings.
207	mkdir $NEW/adddir/conflict
208	touch $NEW/adddir/conflict/newfile
209	touch $TEST/adddir/conflict
210
211	## Tests for removing directories
212	mkdir -p $OLD/rmdir $NEW/rmdir $TEST/rmdir
213
214	# /rmdir/extra: Do not remove a directory with an extra local file.
215	# This should generate a warning.
216	for i in $OLD $TEST; do
217		mkdir $i/rmdir/extra
218	done
219	echo "foo" > $TEST/rmdir/extra/localfile.txt
220
221	# /rmdir/conflict: Do not remove a directory with a conflicted
222	# remove file.  This should generate a warning.
223	for i in $OLD $TEST; do
224		mkdir $i/rmdir/conflict
225	done
226	mkfifo $OLD/rmdir/conflict/difftype
227	mkdir $TEST/rmdir/conflict/difftype
228
229	## Tests for converting files to directories and vice versa
230	for i in $OLD $NEW $TEST; do
231		for j in already old fromdir todir; do
232			mkdir -p $i/dirchange/$j
233		done
234	done
235
236	# /dirchange/fromdir/extradir: Convert a directory tree to a
237	# file.  The test tree includes an extra file in the directory
238	# that is not present in the old tree.  This should generate a
239	# warning.
240	for i in $OLD $TEST; do
241		mkdir $i/dirchange/fromdir/extradir
242		echo "foo" > $i/dirchange/fromdir/extradir/file
243	done
244	mkfifo $TEST/dirchange/fromdir/extradir/fifo
245	ln -s "bar" $NEW/dirchange/fromdir/extradir
246
247	# /dirchange/fromdir/conflict: Convert a directory tree to a
248	# file.  The test tree includes a local change that generates
249	# a warning and prevents the removal of the directory.
250	for i in $OLD $TEST; do
251		mkdir $i/dirchange/fromdir/conflict
252	done
253	echo "foo" > $OLD/dirchange/fromdir/conflict/somefile
254	echo "bar" > $TEST/dirchange/fromdir/conflict/somefile
255	mkfifo $NEW/dirchange/fromdir/conflict
256
257	# /dirchange/todir/difffile: Convert a file to a directory
258	# tree.  The test tree has a locally modified version of the
259	# file so that the conversion fails with a warning.
260	echo "foo" > $OLD/dirchange/todir/difffile
261	mkdir $NEW/dirchange/todir/difffile
262	echo "baz" > $NEW/dirchange/todir/difffile/file
263	echo "bar" > $TEST/dirchange/todir/difffile
264
265	# /dirchange/todir/difftype: Similar to the previous test, but
266	# the conflict is due to a change in the file type.
267	echo "foo" > $OLD/dirchange/todir/difftype
268	mkdir $NEW/dirchange/todir/difftype
269	echo "baz" > $NEW/dirchange/todir/difftype/file
270	mkfifo $TEST/dirchange/todir/difftype
271}
272
273# $1 - relative path to file that should be missing from TEST
274missing()
275{
276	if [ -e $TEST/$1 -o -L $TEST/$1 ]; then
277		echo "File $1 should be missing"
278		FAILED=yes
279	fi
280}
281
282# $1 - relative path to file that should be present in TEST
283present()
284{
285	if ! [ -e $TEST/$1 -o -L $TEST/$1 ]; then
286		echo "File $1 should be present"
287		FAILED=yes
288	fi
289}
290
291# $1 - relative path to file that should be a fifo in TEST
292fifo()
293{
294	if ! [ -p $TEST/$1 ]; then
295		echo "File $1 should be a FIFO"
296		FAILED=yes
297	fi
298}
299
300# $1 - relative path to file that should be a directory in TEST
301dir()
302{
303	if ! [ -d $TEST/$1 ]; then
304		echo "File $1 should be a directory"
305		FAILED=yes
306	fi
307}
308
309# $1 - relative path to file that should be a symlink in TEST
310# $2 - optional value of the link
311link()
312{
313	local val
314
315	if ! [ -L $TEST/$1 ]; then
316		echo "File $1 should be a link"
317		FAILED=yes
318	elif [ $# -gt 1 ]; then
319		val=`readlink $TEST/$1`
320		if [ "$val" != "$2" ]; then
321			echo "Link $1 should link to \"$2\""
322			FAILED=yes
323		fi
324	fi
325}
326
327# $1 - relative path to regular file that should be present in TEST
328# $2 - optional string that should match file contents
329# $3 - optional MD5 of the flie contents, overrides $2 if present
330file()
331{
332	local contents sum
333
334	if ! [ -f $TEST/$1 ]; then
335		echo "File $1 should be a regular file"
336		FAILED=yes
337	elif [ $# -eq 2 ]; then
338		contents=`cat $TEST/$1`
339		if [ "$contents" != "$2" ]; then
340			echo "File $1 has wrong contents"
341			FAILED=yes
342		fi
343	elif [ $# -eq 3 ]; then
344		sum=`md5 -q $TEST/$1`
345		if [ "$sum" != "$3" ]; then
346			echo "File $1 has wrong contents"
347			FAILED=yes
348		fi
349	fi
350}
351
352# $1 - relative path to a regular file that should have a conflict
353# $2 - optional MD5 of the conflict file contents
354conflict()
355{
356	local sum
357
358	if ! [ -f $CONFLICTS/$1 ]; then
359		echo "File $1 missing conflict"
360		FAILED=yes
361	elif [ $# -gt 1 ]; then
362		sum=`md5 -q $CONFLICTS/$1`
363		if [ "$sum" != "$2" ]; then
364			echo "Conflict $1 has wrong contents"
365			FAILED=yes
366		fi
367	fi
368}
369
370# $1 - relative path to a regular file that should not have a conflict
371noconflict()
372{
373	if [ -f $CONFLICTS/$1 ]; then
374		echo "File $1 should not have a conflict"
375		FAILED=yes
376	fi
377}
378
379if [ `id -u` -ne 0 ]; then
380	echo "must be root"
381	exit 0
382fi
383
384if [ -r /etc/etcupdate.conf ]; then
385	echo "WARNING: /etc/etcupdate.conf settings may break some tests."
386fi
387
388# First run the test ignoring no patterns.
389
390build_trees
391
392$COMMAND -r -d $WORKDIR -D $TEST > $WORKDIR/test.out
393
394cat > $WORKDIR/correct.out <<EOF
395  D /dirchange/fromdir/extradir/file
396  C /difffiles/difffiles/difffiles/conflict
397  C /difftype/difftype/difffiles/file
398  C /second/second/difffiles/file
399Warnings:
400  Modified regular file remains: /dirchange/fromdir/conflict/somefile
401  Modified regular file remains: /first/difffiles/second/file
402  Modified symbolic link remains: /first/difflinks/second/link
403  Modified directory remains: /first/difftype/second/fifo
404  Modified directory remains: /rmdir/conflict/difftype
405  Non-empty directory remains: /rmdir/extra
406  Non-empty directory remains: /rmdir/conflict
407  Modified mismatch: /difffiles/difftype/difftype/file (regular file vs fifo file)
408  Removed file changed: /difffiles/first/first/file
409  Modified link changed: /difflinks/difflinks/difflinks/link ("old" became "new")
410  Modified mismatch: /difflinks/difftype/difftype/link (symbolic link vs regular file)
411  Removed link changed: /difflinks/first/first/link ("old" became "new")
412  New link conflict: /difftype/difftype/difflinks/link ("new" vs "test")
413  Modified regular file changed: /difftype/difftype/difftype/one (fifo file became directory)
414  Modified symbolic link changed: /difftype/difftype/difftype/two (directory became regular file)
415  Remove mismatch: /difftype/first/first/fifo (fifo file became directory)
416  Modified directory changed: /dirchange/fromdir/conflict (directory became fifo file)
417  Modified directory changed: /dirchange/fromdir/extradir (directory became symbolic link)
418  Modified regular file changed: /dirchange/todir/difffile (regular file became directory)
419  Modified fifo file changed: /dirchange/todir/difftype (regular file became directory)
420  New file mismatch: /adddir/conflict (directory vs regular file)
421  Directory mismatch: $TEST/adddir/conflict (regular file)
422  Directory mismatch: $TEST/dirchange/todir/difffile (regular file)
423  Directory mismatch: $TEST/dirchange/todir/difftype (fifo file)
424  New link conflict: /second/second/difflinks/link ("new link" vs "test link")
425  New file mismatch: /second/second/difftype/dir (directory vs fifo file)
426EOF
427
428echo "Differences for regular:"
429diff -u -L "correct" $WORKDIR/correct.out -L "test" $WORKDIR/test.out \
430    || FAILED=yes
431
432## /first/difftype/second:
433present /first/difftype/second/fifo
434
435## /first/difflinks/second:
436link /first/difflinks/second/link "test link"
437
438## /first/difffiles/second:
439file /first/difffiles/second/file "bar"
440
441## /second/second/difftype:
442fifo /second/second/difftype/dir
443
444## /second/second/difflinks:
445link /second/second/difflinks/link "test link"
446
447## /second/second/difffiles:
448file /second/second/difffiles/file "test"
449conflict /second/second/difffiles/file 4f2ee8620a251fd53f06bb6112eb6ffa
450
451## /difftype/first/first:
452missing /difftype/first/first/fifo
453
454## /difftype/difftype/difftype:
455file /difftype/difftype/difftype/one "foo"
456link /difftype/difftype/difftype/two "bar"
457
458## /difftype/difftype/difflinks:
459link /difftype/difftype/difflinks/link "test"
460
461## /difftype/difftype/difffile:
462conflict /difftype/difftype/difffiles/file 117f2bcd1f6491f6044e79e5a57a9229
463
464## /difflinks/first/first:
465missing /difflinks/first/first/link
466
467## /difflinks/difftype/difftype:
468file /difflinks/difftype/difftype/link "test"
469
470## /difflinks/difflinks/difflinks:
471link /difflinks/difflinks/difflinks/link "test"
472
473## /difffiles/first/first:
474missing /difffiles/first/first/file
475
476## /difffiles/difftype/difftype:
477fifo /difffiles/difftype/difftype/file
478
479## /difffiles/difffiles/difffiles:
480file /difffiles/difffiles/difffiles/conflict "this is a test file"
481conflict /difffiles/difffiles/difffiles/conflict \
482    8261cfdd89280c4a6c26e4ac86541fe9
483
484## /adddir/conflict:
485file /adddir/conflict
486
487## /rmdir/extra:
488dir /rmdir/extra
489file /rmdir/extra/localfile.txt "foo"
490
491## /rmdir/conflict:
492dir /rmdir/conflict/difftype
493present /rmdir/conflict
494
495## /dirchange/fromdir/extradir:
496missing /dirchange/fromdir/extradir/file
497fifo /dirchange/fromdir/extradir/fifo
498
499## /dirchange/fromdir/conflict:
500file /dirchange/fromdir/conflict/somefile "bar"
501
502## /dirchange/todir/difffile:
503file /dirchange/todir/difffile "bar"
504
505## /dirchange/todir/difftype:
506fifo /dirchange/todir/difftype
507
508# Now test with -A '/first*' -A '/second* /*di*'.  This should remove
509# most of the warnings and conflicts.
510
511build_trees
512
513$COMMAND -r -A '/first*' -A '/second* /*di*' -d $WORKDIR -D $TEST > \
514    $WORKDIR/test1.out
515
516cat > $WORKDIR/correct1.out <<EOF
517  D /dirchange/fromdir/extradir/file
518  U /difffiles/difffiles/difffiles/conflict
519  U /difffiles/difftype/difftype/file
520  A /difffiles/first/first/file
521  U /difflinks/difflinks/difflinks/link
522  U /difflinks/difftype/difftype/link
523  A /difflinks/first/first/link
524  U /difftype/difftype/difffiles/file
525  U /difftype/difftype/difflinks/link
526  D /difftype/difftype/difftype/one
527  U /difftype/difftype/difftype/two
528  U /dirchange/todir/difffile
529  U /dirchange/todir/difftype
530  U /adddir/conflict
531  A /adddir/conflict/newfile
532  A /dirchange/todir/difffile/file
533  A /dirchange/todir/difftype/file
534  U /second/second/difffiles/file
535  U /second/second/difflinks/link
536  D /second/second/difftype/dir
537Warnings:
538  Modified regular file remains: /dirchange/fromdir/conflict/somefile
539  Modified regular file remains: /first/difffiles/second/file
540  Modified symbolic link remains: /first/difflinks/second/link
541  Modified directory remains: /first/difftype/second/fifo
542  Modified directory remains: /rmdir/conflict/difftype
543  Non-empty directory remains: /rmdir/extra
544  Non-empty directory remains: /rmdir/conflict
545  Modified directory changed: /dirchange/fromdir/conflict (directory became fifo file)
546  Modified directory changed: /dirchange/fromdir/extradir (directory became symbolic link)
547EOF
548
549echo "Differences for -A '/first*' -A '/second* /*di*':"
550diff -u -L "correct" $WORKDIR/correct1.out -L "test" $WORKDIR/test1.out \
551    || FAILED=yes
552
553## /first/difftype/second:
554present /first/difftype/second/fifo
555
556## /first/difflinks/second:
557link /first/difflinks/second/link "test link"
558
559## /first/difffiles/second:
560file /first/difffiles/second/file "bar"
561
562## /second/second/difftype:
563missing /second/second/difftype/dir
564
565## /second/second/difflinks:
566link /second/second/difflinks/link "new link"
567
568## /second/second/difffiles:
569file /second/second/difffiles/file "new"
570noconflict /second/second/difffiles/file
571
572## /difftype/first/first:
573missing /difftype/first/first/fifo
574
575## /difftype/difftype/difftype:
576missing /difftype/difftype/difftype/one
577file /difftype/difftype/difftype/two "baz"
578
579## /difftype/difftype/difflinks:
580link /difftype/difftype/difflinks/link "new"
581
582## /difftype/difftype/difffile:
583noconflict /difftype/difftype/difffiles/file
584file /difftype/difftype/difffiles/file "foo"
585
586## /difflinks/first/first:
587link /difflinks/first/first/link "new"
588
589## /difflinks/difftype/difftype:
590link /difflinks/difftype/difftype/link "new"
591
592## /difflinks/difflinks/difflinks:
593link /difflinks/difflinks/difflinks/link "new"
594
595## /difffiles/first/first:
596file /difffiles/first/first/file "bar"
597
598## /difffiles/difftype/difftype:
599file /difffiles/difftype/difftype/file "new"
600
601## /difffiles/difffiles/difffiles:
602noconflict /difffiles/difffiles/difffiles/conflict
603file /difffiles/difffiles/difffiles/conflict "this is a new file"
604
605## /adddir/conflict:
606file /adddir/conflict/newfile
607
608## /rmdir/extra:
609dir /rmdir/extra
610file /rmdir/extra/localfile.txt "foo"
611
612## /rmdir/conflict:
613dir /rmdir/conflict/difftype
614present /rmdir/conflict
615
616## /dirchange/fromdir/extradir:
617missing /dirchange/fromdir/extradir/file
618fifo /dirchange/fromdir/extradir/fifo
619
620## /dirchange/fromdir/conflict:
621file /dirchange/fromdir/conflict/somefile "bar"
622
623## /dirchange/todir/difffile:
624file /dirchange/todir/difffile/file "baz"
625
626## /dirchange/todir/difftype:
627file /dirchange/todir/difftype/file "baz"
628
629[ "${FAILED}" = no ]
630