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