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