]> git.ipfire.org Git - thirdparty/systemd.git/blob - test/test-functions
Merge pull request #14017 from poettering/analyze-calendar-tweaks
[thirdparty/systemd.git] / test / test-functions
1 #!/bin/bash
2 # -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
3 # ex: ts=8 sw=4 sts=4 et filetype=sh
4 PATH=/sbin:/bin:/usr/sbin:/usr/bin
5 export PATH
6
7 LOOKS_LIKE_DEBIAN=$(source /etc/os-release && [[ "$ID" = "debian" || " $ID_LIKE " = *" debian "* ]] && echo yes || :)
8 LOOKS_LIKE_ARCH=$(source /etc/os-release && [[ "$ID" = "arch" || " $ID_LIKE " = *" arch "* ]] && echo yes || :)
9 LOOKS_LIKE_SUSE=$(source /etc/os-release && [[ " $ID_LIKE " = *" suse "* ]] && echo yes || :)
10 KERNEL_VER=${KERNEL_VER-$(uname -r)}
11 KERNEL_MODS="/lib/modules/$KERNEL_VER/"
12 QEMU_TIMEOUT="${QEMU_TIMEOUT:-infinity}"
13 NSPAWN_TIMEOUT="${NSPAWN_TIMEOUT:-infinity}"
14 TIMED_OUT= # will be 1 after run_* if *_TIMEOUT is set and test timed out
15 [[ "$LOOKS_LIKE_SUSE" ]] && FSTYPE="${FSTYPE:-btrfs}" || FSTYPE="${FSTYPE:-ext4}"
16 UNIFIED_CGROUP_HIERARCHY="${UNIFIED_CGROUP_HIERARCHY:-default}"
17 EFI_MOUNT="$(bootctl -x 2>/dev/null || echo /boot)"
18 QEMU_MEM="${QEMU_MEM:-512M}"
19
20 # Decide if we can (and want to) run QEMU with KVM acceleration.
21 # Check if nested KVM is explicitly enabled (TEST_NESTED_KVM). If not,
22 # check if it's not explicitly disabled (TEST_NO_KVM) and we're not already
23 # running under KVM. If these conditions are met, enable KVM (and possibly
24 # nested KVM), otherwise disable it.
25 if [[ -n "$TEST_NESTED_KVM" || ( -z "$TEST_NO_KVM" && $(systemd-detect-virt -v) != kvm ) ]]; then
26 QEMU_KVM=yes
27 else
28 QEMU_KVM=no
29 fi
30
31 if ! ROOTLIBDIR=$(pkg-config --variable=systemdutildir systemd); then
32 echo "WARNING! Cannot determine rootlibdir from pkg-config, assuming /usr/lib/systemd" >&2
33 ROOTLIBDIR=/usr/lib/systemd
34 fi
35
36 PATH_TO_INIT=$ROOTLIBDIR/systemd
37 [ "$SYSTEMD_JOURNALD" ] || SYSTEMD_JOURNALD=$(which -a $BUILD_DIR/systemd-journald $ROOTLIBDIR/systemd-journald 2>/dev/null | grep '^/' -m1)
38 [ "$SYSTEMD" ] || SYSTEMD=$(which -a $BUILD_DIR/systemd $ROOTLIBDIR/systemd 2>/dev/null | grep '^/' -m1)
39 [ "$SYSTEMD_NSPAWN" ] || SYSTEMD_NSPAWN=$(which -a $BUILD_DIR/systemd-nspawn systemd-nspawn 2>/dev/null | grep '^/' -m1)
40 [ "$JOURNALCTL" ] || JOURNALCTL=$(which -a $BUILD_DIR/journalctl journalctl 2>/dev/null | grep '^/' -m1)
41
42 BASICTOOLS="test sh bash setsid loadkeys setfont login sulogin gzip sleep echo head tail cat mount umount cryptsetup date dmsetup modprobe sed cmp tee rm true false chmod chown ln xargs"
43 DEBUGTOOLS="df free ls stty ps ln ip route dmesg dhclient mkdir cp ping dhclient strace less grep id tty touch du sort hostname find vi mv"
44
45 STATEDIR="${BUILD_DIR:-.}/test/$(basename $(dirname $(realpath $0)))"
46 STATEFILE="$STATEDIR/.testdir"
47 TESTLOG="$STATEDIR/test.log"
48
49 is_built_with_asan() {
50 if ! type -P objdump >/dev/null; then
51 ddebug "Failed to find objdump. Assuming systemd hasn't been built with ASAN."
52 return 1
53 fi
54
55 # Borrowed from https://github.com/google/oss-fuzz/blob/cd9acd02f9d3f6e80011cc1e9549be526ce5f270/infra/base-images/base-runner/bad_build_check#L182
56 local _asan_calls=$(objdump -dC $SYSTEMD_JOURNALD | egrep "callq\s+[0-9a-f]+\s+<__asan" -c)
57 if (( $_asan_calls < 1000 )); then
58 return 1
59 else
60 return 0
61 fi
62 }
63
64 IS_BUILT_WITH_ASAN=$(is_built_with_asan && echo yes || echo no)
65
66 if [[ "$IS_BUILT_WITH_ASAN" = "yes" ]]; then
67 STRIP_BINARIES=no
68 SKIP_INITRD="${SKIP_INITRD:-yes}"
69 PATH_TO_INIT=$ROOTLIBDIR/systemd-under-asan
70 QEMU_MEM="2048M"
71 QEMU_SMP=4
72
73 # We need to correctly distinguish between gcc's and clang's ASan DSOs.
74 if ldd $SYSTEMD | grep -q libasan.so; then
75 ASAN_COMPILER=gcc
76 elif ldd $SYSTEMD | grep -q libclang_rt.asan; then
77 ASAN_COMPILER=clang
78
79 # As clang's ASan DSO is usually in a non-standard path, let's check if
80 # the environment is set accordingly. If not, warn the user and exit.
81 # We're not setting the LD_LIBRARY_PATH automagically here, because
82 # user should encounter (and fix) the same issue when running the unit
83 # tests (meson test)
84 if ldd "$SYSTEMD" | grep -q "libclang_rt.asan.*not found"; then
85 _asan_rt_name="$(ldd $SYSTEMD | awk '/libclang_rt.asan/ {print $1; exit}')"
86 _asan_rt_path="$(find /usr/lib* /usr/local/lib* -type f -name "$_asan_rt_name" 2>/dev/null | sed 1q)"
87 echo >&2 "clang's ASan DSO ($_asan_rt_name) is not present in the runtime library path"
88 echo >&2 "Consider setting LD_LIBRARY_PATH=${_asan_rt_path%/*}"
89 exit 1
90 fi
91 else
92 echo >&2 "systemd is not linked against the ASan DSO"
93 echo >&2 "gcc does this by default, for clang compile with -shared-libasan"
94 exit 1
95 fi
96 fi
97
98 function find_qemu_bin() {
99 # SUSE and Red Hat call the binary qemu-kvm. Debian and Gentoo call it kvm.
100 if [[ $QEMU_KVM == "yes" ]]; then
101 [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a kvm qemu-kvm 2>/dev/null | grep '^/' -m1)
102 fi
103
104 [ "$ARCH" ] || ARCH=$(uname -m)
105 case $ARCH in
106 x86_64)
107 # QEMU's own build system calls it qemu-system-x86_64
108 [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a qemu-system-x86_64 2>/dev/null | grep '^/' -m1)
109 ;;
110 i*86)
111 # new i386 version of QEMU
112 [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a qemu-system-i386 2>/dev/null | grep '^/' -m1)
113
114 # i386 version of QEMU
115 [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a qemu 2>/dev/null | grep '^/' -m1)
116 ;;
117 ppc64*)
118 [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a qemu-system-ppc64 2>/dev/null | grep '^/' -m1)
119 ;;
120 esac
121
122 if [ ! -e "$QEMU_BIN" ]; then
123 echo "Could not find a suitable QEMU binary" >&2
124 return 1
125 fi
126 }
127
128 # Return 0 if QEMU did run (then you must check the result state/logs for actual
129 # success), or 1 if QEMU is not available.
130 run_qemu() {
131 if [ -f /etc/machine-id ]; then
132 read MACHINE_ID < /etc/machine-id
133 [ -z "$INITRD" ] && [ -e "$EFI_MOUNT/$MACHINE_ID/$KERNEL_VER/initrd" ] \
134 && INITRD="$EFI_MOUNT/$MACHINE_ID/$KERNEL_VER/initrd"
135 [ -z "$KERNEL_BIN" ] && [ -e "$EFI_MOUNT/$MACHINE_ID/$KERNEL_VER/linux" ] \
136 && KERNEL_BIN="$EFI_MOUNT/$MACHINE_ID/$KERNEL_VER/linux"
137 fi
138
139 CONSOLE=ttyS0
140
141 if [[ ! "$KERNEL_BIN" ]]; then
142 if [[ "$LOOKS_LIKE_ARCH" ]]; then
143 KERNEL_BIN=/boot/vmlinuz-linux
144 else
145 [ "$ARCH" ] || ARCH=$(uname -m)
146 case $ARCH in
147 ppc64*)
148 KERNEL_BIN=/boot/vmlinux-$KERNEL_VER
149 CONSOLE=hvc0
150 ;;
151 *)
152 KERNEL_BIN=/boot/vmlinuz-$KERNEL_VER
153 ;;
154 esac
155 fi
156 fi
157
158 default_fedora_initrd=/boot/initramfs-${KERNEL_VER}.img
159 default_debian_initrd=/boot/initrd.img-${KERNEL_VER}
160 default_arch_initrd=/boot/initramfs-linux-fallback.img
161 default_suse_initrd=/boot/initrd-${KERNEL_VER}
162 if [[ ! "$INITRD" ]]; then
163 if [[ -e "$default_fedora_initrd" ]]; then
164 INITRD="$default_fedora_initrd"
165 elif [[ "$LOOKS_LIKE_DEBIAN" && -e "$default_debian_initrd" ]]; then
166 INITRD="$default_debian_initrd"
167 elif [[ "$LOOKS_LIKE_ARCH" && -e "$default_arch_initrd" ]]; then
168 INITRD="$default_arch_initrd"
169 elif [[ "$LOOKS_LIKE_SUSE" && -e "$default_suse_initrd" ]]; then
170 INITRD="$default_suse_initrd"
171 fi
172 fi
173
174 # If QEMU_SMP was not explicitly set, try to determine the value 'dynamically'
175 # i.e. use the number of online CPUs on the host machine. If the nproc utility
176 # is not installed or there's some other error when calling it, fall back
177 # to the original value (QEMU_SMP=1).
178 if ! [ "$QEMU_SMP" ]; then
179 if ! QEMU_SMP=$(nproc); then
180 dwarn "nproc utility is not installed, falling back to QEMU_SMP=1"
181 QEMU_SMP=1
182 fi
183 fi
184
185 find_qemu_bin || return 1
186
187 local _cgroup_args
188 if [[ "$UNIFIED_CGROUP_HIERARCHY" = "yes" ]]; then
189 _cgroup_args="systemd.unified_cgroup_hierarchy=yes"
190 elif [[ "$UNIFIED_CGROUP_HIERARCHY" = "no" ]]; then
191 _cgroup_args="systemd.unified_cgroup_hierarchy=no systemd.legacy_systemd_cgroup_controller=yes"
192 elif [[ "$UNIFIED_CGROUP_HIERARCHY" = "hybrid" ]]; then
193 _cgroup_args="systemd.unified_cgroup_hierarchy=no systemd.legacy_systemd_cgroup_controller=no"
194 elif [[ "$UNIFIED_CGROUP_HIERARCHY" != "default" ]]; then
195 dfatal "Unknown UNIFIED_CGROUP_HIERARCHY. Got $UNIFIED_CGROUP_HIERARCHY, expected [yes|no|hybrid|default]"
196 exit 1
197 fi
198
199 if [[ "$LOOKS_LIKE_SUSE" ]]; then
200 PARAMS+="rd.hostonly=0"
201 elif [[ "$LOOKS_LIKE_ARCH" ]]; then
202 PARAMS+="rw"
203 else
204 PARAMS+="ro"
205 fi
206
207 KERNEL_APPEND="$PARAMS \
208 root=/dev/sda1 \
209 raid=noautodetect \
210 loglevel=2 \
211 init=$PATH_TO_INIT \
212 console=$CONSOLE \
213 selinux=0 \
214 printk.devkmsg=on \
215 $_cgroup_args \
216 $KERNEL_APPEND \
217 "
218
219 QEMU_OPTIONS="-smp $QEMU_SMP \
220 -net none \
221 -m $QEMU_MEM \
222 -nographic \
223 -kernel $KERNEL_BIN \
224 -drive format=raw,cache=unsafe,file=${TESTDIR}/rootdisk.img \
225 $QEMU_OPTIONS \
226 "
227
228 if [[ "$INITRD" && "$SKIP_INITRD" != "yes" ]]; then
229 QEMU_OPTIONS="$QEMU_OPTIONS -initrd $INITRD"
230 fi
231
232 # Let's use KVM if possible
233 if [[ -c /dev/kvm && $QEMU_KVM == "yes" ]]; then
234 QEMU_OPTIONS="$QEMU_OPTIONS -machine accel=kvm -enable-kvm -cpu host"
235 fi
236
237 if [[ "$QEMU_TIMEOUT" != "infinity" ]]; then
238 QEMU_BIN="timeout --foreground $QEMU_TIMEOUT $QEMU_BIN"
239 fi
240 (set -x; $QEMU_BIN $QEMU_OPTIONS -append "$KERNEL_APPEND")
241 rc=$?
242 if [ "$rc" = 124 ] && [ "$QEMU_TIMEOUT" != "infinity" ]; then
243 derror "test timed out after $QEMU_TIMEOUT s"
244 TIMED_OUT=1
245 else
246 [ "$rc" != 0 ] && derror "QEMU failed with exit code $rc"
247 fi
248 return 0
249 }
250
251 # Return 0 if nspawn did run (then you must check the result state/logs for actual
252 # success), or 1 if nspawn is not available.
253 run_nspawn() {
254 [[ -d /run/systemd/system ]] || return 1
255
256 local _nspawn_cmd="$SYSTEMD_NSPAWN $NSPAWN_ARGUMENTS --register=no --kill-signal=SIGKILL --directory=$TESTDIR/$1 $PATH_TO_INIT $KERNEL_APPEND"
257 if [[ "$NSPAWN_TIMEOUT" != "infinity" ]]; then
258 _nspawn_cmd="timeout --foreground $NSPAWN_TIMEOUT $_nspawn_cmd"
259 fi
260
261 if [[ "$UNIFIED_CGROUP_HIERARCHY" = "hybrid" ]]; then
262 dwarn "nspawn doesn't support SYSTEMD_NSPAWN_UNIFIED_HIERARCHY=hybrid, skipping"
263 exit
264 elif [[ "$UNIFIED_CGROUP_HIERARCHY" = "yes" || "$UNIFIED_CGROUP_HIERARCHY" = "no" ]]; then
265 _nspawn_cmd="env SYSTEMD_NSPAWN_UNIFIED_HIERARCHY=$UNIFIED_CGROUP_HIERARCHY $_nspawn_cmd"
266 elif [[ "$UNIFIED_CGROUP_HIERARCHY" = "default" ]]; then
267 _nspawn_cmd="env --unset=UNIFIED_CGROUP_HIERARCHY --unset=SYSTEMD_NSPAWN_UNIFIED_HIERARCHY $_nspawn_cmd"
268 else
269 dfatal "Unknown UNIFIED_CGROUP_HIERARCHY. Got $UNIFIED_CGROUP_HIERARCHY, expected [yes|no|hybrid|default]"
270 exit 1
271 fi
272
273 (set -x; $_nspawn_cmd)
274 rc=$?
275 if [ "$rc" = 124 ] && [ "$NSPAWN_TIMEOUT" != "infinity" ]; then
276 derror "test timed out after $NSPAWN_TIMEOUT s"
277 TIMED_OUT=1
278 else
279 [ "$rc" != 0 ] && derror "nspawn failed with exit code $rc"
280 fi
281 return 0
282 }
283
284 setup_basic_environment() {
285 # create the basic filesystem layout
286 setup_basic_dirs
287
288 install_systemd
289 install_missing_libraries
290 install_config_files
291 create_rc_local
292 install_basic_tools
293 install_libnss
294 install_pam
295 install_dbus
296 install_fonts
297 install_keymaps
298 install_terminfo
299 install_execs
300 install_fsck
301 install_plymouth
302 install_debug_tools
303 install_ld_so_conf
304 setup_selinux
305 strip_binaries
306 install_depmod_files
307 generate_module_dependencies
308 if [[ "$IS_BUILT_WITH_ASAN" = "yes" ]]; then
309 create_asan_wrapper
310 fi
311 }
312
313 setup_selinux() {
314 # don't forget KERNEL_APPEND='... selinux=1 ...'
315 if [[ "$SETUP_SELINUX" != "yes" ]]; then
316 ddebug "Don't setup SELinux"
317 return 0
318 fi
319 ddebug "Setup SELinux"
320 local _conf_dir=/etc/selinux
321 local _fixfiles_tools="bash uname cat sort uniq awk grep egrep head expr find rm secon setfiles"
322
323 rm -rf $initdir/$_conf_dir
324 if ! cp -ar $_conf_dir $initdir/$_conf_dir; then
325 dfatal "Failed to copy $_conf_dir"
326 exit 1
327 fi
328
329 cat <<EOF >$initdir/etc/systemd/system/autorelabel.service
330 [Unit]
331 Description=Relabel all filesystems
332 DefaultDependencies=no
333 Requires=local-fs.target
334 Conflicts=shutdown.target
335 After=local-fs.target
336 Before=sysinit.target shutdown.target
337 ConditionSecurity=selinux
338 ConditionPathExists=|/.autorelabel
339
340 [Service]
341 ExecStart=/bin/sh -x -c 'echo 0 >/sys/fs/selinux/enforce && fixfiles -f -F relabel && rm /.autorelabel && systemctl --force reboot'
342 Type=oneshot
343 TimeoutSec=0
344 RemainAfterExit=yes
345 EOF
346
347 touch $initdir/.autorelabel
348 mkdir -p $initdir/etc/systemd/system/basic.target.wants
349 ln -fs autorelabel.service $initdir/etc/systemd/system/basic.target.wants/autorelabel.service
350
351 dracut_install $_fixfiles_tools
352 dracut_install fixfiles
353 dracut_install sestatus
354 }
355
356 install_valgrind() {
357 if ! type -p valgrind; then
358 dfatal "Failed to install valgrind"
359 exit 1
360 fi
361
362 local _valgrind_bins=$(strace -e execve valgrind /bin/true 2>&1 >/dev/null | perl -lne 'print $1 if /^execve\("([^"]+)"/')
363 dracut_install $_valgrind_bins
364
365 local _valgrind_libs=$(LD_DEBUG=files valgrind /bin/true 2>&1 >/dev/null | perl -lne 'print $1 if m{calling init: (/.*vgpreload_.*)}')
366 dracut_install $_valgrind_libs
367
368 local _valgrind_dbg_and_supp=$(
369 strace -e open valgrind /bin/true 2>&1 >/dev/null |
370 perl -lne 'if (my ($fname) = /^open\("([^"]+).*= (?!-)\d+/) { print $fname if $fname =~ /debug|\.supp$/ }'
371 )
372 dracut_install $_valgrind_dbg_and_supp
373 }
374
375 create_valgrind_wrapper() {
376 local _valgrind_wrapper=$initdir/$ROOTLIBDIR/systemd-under-valgrind
377 ddebug "Create $_valgrind_wrapper"
378 cat >$_valgrind_wrapper <<EOF
379 #!/bin/bash
380
381 mount -t proc proc /proc
382 exec valgrind --leak-check=full --log-file=/valgrind.out $ROOTLIBDIR/systemd "\$@"
383 EOF
384 chmod 0755 $_valgrind_wrapper
385 }
386
387 create_asan_wrapper() {
388 local _asan_wrapper=$initdir/$ROOTLIBDIR/systemd-under-asan
389 local _asan_rt_pattern
390 ddebug "Create $_asan_wrapper"
391
392 case "$ASAN_COMPILER" in
393 gcc)
394 _asan_rt_pattern="*libasan*"
395 ;;
396 clang)
397 _asan_rt_pattern="libclang_rt.asan-*"
398 # Install llvm-symbolizer to generate useful reports
399 # See: https://clang.llvm.org/docs/AddressSanitizer.html#symbolizing-the-reports
400 dracut_install "llvm-symbolizer"
401 ;;
402 *)
403 dfail "Unsupported compiler: $ASAN_COMPILER"
404 exit 1
405 esac
406
407 cat >$_asan_wrapper <<EOF
408 #!/bin/bash
409
410 set -x
411
412 DEFAULT_ASAN_OPTIONS=${ASAN_OPTIONS:-strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1}
413 DEFAULT_UBSAN_OPTIONS=${UBSAN_OPTIONS:-print_stacktrace=1:print_summary=1:halt_on_error=1}
414 DEFAULT_ENVIRONMENT="ASAN_OPTIONS=\$DEFAULT_ASAN_OPTIONS UBSAN_OPTIONS=\$DEFAULT_UBSAN_OPTIONS"
415
416 # As right now bash is the PID 1, we can't expect PATH to have a sane value.
417 # Let's make one to prevent unexpected "<bin> not found" issues in the future
418 export PATH="/sbin:/bin:/usr/sbin:/usr/bin"
419
420 mount -t proc proc /proc
421 mount -t sysfs sysfs /sys
422 mount -o remount,rw /
423
424 PATH_TO_ASAN=\$(find / -name '$_asan_rt_pattern' | sed 1q)
425 if [[ "\$PATH_TO_ASAN" ]]; then
426 # A lot of services (most notably dbus) won't start without preloading libasan
427 # See https://github.com/systemd/systemd/issues/5004
428 DEFAULT_ENVIRONMENT="\$DEFAULT_ENVIRONMENT LD_PRELOAD=\$PATH_TO_ASAN"
429 # Let's add the ASan DSO's path to the dynamic linker's cache. This is pretty
430 # unnecessary for gcc & libasan, however, for clang this is crucial, as its
431 # runtime ASan DSO is in a non-standard (library) path.
432 echo \${PATH_TO_ASAN%/*} > /etc/ld.so.conf.d/asan-path-override.conf
433 ldconfig
434 fi
435 echo DefaultEnvironment=\$DEFAULT_ENVIRONMENT >>/etc/systemd/system.conf
436 echo DefaultTimeoutStartSec=180s >>/etc/systemd/system.conf
437 echo DefaultStandardOutput=journal+console >>/etc/systemd/system.conf
438
439 # ASAN and syscall filters aren't compatible with each other.
440 find / -name '*.service' -type f | xargs sed -i 's/^\\(MemoryDeny\\|SystemCall\\)/#\\1/'
441
442 # The redirection of ASAN reports to a file prevents them from ending up in /dev/null.
443 # But, apparently, sometimes it doesn't work: https://github.com/google/sanitizers/issues/886.
444 JOURNALD_CONF_DIR=/etc/systemd/system/systemd-journald.service.d
445 mkdir -p "\$JOURNALD_CONF_DIR"
446 printf "[Service]\nEnvironment=ASAN_OPTIONS=\$DEFAULT_ASAN_OPTIONS:log_path=/systemd-journald.asan.log UBSAN_OPTIONS=\$DEFAULT_UBSAN_OPTIONS:log_path=/systemd-journald.ubsan.log\n" >"\$JOURNALD_CONF_DIR/env.conf"
447
448 # Sometimes UBSan sends its reports to stderr regardless of what is specified in log_path
449 # Let's try to catch them by redirecting stderr (and stdout just in case) to a file
450 # See https://github.com/systemd/systemd/pull/12524#issuecomment-491108821
451 printf "[Service]\nStandardOutput=file:/systemd-journald.out\n" >"\$JOURNALD_CONF_DIR/out.conf"
452
453 # 90s isn't enough for some services to finish when literally everything is run
454 # under ASan+UBSan in containers, which, in turn, are run in VMs.
455 # Let's limit which environments such services should be executed in.
456 mkdir -p /etc/systemd/system/systemd-hwdb-update.service.d
457 printf "[Unit]\nConditionVirtualization=container\n\n[Service]\nTimeoutSec=180s\n" >/etc/systemd/system/systemd-hwdb-update.service.d/env-override.conf
458
459 # Let's override another hard-coded timeout that kicks in too early
460 mkdir -p /etc/systemd/system/systemd-journal-flush.service.d
461 printf "[Service]\nTimeoutSec=180s\n" >/etc/systemd/system/systemd-journal-flush.service.d/timeout.conf
462
463 # The 'mount' utility doesn't behave well under libasan, causing unexpected
464 # fails during boot and subsequent test results check:
465 # bash-5.0# mount -o remount,rw -v /
466 # mount: /dev/sda1 mounted on /.
467 # bash-5.0# echo \$?
468 # 1
469 # Let's workaround this by clearing the previously set LD_PRELOAD env variable,
470 # so the libasan library is not loaded for this particular service
471 REMOUNTFS_CONF_DIR=/etc/systemd/system/systemd-remount-fs.service.d
472 mkdir -p "\$REMOUNTFS_CONF_DIR"
473 printf "[Service]\nUnsetEnvironment=LD_PRELOAD\n" >"\$REMOUNTFS_CONF_DIR/env.conf"
474
475 export ASAN_OPTIONS=\$DEFAULT_ASAN_OPTIONS:log_path=/systemd.asan.log UBSAN_OPTIONS=\$DEFAULT_UBSAN_OPTIONS
476 exec $ROOTLIBDIR/systemd "\$@"
477 EOF
478
479 chmod 0755 $_asan_wrapper
480 }
481
482 create_strace_wrapper() {
483 local _strace_wrapper=$initdir/$ROOTLIBDIR/systemd-under-strace
484 ddebug "Create $_strace_wrapper"
485 cat >$_strace_wrapper <<EOF
486 #!/bin/bash
487
488 exec strace -D -o /strace.out $ROOTLIBDIR/systemd "\$@"
489 EOF
490 chmod 0755 $_strace_wrapper
491 }
492
493 install_fsck() {
494 dracut_install /sbin/fsck*
495 dracut_install -o /bin/fsck*
496
497 # fskc.reiserfs calls reiserfsck. so, install it
498 dracut_install -o reiserfsck
499 }
500
501 install_dmevent() {
502 instmods dm_crypt =crypto
503 inst_binary dmeventd
504 if [[ "$LOOKS_LIKE_DEBIAN" ]]; then
505 # dmsetup installs 55-dm and 60-persistent-storage-dm on Debian/Ubuntu
506 # and since buster/bionic 95-dm-notify.rules
507 # see https://gitlab.com/debian-lvm/lvm2/blob/master/debian/patches/udev.patch
508 inst_rules 55-dm.rules 60-persistent-storage-dm.rules 95-dm-notify.rules
509 else
510 inst_rules 10-dm.rules 13-dm-disk.rules 95-dm-notify.rules
511 fi
512 }
513
514 install_systemd() {
515 # install compiled files
516 local _ninja_bin=$(type -P ninja || type -P ninja-build)
517 if [[ -z "$_ninja_bin" ]]; then
518 dfatal "ninja was not found"
519 exit 1
520 fi
521 (set -x; DESTDIR=$initdir "$_ninja_bin" -C $BUILD_DIR install)
522 # remove unneeded documentation
523 rm -fr $initdir/usr/share/{man,doc}
524 # we strip binaries since debug symbols increase binaries size a lot
525 # and it could fill the available space
526 strip_binaries
527
528 [[ "$LOOKS_LIKE_SUSE" ]] && setup_suse
529
530 # enable debug logging in PID1
531 echo LogLevel=debug >> $initdir/etc/systemd/system.conf
532 # store coredumps in journal
533 echo Storage=journal >> $initdir/etc/systemd/coredump.conf
534 }
535
536 get_ldpath() {
537 local _bin="$1"
538 objdump -p "$_bin" 2>/dev/null | awk "/R(UN)?PATH/ { print \"$initdir\" \$2 }" | paste -sd :
539 }
540
541 install_missing_libraries() {
542 # install possible missing libraries
543 for i in $initdir{,/usr}/{sbin,bin}/* $initdir{,/usr}/lib/systemd/{,tests/{,manual/,unsafe/}}*; do
544 LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$(get_ldpath $i)" inst_libs $i
545 done
546 }
547
548 create_empty_image() {
549 local _size=500
550 if [[ "$STRIP_BINARIES" = "no" ]]; then
551 _size=$((4*_size))
552 fi
553 rm -f "$TESTDIR/rootdisk.img"
554 # Create the blank file to use as a root filesystem
555 truncate -s "${_size}M" "$TESTDIR/rootdisk.img"
556 LOOPDEV=$(losetup --show -P -f $TESTDIR/rootdisk.img)
557 [ -b "$LOOPDEV" ] || return 1
558 echo "LOOPDEV=$LOOPDEV" >> $STATEFILE
559 sfdisk "$LOOPDEV" <<EOF
560 ,$((_size-50))M
561 ,
562 EOF
563
564 udevadm settle
565
566 local _label="-L systemd"
567 # mkfs.reiserfs doesn't know -L. so, use --label instead
568 [[ "$FSTYPE" == "reiserfs" ]] && _label="--label systemd"
569 if ! mkfs -t "${FSTYPE}" ${_label} "${LOOPDEV}p1" -q; then
570 dfatal "Failed to mkfs -t ${FSTYPE}"
571 exit 1
572 fi
573 }
574
575 create_empty_image_rootdir() {
576 create_empty_image
577 mkdir -p $initdir
578 mount ${LOOPDEV}p1 $initdir
579 TEST_SETUP_CLEANUP_ROOTDIR=1
580 }
581
582 check_asan_reports() {
583 local ret=0
584 local root="$1"
585
586 if [[ "$IS_BUILT_WITH_ASAN" = "yes" ]]; then
587 ls -l "$root"
588 if [[ -e "$root/systemd.asan.log.1" ]]; then
589 cat "$root/systemd.asan.log.1"
590 ret=$(($ret+1))
591 fi
592
593 journald_report=$(find "$root" -name "systemd-journald.*san.log*" -exec cat {} \;)
594 if [[ ! -z "$journald_report" ]]; then
595 printf "%s\n" "$journald_report"
596 cat "$root/systemd-journald.out" || :
597 ret=$(($ret+1))
598 fi
599
600 pids=$(
601 "$JOURNALCTL" -D "$root/var/log/journal" | perl -alne '
602 BEGIN {
603 %services_to_ignore = (
604 "dbus-daemon" => undef,
605 );
606 }
607 print $2 if /\s(\S*)\[(\d+)\]:\s*SUMMARY:\s+\w+Sanitizer/ && !exists $services_to_ignore{$1}'
608 )
609 if [[ ! -z "$pids" ]]; then
610 ret=$(($ret+1))
611 for pid in $pids; do
612 "$JOURNALCTL" -D "$root/var/log/journal" _PID=$pid --no-pager
613 done
614 fi
615 fi
616
617 return $ret
618 }
619
620 check_result_nspawn() {
621 local ret=1
622 local journald_report=""
623 local pids=""
624 [[ -e $TESTDIR/$1/testok ]] && ret=0
625 [[ -f $TESTDIR/$1/failed ]] && cp -a $TESTDIR/$1/failed $TESTDIR
626 cp -a $TESTDIR/$1/var/log/journal $TESTDIR
627 [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed
628 ls -l $TESTDIR/journal/*/*.journal
629 test -s $TESTDIR/failed && ret=$(($ret+1))
630 [ -n "$TIMED_OUT" ] && ret=$(($ret+1))
631 check_asan_reports "$TESTDIR/$1" || ret=$(($ret+1))
632 return $ret
633 }
634
635 # can be overridden in specific test
636 check_result_qemu() {
637 local ret=1
638 mkdir -p $initdir
639 mount ${LOOPDEV}p1 $initdir
640 [[ -e $initdir/testok ]] && ret=0
641 [[ -f $initdir/failed ]] && cp -a $initdir/failed $TESTDIR
642 cp -a $initdir/var/log/journal $TESTDIR
643 check_asan_reports "$initdir" || ret=$(($ret+1))
644 umount $initdir
645 [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed
646 ls -l $TESTDIR/journal/*/*.journal
647 test -s $TESTDIR/failed && ret=$(($ret+1))
648 [ -n "$TIMED_OUT" ] && ret=$(($ret+1))
649 return $ret
650 }
651
652 strip_binaries() {
653 if [[ "$STRIP_BINARIES" = "no" ]]; then
654 ddebug "Don't strip binaries"
655 return 0
656 fi
657 ddebug "Strip binaries"
658 find "$initdir" -executable -not -path '*/lib/modules/*.ko' -type f | \
659 xargs strip --strip-unneeded |& \
660 grep -vi 'file format not recognized' | \
661 ddebug
662 }
663
664 create_rc_local() {
665 mkdir -p $initdir/etc/rc.d
666 cat >$initdir/etc/rc.d/rc.local <<EOF
667 #!/bin/bash
668 exit 0
669 EOF
670 chmod 0755 $initdir/etc/rc.d/rc.local
671 }
672
673 install_execs() {
674 ddebug "install any Execs from the service files"
675 (
676 export PKG_CONFIG_PATH=$BUILD_DIR/src/core/
677 systemdsystemunitdir=$(pkg-config --variable=systemdsystemunitdir systemd)
678 systemduserunitdir=$(pkg-config --variable=systemduserunitdir systemd)
679 sed -r -n 's|^Exec[a-zA-Z]*=[@+!-]*([^ ]+).*|\1|gp' $initdir/{$systemdsystemunitdir,$systemduserunitdir}/*.service \
680 | sort -u | while read i; do
681 # some {rc,halt}.local scripts and programs are okay to not exist, the rest should
682 # also, plymouth is pulled in by rescue.service, but even there the exit code
683 # is ignored; as it's not present on some distros, don't fail if it doesn't exist
684 dinfo "Attempting to install $i"
685 inst $i || [ "${i%.local}" != "$i" ] || [ "${i%systemd-update-done}" != "$i" ] || [ "/bin/plymouth" == "$i" ]
686 done
687 )
688 }
689
690 generate_module_dependencies() {
691 if [[ -d $initdir/lib/modules/$KERNEL_VER ]] && \
692 ! depmod -a -b "$initdir" $KERNEL_VER; then
693 dfatal "\"depmod -a $KERNEL_VER\" failed."
694 exit 1
695 fi
696 }
697
698 install_depmod_files() {
699 inst /lib/modules/$KERNEL_VER/modules.order
700 inst /lib/modules/$KERNEL_VER/modules.builtin
701 }
702
703 install_plymouth() {
704 # install plymouth, if found... else remove plymouth service files
705 # if [ -x /usr/libexec/plymouth/plymouth-populate-initrd ]; then
706 # PLYMOUTH_POPULATE_SOURCE_FUNCTIONS="$TEST_BASE_DIR/test-functions" \
707 # /usr/libexec/plymouth/plymouth-populate-initrd -t $initdir
708 # dracut_install plymouth plymouthd
709 # else
710 rm -f $initdir/{usr/lib,etc}/systemd/system/plymouth* $initdir/{usr/lib,etc}/systemd/system/*/plymouth*
711 # fi
712 }
713
714 install_ld_so_conf() {
715 cp -a /etc/ld.so.conf* $initdir/etc
716 ldconfig -r "$initdir"
717 }
718
719 install_config_files() {
720 inst /etc/sysconfig/init || :
721 inst /etc/passwd
722 inst /etc/shadow
723 inst /etc/login.defs
724 inst /etc/group
725 inst /etc/shells
726 inst /etc/nsswitch.conf
727 inst /etc/pam.conf || :
728 inst /etc/securetty || :
729 inst /etc/os-release
730 inst /etc/localtime
731 # we want an empty environment
732 > $initdir/etc/environment
733 > $initdir/etc/machine-id
734 # set the hostname
735 echo systemd-testsuite > $initdir/etc/hostname
736 # fstab
737 if [[ "$LOOKS_LIKE_SUSE" ]]; then
738 ROOTMOUNT="/dev/sda1 / ${FSTYPE} rw 0 1"
739 else
740 ROOTMOUNT="LABEL=systemd / ${FSTYPE} rw 0 1"
741 fi
742
743 cat >$initdir/etc/fstab <<EOF
744 $ROOTMOUNT
745 EOF
746 }
747
748 install_basic_tools() {
749 [[ $BASICTOOLS ]] && dracut_install $BASICTOOLS
750 dracut_install -o sushell
751 # in Debian ldconfig is just a shell script wrapper around ldconfig.real
752 dracut_install -o ldconfig.real
753 }
754
755 install_debug_tools() {
756 [[ $DEBUGTOOLS ]] && dracut_install $DEBUGTOOLS
757
758 if [[ $INTERACTIVE_DEBUG ]]; then
759 # Set default TERM from vt220 to linux, so at least basic key shortcuts work
760 local _getty_override="$initdir/etc/systemd/system/serial-getty@.service.d"
761 mkdir -p "$_getty_override"
762 echo -e "[Service]\nEnvironment=TERM=linux" > "$_getty_override/default-TERM.conf"
763
764 cat > "$initdir/etc/motd" << EOF
765 To adjust the terminal size use:
766 export COLUMNS=xx
767 export LINES=yy
768 or
769 stty cols xx rows yy
770 EOF
771 fi
772 }
773
774 install_libnss() {
775 # install libnss_files for login
776 NSS_LIBS=$(LD_DEBUG=files getent passwd 2>&1 >/dev/null |sed -n '/calling init: .*libnss_/ {s!^.* /!/!; p}')
777 dracut_install $NSS_LIBS
778 }
779
780 install_dbus() {
781 inst $ROOTLIBDIR/system/dbus.socket
782
783 # Newer Fedora versions use dbus-broker by default. Let's install it is available.
784 if [ -f $ROOTLIBDIR/system/dbus-broker.service ]; then
785 inst $ROOTLIBDIR/system/dbus-broker.service
786 inst_symlink /etc/systemd/system/dbus.service
787 inst /usr/bin/dbus-broker
788 inst /usr/bin/dbus-broker-launch
789 elif [ -f $ROOTLIBDIR/system/dbus-daemon.service ]; then
790 # Fedora rawhide replaced dbus.service with dbus-daemon.service
791 inst $ROOTLIBDIR/system/dbus-daemon.service
792 # Alias symlink
793 inst_symlink /etc/systemd/system/dbus.service
794 else
795 inst $ROOTLIBDIR/system/dbus.service
796 fi
797
798 find \
799 /etc/dbus-1 /usr/share/dbus-1 -xtype f \
800 | while read file; do
801 inst $file
802 done
803 }
804
805 install_pam() {
806 (
807 if [[ "$LOOKS_LIKE_DEBIAN" ]] && type -p dpkg-architecture &>/dev/null; then
808 find "/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH)/security" -xtype f
809 else
810 find /lib*/security -xtype f
811 fi
812 find /etc/pam.d /etc/security -xtype f
813 ) | while read file; do
814 inst $file
815 done
816
817 # pam_unix depends on unix_chkpwd.
818 # see http://www.linux-pam.org/Linux-PAM-html/sag-pam_unix.html
819 dracut_install -o unix_chkpwd
820
821 [[ "$LOOKS_LIKE_DEBIAN" ]] &&
822 cp /etc/pam.d/systemd-user $initdir/etc/pam.d/
823
824 # set empty root password for easy debugging
825 sed -i 's/^root:x:/root::/' $initdir/etc/passwd
826 }
827
828 install_keymaps() {
829 # The first three paths may be deprecated.
830 # It seems now the last two paths are used by many distributions.
831 for i in \
832 /usr/lib/kbd/keymaps/include/* \
833 /usr/lib/kbd/keymaps/i386/include/* \
834 /usr/lib/kbd/keymaps/i386/qwerty/us.* \
835 /usr/lib/kbd/keymaps/legacy/include/* \
836 /usr/lib/kbd/keymaps/legacy/i386/qwerty/us.*; do
837 [[ -f $i ]] || continue
838 inst $i
839 done
840
841 # When it takes any argument, then install more keymaps.
842 if [[ -n $1 ]]; then
843 for i in \
844 /usr/lib/kbd/keymaps/i386/*/* \
845 /usr/lib/kbd/keymaps/legacy/i386/*/*; do
846 [[ -f $i ]] || continue
847 inst $i
848 done
849 fi
850 }
851
852 install_zoneinfo() {
853 for i in /usr/share/zoneinfo/{,*/,*/*/}*; do
854 [[ -f $i ]] || continue
855 inst $i
856 done
857 }
858
859 install_fonts() {
860 for i in \
861 /usr/lib/kbd/consolefonts/eurlatgr* \
862 /usr/lib/kbd/consolefonts/latarcyrheb-sun16*; do
863 [[ -f $i ]] || continue
864 inst $i
865 done
866 }
867
868 install_terminfo() {
869 for _terminfodir in /lib/terminfo /etc/terminfo /usr/share/terminfo; do
870 [ -f ${_terminfodir}/l/linux ] && break
871 done
872 dracut_install -o ${_terminfodir}/l/linux
873 }
874
875 setup_testsuite() {
876 cp $TEST_BASE_DIR/testsuite.target $initdir/etc/systemd/system/
877 cp $TEST_BASE_DIR/end.service $initdir/etc/systemd/system/
878
879 mkdir -p $initdir/etc/systemd/system/testsuite.target.wants
880 ln -fs $TEST_BASE_DIR/testsuite.service $initdir/etc/systemd/system/testsuite.target.wants/testsuite.service
881 # Don't shutdown the machine after running the test when INTERACTIVE_DEBUG is set
882 [[ -z $INTERACTIVE_DEBUG ]] && ln -fs $TEST_BASE_DIR/end.service $initdir/etc/systemd/system/testsuite.target.wants/end.service
883
884 # make the testsuite the default target
885 ln -fs testsuite.target $initdir/etc/systemd/system/default.target
886 }
887
888 setup_nspawn_root() {
889 rm -fr $TESTDIR/nspawn-root
890 ddebug "cp -ar $initdir $TESTDIR/nspawn-root"
891 cp -ar $initdir $TESTDIR/nspawn-root
892 # we don't mount in the nspawn root
893 rm -f $TESTDIR/nspawn-root/etc/fstab
894 if [[ "$RUN_IN_UNPRIVILEGED_CONTAINER" = "yes" ]]; then
895 cp -ar $TESTDIR/nspawn-root $TESTDIR/unprivileged-nspawn-root
896 fi
897 }
898
899 setup_basic_dirs() {
900 mkdir -p $initdir/run
901 mkdir -p $initdir/etc/systemd/system
902 mkdir -p $initdir/var/log/journal
903
904 for d in usr/bin usr/sbin bin etc lib "$libdir" sbin tmp usr var var/log dev proc sys sysroot root run run/lock run/initramfs; do
905 if [ -L "/$d" ]; then
906 inst_symlink "/$d"
907 else
908 inst_dir "/$d"
909 fi
910 done
911
912 ln -sfn /run "$initdir/var/run"
913 ln -sfn /run/lock "$initdir/var/lock"
914 }
915
916 mask_supporting_services() {
917 # mask some services that we do not want to run in these tests
918 ln -fs /dev/null $initdir/etc/systemd/system/systemd-hwdb-update.service
919 ln -fs /dev/null $initdir/etc/systemd/system/systemd-journal-catalog-update.service
920 ln -fs /dev/null $initdir/etc/systemd/system/systemd-networkd.service
921 ln -fs /dev/null $initdir/etc/systemd/system/systemd-networkd.socket
922 ln -fs /dev/null $initdir/etc/systemd/system/systemd-resolved.service
923 }
924
925 inst_libs() {
926 local _bin=$1
927 local _so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)'
928 local _file _line
929
930 LC_ALL=C ldd "$_bin" 2>/dev/null | while read _line; do
931 [[ $_line = 'not a dynamic executable' ]] && break
932
933 if [[ $_line =~ $_so_regex ]]; then
934 _file=${BASH_REMATCH[1]}
935 [[ -e ${initdir}/$_file ]] && continue
936 inst_library "$_file"
937 continue
938 fi
939
940 if [[ $_line =~ not\ found ]]; then
941 dfatal "Missing a shared library required by $_bin."
942 dfatal "Run \"ldd $_bin\" to find out what it is."
943 dfatal "$_line"
944 dfatal "dracut cannot create an initrd."
945 exit 1
946 fi
947 done
948 }
949
950 import_testdir() {
951 [[ -e $STATEFILE ]] && . $STATEFILE
952 if [[ ! -d "$TESTDIR" ]]; then
953 if [[ -z "$TESTDIR" ]]; then
954 TESTDIR=$(mktemp --tmpdir=/var/tmp -d -t systemd-test.XXXXXX)
955 else
956 mkdir -p "$TESTDIR"
957 fi
958
959 echo "TESTDIR=\"$TESTDIR\"" > $STATEFILE
960 export TESTDIR
961 fi
962 }
963
964 import_initdir() {
965 initdir=$TESTDIR/root
966 mkdir -p $initdir
967 export initdir
968 }
969
970 ## @brief Converts numeric logging level to the first letter of level name.
971 #
972 # @param lvl Numeric logging level in range from 1 to 6.
973 # @retval 1 if @a lvl is out of range.
974 # @retval 0 if @a lvl is correct.
975 # @result Echoes first letter of level name.
976 _lvl2char() {
977 case "$1" in
978 1) echo F;;
979 2) echo E;;
980 3) echo W;;
981 4) echo I;;
982 5) echo D;;
983 6) echo T;;
984 *) return 1;;
985 esac
986 }
987
988 ## @brief Internal helper function for _do_dlog()
989 #
990 # @param lvl Numeric logging level.
991 # @param msg Message.
992 # @retval 0 It's always returned, even if logging failed.
993 #
994 # @note This function is not supposed to be called manually. Please use
995 # dtrace(), ddebug(), or others instead which wrap this one.
996 #
997 # This function calls _do_dlog() either with parameter msg, or if
998 # none is given, it will read standard input and will use every line as
999 # a message.
1000 #
1001 # This enables:
1002 # dwarn "This is a warning"
1003 # echo "This is a warning" | dwarn
1004 LOG_LEVEL=${LOG_LEVEL:-4}
1005
1006 dlog() {
1007 [ -z "$LOG_LEVEL" ] && return 0
1008 [ $1 -le $LOG_LEVEL ] || return 0
1009 local lvl="$1"; shift
1010 local lvlc=$(_lvl2char "$lvl") || return 0
1011
1012 if [ $# -ge 1 ]; then
1013 echo "$lvlc: $*"
1014 else
1015 while read line; do
1016 echo "$lvlc: " "$line"
1017 done
1018 fi
1019 }
1020
1021 ## @brief Logs message at TRACE level (6)
1022 #
1023 # @param msg Message.
1024 # @retval 0 It's always returned, even if logging failed.
1025 dtrace() {
1026 set +x
1027 dlog 6 "$@"
1028 [ -n "$debug" ] && set -x || :
1029 }
1030
1031 ## @brief Logs message at DEBUG level (5)
1032 #
1033 # @param msg Message.
1034 # @retval 0 It's always returned, even if logging failed.
1035 ddebug() {
1036 # set +x
1037 dlog 5 "$@"
1038 # [ -n "$debug" ] && set -x || :
1039 }
1040
1041 ## @brief Logs message at INFO level (4)
1042 #
1043 # @param msg Message.
1044 # @retval 0 It's always returned, even if logging failed.
1045 dinfo() {
1046 set +x
1047 dlog 4 "$@"
1048 [ -n "$debug" ] && set -x || :
1049 }
1050
1051 ## @brief Logs message at WARN level (3)
1052 #
1053 # @param msg Message.
1054 # @retval 0 It's always returned, even if logging failed.
1055 dwarn() {
1056 set +x
1057 dlog 3 "$@"
1058 [ -n "$debug" ] && set -x || :
1059 }
1060
1061 ## @brief Logs message at ERROR level (2)
1062 #
1063 # @param msg Message.
1064 # @retval 0 It's always returned, even if logging failed.
1065 derror() {
1066 # set +x
1067 dlog 2 "$@"
1068 # [ -n "$debug" ] && set -x || :
1069 }
1070
1071 ## @brief Logs message at FATAL level (1)
1072 #
1073 # @param msg Message.
1074 # @retval 0 It's always returned, even if logging failed.
1075 dfatal() {
1076 set +x
1077 dlog 1 "$@"
1078 [ -n "$debug" ] && set -x || :
1079 }
1080
1081
1082 # Generic substring function. If $2 is in $1, return 0.
1083 strstr() { [ "${1#*$2*}" != "$1" ]; }
1084
1085 # normalize_path <path>
1086 # Prints the normalized path, where it removes any duplicated
1087 # and trailing slashes.
1088 # Example:
1089 # $ normalize_path ///test/test//
1090 # /test/test
1091 normalize_path() {
1092 shopt -q -s extglob
1093 set -- "${1//+(\/)//}"
1094 shopt -q -u extglob
1095 echo "${1%/}"
1096 }
1097
1098 # convert_abs_rel <from> <to>
1099 # Prints the relative path, when creating a symlink to <to> from <from>.
1100 # Example:
1101 # $ convert_abs_rel /usr/bin/test /bin/test-2
1102 # ../../bin/test-2
1103 # $ ln -s $(convert_abs_rel /usr/bin/test /bin/test-2) /usr/bin/test
1104 convert_abs_rel() {
1105 local __current __absolute __abssize __cursize __newpath
1106 local -i __i __level
1107
1108 set -- "$(normalize_path "$1")" "$(normalize_path "$2")"
1109
1110 # corner case #1 - self looping link
1111 [[ "$1" == "$2" ]] && { echo "${1##*/}"; return; }
1112
1113 # corner case #2 - own dir link
1114 [[ "${1%/*}" == "$2" ]] && { echo "."; return; }
1115
1116 IFS="/" __current=($1)
1117 IFS="/" __absolute=($2)
1118
1119 __abssize=${#__absolute[@]}
1120 __cursize=${#__current[@]}
1121
1122 while [[ ${__absolute[__level]} == ${__current[__level]} ]]
1123 do
1124 (( __level++ ))
1125 if (( __level > __abssize || __level > __cursize ))
1126 then
1127 break
1128 fi
1129 done
1130
1131 for ((__i = __level; __i < __cursize-1; __i++))
1132 do
1133 if ((__i > __level))
1134 then
1135 __newpath=$__newpath"/"
1136 fi
1137 __newpath=$__newpath".."
1138 done
1139
1140 for ((__i = __level; __i < __abssize; __i++))
1141 do
1142 if [[ -n $__newpath ]]
1143 then
1144 __newpath=$__newpath"/"
1145 fi
1146 __newpath=$__newpath${__absolute[__i]}
1147 done
1148
1149 echo "$__newpath"
1150 }
1151
1152
1153 # Install a directory, keeping symlinks as on the original system.
1154 # Example: if /lib points to /lib64 on the host, "inst_dir /lib/file"
1155 # will create ${initdir}/lib64, ${initdir}/lib64/file,
1156 # and a symlink ${initdir}/lib -> lib64.
1157 inst_dir() {
1158 [[ -e ${initdir}/"$1" ]] && return 0 # already there
1159
1160 local _dir="$1" _part="${1%/*}" _file
1161 while [[ "$_part" != "${_part%/*}" ]] && ! [[ -e "${initdir}/${_part}" ]]; do
1162 _dir="$_part $_dir"
1163 _part=${_part%/*}
1164 done
1165
1166 # iterate over parent directories
1167 for _file in $_dir; do
1168 [[ -e "${initdir}/$_file" ]] && continue
1169 if [[ -L $_file ]]; then
1170 inst_symlink "$_file"
1171 else
1172 # create directory
1173 mkdir -m 0755 -p "${initdir}/$_file" || return 1
1174 [[ -e "$_file" ]] && chmod --reference="$_file" "${initdir}/$_file"
1175 chmod u+w "${initdir}/$_file"
1176 fi
1177 done
1178 }
1179
1180 # $1 = file to copy to ramdisk
1181 # $2 (optional) Name for the file on the ramdisk
1182 # Location of the image dir is assumed to be $initdir
1183 # We never overwrite the target if it exists.
1184 inst_simple() {
1185 [[ -f "$1" ]] || return 1
1186 strstr "$1" "/" || return 1
1187
1188 local _src=$1 target="${2:-$1}"
1189 if ! [[ -d ${initdir}/$target ]]; then
1190 [[ -e ${initdir}/$target ]] && return 0
1191 [[ -L ${initdir}/$target ]] && return 0
1192 [[ -d "${initdir}/${target%/*}" ]] || inst_dir "${target%/*}"
1193 fi
1194 # install checksum files also
1195 if [[ -e "${_src%/*}/.${_src##*/}.hmac" ]]; then
1196 inst "${_src%/*}/.${_src##*/}.hmac" "${target%/*}/.${target##*/}.hmac"
1197 fi
1198 ddebug "Installing $_src"
1199 cp --sparse=always -pfL "$_src" "${initdir}/$target"
1200 }
1201
1202 # find symlinks linked to given library file
1203 # $1 = library file
1204 # Function searches for symlinks by stripping version numbers appended to
1205 # library filename, checks if it points to the same target and finally
1206 # prints the list of symlinks to stdout.
1207 #
1208 # Example:
1209 # rev_lib_symlinks libfoo.so.8.1
1210 # output: libfoo.so.8 libfoo.so
1211 # (Only if libfoo.so.8 and libfoo.so exists on host system.)
1212 rev_lib_symlinks() {
1213 [[ ! $1 ]] && return 0
1214
1215 local fn="$1" orig="$(readlink -f "$1")" links=''
1216
1217 [[ ${fn} =~ .*\.so\..* ]] || return 1
1218
1219 until [[ ${fn##*.} == so ]]; do
1220 fn="${fn%.*}"
1221 [[ -L ${fn} && $(readlink -f "${fn}") == ${orig} ]] && links+=" ${fn}"
1222 done
1223
1224 echo "${links}"
1225 }
1226
1227 # Same as above, but specialized to handle dynamic libraries.
1228 # It handles making symlinks according to how the original library
1229 # is referenced.
1230 inst_library() {
1231 local _src="$1" _dest=${2:-$1} _lib _reallib _symlink
1232 strstr "$1" "/" || return 1
1233 [[ -e $initdir/$_dest ]] && return 0
1234 if [[ -L $_src ]]; then
1235 # install checksum files also
1236 if [[ -e "${_src%/*}/.${_src##*/}.hmac" ]]; then
1237 inst "${_src%/*}/.${_src##*/}.hmac" "${_dest%/*}/.${_dest##*/}.hmac"
1238 fi
1239 _reallib=$(readlink -f "$_src")
1240 inst_simple "$_reallib" "$_reallib"
1241 inst_dir "${_dest%/*}"
1242 [[ -d "${_dest%/*}" ]] && _dest=$(readlink -f "${_dest%/*}")/${_dest##*/}
1243 ln -sfn $(convert_abs_rel "${_dest}" "${_reallib}") "${initdir}/${_dest}"
1244 else
1245 inst_simple "$_src" "$_dest"
1246 fi
1247
1248 # Create additional symlinks. See rev_symlinks description.
1249 for _symlink in $(rev_lib_symlinks $_src) $(rev_lib_symlinks $_reallib); do
1250 [[ -e $initdir/$_symlink ]] || {
1251 ddebug "Creating extra symlink: $_symlink"
1252 inst_symlink $_symlink
1253 }
1254 done
1255 }
1256
1257 # find a binary. If we were not passed the full path directly,
1258 # search in the usual places to find the binary.
1259 find_binary() {
1260 if [[ -z ${1##/*} ]]; then
1261 if [[ -x $1 ]] || { strstr "$1" ".so" && ldd $1 &>/dev/null; }; then
1262 echo $1
1263 return 0
1264 fi
1265 fi
1266
1267 type -P $1
1268 }
1269
1270 # Same as above, but specialized to install binary executables.
1271 # Install binary executable, and all shared library dependencies, if any.
1272 inst_binary() {
1273 local _bin _target
1274
1275 # In certain cases we might attempt to install a binary which is already
1276 # present in the test image, yet it's missing from the host system.
1277 # In such cases, let's check if the binary indeed exists in the image
1278 # before doing any other chcecks. If it does, immediately return with
1279 # success.
1280 [[ $# -eq 1 && -e $initdir/$1 ]] && return 0
1281
1282 _bin=$(find_binary "$1") || return 1
1283 _target=${2:-$_bin}
1284 [[ -e $initdir/$_target ]] && return 0
1285 [[ -L $_bin ]] && inst_symlink $_bin $_target && return 0
1286 local _file _line
1287 local _so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)'
1288 # I love bash!
1289 LC_ALL=C ldd "$_bin" 2>/dev/null | while read _line; do
1290 [[ $_line = 'not a dynamic executable' ]] && break
1291
1292 if [[ $_line =~ $_so_regex ]]; then
1293 _file=${BASH_REMATCH[1]}
1294 [[ -e ${initdir}/$_file ]] && continue
1295 inst_library "$_file"
1296 continue
1297 fi
1298
1299 if [[ $_line =~ not\ found ]]; then
1300 dfatal "Missing a shared library required by $_bin."
1301 dfatal "Run \"ldd $_bin\" to find out what it is."
1302 dfatal "$_line"
1303 dfatal "dracut cannot create an initrd."
1304 exit 1
1305 fi
1306 done
1307 inst_simple "$_bin" "$_target"
1308 }
1309
1310 # same as above, except for shell scripts.
1311 # If your shell script does not start with shebang, it is not a shell script.
1312 inst_script() {
1313 local _bin
1314 _bin=$(find_binary "$1") || return 1
1315 shift
1316 local _line _shebang_regex
1317 read -r -n 80 _line <"$_bin"
1318 # If debug is set, clean unprintable chars to prevent messing up the term
1319 [[ $debug ]] && _line=$(echo -n "$_line" | tr -c -d '[:print:][:space:]')
1320 _shebang_regex='(#! *)(/[^ ]+).*'
1321 [[ $_line =~ $_shebang_regex ]] || return 1
1322 inst "${BASH_REMATCH[2]}" && inst_simple "$_bin" "$@"
1323 }
1324
1325 # same as above, but specialized for symlinks
1326 inst_symlink() {
1327 local _src=$1 _target=${2:-$1} _realsrc
1328 strstr "$1" "/" || return 1
1329 [[ -L $1 ]] || return 1
1330 [[ -L $initdir/$_target ]] && return 0
1331 _realsrc=$(readlink -f "$_src")
1332 if ! [[ -e $initdir/$_realsrc ]]; then
1333 if [[ -d $_realsrc ]]; then
1334 inst_dir "$_realsrc"
1335 else
1336 inst "$_realsrc"
1337 fi
1338 fi
1339 [[ ! -e $initdir/${_target%/*} ]] && inst_dir "${_target%/*}"
1340 [[ -d ${_target%/*} ]] && _target=$(readlink -f ${_target%/*})/${_target##*/}
1341 ln -sfn $(convert_abs_rel "${_target}" "${_realsrc}") "$initdir/$_target"
1342 }
1343
1344 # attempt to install any programs specified in a udev rule
1345 inst_rule_programs() {
1346 local _prog _bin
1347
1348 if grep -qE 'PROGRAM==?"[^ "]+' "$1"; then
1349 for _prog in $(grep -E 'PROGRAM==?"[^ "]+' "$1" | sed -r 's/.*PROGRAM==?"([^ "]+).*/\1/'); do
1350 if [ -x /lib/udev/$_prog ]; then
1351 _bin=/lib/udev/$_prog
1352 else
1353 _bin=$(find_binary "$_prog") || {
1354 dinfo "Skipping program $_prog using in udev rule $(basename $1) as it cannot be found"
1355 continue;
1356 }
1357 fi
1358
1359 #dinfo "Installing $_bin due to it's use in the udev rule $(basename $1)"
1360 dracut_install "$_bin"
1361 done
1362 fi
1363 }
1364
1365 # udev rules always get installed in the same place, so
1366 # create a function to install them to make life simpler.
1367 inst_rules() {
1368 local _target=/etc/udev/rules.d _rule _found
1369
1370 inst_dir "/lib/udev/rules.d"
1371 inst_dir "$_target"
1372 for _rule in "$@"; do
1373 if [ "${rule#/}" = "$rule" ]; then
1374 for r in /lib/udev/rules.d /etc/udev/rules.d; do
1375 if [[ -f $r/$_rule ]]; then
1376 _found="$r/$_rule"
1377 inst_simple "$_found"
1378 inst_rule_programs "$_found"
1379 fi
1380 done
1381 fi
1382 for r in '' ./ $dracutbasedir/rules.d/; do
1383 if [[ -f ${r}$_rule ]]; then
1384 _found="${r}$_rule"
1385 inst_simple "$_found" "$_target/${_found##*/}"
1386 inst_rule_programs "$_found"
1387 fi
1388 done
1389 [[ $_found ]] || dinfo "Skipping udev rule: $_rule"
1390 _found=
1391 done
1392 }
1393
1394 # general purpose installation function
1395 # Same args as above.
1396 inst() {
1397 local _x
1398
1399 case $# in
1400 1) ;;
1401 2) [[ ! $initdir && -d $2 ]] && export initdir=$2
1402 [[ $initdir = $2 ]] && set $1;;
1403 3) [[ -z $initdir ]] && export initdir=$2
1404 set $1 $3;;
1405 *) dfatal "inst only takes 1 or 2 or 3 arguments"
1406 exit 1;;
1407 esac
1408 for _x in inst_symlink inst_script inst_binary inst_simple; do
1409 $_x "$@" && return 0
1410 done
1411 return 1
1412 }
1413
1414 # install any of listed files
1415 #
1416 # If first argument is '-d' and second some destination path, first accessible
1417 # source is installed into this path, otherwise it will installed in the same
1418 # path as source. If none of listed files was installed, function return 1.
1419 # On first successful installation it returns with 0 status.
1420 #
1421 # Example:
1422 #
1423 # inst_any -d /bin/foo /bin/bar /bin/baz
1424 #
1425 # Lets assume that /bin/baz exists, so it will be installed as /bin/foo in
1426 # initramfs.
1427 inst_any() {
1428 local to f
1429
1430 [[ $1 = '-d' ]] && to="$2" && shift 2
1431
1432 for f in "$@"; do
1433 if [[ -e $f ]]; then
1434 [[ $to ]] && inst "$f" "$to" && return 0
1435 inst "$f" && return 0
1436 fi
1437 done
1438
1439 return 1
1440 }
1441
1442 # dracut_install [-o ] <file> [<file> ... ]
1443 # Install <file> to the initramfs image
1444 # -o optionally install the <file> and don't fail, if it is not there
1445 dracut_install() {
1446 local _optional=no
1447 if [[ $1 = '-o' ]]; then
1448 _optional=yes
1449 shift
1450 fi
1451 while (($# > 0)); do
1452 if ! inst "$1" ; then
1453 if [[ $_optional = yes ]]; then
1454 dinfo "Skipping program $1 as it cannot be found and is" \
1455 "flagged to be optional"
1456 else
1457 dfatal "Failed to install $1"
1458 exit 1
1459 fi
1460 fi
1461 shift
1462 done
1463 }
1464
1465 # Install a single kernel module along with any firmware it may require.
1466 # $1 = full path to kernel module to install
1467 install_kmod_with_fw() {
1468 # no need to go further if the module is already installed
1469
1470 [[ -e "${initdir}/lib/modules/$KERNEL_VER/${1##*/lib/modules/$KERNEL_VER/}" ]] \
1471 && return 0
1472
1473 [[ -e "$initdir/.kernelmodseen/${1##*/}" ]] && return 0
1474
1475 if [[ $omit_drivers ]]; then
1476 local _kmod=${1##*/}
1477 _kmod=${_kmod%.ko}
1478 _kmod=${_kmod/-/_}
1479 if [[ "$_kmod" =~ $omit_drivers ]]; then
1480 dinfo "Omitting driver $_kmod"
1481 return 1
1482 fi
1483 if [[ "${1##*/lib/modules/$KERNEL_VER/}" =~ $omit_drivers ]]; then
1484 dinfo "Omitting driver $_kmod"
1485 return 1
1486 fi
1487 fi
1488
1489 [ -d "$initdir/.kernelmodseen" ] && \
1490 > "$initdir/.kernelmodseen/${1##*/}"
1491
1492 inst_simple "$1" "/lib/modules/$KERNEL_VER/${1##*/lib/modules/$KERNEL_VER/}" \
1493 || return $?
1494
1495 local _modname=${1##*/} _fwdir _found _fw
1496 _modname=${_modname%.ko*}
1497 for _fw in $(modinfo -k $KERNEL_VER -F firmware $1 2>/dev/null); do
1498 _found=''
1499 for _fwdir in $fw_dir; do
1500 if [[ -d $_fwdir && -f $_fwdir/$_fw ]]; then
1501 inst_simple "$_fwdir/$_fw" "/lib/firmware/$_fw"
1502 _found=yes
1503 fi
1504 done
1505 if [[ $_found != yes ]]; then
1506 if ! grep -qe "\<${_modname//-/_}\>" /proc/modules; then
1507 dinfo "Possible missing firmware \"${_fw}\" for kernel module" \
1508 "\"${_modname}.ko\""
1509 else
1510 dwarn "Possible missing firmware \"${_fw}\" for kernel module" \
1511 "\"${_modname}.ko\""
1512 fi
1513 fi
1514 done
1515 return 0
1516 }
1517
1518 # Do something with all the dependencies of a kernel module.
1519 # Note that kernel modules depend on themselves using the technique we use
1520 # $1 = function to call for each dependency we find
1521 # It will be passed the full path to the found kernel module
1522 # $2 = module to get dependencies for
1523 # rest of args = arguments to modprobe
1524 # _fderr specifies FD passed from surrounding scope
1525 for_each_kmod_dep() {
1526 local _func=$1 _kmod=$2 _cmd _modpath _options _found=0
1527 shift 2
1528 modprobe "$@" --ignore-install --show-depends $_kmod 2>&${_fderr} | (
1529 while read _cmd _modpath _options; do
1530 [[ $_cmd = insmod ]] || continue
1531 $_func ${_modpath} || exit $?
1532 _found=1
1533 done
1534 [[ $_found -eq 0 ]] && exit 1
1535 exit 0
1536 )
1537 }
1538
1539 # filter kernel modules to install certain modules that meet specific
1540 # requirements.
1541 # $1 = search only in subdirectory of /kernel/$1
1542 # $2 = function to call with module name to filter.
1543 # This function will be passed the full path to the module to test.
1544 # The behavior of this function can vary depending on whether $hostonly is set.
1545 # If it is, we will only look at modules that are already in memory.
1546 # If it is not, we will look at all kernel modules
1547 # This function returns the full filenames of modules that match $1
1548 filter_kernel_modules_by_path () (
1549 local _modname _filtercmd
1550 if ! [[ $hostonly ]]; then
1551 _filtercmd='find "$KERNEL_MODS/kernel/$1" "$KERNEL_MODS/extra"'
1552 _filtercmd+=' "$KERNEL_MODS/weak-updates" -name "*.ko" -o -name "*.ko.gz"'
1553 _filtercmd+=' -o -name "*.ko.xz"'
1554 _filtercmd+=' 2>/dev/null'
1555 else
1556 _filtercmd='cut -d " " -f 1 </proc/modules|xargs modinfo -F filename '
1557 _filtercmd+='-k $KERNEL_VER 2>/dev/null'
1558 fi
1559 for _modname in $(eval $_filtercmd); do
1560 case $_modname in
1561 *.ko) "$2" "$_modname" && echo "$_modname";;
1562 *.ko.gz) gzip -dc "$_modname" > $initdir/$$.ko
1563 $2 $initdir/$$.ko && echo "$_modname"
1564 rm -f $initdir/$$.ko
1565 ;;
1566 *.ko.xz) xz -dc "$_modname" > $initdir/$$.ko
1567 $2 $initdir/$$.ko && echo "$_modname"
1568 rm -f $initdir/$$.ko
1569 ;;
1570 esac
1571 done
1572 )
1573 find_kernel_modules_by_path () (
1574 if ! [[ $hostonly ]]; then
1575 find "$KERNEL_MODS/kernel/$1" "$KERNEL_MODS/extra" "$KERNEL_MODS/weak-updates" \
1576 -name "*.ko" -o -name "*.ko.gz" -o -name "*.ko.xz" 2>/dev/null
1577 else
1578 cut -d " " -f 1 </proc/modules \
1579 | xargs modinfo -F filename -k $KERNEL_VER 2>/dev/null
1580 fi
1581 )
1582
1583 filter_kernel_modules () {
1584 filter_kernel_modules_by_path drivers "$1"
1585 }
1586
1587 find_kernel_modules () {
1588 find_kernel_modules_by_path drivers
1589 }
1590
1591 # instmods [-c] <kernel module> [<kernel module> ... ]
1592 # instmods [-c] <kernel subsystem>
1593 # install kernel modules along with all their dependencies.
1594 # <kernel subsystem> can be e.g. "=block" or "=drivers/usb/storage"
1595 instmods() {
1596 [[ $no_kernel = yes ]] && return
1597 # called [sub]functions inherit _fderr
1598 local _fderr=9
1599 local _check=no
1600 if [[ $1 = '-c' ]]; then
1601 _check=yes
1602 shift
1603 fi
1604
1605 function inst1mod() {
1606 local _ret=0 _mod="$1"
1607 case $_mod in
1608 =*)
1609 if [ -f $KERNEL_MODS/modules.${_mod#=} ]; then
1610 ( [[ "$_mpargs" ]] && echo $_mpargs
1611 cat "${KERNEL_MODS}/modules.${_mod#=}" ) \
1612 | instmods
1613 else
1614 ( [[ "$_mpargs" ]] && echo $_mpargs
1615 find "$KERNEL_MODS" -path "*/${_mod#=}/*" -type f -printf '%f\n' ) \
1616 | instmods
1617 fi
1618 ;;
1619 --*) _mpargs+=" $_mod" ;;
1620 i2o_scsi) return ;; # Do not load this diagnostic-only module
1621 *)
1622 _mod=${_mod##*/}
1623 # if we are already installed, skip this module and go on
1624 # to the next one.
1625 [[ -f "$initdir/.kernelmodseen/${_mod%.ko}.ko" ]] && return
1626
1627 if [[ $omit_drivers ]] && [[ "$1" =~ $omit_drivers ]]; then
1628 dinfo "Omitting driver ${_mod##$KERNEL_MODS}"
1629 return
1630 fi
1631 # If we are building a host-specific initramfs and this
1632 # module is not already loaded, move on to the next one.
1633 [[ $hostonly ]] && ! grep -qe "\<${_mod//-/_}\>" /proc/modules \
1634 && ! echo $add_drivers | grep -qe "\<${_mod}\>" \
1635 && return
1636
1637 # We use '-d' option in modprobe only if modules prefix path
1638 # differs from default '/'. This allows us to use Dracut with
1639 # old version of modprobe which doesn't have '-d' option.
1640 local _moddirname=${KERNEL_MODS%%/lib/modules/*}
1641 [[ -n ${_moddirname} ]] && _moddirname="-d ${_moddirname}/"
1642
1643 # ok, load the module, all its dependencies, and any firmware
1644 # it may require
1645 for_each_kmod_dep install_kmod_with_fw $_mod \
1646 --set-version $KERNEL_VER ${_moddirname} $_mpargs
1647 ((_ret+=$?))
1648 ;;
1649 esac
1650 return $_ret
1651 }
1652
1653 function instmods_1() {
1654 local _mod _mpargs
1655 if (($# == 0)); then # filenames from stdin
1656 while read _mod; do
1657 inst1mod "${_mod%.ko*}" || {
1658 if [ "$_check" = "yes" ]; then
1659 dfatal "Failed to install $_mod"
1660 return 1
1661 fi
1662 }
1663 done
1664 fi
1665 while (($# > 0)); do # filenames as arguments
1666 inst1mod ${1%.ko*} || {
1667 if [ "$_check" = "yes" ]; then
1668 dfatal "Failed to install $1"
1669 return 1
1670 fi
1671 }
1672 shift
1673 done
1674 return 0
1675 }
1676
1677 local _ret _filter_not_found='FATAL: Module .* not found.'
1678 set -o pipefail
1679 # Capture all stderr from modprobe to _fderr. We could use {var}>...
1680 # redirections, but that would make dracut require bash4 at least.
1681 eval "( instmods_1 \"\$@\" ) ${_fderr}>&1" \
1682 | while read line; do [[ "$line" =~ $_filter_not_found ]] && echo $line || echo $line >&2 ;done | derror
1683 _ret=$?
1684 set +o pipefail
1685 return $_ret
1686 }
1687
1688 setup_suse() {
1689 ln -fs ../usr/bin/systemctl $initdir/bin/
1690 ln -fs ../usr/lib/systemd $initdir/lib/
1691 inst_simple "/usr/lib/systemd/system/haveged.service"
1692 }
1693
1694 _umount_dir() {
1695 if mountpoint -q $1; then
1696 ddebug "umount $1"
1697 umount $1
1698 fi
1699 }
1700
1701 _test_setup_cleanup() {
1702 # only umount if create_empty_image_rootdir() was called to mount it
1703 [[ -z $TEST_SETUP_CLEANUP_ROOTDIR ]] || _umount_dir $initdir
1704 }
1705
1706 # can be overridden in specific test
1707 test_setup_cleanup() {
1708 _test_setup_cleanup
1709 }
1710
1711 _test_cleanup() {
1712 # (post-test) cleanup should always ignore failure and cleanup as much as possible
1713 (
1714 set +e
1715 _umount_dir $initdir
1716 if [[ $LOOPDEV && -b $LOOPDEV ]]; then
1717 ddebug "losetup -d $LOOPDEV"
1718 losetup -d $LOOPDEV
1719 fi
1720 rm -fr "$TESTDIR"
1721 rm -f "$STATEFILE"
1722 ) || :
1723 }
1724
1725 # can be overridden in specific test
1726 test_cleanup() {
1727 _test_cleanup
1728 }
1729
1730 test_run() {
1731 if [ -z "$TEST_NO_QEMU" ]; then
1732 if run_qemu; then
1733 check_result_qemu || return 1
1734 else
1735 dwarn "can't run QEMU, skipping"
1736 fi
1737 fi
1738 if [ -z "$TEST_NO_NSPAWN" ]; then
1739 if run_nspawn "nspawn-root"; then
1740 check_result_nspawn "nspawn-root" || return 1
1741 else
1742 dwarn "can't run systemd-nspawn, skipping"
1743 fi
1744
1745 if [[ "$RUN_IN_UNPRIVILEGED_CONTAINER" = "yes" ]]; then
1746 if NSPAWN_ARGUMENTS="-U --private-network $NSPAWN_ARGUMENTS" run_nspawn "unprivileged-nspawn-root"; then
1747 check_result_nspawn "unprivileged-nspawn-root" || return 1
1748 else
1749 dwarn "can't run systemd-nspawn, skipping"
1750 fi
1751 fi
1752 fi
1753 return 0
1754 }
1755
1756 do_test() {
1757 if [[ $UID != "0" ]]; then
1758 echo "TEST: $TEST_DESCRIPTION [SKIPPED]: not root" >&2
1759 exit 0
1760 fi
1761
1762 # Detect lib paths
1763 [[ $libdir ]] || for libdir in /lib64 /lib; do
1764 [[ -d $libdir ]] && libdirs+=" $libdir" && break
1765 done
1766
1767 [[ $usrlibdir ]] || for usrlibdir in /usr/lib64 /usr/lib; do
1768 [[ -d $usrlibdir ]] && libdirs+=" $usrlibdir" && break
1769 done
1770
1771 mkdir -p "$STATEDIR"
1772
1773 import_testdir
1774 import_initdir
1775
1776 while (($# > 0)); do
1777 case $1 in
1778 --run)
1779 echo "TEST RUN: $TEST_DESCRIPTION"
1780 test_run
1781 ret=$?
1782 if (( $ret == 0 )); then
1783 echo "TEST RUN: $TEST_DESCRIPTION [OK]"
1784 else
1785 echo "TEST RUN: $TEST_DESCRIPTION [FAILED]"
1786 fi
1787 exit $ret;;
1788 --setup)
1789 echo "TEST SETUP: $TEST_DESCRIPTION"
1790 test_setup
1791 test_setup_cleanup
1792 ;;
1793 --clean)
1794 echo "TEST CLEANUP: $TEST_DESCRIPTION"
1795 test_cleanup
1796 ;;
1797 --all)
1798 ret=0
1799 echo -n "TEST: $TEST_DESCRIPTION "
1800 (
1801 test_setup
1802 test_setup_cleanup
1803 test_run
1804 ) </dev/null >"$TESTLOG" 2>&1 || ret=$?
1805 test_cleanup
1806 if [ $ret -eq 0 ]; then
1807 rm "$TESTLOG"
1808 echo "[OK]"
1809 else
1810 echo "[FAILED]"
1811 echo "see $TESTLOG"
1812 fi
1813 exit $ret;;
1814 *) break ;;
1815 esac
1816 shift
1817 done
1818 }