]> git.ipfire.org Git - thirdparty/systemd.git/blob - test/test-functions
update TODO
[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=240s\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 if [[ "$LOOKS_LIKE_SUSE" ]]; then
637 inst_rules 60-persistent-storage.rules 61-persistent-storage-compat.rules 99-systemd.rules
638 fi
639 }
640
641 install_systemd() {
642 # install compiled files
643 local _ninja_bin=$(type -P ninja || type -P ninja-build)
644 if [[ -z "$_ninja_bin" ]]; then
645 dfatal "ninja was not found"
646 exit 1
647 fi
648 (set -x; DESTDIR=$initdir "$_ninja_bin" -C $BUILD_DIR install)
649 # remove unneeded documentation
650 rm -fr $initdir/usr/share/{man,doc}
651 # we strip binaries since debug symbols increase binaries size a lot
652 # and it could fill the available space
653 strip_binaries
654
655 [[ "$LOOKS_LIKE_SUSE" ]] && setup_suse
656
657 # enable debug logging in PID1
658 echo LogLevel=debug >> $initdir/etc/systemd/system.conf
659 # store coredumps in journal
660 echo Storage=journal >> $initdir/etc/systemd/coredump.conf
661 }
662
663 get_ldpath() {
664 local _bin="$1"
665 local rpath=$(objdump -p "$_bin" 2>/dev/null | awk "/R(UN)?PATH/ { print \"$initdir\" \$2 }" | paste -sd :)
666
667 if [ -z "$rpath" ] ; then
668 echo $BUILD_DIR
669 else
670 echo $rpath
671 fi
672 }
673
674 install_missing_libraries() {
675 # install possible missing libraries
676 for i in $initdir{,/usr}/{sbin,bin}/* $initdir{,/usr}/lib/systemd/{,tests/{,manual/,unsafe/}}*; do
677 LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$(get_ldpath $i)" inst_libs $i
678 done
679 }
680
681 cleanup_loopdev() {
682 if [ -n "${LOOPDEV}" ]; then
683 ddebug "losetup -d $LOOPDEV"
684 losetup -d "${LOOPDEV}"
685 unset LOOPDEV
686 fi
687 }
688
689 trap cleanup_loopdev EXIT INT QUIT PIPE
690
691 create_empty_image() {
692 if [ -z "$IMAGE_NAME" ]; then
693 echo "create_empty_image: \$IMAGE_NAME not set"
694 exit 1
695 fi
696
697 local _size=500
698 if [[ "$STRIP_BINARIES" = "no" ]]; then
699 _size=$((4*_size))
700 fi
701
702 echo "Setting up $IMAGE_PUBLIC (${_size} MB)"
703 rm -f "$IMAGE_PRIVATE" "$IMAGE_PUBLIC"
704
705 # Create the blank file to use as a root filesystem
706 truncate -s "${_size}M" "$IMAGE_PUBLIC"
707
708 LOOPDEV=$(losetup --show -P -f "$IMAGE_PUBLIC")
709 [ -b "$LOOPDEV" ] || return 1
710 sfdisk "$LOOPDEV" <<EOF
711 ,$((_size-50))M
712 ,
713 EOF
714
715 udevadm settle
716
717 local _label="-L systemd.${name}"
718 # mkfs.reiserfs doesn't know -L. so, use --label instead
719 [[ "$FSTYPE" == "reiserfs" ]] && _label="--label systemd.${name}"
720 mkfs -t "${FSTYPE}" ${_label} "${LOOPDEV}p1" -q; ret=$?
721 if [ $ret -ne 0 ] ; then
722 dfatal "Failed to mkfs -t ${FSTYPE}"
723 exit 1
724 fi
725 }
726
727 mount_initdir() {
728 if [ -z "${LOOPDEV}" ]; then
729 [ -e "$IMAGE_PRIVATE" ] && image="$IMAGE_PRIVATE" || image="$IMAGE_PUBLIC"
730 LOOPDEV=$(losetup --show -P -f "$image")
731 [ -b "$LOOPDEV" ] || return 1
732
733 udevadm settle
734 fi
735
736 if ! mountpoint -q $initdir; then
737 mkdir -p $initdir
738 mount ${LOOPDEV}p1 $initdir
739 TEST_SETUP_CLEANUP_ROOTDIR=1
740 fi
741 }
742
743 cleanup_initdir() {
744 # only umount if create_empty_image_rootdir() was called to mount it
745 [[ -z $TEST_SETUP_CLEANUP_ROOTDIR ]] || _umount_dir $initdir
746 }
747
748 umount_loopback() {
749 # unmount the loopback device from all places. Otherwise we risk file
750 # system corruption.
751 for device in $(losetup -l | awk '$6=="'"$IMAGE_PUBLIC"'" {print $1}'); do
752 ddebug "Unmounting all uses of $device"
753 mount | awk '/^'"${device}"'p/{print $1}' | xargs --no-run-if-empty umount -v
754 done
755 }
756
757 create_empty_image_rootdir() {
758 create_empty_image
759 mount_initdir
760 }
761
762 check_asan_reports() {
763 local ret=0
764 local root="$1"
765
766 if [[ "$IS_BUILT_WITH_ASAN" = "yes" ]]; then
767 ls -l "$root"
768 if [[ -e "$root/systemd.asan.log.1" ]]; then
769 cat "$root/systemd.asan.log.1"
770 ret=$(($ret+1))
771 fi
772
773 journald_report=$(find "$root" -name "systemd-journald.*san.log*" -exec cat {} \;)
774 if [[ ! -z "$journald_report" ]]; then
775 printf "%s\n" "$journald_report"
776 cat "$root/systemd-journald.out" || :
777 ret=$(($ret+1))
778 fi
779
780 pids=$(
781 "$JOURNALCTL" -D "$root/var/log/journal" | perl -alne '
782 BEGIN {
783 %services_to_ignore = (
784 "dbus-daemon" => undef,
785 );
786 }
787 print $2 if /\s(\S*)\[(\d+)\]:\s*SUMMARY:\s+\w+Sanitizer/ && !exists $services_to_ignore{$1}'
788 )
789 if [[ ! -z "$pids" ]]; then
790 ret=$(($ret+1))
791 for pid in $pids; do
792 "$JOURNALCTL" -D "$root/var/log/journal" _PID=$pid --no-pager
793 done
794 fi
795 fi
796
797 return $ret
798 }
799
800 save_journal() {
801 if [ -n "${ARTIFACT_DIRECTORY}" ]; then
802 dest="${ARTIFACT_DIRECTORY}/${testname}.journal"
803 else
804 dest="$TESTDIR/system.journal"
805 fi
806
807 for j in $1/*; do
808 $SYSTEMD_JOURNAL_REMOTE \
809 -o $dest \
810 --getter="$JOURNALCTL -o export -D $j"
811
812 if [ -n "${TEST_SHOW_JOURNAL}" ]; then
813 echo "---- $j ----"
814 $JOURNALCTL --no-pager -o short-monotonic --no-hostname --priority=${TEST_SHOW_JOURNAL} -D $j
815 fi
816
817 rm -r $j
818 done
819
820 # we want to print this sometime later, so save this in a variable
821 JOURNAL_LIST="$(ls -l $dest*)"
822 }
823
824 check_result_nspawn() {
825 local ret=1
826 local journald_report=""
827 local pids=""
828 [[ -e $1/testok ]] && ret=0
829 [[ -f $1/failed ]] && cp -a $1/failed $TESTDIR
830 save_journal $1/var/log/journal
831 [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed
832 echo $JOURNAL_LIST
833 test -s $TESTDIR/failed && ret=$(($ret+1))
834 [ -n "$TIMED_OUT" ] && ret=$(($ret+1))
835 check_asan_reports "$1" || ret=$(($ret+1))
836 _umount_dir $initdir
837 return $ret
838 }
839
840 # can be overridden in specific test
841 check_result_qemu() {
842 local ret=1
843 mount_initdir
844 [[ -e $initdir/testok ]] && ret=0
845 [[ -f $initdir/failed ]] && cp -a $initdir/failed $TESTDIR
846 save_journal $initdir/var/log/journal
847 check_asan_reports "$initdir" || ret=$(($ret+1))
848 _umount_dir $initdir
849 [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed
850 echo $JOURNAL_LIST
851 test -s $TESTDIR/failed && ret=$(($ret+1))
852 [ -n "$TIMED_OUT" ] && ret=$(($ret+1))
853 return $ret
854 }
855
856 strip_binaries() {
857 if [[ "$STRIP_BINARIES" = "no" ]]; then
858 ddebug "Don't strip binaries"
859 return 0
860 fi
861 ddebug "Strip binaries"
862 find "$initdir" -executable -not -path '*/lib/modules/*.ko' -type f | \
863 xargs strip --strip-unneeded |& \
864 grep -vi 'file format not recognized' | \
865 ddebug
866 }
867
868 create_rc_local() {
869 mkdir -p $initdir/etc/rc.d
870 cat >$initdir/etc/rc.d/rc.local <<EOF
871 #!/usr/bin/env bash
872 exit 0
873 EOF
874 chmod 0755 $initdir/etc/rc.d/rc.local
875 }
876
877 install_execs() {
878 ddebug "install any Execs from the service files"
879 (
880 export PKG_CONFIG_PATH=$BUILD_DIR/src/core/
881 systemdsystemunitdir=$(pkg-config --variable=systemdsystemunitdir systemd)
882 systemduserunitdir=$(pkg-config --variable=systemduserunitdir systemd)
883 sed -r -n 's|^Exec[a-zA-Z]*=[@+!-]*([^ ]+).*|\1|gp' $initdir/{$systemdsystemunitdir,$systemduserunitdir}/*.service \
884 | sort -u | while read i; do
885 # some {rc,halt}.local scripts and programs are okay to not exist, the rest should
886 # also, plymouth is pulled in by rescue.service, but even there the exit code
887 # is ignored; as it's not present on some distros, don't fail if it doesn't exist
888 dinfo "Attempting to install $i"
889 inst $i || [ "${i%.local}" != "$i" ] || [ "${i%systemd-update-done}" != "$i" ] || [ "${i##*/}" == "plymouth" ]
890 done
891 )
892 }
893
894 generate_module_dependencies() {
895 if [[ -d $initdir/lib/modules/$KERNEL_VER ]] && \
896 ! depmod -a -b "$initdir" $KERNEL_VER; then
897 dfatal "\"depmod -a $KERNEL_VER\" failed."
898 exit 1
899 fi
900 }
901
902 install_depmod_files() {
903 inst /lib/modules/$KERNEL_VER/modules.order
904 inst /lib/modules/$KERNEL_VER/modules.builtin
905 }
906
907 install_plymouth() {
908 # install plymouth, if found... else remove plymouth service files
909 # if [ -x /usr/libexec/plymouth/plymouth-populate-initrd ]; then
910 # PLYMOUTH_POPULATE_SOURCE_FUNCTIONS="$TEST_BASE_DIR/test-functions" \
911 # /usr/libexec/plymouth/plymouth-populate-initrd -t $initdir
912 # dracut_install plymouth plymouthd
913 # else
914 rm -f $initdir/{usr/lib,lib,etc}/systemd/system/plymouth* $initdir/{usr/lib,lib,etc}/systemd/system/*/plymouth*
915 # fi
916 }
917
918 install_ld_so_conf() {
919 cp -a /etc/ld.so.conf* $initdir/etc
920 ldconfig -r "$initdir"
921 }
922
923 install_testuser() {
924 # create unprivileged user for user manager tests
925 mkdir -p $initdir/etc/sysusers.d
926 cat >$initdir/etc/sysusers.d/testuser.conf <<EOF
927 u testuser 4711 "Test User" /home/testuser
928 EOF
929
930 mkdir -p $initdir/home/testuser -m 0700
931 chown 4711:4711 $initdir/home/testuser
932 }
933
934 install_config_files() {
935 inst /etc/sysconfig/init || :
936 inst /etc/passwd
937 inst /etc/shadow
938 inst_any /etc/login.defs /usr/etc/login.defs
939 inst /etc/group
940 inst /etc/shells
941 inst_any /etc/nsswitch.conf /usr/etc/nsswitch.conf
942 inst /etc/pam.conf || :
943 inst /etc/os-release
944 inst /etc/localtime
945 # we want an empty environment
946 > $initdir/etc/environment
947 > $initdir/etc/machine-id
948
949 # set the hostname
950 echo systemd-testsuite > $initdir/etc/hostname
951
952 # let's set up just one image with the traditional verbose output
953 if [ ${IMAGE_NAME} != "basic" ]; then
954 mkdir -p $initdir/etc/systemd/system.conf.d
955 echo -e '[Manager]\nStatusUnitFormat=name' >$initdir/etc/systemd/system.conf.d/status.conf
956 fi
957 }
958
959 install_basic_tools() {
960 dracut_install "${BASICTOOLS[@]}"
961 dracut_install -o sushell
962 # in Debian ldconfig is just a shell script wrapper around ldconfig.real
963 dracut_install -o ldconfig.real
964 }
965
966 install_debug_tools() {
967 dracut_install "${DEBUGTOOLS[@]}"
968
969 if [[ $INTERACTIVE_DEBUG ]]; then
970 # Set default TERM from vt220 to linux, so at least basic key shortcuts work
971 local _getty_override="$initdir/etc/systemd/system/serial-getty@.service.d"
972 mkdir -p "$_getty_override"
973 echo -e "[Service]\nEnvironment=TERM=linux" > "$_getty_override/default-TERM.conf"
974
975 cat > "$initdir/etc/motd" << EOF
976 To adjust the terminal size use:
977 export COLUMNS=xx
978 export LINES=yy
979 or
980 stty cols xx rows yy
981 EOF
982 fi
983 }
984
985 install_libnss() {
986 # install libnss_files for login
987 NSS_LIBS=$(LD_DEBUG=files getent passwd 2>&1 >/dev/null |sed -n '/calling init: .*libnss_/ {s!^.* /!/!; p}')
988 dracut_install $NSS_LIBS
989 }
990
991 install_dbus() {
992 inst $ROOTLIBDIR/system/dbus.socket
993
994 # Newer Fedora versions use dbus-broker by default. Let's install it if it's available.
995 if [ -f $ROOTLIBDIR/system/dbus-broker.service ]; then
996 inst $ROOTLIBDIR/system/dbus-broker.service
997 inst_symlink /etc/systemd/system/dbus.service
998 inst /usr/bin/dbus-broker
999 inst /usr/bin/dbus-broker-launch
1000 elif [ -f $ROOTLIBDIR/system/dbus-daemon.service ]; then
1001 # Fedora rawhide replaced dbus.service with dbus-daemon.service
1002 inst $ROOTLIBDIR/system/dbus-daemon.service
1003 # Alias symlink
1004 inst_symlink /etc/systemd/system/dbus.service
1005 else
1006 inst $ROOTLIBDIR/system/dbus.service
1007 fi
1008
1009 find \
1010 /etc/dbus-1 /usr/share/dbus-1 -xtype f \
1011 | while read file; do
1012 inst $file
1013 done
1014
1015 # setup policy for Type=dbus test
1016 mkdir -p $initdir/etc/dbus-1/system.d
1017 cat > $initdir/etc/dbus-1/system.d/systemd.test.ExecStopPost.conf <<EOF
1018 <?xml version="1.0"?>
1019 <!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
1020 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
1021 <busconfig>
1022 <policy user="root">
1023 <allow own="systemd.test.ExecStopPost"/>
1024 </policy>
1025 </busconfig>
1026 EOF
1027 }
1028
1029 install_user_dbus() {
1030 inst $ROOTLIBDIR/user/dbus.socket
1031 inst_symlink /usr/lib/systemd/user/sockets.target.wants/dbus.socket || inst_symlink /etc/systemd/user/sockets.target.wants/dbus.socket
1032
1033 # Append the After= dependency on dbus in case it isn't already set up
1034 mkdir -p "$initdir/etc/systemd/system/user@.service.d/"
1035 cat <<EOF >"$initdir/etc/systemd/system/user@.service.d/dbus.conf"
1036 [Unit]
1037 After=dbus.service
1038 EOF
1039
1040 # Newer Fedora versions use dbus-broker by default. Let's install it if it's available.
1041 if [ -f $ROOTLIBDIR/user/dbus-broker.service ]; then
1042 inst $ROOTLIBDIR/user/dbus-broker.service
1043 inst_symlink /etc/systemd/user/dbus.service
1044 elif [ -f $ROOTLIBDIR/system/dbus-daemon.service ]; then
1045 # Fedora rawhide replaced dbus.service with dbus-daemon.service
1046 inst $ROOTLIBDIR/user/dbus-daemon.service
1047 # Alias symlink
1048 inst_symlink /etc/systemd/user/dbus.service
1049 else
1050 inst $ROOTLIBDIR/user/dbus.service
1051 fi
1052 }
1053
1054 install_pam() {
1055 (
1056 if [[ "$LOOKS_LIKE_DEBIAN" ]] && type -p dpkg-architecture &>/dev/null; then
1057 find "/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH)/security" -xtype f
1058 else
1059 find /lib*/security -xtype f
1060 fi
1061 find /etc/pam.d /etc/security -xtype f
1062 ) | while read file; do
1063 inst $file
1064 done
1065
1066 # pam_unix depends on unix_chkpwd.
1067 # see http://www.linux-pam.org/Linux-PAM-html/sag-pam_unix.html
1068 dracut_install -o unix_chkpwd
1069
1070 [[ "$LOOKS_LIKE_DEBIAN" ]] &&
1071 cp /etc/pam.d/systemd-user $initdir/etc/pam.d/
1072
1073 # set empty root password for easy debugging
1074 sed -i 's/^root:x:/root::/' $initdir/etc/passwd
1075 }
1076
1077 install_keymaps() {
1078 # The first three paths may be deprecated.
1079 # It seems now the last two paths are used by many distributions.
1080 for i in \
1081 /usr/lib/kbd/keymaps/include/* \
1082 /usr/lib/kbd/keymaps/i386/include/* \
1083 /usr/lib/kbd/keymaps/i386/qwerty/us.* \
1084 /usr/lib/kbd/keymaps/legacy/include/* \
1085 /usr/lib/kbd/keymaps/legacy/i386/qwerty/us.*; do
1086 [[ -f $i ]] || continue
1087 inst $i
1088 done
1089
1090 # When it takes any argument, then install more keymaps.
1091 if [[ -n $1 ]]; then
1092 for i in \
1093 /usr/lib/kbd/keymaps/i386/*/* \
1094 /usr/lib/kbd/keymaps/legacy/i386/*/*; do
1095 [[ -f $i ]] || continue
1096 inst $i
1097 done
1098 fi
1099 }
1100
1101 install_zoneinfo() {
1102 inst_any /usr/share/zoneinfo/Asia/Seoul
1103 inst_any /usr/share/zoneinfo/Asia/Vladivostok
1104 inst_any /usr/share/zoneinfo/Australia/Sydney
1105 inst_any /usr/share/zoneinfo/Europe/Berlin
1106 inst_any /usr/share/zoneinfo/Europe/Kiev
1107 inst_any /usr/share/zoneinfo/Pacific/Auckland
1108 inst_any /usr/share/zoneinfo/Pacific/Honolulu
1109 inst_any /usr/share/zoneinfo/CET
1110 inst_any /usr/share/zoneinfo/EET
1111 inst_any /usr/share/zoneinfo/UTC
1112 }
1113
1114 install_fonts() {
1115 for i in \
1116 /usr/lib/kbd/consolefonts/eurlatgr* \
1117 /usr/lib/kbd/consolefonts/latarcyrheb-sun16*; do
1118 [[ -f $i ]] || continue
1119 inst $i
1120 done
1121 }
1122
1123 install_terminfo() {
1124 for _terminfodir in /lib/terminfo /etc/terminfo /usr/share/terminfo; do
1125 [ -f ${_terminfodir}/l/linux ] && break
1126 done
1127 dracut_install -o ${_terminfodir}/l/linux
1128 }
1129
1130 has_user_dbus_socket() {
1131 if [ -f /usr/lib/systemd/user/dbus.socket ] || [ -f /etc/systemd/user/dbus.socket ]; then
1132 return 0
1133 else
1134 echo "Per-user instances are not supported. Skipping..."
1135 return 1
1136 fi
1137 }
1138
1139 setup_nspawn_root() {
1140 if [ -z "${initdir}" ]; then
1141 dfatal "\$initdir not defined"
1142 exit 1
1143 fi
1144
1145 rm -rf "$TESTDIR/unprivileged-nspawn-root"
1146
1147 if [[ "$RUN_IN_UNPRIVILEGED_CONTAINER" = "yes" ]]; then
1148 ddebug "cp -ar $initdir $TESTDIR/unprivileged-nspawn-root"
1149 cp -ar $initdir $TESTDIR/unprivileged-nspawn-root
1150 fi
1151 }
1152
1153 setup_basic_dirs() {
1154 mkdir -p $initdir/run
1155 mkdir -p $initdir/etc/systemd/system
1156 mkdir -p $initdir/var/log/journal
1157
1158 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
1159 if [ -L "/$d" ]; then
1160 inst_symlink "/$d"
1161 else
1162 inst_dir "/$d"
1163 fi
1164 done
1165
1166 ln -sfn /run "$initdir/var/run"
1167 ln -sfn /run/lock "$initdir/var/lock"
1168 }
1169
1170 mask_supporting_services() {
1171 # mask some services that we do not want to run in these tests
1172 ln -fs /dev/null $initdir/etc/systemd/system/systemd-hwdb-update.service
1173 ln -fs /dev/null $initdir/etc/systemd/system/systemd-journal-catalog-update.service
1174 ln -fs /dev/null $initdir/etc/systemd/system/systemd-networkd.service
1175 ln -fs /dev/null $initdir/etc/systemd/system/systemd-networkd.socket
1176 ln -fs /dev/null $initdir/etc/systemd/system/systemd-resolved.service
1177 }
1178
1179 inst_libs() {
1180 local _bin=$1
1181 local _so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)'
1182 local _file _line
1183
1184 LC_ALL=C ldd "$_bin" 2>/dev/null | while read _line; do
1185 [[ $_line = 'not a dynamic executable' ]] && break
1186
1187 if [[ $_line =~ $_so_regex ]]; then
1188 _file=${BASH_REMATCH[1]}
1189 [[ -e ${initdir}/$_file ]] && continue
1190 inst_library "$_file"
1191 continue
1192 fi
1193
1194 if [[ $_line =~ not\ found ]]; then
1195 dfatal "Missing a shared library required by $_bin."
1196 dfatal "Run \"ldd $_bin\" to find out what it is."
1197 dfatal "$_line"
1198 dfatal "dracut cannot create an initrd."
1199 exit 1
1200 fi
1201 done
1202 }
1203
1204 import_testdir() {
1205 # make sure we don't get a stale LOOPDEV value from old times
1206 __LOOPDEV=$LOOPDEV
1207 [[ -e $STATEFILE ]] && . $STATEFILE
1208 LOOPDEV=$__LOOPDEV
1209 if [[ ! -d "$TESTDIR" ]]; then
1210 if [[ -z "$TESTDIR" ]]; then
1211 TESTDIR=$(mktemp --tmpdir=/var/tmp -d -t systemd-test.XXXXXX)
1212 else
1213 mkdir -p "$TESTDIR"
1214 fi
1215
1216 cat >$STATEFILE<<EOF
1217 TESTDIR="$TESTDIR"
1218 EOF
1219 export TESTDIR
1220 fi
1221
1222 IMAGE_PRIVATE="${TESTDIR}/${IMAGE_NAME}.img"
1223 IMAGE_PUBLIC="${IMAGESTATEDIR}/${IMAGE_NAME}.img"
1224 }
1225
1226 import_initdir() {
1227 initdir=$TESTDIR/root
1228 mkdir -p $initdir
1229 export initdir
1230 }
1231
1232 ## @brief Converts numeric logging level to the first letter of level name.
1233 #
1234 # @param lvl Numeric logging level in range from 1 to 6.
1235 # @retval 1 if @a lvl is out of range.
1236 # @retval 0 if @a lvl is correct.
1237 # @result Echoes first letter of level name.
1238 _lvl2char() {
1239 case "$1" in
1240 1) echo F;;
1241 2) echo E;;
1242 3) echo W;;
1243 4) echo I;;
1244 5) echo D;;
1245 6) echo T;;
1246 *) return 1;;
1247 esac
1248 }
1249
1250 ## @brief Internal helper function for _do_dlog()
1251 #
1252 # @param lvl Numeric logging level.
1253 # @param msg Message.
1254 # @retval 0 It's always returned, even if logging failed.
1255 #
1256 # @note This function is not supposed to be called manually. Please use
1257 # dtrace(), ddebug(), or others instead which wrap this one.
1258 #
1259 # This function calls _do_dlog() either with parameter msg, or if
1260 # none is given, it will read standard input and will use every line as
1261 # a message.
1262 #
1263 # This enables:
1264 # dwarn "This is a warning"
1265 # echo "This is a warning" | dwarn
1266 LOG_LEVEL=${LOG_LEVEL:-4}
1267
1268 dlog() {
1269 [ -z "$LOG_LEVEL" ] && return 0
1270 [ $1 -le $LOG_LEVEL ] || return 0
1271 local lvl="$1"; shift
1272 local lvlc=$(_lvl2char "$lvl") || return 0
1273
1274 if [ $# -ge 1 ]; then
1275 echo "$lvlc: $*"
1276 else
1277 while read line; do
1278 echo "$lvlc: " "$line"
1279 done
1280 fi
1281 }
1282
1283 ## @brief Logs message at TRACE level (6)
1284 #
1285 # @param msg Message.
1286 # @retval 0 It's always returned, even if logging failed.
1287 dtrace() {
1288 set +x
1289 dlog 6 "$@"
1290 [ -n "$debug" ] && set -x || :
1291 }
1292
1293 ## @brief Logs message at DEBUG level (5)
1294 #
1295 # @param msg Message.
1296 # @retval 0 It's always returned, even if logging failed.
1297 ddebug() {
1298 # set +x
1299 dlog 5 "$@"
1300 # [ -n "$debug" ] && set -x || :
1301 }
1302
1303 ## @brief Logs message at INFO level (4)
1304 #
1305 # @param msg Message.
1306 # @retval 0 It's always returned, even if logging failed.
1307 dinfo() {
1308 set +x
1309 dlog 4 "$@"
1310 [ -n "$debug" ] && set -x || :
1311 }
1312
1313 ## @brief Logs message at WARN level (3)
1314 #
1315 # @param msg Message.
1316 # @retval 0 It's always returned, even if logging failed.
1317 dwarn() {
1318 set +x
1319 dlog 3 "$@"
1320 [ -n "$debug" ] && set -x || :
1321 }
1322
1323 ## @brief Logs message at ERROR level (2)
1324 #
1325 # @param msg Message.
1326 # @retval 0 It's always returned, even if logging failed.
1327 derror() {
1328 # set +x
1329 dlog 2 "$@"
1330 # [ -n "$debug" ] && set -x || :
1331 }
1332
1333 ## @brief Logs message at FATAL level (1)
1334 #
1335 # @param msg Message.
1336 # @retval 0 It's always returned, even if logging failed.
1337 dfatal() {
1338 set +x
1339 dlog 1 "$@"
1340 [ -n "$debug" ] && set -x || :
1341 }
1342
1343
1344 # Generic substring function. If $2 is in $1, return 0.
1345 strstr() { [ "${1#*$2*}" != "$1" ]; }
1346
1347 # normalize_path <path>
1348 # Prints the normalized path, where it removes any duplicated
1349 # and trailing slashes.
1350 # Example:
1351 # $ normalize_path ///test/test//
1352 # /test/test
1353 normalize_path() {
1354 shopt -q -s extglob
1355 set -- "${1//+(\/)//}"
1356 shopt -q -u extglob
1357 echo "${1%/}"
1358 }
1359
1360 # convert_abs_rel <from> <to>
1361 # Prints the relative path, when creating a symlink to <to> from <from>.
1362 # Example:
1363 # $ convert_abs_rel /usr/bin/test /bin/test-2
1364 # ../../bin/test-2
1365 # $ ln -s $(convert_abs_rel /usr/bin/test /bin/test-2) /usr/bin/test
1366 convert_abs_rel() {
1367 local __current __absolute __abssize __cursize __newpath
1368 local -i __i __level
1369
1370 set -- "$(normalize_path "$1")" "$(normalize_path "$2")"
1371
1372 # corner case #1 - self looping link
1373 [[ "$1" == "$2" ]] && { echo "${1##*/}"; return; }
1374
1375 # corner case #2 - own dir link
1376 [[ "${1%/*}" == "$2" ]] && { echo "."; return; }
1377
1378 IFS="/" __current=($1)
1379 IFS="/" __absolute=($2)
1380
1381 __abssize=${#__absolute[@]}
1382 __cursize=${#__current[@]}
1383
1384 while [[ ${__absolute[__level]} == ${__current[__level]} ]]
1385 do
1386 (( __level++ ))
1387 if (( __level > __abssize || __level > __cursize ))
1388 then
1389 break
1390 fi
1391 done
1392
1393 for ((__i = __level; __i < __cursize-1; __i++))
1394 do
1395 if ((__i > __level))
1396 then
1397 __newpath=$__newpath"/"
1398 fi
1399 __newpath=$__newpath".."
1400 done
1401
1402 for ((__i = __level; __i < __abssize; __i++))
1403 do
1404 if [[ -n $__newpath ]]
1405 then
1406 __newpath=$__newpath"/"
1407 fi
1408 __newpath=$__newpath${__absolute[__i]}
1409 done
1410
1411 echo "$__newpath"
1412 }
1413
1414
1415 # Install a directory, keeping symlinks as on the original system.
1416 # Example: if /lib points to /lib64 on the host, "inst_dir /lib/file"
1417 # will create ${initdir}/lib64, ${initdir}/lib64/file,
1418 # and a symlink ${initdir}/lib -> lib64.
1419 inst_dir() {
1420 [[ -e ${initdir}/"$1" ]] && return 0 # already there
1421
1422 local _dir="$1" _part="${1%/*}" _file
1423 while [[ "$_part" != "${_part%/*}" ]] && ! [[ -e "${initdir}/${_part}" ]]; do
1424 _dir="$_part $_dir"
1425 _part=${_part%/*}
1426 done
1427
1428 # iterate over parent directories
1429 for _file in $_dir; do
1430 [[ -e "${initdir}/$_file" ]] && continue
1431 if [[ -L $_file ]]; then
1432 inst_symlink "$_file"
1433 else
1434 # create directory
1435 mkdir -m 0755 -p "${initdir}/$_file" || return 1
1436 [[ -e "$_file" ]] && chmod --reference="$_file" "${initdir}/$_file"
1437 chmod u+w "${initdir}/$_file"
1438 fi
1439 done
1440 }
1441
1442 # $1 = file to copy to ramdisk
1443 # $2 (optional) Name for the file on the ramdisk
1444 # Location of the image dir is assumed to be $initdir
1445 # We never overwrite the target if it exists.
1446 inst_simple() {
1447 [[ -f "$1" ]] || return 1
1448 strstr "$1" "/" || return 1
1449
1450 local _src=$1 target="${2:-$1}"
1451 if ! [[ -d ${initdir}/$target ]]; then
1452 [[ -e ${initdir}/$target ]] && return 0
1453 [[ -L ${initdir}/$target ]] && return 0
1454 [[ -d "${initdir}/${target%/*}" ]] || inst_dir "${target%/*}"
1455 fi
1456 # install checksum files also
1457 if [[ -e "${_src%/*}/.${_src##*/}.hmac" ]]; then
1458 inst "${_src%/*}/.${_src##*/}.hmac" "${target%/*}/.${target##*/}.hmac"
1459 fi
1460 ddebug "Installing $_src"
1461 cp --sparse=always -pfL "$_src" "${initdir}/$target"
1462 }
1463
1464 # find symlinks linked to given library file
1465 # $1 = library file
1466 # Function searches for symlinks by stripping version numbers appended to
1467 # library filename, checks if it points to the same target and finally
1468 # prints the list of symlinks to stdout.
1469 #
1470 # Example:
1471 # rev_lib_symlinks libfoo.so.8.1
1472 # output: libfoo.so.8 libfoo.so
1473 # (Only if libfoo.so.8 and libfoo.so exists on host system.)
1474 rev_lib_symlinks() {
1475 [[ ! $1 ]] && return 0
1476
1477 local fn="$1" orig="$(readlink -f "$1")" links=''
1478
1479 [[ ${fn} =~ .*\.so\..* ]] || return 1
1480
1481 until [[ ${fn##*.} == so ]]; do
1482 fn="${fn%.*}"
1483 [[ -L ${fn} && $(readlink -f "${fn}") == ${orig} ]] && links+=" ${fn}"
1484 done
1485
1486 echo "${links}"
1487 }
1488
1489 # Same as above, but specialized to handle dynamic libraries.
1490 # It handles making symlinks according to how the original library
1491 # is referenced.
1492 inst_library() {
1493 local _src="$1" _dest=${2:-$1} _lib _reallib _symlink
1494 strstr "$1" "/" || return 1
1495 [[ -e $initdir/$_dest ]] && return 0
1496 if [[ -L $_src ]]; then
1497 # install checksum files also
1498 if [[ -e "${_src%/*}/.${_src##*/}.hmac" ]]; then
1499 inst "${_src%/*}/.${_src##*/}.hmac" "${_dest%/*}/.${_dest##*/}.hmac"
1500 fi
1501 _reallib=$(readlink -f "$_src")
1502 inst_simple "$_reallib" "$_reallib"
1503 inst_dir "${_dest%/*}"
1504 [[ -d "${_dest%/*}" ]] && _dest=$(readlink -f "${_dest%/*}")/${_dest##*/}
1505 ln -sfn $(convert_abs_rel "${_dest}" "${_reallib}") "${initdir}/${_dest}"
1506 else
1507 inst_simple "$_src" "$_dest"
1508 fi
1509
1510 # Create additional symlinks. See rev_symlinks description.
1511 for _symlink in $(rev_lib_symlinks $_src) $(rev_lib_symlinks $_reallib); do
1512 [[ -e $initdir/$_symlink ]] || {
1513 ddebug "Creating extra symlink: $_symlink"
1514 inst_symlink $_symlink
1515 }
1516 done
1517 }
1518
1519 # find a binary. If we were not passed the full path directly,
1520 # search in the usual places to find the binary.
1521 find_binary() {
1522 if [[ -z ${1##/*} ]]; then
1523 if [[ -x $1 ]] || { strstr "$1" ".so" && ldd $1 &>/dev/null; }; then
1524 echo $1
1525 return 0
1526 fi
1527 fi
1528
1529 type -P $1
1530 }
1531
1532 # Same as above, but specialized to install binary executables.
1533 # Install binary executable, and all shared library dependencies, if any.
1534 inst_binary() {
1535 local _bin _target
1536
1537 # In certain cases we might attempt to install a binary which is already
1538 # present in the test image, yet it's missing from the host system.
1539 # In such cases, let's check if the binary indeed exists in the image
1540 # before doing any other chcecks. If it does, immediately return with
1541 # success.
1542 [[ $# -eq 1 && -e $initdir/$1 ]] && return 0
1543
1544 _bin=$(find_binary "$1") || return 1
1545 _target=${2:-$_bin}
1546 [[ -e $initdir/$_target ]] && return 0
1547 [[ -L $_bin ]] && inst_symlink $_bin $_target && return 0
1548 local _file _line
1549 local _so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)'
1550 # I love bash!
1551 LC_ALL=C ldd "$_bin" 2>/dev/null | while read _line; do
1552 [[ $_line = 'not a dynamic executable' ]] && break
1553
1554 if [[ $_line =~ $_so_regex ]]; then
1555 _file=${BASH_REMATCH[1]}
1556 [[ -e ${initdir}/$_file ]] && continue
1557 inst_library "$_file"
1558 continue
1559 fi
1560
1561 if [[ $_line =~ not\ found ]]; then
1562 dfatal "Missing a shared library required by $_bin."
1563 dfatal "Run \"ldd $_bin\" to find out what it is."
1564 dfatal "$_line"
1565 dfatal "dracut cannot create an initrd."
1566 exit 1
1567 fi
1568 done
1569 inst_simple "$_bin" "$_target"
1570 }
1571
1572 # same as above, except for shell scripts.
1573 # If your shell script does not start with shebang, it is not a shell script.
1574 inst_script() {
1575 local _bin
1576 _bin=$(find_binary "$1") || return 1
1577 shift
1578 local _line _shebang_regex
1579 read -r -n 80 _line <"$_bin"
1580 # If debug is set, clean unprintable chars to prevent messing up the term
1581 [[ $debug ]] && _line=$(echo -n "$_line" | tr -c -d '[:print:][:space:]')
1582 _shebang_regex='(#! *)(/[^ ]+).*'
1583 [[ $_line =~ $_shebang_regex ]] || return 1
1584 inst "${BASH_REMATCH[2]}" && inst_simple "$_bin" "$@"
1585 }
1586
1587 # same as above, but specialized for symlinks
1588 inst_symlink() {
1589 local _src=$1 _target=${2:-$1} _realsrc
1590 strstr "$1" "/" || return 1
1591 [[ -L $1 ]] || return 1
1592 [[ -L $initdir/$_target ]] && return 0
1593 _realsrc=$(readlink -f "$_src")
1594 if ! [[ -e $initdir/$_realsrc ]]; then
1595 if [[ -d $_realsrc ]]; then
1596 inst_dir "$_realsrc"
1597 else
1598 inst "$_realsrc"
1599 fi
1600 fi
1601 [[ ! -e $initdir/${_target%/*} ]] && inst_dir "${_target%/*}"
1602 [[ -d ${_target%/*} ]] && _target=$(readlink -f ${_target%/*})/${_target##*/}
1603 ln -sfn $(convert_abs_rel "${_target}" "${_realsrc}") "$initdir/$_target"
1604 }
1605
1606 # attempt to install any programs specified in a udev rule
1607 inst_rule_programs() {
1608 local _prog _bin
1609
1610 if grep -qE 'PROGRAM==?"[^ "]+' "$1"; then
1611 for _prog in $(grep -E 'PROGRAM==?"[^ "]+' "$1" | sed -r 's/.*PROGRAM==?"([^ "]+).*/\1/'); do
1612 if [ -x /lib/udev/$_prog ]; then
1613 _bin=/lib/udev/$_prog
1614 else
1615 _bin=$(find_binary "$_prog") || {
1616 dinfo "Skipping program $_prog using in udev rule $(basename $1) as it cannot be found"
1617 continue;
1618 }
1619 fi
1620
1621 #dinfo "Installing $_bin due to it's use in the udev rule $(basename $1)"
1622 dracut_install "$_bin"
1623 done
1624 fi
1625 }
1626
1627 # udev rules always get installed in the same place, so
1628 # create a function to install them to make life simpler.
1629 inst_rules() {
1630 local _target=/etc/udev/rules.d _rule _found
1631
1632 inst_dir "/lib/udev/rules.d"
1633 inst_dir "$_target"
1634 for _rule in "$@"; do
1635 if [ "${rule#/}" = "$rule" ]; then
1636 for r in /lib/udev/rules.d /etc/udev/rules.d; do
1637 if [[ -f $r/$_rule ]]; then
1638 _found="$r/$_rule"
1639 inst_simple "$_found"
1640 inst_rule_programs "$_found"
1641 fi
1642 done
1643 fi
1644 for r in '' ./ $dracutbasedir/rules.d/; do
1645 if [[ -f ${r}$_rule ]]; then
1646 _found="${r}$_rule"
1647 inst_simple "$_found" "$_target/${_found##*/}"
1648 inst_rule_programs "$_found"
1649 fi
1650 done
1651 [[ $_found ]] || dinfo "Skipping udev rule: $_rule"
1652 _found=
1653 done
1654 }
1655
1656 # general purpose installation function
1657 # Same args as above.
1658 inst() {
1659 local _x
1660
1661 case $# in
1662 1) ;;
1663 2) [[ ! $initdir && -d $2 ]] && export initdir=$2
1664 [[ $initdir = $2 ]] && set $1;;
1665 3) [[ -z $initdir ]] && export initdir=$2
1666 set $1 $3;;
1667 *) dfatal "inst only takes 1 or 2 or 3 arguments"
1668 exit 1;;
1669 esac
1670 for _x in inst_symlink inst_script inst_binary inst_simple; do
1671 $_x "$@" && return 0
1672 done
1673 return 1
1674 }
1675
1676 # install any of listed files
1677 #
1678 # If first argument is '-d' and second some destination path, first accessible
1679 # source is installed into this path, otherwise it will installed in the same
1680 # path as source. If none of listed files was installed, function return 1.
1681 # On first successful installation it returns with 0 status.
1682 #
1683 # Example:
1684 #
1685 # inst_any -d /bin/foo /bin/bar /bin/baz
1686 #
1687 # Lets assume that /bin/baz exists, so it will be installed as /bin/foo in
1688 # initramfs.
1689 inst_any() {
1690 local to f
1691
1692 [[ $1 = '-d' ]] && to="$2" && shift 2
1693
1694 for f in "$@"; do
1695 if [[ -e $f ]]; then
1696 [[ $to ]] && inst "$f" "$to" && return 0
1697 inst "$f" && return 0
1698 fi
1699 done
1700
1701 return 1
1702 }
1703
1704 # dracut_install [-o ] <file> [<file> ... ]
1705 # Install <file> to the initramfs image
1706 # -o optionally install the <file> and don't fail, if it is not there
1707 dracut_install() {
1708 local _optional=no
1709 if [[ $1 = '-o' ]]; then
1710 _optional=yes
1711 shift
1712 fi
1713 while (($# > 0)); do
1714 if ! inst "$1" ; then
1715 if [[ $_optional = yes ]]; then
1716 dinfo "Skipping program $1 as it cannot be found and is" \
1717 "flagged to be optional"
1718 else
1719 dfatal "Failed to install $1"
1720 exit 1
1721 fi
1722 fi
1723 shift
1724 done
1725 }
1726
1727 # Install a single kernel module along with any firmware it may require.
1728 # $1 = full path to kernel module to install
1729 install_kmod_with_fw() {
1730 # no need to go further if the module is already installed
1731
1732 [[ -e "${initdir}/lib/modules/$KERNEL_VER/${1##*/lib/modules/$KERNEL_VER/}" ]] \
1733 && return 0
1734
1735 [[ -e "$initdir/.kernelmodseen/${1##*/}" ]] && return 0
1736
1737 if [[ $omit_drivers ]]; then
1738 local _kmod=${1##*/}
1739 _kmod=${_kmod%.ko}
1740 _kmod=${_kmod/-/_}
1741 if [[ "$_kmod" =~ $omit_drivers ]]; then
1742 dinfo "Omitting driver $_kmod"
1743 return 1
1744 fi
1745 if [[ "${1##*/lib/modules/$KERNEL_VER/}" =~ $omit_drivers ]]; then
1746 dinfo "Omitting driver $_kmod"
1747 return 1
1748 fi
1749 fi
1750
1751 [ -d "$initdir/.kernelmodseen" ] && \
1752 > "$initdir/.kernelmodseen/${1##*/}"
1753
1754 inst_simple "$1" "/lib/modules/$KERNEL_VER/${1##*/lib/modules/$KERNEL_VER/}" \
1755 || return $?
1756
1757 local _modname=${1##*/} _fwdir _found _fw
1758 _modname=${_modname%.ko*}
1759 for _fw in $(modinfo -k $KERNEL_VER -F firmware $1 2>/dev/null); do
1760 _found=''
1761 for _fwdir in $fw_dir; do
1762 if [[ -d $_fwdir && -f $_fwdir/$_fw ]]; then
1763 inst_simple "$_fwdir/$_fw" "/lib/firmware/$_fw"
1764 _found=yes
1765 fi
1766 done
1767 if [[ $_found != yes ]]; then
1768 if ! grep -qe "\<${_modname//-/_}\>" /proc/modules; then
1769 dinfo "Possible missing firmware \"${_fw}\" for kernel module" \
1770 "\"${_modname}.ko\""
1771 else
1772 dwarn "Possible missing firmware \"${_fw}\" for kernel module" \
1773 "\"${_modname}.ko\""
1774 fi
1775 fi
1776 done
1777 return 0
1778 }
1779
1780 # Do something with all the dependencies of a kernel module.
1781 # Note that kernel modules depend on themselves using the technique we use
1782 # $1 = function to call for each dependency we find
1783 # It will be passed the full path to the found kernel module
1784 # $2 = module to get dependencies for
1785 # rest of args = arguments to modprobe
1786 # _fderr specifies FD passed from surrounding scope
1787 for_each_kmod_dep() {
1788 local _func=$1 _kmod=$2 _cmd _modpath _options _found=0
1789 shift 2
1790 modprobe "$@" --ignore-install --show-depends $_kmod 2>&${_fderr} | (
1791 while read _cmd _modpath _options; do
1792 [[ $_cmd = insmod ]] || continue
1793 $_func ${_modpath} || exit $?
1794 _found=1
1795 done
1796 [[ $_found -eq 0 ]] && exit 1
1797 exit 0
1798 )
1799 }
1800
1801 # filter kernel modules to install certain modules that meet specific
1802 # requirements.
1803 # $1 = search only in subdirectory of /kernel/$1
1804 # $2 = function to call with module name to filter.
1805 # This function will be passed the full path to the module to test.
1806 # The behavior of this function can vary depending on whether $hostonly is set.
1807 # If it is, we will only look at modules that are already in memory.
1808 # If it is not, we will look at all kernel modules
1809 # This function returns the full filenames of modules that match $1
1810 filter_kernel_modules_by_path () (
1811 local _modname _filtercmd
1812 if ! [[ $hostonly ]]; then
1813 _filtercmd='find "$KERNEL_MODS/kernel/$1" "$KERNEL_MODS/extra"'
1814 _filtercmd+=' "$KERNEL_MODS/weak-updates" -name "*.ko" -o -name "*.ko.gz"'
1815 _filtercmd+=' -o -name "*.ko.xz"'
1816 _filtercmd+=' 2>/dev/null'
1817 else
1818 _filtercmd='cut -d " " -f 1 </proc/modules|xargs modinfo -F filename '
1819 _filtercmd+='-k $KERNEL_VER 2>/dev/null'
1820 fi
1821 for _modname in $(eval $_filtercmd); do
1822 case $_modname in
1823 *.ko) "$2" "$_modname" && echo "$_modname";;
1824 *.ko.gz) gzip -dc "$_modname" > $initdir/$$.ko
1825 $2 $initdir/$$.ko && echo "$_modname"
1826 rm -f $initdir/$$.ko
1827 ;;
1828 *.ko.xz) xz -dc "$_modname" > $initdir/$$.ko
1829 $2 $initdir/$$.ko && echo "$_modname"
1830 rm -f $initdir/$$.ko
1831 ;;
1832 esac
1833 done
1834 )
1835 find_kernel_modules_by_path () (
1836 if ! [[ $hostonly ]]; then
1837 find "$KERNEL_MODS/kernel/$1" "$KERNEL_MODS/extra" "$KERNEL_MODS/weak-updates" \
1838 -name "*.ko" -o -name "*.ko.gz" -o -name "*.ko.xz" 2>/dev/null
1839 else
1840 cut -d " " -f 1 </proc/modules \
1841 | xargs modinfo -F filename -k $KERNEL_VER 2>/dev/null
1842 fi
1843 )
1844
1845 filter_kernel_modules () {
1846 filter_kernel_modules_by_path drivers "$1"
1847 }
1848
1849 find_kernel_modules () {
1850 find_kernel_modules_by_path drivers
1851 }
1852
1853 # instmods [-c] <kernel module> [<kernel module> ... ]
1854 # instmods [-c] <kernel subsystem>
1855 # install kernel modules along with all their dependencies.
1856 # <kernel subsystem> can be e.g. "=block" or "=drivers/usb/storage"
1857 instmods() {
1858 [[ $no_kernel = yes ]] && return
1859 # called [sub]functions inherit _fderr
1860 local _fderr=9
1861 local _check=no
1862 if [[ $1 = '-c' ]]; then
1863 _check=yes
1864 shift
1865 fi
1866
1867 function inst1mod() {
1868 local _ret=0 _mod="$1"
1869 case $_mod in
1870 =*)
1871 if [ -f $KERNEL_MODS/modules.${_mod#=} ]; then
1872 ( [[ "$_mpargs" ]] && echo $_mpargs
1873 cat "${KERNEL_MODS}/modules.${_mod#=}" ) \
1874 | instmods
1875 else
1876 ( [[ "$_mpargs" ]] && echo $_mpargs
1877 find "$KERNEL_MODS" -path "*/${_mod#=}/*" -type f -printf '%f\n' ) \
1878 | instmods
1879 fi
1880 ;;
1881 --*) _mpargs+=" $_mod" ;;
1882 i2o_scsi) return ;; # Do not load this diagnostic-only module
1883 *)
1884 _mod=${_mod##*/}
1885 # if we are already installed, skip this module and go on
1886 # to the next one.
1887 [[ -f "$initdir/.kernelmodseen/${_mod%.ko}.ko" ]] && return
1888
1889 if [[ $omit_drivers ]] && [[ "$1" =~ $omit_drivers ]]; then
1890 dinfo "Omitting driver ${_mod##$KERNEL_MODS}"
1891 return
1892 fi
1893 # If we are building a host-specific initramfs and this
1894 # module is not already loaded, move on to the next one.
1895 [[ $hostonly ]] && ! grep -qe "\<${_mod//-/_}\>" /proc/modules \
1896 && ! echo $add_drivers | grep -qe "\<${_mod}\>" \
1897 && return
1898
1899 # We use '-d' option in modprobe only if modules prefix path
1900 # differs from default '/'. This allows us to use Dracut with
1901 # old version of modprobe which doesn't have '-d' option.
1902 local _moddirname=${KERNEL_MODS%%/lib/modules/*}
1903 [[ -n ${_moddirname} ]] && _moddirname="-d ${_moddirname}/"
1904
1905 # ok, load the module, all its dependencies, and any firmware
1906 # it may require
1907 for_each_kmod_dep install_kmod_with_fw $_mod \
1908 --set-version $KERNEL_VER ${_moddirname} $_mpargs
1909 ((_ret+=$?))
1910 ;;
1911 esac
1912 return $_ret
1913 }
1914
1915 function instmods_1() {
1916 local _mod _mpargs
1917 if (($# == 0)); then # filenames from stdin
1918 while read _mod; do
1919 inst1mod "${_mod%.ko*}" || {
1920 if [ "$_check" = "yes" ]; then
1921 dfatal "Failed to install $_mod"
1922 return 1
1923 fi
1924 }
1925 done
1926 fi
1927 while (($# > 0)); do # filenames as arguments
1928 inst1mod ${1%.ko*} || {
1929 if [ "$_check" = "yes" ]; then
1930 dfatal "Failed to install $1"
1931 return 1
1932 fi
1933 }
1934 shift
1935 done
1936 return 0
1937 }
1938
1939 local _ret _filter_not_found='FATAL: Module .* not found.'
1940 set -o pipefail
1941 # Capture all stderr from modprobe to _fderr. We could use {var}>...
1942 # redirections, but that would make dracut require bash4 at least.
1943 eval "( instmods_1 \"\$@\" ) ${_fderr}>&1" \
1944 | while read line; do [[ "$line" =~ $_filter_not_found ]] && echo $line || echo $line >&2 ;done | derror
1945 _ret=$?
1946 set +o pipefail
1947 return $_ret
1948 }
1949
1950 setup_suse() {
1951 ln -fs ../usr/bin/systemctl $initdir/bin/
1952 ln -fs ../usr/lib/systemd $initdir/lib/
1953 inst_simple "/usr/lib/systemd/system/haveged.service"
1954 instmods ext4
1955 }
1956
1957 _umount_dir() {
1958 if mountpoint -q $1; then
1959 ddebug "umount $1"
1960 umount $1
1961 fi
1962 }
1963
1964 # can be overridden in specific test
1965 test_setup_cleanup() {
1966 cleanup_initdir
1967 }
1968
1969 _test_cleanup() {
1970 # (post-test) cleanup should always ignore failure and cleanup as much as possible
1971 (
1972 set +e
1973 _umount_dir $initdir
1974 rm -vf "$IMAGE_PUBLIC"
1975 rm -vfr "$TESTDIR"
1976 rm -vf "$STATEFILE"
1977 ) || :
1978 }
1979
1980 # can be overridden in specific test
1981 test_cleanup() {
1982 _test_cleanup
1983 }
1984
1985 test_cleanup_again() {
1986 [ -n "$TESTDIR" ] || return
1987 rm -rf "$TESTDIR/unprivileged-nspawn-root"
1988 _umount_dir $initdir
1989 }
1990
1991 test_create_image() {
1992 create_empty_image_rootdir
1993
1994 # Create what will eventually be our root filesystem onto an overlay
1995 (
1996 LOG_LEVEL=5
1997 setup_basic_environment
1998 mask_supporting_services
1999 )
2000 }
2001
2002 test_setup() {
2003 if [ ${TEST_REQUIRE_INSTALL_TESTS} -ne 0 ] && \
2004 type -P meson >/dev/null && \
2005 [[ "$(meson configure $BUILD_DIR | grep install-tests | awk '{ print $2 }')" != "true" ]]; then
2006 dfatal "$BUILD_DIR needs to be built with -Dinstall-tests=true"
2007 exit 1
2008 fi
2009
2010 if [ -e "$IMAGE_PRIVATE" ]; then
2011 echo "Reusing existing image $IMAGE_PRIVATE → $(realpath $IMAGE_PRIVATE)"
2012 mount_initdir
2013 else
2014 if [ ! -e "$IMAGE_PUBLIC" ]; then
2015 # Create the backing public image, but then completely unmount
2016 # it and drop the loopback device responsible for it, since we're
2017 # going to symlink/copy the image and mount it again from
2018 # elsewhere.
2019 test_create_image
2020 test_setup_cleanup
2021 umount_loopback
2022 cleanup_loopdev
2023 fi
2024
2025 echo "Reusing existing cached image $IMAGE_PUBLIC → $(realpath $IMAGE_PUBLIC)"
2026 if [ ${TEST_PARALLELIZE} -ne 0 ]; then
2027 cp -v "$(realpath $IMAGE_PUBLIC)" "$IMAGE_PRIVATE"
2028 else
2029 ln -sv "$(realpath $IMAGE_PUBLIC)" "$IMAGE_PRIVATE"
2030 fi
2031
2032 mount_initdir
2033 fi
2034
2035 setup_nspawn_root
2036 }
2037
2038 test_run() {
2039 mount_initdir
2040
2041 if [ -z "$TEST_NO_QEMU" ]; then
2042 if run_qemu "$1"; then
2043 check_result_qemu || { echo "QEMU test failed"; return 1; }
2044 else
2045 dwarn "can't run QEMU, skipping"
2046 fi
2047 fi
2048 if [ -z "$TEST_NO_NSPAWN" ]; then
2049 mount_initdir
2050 if run_nspawn "$initdir" "$1"; then
2051 check_result_nspawn "$initdir" || { echo "nspawn-root test failed"; return 1; }
2052 else
2053 dwarn "can't run systemd-nspawn, skipping"
2054 fi
2055
2056 if [[ "$RUN_IN_UNPRIVILEGED_CONTAINER" = "yes" ]]; then
2057 dir="$TESTDIR/unprivileged-nspawn-root"
2058 if NSPAWN_ARGUMENTS="-U --private-network $NSPAWN_ARGUMENTS" run_nspawn "$dir" "$1"; then
2059 check_result_nspawn "$dir" || { echo "unprivileged-nspawn-root test failed"; return 1; }
2060 else
2061 dwarn "can't run systemd-nspawn, skipping"
2062 fi
2063 fi
2064 fi
2065 return 0
2066 }
2067
2068 do_test() {
2069 if [[ $UID != "0" ]]; then
2070 echo "TEST: $TEST_DESCRIPTION [SKIPPED]: not root" >&2
2071 exit 0
2072 fi
2073
2074 # Detect lib paths
2075 [[ $libdir ]] || for libdir in /lib64 /lib; do
2076 [[ -d $libdir ]] && libdirs+=" $libdir" && break
2077 done
2078
2079 [[ $usrlibdir ]] || for usrlibdir in /usr/lib64 /usr/lib; do
2080 [[ -d $usrlibdir ]] && libdirs+=" $usrlibdir" && break
2081 done
2082
2083 mkdir -p "$STATEDIR"
2084
2085 import_testdir
2086 import_initdir
2087
2088 testname="$(basename $PWD)"
2089
2090 while (($# > 0)); do
2091 case $1 in
2092 --run)
2093 echo "${testname} RUN: $TEST_DESCRIPTION"
2094 test_run "$2"
2095 ret=$?
2096 if (( $ret == 0 )); then
2097 echo "${testname} RUN: $TEST_DESCRIPTION [OK]"
2098 else
2099 echo "${testname} RUN: $TEST_DESCRIPTION [FAILED]"
2100 fi
2101 exit $ret;;
2102 --setup)
2103 echo "${testname} SETUP: $TEST_DESCRIPTION"
2104 test_setup
2105 test_setup_cleanup
2106 ;;
2107 --clean)
2108 echo "${testname} CLEANUP: $TEST_DESCRIPTION"
2109 test_cleanup
2110 ;;
2111 --clean-again)
2112 echo "${testname} CLEANUP AGAIN: $TEST_DESCRIPTION"
2113 test_cleanup_again
2114 ;;
2115 --all)
2116 ret=0
2117 echo -n "${testname}: $TEST_DESCRIPTION "
2118 (
2119 test_setup
2120 test_setup_cleanup
2121 test_run "$2"
2122 ) </dev/null >"$TESTLOG" 2>&1 || ret=$?
2123 test_cleanup
2124 if [ $ret -eq 0 ]; then
2125 rm "$TESTLOG"
2126 echo "[OK]"
2127 else
2128 echo "[FAILED]"
2129 echo "see $TESTLOG"
2130 fi
2131 exit $ret;;
2132 *) break ;;
2133 esac
2134 shift
2135 done
2136 }