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