1#!/usr/bin/env bash
2# SPDX-License-Identifier: LGPL-2.1-or-later
3# shellcheck disable=SC2016
4set -eux
5set -o pipefail
6
7export SYSTEMD_LOG_LEVEL=debug
8
9# check cgroup-v2
10is_v2_supported=no
11mkdir -p /tmp/cgroup2
12if mount -t cgroup2 cgroup2 /tmp/cgroup2; then
13    is_v2_supported=yes
14    umount /tmp/cgroup2
15fi
16rmdir /tmp/cgroup2
17
18# check cgroup namespaces
19is_cgns_supported=no
20if [[ -f /proc/1/ns/cgroup ]]; then
21    is_cgns_supported=yes
22fi
23
24is_user_ns_supported=no
25# On some systems (e.g. CentOS 7) the default limit for user namespaces
26# is set to 0, which causes the following unshare syscall to fail, even
27# with enabled user namespaces support. By setting this value explicitly
28# we can ensure the user namespaces support to be detected correctly.
29sysctl -w user.max_user_namespaces=10000
30if unshare -U sh -c :; then
31    is_user_ns_supported=yes
32fi
33
34function check_bind_tmp_path {
35    # https://github.com/systemd/systemd/issues/4789
36    local _root="/var/lib/machines/testsuite-13.bind-tmp-path"
37    rm -rf "$_root"
38    /usr/lib/systemd/tests/testdata/create-busybox-container "$_root"
39    : >/tmp/bind
40    systemd-nspawn --register=no -D "$_root" --bind=/tmp/bind /bin/sh -c 'test -e /tmp/bind'
41}
42
43function check_norbind {
44    # https://github.com/systemd/systemd/issues/13170
45    local _root="/var/lib/machines/testsuite-13.norbind-path"
46    rm -rf "$_root"
47    mkdir -p /tmp/binddir/subdir
48    echo -n "outer" >/tmp/binddir/subdir/file
49    mount -t tmpfs tmpfs /tmp/binddir/subdir
50    echo -n "inner" >/tmp/binddir/subdir/file
51    /usr/lib/systemd/tests/testdata/create-busybox-container "$_root"
52    systemd-nspawn --register=no -D "$_root" --bind=/tmp/binddir:/mnt:norbind /bin/sh -c 'CONTENT=$(cat /mnt/subdir/file); if [[ $CONTENT != "outer" ]]; then echo "*** unexpected content: $CONTENT"; return 1; fi'
53}
54
55function check_notification_socket {
56    # https://github.com/systemd/systemd/issues/4944
57    local _cmd='echo a | $(busybox which nc) -U -u -w 1 /run/host/notify'
58    # /testsuite-13.nc-container is prepared by test.sh
59    systemd-nspawn --register=no -D /testsuite-13.nc-container /bin/sh -x -c "$_cmd"
60    systemd-nspawn --register=no -D /testsuite-13.nc-container -U /bin/sh -x -c "$_cmd"
61}
62
63function check_os_release {
64    local _cmd='. /tmp/os-release
65if [ -n "${ID:+set}" ] && [ "${ID}" != "${container_host_id}" ]; then exit 1; fi
66if [ -n "${VERSION_ID:+set}" ] && [ "${VERSION_ID}" != "${container_host_version_id}" ]; then exit 1; fi
67if [ -n "${BUILD_ID:+set}" ] && [ "${BUILD_ID}" != "${container_host_build_id}" ]; then exit 1; fi
68if [ -n "${VARIANT_ID:+set}" ] && [ "${VARIANT_ID}" != "${container_host_variant_id}" ]; then exit 1; fi
69cd /tmp; (cd /run/host; md5sum os-release) | md5sum -c
70if echo test >>/run/host/os-release; then exit 1; fi
71'
72
73    local _os_release_source="/etc/os-release"
74    if [[ ! -r "${_os_release_source}" ]]; then
75        _os_release_source="/usr/lib/os-release"
76    elif [[ -L "${_os_release_source}" ]] && rm /etc/os-release; then
77        # Ensure that /etc always wins if available
78        cp /usr/lib/os-release /etc
79        echo MARKER=1 >>/etc/os-release
80    fi
81
82    systemd-nspawn --register=no -D /testsuite-13.nc-container --bind="${_os_release_source}":/tmp/os-release /bin/sh -x -e -c "$_cmd"
83
84    if grep -q MARKER /etc/os-release; then
85        rm /etc/os-release
86        ln -s ../usr/lib/os-release /etc/os-release
87    fi
88}
89
90function check_machinectl_bind {
91    local _cmd='for i in $(seq 1 20); do if test -f /tmp/marker; then exit 0; fi; usleep 500000; done; exit 1;'
92
93    cat >/run/systemd/system/nspawn_machinectl_bind.service <<EOF
94[Service]
95Type=notify
96ExecStart=systemd-nspawn ${SUSE_OPTS[@]} -D /testsuite-13.nc-container --notify-ready=no /bin/sh -x -e -c "$_cmd"
97EOF
98
99    systemctl start nspawn_machinectl_bind.service
100
101    touch /tmp/marker
102
103    machinectl bind --mkdir testsuite-13.nc-container /tmp/marker
104
105    while systemctl show -P SubState nspawn_machinectl_bind.service | grep -q running
106    do
107        sleep 0.1
108    done
109
110    return "$(systemctl show -P ExecMainStatus nspawn_machinectl_bind.service)"
111}
112
113function check_selinux {
114    if ! selinuxenabled; then
115        echo >&2 "SELinux is not enabled, skipping SELinux-related tests"
116        return 0
117    fi
118
119    # Basic test coverage to avoid issues like https://github.com/systemd/systemd/issues/19976
120    systemd-nspawn "${SUSE_OPTS[@]}" --register=no -b -D /testsuite-13.nc-container --selinux-apifs-context=system_u:object_r:container_file_t:s0:c0,c1 --selinux-context=system_u:system_r:container_t:s0:c0,c1
121}
122
123function check_ephemeral_config {
124    # https://github.com/systemd/systemd/issues/13297
125
126    mkdir -p /run/systemd/nspawn/
127    cat >/run/systemd/nspawn/testsuite-13.nc-container.nspawn <<EOF
128[Files]
129BindReadOnly=/tmp/ephemeral-config
130EOF
131    touch /tmp/ephemeral-config
132
133    # /testsuite-13.nc-container is prepared by test.sh
134    systemd-nspawn --register=no -D /testsuite-13.nc-container --ephemeral /bin/sh -x -c "test -f /tmp/ephemeral-config"
135
136    systemd-nspawn --register=no -D /testsuite-13.nc-container --ephemeral --machine foobar /bin/sh -x -c "! test -f /tmp/ephemeral-config"
137
138    rm -f /run/systemd/nspawn/testsuite-13.nc-container.nspawn
139}
140
141function run {
142    if [[ "$1" = "yes" && "$is_v2_supported" = "no" ]]; then
143        printf "Unified cgroup hierarchy is not supported. Skipping.\n" >&2
144        return 0
145    fi
146    if [[ "$2" = "yes" && "$is_cgns_supported" = "no" ]];  then
147        printf "CGroup namespaces are not supported. Skipping.\n" >&2
148        return 0
149    fi
150
151    local _root="/var/lib/machines/testsuite-13.unified-$1-cgns-$2-api-vfs-writable-$3"
152    rm -rf "$_root"
153    /usr/lib/systemd/tests/testdata/create-busybox-container "$_root"
154    SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$3" systemd-nspawn --register=no -D "$_root" -b
155    SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$3" systemd-nspawn --register=no -D "$_root" --private-network -b
156
157    if SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$3" systemd-nspawn --register=no -D "$_root" -U -b; then
158        [[ "$is_user_ns_supported" = "yes" && "$3" = "network" ]] && return 1
159    else
160        [[ "$is_user_ns_supported" = "no" && "$3" = "network" ]] && return 1
161    fi
162
163    if SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$3" systemd-nspawn --register=no -D "$_root" --private-network -U -b; then
164        [[ "$is_user_ns_supported" = "yes" && "$3" = "yes" ]] && return 1
165    else
166        [[ "$is_user_ns_supported" = "no" && "$3" = "yes" ]] && return 1
167    fi
168
169    local _netns_opt="--network-namespace-path=/proc/self/ns/net"
170    local _net_opts=(
171        "--network-bridge=lo"
172        "--network-interface=lo"
173        "--network-ipvlan=lo"
174        "--network-macvlan=lo"
175        "--network-veth"
176        "--network-veth-extra=lo"
177        "--network-zone=zone"
178    )
179
180    # --network-namespace-path and network-related options cannot be used together
181    for netopt in "${_net_opts[@]}"; do
182        echo "$_netns_opt in combination with $netopt should fail"
183        if SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$3" systemd-nspawn --register=no -D "$_root" -b "$_netns_opt" "$netopt"; then
184            echo >&2 "unexpected pass"
185            return 1
186        fi
187    done
188
189    # allow combination of --network-namespace-path and --private-network
190    if ! SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$3" systemd-nspawn --register=no -D "$_root" -b "$_netns_opt" --private-network; then
191        return 1
192    fi
193
194    # test --network-namespace-path works with a network namespace created by "ip netns"
195    ip netns add nspawn_test
196    _netns_opt="--network-namespace-path=/run/netns/nspawn_test"
197    SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$3" systemd-nspawn --register=no -D "$_root" "$_netns_opt" /bin/ip a | grep -v -E '^1: lo.*UP'
198    local r=$?
199    ip netns del nspawn_test
200
201    if [[ $r -ne 0 ]]; then
202        return 1
203    fi
204
205    return 0
206}
207
208check_bind_tmp_path
209
210check_norbind
211
212check_notification_socket
213
214check_os_release
215
216for api_vfs_writable in yes no network; do
217    run no no $api_vfs_writable
218    run yes no $api_vfs_writable
219    run no yes $api_vfs_writable
220    run yes yes $api_vfs_writable
221done
222
223check_machinectl_bind
224
225check_selinux
226
227check_ephemeral_config
228
229touch /testok
230