#!/bin/bash # initscripts functions # # sanitize PATH (will be overridden later when /etc/profile is sourced but is useful for udev) export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" localevars=(LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION LC_ALL) vconsolevars=(KEYMAP KEYMAP_TOGGLE FONT FONT_MAP FONT_UNIMAP) if [[ $1 == "start" ]]; then if [[ $STARTING ]]; then echo "A daemon is starting another daemon; this is unlikely to work as intended." else export STARTING=1 fi fi # width: calc_columns () { STAT_COL=80 if [[ ! -t 1 ]]; then USECOLOR="" elif [[ -t 0 ]]; then # stty will fail when stdin isn't a terminal STAT_COL=$(stty size) # stty gives "rows cols"; strip the rows number, we just want columns STAT_COL=${STAT_COL##* } elif tput cols &>/dev/null; then # is /usr/share/terminfo already mounted, and TERM recognized? STAT_COL=$(tput cols) fi if (( STAT_COL == 0 )); then # if output was 0 (serial console), set default width to 80 STAT_COL=80 USECOLOR="" fi # we use 13 characters for our own stuff STAT_COL=$(( STAT_COL - 13 )) if [[ -t 1 ]]; then SAVE_POSITION="\e[s" RESTORE_POSITION="\e[u" DEL_TEXT="\e[$(( STAT_COL + 4 ))G" else SAVE_POSITION="" RESTORE_POSITION="" DEL_TEXT="" fi } calc_columns # disable colors on broken terminals TERM_COLORS=$(tput colors 2>/dev/null) if (( $? != 3 )); then case $TERM_COLORS in *[!0-9]*) USECOLOR="";; [0-7]) USECOLOR="";; '') USECOLOR="";; esac fi unset TERM_COLORS # clear the TZ envvar, so daemons always respect /etc/localtime unset TZ # sanitize the locale settings unset "${localevars[@]}" parse_envfile() { local file=$1 validkeys=("${@:2}") ret=0 lineno=0 key= val= local -r quotes=$'[\'"]' comments=$'[;#]*' if [[ -z $file ]]; then printf "error: no environment file specified\n" return 1 fi if [[ ! -f $file ]]; then printf "error: cannot parse \`%s': No such file or directory\n" "$file" return 1 fi if [[ ! -r $file ]]; then printf "error: cannot read \`%s': Permission denied\n" "$file" return 1 fi while IFS='=' read -r key val; do (( ++lineno )) # trim whitespace, avoiding usage of a tempfile key=$(echo "$key" | { read -r key; echo "$key"; }) # key must exist and line must not be a comment [[ -z $key || ${key:0:1} = $comments ]] && continue # trim whitespace, strip matching quotes val=$(echo "$val" | { read -r val; echo "$val"; }) [[ ${val:0:1} = $quotes && ${val:(-1)} = "${val:0:1}" ]] && val=${val:1:(-1)} if [[ -z $val ]]; then printf "error: found key \`%s' without value on line %s of %s\n" \ "$key" "$lineno" "$file" (( ++ret )) continue fi # ignore invalid keys if we have a list of valid ones if (( ${#validkeys[*]} )) && ! in_array "$key" "${validkeys[@]}"; then continue fi export "$key=$val" || (( ++ret )) done <"$file" return $ret } # functions: deltext() { printf "${DEL_TEXT}" } printhl() { printf "${C_OTHER}${PREFIX_HL} ${C_H1}${1}${C_CLEAR} \n" } printsep() { printf "\n${C_SEPARATOR} ------------------------------\n" } stat_bkgd() { printf "${C_OTHER}${PREFIX_REG} ${C_MAIN}${1}${C_CLEAR} " deltext printf " ${C_OTHER}[${C_BKGD}BKGD${C_OTHER}]${C_CLEAR} \n" } stat_busy() { printf "${C_OTHER}${PREFIX_REG} ${C_MAIN}${1}${C_CLEAR} " printf "${SAVE_POSITION}" deltext printf " ${C_OTHER}[${C_BUSY}BUSY${C_OTHER}]${C_CLEAR} " } stat_append() { printf "${RESTORE_POSITION}" printf -- "${C_MAIN}${1}${C_CLEAR}" printf "${SAVE_POSITION}" } stat_done() { deltext printf " ${C_OTHER}[${C_DONE}DONE${C_OTHER}]${C_CLEAR} \n" } stat_fail() { deltext printf " ${C_OTHER}[${C_FAIL}FAIL${C_OTHER}]${C_CLEAR} \n" } stat_die() { stat_fail exit ${1:-1} } status() { local quiet case $1 in -q) quiet=1 ;;& -v) # NOOP: supported for backwards compat shift ;; esac stat_busy "$1" shift if (( quiet )); then "$@" &>/dev/null else "$@" fi local ret=$? (( ret == 0 )) && stat_done || stat_fail return $ret } # usage : in_array( $needle, $haystack ) # return : 0 - found # 1 - not found in_array() { local needle=$1; shift local item for item; do [[ $item = "${needle}" ]] && return 0 done return 1 # Not Found } # daemons: add_daemon() { [[ -d /run/daemons ]] || mkdir -p /run/daemons >| /run/daemons/"$1" } rm_daemon() { rm -f /run/daemons/"$1" } ck_daemon() { [[ ! -f /run/daemons/$1 ]] } # Check if $1 is a valid daemon name have_daemon() { [[ -f /etc/rc.d/$1 && -x /etc/rc.d/$1 ]] } # Check if $1 is started at boot ck_autostart() { local daemon for daemon in "${DAEMONS[@]}"; do [[ $1 = "${daemon#@}" ]] && return 1 done return 0 } start_daemon() { have_daemon "$1" && /etc/rc.d/"$1" start } start_daemon_bkgd() { stat_bkgd "Starting $1" (start_daemon "$1") >/dev/null & } stop_daemon() { have_daemon "$1" && /etc/rc.d/"$1" stop } # Status functions status_started() { deltext echo -ne "$C_OTHER[${C_STRT}STARTED$C_OTHER]$C_CLEAR " } status_stopped() { deltext echo -ne "$C_OTHER[${C_STRT}STOPPED$C_OTHER]$C_CLEAR " } ck_status() { ! ck_daemon "$1" && status_started || status_stopped } # Return PID of $1 get_pid() { pidof -o %PPID $1 || return 1 } # Check if PID-file $1 is still the active PID-file for command $2 ck_pidfile() { if [[ -f $1 ]]; then local fpid ppid read -r fpid <"$1" ppid=$(get_pid "$2") [[ $fpid = "${ppid}" ]] && return 0 fi return 1 } # PIDs to be omitted by killall5 declare -a omit_pids add_omit_pids() { omit_pids+=( $@ ) } # Stop all daemons # This function should *never* ever perform any other actions beside calling stop_daemon()! # It might be used by a splash system etc. to get a list of daemons to be stopped. stop_all_daemons() { # Find daemons NOT in the DAEMONS array. Shut these down first local daemon for daemon in /run/daemons/*; do [[ -f $daemon ]] || continue daemon=${daemon##*/} ck_autostart "$daemon" && stop_daemon "$daemon" done # Shutdown daemons in reverse order local i daemon for (( i=${#DAEMONS[@]}-1; i>=0; i-- )); do [[ ${DAEMONS[i]} = '!'* ]] && continue daemon=${DAEMONS[i]#@} ck_daemon "$daemon" || stop_daemon "$daemon" done } # $1 - signal # $2 - iterations kill_all_wait() { # Send SIGTERM/SIGKILL all processes and wait until killall5 # reports all done or timeout. # Unfortunately killall5 does not support the 0 signal, so just # use SIGCONT for checking (which should be ignored). local i killall5 -${1} ${omit_pids[@]/#/-o } &>/dev/null for (( i=0; i<${2}; i++ )); do sleep .25 # 1/4 second # sending SIGCONT to processes to check if they are there killall5 -18 ${omit_pids[@]/#/-o } &>/dev/null if (( $? == 2 )); then return 0 fi done return 1 } kill_all() { stat_busy "Sending SIGTERM to processes" kill_all_wait 15 40 if (( $? == 0 )); then stat_done else stat_fail status "Sending SIGKILL to processes" kill_all_wait 9 60 fi } print_welcome() { # see os-release(5) . /etc/os-release echo " " printhl "${PRETTY_NAME}\n" printhl "${C_H2}${HOME_URL}" printsep } load_modules() { local rc=0 /usr/lib/systemd/systemd-modules-load &>/dev/null rc=$? if (( ${#MODULES[*]} )); then modprobe -ab "${MODULES[@]}" (( rc += $? )) fi return $rc } # Start/trigger udev, load MODULES, and settle udev udevd_modprobe() { # $1 = where we are being called from. # This is used to determine which hooks to run. status "Starting udev daemon" /usr/lib/systemd/systemd-udevd --daemon run_hook "$1_udevlaunched" stat_busy "Triggering udev uevents" udevadm trigger --action=add --type=subsystems udevadm trigger --action=add --type=devices stat_done # Load modules from the MODULES array and modules-load.d status "Loading user-specified modules" load_modules status "Waiting for udev uevents to be processed" \ udevadm settle run_hook "$1_udevsettled" # in case loading a module changed the display mode calc_columns } activate_vgs() { [[ $USELVM = [yY][eE][sS] && -x $(type -P lvm) && -d /sys/block ]] || return 0 stat_busy "Activating LVM2 groups" vgchange --sysinit -a y >/dev/null (( $? == 0 )) && stat_done || stat_fail } do_unlock_legacy() { # $1 = requested name # $2 = source device # $3 = password # $4 = options printf "${C_FAIL}Using legacy crypttab format. This will stop working in the future. See crypttab(5).${C_CLEAR}\n" local open=create a=$1 b=$2 failed=0 # Ordering of options is different if you are using LUKS vs. not. # Use ugly swizzling to deal with it. # isLuks only gives an exit code but no output to stdout or stderr. if cryptsetup isLuks "$2" 2>/dev/null; then open=luksOpen a=$2 b=$1 fi case $3 in SWAP) local _overwriteokay=0 if [[ -b $2 && -r $2 ]]; then # This is DANGEROUS! If there is any known file system, # partition table, RAID, or LVM volume on the device, # we don't overwrite it. # # 'blkid' returns 2 if no valid signature has been found. # Only in this case should we allow overwriting the device. # # This sanity check _should_ be sufficient, but it might not. # This may cause data loss if it is not used carefully. blkid -p "$2" &>/dev/null (( $? == 2 )) && _overwriteokay=1 fi if (( _overwriteokay == 0 )); then false elif cryptsetup -d /dev/urandom $4 $open "$a" "$b" >/dev/null; then printf "creating swapspace..\n" mkswap -f -L $1 /dev/mapper/$1 >/dev/null fi;; ASK) printf "\nOpening '$1' volume:\n" cryptsetup $4 $open "$a" "$b" < /dev/console;; /dev*) local ckdev=${3%%:*} local cka=${3#*:} local ckb=${cka#*:} local cka=${cka%:*} local ckfile=/dev/ckfile local ckdir=/dev/ckdir case ${cka} in *[!0-9]*) # Use a file on the device # cka is not numeric: cka=filesystem, ckb=path mkdir ${ckdir} mount -r -t ${cka} ${ckdev} ${ckdir} dd if=${ckdir}/${ckb} of=${ckfile} >/dev/null 2>&1 umount ${ckdir} rmdir ${ckdir};; *) # Read raw data from the block device # cka is numeric: cka=offset, ckb=length dd if=${ckdev} of=${ckfile} bs=1 skip=${cka} count=${ckb} >/dev/null 2>&1;; esac cryptsetup -d ${ckfile} $4 $open "$a" "$b" >/dev/null dd if=/dev/urandom of=${ckfile} bs=1 count=$(stat -c %s ${ckfile}) conv=notrunc >/dev/null 2>&1 rm ${ckfile};; /*) cryptsetup -d "$3" $4 $open "$a" "$b" >/dev/null;; *) echo "$3" | cryptsetup $4 $open "$a" "$b" >/dev/null;; esac return $? } do_unlock_systemd() { local name=$1 device=$2 password=$3 options=$4 failed=0 if ! /usr/lib/systemd/systemd-cryptsetup attach "$name" "$device" "$password" $options; then failed=1 else options=${options//,/ } if in_array swap ${options[@]}; then # create swap on the device only if no fs signature exists blkid -p "$2" &>/dev/null if (( $? != 2 )) || ! mkswap -f /dev/mapper/$name >/dev/null; then failed=1 fi elif in_array tmp ${options[@]}; then # create fs on the device only if no fs signature exists blkid -p "$2" &>/dev/null if (( $? != 2 )) || ! mke2fs /dev/mapper/$name >/dev/null; then failed=1 fi fi fi return $failed } do_unlock() { local name=$1 device=$2 password=$3 options=$4 printf "${C_MAIN}Unlocking $1${C_CLEAR}\n" if [[ ${options:0:2} =~ -. ]]; then do_unlock_legacy "$name" "$device" "$password" "$options" return $? fi case $password in ASK|SWAP) do_unlock_legacy "$name" "$device" "$password" "$options" ;; /dev/*) if [[ ${password##*:} == $password ]]; then do_unlock_systemd "$name" "$device" "$password" "$options" else do_unlock_legacy "$name" "$device" "$password" "$options" fi ;; /*|none|-) do_unlock_systemd "$name" "$device" "$password" "$options" ;; *) do_unlock_legacy "$name" "$device" "$password" "$options" ;; esac failed=$? if (( $failed )); then printf "${C_FAIL}Unlocking of $1 failed.${C_CLEAR}\n" fi return $? } do_lock() { status "Detaching encrypted device ${1}" /usr/lib/systemd/systemd-cryptsetup detach "$1" >/dev/null } read_crypttab() { # $1 = function to call with the split out line from the crypttab local line nspo failed=0 while read line <&3; do [[ $line && $line != '#'* ]] || continue eval nspo=("${line%#*}") if $1 "${nspo[0]}" "${nspo[1]}" "${nspo[2]}" "${nspo[*]:3}"; then crypto_unlocked=1 else failed=1 fi done 3< /etc/crypttab return $failed } set_timezone() { local tz=$1 zonefile=/usr/share/zoneinfo/$1 [[ $tz ]] || return 1 if [[ ! -e $zonefile ]]; then printf "error: \`%s' is not a valid time zone\n" "$tz" return 1 fi if [[ -L /etc/localtime && /etc/localtime -ef $zonefile ]]; then return 0 else ln -sf "/usr/share/zoneinfo/$tz" /etc/localtime fi } # Filesystem functions # These can be overridden/reused for customizations like shutdown/loop-fsck. NETFS="nfs,nfs4,smbfs,cifs,codafs,ncpfs,shfs,fuse,fuseblk,glusterfs,davfs,fuse.glusterfs" # Check local filesystems fsck_all() { if [[ -f /forcefsck ]] || in_array forcefsck $(< /proc/cmdline); then FORCEFSCK="-f" elif [[ -f /fastboot ]] || in_array fastboot $(< /proc/cmdline); then return 0 elif [[ -e /run/initramfs/root-fsck ]]; then IGNORE_MOUNTED="-M" fi fsck -A -T -C${FSCK_FD} -a -t no${NETFS//,/,no},noopts=_netdev ${IGNORE_MOUNTED} -- ${FORCEFSCK} } # Single-user login and/or automatic reboot after fsck (if needed) fsck_reboot() { # $1 = exit code returned by fsck # Ignore conditions 'FS errors corrected' and 'Cancelled by the user' (( ($1 | 33) == 33 )) && return 0 if (( $1 & 2 )); then echo echo "********************** REBOOT REQUIRED *********************" echo "* *" echo "* The system will be rebooted automatically in 15 seconds. *" echo "* *" echo "************************************************************" echo sleep 15 else echo echo "***************** FILESYSTEM CHECK FAILED ****************" echo "* *" echo "* Please repair manually and reboot. Note that the root *" echo "* file system is currently mounted read-only. To remount *" echo "* it read-write, type: mount -o remount,rw / *" echo "* When you exit the maintenance shell, the system will *" echo "* reboot automatically. *" echo "* *" echo "************************************************************" echo sulogin -p fi echo "Automatic reboot in progress..." umount -a mount -o remount,ro / reboot -f exit 0 } mount_all() { mount -a -t "no${NETFS//,/,no}" -O no_netdev } umount_all() { # $1: restrict to fstype findmnt -mrunRo TARGET,FSTYPE,OPTIONS / | { while read -r target fstype options; do # match only targeted fstypes if [[ $1 && $1 != "$fstype" ]]; then continue fi # do not unmount API filesystems if [[ $target = /@(proc|sys|run|dev|dev/pts) ]]; then continue fi # avoid networked devices IFS=, read -ra opts <<< "$options" if in_array _netdev "${opts[@]}"; then continue fi mounts=("$target" "${mounts[@]}") done if (( ${#mounts[*]} )); then umount -r "${mounts[@]}" fi } } remove_leftover() { status 'Removing leftover files' systemd-tmpfiles --create --remove --clean } bootlogd_stop() { [[ -f /run/bootlogd.pid ]] || return 0 touch /var/log/boot kill $(< /run/bootlogd.pid) rm -f /run/bootlogd.pid } ############################### # Custom hooks in initscripts # ############################### # Hooks can be used to include custom code in various places in the rc.* scripts # # Define a hook function in a functions.d file using: # function_name() { # ... # } # add_hook hook_name function_name # It is allowed to register several hook functions for the same hook # Is is also allowed to register the same hook function for several hooks # # Currently, the following hooks exist: # sysinit_start: at the beginning of rc.sysinit # multi_start: at the beginning of rc.multi # single_start: at the beginning of rc.single # shutdown_start: at the beginning of rc.shutdown # sysinit_end: at the end of rc.sysinit # multi_end: at the end of rc.multi # single_end: at the end of rc.single # sysinit_udevlaunched: after udev has been launched in rc.sysinit # single_udevlaunched: after udev has been launched in rc.single # sysinit_udevsettled: after uevents have settled in rc.sysinit # single_udevsettled: after uevents have settled in rc.single # sysinit_premount: before local filesystems are mounted, but after root is mounted read-write in rc.sysinit # sysinit_postmount: after local filesystems are mounted # shutdown_prekillall: before all processes are being killed in rc.shutdown # single_prekillall: before all processes are being killed in rc.single # shutdown_postkillall: after all processes have been killed in rc.shutdown # single_postkillall: after all processes have been killed in rc.single # shutdown_preumount: after last filesystem write, but before filesystems are unmounted # shutdown_postumount: after filesystems are unmounted # shutdown_poweroff: directly before powering off in rc.shutdown # # Declare add_hook and run_hook as read-only to prevent overwriting them. # Too bad we cannot do the same thing with hook_funcs if (( RC_FUNCTIONS_HOOK_FUNCS_DEFINED != 1 )); then declare -A hook_funcs add_hook() { [[ $1 && $2 ]] || return 1 hook_funcs[$1]+=" $2" } run_hook() { [[ $1 ]] || return 1 local func for func in ${hook_funcs["$1"]}; do "${func}" done } declare -fr add_hook run_hook declare -r RC_FUNCTIONS_HOOK_FUNCS_DEFINED=1 fi if [[ $DAEMON_LOCALE != [nN][oO] ]]; then export LANG=${LOCALE:-C} if [[ -r /etc/locale.conf ]]; then parse_envfile /etc/locale.conf "${localevars[@]}" fi else export LANG=C fi # set colors if [[ $USECOLOR != [nN][oO] ]]; then if tput setaf 0 &>/dev/null; then C_CLEAR=$(tput sgr0) # clear text C_MAIN=${C_CLEAR}$(tput bold) # main text C_OTHER=${C_MAIN}$(tput setaf 4) # prefix & brackets C_SEPARATOR=${C_MAIN}$(tput setaf 0) # separator C_BUSY=${C_CLEAR}$(tput setaf 6) # busy C_FAIL=${C_MAIN}$(tput setaf 1) # failed C_DONE=${C_MAIN} # completed C_BKGD=${C_MAIN}$(tput setaf 5) # backgrounded C_H1=${C_MAIN} # highlight text 1 C_H2=${C_MAIN}$(tput setaf 6) # highlight text 2 else C_CLEAR="\e[m" # clear text C_MAIN="\e[;1m" # main text C_OTHER="\e[1;34m" # prefix & brackets C_SEPARATOR="\e[1;30m" # separator C_BUSY="\e[;36m" # busy C_FAIL="\e[1;31m" # failed C_DONE=${C_MAIN} # completed C_BKGD="\e[1;35m" # backgrounded C_H1=${C_MAIN} # highlight text 1 C_H2="\e[1;36m" # highlight text 2 fi fi # prefixes: PREFIX_REG="::" PREFIX_HL=" >" # Source additional functions at the end to allow overrides for f in /etc/rc.d/functions.d/*; do [[ -e $f ]] && . "$f" done # End of file # vim: set ts=2 sw=2 noet: