diff options
Diffstat (limited to 'debian/functions')
-rw-r--r-- | debian/functions | 686 |
1 files changed, 686 insertions, 0 deletions
diff --git a/debian/functions b/debian/functions new file mode 100644 index 0000000..917abad --- /dev/null +++ b/debian/functions @@ -0,0 +1,686 @@ +if [ "${0#/usr/share/initramfs-tools/hooks/}" != "$0" ] || + [ "${0#/etc/initramfs-tools/hooks/}" != "$0" ]; then + # called from an initramfs-tools hook script + TABFILE="$DESTDIR/cryptroot/crypttab" +elif [ "${0#/scripts/}" != "$0" ]; then + # called at initramfs stage from a boot script + TABFILE="/cryptroot/crypttab" + CRYPTROOT_COUNT_FILE="/run/cryptroot.initrd.cnt" +else + TABFILE="${TABFILE-/etc/crypttab}" +fi +export DM_DEFAULT_NAME_MANGLING_MODE=hex # for dmsetup(8) + +# Logging helpers. Send the argument list to plymouth(1), or fold it +# and print it to the standard error. +cryptsetup_message() { + local IFS=' ' + if [ "${0#/scripts/}" != "$0" ] && [ -x /bin/plymouth ] && plymouth --ping; then + plymouth message --text="cryptsetup: $*" + elif [ ${#*} -lt 70 ]; then + echo "cryptsetup: $*" >&2 + else + # use busybox's fold(1) and sed(1) at initramfs stage + echo "cryptsetup: $*" | fold -s | sed '1! s/^/ /' >&2 + fi + return 0 +} + +# crypttab_parse_options([--export], [--quiet], [--missing-path={ignore|warn|fail}]) +# Parse $_CRYPTTAB_OPTIONS, a comma-separated option string from the +# crypttab(5) 4th column, and sets corresponding variables +# CRYPTTAB_OPTION_<option>=<value> (which are added to the environment +# if --export is set). If --path-exists isn't set to "ignore" (the +# default), then options taking a file name, such as header=<path>, +# need to point to an existing path, otherwise a warning is printed; +# and an error is raised if the value is set to "fail". +# For error and warning messages, CRYPTTAB_NAME, (resp. CRYPTTAB_KEY) +# should be set to the (unmangled) mapped device name (resp. key +# file). +# Moreover CRYPTTAB_TYPE is set the device type. +# Return 1 on parsing error, 0 otherwise (incl. if unknown options +# were encountered). +crypttab_parse_options() { + local quiet="n" export="n" missing_path="ignore" + while [ $# -gt 0 ]; do + case "$1" in + --quiet) quiet="y";; + --export) export="y";; + --missing-path=*) missing_path="${1#--missing-path=}";; + *) cryptsetup_message "WARNING: crypttab_parse_options(): unknown option $1" + esac + shift + done + + local IFS=',' x OPTION VALUE + CRYPTTAB_TYPE="" + unset -v CRYPTTAB_OPTION_cipher \ + CRYPTTAB_OPTION_size \ + CRYPTTAB_OPTION_sector_size \ + CRYPTTAB_OPTION_hash \ + CRYPTTAB_OPTION_offset \ + CRYPTTAB_OPTION_skip \ + CRYPTTAB_OPTION_verify \ + CRYPTTAB_OPTION_readonly \ + CRYPTTAB_OPTION_discard \ + CRYPTTAB_OPTION_plain \ + CRYPTTAB_OPTION_luks \ + CRYPTTAB_OPTION_tcrypt \ + CRYPTTAB_OPTION_veracrypt \ + CRYPTTAB_OPTION_bitlk \ + CRYPTTAB_OPTION_fvault2 \ + CRYPTTAB_OPTION_swap \ + CRYPTTAB_OPTION_tmp \ + CRYPTTAB_OPTION_check \ + CRYPTTAB_OPTION_checkargs \ + CRYPTTAB_OPTION_tries \ + CRYPTTAB_OPTION_initramfs \ + CRYPTTAB_OPTION_noearly \ + CRYPTTAB_OPTION_noauto \ + CRYPTTAB_OPTION_loud \ + CRYPTTAB_OPTION_quiet \ + CRYPTTAB_OPTION_keyscript \ + CRYPTTAB_OPTION_keyslot \ + CRYPTTAB_OPTION_header \ + CRYPTTAB_OPTION_tcrypthidden \ + CRYPTTAB_OPTION_same_cpu_crypt \ + CRYPTTAB_OPTION_submit_from_crypt_cpus \ + CRYPTTAB_OPTION_no_read_workqueue \ + CRYPTTAB_OPTION_no_write_workqueue + # use $_CRYPTTAB_OPTIONS not $CRYPTTAB_OPTIONS as options values may + # contain '\054' which is decoded to ',' in the latter + for x in $_CRYPTTAB_OPTIONS; do + OPTION="${x%%=*}" + VALUE="${x#*=}" + if [ "$x" = "$OPTION" ]; then + unset -v VALUE + else + VALUE="$(printf '%b' "$VALUE")" + fi + if ! crypttab_validate_option; then + if [ "$quiet" = "n" ]; then + cryptsetup_message "ERROR: $CRYPTTAB_NAME: invalid value for '${x%%=*}' option, skipping" + fi + return 1 + elif [ -z "${OPTION+x}" ]; then + continue + fi + if [ "$export" = "y" ]; then + export "CRYPTTAB_OPTION_$OPTION"="${VALUE-yes}" + else + eval "CRYPTTAB_OPTION_$OPTION"='${VALUE-yes}' + fi + done + IFS=" " + + if ! _get_crypt_type; then # set CRYPTTAB_TYPE to the type of crypt device + CRYPTTAB_TYPE="plain" + if [ "$quiet" = "n" ]; then + cryptsetup_message "WARNING: $CRYPTTAB_NAME: couldn't determine device type," \ + "assuming default ($CRYPTTAB_TYPE)." + fi + fi + + if [ "$quiet" = "n" ] && [ -n "${CRYPTTAB_OPTION_header+x}" ] && [ "$CRYPTTAB_TYPE" != "luks" ]; then + cryptsetup_message "WARNING: $CRYPTTAB_NAME: Headers are only supported for LUKS devices." + fi + if [ "$CRYPTTAB_TYPE" = "plain" ]; then + # the compiled-in default for these are subject to change + options='cipher size' + if [ -n "${CRYPTTAB_OPTION_keyscript+x}" ] || [ "$CRYPTTAB_KEY" = "none" ]; then + options="$options hash" # --hash is being ignored in plain mode with keyfile specified + fi + for o in $options; do + if [ "$quiet" = "n" ] && eval [ -z "\${CRYPTTAB_OPTION_$o+x}" ]; then + cryptsetup_message "WARNING: Option '$o' missing in crypttab for plain dm-crypt" \ + "mapping $CRYPTTAB_NAME. Please read /usr/share/doc/cryptsetup-initramfs/README.initramfs.gz and" \ + "add the correct '$o' option to your crypttab(5)." + fi + done + fi +} + +# crypttab_validate_option() +# Validate $OPTION=$VALUE (or flag $OPTION if VALUE is unset). return +# 1 on error, unsets OPTION for unknown or useless options. +crypttab_validate_option() { + # option aliases + case "$OPTION" in + read-only) OPTION="readonly";; + key-slot) OPTION="keyslot";; + tcrypt-hidden) OPTION="tcrypthidden";; + tcrypt-veracrypt) OPTION="veracrypt";; + esac + + # sanitize the option name so CRYPTTAB_OPTION_$OPTION is a valid variable name + local o="$OPTION" + case "$o" in + keyfile-offset) OPTION="keyfile_offset";; + keyfile-size) OPTION="keyfile_size";; + sector-size) OPTION="sector_size";; + same-cpu-crypt) OPTION="same_cpu_crypt";; + submit-from-crypt-cpus) OPTION="submit_from_crypt_cpus";; + no-read-workqueue) OPTION="no_read_workqueue";; + no-write-workqueue) OPTION="no_write_workqueue";; + esac + + case "$o" in + # value must be a non-empty string + cipher|hash) + [ -n "${VALUE:+x}" ] || return 1 + ;; + # value must be a non-empty string, and an existing path if --missing-path is set + header) + [ -n "${VALUE:+x}" ] || return 1 + if [ "$missing_path" != "ignore" ]; then + if [ ! -e "$VALUE" ]; then + cryptsetup_message "WARNING: $CRYPTTAB_NAME: $VALUE does not exist"; + [ "$missing_path" = "warn" ] || return 1 + fi + fi + ;; + # numeric options >0 + size|keyfile-size|sector-size) + if ! printf '%s' "${VALUE-}" | grep -Exq "0*[1-9][0-9]*"; then + return 1 + fi + ;; + # numeric options >=0 + offset|skip|tries|keyslot|keyfile-offset) + if ! printf '%s' "${VALUE-}" | grep -Exq "[0-9]+"; then + return 1 + fi + ;; + tmp) + if [ -z "${VALUE+x}" ]; then + VALUE="ext4" # 'tmp flag' + elif [ -z "$VALUE" ]; then + return 1 + fi + ;; + check) + if [ -z "${VALUE+x}" ]; then + if [ -n "${CRYPTDISKS_CHECK-}" ]; then + VALUE="$CRYPTDISKS_CHECK" + else + unset -v OPTION + return 0 + fi + fi + if [ "${VALUE#/}" = "$VALUE" ]; then + VALUE="/lib/cryptsetup/checks/$VALUE" + fi + if [ ! -x "$VALUE" ] || [ ! -f "$VALUE" ]; then + return 1 + fi + ;; + checkargs) + [ -n "${VALUE+x}" ] || return 1 # must have a value (possibly empty) + ;; + keyscript) + [ -n "${VALUE:+x}" ] || return 1 # must have a value + if [ "${VALUE#/}" = "$VALUE" ]; then + VALUE="/lib/cryptsetup/scripts/$VALUE" + fi + if [ ! -x "$VALUE" ] || [ ! -f "$VALUE" ]; then + return 1 + fi + ;; + # and now the flags + verify) ;; + loud) ;; + quiet) ;; + initramfs) ;; + noearly) ;; + noauto) ;; + readonly) ;; + discard) ;; + plain) ;; + luks) ;; + swap) ;; + tcrypt) ;; + veracrypt) ;; + tcrypthidden) ;; + bitlk) ;; + fvault2) ;; + same-cpu-crypt) ;; + submit-from-crypt-cpus) ;; + no-read-workqueue) ;; + no-write-workqueue) ;; + *) + if [ "${quiet:-n}" = "n" ]; then + cryptsetup_message "WARNING: $CRYPTTAB_NAME: ignoring unknown option '$o'"; + fi + unset -v OPTION + ;; + esac +} + +# crypttab_resolve_source() +# Resolve the CRYPTTAB_SOURCE variable, containing value of the second +# field of a crypttab(5)-like file. +# On error (non-existing source), CRYPTTAB_SOURCE is not changed and 1 +# is returned. +crypttab_resolve_source() { + # return immediately if source is a regular file + [ ! -f "$CRYPTTAB_SOURCE" ] || return 0 + # otherwise resolve the block device specification + local dev="$CRYPTTAB_SOURCE" + dev="$(_resolve_device_spec "$dev")" && CRYPTTAB_SOURCE="$dev" || return 1 +} + +# run_keyscript($tried_count) +# exec()'ute `$CRYPTTAB_OPTION_keyscript "$CRYPTTAB_KEY"`. +# If $CRYPTTAB_OPTION_keyscript is unset or null and $CRYPTTAB_KEY is +# "none" (meaning the passphrase is to be read interactively from the +# console), then use `/lib/cryptsetup/askpass` as keyscript with a +# suitable prompt message instead. +# Since the shell process is replaced with the $CRYPTTAB_OPTION_keyscript +# program, run_keyscript() must be used on the left-hand side of a +# pipe, or similar. +run_keyscript() { + local keyscript keyscriptarg="$CRYPTTAB_KEY" + export CRYPTTAB_NAME CRYPTTAB_SOURCE CRYPTTAB_OPTIONS + export _CRYPTTAB_NAME _CRYPTTAB_SOURCE _CRYPTTAB_OPTIONS + export CRYPTTAB_TRIED="$1" + + if [ -n "${CRYPTTAB_OPTION_keyscript+x}" ] && \ + [ "$CRYPTTAB_OPTION_keyscript" != "/lib/cryptsetup/askpass" ]; then + # 'keyscript' option is present: export its argument as $CRYPTTAB_KEY + export CRYPTTAB_KEY _CRYPTTAB_KEY + keyscript="$CRYPTTAB_OPTION_keyscript" + elif [ "$keyscriptarg" = "none" ]; then + # don't export the prompt message as CRYPTTAB_KEY + keyscript="/lib/cryptsetup/askpass" + keyscriptarg="Please unlock disk $CRYPTTAB_NAME: " + fi + + exec "$keyscript" "$keyscriptarg" +} + +# _get_crypt_type() +# Set CRYPTTAB_TYPE to the mapping type, depending on its +# $CRYPTTAB_OPTION_<option> values +# Return a non-zero status if the mapping couldn't be determined +_get_crypt_type() { + local s="$CRYPTTAB_SOURCE" t="" blk_t + + if [ "${CRYPTTAB_OPTION_luks-}" = "yes" ]; then + t="luks" + elif [ "${CRYPTTAB_OPTION_tcrypt-}" = "yes" ]; then + t="tcrypt" + elif [ "${CRYPTTAB_OPTION_plain-}" = "yes" ]; then + t="plain" + elif [ "${CRYPTTAB_OPTION_bitlk-}" = "yes" ]; then + t="bitlk" + elif [ "${CRYPTTAB_OPTION_fvault2-}" = "yes" ]; then + t="fvault2" + elif [ -n "${CRYPTTAB_OPTION_header+x}" ]; then + # detached headers are only supported for LUKS devices + if [ -e "$CRYPTTAB_OPTION_header" ] && /sbin/cryptsetup isLuks -- "$CRYPTTAB_OPTION_header"; then + t="luks" + fi + elif [ -f "$s" ] || s="$(_resolve_device_spec "$CRYPTTAB_SOURCE")"; then + if /sbin/cryptsetup isLuks -- "$s"; then + t="luks" + elif blk_t="$(blkid -s TYPE -o value -- "$s")" && [ "$blk_t" = "BitLocker" ]; then + t="bitlk" + fi + fi + + [ -n "$t" ] || return 1 + CRYPTTAB_TYPE="$t" +} + +# unlock_mapping([$keyfile]) +# Run cryptsetup(8) with suitable options and arguments to unlock +# $CRYPTTAB_SOURCE and setup dm-crypt managed device-mapper mapping +# $CRYPTTAB_NAME. +unlock_mapping() { + local keyfile="${1:--}" + + if [ "$CRYPTTAB_TYPE" = "luks" ] || [ "$CRYPTTAB_TYPE" = "tcrypt" ]; then + # ignored for LUKS and TCRYPT devices + unset -v CRYPTTAB_OPTION_cipher \ + CRYPTTAB_OPTION_size \ + CRYPTTAB_OPTION_hash \ + CRYPTTAB_OPTION_offset \ + CRYPTTAB_OPTION_skip + fi + if [ "$CRYPTTAB_TYPE" = "plain" ] || [ "$CRYPTTAB_TYPE" = "tcrypt" ]; then + unset -v CRYPTTAB_OPTION_keyfile_size + fi + if [ "$CRYPTTAB_TYPE" = "tcrypt" ]; then + # ignored for TCRYPT devices + unset -v CRYPTTAB_OPTION_keyfile_offset + else + # ignored for non-TCRYPT devices + unset -v CRYPTTAB_OPTION_veracrypt CRYPTTAB_OPTION_tcrypthidden + fi + + if [ "$CRYPTTAB_TYPE" != "luks" ]; then + # ignored for non-LUKS devices + unset -v CRYPTTAB_OPTION_keyslot + fi + + /sbin/cryptsetup -T1 \ + ${CRYPTTAB_OPTION_header:+--header="$CRYPTTAB_OPTION_header"} \ + ${CRYPTTAB_OPTION_cipher:+--cipher="$CRYPTTAB_OPTION_cipher"} \ + ${CRYPTTAB_OPTION_size:+--key-size="$CRYPTTAB_OPTION_size"} \ + ${CRYPTTAB_OPTION_sector_size:+--sector-size="$CRYPTTAB_OPTION_sector_size"} \ + ${CRYPTTAB_OPTION_hash:+--hash="$CRYPTTAB_OPTION_hash"} \ + ${CRYPTTAB_OPTION_offset:+--offset="$CRYPTTAB_OPTION_offset"} \ + ${CRYPTTAB_OPTION_skip:+--skip="$CRYPTTAB_OPTION_skip"} \ + ${CRYPTTAB_OPTION_verify:+--verify-passphrase} \ + ${CRYPTTAB_OPTION_readonly:+--readonly} \ + ${CRYPTTAB_OPTION_discard:+--allow-discards} \ + ${CRYPTTAB_OPTION_veracrypt:+--veracrypt} \ + ${CRYPTTAB_OPTION_keyslot:+--key-slot="$CRYPTTAB_OPTION_keyslot"} \ + ${CRYPTTAB_OPTION_tcrypthidden:+--tcrypt-hidden} \ + ${CRYPTTAB_OPTION_keyfile_size:+--keyfile-size="$CRYPTTAB_OPTION_keyfile_size"} \ + ${CRYPTTAB_OPTION_keyfile_offset:+--keyfile-offset="$CRYPTTAB_OPTION_keyfile_offset"} \ + ${CRYPTTAB_OPTION_same_cpu_crypt:+--perf-same_cpu_crypt} \ + ${CRYPTTAB_OPTION_submit_from_crypt_cpus:+--perf-submit_from_crypt_cpus} \ + ${CRYPTTAB_OPTION_no_read_workqueue:+--perf-no_read_workqueue} \ + ${CRYPTTAB_OPTION_no_write_workqueue:+--perf-no_write_workqueue} \ + --type="$CRYPTTAB_TYPE" --key-file="$keyfile" \ + open -- "$CRYPTTAB_SOURCE" "$CRYPTTAB_NAME" +} + +# resume_mapping([$keyfile]) +# Run cryptsetup(8) with suitable options and arguments to resume +# $CRYPTTAB_NAME. +resume_mapping() { + local keyfile="${1:--}" + + /sbin/cryptsetup -T1 \ + ${CRYPTTAB_OPTION_header:+--header="$CRYPTTAB_OPTION_header"} \ + ${CRYPTTAB_OPTION_keyslot:+--key-slot="$CRYPTTAB_OPTION_keyslot"} \ + ${CRYPTTAB_OPTION_keyfile_size:+--keyfile-size="$CRYPTTAB_OPTION_keyfile_size"} \ + ${CRYPTTAB_OPTION_keyfile_offset:+--keyfile-offset="$CRYPTTAB_OPTION_keyfile_offset"} \ + --type="$CRYPTTAB_TYPE" --key-file="$keyfile" \ + luksResume "$CRYPTTAB_NAME" +} + +# resume_device($device) +# Resume $device with endless retries. Used by cryptsetup-suspend-wrapper. +resume_device() { + local device="$1" + # check if device is really suspended + if [ "$(dmsetup info -c --noheadings -o suspended -- "$device" 2>/dev/null)" != "Suspended" ]; then + cryptsetup_message "ERROR: $device: device was not suspendend" + return 1 + fi + + if ! crypttab_find_entry "$device" || ! crypttab_parse_options --quiet; then + cryptsetup_message "ERROR: $device: not found in $TABFILE" + return 1 + fi + + if [ "$CRYPTTAB_TYPE" != "luks" ]; then + cryptsetup_message "ERROR: $CRYPTTAB_NAME: unable to resume non-LUKS device" + return 1 + fi + + # Loop endlessly until the resume command succeeded + while true; do + if [ -z "${CRYPTTAB_OPTION_keyscript+x}" ] && [ "$CRYPTTAB_KEY" != "none" ]; then + resume_mapping "$CRYPTTAB_KEY" && break || true + else + run_keyscript 1 | resume_mapping && break || true + fi + done +} + +# crypttab_key_check() +# Sanity checks for keyfile $CRYPTTAB_KEY. CRYPTTAB_NAME and +# CRYPTTAB_OPTION_<option> must be set appropriately. +crypttab_key_check() { + if [ ! -f "$CRYPTTAB_KEY" ] && [ ! -b "$CRYPTTAB_KEY" ] && [ ! -c "$CRYPTTAB_KEY" ] ; then + cryptsetup_message "WARNING: $CRYPTTAB_NAME: keyfile '$CRYPTTAB_KEY' not found" + return 0 + fi + + if [ "$CRYPTTAB_KEY" = "/dev/random" ] || [ "$CRYPTTAB_KEY" = "/dev/urandom" ]; then + if [ -n "${CRYPTTAB_OPTION_luks+x}" ] || [ -n "${CRYPTTAB_OPTION_tcrypt+x}" ]; then + cryptsetup_message "WARNING: $CRYPTTAB_NAME: has random data as key" + return 1 + else + return 0 + fi + fi + + local mode="$(stat -L -c"%04a" -- "$CRYPTTAB_KEY")" + if [ $(stat -L -c"%u" -- "$CRYPTTAB_KEY") -ne 0 ] || [ "${mode%00}" = "$mode" ]; then + cryptsetup_message "WARNING: $CRYPTTAB_NAME: key file $CRYPTTAB_KEY has" \ + "insecure ownership, see /usr/share/doc/cryptsetup/README.Debian.gz." + fi +} + +# _resolve_device_spec($spec) +# Resolve LABEL=<label>, UUID=<uuid>, PARTUUID=<partuuid> and +# PARTLABEL=<partlabel> to a block special device. If $spec is +# already a (link to a block special device) then it is echoed as is. +# Return 1 if $spec doesn't correspond to a block special device. +_resolve_device_spec() { + local spec="$1" + case "$spec" in + UUID=*|LABEL=*|PARTUUID=*|PARTLABEL=*) + # don't use /dev/disk/by-label/... to avoid gessing udev mangling + spec="$(blkid -l -t "$spec" -o device)" || spec= + ;; + esac + [ -b "$spec" ] && printf '%s\n' "$spec" || return 1 +} + +# dm_blkdevname($name) +# Print the mapped device name, or return 1 if the the device doesn't exist. +dm_blkdevname() { + local name="$1" dev + # /dev/mapper/$name isn't reliable due to udev mangling + if dev="$(dmsetup info -c --noheadings -o blkdevname -- "$name" 2>/dev/null)" && + [ -n "$dev" ] && [ -b "/dev/$dev" ]; then + echo "/dev/$dev" + return 0 + else + return 1 + fi +} + +# crypttab_find_entry([--quiet], $target) +# Search in the crypttab(5) for the given $target, and sets the +# variables CRYPTTAB_NAME, CRYPTTAB_SOURCE, CRYPTTAB_KEY and +# CRYPTTAB_OPTIONS accordingly. (In addition _CRYPTTAB_NAME, +# _CRYPTTAB_SOURCE, _CRYPTTAB_KEY and _CRYPTTAB_OPTIONS are set to the +# unmangled values before decoding the escape sequence.) If there are +# duplicates then only the first match is considered. +# Return 0 if a match is found, and 1 otherwise. +crypttab_find_entry() { + local target="$1" quiet="n" IFS + if [ "$target" = "--quiet" ] && [ $# -eq 2 ]; then + quiet="y" + target="$2" + fi + + if [ -f "$TABFILE" ]; then + while IFS=" " read -r _CRYPTTAB_NAME _CRYPTTAB_SOURCE _CRYPTTAB_KEY _CRYPTTAB_OPTIONS; do + if [ "${_CRYPTTAB_NAME#\#}" != "$_CRYPTTAB_NAME" ] || [ -z "$_CRYPTTAB_NAME" ]; then + # ignore comments and empty lines + continue + fi + + # unmangle names + CRYPTTAB_NAME="$(printf '%b' "$_CRYPTTAB_NAME")" + if [ -z "$_CRYPTTAB_SOURCE" ] || [ -z "$_CRYPTTAB_KEY" ]; then + cryptsetup_message "WARNING: '$CRYPTTAB_NAME' is missing some arguments, see crypttab(5)" + continue + elif [ "$CRYPTTAB_NAME" = "$target" ]; then + CRYPTTAB_SOURCE="$( printf '%b' "$_CRYPTTAB_SOURCE" )" + CRYPTTAB_KEY="$( printf '%b' "$_CRYPTTAB_KEY" )" + CRYPTTAB_OPTIONS="$(printf '%b' "$_CRYPTTAB_OPTIONS")" + return 0 + fi + done <"$TABFILE" + fi + + if [ "$quiet" = "n" ]; then + cryptsetup_message "WARNING: target '$target' not found in $TABFILE" + fi + return 1 +} + +# crypttab_foreach_entry($callback) +# Iterate through the crypttab(5) and run the given $callback for each +# entry found. Variables CRYPTTAB_NAME, CRYPTTAB_SOURCE, CRYPTTAB_KEY +# and CRYPTTAB_OPTIONS are set accordingly and available to the +# $callback. (In addition _CRYPTTAB_NAME, _CRYPTTAB_SOURCE, +# _CRYPTTAB_KEY and _CRYPTTAB_OPTIONS are set to the original values +# before decoding the escape sequence.) +# Return 0 if a match is found, and 1 otherwise. +crypttab_foreach_entry() { + local callback="$1" IFS + local _CRYPTTAB_NAME _CRYPTTAB_SOURCE _CRYPTTAB_KEY _CRYPTTAB_OPTIONS \ + CRYPTTAB_NAME CRYPTTAB_SOURCE CRYPTTAB_KEY CRYPTTAB_OPTIONS + + [ -f "$TABFILE" ] || return + while IFS=" " read -r _CRYPTTAB_NAME _CRYPTTAB_SOURCE _CRYPTTAB_KEY _CRYPTTAB_OPTIONS <&9; do + if [ "${_CRYPTTAB_NAME#\#}" != "$_CRYPTTAB_NAME" ] || [ -z "$_CRYPTTAB_NAME" ]; then + # ignore comments and empty lines + continue + fi + + # unmangle names + CRYPTTAB_NAME="$(printf '%b' "$_CRYPTTAB_NAME")" + + if [ -z "$_CRYPTTAB_SOURCE" ] || [ -z "$_CRYPTTAB_KEY" ]; then + cryptsetup_message "WARNING: '$CRYPTTAB_NAME' is missing some arguments, see crypttab(5)" + continue + fi + + CRYPTTAB_SOURCE="$( printf '%b' "$_CRYPTTAB_SOURCE" )" + CRYPTTAB_KEY="$( printf '%b' "$_CRYPTTAB_KEY" )" + CRYPTTAB_OPTIONS="$(printf '%b' "$_CRYPTTAB_OPTIONS")" + + "$callback" 9<&- + done 9<"$TABFILE" +} + +# _device_uuid($device) +# Print the UUID attribute of given block special $device. Return 0 +# on success, 1 on error. +_device_uuid() { + local device="$1" uuid + if uuid="$(blkid -s UUID -o value -- "$device")" && [ -n "$uuid" ]; then + printf '%s\n' "$uuid" + else + return 1 + fi +} + +# _resolve_device({$device | $spec}) +# Take a path to (or spec for) a block special device, and set DEV to +# the (symlink to block) device, and MAJ (resp. MIN) to its major-ID +# (resp. minor ID) decimal value. On error these variables are not +# changed and 1 is returned. +_resolve_device() { + local spec="$1" dev devno maj min + if dev="$(_resolve_device_spec "$spec")" && + devno="$(stat -L -c"%t:%T" -- "$dev" 2>/dev/null)" && + maj="${devno%:*}" && min="${devno#*:}" && + [ "$devno" = "$maj:$min" ] && [ -n "$maj" ] && [ -n "$min" ] && + maj=$(( 0x$maj )) && min=$(( 0x$min )) && [ $maj -gt 0 ]; then + DEV="$dev" + MAJ="$maj" + MIN="$min" + return 0 + else + cryptsetup_message "ERROR: Couldn't resolve device $spec" + fi + return 1 +} + +# get_mnt_devno($mountpoint) +# Print the major:minor device ID(s) holding the file system currently +# mounted currenty mounted on $mountpoint. +# Return 0 on success, 1 on error (if $mountpoint is not a mountpoint). +get_mnt_devno() { + local wantmount="$1" devnos="" uuid dev IFS + local spec mountpoint fstype _ DEV MAJ MIN + + while IFS=" " read -r spec mountpoint fstype _; do + # treat lines starting with '#' as comments; /proc/mounts + # doesn't seem to contain these but per procfs(5) the format of + # that file is analogous to fstab(5)'s + if [ "${spec#\#}" = "$spec" ] && [ -n "$spec" ] && + [ "$(printf '%b' "$mountpoint")" = "$wantmount" ]; then + # take the last mountpoint if used several times (shadowed) + unset -v devnos + spec="$(printf '%b' "$spec")" + _resolve_device "$spec" || continue # _resolve_device() already warns on error + fstype="$(printf '%b' "$fstype")" + if [ "$fstype" = "btrfs" ]; then + # btrfs can span over multiple devices + if uuid="$(_device_uuid "$DEV")"; then + for dev in "/sys/fs/$fstype/$uuid/devices"/*/dev; do + devnos="${devnos:+$devnos }$(cat "$dev")" + done + else + cryptsetup_message "ERROR: $spec: Couldn't determine UUID" + fi + elif [ -n "$fstype" ]; then + devnos="$MAJ:$MIN" + fi + fi + done </proc/mounts + + if [ -z "${devnos:+x}" ]; then + return 1 # not found + else + printf '%s' "$devnos" + fi +} + +# foreach_cryptdev([--reverse], $callback, $maj:$min, [$maj:$min ..]) +# Run $callback on the (unmangled) name of each dm-crypt device +# recursively holding $maj:$min (typically corresponding to an md, +# linear, or dm-crypt device). Slaves that aren't dm-crypt devices +# are ignored. +# By default each device is processed after its *slaves*. If +# --reverse is set then each device is processed after its *holders* +# instead. +foreach_cryptdev() { + local callback="$1" reverse="n" devno base + shift + if [ "$callback" = "--reverse" ]; then + reverse="y" + callback="$1" + shift + fi + for devno in "$@"; do + base="/sys/dev/block/$devno" + if [ ! -d "$base" ]; then + cryptsetup_message "ERROR: Couldn't find sysfs directory for $devno" + return 1 + fi + _foreach_cryptdev "$base" + done +} +_foreach_cryptdev() { + local d="$1" devno maj min name t d2 + [ "$reverse" = "y" ] && t="holders" || t="slaves" + [ -d "$d/$t" ] || return 0 + for d2 in "$d/$t"/*; do + if [ -d "$d2" ] && d2="$(realpath -e -- "$d2")"; then + _foreach_cryptdev "$d2" + fi + done + if [ -d "$d/dm" ] && devno="$(cat "$d/dev")" && + maj="${devno%:*}" && min="${devno#*:}" && + [ "$devno" = "$maj:$min" ] && [ -n "$maj" ] && [ -n "$min" ] && + [ "$(dmsetup info -c --noheadings -o subsystem -j "$maj" -m "$min")" = "CRYPT" ] && + name="$(dmsetup info -c --noheadings -o unmangled_name -j "$maj" -m "$min")"; then + "$callback" "$name" + fi +} + +# vim: set filetype=sh : |