• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/bin/sh
2# SPDX-License-Identifier: GPL-2.0-or-later
3# Copyright (c) International Business Machines  Corp., 2005
4# Copyright (c) 2021 Joerg Vehlow <joerg.vehlow@aox-tech.de>
5# Copyright (c) Linux Test Project, 2022-2023
6# Based on work by: Avantika Mathur (mathurav@us.ibm.com)
7
8TST_NEEDS_TMPDIR=1
9TST_NEEDS_ROOT=1
10TST_MIN_KVER=2.6.15
11TST_SETUP="${TST_SETUP:-fs_bind_setup}"
12TST_CLEANUP="${TST_CLEANUP:-fs_bind_cleanup}"
13TST_TESTFUNC=fs_bind_test
14TST_NEEDS_CMDS="mount umount awk sed"
15
16# Test interface:
17#
18# FS_BIND_TESTFUNC is the real testfunction. Please do not use
19# TST_TESTFUNC in the test. FS_BIND_TESTFUNC is used to wrap some additional logic.
20# TST_CNT can be used as usual
21
22FS_BIND_SANDBOX="sandbox"
23FS_BIND_DISK1="disk1"
24FS_BIND_DISK2="disk2"
25FS_BIND_DISK3="disk3"
26FS_BIND_DISK4="disk4"
27
28FS_BIND_MNTNS_PID=
29
30# Creates a directory and bind-mounts it to itself.
31# usage: fs_bind_makedir share_mode directory
32# where
33#  share_mode is one of the --make-{shared,priv,...}
34#  supported by mount
35#  directory is directory where to be created/configured.
36#  If it does not exist, it will be created
37fs_bind_makedir()
38{
39	local bind_type dir
40
41	bind_type="$1"
42	dir="$2"
43
44	case "$bind_type" in
45	shared|private|rshared|slave|rslave|rprivate|runbindable) ;;
46	*) tst_brk TBROK "Unknown share type \"$bind_type\""
47	esac
48
49	if [ -e "$dir" ]; then
50		tst_brk TBROK "An entry by the name \"$dir\" exists already"
51	fi
52
53	ROD mkdir -p "$dir"
54
55	# Most mount implementations can use --make-* in the same command as bind,
56	# but busybox mount, fails at least to set --make-private
57	EXPECT_PASS mount --bind "$dir" "$dir"
58	EXPECT_PASS mount --make-$bind_type "$dir"
59}
60
61# Verifies that subtrees contain the same content
62# usage: fs_bind_check [-n] dir1 dirn...
63# where
64#  -n  If set, expectes the subtrees to not be equal
65#  -s  Run check in mount namespace
66# dir1 dirn... An arbitraty number of directories, that are compared.
67#              If -n is given, only two directories are allowed.
68fs_bind_check()
69{
70	local OPTIND expect_diff use_ns args msg dir1 dir2 fail output
71	expect_diff=0
72	use_ns=0
73	while getopts "ns" args; do
74		case "$args" in
75		n) expect_diff=1; ;;
76		s) use_ns=1; ;;
77		esac
78	done
79	shift $((OPTIND-1))
80	msg="Check"
81	[ $expect_diff -eq 1 ] && msg="$msg no"
82	msg="$msg propagation"
83	if [ $use_ns -eq 1 ]; then
84		[ -z "$FS_BIND_MNTNS_PID" ] && tst_brk TBROK "Namespace does not exist"
85		msg="$msg in mnt namespace"
86	fi
87	msg="$msg $*"
88
89	if [ $# -lt 2 ] || ( [ $expect_diff -eq 1 ] && [ $# -ne 2 ] ); then
90		tst_brk TBROK "Insufficient arguments"
91	fi
92
93	dir1=$1
94	shift
95
96	for dir2 in "$@"; do
97		# compare adjacent pairs of directory trees
98
99	    if [ ! -d "$dir1" ]; then
100	        tst_res TFAIL "$msg: \"$dir1\" does not exist"
101	        return 1
102	    elif [ ! -d "$dir2" ]; then
103	        if [ $expect_diff -eq 1 ]; then
104	            tst_res TPASS "$msg"
105	            return 0
106	        else
107	            tst_res TFAIL "$msg: \"$dir2\" does not exist"
108	            return 1
109	        fi
110	    fi
111
112		if [ $use_ns -eq 1 ]; then
113			output="$(tst_ns_exec ${FS_BIND_MNTNS_PID} mnt diff -r "$PWD/$dir1" "$PWD/$dir2" 2> /dev/null)"
114		else
115			output="$(diff -r "$dir1" "$dir2" 2> /dev/null)"
116		fi
117
118		if [ $? -ne 0 ]; then
119			if [ $expect_diff -eq 1 ]; then
120	            # Since expect_diff=1 allows only two directories
121	            # to be compared, we are done after finding the first diff
122				tst_res TPASS "$msg"
123				return 0
124			else
125				tst_res TFAIL "$msg:"
126	            tst_res TFAIL "\"$dir1\" \"$dir2\" differ:\n$output"
127				return 1
128			fi
129		fi
130		dir1="$dir2"
131	done
132
133	if [ $expect_diff -eq 1 ]; then
134		tst_res TFAIL "$msg"
135		return 1
136	else
137		tst_res TPASS "$msg"
138		return 0
139	fi
140}
141
142fs_bind_setup()
143{
144	fs_bind_makedir private "$FS_BIND_SANDBOX"
145
146	cd $FS_BIND_SANDBOX
147	mkdir -p "$FS_BIND_DISK1" "$FS_BIND_DISK2" "$FS_BIND_DISK3" "$FS_BIND_DISK4"
148	mkdir "$FS_BIND_DISK1/a" "$FS_BIND_DISK1/b" "$FS_BIND_DISK1/c"
149	mkdir "$FS_BIND_DISK2/d" "$FS_BIND_DISK2/e" "$FS_BIND_DISK2/f"
150	mkdir "$FS_BIND_DISK3/g" "$FS_BIND_DISK3/h" "$FS_BIND_DISK3/i"
151	mkdir "$FS_BIND_DISK4/j" "$FS_BIND_DISK4/k" "$FS_BIND_DISK4/l"
152}
153
154_fs_bind_unmount_all()
155{
156	local mounts
157
158	cd "$TST_TMPDIR"
159
160	# Cleanup leftover mounts from the test
161	# sed '1!G;h;$!d' is used to reverse /proc/mounts.
162	# unmounting in reverse order requires significantly less iterations
163	# There is a slight chance, this loop does not terminate, if a mount
164	# cannot be unmounted for some reason. In that case the timeout
165	# will kill the test, but we cannot restore the system anyway
166	while true; do
167		mounts=$( sed '1!G;h;$!d' /proc/mounts \
168			| awk -vtmp="$TST_TMPDIR/$FS_BIND_SANDBOX/" \
169			'index($2, tmp) {print $2}' )
170		[ -z "$mounts" ] && break
171		echo $mounts | xargs umount 2>/dev/null
172	done
173}
174
175fs_bind_cleanup()
176{
177	_fs_bind_unmount_all
178	umount "$FS_BIND_SANDBOX"
179}
180
181_fs_bind_setup_test()
182{
183	local e
184
185	cd "$TST_TMPDIR/$FS_BIND_SANDBOX" || tst_brk "Unable to cd into sandbox"
186
187	for e in ls *; do
188		if   [ "$e" = "$FS_BIND_DISK1" ] \
189		  || [ "$e" = "$FS_BIND_DISK2" ] \
190		  || [ "$e" = "$FS_BIND_DISK3" ] \
191		  || [ "$e" = "$FS_BIND_DISK4" ]; then
192		  continue
193		fi
194		rm -rf "$e"
195	done
196}
197
198fs_bind_create_ns()
199{
200	[ -n "$FS_BIND_MNTNS_PID" ] && tst_brk TBROK "Namespace exist already"
201	FS_BIND_MNTNS_PID=$(tst_ns_create mnt)
202}
203
204fs_bind_exec_ns()
205{
206	[ -z "$FS_BIND_MNTNS_PID" ] && tst_brk TBROK "Namespace does not exist"
207	EXPECT_PASS tst_ns_exec $FS_BIND_MNTNS_PID mnt "$@"
208}
209
210fs_bind_destroy_ns()
211{
212	[ -n "$FS_BIND_MNTNS_PID" ] && kill $FS_BIND_MNTNS_PID 2>/dev/null
213	FS_BIND_MNTNS_PID=
214}
215
216_fs_bind_cleanup_test()
217{
218	local mounts
219
220	fs_bind_destroy_ns
221
222	mounts=$( awk -v tmp="$TST_TMPDIR/$FS_BIND_SANDBOX/" '
223		index($2, tmp) {
224			print substr($2, length(tmp) + 1)
225		}
226	' /proc/mounts )
227	if [ -n "$mounts" ]; then
228		tst_res TFAIL "There are still mounts in the sandbox:\n$mounts"
229	fi
230	_fs_bind_unmount_all
231}
232
233fs_bind_test()
234{
235	_fs_bind_setup_test
236
237	if type ${FS_BIND_TESTFUNC}1 > /dev/null 2>&1; then
238		"$FS_BIND_TESTFUNC$_tst_i" $1
239	else
240		"$FS_BIND_TESTFUNC" $1
241	fi
242
243	_fs_bind_cleanup_test
244}
245
246. tst_test.sh
247[ -z "$FS_BIND_TESTFUNC" ] && tst_brk TBROK "Please set FS_BIND_TESTFUNC before sourcing fs_bind_lib.sh"
248