chiark / gitweb /
update TODO
[elogind.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 KERNEL_VER=${KERNEL_VER-$(uname -r)}
8 KERNEL_MODS="/lib/modules/$KERNEL_VER/"
9
10 BASICTOOLS="sh bash setsid loadkeys setfont login sushell sulogin gzip sleep echo mount umount cryptsetup date dmsetup modprobe"
11 DEBUGTOOLS="df free ls stty cat ps ln ip route dmesg dhclient mkdir cp ping dhclient strace less grep id tty touch du sort"
12
13 function find_qemu_bin() {
14     # SUSE and Red Hat call the binary qemu-kvm
15     # Debian and Gentoo call it kvm
16     [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a kvm qemu-kvm 2>/dev/null | grep '^/' -m1)
17
18     [ "$ARCH" ] || ARCH=$(uname -m)
19     case $ARCH in
20     x86_64)
21         # QEMU's own build system calls it qemu-system-x86_64
22         [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a qemu-system-x86_64 2>/dev/null | grep '^/' -m1)
23         ;;
24     i*86)
25         # new i386 version of QEMU
26         [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a qemu-system-i386 2>/dev/null | grep '^/' -m1)
27
28         # i386 version of QEMU
29         [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a qemu 2>/dev/null | grep '^/' -m1)
30         ;;
31     esac
32
33     if [ ! -e "$QEMU_BIN" ]; then
34         echo "Could not find a suitable QEMU binary" >&2
35         return 1
36     fi
37 }
38
39 run_qemu() {
40     [ "$KERNEL_BIN" ] || KERNEL_BIN=/boot/vmlinuz-$KERNEL_VER
41     [ "$INITRD" ]     || INITRD=/boot/initramfs-${KERNEL_VER}.img
42     [ "$QEMU_SMP" ]   || QEMU_SMP=1
43
44     find_qemu_bin || return 1
45
46     KERNEL_APPEND="root=/dev/sda1 \
47 systemd.log_level=debug \
48 raid=noautodetect \
49 loglevel=2 \
50 init=/usr/lib/systemd/systemd \
51 ro \
52 console=ttyS0 \
53 selinux=0 \
54 $KERNEL_APPEND \
55 "
56
57     QEMU_OPTIONS="-machine accel=kvm:tcg \
58 -smp $QEMU_SMP \
59 -net none \
60 -m 512M \
61 -nographic \
62 -kernel $KERNEL_BIN \
63 "
64
65     if [ "$INITRD" ]; then
66         QEMU_OPTIONS="$QEMU_OPTIONS -initrd $INITRD"
67     fi
68
69     ( set -x
70       $QEMU_BIN $QEMU_OPTIONS -append "$KERNEL_APPEND" $TESTDIR/rootdisk.img ) || return 1
71 }
72
73 run_nspawn() {
74     set -x
75     ../../systemd-nspawn --boot --directory=$TESTDIR/nspawn-root /usr/lib/systemd/systemd $KERNEL_APPEND
76 }
77
78 setup_basic_environment() {
79     # create the basic filesystem layout
80     setup_basic_dirs
81
82     install_systemd
83     install_missing_libraries
84     install_config_files
85     create_rc_local
86     install_basic_tools
87     install_libnss
88     install_pam
89     install_dbus
90     install_fonts
91     install_keymaps
92     install_terminfo
93     install_execs
94     install_plymouth
95     install_debug_tools
96     install_ld_so_conf
97     strip_binaries
98     install_depmod_files
99     generate_module_dependencies
100     # softlink mtab
101     ln -fs /proc/self/mounts $initdir/etc/mtab
102 }
103
104 install_dmevent() {
105     instmods dm_crypt =crypto
106     type -P dmeventd >/dev/null && dracut_install dmeventd
107     inst_libdir_file "libdevmapper-event.so*"
108     inst_rules 10-dm.rules 13-dm-disk.rules 95-dm-notify.rules
109 }
110
111 install_systemd() {
112     # install compiled files
113     (cd $TEST_BASE_DIR/..; set -x; make DESTDIR=$initdir install)
114     # remove unneeded documentation
115     rm -fr $initdir/usr/share/{man,doc,gtk-doc}
116     # we strip binaries since debug symbols increase binaries size a lot
117     # and it could fill the available space
118     strip_binaries
119 }
120
121 install_missing_libraries() {
122     # install possible missing libraries
123     for i in $initdir/{sbin,bin}/* $initdir/lib/systemd/*; do
124         inst_libs $i
125     done
126 }
127
128 create_empty_image() {
129     rm -f "$TESTDIR/rootdisk.img"
130     # Create the blank file to use as a root filesystem
131     dd if=/dev/null of="$TESTDIR/rootdisk.img" bs=1M seek=300
132     LOOPDEV=$(losetup --show -P -f $TESTDIR/rootdisk.img)
133     [ -b "$LOOPDEV" ] || return 1
134     echo "LOOPDEV=$LOOPDEV" >> $STATEFILE
135     sfdisk -C 9600 -H 2 -S 32 -L "$LOOPDEV" <<EOF
136 ,4800
137 ,
138 EOF
139
140     mkfs.ext3 -L systemd "${LOOPDEV}p1"
141 }
142
143 check_result_nspawn() {
144     ret=1
145     [[ -e $TESTDIR/nspawn-root/testok ]] && ret=0
146     [[ -f $TESTDIR/nspawn-root/failed ]] && cp -a $TESTDIR/nspawn-root/failed $TESTDIR
147     cp -a $TESTDIR/nspawn-root/var/log/journal $TESTDIR
148     [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed
149     ls -l $TESTDIR/journal/*/*.journal
150     test -s $TESTDIR/failed && ret=$(($ret+1))
151     return $ret
152 }
153
154 strip_binaries() {
155     ddebug "Strip binaries"
156     find "$initdir" -executable -not -path '*/lib/modules/*.ko' -type f | xargs strip --strip-unneeded | ddebug
157 }
158
159 create_rc_local() {
160     mkdir -p $initdir/etc/rc.d
161     cat >$initdir/etc/rc.d/rc.local <<EOF
162 #!/bin/bash
163 exit 0
164 EOF
165     chmod 0755 $initdir/etc/rc.d/rc.local
166 }
167
168 install_execs() {
169     # install any Execs from the service files
170     egrep -ho '^Exec[^ ]*=[^ ]+' $initdir/lib/systemd/system/*.service \
171         | while read i; do
172         i=${i##Exec*=}; i=${i##-}
173         inst $i
174     done
175 }
176
177 generate_module_dependencies() {
178     if [[ -d $initdir/lib/modules/$KERNEL_VER ]] && \
179         ! depmod -a -b "$initdir" $KERNEL_VER; then
180             dfatal "\"depmod -a $KERNEL_VER\" failed."
181             exit 1
182     fi
183 }
184
185 install_depmod_files() {
186     inst /lib/modules/$KERNEL_VER/modules.order
187     inst /lib/modules/$KERNEL_VER/modules.builtin
188 }
189
190 install_plymouth() {
191     # install plymouth, if found... else remove plymouth service files
192     # if [ -x /usr/libexec/plymouth/plymouth-populate-initrd ]; then
193     #     PLYMOUTH_POPULATE_SOURCE_FUNCTIONS="$TEST_BASE_DIR/test-functions" \
194     #         /usr/libexec/plymouth/plymouth-populate-initrd -t $initdir
195     #         dracut_install plymouth plymouthd
196     # else
197         rm -f $initdir/{usr/lib,etc}/systemd/system/plymouth* $initdir/{usr/lib,etc}/systemd/system/*/plymouth*
198     # fi
199 }
200
201 install_ld_so_conf() {
202     cp -a /etc/ld.so.conf* $initdir/etc
203     ldconfig -r "$initdir"
204 }
205
206 install_config_files() {
207     inst /etc/sysconfig/init
208     inst /etc/passwd
209     inst /etc/shadow
210     inst /etc/group
211     inst /etc/shells
212     inst /etc/nsswitch.conf
213     inst /etc/pam.conf
214     inst /etc/securetty
215     inst /etc/os-release
216     inst /etc/localtime
217     # we want an empty environment
218     > $initdir/etc/environment
219     > $initdir/etc/machine-id
220     # set the hostname
221     echo systemd-testsuite > $initdir/etc/hostname
222     # fstab
223     cat >$initdir/etc/fstab <<EOF
224 LABEL=systemd           /       ext3    rw 0 1
225 EOF
226 }
227
228 install_basic_tools() {
229     [[ $BASICTOOLS ]] && dracut_install $BASICTOOLS
230 }
231
232 install_debug_tools() {
233     [[ $DEBUGTOOLS ]] && dracut_install $DEBUGTOOLS
234 }
235
236 install_libnss() {
237     # install libnss_files for login
238     inst_libdir_file "libnss_files*"
239 }
240
241 install_dbus() {
242     inst /usr/lib/systemd/system/dbus.socket
243     inst /usr/lib/systemd/system/dbus.service
244
245     find \
246         /etc/dbus-1 -xtype f \
247         | while read file; do
248         inst $file
249     done
250 }
251
252 install_pam() {
253     find \
254         /etc/pam.d \
255         /etc/security \
256         /lib64/security \
257         /lib/security -xtype f \
258         | while read file; do
259         inst $file
260     done
261 }
262
263 install_keymaps() {
264     for i in \
265         /usr/lib/kbd/keymaps/include/* \
266         /usr/lib/kbd/keymaps/i386/include/* \
267         /usr/lib/kbd/keymaps/i386/qwerty/us.*; do
268             [[ -f $i ]] || continue
269             inst $i
270     done
271 }
272
273 install_fonts() {
274     for i in \
275         /usr/lib/kbd/consolefonts/latarcyrheb-sun16*; do
276             [[ -f $i ]] || continue
277             inst $i
278     done
279 }
280
281 install_terminfo() {
282     for _terminfodir in /lib/terminfo /etc/terminfo /usr/share/terminfo; do
283         [ -f ${_terminfodir}/l/linux ] && break
284     done
285     dracut_install -o ${_terminfodir}/l/linux
286 }
287
288 setup_testsuite() {
289     cp $TEST_BASE_DIR/{testsuite.target,end.service} $initdir/etc/systemd/system/
290
291     mkdir -p $initdir/etc/systemd/system/testsuite.target.wants
292     ln -fs $TEST_BASE_DIR/testsuite.service $initdir/etc/systemd/system/testsuite.target.wants/testsuite.service
293     ln -fs $TEST_BASE_DIR/end.service $initdir/etc/systemd/system/testsuite.target.wants/end.service
294
295     # make the testsuite the default target
296     ln -fs testsuite.target $initdir/etc/systemd/system/default.target
297 }
298
299 setup_nspawn_root() {
300     rm -fr $TESTDIR/nspawn-root
301     ddebug "cp -ar $initdir $TESTDIR/nspawn-root"
302     cp -ar $initdir $TESTDIR/nspawn-root
303     # we don't mount in the nspawn root
304     rm -f $TESTDIR/nspawn-root/etc/fstab
305 }
306
307 setup_basic_dirs() {
308     mkdir -p $initdir/run
309     mkdir -p $initdir/etc/systemd/system
310     mkdir -p $initdir/var/log/journal
311
312     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
313         if [ -L "/$d" ]; then
314             inst_symlink "/$d"
315         else
316             inst_dir "/$d"
317         fi
318     done
319
320     ln -sfn /run "$initdir/var/run"
321     ln -sfn /run/lock "$initdir/var/lock"
322 }
323
324 inst_libs() {
325     local _bin=$1
326     local _so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)'
327     local _file _line
328
329     LC_ALL=C ldd "$_bin" 2>/dev/null | while read _line; do
330         [[ $_line = 'not a dynamic executable' ]] && break
331
332         if [[ $_line =~ $_so_regex ]]; then
333             _file=${BASH_REMATCH[1]}
334             [[ -e ${initdir}/$_file ]] && continue
335             inst_library "$_file"
336             continue
337         fi
338
339         if [[ $_line =~ not\ found ]]; then
340             dfatal "Missing a shared library required by $_bin."
341             dfatal "Run \"ldd $_bin\" to find out what it is."
342             dfatal "$_line"
343             dfatal "dracut cannot create an initrd."
344             exit 1
345         fi
346     done
347 }
348
349 import_testdir() {
350     STATEFILE=".testdir"
351     [[ -e $STATEFILE ]] && . $STATEFILE
352     if [[ -z "$TESTDIR" ]] || [[ ! -d "$TESTDIR" ]]; then
353         TESTDIR=$(mktemp --tmpdir=/var/tmp -d -t systemd-test.XXXXXX)
354         echo "TESTDIR=\"$TESTDIR\"" > $STATEFILE
355         export TESTDIR
356     fi
357 }
358
359 import_initdir() {
360     initdir=$TESTDIR/root
361     export initdir
362 }
363
364 ## @brief Converts numeric logging level to the first letter of level name.
365 #
366 # @param lvl Numeric logging level in range from 1 to 6.
367 # @retval 1 if @a lvl is out of range.
368 # @retval 0 if @a lvl is correct.
369 # @result Echoes first letter of level name.
370 _lvl2char() {
371     case "$1" in
372         1) echo F;;
373         2) echo E;;
374         3) echo W;;
375         4) echo I;;
376         5) echo D;;
377         6) echo T;;
378         *) return 1;;
379     esac
380 }
381
382 ## @brief Internal helper function for _do_dlog()
383 #
384 # @param lvl Numeric logging level.
385 # @param msg Message.
386 # @retval 0 It's always returned, even if logging failed.
387 #
388 # @note This function is not supposed to be called manually. Please use
389 # dtrace(), ddebug(), or others instead which wrap this one.
390 #
391 # This function calls _do_dlog() either with parameter msg, or if
392 # none is given, it will read standard input and will use every line as
393 # a message.
394 #
395 # This enables:
396 # dwarn "This is a warning"
397 # echo "This is a warning" | dwarn
398 LOG_LEVEL=4
399
400 dlog() {
401     [ -z "$LOG_LEVEL" ] && return 0
402     [ $1 -le $LOG_LEVEL ] || return 0
403     local lvl="$1"; shift
404     local lvlc=$(_lvl2char "$lvl") || return 0
405
406     if [ $# -ge 1 ]; then
407         echo "$lvlc: $*"
408     else
409         while read line; do
410             echo "$lvlc: " "$line"
411         done
412     fi
413 }
414
415 ## @brief Logs message at TRACE level (6)
416 #
417 # @param msg Message.
418 # @retval 0 It's always returned, even if logging failed.
419 dtrace() {
420     set +x
421     dlog 6 "$@"
422     [ -n "$debug" ] && set -x || :
423 }
424
425 ## @brief Logs message at DEBUG level (5)
426 #
427 # @param msg Message.
428 # @retval 0 It's always returned, even if logging failed.
429 ddebug() {
430 #    set +x
431     dlog 5 "$@"
432 #    [ -n "$debug" ] && set -x || :
433 }
434
435 ## @brief Logs message at INFO level (4)
436 #
437 # @param msg Message.
438 # @retval 0 It's always returned, even if logging failed.
439 dinfo() {
440     set +x
441     dlog 4 "$@"
442     [ -n "$debug" ] && set -x || :
443 }
444
445 ## @brief Logs message at WARN level (3)
446 #
447 # @param msg Message.
448 # @retval 0 It's always returned, even if logging failed.
449 dwarn() {
450     set +x
451     dlog 3 "$@"
452     [ -n "$debug" ] && set -x || :
453 }
454
455 ## @brief Logs message at ERROR level (2)
456 #
457 # @param msg Message.
458 # @retval 0 It's always returned, even if logging failed.
459 derror() {
460 #    set +x
461     dlog 2 "$@"
462 #    [ -n "$debug" ] && set -x || :
463 }
464
465 ## @brief Logs message at FATAL level (1)
466 #
467 # @param msg Message.
468 # @retval 0 It's always returned, even if logging failed.
469 dfatal() {
470     set +x
471     dlog 1 "$@"
472     [ -n "$debug" ] && set -x || :
473 }
474
475
476 # Generic substring function.  If $2 is in $1, return 0.
477 strstr() { [ "${1#*$2*}" != "$1" ]; }
478
479 # normalize_path <path>
480 # Prints the normalized path, where it removes any duplicated
481 # and trailing slashes.
482 # Example:
483 # $ normalize_path ///test/test//
484 # /test/test
485 normalize_path() {
486     shopt -q -s extglob
487     set -- "${1//+(\/)//}"
488     shopt -q -u extglob
489     echo "${1%/}"
490 }
491
492 # convert_abs_rel <from> <to>
493 # Prints the relative path, when creating a symlink to <to> from <from>.
494 # Example:
495 # $ convert_abs_rel /usr/bin/test /bin/test-2
496 # ../../bin/test-2
497 # $ ln -s $(convert_abs_rel /usr/bin/test /bin/test-2) /usr/bin/test
498 convert_abs_rel() {
499     local __current __absolute __abssize __cursize __newpath
500     local -i __i __level
501
502     set -- "$(normalize_path "$1")" "$(normalize_path "$2")"
503
504     # corner case #1 - self looping link
505     [[ "$1" == "$2" ]] && { echo "${1##*/}"; return; }
506
507     # corner case #2 - own dir link
508     [[ "${1%/*}" == "$2" ]] && { echo "."; return; }
509
510     IFS="/" __current=($1)
511     IFS="/" __absolute=($2)
512
513     __abssize=${#__absolute[@]}
514     __cursize=${#__current[@]}
515
516     while [[ ${__absolute[__level]} == ${__current[__level]} ]]
517     do
518         (( __level++ ))
519         if (( __level > __abssize || __level > __cursize ))
520         then
521             break
522         fi
523     done
524
525     for ((__i = __level; __i < __cursize-1; __i++))
526     do
527         if ((__i > __level))
528         then
529             __newpath=$__newpath"/"
530         fi
531         __newpath=$__newpath".."
532     done
533
534     for ((__i = __level; __i < __abssize; __i++))
535     do
536         if [[ -n $__newpath ]]
537         then
538             __newpath=$__newpath"/"
539         fi
540         __newpath=$__newpath${__absolute[__i]}
541     done
542
543     echo "$__newpath"
544 }
545
546
547 # Install a directory, keeping symlinks as on the original system.
548 # Example: if /lib points to /lib64 on the host, "inst_dir /lib/file"
549 # will create ${initdir}/lib64, ${initdir}/lib64/file,
550 # and a symlink ${initdir}/lib -> lib64.
551 inst_dir() {
552     [[ -e ${initdir}/"$1" ]] && return 0  # already there
553
554     local _dir="$1" _part="${1%/*}" _file
555     while [[ "$_part" != "${_part%/*}" ]] && ! [[ -e "${initdir}/${_part}" ]]; do
556         _dir="$_part $_dir"
557         _part=${_part%/*}
558     done
559
560     # iterate over parent directories
561     for _file in $_dir; do
562         [[ -e "${initdir}/$_file" ]] && continue
563         if [[ -L $_file ]]; then
564             inst_symlink "$_file"
565         else
566             # create directory
567             mkdir -m 0755 -p "${initdir}/$_file" || return 1
568             [[ -e "$_file" ]] && chmod --reference="$_file" "${initdir}/$_file"
569             chmod u+w "${initdir}/$_file"
570         fi
571     done
572 }
573
574 # $1 = file to copy to ramdisk
575 # $2 (optional) Name for the file on the ramdisk
576 # Location of the image dir is assumed to be $initdir
577 # We never overwrite the target if it exists.
578 inst_simple() {
579     [[ -f "$1" ]] || return 1
580     strstr "$1" "/" || return 1
581
582     local _src=$1 target="${2:-$1}"
583     if ! [[ -d ${initdir}/$target ]]; then
584         [[ -e ${initdir}/$target ]] && return 0
585         [[ -L ${initdir}/$target ]] && return 0
586         [[ -d "${initdir}/${target%/*}" ]] || inst_dir "${target%/*}"
587     fi
588     # install checksum files also
589     if [[ -e "${_src%/*}/.${_src##*/}.hmac" ]]; then
590         inst "${_src%/*}/.${_src##*/}.hmac" "${target%/*}/.${target##*/}.hmac"
591     fi
592     ddebug "Installing $_src"
593     cp --sparse=always -pfL "$_src" "${initdir}/$target"
594 }
595
596 # find symlinks linked to given library file
597 # $1 = library file
598 # Function searches for symlinks by stripping version numbers appended to
599 # library filename, checks if it points to the same target and finally
600 # prints the list of symlinks to stdout.
601 #
602 # Example:
603 # rev_lib_symlinks libfoo.so.8.1
604 # output: libfoo.so.8 libfoo.so
605 # (Only if libfoo.so.8 and libfoo.so exists on host system.)
606 rev_lib_symlinks() {
607     [[ ! $1 ]] && return 0
608
609     local fn="$1" orig="$(readlink -f "$1")" links=''
610
611     [[ ${fn} =~ .*\.so\..* ]] || return 1
612
613     until [[ ${fn##*.} == so ]]; do
614         fn="${fn%.*}"
615         [[ -L ${fn} && $(readlink -f "${fn}") == ${orig} ]] && links+=" ${fn}"
616     done
617
618     echo "${links}"
619 }
620
621 # Same as above, but specialized to handle dynamic libraries.
622 # It handles making symlinks according to how the original library
623 # is referenced.
624 inst_library() {
625     local _src="$1" _dest=${2:-$1} _lib _reallib _symlink
626     strstr "$1" "/" || return 1
627     [[ -e $initdir/$_dest ]] && return 0
628     if [[ -L $_src ]]; then
629         # install checksum files also
630         if [[ -e "${_src%/*}/.${_src##*/}.hmac" ]]; then
631             inst "${_src%/*}/.${_src##*/}.hmac" "${_dest%/*}/.${_dest##*/}.hmac"
632         fi
633         _reallib=$(readlink -f "$_src")
634         inst_simple "$_reallib" "$_reallib"
635         inst_dir "${_dest%/*}"
636         [[ -d "${_dest%/*}" ]] && _dest=$(readlink -f "${_dest%/*}")/${_dest##*/}
637         ln -sfn $(convert_abs_rel "${_dest}" "${_reallib}") "${initdir}/${_dest}"
638     else
639         inst_simple "$_src" "$_dest"
640     fi
641
642     # Create additional symlinks.  See rev_symlinks description.
643     for _symlink in $(rev_lib_symlinks $_src) $(rev_lib_symlinks $_reallib); do
644         [[ ! -e $initdir/$_symlink ]] && {
645             ddebug "Creating extra symlink: $_symlink"
646             inst_symlink $_symlink
647         }
648     done
649 }
650
651 # find a binary.  If we were not passed the full path directly,
652 # search in the usual places to find the binary.
653 find_binary() {
654     if [[ -z ${1##/*} ]]; then
655         if [[ -x $1 ]] || { strstr "$1" ".so" && ldd $1 &>/dev/null; };  then
656             echo $1
657             return 0
658         fi
659     fi
660
661     type -P $1
662 }
663
664 # Same as above, but specialized to install binary executables.
665 # Install binary executable, and all shared library dependencies, if any.
666 inst_binary() {
667     local _bin _target
668     _bin=$(find_binary "$1") || return 1
669     _target=${2:-$_bin}
670     [[ -e $initdir/$_target ]] && return 0
671     [[ -L $_bin ]] && inst_symlink $_bin $_target && return 0
672     local _file _line
673     local _so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)'
674     # I love bash!
675     LC_ALL=C ldd "$_bin" 2>/dev/null | while read _line; do
676         [[ $_line = 'not a dynamic executable' ]] && break
677
678         if [[ $_line =~ $_so_regex ]]; then
679             _file=${BASH_REMATCH[1]}
680             [[ -e ${initdir}/$_file ]] && continue
681             inst_library "$_file"
682             continue
683         fi
684
685         if [[ $_line =~ not\ found ]]; then
686             dfatal "Missing a shared library required by $_bin."
687             dfatal "Run \"ldd $_bin\" to find out what it is."
688             dfatal "$_line"
689             dfatal "dracut cannot create an initrd."
690             exit 1
691         fi
692     done
693     inst_simple "$_bin" "$_target"
694 }
695
696 # same as above, except for shell scripts.
697 # If your shell script does not start with shebang, it is not a shell script.
698 inst_script() {
699     local _bin
700     _bin=$(find_binary "$1") || return 1
701     shift
702     local _line _shebang_regex
703     read -r -n 80 _line <"$_bin"
704     # If debug is set, clean unprintable chars to prevent messing up the term
705     [[ $debug ]] && _line=$(echo -n "$_line" | tr -c -d '[:print:][:space:]')
706     _shebang_regex='(#! *)(/[^ ]+).*'
707     [[ $_line =~ $_shebang_regex ]] || return 1
708     inst "${BASH_REMATCH[2]}" && inst_simple "$_bin" "$@"
709 }
710
711 # same as above, but specialized for symlinks
712 inst_symlink() {
713     local _src=$1 _target=${2:-$1} _realsrc
714     strstr "$1" "/" || return 1
715     [[ -L $1 ]] || return 1
716     [[ -L $initdir/$_target ]] && return 0
717     _realsrc=$(readlink -f "$_src")
718     if ! [[ -e $initdir/$_realsrc ]]; then
719         if [[ -d $_realsrc ]]; then
720             inst_dir "$_realsrc"
721         else
722             inst "$_realsrc"
723         fi
724     fi
725     [[ ! -e $initdir/${_target%/*} ]] && inst_dir "${_target%/*}"
726     [[ -d ${_target%/*} ]] && _target=$(readlink -f ${_target%/*})/${_target##*/}
727     ln -sfn $(convert_abs_rel "${_target}" "${_realsrc}") "$initdir/$_target"
728 }
729
730 # attempt to install any programs specified in a udev rule
731 inst_rule_programs() {
732     local _prog _bin
733
734     if grep -qE 'PROGRAM==?"[^ "]+' "$1"; then
735         for _prog in $(grep -E 'PROGRAM==?"[^ "]+' "$1" | sed -r 's/.*PROGRAM==?"([^ "]+).*/\1/'); do
736             if [ -x /lib/udev/$_prog ]; then
737                 _bin=/lib/udev/$_prog
738             else
739                 _bin=$(find_binary "$_prog") || {
740                     dinfo "Skipping program $_prog using in udev rule $(basename $1) as it cannot be found"
741                     continue;
742                 }
743             fi
744
745             #dinfo "Installing $_bin due to it's use in the udev rule $(basename $1)"
746             dracut_install "$_bin"
747         done
748     fi
749 }
750
751 # udev rules always get installed in the same place, so
752 # create a function to install them to make life simpler.
753 inst_rules() {
754     local _target=/etc/udev/rules.d _rule _found
755
756     inst_dir "/lib/udev/rules.d"
757     inst_dir "$_target"
758     for _rule in "$@"; do
759         if [ "${rule#/}" = "$rule" ]; then
760             for r in /lib/udev/rules.d /etc/udev/rules.d; do
761                 if [[ -f $r/$_rule ]]; then
762                     _found="$r/$_rule"
763                     inst_simple "$_found"
764                     inst_rule_programs "$_found"
765                 fi
766             done
767         fi
768         for r in '' ./ $dracutbasedir/rules.d/; do
769             if [[ -f ${r}$_rule ]]; then
770                 _found="${r}$_rule"
771                 inst_simple "$_found" "$_target/${_found##*/}"
772                 inst_rule_programs "$_found"
773             fi
774         done
775         [[ $_found ]] || dinfo "Skipping udev rule: $_rule"
776     done
777 }
778
779 # general purpose installation function
780 # Same args as above.
781 inst() {
782     local _x
783
784     case $# in
785         1) ;;
786         2) [[ ! $initdir && -d $2 ]] && export initdir=$2
787             [[ $initdir = $2 ]] && set $1;;
788         3) [[ -z $initdir ]] && export initdir=$2
789             set $1 $3;;
790         *) dfatal "inst only takes 1 or 2 or 3 arguments"
791             exit 1;;
792     esac
793     for _x in inst_symlink inst_script inst_binary inst_simple; do
794         $_x "$@" && return 0
795     done
796     return 1
797 }
798
799 # install any of listed files
800 #
801 # If first argument is '-d' and second some destination path, first accessible
802 # source is installed into this path, otherwise it will installed in the same
803 # path as source.  If none of listed files was installed, function return 1.
804 # On first successful installation it returns with 0 status.
805 #
806 # Example:
807 #
808 # inst_any -d /bin/foo /bin/bar /bin/baz
809 #
810 # Lets assume that /bin/baz exists, so it will be installed as /bin/foo in
811 # initramfs.
812 inst_any() {
813     local to f
814
815     [[ $1 = '-d' ]] && to="$2" && shift 2
816
817     for f in "$@"; do
818         if [[ -e $f ]]; then
819             [[ $to ]] && inst "$f" "$to" && return 0
820             inst "$f" && return 0
821         fi
822     done
823
824     return 1
825 }
826
827 # dracut_install [-o ] <file> [<file> ... ]
828 # Install <file> to the initramfs image
829 # -o optionally install the <file> and don't fail, if it is not there
830 dracut_install() {
831     local _optional=no
832     if [[ $1 = '-o' ]]; then
833         _optional=yes
834         shift
835     fi
836     while (($# > 0)); do
837         if ! inst "$1" ; then
838             if [[ $_optional = yes ]]; then
839                 dinfo "Skipping program $1 as it cannot be found and is" \
840                     "flagged to be optional"
841             else
842                 dfatal "Failed to install $1"
843                 exit 1
844             fi
845         fi
846         shift
847     done
848 }
849
850 # Install a single kernel module along with any firmware it may require.
851 # $1 = full path to kernel module to install
852 install_kmod_with_fw() {
853     # no need to go further if the module is already installed
854
855     [[ -e "${initdir}/lib/modules/$KERNEL_VER/${1##*/lib/modules/$KERNEL_VER/}" ]] \
856         && return 0
857
858     [[ -e "$initdir/.kernelmodseen/${1##*/}" ]] && return 0
859
860     if [[ $omit_drivers ]]; then
861         local _kmod=${1##*/}
862         _kmod=${_kmod%.ko}
863         _kmod=${_kmod/-/_}
864         if [[ "$_kmod" =~ $omit_drivers ]]; then
865             dinfo "Omitting driver $_kmod"
866             return 1
867         fi
868         if [[ "${1##*/lib/modules/$KERNEL_VER/}" =~ $omit_drivers ]]; then
869             dinfo "Omitting driver $_kmod"
870             return 1
871         fi
872     fi
873
874     [ -d "$initdir/.kernelmodseen" ] && \
875         > "$initdir/.kernelmodseen/${1##*/}"
876
877     inst_simple "$1" "/lib/modules/$KERNEL_VER/${1##*/lib/modules/$KERNEL_VER/}" \
878         || return $?
879
880     local _modname=${1##*/} _fwdir _found _fw
881     _modname=${_modname%.ko*}
882     for _fw in $(modinfo -k $KERNEL_VER -F firmware $1 2>/dev/null); do
883         _found=''
884         for _fwdir in $fw_dir; do
885             if [[ -d $_fwdir && -f $_fwdir/$_fw ]]; then
886                 inst_simple "$_fwdir/$_fw" "/lib/firmware/$_fw"
887                 _found=yes
888             fi
889         done
890         if [[ $_found != yes ]]; then
891             if ! grep -qe "\<${_modname//-/_}\>" /proc/modules; then
892                 dinfo "Possible missing firmware \"${_fw}\" for kernel module" \
893                     "\"${_modname}.ko\""
894             else
895                 dwarn "Possible missing firmware \"${_fw}\" for kernel module" \
896                     "\"${_modname}.ko\""
897             fi
898         fi
899     done
900     return 0
901 }
902
903 # Do something with all the dependencies of a kernel module.
904 # Note that kernel modules depend on themselves using the technique we use
905 # $1 = function to call for each dependency we find
906 #      It will be passed the full path to the found kernel module
907 # $2 = module to get dependencies for
908 # rest of args = arguments to modprobe
909 # _fderr specifies FD passed from surrounding scope
910 for_each_kmod_dep() {
911     local _func=$1 _kmod=$2 _cmd _modpath _options _found=0
912     shift 2
913     modprobe "$@" --ignore-install --show-depends $_kmod 2>&${_fderr} | (
914         while read _cmd _modpath _options; do
915             [[ $_cmd = insmod ]] || continue
916             $_func ${_modpath} || exit $?
917             _found=1
918         done
919         [[ $_found -eq 0 ]] && exit 1
920         exit 0
921     )
922 }
923
924 # filter kernel modules to install certain modules that meet specific
925 # requirements.
926 # $1 = search only in subdirectory of /kernel/$1
927 # $2 = function to call with module name to filter.
928 #      This function will be passed the full path to the module to test.
929 # The behavior of this function can vary depending on whether $hostonly is set.
930 # If it is, we will only look at modules that are already in memory.
931 # If it is not, we will look at all kernel modules
932 # This function returns the full filenames of modules that match $1
933 filter_kernel_modules_by_path () (
934     local _modname _filtercmd
935     if ! [[ $hostonly ]]; then
936         _filtercmd='find "$KERNEL_MODS/kernel/$1" "$KERNEL_MODS/extra"'
937         _filtercmd+=' "$KERNEL_MODS/weak-updates" -name "*.ko" -o -name "*.ko.gz"'
938         _filtercmd+=' -o -name "*.ko.xz"'
939         _filtercmd+=' 2>/dev/null'
940     else
941         _filtercmd='cut -d " " -f 1 </proc/modules|xargs modinfo -F filename '
942         _filtercmd+='-k $KERNEL_VER 2>/dev/null'
943     fi
944     for _modname in $(eval $_filtercmd); do
945         case $_modname in
946             *.ko) "$2" "$_modname" && echo "$_modname";;
947             *.ko.gz) gzip -dc "$_modname" > $initdir/$$.ko
948                 $2 $initdir/$$.ko && echo "$_modname"
949                 rm -f $initdir/$$.ko
950                 ;;
951             *.ko.xz) xz -dc "$_modname" > $initdir/$$.ko
952                 $2 $initdir/$$.ko && echo "$_modname"
953                 rm -f $initdir/$$.ko
954                 ;;
955         esac
956     done
957 )
958 find_kernel_modules_by_path () (
959     if ! [[ $hostonly ]]; then
960         find "$KERNEL_MODS/kernel/$1" "$KERNEL_MODS/extra" "$KERNEL_MODS/weak-updates" \
961           -name "*.ko" -o -name "*.ko.gz" -o -name "*.ko.xz" 2>/dev/null
962     else
963         cut -d " " -f 1 </proc/modules \
964         | xargs modinfo -F filename -k $KERNEL_VER 2>/dev/null
965     fi
966 )
967
968 filter_kernel_modules () {
969     filter_kernel_modules_by_path  drivers  "$1"
970 }
971
972 find_kernel_modules () {
973     find_kernel_modules_by_path  drivers
974 }
975
976 # instmods [-c] <kernel module> [<kernel module> ... ]
977 # instmods [-c] <kernel subsystem>
978 # install kernel modules along with all their dependencies.
979 # <kernel subsystem> can be e.g. "=block" or "=drivers/usb/storage"
980 instmods() {
981     [[ $no_kernel = yes ]] && return
982     # called [sub]functions inherit _fderr
983     local _fderr=9
984     local _check=no
985     if [[ $1 = '-c' ]]; then
986         _check=yes
987         shift
988     fi
989
990     function inst1mod() {
991         local _ret=0 _mod="$1"
992         case $_mod in
993             =*)
994                 if [ -f $KERNEL_MODS/modules.${_mod#=} ]; then
995                     ( [[ "$_mpargs" ]] && echo $_mpargs
996                       cat "${KERNEL_MODS}/modules.${_mod#=}" ) \
997                     | instmods
998                 else
999                     ( [[ "$_mpargs" ]] && echo $_mpargs
1000                       find "$KERNEL_MODS" -path "*/${_mod#=}/*" -printf '%f\n' ) \
1001                     | instmods
1002                 fi
1003                 ;;
1004             --*) _mpargs+=" $_mod" ;;
1005             i2o_scsi) return ;; # Do not load this diagnostic-only module
1006             *)
1007                 _mod=${_mod##*/}
1008                 # if we are already installed, skip this module and go on
1009                 # to the next one.
1010                 [[ -f "$initdir/.kernelmodseen/${_mod%.ko}.ko" ]] && return
1011
1012                 if [[ $omit_drivers ]] && [[ "$1" =~ $omit_drivers ]]; then
1013                     dinfo "Omitting driver ${_mod##$KERNEL_MODS}"
1014                     return
1015                 fi
1016                 # If we are building a host-specific initramfs and this
1017                 # module is not already loaded, move on to the next one.
1018                 [[ $hostonly ]] && ! grep -qe "\<${_mod//-/_}\>" /proc/modules \
1019                     && ! echo $add_drivers | grep -qe "\<${_mod}\>" \
1020                     && return
1021
1022                 # We use '-d' option in modprobe only if modules prefix path
1023                 # differs from default '/'.  This allows us to use Dracut with
1024                 # old version of modprobe which doesn't have '-d' option.
1025                 local _moddirname=${KERNEL_MODS%%/lib/modules/*}
1026                 [[ -n ${_moddirname} ]] && _moddirname="-d ${_moddirname}/"
1027
1028                 # ok, load the module, all its dependencies, and any firmware
1029                 # it may require
1030                 for_each_kmod_dep install_kmod_with_fw $_mod \
1031                     --set-version $KERNEL_VER ${_moddirname} $_mpargs
1032                 ((_ret+=$?))
1033                 ;;
1034         esac
1035         return $_ret
1036     }
1037
1038     function instmods_1() {
1039         local _mod _mpargs
1040         if (($# == 0)); then  # filenames from stdin
1041             while read _mod; do
1042                 inst1mod "${_mod%.ko*}" || {
1043                     if [ "$_check" = "yes" ]; then
1044                         dfatal "Failed to install $_mod"
1045                         return 1
1046                     fi
1047                 }
1048             done
1049         fi
1050         while (($# > 0)); do  # filenames as arguments
1051             inst1mod ${1%.ko*} || {
1052                 if [ "$_check" = "yes" ]; then
1053                     dfatal "Failed to install $1"
1054                     return 1
1055                 fi
1056             }
1057             shift
1058         done
1059         return 0
1060     }
1061
1062     local _ret _filter_not_found='FATAL: Module .* not found.'
1063     set -o pipefail
1064     # Capture all stderr from modprobe to _fderr. We could use {var}>...
1065     # redirections, but that would make dracut require bash4 at least.
1066     eval "( instmods_1 \"\$@\" ) ${_fderr}>&1" \
1067     | while read line; do [[ "$line" =~ $_filter_not_found ]] && echo $line || echo $line >&2 ;done | derror
1068     _ret=$?
1069     set +o pipefail
1070     return $_ret
1071 }
1072
1073 # inst_libdir_file [-n <pattern>] <file> [<file>...]
1074 # Install a <file> located on a lib directory to the initramfs image
1075 # -n <pattern> install non-matching files
1076 inst_libdir_file() {
1077     if [[ "$1" == "-n" ]]; then
1078         local _pattern=$1
1079         shift 2
1080         for _dir in $libdirs; do
1081             for _i in "$@"; do
1082                 for _f in "$_dir"/$_i; do
1083                     [[ "$_i" =~ $_pattern ]] || continue
1084                     [[ -e "$_i" ]] && dracut_install "$_i"
1085                 done
1086             done
1087         done
1088     else
1089         for _dir in $libdirs; do
1090             for _i in "$@"; do
1091                 for _f in "$_dir"/$_i; do
1092                     [[ -e "$_f" ]] && dracut_install "$_f"
1093                 done
1094             done
1095         done
1096     fi
1097 }
1098
1099 check_nspawn() {
1100     [[ -d /sys/fs/cgroup/systemd ]]
1101 }
1102
1103
1104 do_test() {
1105     if [[ $UID != "0" ]]; then
1106         echo "TEST: $TEST_DESCRIPTION [SKIPPED]: not root" >&2
1107         exit 0
1108     fi
1109
1110 # Detect lib paths
1111     [[ $libdir ]] || for libdir in /lib64 /lib; do
1112         [[ -d $libdir ]] && libdirs+=" $libdir" && break
1113     done
1114
1115     [[ $usrlibdir ]] || for usrlibdir in /usr/lib64 /usr/lib; do
1116         [[ -d $usrlibdir ]] && libdirs+=" $usrlibdir" && break
1117     done
1118
1119     import_testdir
1120     import_initdir
1121
1122     while (($# > 0)); do
1123         case $1 in
1124             --run)
1125                 echo "TEST RUN: $TEST_DESCRIPTION"
1126                 test_run
1127                 ret=$?
1128                 if [ $ret -eq 0 ]; then
1129                     echo "TEST RUN: $TEST_DESCRIPTION [OK]"
1130                 else
1131                     echo "TEST RUN: $TEST_DESCRIPTION [FAILED]"
1132                 fi
1133                 exit $ret;;
1134             --setup)
1135                 echo "TEST SETUP: $TEST_DESCRIPTION"
1136                 test_setup
1137                 exit $?;;
1138             --clean)
1139                 echo "TEST CLEANUP: $TEST_DESCRIPTION"
1140                 test_cleanup
1141                 rm -fr "$TESTDIR"
1142                 rm -f .testdir
1143                 exit $?;;
1144             --all)
1145                 echo -n "TEST: $TEST_DESCRIPTION ";
1146                 (
1147                     test_setup && test_run
1148                     ret=$?
1149                     test_cleanup
1150                     rm -fr "$TESTDIR"
1151                     rm -f .testdir
1152                     exit $ret
1153                 ) </dev/null >test.log 2>&1
1154                 ret=$?
1155                 if [ $ret -eq 0 ]; then
1156                     rm test.log
1157                     echo "[OK]"
1158                 else
1159                     echo "[FAILED]"
1160                     echo "see $(pwd)/test.log"
1161                 fi
1162                 exit $ret;;
1163             *) break ;;
1164         esac
1165         shift
1166     done
1167 }