]>
Commit | Line | Data |
---|---|---|
9026f5c2 SM |
1 | #!/bin/bash |
2 | # | |
3 | # This is a bash test case to test lxc-usernsexec. | |
4 | # It basically supports usring lxc-usernsexec to execute itself | |
5 | # and then create files and check that their ownership is as expected. | |
6 | # | |
7 | # It requires that the current user has at least 1 value in subuid and /etc/subgid | |
8 | TEMP_D="" | |
4c93c06e | 9 | VERBOSITY=0 |
9026f5c2 SM |
10 | set -f |
11 | ||
12 | fail() { echo "$@" 1>&2; exit 1; } | |
13 | error() { echo "$@" 1>&2; } | |
14 | skip() { | |
15 | error "SKIP:" "$@" | |
16 | exit 0 | |
17 | } | |
4c93c06e SM |
18 | debug() { |
19 | local level=${1}; shift; | |
20 | [ "${level}" -gt "${VERBOSITY}" ] && return | |
21 | error "${@}" | |
22 | } | |
9026f5c2 SM |
23 | |
24 | collect_owners() { | |
25 | # collect_owners([--dir=dir], file1, file2 ...) | |
26 | # set _RET to a space delimited array of | |
27 | # <file1>:owner:group <file2>:owner:group ... | |
28 | local out="" ret="" dir="" | |
29 | if [ "${1#--dir=}" != "$1" ]; then | |
30 | dir="${1#--dir=}" | |
31 | shift | |
32 | fi | |
33 | for arg in "$@"; do | |
34 | # drop the :* so that input can be same as touch_files. | |
35 | out=$(stat --format "%n:%u:%g" "${dir}${arg}") || { | |
36 | error "failed to stat ${arg}" | |
37 | return 1; | |
38 | } | |
39 | ret="$ret ${out##*/}" | |
40 | done | |
41 | _RET="${ret# }" | |
42 | } | |
43 | ||
44 | cleanup() { | |
45 | if [ -d "$TEMP_D" ]; then | |
46 | rm -Rf "$TEMP_D" | |
47 | fi | |
48 | } | |
49 | ||
50 | touch_files() { | |
51 | # touch_files tok [tok ...] | |
52 | # tok is filename:chown_id:chown_gid | |
53 | # if chown_id or chown_gid is empty, then chown will do the right thing | |
54 | # and only change the provided value. | |
55 | local args="" tok="" fname="" uidgid="" | |
56 | args=( "$@" ) | |
57 | for tok in "$@"; do | |
58 | fname=${tok%%:*} | |
59 | uidgid=${tok#$fname} | |
60 | uidgid=${uidgid#:} | |
61 | : > "$fname" || { error "failed to create $fname"; return 1; } | |
62 | [ -z "$uidgid" ] && continue | |
63 | chown $uidgid "$fname" || { error "failed to chmod '$uidgid' $fname ($?)"; return 1; } | |
64 | done | |
65 | } | |
66 | ||
67 | inside_cleanup() { | |
68 | local f="" | |
69 | rm -f "${FILES[@]}" | |
70 | echo "$STATUS" >&5 | |
71 | echo "$STATUS" >&6 | |
72 | } | |
73 | ||
74 | set_files() { | |
75 | local x="" | |
76 | FILES=( ) | |
77 | for x in "$@"; do | |
78 | FILES[${#FILES[@]}]="${x%%:*}" | |
79 | done | |
80 | } | |
81 | ||
82 | inside() { | |
83 | # this what gets run inside the usernsexec environment. | |
84 | # basically expects arguments of <filename>:uid:gid | |
85 | # it will create the file, and then chmod it to the provided uid:gid | |
86 | # it writes to file descriptor 5 a single line with space delimited | |
87 | # exit_value uid gid [<filename>:<owner>:<group> ... ] | |
88 | STATUS=127 | |
89 | trap inside_cleanup EXIT | |
90 | local uid="" gid="" x="" | |
91 | ||
92 | uid=$(id -u) || fail "failed execution of id -u" | |
93 | gid=$(id -g) || fail "failed execution of id -g" | |
94 | ||
95 | set_files "$@" | |
96 | ||
97 | touch_files "$@" || fail "failed to create files" | |
98 | ||
99 | collect_owners "${FILES[@]}" || fail "failed to collect owners" | |
100 | result="$_RET" | |
101 | ||
102 | # tell caller we are done. | |
103 | echo "0" "$uid" "$gid" "$result" >&5 | |
104 | STATUS=0 | |
105 | ||
106 | # let the caller do things while the files are around. | |
107 | read -t 30 x <&6 | |
108 | ||
109 | exit | |
110 | } | |
111 | ||
112 | runtest() { | |
113 | # runtest(mydir, nsexec_args, [inside [...]]) | |
114 | # - use 'mydir' as a working dir. | |
115 | # - execute lxc-usernsexec $nsexec_args -- <self> inside <inside args> | |
116 | # | |
117 | # write to stdout | |
118 | # exit_value inside_exit_value inside_uid:inside_gid <results> | |
119 | # | |
120 | # where results are a list of space separated | |
121 | # filename:uid:gid | |
122 | # for each file passed in inside_args | |
123 | [ $# -ge 3 ] || { error "runtest expects 2 args"; return 1; } | |
124 | local mydir="$1" nsexec_args="$2" | |
125 | shift 2 | |
126 | local ret inside_owners t="" | |
127 | KIDPID="" | |
128 | ||
129 | mkfifo "${mydir}/5" && exec 5<>"${mydir}/5" || return | |
130 | mkfifo "${mydir}/6" && exec 6<>"${mydir}/6" || return | |
131 | mkdir --mode=777 "${mydir}/work" || return | |
132 | cd "${mydir}/work" | |
133 | ||
134 | set_files "$@" | |
135 | ||
136 | local results="" oresults="" iresults="" iuid="" igid="" n=0 | |
137 | ||
138 | error "$" $USERNSEXEC ${nsexec_args} -- "$MYPATH" inside "$*" | |
139 | ${USERNSEXEC} ${nsexec_args} -- "$MYPATH" inside "$@" & | |
140 | KIDPID=$! | |
141 | ||
142 | [ -d "/proc/$KIDPID" ] || { | |
143 | wait $KIDPID | |
144 | fail "kid $KIDPID died quickly $?" | |
145 | } | |
146 | ||
147 | # if lxc-usernsexec fails to execute MYPATH inside, then | |
148 | # the read below would timeout. To avoid a long timeout, | |
149 | # we do a short timeout and check the pid is alive. | |
150 | while ! read -t 1 ret iuid igid inside_owners <&5; do | |
151 | n=$((n+1)) | |
152 | if [ ! -d "/proc/$KIDPID" ]; then | |
153 | wait $KIDPID | |
154 | fail "kid $KIDPID is gone $?" | |
155 | fi | |
156 | [ $n -ge 30 ] && fail "child never wrote to pipe" | |
157 | done | |
158 | iresults=( $inside_owners ) | |
159 | ||
160 | collect_owners "--dir=${mydir}/work/" "${FILES[@]}" || return | |
161 | oresults=( $_RET ) | |
162 | ||
163 | echo 0 >&6 | |
164 | wait | |
165 | ||
166 | ret=$? | |
167 | ||
168 | results=( ) | |
169 | for((i=0;i<${#iresults[@]};i++)); do | |
170 | results[$i]="${oresults[$i]}:${iresults[$i]#*:}" | |
171 | done | |
172 | ||
173 | echo 0 $ret "$iuid:$igid" "${results[@]}" | |
174 | } | |
175 | ||
176 | runcheck() { | |
177 | local name="$1" expected="$2" nsexec_args="$3" found="" | |
178 | shift 3 | |
179 | mkdir "${TEMP_D}/$name" || fail "failed mkdir <TEMP_D>/$name.d" | |
180 | local err="${TEMP_D}/$name.err" | |
181 | out=$("$MYPATH" runtest "${TEMP_D}/$name" "$nsexec_args" "$@" 2>"$err") || { | |
182 | error "$name: FAIL - runtest failed $?" | |
183 | [ -n "$out" ] && error " $out" | |
184 | sed 's,^, ,' "$err" 1>&2 | |
185 | ERRORS="${ERRORS} $name" | |
186 | return 1 | |
187 | } | |
188 | set -- $out | |
189 | local parentrc=$1 kidrc=$2 iuidgid="$3" found="" | |
190 | shift 3 | |
191 | found="$*" | |
192 | [ "$parentrc" = "0" -a "$kidrc" = "0" ] || { | |
193 | error "$name: FAIL - parentrc=$parentrc kidrc=$kidrc found=$found" | |
194 | ERRORS="${ERRORS} $name" | |
195 | return 1 | |
196 | } | |
197 | [ "$expected" = "$found" ] && { | |
198 | error "$name: PASS" | |
199 | PASS="${PASSES} $name" | |
200 | return 0 | |
201 | } | |
202 | echo "$name: FAIL expected '$expected' != found '$found'" | |
203 | FAILS="${FAILS} $name" | |
204 | return 1 | |
205 | } | |
206 | ||
4c93c06e SM |
207 | setup_Usage() { |
208 | cat <<EOF | |
209 | ${0} setup_and_run [-- run-args] | |
210 | ||
211 | setup the system by creating a user (default is '${asuser:-test-userns}') | |
212 | and then run test as that user. Must be root. | |
213 | ||
214 | If user exists, then do not create the user. | |
215 | ||
216 | -v | --verbose - be more verbose | |
217 | --create-subuid=UID:RANGE | |
218 | --create-subgid=UID:RANGE if adding subuid/subgid use this START:RANGE | |
219 | example (default) 3000000000:5 | |
220 | EOF | |
221 | } | |
222 | ||
223 | setup_and_run() { | |
224 | local short_opts="hv" | |
225 | local long_opts="help,user:,create-subuid:,create-subgid:,verbose" | |
226 | local getopt_out="" | |
227 | getopt_out=$(getopt --name "${0##*/}" \ | |
228 | --options "${short_opts}" --long "${long_opts}" -- "$@") && | |
229 | eval set -- "${getopt_out}" || | |
230 | { bad_Usage; return; } | |
231 | ||
232 | local cur="" next="" asuser="test-userns" | |
233 | local create_subuid="3000000000:5" create_subgid="3000000000:5" | |
234 | while [ $# -ne 0 ]; do | |
235 | cur="$1"; next="$2"; | |
236 | case "$cur" in | |
237 | -h|--help) setup_Usage ; exit 0;; | |
238 | --user) asuser="$next"; shift;; | |
239 | --create-subuid) create_subuid=$next; shift;; | |
240 | --create-subgid) create_subgid=$next; shift;; | |
241 | -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));; | |
242 | --) shift; break;; | |
243 | esac | |
244 | shift; | |
245 | done | |
246 | ||
247 | local pt_args="" | |
248 | pt_args=( "$@" ) | |
249 | ||
250 | if [ "$(id -u)" != "0" ]; then | |
251 | error "Sorry, setup_and_run has to be done as root, not uid=$(id -u)" | |
252 | return 1 | |
253 | fi | |
254 | ||
255 | local home="/home/$asuser" | |
256 | if [ ! -d "$home" ]; then | |
257 | debug 1 "creating user $asuser" | |
258 | useradd "$asuser" --create-home "--home-dir=$home" || { | |
259 | error "failed to create $asuser" | |
260 | return 1 | |
261 | } | |
262 | else | |
263 | debug 1 "$asuser existed" | |
264 | fi | |
265 | ||
266 | local subuid="" subgid="" | |
267 | subuid=$(awk -F: '$1 == n { print $2; exit(0); }' "n=$asuser" /etc/subuid) || { | |
268 | error "failed to read /etc/subuid for $asuser" | |
269 | return 1 | |
270 | } | |
271 | ||
272 | if [ -n "$subuid" ]; then | |
273 | debug 1 "$asuser already had subuid=$subuid" | |
274 | else | |
275 | debug 1 "adding $asuser:$create_subuid to /etc/subuid" | |
276 | echo "$asuser:$create_subuid" >> /etc/subuid || { | |
277 | error "failed to add $asuser to /etc/subuid" | |
278 | } | |
279 | fi | |
280 | ||
281 | subgid=$(awk -F: '$1 == n { print $2; exit(0); }' "n=$asuser" /etc/subgid) || { | |
282 | error "failed to read /etc/subgid for $asuser" | |
283 | return 1 | |
284 | } | |
285 | if [ -n "$subgid" ]; then | |
286 | debug 1 "$asuser already had subgid=$subgid" | |
287 | else | |
288 | debug 1 "adding $asuser:$create_subgid to /etc/subgid" | |
289 | echo "$asuser:$create_subgid" >> /etc/subgid || { | |
290 | error "failed to add $asuser to /etc/subgid" | |
291 | } | |
292 | fi | |
293 | ||
294 | debug 0 "as $asuser executing ${MYPATH} ${pt_args[*]}" | |
5f850cf9 | 295 | sudo -Hu "$asuser" ASAN_OPTIONS=${ASAN_OPTIONS:-} UBSAN_OPTIONS=${UBSAN_OPTIONS:-} "${MYPATH}" "${pt_args[@]}" |
4c93c06e SM |
296 | } |
297 | ||
9026f5c2 | 298 | USERNSEXEC=${USERNSEXEC:-lxc-usernsexec} |
4c93c06e SM |
299 | MYPATH=$(readlink -f "$0") || { echo "failed to get full path to self: $0"; exit 1; } |
300 | export MYPATH | |
301 | ||
9026f5c2 SM |
302 | if [ "$1" = "inside" ]; then |
303 | shift | |
304 | inside "$@" | |
305 | exit | |
306 | elif [ "$1" = "runtest" ]; then | |
307 | shift | |
308 | runtest "$@" | |
309 | exit | |
4c93c06e SM |
310 | elif [ "$1" = "setup_and_run" ]; then |
311 | shift | |
312 | setup_and_run "$@" | |
313 | exit | |
9026f5c2 SM |
314 | fi |
315 | ||
316 | name=$(id --user --name) || fail "failed to get username" | |
4c93c06e SM |
317 | if [ "$name" = "root" ]; then |
318 | setup_and_run "$@" | |
319 | exit | |
320 | fi | |
321 | ||
9026f5c2 | 322 | subuid=$(awk -F: '$1 == n { print $2; exit(0); }' "n=$name" /etc/subuid) && |
4c93c06e | 323 | [ -n "$subuid" ] || fail "did not find $name in /etc/subuid" |
9026f5c2 SM |
324 | |
325 | subgid=$(awk -F: '$1 == n { print $2; exit(0); }' "n=$name" /etc/subgid) && | |
4c93c06e | 326 | [ -n "$subgid" ] || fail "did not find $name in /etc/subgid" |
9026f5c2 SM |
327 | |
328 | ||
329 | uid=$(id --user) || fail "failed to get uid" | |
330 | gid=$(id --group) || fail "failed to get gid" | |
331 | ||
332 | mapuid="u:0:$uid:1" | |
333 | mapgid="g:0:$gid:1" | |
334 | ||
335 | ver=$(dpkg-query --show lxc-utils | awk '{print $2}') | |
336 | error "uid=$uid gid=$gid name=$name subuid=$subuid subgid=$subgid ver=$ver" | |
337 | error "lxc-utils=$ver kver=$(uname -r)" | |
338 | error "USERNSEXEC=$USERNSEXEC" | |
339 | ||
340 | TEMP_D=$(mktemp -d) | |
341 | trap cleanup EXIT | |
9026f5c2 SM |
342 | |
343 | PASSES=""; FAILS=""; ERRORS="" | |
344 | runcheck nouidgid "f0:$subuid:$subgid:0:0" "" f0 | |
345 | ||
346 | runcheck myuidgid "f0:$uid:$gid:0:0" \ | |
347 | "-m$mapuid -m$mapgid" f0 | |
348 | ||
349 | runcheck subuidgid \ | |
350 | "f0:$subuid:$subgid:0:0" \ | |
351 | "-mu:0:$subuid:1 -mg:0:$subgid:1" f0:0:0 | |
352 | ||
353 | runcheck bothsets "f0:$uid:$gid:0:0 f1:$subuid:$subgid:1:1 f2:$uid:$subgid:0:1" \ | |
354 | "-m$mapuid -m$mapgid -mu:1:$subuid:1 -mg:1:$subgid:1" \ | |
355 | f0 f1:1:1 f2::1 | |
356 | ||
357 | runcheck mismatch "f0:$uid:$subgid:0:0 f1:$subuid:$gid:15:31" \ | |
358 | "-mu:0:$uid:1 -mg:0:$subgid:1 -mu:15:$subuid:1 -mg:31:$gid:1" \ | |
359 | f0 f1:15:31 | |
360 | ||
361 | FAILS=${FAILS# } | |
362 | ERRORS=${ERRORS# } | |
363 | PASSES=${PASSES# } | |
364 | ||
365 | [ -z "${FAILS}" ] || error "FAILS: ${FAILS}" | |
366 | [ -z "${ERRORS}" ] || error "ERRORS: ${ERRORS}" | |
367 | [ -z "${FAILS}" -a -z "${ERRORS}" ] || exit 1 | |
368 | exit 0 |