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