#
# This file is for inclusion with
#    . /lib/cryptsetup/cryptdisks-functions
# and should not be executed directly.

PATH="/usr/sbin:/usr/bin:/sbin:/bin"
TABFILE=${TABFILE-"/etc/crypttab"}
CRYPTDISKS_ENABLE="Yes"

#set -x

# Sanity check #1
[ -x /sbin/cryptsetup ] || exit 0

. /lib/lsb/init-functions

if [ -r /etc/default/cryptdisks ]; then
    . /etc/default/cryptdisks
fi

MOUNT="$CRYPTDISKS_MOUNT"

. /lib/cryptsetup/functions


# do_start()
#   Unlock all devices in the crypttab(5)
do_start() {
    local target source key options
    [ -s "$TABFILE" ] || return 0

    # Create locking directory before invoking cryptsetup(8) to avoid warnings
    mkdir -pm0700 /run/cryptsetup
    modprobe -qb dm-mod || true
    modprobe -qb dm-crypt || true
    dmsetup mknodes >/dev/null 2>&1 || true

    if [ "$INITSTATE" != "init" ]; then
        log_action_begin_msg "Starting $INITSTATE crypto disks"
    fi
    mount_fs

    while read target source key options <&3; do
        [ "${target#\#}" = "$target" ] || continue
        setup_mapping "$target" "$source" "$key" "$options" 3<&- || log_action_end_msg $?
    done 3< "$TABFILE"

    umount_fs
    log_action_end_msg 0
}

# mount_fs()
#   Premounts file systems
mount_fs() {
    local point
    MOUNTED=""

    for point in $MOUNT; do
        if mount "$point" >/dev/null; then
            MOUNTED="$MOUNTED $point"
        fi
    done
}

# Postunmounts file systems
umount_fs() {
    local point

    for point in $MOUNTED; do
        umount "$point" >/dev/null
    done
}

# setup_mapping($target, $source, $key, $options)
#   Set up a given crypttab(5) mapping
setup_mapping() {
    export CRYPTTAB_NAME="$1" CRYPTTAB_SOURCE="$2" CRYPTTAB_OPTIONS="$4"
    local key="$3"

    if [ -e "/dev/mapper/$CRYPTTAB_NAME" ]; then
        device_msg "running"
        return 0
    fi

    local loud="${DEFAULT_LOUD:-}"
    crypttab_parse_options || return 1
    if [ -n "${CRYPTTAB_OPTION_quiet+x}" ]; then
        loud="no"
    elif [ -n "${CRYPTTAB_OPTION_loud+x}" ]; then
        loud="yes"
    fi

    if [ -n "${CRYPTTAB_OPTION_noearly+x}" ] && [ "$INITSTATE" = "early" ]; then
        [ -z "${FORCE_START-}" ] || device_msg "ignored"
        return 0
    fi
    if [ -n "${CRYPTTAB_OPTION_noauto+x}" ] && [ "$INITSTATE" != "manual" ]; then
        [ -z "${FORCE_START-}" ] || device_msg "ignored"
        return 0
    fi

    if [ -z "${CRYPTTAB_OPTION_keyscript+x}" ] && [ "$key" != "none" ]; then
        if ! check_key "$key"; then
            device_msg "invalid key"
            return 1
        fi
        CRYPTTAB_OPTION_tries=1
    fi

    local source
    if ! source="$(normalise_device --quiet "$CRYPTTAB_SOURCE" )"; then
        if [ "$loud" = "yes" ]; then
            device_msg "skipped, device $CRYPTTAB_SOURCE does not exist"
        fi
        return 1
    fi
    CRYPTTAB_SOURCE="$source"
    device_msg "starting"

    local type="$(get_crypt_type)" out
    if [ "$type" != "luks" ]; then
        if ! out="$(/lib/cryptsetup/checks/un_blkid "$CRYPTTAB_SOURCE" 2>/dev/null)" &&
                ! /lib/cryptsetup/checks/blkid "$CRYPTTAB_SOURCE" swap >/dev/null; then
            # fail if the device has a filesystem; unless it's swap,
            # otherwise people can't easily convert an existing
            # plainttext swap partition to an encrypted one
            log_warning_msg "$CRYPTTAB_NAME: the precheck for '$CRYPTTAB_SOURCE' failed: $out"
            return 1
        fi
    fi

    local count=0 maxtries="${CRYPTTAB_OPTION_tries:-3}" fstype rv
    local tmptarget="${CRYPTTAB_NAME}_unformatted" # XXX potential conflict
    while [ $maxtries -le 0 ] || [ $count -lt $maxtries ]; do
        if [ -z "${CRYPTTAB_OPTION_keyscript+x}" ] && [ "$key" != "none" ]; then
            # unlock via keyfile
            unlock_mapping "$key" "$tmptarget"
        else
            # unlock interactively or via keyscript
            run_keyscript "$key" "$count" | unlock_mapping - "$tmptarget"
        fi
        rv=$?
        count=$(( $count + 1 ))

        if [ $rv -ne 0 ] || [ ! -e "/dev/mapper/$tmptarget" ]; then
            continue
        fi
        if [ -n "${CRYPTTAB_OPTION_check+x}" ] && \
                ! "$CRYPTTAB_OPTION_check" "/dev/mapper/$tmptarget" $CRYPTTAB_OPTION_checkargs ; then
            log_warning_msg "$CRYPTTAB_NAME: the check for '/dev/mapper/$tmptarget' failed"
            cryptsetup remove -- "$tmptarget"
            continue
        fi
        if [ "${CRYPTTAB_OPTION_swap+x}" ]; then
            if out="$(/lib/cryptsetup/checks/un_blkid "/dev/mapper/$tmptarget" 2>/dev/null)" ||
                    /lib/cryptsetup/checks/blkid "/dev/mapper/$tmptarget" swap >/dev/null 2>&1; then
                mkswap "/dev/mapper/$tmptarget" >/dev/null 2>&1
            else
                log_warning_msg "$CRYPTTAB_NAME: the check for '/dev/mapper/$tmptarget' failed. /dev/mapper/$tmptarget contains data: $out"
                cryptsetup remove -- "$tmptarget"
                return 1
            fi
        elif [ "${CRYPTTAB_OPTION_tmp+x}" ]; then
            local tmpdir="$(mktemp --tmpdir="/run/cryptsetup" --directory)" rv=0
            if ! mkfs -t "$CRYPTTAB_OPTION_tmp" -q "/dev/mapper/$tmptarget" >/dev/null 2>&1 ||
                    ! mount -t "$CRYPTTAB_OPTION_tmp" "/dev/mapper/$tmptarget" "$tmpdir" ||
                    ! chmod 1777 "$tmpdir"; then
                rv=1
            fi
            umount "$tmpdir" || true
            rmdir "$tmpdir" || true
            [ $rv -eq 0 ] || return $rv
        fi
        if command -v udevadm >/dev/null 2>&1; then
            udevadm settle
        fi
        dmsetup rename "$tmptarget" "$CRYPTTAB_NAME"
        device_msg "started"
        return 0
    done
    device_msg "failed"
    return 1
}

# crypttab_find_target($name)
#   Look for a crypttab(5) entry with target $name, and set
#   CRYPTTAB_NAME, CRYPTTAB_SOURCE, CRYPTTAB_KEY and CRYPTTAB_OPTIONS if
#   there a match.
crypttab_find_target() {
    local name="$1"
    local target source key options
    while read target source key options; do
        [ "${target#\#}" = "$target" ] || continue
        if [ "$target" = "$name" ]; then
            CRYPTTAB_NAME="$target"
            CRYPTTAB_SOURCE="$source"
            CRYPTTAB_KEY="$key"
            CRYPTTAB_OPTIONS="$options"
            return 0
        fi
    done <"$TABFILE"
    return 1
}

# Removes all mappings in crypttab
do_stop () {
    local target source key options i rv

    dmsetup mknodes
    log_action_begin_msg "Stopping $INITSTATE crypto disks"

    while read target source key options <&3; do
        [ "${target#\#}" = "$target" ] || continue
        for i in 1 2 4 8 16 32; do
            remove_mapping "$target" "$options" 3<&- && break || rv=$?
            if [ $rv -eq 1 ] || [ $rv -eq 2 -a $i -gt 16 ]; then
                log_action_end_msg $rv
                break
            fi
            log_action_cont_msg "$target busy..."
            sleep $i
        done
    done 3< "$TABFILE"

    log_action_end_msg 0
}

# device_msg($message)
#   Convenience function to handle $VERBOSE
device_msg() {
    local name message
    if [ $# -eq 1 ]; then
        name="$CRYPTTAB_NAME"
        message="$1"
    else
        name="$1"
        message="$2"
    fi

    if [ "$VERBOSE" != "no" ]; then
        log_action_cont_msg "$name ($message)"
    fi
}

# remove_mapping($target, $options)
#   Remove mapping $target
remove_mapping() {
    local CRYPTTAB_NAME="$1" options="$2"

    if [ ! -b "/dev/mapper/$CRYPTTAB_NAME" ]; then
        device_msg "stopped"
        return 0
    fi

    if [ "$(dmsetup info --noheadings -c -o subsystem -- "$CRYPTTAB_NAME")" != "CRYPT" ]; then
        device_msg "error"
        return 1
    fi

    crypttab_parse_options "$options" n 2>/dev/null || true


    local opencount="$(dmsetup info -c --noheadings -o open -- "$CRYPTTAB_NAME" 2>/dev/null || true)"
    if [ -z "$opencount" ]; then
        device_msg "error"
        return 1
    elif [ "$opencount" != "0" ]; then
        device_msg "busy"
        if [ "$INITSTATE" = "early" ] || [ "$INITSTATE" = "manual" ]; then
            return 1
        elif [ "$INITSTATE" = "remaining" ]; then
            return 2
        fi
        return 0
    fi

    if cryptsetup remove -- "$CRYPTTAB_NAME"; then
        device_msg "stopping"
        return 0
    else
        device_msg "error"
        return 1
    fi
}

# vim: set filetype=sh :
