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