]> git.ipfire.org Git - thirdparty/systemd.git/blob - test/test-functions
scripts: use 4 space indentation
[thirdparty/systemd.git] / test / test-functions
1 #!/bin/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 || true)
8 LOOKS_LIKE_ARCH=$(source /etc/os-release && [[ "$ID" = "arch" || " $ID_LIKE " = *" arch "* ]] && echo yes || true)
9 LOOKS_LIKE_SUSE=$(source /etc/os-release && [[ " $ID_LIKE " = *" suse "* ]] && echo yes || true)
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="$(bootctl -p 2>/dev/null || echo /boot)"
18 QEMU_MEM="${QEMU_MEM:-512M}"
19
20 if ! ROOTLIBDIR=$(pkg-config --variable=systemdutildir systemd); then
21 echo "WARNING! Cannot determine rootlibdir from pkg-config, assuming /usr/lib/systemd" >&2
22 ROOTLIBDIR=/usr/lib/systemd
23 fi
24
25 PATH_TO_INIT=$ROOTLIBDIR/systemd
26
27 BASICTOOLS="test sh bash setsid loadkeys setfont login sulogin gzip sleep echo head tail cat mount umount cryptsetup date dmsetup modprobe sed cmp tee rm true false chmod chown ln xargs"
28 DEBUGTOOLS="df free ls stty ps ln ip route dmesg dhclient mkdir cp ping dhclient strace less grep id tty touch du sort hostname find vi mv"
29
30 STATEDIR="${BUILD_DIR:-.}/test/$(basename $(dirname $(realpath $0)))"
31 STATEFILE="$STATEDIR/.testdir"
32 TESTLOG="$STATEDIR/test.log"
33
34 is_built_with_asan() {
35 if ! type -P objdump >/dev/null; then
36 ddebug "Failed to find objdump. Assuming systemd hasn't been built with ASAN."
37 return 1
38 fi
39
40 # Borrowed from https://github.com/google/oss-fuzz/blob/cd9acd02f9d3f6e80011cc1e9549be526ce5f270/infra/base-images/base-runner/bad_build_check#L182
41 local _asan_calls=$(objdump -dC $BUILD_DIR/systemd-journald | egrep "callq\s+[0-9a-f]+\s+<__asan" -c)
42 if (( $_asan_calls < 1000 )); then
43 return 1
44 else
45 return 0
46 fi
47 }
48
49 IS_BUILT_WITH_ASAN=$(is_built_with_asan && echo yes || echo no)
50
51 if [[ "$IS_BUILT_WITH_ASAN" = "yes" ]]; then
52 STRIP_BINARIES=no
53 SKIP_INITRD=yes
54 PATH_TO_INIT=$ROOTLIBDIR/systemd-under-asan
55 QEMU_MEM="1536M"
56 QEMU_SMP=4
57 fi
58
59 function find_qemu_bin() {
60 # SUSE and Red Hat call the binary qemu-kvm. Debian and Gentoo call it kvm.
61 # Either way, only use this version if we aren't running in KVM, because
62 # nested KVM is flaky still.
63 if [[ $(systemd-detect-virt -v) != kvm && -z $TEST_NO_KVM ]] ; then
64 [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a kvm qemu-kvm 2>/dev/null | grep '^/' -m1)
65 fi
66
67 [ "$ARCH" ] || ARCH=$(uname -m)
68 case $ARCH in
69 x86_64)
70 # QEMU's own build system calls it qemu-system-x86_64
71 [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a qemu-system-x86_64 2>/dev/null | grep '^/' -m1)
72 ;;
73 i*86)
74 # new i386 version of QEMU
75 [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a qemu-system-i386 2>/dev/null | grep '^/' -m1)
76
77 # i386 version of QEMU
78 [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a qemu 2>/dev/null | grep '^/' -m1)
79 ;;
80 ppc64*)
81 [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a qemu-system-$ARCH 2>/dev/null | grep '^/' -m1)
82 ;;
83 esac
84
85 if [ ! -e "$QEMU_BIN" ]; then
86 echo "Could not find a suitable QEMU binary" >&2
87 return 1
88 fi
89 }
90
91 # Return 0 if QEMU did run (then you must check the result state/logs for actual
92 # success), or 1 if QEMU is not available.
93 run_qemu() {
94 if [ -f /etc/machine-id ]; then
95 read MACHINE_ID < /etc/machine-id
96 [ -z "$INITRD" ] && [ -e "$EFI_MOUNT/$MACHINE_ID/$KERNEL_VER/initrd" ] \
97 && INITRD="$EFI_MOUNT/$MACHINE_ID/$KERNEL_VER/initrd"
98 [ -z "$KERNEL_BIN" ] && [ -e "$EFI_MOUNT/$MACHINE_ID/$KERNEL_VER/linux" ] \
99 && KERNEL_BIN="$EFI_MOUNT/$MACHINE_ID/$KERNEL_VER/linux"
100 fi
101
102 CONSOLE=ttyS0
103
104 if [[ ! "$KERNEL_BIN" ]]; then
105 if [[ "$LOOKS_LIKE_ARCH" ]]; then
106 KERNEL_BIN=/boot/vmlinuz-linux
107 else
108 [ "$ARCH" ] || ARCH=$(uname -m)
109 case $ARCH in
110 ppc64*)
111 KERNEL_BIN=/boot/vmlinux-$KERNEL_VER
112 CONSOLE=hvc0
113 ;;
114 *)
115 KERNEL_BIN=/boot/vmlinuz-$KERNEL_VER
116 ;;
117 esac
118 fi
119 fi
120
121 default_fedora_initrd=/boot/initramfs-${KERNEL_VER}.img
122 default_debian_initrd=/boot/initrd.img-${KERNEL_VER}
123 default_arch_initrd=/boot/initramfs-linux-fallback.img
124 default_suse_initrd=/boot/initrd-${KERNEL_VER}
125 if [[ ! "$INITRD" ]]; then
126 if [[ -e "$default_fedora_initrd" ]]; then
127 INITRD="$default_fedora_initrd"
128 elif [[ "$LOOKS_LIKE_DEBIAN" && -e "$default_debian_initrd" ]]; then
129 INITRD="$default_debian_initrd"
130 elif [[ "$LOOKS_LIKE_ARCH" && -e "$default_arch_initrd" ]]; then
131 INITRD="$default_arch_initrd"
132 elif [[ "$LOOKS_LIKE_SUSE" && -e "$default_suse_initrd" ]]; then
133 INITRD="$default_suse_initrd"
134 fi
135 fi
136
137 # If QEMU_SMP was not explicitly set, try to determine the value 'dynamically'
138 # i.e. use the number of online CPUs on the host machine. If the nproc utility
139 # is not installed or there's some other error when calling it, fall back
140 # to the original value (QEMU_SMP=1).
141 if ! [ "$QEMU_SMP" ]; then
142 if ! QEMU_SMP=$(nproc); then
143 dwarn "nproc utility is not installed, falling back to QEMU_SMP=1"
144 QEMU_SMP=1
145 fi
146 fi
147
148 find_qemu_bin || return 1
149
150 local _cgroup_args
151 if [[ "$UNIFIED_CGROUP_HIERARCHY" = "yes" ]]; then
152 _cgroup_args="systemd.unified_cgroup_hierarchy=yes"
153 elif [[ "$UNIFIED_CGROUP_HIERARCHY" = "no" ]]; then
154 _cgroup_args="systemd.unified_cgroup_hierarchy=no systemd.legacy_systemd_cgroup_controller=yes"
155 elif [[ "$UNIFIED_CGROUP_HIERARCHY" = "hybrid" ]]; then
156 _cgroup_args="systemd.unified_cgroup_hierarchy=no systemd.legacy_systemd_cgroup_controller=no"
157 elif [[ "$UNIFIED_CGROUP_HIERARCHY" != "default" ]]; then
158 dfatal "Unknown UNIFIED_CGROUP_HIERARCHY. Got $UNIFIED_CGROUP_HIERARCHY, expected [yes|no|hybrid|default]"
159 exit 1
160 fi
161
162 if [[ "$LOOKS_LIKE_SUSE" ]]; then
163 PARAMS+="rd.hostonly=0"
164 elif [[ "$LOOKS_LIKE_ARCH" ]]; then
165 PARAMS+="rw"
166 else
167 PARAMS+="ro"
168 fi
169
170 KERNEL_APPEND="$PARAMS \
171 root=/dev/sda1 \
172 raid=noautodetect \
173 loglevel=2 \
174 init=$PATH_TO_INIT \
175 console=$CONSOLE \
176 selinux=0 \
177 printk.devkmsg=on \
178 $_cgroup_args \
179 $KERNEL_APPEND \
180 "
181
182 QEMU_OPTIONS="-smp $QEMU_SMP \
183 -net none \
184 -m $QEMU_MEM \
185 -nographic \
186 -kernel $KERNEL_BIN \
187 -drive format=raw,cache=unsafe,file=${TESTDIR}/rootdisk.img \
188 "
189
190 if [[ "$INITRD" && "$SKIP_INITRD" != "yes" ]]; then
191 QEMU_OPTIONS="$QEMU_OPTIONS -initrd $INITRD"
192 fi
193
194 # Let's use KVM if it is available, but let's avoid using nested KVM as that is still flaky
195 if [[ -c /dev/kvm && $(systemd-detect-virt -v) != kvm && -z $TEST_NO_KVM ]] ; then
196 QEMU_OPTIONS="$QEMU_OPTIONS -machine accel=kvm -enable-kvm -cpu host"
197 fi
198
199 if [[ "$QEMU_TIMEOUT" != "infinity" ]]; then
200 QEMU_BIN="timeout --foreground $QEMU_TIMEOUT $QEMU_BIN"
201 fi
202 (set -x; $QEMU_BIN $QEMU_OPTIONS -append "$KERNEL_APPEND")
203 rc=$?
204 if [ "$rc" = 124 ] && [ "$QEMU_TIMEOUT" != "infinity" ]; then
205 derror "test timed out after $QEMU_TIMEOUT s"
206 TIMED_OUT=1
207 else
208 [ "$rc" != 0 ] && derror "QEMU failed with exit code $rc"
209 fi
210 return 0
211 }
212
213 # Return 0 if nspawn did run (then you must check the result state/logs for actual
214 # success), or 1 if nspawn is not available.
215 run_nspawn() {
216 [[ -d /run/systemd/system ]] || return 1
217
218 local _nspawn_cmd="$BUILD_DIR/systemd-nspawn $NSPAWN_ARGUMENTS --register=no --kill-signal=SIGKILL --directory=$TESTDIR/$1 $PATH_TO_INIT $KERNEL_APPEND"
219 if [[ "$NSPAWN_TIMEOUT" != "infinity" ]]; then
220 _nspawn_cmd="timeout --foreground $NSPAWN_TIMEOUT $_nspawn_cmd"
221 fi
222
223 if [[ "$UNIFIED_CGROUP_HIERARCHY" = "hybrid" ]]; then
224 dwarn "nspawn doesn't support UNIFIED_CGROUP_HIERARCHY=hybrid, skipping"
225 exit
226 elif [[ "$UNIFIED_CGROUP_HIERARCHY" = "yes" || "$UNIFIED_CGROUP_HIERARCHY" = "no" ]]; then
227 _nspawn_cmd="env UNIFIED_CGROUP_HIERARCHY=$UNIFIED_CGROUP_HIERARCHY $_nspawn_cmd"
228 elif [[ "$UNIFIED_CGROUP_HIERARCHY" = "default" ]]; then
229 _nspawn_cmd="env --unset=UNIFIED_CGROUP_HIERARCHY $_nspawn_cmd"
230 else
231 dfatal "Unknown UNIFIED_CGROUP_HIERARCHY. Got $UNIFIED_CGROUP_HIERARCHY, expected [yes|no|hybrid|default]"
232 exit 1
233 fi
234
235 (set -x; $_nspawn_cmd)
236 rc=$?
237 if [ "$rc" = 124 ] && [ "$NSPAWN_TIMEOUT" != "infinity" ]; then
238 derror "test timed out after $NSPAWN_TIMEOUT s"
239 TIMED_OUT=1
240 else
241 [ "$rc" != 0 ] && derror "nspawn failed with exit code $rc"
242 fi
243 return 0
244 }
245
246 setup_basic_environment() {
247 # create the basic filesystem layout
248 setup_basic_dirs
249
250 install_systemd
251 install_missing_libraries
252 install_config_files
253 create_rc_local
254 install_basic_tools
255 install_libnss
256 install_pam
257 install_dbus
258 install_fonts
259 install_keymaps
260 install_terminfo
261 install_execs
262 install_fsck
263 install_plymouth
264 install_debug_tools
265 install_ld_so_conf
266 setup_selinux
267 strip_binaries
268 install_depmod_files
269 generate_module_dependencies
270 if [[ "$IS_BUILT_WITH_ASAN" = "yes" ]]; then
271 create_asan_wrapper
272 fi
273 }
274
275 setup_selinux() {
276 # don't forget KERNEL_APPEND='... selinux=1 ...'
277 if [[ "$SETUP_SELINUX" != "yes" ]]; then
278 ddebug "Don't setup SELinux"
279 return 0
280 fi
281 ddebug "Setup SELinux"
282 local _conf_dir=/etc/selinux
283 local _fixfiles_tools="bash uname cat sort uniq awk grep egrep head expr find rm secon setfiles"
284
285 rm -rf $initdir/$_conf_dir
286 if ! cp -ar $_conf_dir $initdir/$_conf_dir; then
287 dfatal "Failed to copy $_conf_dir"
288 exit 1
289 fi
290
291 cat <<EOF >$initdir/etc/systemd/system/autorelabel.service
292 [Unit]
293 Description=Relabel all filesystems
294 DefaultDependencies=no
295 Requires=local-fs.target
296 Conflicts=shutdown.target
297 After=local-fs.target
298 Before=sysinit.target shutdown.target
299 ConditionSecurity=selinux
300 ConditionPathExists=|/.autorelabel
301
302 [Service]
303 ExecStart=/bin/sh -x -c 'echo 0 >/sys/fs/selinux/enforce && fixfiles -f -F relabel && rm /.autorelabel && systemctl --force reboot'
304 Type=oneshot
305 TimeoutSec=0
306 RemainAfterExit=yes
307 EOF
308
309 touch $initdir/.autorelabel
310 mkdir -p $initdir/etc/systemd/system/basic.target.wants
311 ln -fs autorelabel.service $initdir/etc/systemd/system/basic.target.wants/autorelabel.service
312
313 dracut_install $_fixfiles_tools
314 dracut_install fixfiles
315 dracut_install sestatus
316 }
317
318 install_valgrind() {
319 if ! type -p valgrind; then
320 dfatal "Failed to install valgrind"
321 exit 1
322 fi
323
324 local _valgrind_bins=$(strace -e execve valgrind /bin/true 2>&1 >/dev/null | perl -lne 'print $1 if /^execve\("([^"]+)"/')
325 dracut_install $_valgrind_bins
326
327 local _valgrind_libs=$(LD_DEBUG=files valgrind /bin/true 2>&1 >/dev/null | perl -lne 'print $1 if m{calling init: (/.*vgpreload_.*)}')
328 dracut_install $_valgrind_libs
329
330 local _valgrind_dbg_and_supp=$(
331 strace -e open valgrind /bin/true 2>&1 >/dev/null |
332 perl -lne 'if (my ($fname) = /^open\("([^"]+).*= (?!-)\d+/) { print $fname if $fname =~ /debug|\.supp$/ }'
333 )
334 dracut_install $_valgrind_dbg_and_supp
335 }
336
337 create_valgrind_wrapper() {
338 local _valgrind_wrapper=$initdir/$ROOTLIBDIR/systemd-under-valgrind
339 ddebug "Create $_valgrind_wrapper"
340 cat >$_valgrind_wrapper <<EOF
341 #!/bin/bash
342
343 mount -t proc proc /proc
344 exec valgrind --leak-check=full --log-file=/valgrind.out $ROOTLIBDIR/systemd "\$@"
345 EOF
346 chmod 0755 $_valgrind_wrapper
347 }
348
349 create_asan_wrapper() {
350 local _asan_wrapper=$initdir/$ROOTLIBDIR/systemd-under-asan
351 ddebug "Create $_asan_wrapper"
352 cat >$_asan_wrapper <<EOF
353 #!/bin/bash
354
355 set -x
356
357 DEFAULT_ASAN_OPTIONS=strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1
358 DEFAULT_UBSAN_OPTIONS=print_stacktrace=1:print_summary=1:halt_on_error=1
359 DEFAULT_ENVIRONMENT="ASAN_OPTIONS=\$DEFAULT_ASAN_OPTIONS UBSAN_OPTIONS=\$DEFAULT_UBSAN_OPTIONS"
360
361 mount -t proc proc /proc
362 mount -t sysfs sysfs /sys
363 mount -o remount,rw /
364
365 PATH_TO_ASAN=\$(find / -name '*libasan*' | sed 1q)
366 if [[ "\$PATH_TO_ASAN" ]]; then
367 # A lot of services (most notably dbus) won't start without preloading libasan
368 # See https://github.com/systemd/systemd/issues/5004
369 DEFAULT_ENVIRONMENT="\$DEFAULT_ENVIRONMENT LD_PRELOAD=\$PATH_TO_ASAN"
370 fi
371 echo DefaultEnvironment=\$DEFAULT_ENVIRONMENT >>/etc/systemd/system.conf
372 echo DefaultTimeoutStartSec=180s >>/etc/systemd/system.conf
373
374 # ASAN and syscall filters aren't compatible with each other.
375 find / -name '*.service' -type f | xargs sed -i 's/^\\(MemoryDeny\\|SystemCall\\)/#\\1/'
376
377 # The redirection of ASAN reports to a file prevents them from ending up in /dev/null.
378 # But, apparently, sometimes it doesn't work: https://github.com/google/sanitizers/issues/886.
379 JOURNALD_CONF_DIR=/etc/systemd/system/systemd-journald.service.d
380 mkdir -p "\$JOURNALD_CONF_DIR"
381 printf "[Service]\nEnvironment=ASAN_OPTIONS=\$DEFAULT_ASAN_OPTIONS:log_path=/systemd-journald.asan.log\n" >"\$JOURNALD_CONF_DIR/env.conf"
382
383 # 90s isn't enough for some services to finish when literally everything is run
384 # under ASan+UBSan in containers, which, in turn, are run in VMs.
385 # Let's limit which environments such services should be executed in.
386 mkdir -p /etc/systemd/system/systemd-hwdb-update.service.d
387 printf "[Unit]\nConditionVirtualization=container\n\n[Service]\nTimeoutSec=180s\n" >/etc/systemd/system/systemd-hwdb-update.service.d/env-override.conf
388
389 export ASAN_OPTIONS=\$DEFAULT_ASAN_OPTIONS:log_path=/systemd.asan.log UBSAN_OPTIONS=\$DEFAULT_UBSAN_OPTIONS
390 exec $ROOTLIBDIR/systemd "\$@"
391 EOF
392
393 chmod 0755 $_asan_wrapper
394 }
395
396 create_strace_wrapper() {
397 local _strace_wrapper=$initdir/$ROOTLIBDIR/systemd-under-strace
398 ddebug "Create $_strace_wrapper"
399 cat >$_strace_wrapper <<EOF
400 #!/bin/bash
401
402 exec strace -D -o /strace.out $ROOTLIBDIR/systemd "\$@"
403 EOF
404 chmod 0755 $_strace_wrapper
405 }
406
407 install_fsck() {
408 dracut_install /sbin/fsck*
409 dracut_install -o /bin/fsck*
410
411 # fskc.reiserfs calls reiserfsck. so, install it
412 dracut_install -o reiserfsck
413 }
414
415 install_dmevent() {
416 instmods dm_crypt =crypto
417 type -P dmeventd >/dev/null && dracut_install dmeventd
418 inst_libdir_file "libdevmapper-event.so*"
419 if [[ "$LOOKS_LIKE_DEBIAN" ]]; then
420 # dmsetup installs 55-dm and 60-persistent-storage-dm on Debian/Ubuntu
421 # and since buster/bionic 95-dm-notify.rules
422 # see https://gitlab.com/debian-lvm/lvm2/blob/master/debian/patches/udev.patch
423 inst_rules 55-dm.rules 60-persistent-storage-dm.rules 95-dm-notify.rules
424 else
425 inst_rules 10-dm.rules 13-dm-disk.rules 95-dm-notify.rules
426 fi
427 }
428
429 install_systemd() {
430 # install compiled files
431 local _ninja_bin=$(type -P ninja || type -P ninja-build)
432 if [[ -z "$_ninja_bin" ]]; then
433 dfatal "ninja was not found"
434 exit 1
435 fi
436 (set -x; DESTDIR=$initdir "$_ninja_bin" -C $BUILD_DIR install)
437 # remove unneeded documentation
438 rm -fr $initdir/usr/share/{man,doc}
439 # we strip binaries since debug symbols increase binaries size a lot
440 # and it could fill the available space
441 strip_binaries
442
443 [[ "$LOOKS_LIKE_SUSE" ]] && setup_suse
444
445 # enable debug logging in PID1
446 echo LogLevel=debug >> $initdir/etc/systemd/system.conf
447 }
448
449 get_ldpath() {
450 local _bin="$1"
451 objdump -p "$_bin" 2>/dev/null | awk "/R(UN)?PATH/ { print \"$initdir\" \$2 }" | paste -sd :
452 }
453
454 install_missing_libraries() {
455 # install possible missing libraries
456 for i in $initdir{,/usr}/{sbin,bin}/* $initdir{,/usr}/lib/systemd/{,tests/{,manual/,unsafe/}}*; do
457 LD_LIBRARY_PATH=$(get_ldpath $i) inst_libs $i
458 done
459 }
460
461 create_empty_image() {
462 local _size=500
463 if [[ "$STRIP_BINARIES" = "no" ]]; then
464 _size=$((2*_size))
465 fi
466 rm -f "$TESTDIR/rootdisk.img"
467 # Create the blank file to use as a root filesystem
468 dd if=/dev/null of="$TESTDIR/rootdisk.img" bs=1M seek="$_size"
469 LOOPDEV=$(losetup --show -P -f $TESTDIR/rootdisk.img)
470 [ -b "$LOOPDEV" ] || return 1
471 echo "LOOPDEV=$LOOPDEV" >> $STATEFILE
472 sfdisk "$LOOPDEV" <<EOF
473 ,$((_size-50))M
474 ,
475 EOF
476
477 udevadm settle
478
479 local _label="-L systemd"
480 # mkfs.reiserfs doesn't know -L. so, use --label instead
481 [[ "$FSTYPE" == "reiserfs" ]] && _label="--label systemd"
482 if ! mkfs -t "${FSTYPE}" ${_label} "${LOOPDEV}p1" -q; then
483 dfatal "Failed to mkfs -t ${FSTYPE}"
484 exit 1
485 fi
486 }
487
488 check_asan_reports() {
489 local ret=0
490 local root="$1"
491
492 if [[ "$IS_BUILT_WITH_ASAN" = "yes" ]]; then
493 ls -l "$root"
494 if [[ -e "$root/systemd.asan.log.1" ]]; then
495 cat "$root/systemd.asan.log.1"
496 ret=$(($ret+1))
497 fi
498
499 journald_report=$(find "$root" -name "systemd-journald.asan.log*" -exec cat {} \;)
500 if [[ ! -z "$journald_report" ]]; then
501 printf "%s" "$journald_report"
502 ret=$(($ret+1))
503 fi
504
505 pids=$(
506 "$BUILD_DIR/journalctl" -D "$root/var/log/journal" | perl -alne '
507 BEGIN {
508 %services_to_ignore = (
509 "dbus-daemon" => undef,
510 );
511 }
512 print $2 if /\s(\S*)\[(\d+)\]:\s*SUMMARY:\s+\w+Sanitizer/ && !exists $services_to_ignore{$1}'
513 )
514 if [[ ! -z "$pids" ]]; then
515 ret=$(($ret+1))
516 for pid in $pids; do
517 "$BUILD_DIR/journalctl" -D "$root/var/log/journal" _PID=$pid --no-pager
518 done
519 fi
520 fi
521
522 return $ret
523 }
524
525 check_result_nspawn() {
526 local ret=1
527 local journald_report=""
528 local pids=""
529 [[ -e $TESTDIR/$1/testok ]] && ret=0
530 [[ -f $TESTDIR/$1/failed ]] && cp -a $TESTDIR/$1/failed $TESTDIR
531 cp -a $TESTDIR/$1/var/log/journal $TESTDIR
532 [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed
533 ls -l $TESTDIR/journal/*/*.journal
534 test -s $TESTDIR/failed && ret=$(($ret+1))
535 [ -n "$TIMED_OUT" ] && ret=$(($ret+1))
536 check_asan_reports "$TESTDIR/$1" || ret=$(($ret+1))
537 return $ret
538 }
539
540 # can be overridden in specific test
541 check_result_qemu() {
542 local ret=1
543 mkdir -p $TESTDIR/root
544 mount ${LOOPDEV}p1 $TESTDIR/root
545 [[ -e $TESTDIR/root/testok ]] && ret=0
546 [[ -f $TESTDIR/root/failed ]] && cp -a $TESTDIR/root/failed $TESTDIR
547 cp -a $TESTDIR/root/var/log/journal $TESTDIR
548 check_asan_reports "$TESTDIR/root" || ret=$(($ret+1))
549 umount $TESTDIR/root
550 [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed
551 ls -l $TESTDIR/journal/*/*.journal
552 test -s $TESTDIR/failed && ret=$(($ret+1))
553 [ -n "$TIMED_OUT" ] && ret=$(($ret+1))
554 return $ret
555 }
556
557 strip_binaries() {
558 if [[ "$STRIP_BINARIES" = "no" ]]; then
559 ddebug "Don't strip binaries"
560 return 0
561 fi
562 ddebug "Strip binaries"
563 find "$initdir" -executable -not -path '*/lib/modules/*.ko' -type f | \
564 xargs strip --strip-unneeded |& \
565 grep -v 'file format not recognized' | \
566 ddebug
567 }
568
569 create_rc_local() {
570 mkdir -p $initdir/etc/rc.d
571 cat >$initdir/etc/rc.d/rc.local <<EOF
572 #!/bin/bash
573 exit 0
574 EOF
575 chmod 0755 $initdir/etc/rc.d/rc.local
576 }
577
578 install_execs() {
579 ddebug "install any Execs from the service files"
580 (
581 export PKG_CONFIG_PATH=$BUILD_DIR/src/core/
582 systemdsystemunitdir=$(pkg-config --variable=systemdsystemunitdir systemd)
583 systemduserunitdir=$(pkg-config --variable=systemduserunitdir systemd)
584 sed -r -n 's|^Exec[a-zA-Z]*=[@+!-]*([^ ]+).*|\1|gp' $initdir/{$systemdsystemunitdir,$systemduserunitdir}/*.service \
585 | sort -u | while read i; do
586 # some {rc,halt}.local scripts and programs are okay to not exist, the rest should
587 # also, plymouth is pulled in by rescue.service, but even there the exit code
588 # is ignored; as it's not present on some distros, don't fail if it doesn't exist
589 inst $i || [ "${i%.local}" != "$i" ] || [ "${i%systemd-update-done}" != "$i" ] || [ "/bin/plymouth" == "$i" ]
590 done
591 )
592 }
593
594 generate_module_dependencies() {
595 if [[ -d $initdir/lib/modules/$KERNEL_VER ]] && \
596 ! depmod -a -b "$initdir" $KERNEL_VER; then
597 dfatal "\"depmod -a $KERNEL_VER\" failed."
598 exit 1
599 fi
600 }
601
602 install_depmod_files() {
603 inst /lib/modules/$KERNEL_VER/modules.order
604 inst /lib/modules/$KERNEL_VER/modules.builtin
605 }
606
607 install_plymouth() {
608 # install plymouth, if found... else remove plymouth service files
609 # if [ -x /usr/libexec/plymouth/plymouth-populate-initrd ]; then
610 # PLYMOUTH_POPULATE_SOURCE_FUNCTIONS="$TEST_BASE_DIR/test-functions" \
611 # /usr/libexec/plymouth/plymouth-populate-initrd -t $initdir
612 # dracut_install plymouth plymouthd
613 # else
614 rm -f $initdir/{usr/lib,etc}/systemd/system/plymouth* $initdir/{usr/lib,etc}/systemd/system/*/plymouth*
615 # fi
616 }
617
618 install_ld_so_conf() {
619 cp -a /etc/ld.so.conf* $initdir/etc
620 ldconfig -r "$initdir"
621 }
622
623 install_config_files() {
624 inst /etc/sysconfig/init || true
625 inst /etc/passwd
626 inst /etc/shadow
627 inst /etc/login.defs
628 inst /etc/group
629 inst /etc/shells
630 inst /etc/nsswitch.conf
631 inst /etc/pam.conf || true
632 inst /etc/securetty || true
633 inst /etc/os-release
634 inst /etc/localtime
635 # we want an empty environment
636 > $initdir/etc/environment
637 > $initdir/etc/machine-id
638 # set the hostname
639 echo systemd-testsuite > $initdir/etc/hostname
640 # fstab
641 if [[ "$LOOKS_LIKE_SUSE" ]]; then
642 ROOTMOUNT="/dev/sda1 / ${FSTYPE} rw 0 1"
643 else
644 ROOTMOUNT="LABEL=systemd / ${FSTYPE} rw 0 1"
645 fi
646
647 cat >$initdir/etc/fstab <<EOF
648 $ROOTMOUNT
649 EOF
650 }
651
652 install_basic_tools() {
653 [[ $BASICTOOLS ]] && dracut_install $BASICTOOLS
654 dracut_install -o sushell
655 # in Debian ldconfig is just a shell script wrapper around ldconfig.real
656 dracut_install -o ldconfig.real
657 }
658
659 install_debug_tools() {
660 [[ $DEBUGTOOLS ]] && dracut_install $DEBUGTOOLS
661
662 if [[ $INTERACTIVE_DEBUG ]]; then
663 # Set default TERM from vt220 to linux, so at least basic key shortcuts work
664 local _getty_override="$initdir/etc/systemd/system/serial-getty@.service.d"
665 mkdir -p "$_getty_override"
666 echo -e "[Service]\nEnvironment=TERM=linux" > "$_getty_override/default-TERM.conf"
667
668 cat > "$initdir/etc/motd" << EOF
669 To adjust the terminal size use:
670 export COLUMNS=xx
671 export LINES=yy
672 or
673 stty cols xx rows yy
674 EOF
675 fi
676 }
677
678 install_libnss() {
679 # install libnss_files for login
680 NSS_LIBS=$(LD_DEBUG=files getent passwd 2>&1 >/dev/null |sed -n '/calling init: .*libnss_/ {s!^.* /!/!; p}')
681 dracut_install $NSS_LIBS
682 }
683
684 install_dbus() {
685 inst $ROOTLIBDIR/system/dbus.socket
686
687 # Fedora rawhide replaced dbus.service with dbus-daemon.service
688 if [ -f $ROOTLIBDIR/system/dbus-daemon.service ]; then
689 inst $ROOTLIBDIR/system/dbus-daemon.service
690 # Alias symlink
691 inst_symlink /etc/systemd/system/dbus.service
692 else
693 inst $ROOTLIBDIR/system/dbus.service
694 fi
695 # Newer Fedora versions use dbus-broker by default. Let's install it is available.
696 [ -f /usr/bin/dbus-broker ] && inst /usr/bin/dbus-broker
697 [ -f /usr/bin/dbus-broker-launch ] && inst /usr/bin/dbus-broker-launch
698
699 find \
700 /etc/dbus-1 /usr/share/dbus-1 -xtype f \
701 | while read file; do
702 inst $file
703 done
704 }
705
706 install_pam() {
707 (
708 if [[ "$LOOKS_LIKE_DEBIAN" ]] && type -p dpkg-architecture &>/dev/null; then
709 find "/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH)/security" -xtype f
710 else
711 find /lib*/security -xtype f
712 fi
713 find /etc/pam.d /etc/security -xtype f
714 ) | while read file; do
715 inst $file
716 done
717
718 # pam_unix depends on unix_chkpwd.
719 # see http://www.linux-pam.org/Linux-PAM-html/sag-pam_unix.html
720 dracut_install -o unix_chkpwd
721
722 [[ "$LOOKS_LIKE_DEBIAN" ]] &&
723 cp /etc/pam.d/systemd-user $initdir/etc/pam.d/
724
725 # set empty root password for easy debugging
726 sed -i 's/^root:x:/root::/' $initdir/etc/passwd
727 }
728
729 install_keymaps() {
730 # The first three paths may be deprecated.
731 # It seems now the last two paths are used by many distributions.
732 for i in \
733 /usr/lib/kbd/keymaps/include/* \
734 /usr/lib/kbd/keymaps/i386/include/* \
735 /usr/lib/kbd/keymaps/i386/qwerty/us.* \
736 /usr/lib/kbd/keymaps/legacy/include/* \
737 /usr/lib/kbd/keymaps/legacy/i386/qwerty/us.*; do
738 [[ -f $i ]] || continue
739 inst $i
740 done
741
742 # When it takes any argument, then install more keymaps.
743 if [[ -n $1 ]]; then
744 for i in \
745 /usr/lib/kbd/keymaps/i386/*/* \
746 /usr/lib/kbd/keymaps/legacy/i386/*/*; do
747 [[ -f $i ]] || continue
748 inst $i
749 done
750 fi
751 }
752
753 install_zoneinfo() {
754 for i in /usr/share/zoneinfo/{,*/,*/*/}*; do
755 [[ -f $i ]] || continue
756 inst $i
757 done
758 }
759
760 install_fonts() {
761 for i in \
762 /usr/lib/kbd/consolefonts/eurlatgr* \
763 /usr/lib/kbd/consolefonts/latarcyrheb-sun16*; do
764 [[ -f $i ]] || continue
765 inst $i
766 done
767 }
768
769 install_terminfo() {
770 for _terminfodir in /lib/terminfo /etc/terminfo /usr/share/terminfo; do
771 [ -f ${_terminfodir}/l/linux ] && break
772 done
773 dracut_install -o ${_terminfodir}/l/linux
774 }
775
776 setup_testsuite() {
777 cp $TEST_BASE_DIR/testsuite.target $initdir/etc/systemd/system/
778 cp $TEST_BASE_DIR/end.service $initdir/etc/systemd/system/
779
780 mkdir -p $initdir/etc/systemd/system/testsuite.target.wants
781 ln -fs $TEST_BASE_DIR/testsuite.service $initdir/etc/systemd/system/testsuite.target.wants/testsuite.service
782 # Don't shutdown the machine after running the test when INTERACTIVE_DEBUG is set
783 [[ -z $INTERACTIVE_DEBUG ]] && ln -fs $TEST_BASE_DIR/end.service $initdir/etc/systemd/system/testsuite.target.wants/end.service
784
785 # make the testsuite the default target
786 ln -fs testsuite.target $initdir/etc/systemd/system/default.target
787 }
788
789 setup_nspawn_root() {
790 rm -fr $TESTDIR/nspawn-root
791 ddebug "cp -ar $initdir $TESTDIR/nspawn-root"
792 cp -ar $initdir $TESTDIR/nspawn-root
793 # we don't mount in the nspawn root
794 rm -f $TESTDIR/nspawn-root/etc/fstab
795 if [[ "$RUN_IN_UNPRIVILEGED_CONTAINER" = "yes" ]]; then
796 cp -ar $TESTDIR/nspawn-root $TESTDIR/unprivileged-nspawn-root
797 fi
798 }
799
800 setup_basic_dirs() {
801 mkdir -p $initdir/run
802 mkdir -p $initdir/etc/systemd/system
803 mkdir -p $initdir/var/log/journal
804
805 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
806 if [ -L "/$d" ]; then
807 inst_symlink "/$d"
808 else
809 inst_dir "/$d"
810 fi
811 done
812
813 ln -sfn /run "$initdir/var/run"
814 ln -sfn /run/lock "$initdir/var/lock"
815 }
816
817 inst_libs() {
818 local _bin=$1
819 local _so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)'
820 local _file _line
821
822 LC_ALL=C ldd "$_bin" 2>/dev/null | while read _line; do
823 [[ $_line = 'not a dynamic executable' ]] && break
824
825 if [[ $_line =~ $_so_regex ]]; then
826 _file=${BASH_REMATCH[1]}
827 [[ -e ${initdir}/$_file ]] && continue
828 inst_library "$_file"
829 continue
830 fi
831
832 if [[ $_line =~ not\ found ]]; then
833 dfatal "Missing a shared library required by $_bin."
834 dfatal "Run \"ldd $_bin\" to find out what it is."
835 dfatal "$_line"
836 dfatal "dracut cannot create an initrd."
837 exit 1
838 fi
839 done
840 }
841
842 import_testdir() {
843 [[ -e $STATEFILE ]] && . $STATEFILE
844 if [[ ! -d "$TESTDIR" ]]; then
845 if [[ -z "$TESTDIR" ]]; then
846 TESTDIR=$(mktemp --tmpdir=/var/tmp -d -t systemd-test.XXXXXX)
847 else
848 mkdir -p "$TESTDIR"
849 fi
850
851 echo "TESTDIR=\"$TESTDIR\"" > $STATEFILE
852 export TESTDIR
853 fi
854 }
855
856 import_initdir() {
857 initdir=$TESTDIR/root
858 export initdir
859 }
860
861 ## @brief Converts numeric logging level to the first letter of level name.
862 #
863 # @param lvl Numeric logging level in range from 1 to 6.
864 # @retval 1 if @a lvl is out of range.
865 # @retval 0 if @a lvl is correct.
866 # @result Echoes first letter of level name.
867 _lvl2char() {
868 case "$1" in
869 1) echo F;;
870 2) echo E;;
871 3) echo W;;
872 4) echo I;;
873 5) echo D;;
874 6) echo T;;
875 *) return 1;;
876 esac
877 }
878
879 ## @brief Internal helper function for _do_dlog()
880 #
881 # @param lvl Numeric logging level.
882 # @param msg Message.
883 # @retval 0 It's always returned, even if logging failed.
884 #
885 # @note This function is not supposed to be called manually. Please use
886 # dtrace(), ddebug(), or others instead which wrap this one.
887 #
888 # This function calls _do_dlog() either with parameter msg, or if
889 # none is given, it will read standard input and will use every line as
890 # a message.
891 #
892 # This enables:
893 # dwarn "This is a warning"
894 # echo "This is a warning" | dwarn
895 LOG_LEVEL=${LOG_LEVEL:-4}
896
897 dlog() {
898 [ -z "$LOG_LEVEL" ] && return 0
899 [ $1 -le $LOG_LEVEL ] || return 0
900 local lvl="$1"; shift
901 local lvlc=$(_lvl2char "$lvl") || return 0
902
903 if [ $# -ge 1 ]; then
904 echo "$lvlc: $*"
905 else
906 while read line; do
907 echo "$lvlc: " "$line"
908 done
909 fi
910 }
911
912 ## @brief Logs message at TRACE level (6)
913 #
914 # @param msg Message.
915 # @retval 0 It's always returned, even if logging failed.
916 dtrace() {
917 set +x
918 dlog 6 "$@"
919 [ -n "$debug" ] && set -x || :
920 }
921
922 ## @brief Logs message at DEBUG level (5)
923 #
924 # @param msg Message.
925 # @retval 0 It's always returned, even if logging failed.
926 ddebug() {
927 # set +x
928 dlog 5 "$@"
929 # [ -n "$debug" ] && set -x || :
930 }
931
932 ## @brief Logs message at INFO level (4)
933 #
934 # @param msg Message.
935 # @retval 0 It's always returned, even if logging failed.
936 dinfo() {
937 set +x
938 dlog 4 "$@"
939 [ -n "$debug" ] && set -x || :
940 }
941
942 ## @brief Logs message at WARN level (3)
943 #
944 # @param msg Message.
945 # @retval 0 It's always returned, even if logging failed.
946 dwarn() {
947 set +x
948 dlog 3 "$@"
949 [ -n "$debug" ] && set -x || :
950 }
951
952 ## @brief Logs message at ERROR level (2)
953 #
954 # @param msg Message.
955 # @retval 0 It's always returned, even if logging failed.
956 derror() {
957 # set +x
958 dlog 2 "$@"
959 # [ -n "$debug" ] && set -x || :
960 }
961
962 ## @brief Logs message at FATAL level (1)
963 #
964 # @param msg Message.
965 # @retval 0 It's always returned, even if logging failed.
966 dfatal() {
967 set +x
968 dlog 1 "$@"
969 [ -n "$debug" ] && set -x || :
970 }
971
972
973 # Generic substring function. If $2 is in $1, return 0.
974 strstr() { [ "${1#*$2*}" != "$1" ]; }
975
976 # normalize_path <path>
977 # Prints the normalized path, where it removes any duplicated
978 # and trailing slashes.
979 # Example:
980 # $ normalize_path ///test/test//
981 # /test/test
982 normalize_path() {
983 shopt -q -s extglob
984 set -- "${1//+(\/)//}"
985 shopt -q -u extglob
986 echo "${1%/}"
987 }
988
989 # convert_abs_rel <from> <to>
990 # Prints the relative path, when creating a symlink to <to> from <from>.
991 # Example:
992 # $ convert_abs_rel /usr/bin/test /bin/test-2
993 # ../../bin/test-2
994 # $ ln -s $(convert_abs_rel /usr/bin/test /bin/test-2) /usr/bin/test
995 convert_abs_rel() {
996 local __current __absolute __abssize __cursize __newpath
997 local -i __i __level
998
999 set -- "$(normalize_path "$1")" "$(normalize_path "$2")"
1000
1001 # corner case #1 - self looping link
1002 [[ "$1" == "$2" ]] && { echo "${1##*/}"; return; }
1003
1004 # corner case #2 - own dir link
1005 [[ "${1%/*}" == "$2" ]] && { echo "."; return; }
1006
1007 IFS="/" __current=($1)
1008 IFS="/" __absolute=($2)
1009
1010 __abssize=${#__absolute[@]}
1011 __cursize=${#__current[@]}
1012
1013 while [[ ${__absolute[__level]} == ${__current[__level]} ]]
1014 do
1015 (( __level++ ))
1016 if (( __level > __abssize || __level > __cursize ))
1017 then
1018 break
1019 fi
1020 done
1021
1022 for ((__i = __level; __i < __cursize-1; __i++))
1023 do
1024 if ((__i > __level))
1025 then
1026 __newpath=$__newpath"/"
1027 fi
1028 __newpath=$__newpath".."
1029 done
1030
1031 for ((__i = __level; __i < __abssize; __i++))
1032 do
1033 if [[ -n $__newpath ]]
1034 then
1035 __newpath=$__newpath"/"
1036 fi
1037 __newpath=$__newpath${__absolute[__i]}
1038 done
1039
1040 echo "$__newpath"
1041 }
1042
1043
1044 # Install a directory, keeping symlinks as on the original system.
1045 # Example: if /lib points to /lib64 on the host, "inst_dir /lib/file"
1046 # will create ${initdir}/lib64, ${initdir}/lib64/file,
1047 # and a symlink ${initdir}/lib -> lib64.
1048 inst_dir() {
1049 [[ -e ${initdir}/"$1" ]] && return 0 # already there
1050
1051 local _dir="$1" _part="${1%/*}" _file
1052 while [[ "$_part" != "${_part%/*}" ]] && ! [[ -e "${initdir}/${_part}" ]]; do
1053 _dir="$_part $_dir"
1054 _part=${_part%/*}
1055 done
1056
1057 # iterate over parent directories
1058 for _file in $_dir; do
1059 [[ -e "${initdir}/$_file" ]] && continue
1060 if [[ -L $_file ]]; then
1061 inst_symlink "$_file"
1062 else
1063 # create directory
1064 mkdir -m 0755 -p "${initdir}/$_file" || return 1
1065 [[ -e "$_file" ]] && chmod --reference="$_file" "${initdir}/$_file"
1066 chmod u+w "${initdir}/$_file"
1067 fi
1068 done
1069 }
1070
1071 # $1 = file to copy to ramdisk
1072 # $2 (optional) Name for the file on the ramdisk
1073 # Location of the image dir is assumed to be $initdir
1074 # We never overwrite the target if it exists.
1075 inst_simple() {
1076 [[ -f "$1" ]] || return 1
1077 strstr "$1" "/" || return 1
1078
1079 local _src=$1 target="${2:-$1}"
1080 if ! [[ -d ${initdir}/$target ]]; then
1081 [[ -e ${initdir}/$target ]] && return 0
1082 [[ -L ${initdir}/$target ]] && return 0
1083 [[ -d "${initdir}/${target%/*}" ]] || inst_dir "${target%/*}"
1084 fi
1085 # install checksum files also
1086 if [[ -e "${_src%/*}/.${_src##*/}.hmac" ]]; then
1087 inst "${_src%/*}/.${_src##*/}.hmac" "${target%/*}/.${target##*/}.hmac"
1088 fi
1089 ddebug "Installing $_src"
1090 cp --sparse=always -pfL "$_src" "${initdir}/$target"
1091 }
1092
1093 # find symlinks linked to given library file
1094 # $1 = library file
1095 # Function searches for symlinks by stripping version numbers appended to
1096 # library filename, checks if it points to the same target and finally
1097 # prints the list of symlinks to stdout.
1098 #
1099 # Example:
1100 # rev_lib_symlinks libfoo.so.8.1
1101 # output: libfoo.so.8 libfoo.so
1102 # (Only if libfoo.so.8 and libfoo.so exists on host system.)
1103 rev_lib_symlinks() {
1104 [[ ! $1 ]] && return 0
1105
1106 local fn="$1" orig="$(readlink -f "$1")" links=''
1107
1108 [[ ${fn} =~ .*\.so\..* ]] || return 1
1109
1110 until [[ ${fn##*.} == so ]]; do
1111 fn="${fn%.*}"
1112 [[ -L ${fn} && $(readlink -f "${fn}") == ${orig} ]] && links+=" ${fn}"
1113 done
1114
1115 echo "${links}"
1116 }
1117
1118 # Same as above, but specialized to handle dynamic libraries.
1119 # It handles making symlinks according to how the original library
1120 # is referenced.
1121 inst_library() {
1122 local _src="$1" _dest=${2:-$1} _lib _reallib _symlink
1123 strstr "$1" "/" || return 1
1124 [[ -e $initdir/$_dest ]] && return 0
1125 if [[ -L $_src ]]; then
1126 # install checksum files also
1127 if [[ -e "${_src%/*}/.${_src##*/}.hmac" ]]; then
1128 inst "${_src%/*}/.${_src##*/}.hmac" "${_dest%/*}/.${_dest##*/}.hmac"
1129 fi
1130 _reallib=$(readlink -f "$_src")
1131 inst_simple "$_reallib" "$_reallib"
1132 inst_dir "${_dest%/*}"
1133 [[ -d "${_dest%/*}" ]] && _dest=$(readlink -f "${_dest%/*}")/${_dest##*/}
1134 ln -sfn $(convert_abs_rel "${_dest}" "${_reallib}") "${initdir}/${_dest}"
1135 else
1136 inst_simple "$_src" "$_dest"
1137 fi
1138
1139 # Create additional symlinks. See rev_symlinks description.
1140 for _symlink in $(rev_lib_symlinks $_src) $(rev_lib_symlinks $_reallib); do
1141 [[ -e $initdir/$_symlink ]] || {
1142 ddebug "Creating extra symlink: $_symlink"
1143 inst_symlink $_symlink
1144 }
1145 done
1146 }
1147
1148 # find a binary. If we were not passed the full path directly,
1149 # search in the usual places to find the binary.
1150 find_binary() {
1151 if [[ -z ${1##/*} ]]; then
1152 if [[ -x $1 ]] || { strstr "$1" ".so" && ldd $1 &>/dev/null; }; then
1153 echo $1
1154 return 0
1155 fi
1156 fi
1157
1158 type -P $1
1159 }
1160
1161 # Same as above, but specialized to install binary executables.
1162 # Install binary executable, and all shared library dependencies, if any.
1163 inst_binary() {
1164 local _bin _target
1165 _bin=$(find_binary "$1") || return 1
1166 _target=${2:-$_bin}
1167 [[ -e $initdir/$_target ]] && return 0
1168 [[ -L $_bin ]] && inst_symlink $_bin $_target && return 0
1169 local _file _line
1170 local _so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)'
1171 # I love bash!
1172 LC_ALL=C ldd "$_bin" 2>/dev/null | while read _line; do
1173 [[ $_line = 'not a dynamic executable' ]] && break
1174
1175 if [[ $_line =~ $_so_regex ]]; then
1176 _file=${BASH_REMATCH[1]}
1177 [[ -e ${initdir}/$_file ]] && continue
1178 inst_library "$_file"
1179 continue
1180 fi
1181
1182 if [[ $_line =~ not\ found ]]; then
1183 dfatal "Missing a shared library required by $_bin."
1184 dfatal "Run \"ldd $_bin\" to find out what it is."
1185 dfatal "$_line"
1186 dfatal "dracut cannot create an initrd."
1187 exit 1
1188 fi
1189 done
1190 inst_simple "$_bin" "$_target"
1191 }
1192
1193 # same as above, except for shell scripts.
1194 # If your shell script does not start with shebang, it is not a shell script.
1195 inst_script() {
1196 local _bin
1197 _bin=$(find_binary "$1") || return 1
1198 shift
1199 local _line _shebang_regex
1200 read -r -n 80 _line <"$_bin"
1201 # If debug is set, clean unprintable chars to prevent messing up the term
1202 [[ $debug ]] && _line=$(echo -n "$_line" | tr -c -d '[:print:][:space:]')
1203 _shebang_regex='(#! *)(/[^ ]+).*'
1204 [[ $_line =~ $_shebang_regex ]] || return 1
1205 inst "${BASH_REMATCH[2]}" && inst_simple "$_bin" "$@"
1206 }
1207
1208 # same as above, but specialized for symlinks
1209 inst_symlink() {
1210 local _src=$1 _target=${2:-$1} _realsrc
1211 strstr "$1" "/" || return 1
1212 [[ -L $1 ]] || return 1
1213 [[ -L $initdir/$_target ]] && return 0
1214 _realsrc=$(readlink -f "$_src")
1215 if ! [[ -e $initdir/$_realsrc ]]; then
1216 if [[ -d $_realsrc ]]; then
1217 inst_dir "$_realsrc"
1218 else
1219 inst "$_realsrc"
1220 fi
1221 fi
1222 [[ ! -e $initdir/${_target%/*} ]] && inst_dir "${_target%/*}"
1223 [[ -d ${_target%/*} ]] && _target=$(readlink -f ${_target%/*})/${_target##*/}
1224 ln -sfn $(convert_abs_rel "${_target}" "${_realsrc}") "$initdir/$_target"
1225 }
1226
1227 # attempt to install any programs specified in a udev rule
1228 inst_rule_programs() {
1229 local _prog _bin
1230
1231 if grep -qE 'PROGRAM==?"[^ "]+' "$1"; then
1232 for _prog in $(grep -E 'PROGRAM==?"[^ "]+' "$1" | sed -r 's/.*PROGRAM==?"([^ "]+).*/\1/'); do
1233 if [ -x /lib/udev/$_prog ]; then
1234 _bin=/lib/udev/$_prog
1235 else
1236 _bin=$(find_binary "$_prog") || {
1237 dinfo "Skipping program $_prog using in udev rule $(basename $1) as it cannot be found"
1238 continue;
1239 }
1240 fi
1241
1242 #dinfo "Installing $_bin due to it's use in the udev rule $(basename $1)"
1243 dracut_install "$_bin"
1244 done
1245 fi
1246 }
1247
1248 # udev rules always get installed in the same place, so
1249 # create a function to install them to make life simpler.
1250 inst_rules() {
1251 local _target=/etc/udev/rules.d _rule _found
1252
1253 inst_dir "/lib/udev/rules.d"
1254 inst_dir "$_target"
1255 for _rule in "$@"; do
1256 if [ "${rule#/}" = "$rule" ]; then
1257 for r in /lib/udev/rules.d /etc/udev/rules.d; do
1258 if [[ -f $r/$_rule ]]; then
1259 _found="$r/$_rule"
1260 inst_simple "$_found"
1261 inst_rule_programs "$_found"
1262 fi
1263 done
1264 fi
1265 for r in '' ./ $dracutbasedir/rules.d/; do
1266 if [[ -f ${r}$_rule ]]; then
1267 _found="${r}$_rule"
1268 inst_simple "$_found" "$_target/${_found##*/}"
1269 inst_rule_programs "$_found"
1270 fi
1271 done
1272 [[ $_found ]] || dinfo "Skipping udev rule: $_rule"
1273 _found=
1274 done
1275 }
1276
1277 # general purpose installation function
1278 # Same args as above.
1279 inst() {
1280 local _x
1281
1282 case $# in
1283 1) ;;
1284 2) [[ ! $initdir && -d $2 ]] && export initdir=$2
1285 [[ $initdir = $2 ]] && set $1;;
1286 3) [[ -z $initdir ]] && export initdir=$2
1287 set $1 $3;;
1288 *) dfatal "inst only takes 1 or 2 or 3 arguments"
1289 exit 1;;
1290 esac
1291 for _x in inst_symlink inst_script inst_binary inst_simple; do
1292 $_x "$@" && return 0
1293 done
1294 return 1
1295 }
1296
1297 # install any of listed files
1298 #
1299 # If first argument is '-d' and second some destination path, first accessible
1300 # source is installed into this path, otherwise it will installed in the same
1301 # path as source. If none of listed files was installed, function return 1.
1302 # On first successful installation it returns with 0 status.
1303 #
1304 # Example:
1305 #
1306 # inst_any -d /bin/foo /bin/bar /bin/baz
1307 #
1308 # Lets assume that /bin/baz exists, so it will be installed as /bin/foo in
1309 # initramfs.
1310 inst_any() {
1311 local to f
1312
1313 [[ $1 = '-d' ]] && to="$2" && shift 2
1314
1315 for f in "$@"; do
1316 if [[ -e $f ]]; then
1317 [[ $to ]] && inst "$f" "$to" && return 0
1318 inst "$f" && return 0
1319 fi
1320 done
1321
1322 return 1
1323 }
1324
1325 # dracut_install [-o ] <file> [<file> ... ]
1326 # Install <file> to the initramfs image
1327 # -o optionally install the <file> and don't fail, if it is not there
1328 dracut_install() {
1329 local _optional=no
1330 if [[ $1 = '-o' ]]; then
1331 _optional=yes
1332 shift
1333 fi
1334 while (($# > 0)); do
1335 if ! inst "$1" ; then
1336 if [[ $_optional = yes ]]; then
1337 dinfo "Skipping program $1 as it cannot be found and is" \
1338 "flagged to be optional"
1339 else
1340 dfatal "Failed to install $1"
1341 exit 1
1342 fi
1343 fi
1344 shift
1345 done
1346 }
1347
1348 # Install a single kernel module along with any firmware it may require.
1349 # $1 = full path to kernel module to install
1350 install_kmod_with_fw() {
1351 # no need to go further if the module is already installed
1352
1353 [[ -e "${initdir}/lib/modules/$KERNEL_VER/${1##*/lib/modules/$KERNEL_VER/}" ]] \
1354 && return 0
1355
1356 [[ -e "$initdir/.kernelmodseen/${1##*/}" ]] && return 0
1357
1358 if [[ $omit_drivers ]]; then
1359 local _kmod=${1##*/}
1360 _kmod=${_kmod%.ko}
1361 _kmod=${_kmod/-/_}
1362 if [[ "$_kmod" =~ $omit_drivers ]]; then
1363 dinfo "Omitting driver $_kmod"
1364 return 1
1365 fi
1366 if [[ "${1##*/lib/modules/$KERNEL_VER/}" =~ $omit_drivers ]]; then
1367 dinfo "Omitting driver $_kmod"
1368 return 1
1369 fi
1370 fi
1371
1372 [ -d "$initdir/.kernelmodseen" ] && \
1373 > "$initdir/.kernelmodseen/${1##*/}"
1374
1375 inst_simple "$1" "/lib/modules/$KERNEL_VER/${1##*/lib/modules/$KERNEL_VER/}" \
1376 || return $?
1377
1378 local _modname=${1##*/} _fwdir _found _fw
1379 _modname=${_modname%.ko*}
1380 for _fw in $(modinfo -k $KERNEL_VER -F firmware $1 2>/dev/null); do
1381 _found=''
1382 for _fwdir in $fw_dir; do
1383 if [[ -d $_fwdir && -f $_fwdir/$_fw ]]; then
1384 inst_simple "$_fwdir/$_fw" "/lib/firmware/$_fw"
1385 _found=yes
1386 fi
1387 done
1388 if [[ $_found != yes ]]; then
1389 if ! grep -qe "\<${_modname//-/_}\>" /proc/modules; then
1390 dinfo "Possible missing firmware \"${_fw}\" for kernel module" \
1391 "\"${_modname}.ko\""
1392 else
1393 dwarn "Possible missing firmware \"${_fw}\" for kernel module" \
1394 "\"${_modname}.ko\""
1395 fi
1396 fi
1397 done
1398 return 0
1399 }
1400
1401 # Do something with all the dependencies of a kernel module.
1402 # Note that kernel modules depend on themselves using the technique we use
1403 # $1 = function to call for each dependency we find
1404 # It will be passed the full path to the found kernel module
1405 # $2 = module to get dependencies for
1406 # rest of args = arguments to modprobe
1407 # _fderr specifies FD passed from surrounding scope
1408 for_each_kmod_dep() {
1409 local _func=$1 _kmod=$2 _cmd _modpath _options _found=0
1410 shift 2
1411 modprobe "$@" --ignore-install --show-depends $_kmod 2>&${_fderr} | (
1412 while read _cmd _modpath _options; do
1413 [[ $_cmd = insmod ]] || continue
1414 $_func ${_modpath} || exit $?
1415 _found=1
1416 done
1417 [[ $_found -eq 0 ]] && exit 1
1418 exit 0
1419 )
1420 }
1421
1422 # filter kernel modules to install certain modules that meet specific
1423 # requirements.
1424 # $1 = search only in subdirectory of /kernel/$1
1425 # $2 = function to call with module name to filter.
1426 # This function will be passed the full path to the module to test.
1427 # The behavior of this function can vary depending on whether $hostonly is set.
1428 # If it is, we will only look at modules that are already in memory.
1429 # If it is not, we will look at all kernel modules
1430 # This function returns the full filenames of modules that match $1
1431 filter_kernel_modules_by_path () (
1432 local _modname _filtercmd
1433 if ! [[ $hostonly ]]; then
1434 _filtercmd='find "$KERNEL_MODS/kernel/$1" "$KERNEL_MODS/extra"'
1435 _filtercmd+=' "$KERNEL_MODS/weak-updates" -name "*.ko" -o -name "*.ko.gz"'
1436 _filtercmd+=' -o -name "*.ko.xz"'
1437 _filtercmd+=' 2>/dev/null'
1438 else
1439 _filtercmd='cut -d " " -f 1 </proc/modules|xargs modinfo -F filename '
1440 _filtercmd+='-k $KERNEL_VER 2>/dev/null'
1441 fi
1442 for _modname in $(eval $_filtercmd); do
1443 case $_modname in
1444 *.ko) "$2" "$_modname" && echo "$_modname";;
1445 *.ko.gz) gzip -dc "$_modname" > $initdir/$$.ko
1446 $2 $initdir/$$.ko && echo "$_modname"
1447 rm -f $initdir/$$.ko
1448 ;;
1449 *.ko.xz) xz -dc "$_modname" > $initdir/$$.ko
1450 $2 $initdir/$$.ko && echo "$_modname"
1451 rm -f $initdir/$$.ko
1452 ;;
1453 esac
1454 done
1455 )
1456 find_kernel_modules_by_path () (
1457 if ! [[ $hostonly ]]; then
1458 find "$KERNEL_MODS/kernel/$1" "$KERNEL_MODS/extra" "$KERNEL_MODS/weak-updates" \
1459 -name "*.ko" -o -name "*.ko.gz" -o -name "*.ko.xz" 2>/dev/null
1460 else
1461 cut -d " " -f 1 </proc/modules \
1462 | xargs modinfo -F filename -k $KERNEL_VER 2>/dev/null
1463 fi
1464 )
1465
1466 filter_kernel_modules () {
1467 filter_kernel_modules_by_path drivers "$1"
1468 }
1469
1470 find_kernel_modules () {
1471 find_kernel_modules_by_path drivers
1472 }
1473
1474 # instmods [-c] <kernel module> [<kernel module> ... ]
1475 # instmods [-c] <kernel subsystem>
1476 # install kernel modules along with all their dependencies.
1477 # <kernel subsystem> can be e.g. "=block" or "=drivers/usb/storage"
1478 instmods() {
1479 [[ $no_kernel = yes ]] && return
1480 # called [sub]functions inherit _fderr
1481 local _fderr=9
1482 local _check=no
1483 if [[ $1 = '-c' ]]; then
1484 _check=yes
1485 shift
1486 fi
1487
1488 function inst1mod() {
1489 local _ret=0 _mod="$1"
1490 case $_mod in
1491 =*)
1492 if [ -f $KERNEL_MODS/modules.${_mod#=} ]; then
1493 ( [[ "$_mpargs" ]] && echo $_mpargs
1494 cat "${KERNEL_MODS}/modules.${_mod#=}" ) \
1495 | instmods
1496 else
1497 ( [[ "$_mpargs" ]] && echo $_mpargs
1498 find "$KERNEL_MODS" -path "*/${_mod#=}/*" -printf '%f\n' ) \
1499 | instmods
1500 fi
1501 ;;
1502 --*) _mpargs+=" $_mod" ;;
1503 i2o_scsi) return ;; # Do not load this diagnostic-only module
1504 *)
1505 _mod=${_mod##*/}
1506 # if we are already installed, skip this module and go on
1507 # to the next one.
1508 [[ -f "$initdir/.kernelmodseen/${_mod%.ko}.ko" ]] && return
1509
1510 if [[ $omit_drivers ]] && [[ "$1" =~ $omit_drivers ]]; then
1511 dinfo "Omitting driver ${_mod##$KERNEL_MODS}"
1512 return
1513 fi
1514 # If we are building a host-specific initramfs and this
1515 # module is not already loaded, move on to the next one.
1516 [[ $hostonly ]] && ! grep -qe "\<${_mod//-/_}\>" /proc/modules \
1517 && ! echo $add_drivers | grep -qe "\<${_mod}\>" \
1518 && return
1519
1520 # We use '-d' option in modprobe only if modules prefix path
1521 # differs from default '/'. This allows us to use Dracut with
1522 # old version of modprobe which doesn't have '-d' option.
1523 local _moddirname=${KERNEL_MODS%%/lib/modules/*}
1524 [[ -n ${_moddirname} ]] && _moddirname="-d ${_moddirname}/"
1525
1526 # ok, load the module, all its dependencies, and any firmware
1527 # it may require
1528 for_each_kmod_dep install_kmod_with_fw $_mod \
1529 --set-version $KERNEL_VER ${_moddirname} $_mpargs
1530 ((_ret+=$?))
1531 ;;
1532 esac
1533 return $_ret
1534 }
1535
1536 function instmods_1() {
1537 local _mod _mpargs
1538 if (($# == 0)); then # filenames from stdin
1539 while read _mod; do
1540 inst1mod "${_mod%.ko*}" || {
1541 if [ "$_check" = "yes" ]; then
1542 dfatal "Failed to install $_mod"
1543 return 1
1544 fi
1545 }
1546 done
1547 fi
1548 while (($# > 0)); do # filenames as arguments
1549 inst1mod ${1%.ko*} || {
1550 if [ "$_check" = "yes" ]; then
1551 dfatal "Failed to install $1"
1552 return 1
1553 fi
1554 }
1555 shift
1556 done
1557 return 0
1558 }
1559
1560 local _ret _filter_not_found='FATAL: Module .* not found.'
1561 set -o pipefail
1562 # Capture all stderr from modprobe to _fderr. We could use {var}>...
1563 # redirections, but that would make dracut require bash4 at least.
1564 eval "( instmods_1 \"\$@\" ) ${_fderr}>&1" \
1565 | while read line; do [[ "$line" =~ $_filter_not_found ]] && echo $line || echo $line >&2 ;done | derror
1566 _ret=$?
1567 set +o pipefail
1568 return $_ret
1569 }
1570
1571 # inst_libdir_file [-n <pattern>] <file> [<file>...]
1572 # Install a <file> located on a lib directory to the initramfs image
1573 # -n <pattern> install non-matching files
1574 inst_libdir_file() {
1575 if [[ "$1" == "-n" ]]; then
1576 local _pattern=$1
1577 shift 2
1578 for _dir in $libdirs; do
1579 for _i in "$@"; do
1580 for _f in "$_dir"/$_i; do
1581 [[ "$_i" =~ $_pattern ]] || continue
1582 [[ -e "$_i" ]] && dracut_install "$_i"
1583 done
1584 done
1585 done
1586 else
1587 for _dir in $libdirs; do
1588 for _i in "$@"; do
1589 for _f in "$_dir"/$_i; do
1590 [[ -e "$_f" ]] && dracut_install "$_f"
1591 done
1592 done
1593 done
1594 fi
1595 }
1596
1597 setup_suse() {
1598 ln -fs ../usr/bin/systemctl $initdir/bin/
1599 ln -fs ../usr/lib/systemd $initdir/lib/
1600 inst_simple "/usr/lib/systemd/system/haveged.service"
1601 }
1602
1603 # can be overridden in specific test
1604 test_cleanup() {
1605 umount $TESTDIR/root 2>/dev/null || true
1606 [[ $LOOPDEV ]] && losetup -d $LOOPDEV || true
1607 return 0
1608 }
1609
1610 test_run() {
1611 if [ -z "$TEST_NO_QEMU" ]; then
1612 if run_qemu; then
1613 check_result_qemu || return 1
1614 else
1615 dwarn "can't run QEMU, skipping"
1616 fi
1617 fi
1618 if [ -z "$TEST_NO_NSPAWN" ]; then
1619 if run_nspawn "nspawn-root"; then
1620 check_result_nspawn "nspawn-root" || return 1
1621 else
1622 dwarn "can't run systemd-nspawn, skipping"
1623 fi
1624
1625 if [[ "$RUN_IN_UNPRIVILEGED_CONTAINER" = "yes" ]]; then
1626 if NSPAWN_ARGUMENTS="-U --private-network $NSPAWN_ARGUMENTS" run_nspawn "unprivileged-nspawn-root"; then
1627 check_result_nspawn "unprivileged-nspawn-root" || return 1
1628 else
1629 dwarn "can't run systemd-nspawn, skipping"
1630 fi
1631 fi
1632 fi
1633 return 0
1634 }
1635
1636 do_test() {
1637 if [[ $UID != "0" ]]; then
1638 echo "TEST: $TEST_DESCRIPTION [SKIPPED]: not root" >&2
1639 exit 0
1640 fi
1641
1642 # Detect lib paths
1643 [[ $libdir ]] || for libdir in /lib64 /lib; do
1644 [[ -d $libdir ]] && libdirs+=" $libdir" && break
1645 done
1646
1647 [[ $usrlibdir ]] || for usrlibdir in /usr/lib64 /usr/lib; do
1648 [[ -d $usrlibdir ]] && libdirs+=" $usrlibdir" && break
1649 done
1650
1651 mkdir -p "$STATEDIR"
1652
1653 import_testdir
1654 import_initdir
1655
1656 while (($# > 0)); do
1657 case $1 in
1658 --run)
1659 echo "TEST RUN: $TEST_DESCRIPTION"
1660 test_run
1661 ret=$?
1662 if (( $ret == 0 )); then
1663 echo "TEST RUN: $TEST_DESCRIPTION [OK]"
1664 else
1665 echo "TEST RUN: $TEST_DESCRIPTION [FAILED]"
1666 fi
1667 exit $ret;;
1668 --setup)
1669 echo "TEST SETUP: $TEST_DESCRIPTION"
1670 test_setup
1671 ;;
1672 --clean)
1673 echo "TEST CLEANUP: $TEST_DESCRIPTION"
1674 test_cleanup
1675 rm -fr "$TESTDIR"
1676 rm -f "$STATEFILE"
1677 ;;
1678 --all)
1679 ret=0
1680 echo -n "TEST: $TEST_DESCRIPTION ";
1681 (
1682 test_setup && test_run
1683 ret=$?
1684 test_cleanup
1685 rm -fr "$TESTDIR"
1686 rm -f "$STATEFILE"
1687 exit $ret
1688 ) </dev/null >"$TESTLOG" 2>&1 || ret=$?
1689 if [ $ret -eq 0 ]; then
1690 rm "$TESTLOG"
1691 echo "[OK]"
1692 else
1693 echo "[FAILED]"
1694 echo "see $TESTLOG"
1695 fi
1696 exit $ret;;
1697 *) break ;;
1698 esac
1699 shift
1700 done
1701 }