chiark / gitweb /
test/TEST-01-BASIC: only test systemd-nspawn, if the test system uses systemd
[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 setup_basic_dirs() {
8     for d in usr/bin usr/sbin bin etc lib "$libdir" sbin tmp usr var var/log; do
9         [[ -e "${initdir}${prefix}/$d" ]] && continue
10         if [ -L "/$d" ]; then
11             inst_symlink "/$d" "${prefix}/$d"
12         else
13             mkdir -m 0755 -p "${initdir}${prefix}/$d"
14         fi
15     done
16
17     for d in dev proc sys sysroot root run run/lock run/initramfs; do
18         if [ -L "/$d" ]; then
19             inst_symlink "/$d"
20         else
21             mkdir -m 0755 -p "$initdir/$d"
22         fi
23     done
24
25     ln -sfn /run "$initdir/var/run"
26     ln -sfn /run/lock "$initdir/var/lock"
27 }
28
29 inst_libs() {
30     local _bin=$1
31     local _so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)'
32     local _file _line
33
34     LC_ALL=C ldd "$_bin" 2>/dev/null | while read _line; do
35         [[ $_line = 'not a dynamic executable' ]] && break
36
37         if [[ $_line =~ $_so_regex ]]; then
38             _file=${BASH_REMATCH[1]}
39             [[ -e ${initdir}/$_file ]] && continue
40             inst_library "$_file"
41             continue
42         fi
43
44         if [[ $_line =~ not\ found ]]; then
45             dfatal "Missing a shared library required by $_bin."
46             dfatal "Run \"ldd $_bin\" to find out what it is."
47             dfatal "$_line"
48             dfatal "dracut cannot create an initrd."
49             exit 1
50         fi
51     done
52 }
53
54 import_testdir() {
55     STATEFILE=".testdir"
56     [[ -e $STATEFILE ]] && . $STATEFILE
57     if [[ -z "$TESTDIR" ]] || [[ ! -d "$TESTDIR" ]]; then
58         TESTDIR=$(mktemp --tmpdir=/var/tmp -d -t systemd-test.XXXXXX)
59         echo "TESTDIR=\"$TESTDIR\"" > $STATEFILE
60         export TESTDIR
61     fi
62 }
63
64 ## @brief Converts numeric logging level to the first letter of level name.
65 #
66 # @param lvl Numeric logging level in range from 1 to 6.
67 # @retval 1 if @a lvl is out of range.
68 # @retval 0 if @a lvl is correct.
69 # @result Echoes first letter of level name.
70 _lvl2char() {
71     case "$1" in
72         1) echo F;;
73         2) echo E;;
74         3) echo W;;
75         4) echo I;;
76         5) echo D;;
77         6) echo T;;
78         *) return 1;;
79     esac
80 }
81
82 ## @brief Internal helper function for _do_dlog()
83 #
84 # @param lvl Numeric logging level.
85 # @param msg Message.
86 # @retval 0 It's always returned, even if logging failed.
87 #
88 # @note This function is not supposed to be called manually. Please use
89 # dtrace(), ddebug(), or others instead which wrap this one.
90 #
91 # This function calls _do_dlog() either with parameter msg, or if
92 # none is given, it will read standard input and will use every line as
93 # a message.
94 #
95 # This enables:
96 # dwarn "This is a warning"
97 # echo "This is a warning" | dwarn
98 LOG_LEVEL=4
99
100 dlog() {
101     [ -z "$LOG_LEVEL" ] && return 0
102     [ $1 -le $LOG_LEVEL ] || return 0
103     local lvl="$1"; shift
104     local lvlc=$(_lvl2char "$lvl") || return 0
105
106     if [ $# -ge 1 ]; then
107         echo "$lvlc: $*"
108     else
109         while read line; do
110             echo "$lvlc: " "$line"
111         done
112     fi
113 }
114
115 ## @brief Logs message at TRACE level (6)
116 #
117 # @param msg Message.
118 # @retval 0 It's always returned, even if logging failed.
119 dtrace() {
120     set +x
121     dlog 6 "$@"
122     [ -n "$debug" ] && set -x || :
123 }
124
125 ## @brief Logs message at DEBUG level (5)
126 #
127 # @param msg Message.
128 # @retval 0 It's always returned, even if logging failed.
129 ddebug() {
130     set +x
131     dlog 5 "$@"
132     [ -n "$debug" ] && set -x || :
133 }
134
135 ## @brief Logs message at INFO level (4)
136 #
137 # @param msg Message.
138 # @retval 0 It's always returned, even if logging failed.
139 dinfo() {
140     set +x
141     dlog 4 "$@"
142     [ -n "$debug" ] && set -x || :
143 }
144
145 ## @brief Logs message at WARN level (3)
146 #
147 # @param msg Message.
148 # @retval 0 It's always returned, even if logging failed.
149 dwarn() {
150     set +x
151     dlog 3 "$@"
152     [ -n "$debug" ] && set -x || :
153 }
154
155 ## @brief Logs message at ERROR level (2)
156 #
157 # @param msg Message.
158 # @retval 0 It's always returned, even if logging failed.
159 derror() {
160     set +x
161     dlog 2 "$@"
162     [ -n "$debug" ] && set -x || :
163 }
164
165 ## @brief Logs message at FATAL level (1)
166 #
167 # @param msg Message.
168 # @retval 0 It's always returned, even if logging failed.
169 dfatal() {
170     set +x
171     dlog 1 "$@"
172     [ -n "$debug" ] && set -x || :
173 }
174
175
176 # Generic substring function.  If $2 is in $1, return 0.
177 strstr() { [ "${1#*$2*}" != "$1" ]; }
178
179 # normalize_path <path>
180 # Prints the normalized path, where it removes any duplicated
181 # and trailing slashes.
182 # Example:
183 # $ normalize_path ///test/test//
184 # /test/test
185 normalize_path() {
186     shopt -q -s extglob
187     set -- "${1//+(\/)//}"
188     shopt -q -u extglob
189     echo "${1%/}"
190 }
191
192 # convert_abs_rel <from> <to>
193 # Prints the relative path, when creating a symlink to <to> from <from>.
194 # Example:
195 # $ convert_abs_rel /usr/bin/test /bin/test-2
196 # ../../bin/test-2
197 # $ ln -s $(convert_abs_rel /usr/bin/test /bin/test-2) /usr/bin/test
198 convert_abs_rel() {
199     local __current __absolute __abssize __cursize __newpath
200     local -i __i __level
201
202     set -- "$(normalize_path "$1")" "$(normalize_path "$2")"
203
204     # corner case #1 - self looping link
205     [[ "$1" == "$2" ]] && { echo "${1##*/}"; return; }
206
207     # corner case #2 - own dir link
208     [[ "${1%/*}" == "$2" ]] && { echo "."; return; }
209
210     IFS="/" __current=($1)
211     IFS="/" __absolute=($2)
212
213     __abssize=${#__absolute[@]}
214     __cursize=${#__current[@]}
215
216     while [[ ${__absolute[__level]} == ${__current[__level]} ]]
217     do
218         (( __level++ ))
219         if (( __level > __abssize || __level > __cursize ))
220         then
221             break
222         fi
223     done
224
225     for ((__i = __level; __i < __cursize-1; __i++))
226     do
227         if ((__i > __level))
228         then
229             __newpath=$__newpath"/"
230         fi
231         __newpath=$__newpath".."
232     done
233
234     for ((__i = __level; __i < __abssize; __i++))
235     do
236         if [[ -n $__newpath ]]
237         then
238             __newpath=$__newpath"/"
239         fi
240         __newpath=$__newpath${__absolute[__i]}
241     done
242
243     echo "$__newpath"
244 }
245
246
247 # Install a directory, keeping symlinks as on the original system.
248 # Example: if /lib points to /lib64 on the host, "inst_dir /lib/file"
249 # will create ${initdir}/lib64, ${initdir}/lib64/file,
250 # and a symlink ${initdir}/lib -> lib64.
251 inst_dir() {
252     [[ -e ${initdir}/"$1" ]] && return 0  # already there
253
254     local _dir="$1" _part="${1%/*}" _file
255     while [[ "$_part" != "${_part%/*}" ]] && ! [[ -e "${initdir}/${_part}" ]]; do
256         _dir="$_part $_dir"
257         _part=${_part%/*}
258     done
259
260     # iterate over parent directories
261     for _file in $_dir; do
262         [[ -e "${initdir}/$_file" ]] && continue
263         if [[ -L $_file ]]; then
264             inst_symlink "$_file"
265         else
266             # create directory
267             mkdir -m 0755 -p "${initdir}/$_file" || return 1
268             [[ -e "$_file" ]] && chmod --reference="$_file" "${initdir}/$_file"
269             chmod u+w "${initdir}/$_file"
270         fi
271     done
272 }
273
274 # $1 = file to copy to ramdisk
275 # $2 (optional) Name for the file on the ramdisk
276 # Location of the image dir is assumed to be $initdir
277 # We never overwrite the target if it exists.
278 inst_simple() {
279     [[ -f "$1" ]] || return 1
280     strstr "$1" "/" || return 1
281
282     local _src=$1 target="${2:-$1}"
283     if ! [[ -d ${initdir}/$target ]]; then
284         [[ -e ${initdir}/$target ]] && return 0
285         [[ -L ${initdir}/$target ]] && return 0
286         [[ -d "${initdir}/${target%/*}" ]] || inst_dir "${target%/*}"
287     fi
288     # install checksum files also
289     if [[ -e "${_src%/*}/.${_src##*/}.hmac" ]]; then
290         inst "${_src%/*}/.${_src##*/}.hmac" "${target%/*}/.${target##*/}.hmac"
291     fi
292     ddebug "Installing $_src"
293     cp --sparse=always -pfL "$_src" "${initdir}/$target"
294 }
295
296 # find symlinks linked to given library file
297 # $1 = library file
298 # Function searches for symlinks by stripping version numbers appended to
299 # library filename, checks if it points to the same target and finally
300 # prints the list of symlinks to stdout.
301 #
302 # Example:
303 # rev_lib_symlinks libfoo.so.8.1
304 # output: libfoo.so.8 libfoo.so
305 # (Only if libfoo.so.8 and libfoo.so exists on host system.)
306 rev_lib_symlinks() {
307     [[ ! $1 ]] && return 0
308
309     local fn="$1" orig="$(readlink -f "$1")" links=''
310
311     [[ ${fn} =~ .*\.so\..* ]] || return 1
312
313     until [[ ${fn##*.} == so ]]; do
314         fn="${fn%.*}"
315         [[ -L ${fn} && $(readlink -f "${fn}") == ${orig} ]] && links+=" ${fn}"
316     done
317
318     echo "${links}"
319 }
320
321 # Same as above, but specialized to handle dynamic libraries.
322 # It handles making symlinks according to how the original library
323 # is referenced.
324 inst_library() {
325     local _src="$1" _dest=${2:-$1} _lib _reallib _symlink
326     strstr "$1" "/" || return 1
327     [[ -e $initdir/$_dest ]] && return 0
328     if [[ -L $_src ]]; then
329         # install checksum files also
330         if [[ -e "${_src%/*}/.${_src##*/}.hmac" ]]; then
331             inst "${_src%/*}/.${_src##*/}.hmac" "${_dest%/*}/.${_dest##*/}.hmac"
332         fi
333         _reallib=$(readlink -f "$_src")
334         inst_simple "$_reallib" "$_reallib"
335         inst_dir "${_dest%/*}"
336         [[ -d "${_dest%/*}" ]] && _dest=$(readlink -f "${_dest%/*}")/${_dest##*/}
337         ln -sfn $(convert_abs_rel "${_dest}" "${_reallib}") "${initdir}/${_dest}"
338     else
339         inst_simple "$_src" "$_dest"
340     fi
341
342     # Create additional symlinks.  See rev_symlinks description.
343     for _symlink in $(rev_lib_symlinks $_src) $(rev_lib_symlinks $_reallib); do
344         [[ ! -e $initdir/$_symlink ]] && {
345             ddebug "Creating extra symlink: $_symlink"
346             inst_symlink $_symlink
347         }
348     done
349 }
350
351 # find a binary.  If we were not passed the full path directly,
352 # search in the usual places to find the binary.
353 find_binary() {
354     if [[ -z ${1##/*} ]]; then
355         if [[ -x $1 ]] || { strstr "$1" ".so" && ldd $1 &>/dev/null; };  then
356             echo $1
357             return 0
358         fi
359     fi
360
361     type -P $1
362 }
363
364 # Same as above, but specialized to install binary executables.
365 # Install binary executable, and all shared library dependencies, if any.
366 inst_binary() {
367     local _bin _target
368     _bin=$(find_binary "$1") || return 1
369     _target=${2:-$_bin}
370     [[ -e $initdir/$_target ]] && return 0
371     [[ -L $_bin ]] && inst_symlink $_bin $_target && return 0
372     local _file _line
373     local _so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)'
374     # I love bash!
375     LC_ALL=C ldd "$_bin" 2>/dev/null | while read _line; do
376         [[ $_line = 'not a dynamic executable' ]] && break
377
378         if [[ $_line =~ $_so_regex ]]; then
379             _file=${BASH_REMATCH[1]}
380             [[ -e ${initdir}/$_file ]] && continue
381             inst_library "$_file"
382             continue
383         fi
384
385         if [[ $_line =~ not\ found ]]; then
386             dfatal "Missing a shared library required by $_bin."
387             dfatal "Run \"ldd $_bin\" to find out what it is."
388             dfatal "$_line"
389             dfatal "dracut cannot create an initrd."
390             exit 1
391         fi
392     done
393     inst_simple "$_bin" "$_target"
394 }
395
396 # same as above, except for shell scripts.
397 # If your shell script does not start with shebang, it is not a shell script.
398 inst_script() {
399     local _bin
400     _bin=$(find_binary "$1") || return 1
401     shift
402     local _line _shebang_regex
403     read -r -n 80 _line <"$_bin"
404     # If debug is set, clean unprintable chars to prevent messing up the term
405     [[ $debug ]] && _line=$(echo -n "$_line" | tr -c -d '[:print:][:space:]')
406     _shebang_regex='(#! *)(/[^ ]+).*'
407     [[ $_line =~ $_shebang_regex ]] || return 1
408     inst "${BASH_REMATCH[2]}" && inst_simple "$_bin" "$@"
409 }
410
411 # same as above, but specialized for symlinks
412 inst_symlink() {
413     local _src=$1 _target=${2:-$1} _realsrc
414     strstr "$1" "/" || return 1
415     [[ -L $1 ]] || return 1
416     [[ -L $initdir/$_target ]] && return 0
417     _realsrc=$(readlink -f "$_src")
418     if ! [[ -e $initdir/$_realsrc ]]; then
419         if [[ -d $_realsrc ]]; then
420             inst_dir "$_realsrc"
421         else
422             inst "$_realsrc"
423         fi
424     fi
425     [[ ! -e $initdir/${_target%/*} ]] && inst_dir "${_target%/*}"
426     [[ -d ${_target%/*} ]] && _target=$(readlink -f ${_target%/*})/${_target##*/}
427     ln -sfn $(convert_abs_rel "${_target}" "${_realsrc}") "$initdir/$_target"
428 }
429
430 # attempt to install any programs specified in a udev rule
431 inst_rule_programs() {
432     local _prog _bin
433
434     if grep -qE 'PROGRAM==?"[^ "]+' "$1"; then
435         for _prog in $(grep -E 'PROGRAM==?"[^ "]+' "$1" | sed -r 's/.*PROGRAM==?"([^ "]+).*/\1/'); do
436             if [ -x /lib/udev/$_prog ]; then
437                 _bin=/lib/udev/$_prog
438             else
439                 _bin=$(find_binary "$_prog") || {
440                     dinfo "Skipping program $_prog using in udev rule $(basename $1) as it cannot be found"
441                     continue;
442                 }
443             fi
444
445             #dinfo "Installing $_bin due to it's use in the udev rule $(basename $1)"
446             dracut_install "$_bin"
447         done
448     fi
449 }
450
451 # udev rules always get installed in the same place, so
452 # create a function to install them to make life simpler.
453 inst_rules() {
454     local _target=/etc/udev/rules.d _rule _found
455
456     inst_dir "/lib/udev/rules.d"
457     inst_dir "$_target"
458     for _rule in "$@"; do
459         if [ "${rule#/}" = "$rule" ]; then
460             for r in /lib/udev/rules.d /etc/udev/rules.d; do
461                 if [[ -f $r/$_rule ]]; then
462                     _found="$r/$_rule"
463                     inst_simple "$_found"
464                     inst_rule_programs "$_found"
465                 fi
466             done
467         fi
468         for r in '' ./ $dracutbasedir/rules.d/; do
469             if [[ -f ${r}$_rule ]]; then
470                 _found="${r}$_rule"
471                 inst_simple "$_found" "$_target/${_found##*/}"
472                 inst_rule_programs "$_found"
473             fi
474         done
475         [[ $_found ]] || dinfo "Skipping udev rule: $_rule"
476     done
477 }
478
479 # general purpose installation function
480 # Same args as above.
481 inst() {
482     local _x
483
484     case $# in
485         1) ;;
486         2) [[ ! $initdir && -d $2 ]] && export initdir=$2
487             [[ $initdir = $2 ]] && set $1;;
488         3) [[ -z $initdir ]] && export initdir=$2
489             set $1 $3;;
490         *) dfatal "inst only takes 1 or 2 or 3 arguments"
491             exit 1;;
492     esac
493     for _x in inst_symlink inst_script inst_binary inst_simple; do
494         $_x "$@" && return 0
495     done
496     return 1
497 }
498
499 # install any of listed files
500 #
501 # If first argument is '-d' and second some destination path, first accessible
502 # source is installed into this path, otherwise it will installed in the same
503 # path as source.  If none of listed files was installed, function return 1.
504 # On first successful installation it returns with 0 status.
505 #
506 # Example:
507 #
508 # inst_any -d /bin/foo /bin/bar /bin/baz
509 #
510 # Lets assume that /bin/baz exists, so it will be installed as /bin/foo in
511 # initramfs.
512 inst_any() {
513     local to f
514
515     [[ $1 = '-d' ]] && to="$2" && shift 2
516
517     for f in "$@"; do
518         if [[ -e $f ]]; then
519             [[ $to ]] && inst "$f" "$to" && return 0
520             inst "$f" && return 0
521         fi
522     done
523
524     return 1
525 }
526
527 # dracut_install [-o ] <file> [<file> ... ]
528 # Install <file> to the initramfs image
529 # -o optionally install the <file> and don't fail, if it is not there
530 dracut_install() {
531     local _optional=no
532     if [[ $1 = '-o' ]]; then
533         _optional=yes
534         shift
535     fi
536     while (($# > 0)); do
537         if ! inst "$1" ; then
538             if [[ $_optional = yes ]]; then
539                 dinfo "Skipping program $1 as it cannot be found and is" \
540                     "flagged to be optional"
541             else
542                 dfatal "Failed to install $1"
543                 exit 1
544             fi
545         fi
546         shift
547     done
548 }
549
550
551 # inst_libdir_file [-n <pattern>] <file> [<file>...]
552 # Install a <file> located on a lib directory to the initramfs image
553 # -n <pattern> install non-matching files
554 inst_libdir_file() {
555     if [[ "$1" == "-n" ]]; then
556         local _pattern=$1
557         shift 2
558         for _dir in $libdirs; do
559             for _i in "$@"; do
560                 for _f in "$_dir"/$_i; do
561                     [[ "$_i" =~ $_pattern ]] || continue
562                     [[ -e "$_i" ]] && dracut_install "$_i"
563                 done
564             done
565         done
566     else
567         for _dir in $libdirs; do
568             for _i in "$@"; do
569                 for _f in "$_dir"/$_i; do
570                     [[ -e "$_f" ]] && dracut_install "$_f"
571                 done
572             done
573         done
574     fi
575 }
576
577 do_test() {
578     [[ $UID != "0" ]] && exit 0
579     command -v qemu-kvm &>/dev/null || exit 0
580 # Detect lib paths
581     [[ $libdir ]] || for libdir in /lib64 /lib; do
582         [[ -d $libdir ]] && libdirs+=" $libdir" && break
583     done
584
585     [[ $usrlibdir ]] || for usrlibdir in /usr/lib64 /usr/lib; do
586         [[ -d $usrlibdir ]] && libdirs+=" $usrlibdir" && break
587     done
588
589     import_testdir
590
591     while (($# > 0)); do
592         case $1 in
593             --run)
594                 echo "TEST RUN: $TEST_DESCRIPTION"
595                 test_run
596                 ret=$?
597                 if [ $ret -eq 0 ]; then
598                     echo "TEST RUN: $TEST_DESCRIPTION [OK]"
599                 else
600                     echo "TEST RUN: $TEST_DESCRIPTION [FAILED]"
601                 fi
602                 exit $ret;;
603             --setup)
604                 echo "TEST SETUP: $TEST_DESCRIPTION"
605                 test_setup
606                 exit $?;;
607             --clean)
608                 echo "TEST CLEANUP: $TEST_DESCRIPTION"
609                 test_cleanup
610                 rm -fr "$TESTDIR"
611                 rm -f .testdir
612                 exit $?;;
613             --all)
614                 echo -n "TEST: $TEST_DESCRIPTION ";
615                 (
616                     test_setup && test_run
617                     ret=$?
618                     test_cleanup
619                     rm -fr "$TESTDIR"
620                     rm -f .testdir
621                     exit $ret
622                 ) </dev/null >test.log 2>&1
623                 ret=$?
624                 if [ $ret -eq 0 ]; then
625                     rm test.log
626                     echo "[OK]"
627                 else
628                     echo "[FAILED]"
629                     echo "see $(pwd)/test.log"
630                 fi
631                 exit $ret;;
632             *) break ;;
633         esac
634         shift
635     done
636 }