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