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