#!/bin/sh

type getarg > /dev/null 2>&1 || . /lib/dracut-lib.sh

fsck_ask_reboot() {
    info "note - fsck suggests reboot, if you"
    info "leave shell, booting will continue normally"
    emergency_shell -n "(reboot ?)"
}

fsck_ask_err() {
    warn "*** An error occurred during the file system check."
    warn "*** Dropping you to a shell; the system will try"
    warn "*** to mount the filesystem(s), when you leave the shell."
    emergency_shell -n "(Repair filesystem)"
}

# inherits: _ret _drv _out
fsck_tail() {
    [ "$_ret" -gt 0 ] && warn "$_drv returned with $_ret"
    if [ "$_ret" -ge 4 ]; then
        [ -n "$_out" ] && echo "$_out" | vwarn
        fsck_ask_err
    else
        [ -n "$_out" ] && echo "$_out" | vinfo
        [ "$_ret" -ge 2 ] && fsck_ask_reboot
    fi
}

# note: this function sets _drv of the caller
fsck_able() {
    _drv=":" && return 0
    case "$1" in
        xfs)
            # {
            #     type xfs_db &&
            #     type xfs_repair &&
            #     type xfs_check &&
            #     type mount &&
            #     type umount
            # } >/dev/null 2>&1 &&
            # _drv="_drv=none fsck_drv_xfs" &&
            # return 0
            return 1
            ;;
        ext?)
            type e2fsck > /dev/null 2>&1 \
                && _drv="fsck_drv_com e2fsck" \
                && return 0
            ;;
        f2fs)
            type fsck.f2fs > /dev/null 2>&1 \
                && _drv="fsck_drv_com fsck.f2fs" \
                && return 0
            ;;
        jfs)
            type jfs_fsck > /dev/null 2>&1 \
                && _drv="fsck_drv_com jfs_fsck" \
                && return 0
            ;;
        reiserfs)
            type reiserfsck > /dev/null 2>&1 \
                && _drv="fsck_drv_com reiserfsck" \
                && return 0
            ;;
        btrfs)
            # type btrfsck >/dev/null 2>&1 &&
            # _drv="_drv=none fsck_drv_btrfs" &&
            # return 0
            return 1
            ;;
        nfs*)
            # nfs can be a nop, returning success
            _drv=":" \
                && return 0
            ;;
        *)
            type fsck > /dev/null 2>&1 \
                && _drv="fsck_drv_std fsck" \
                && return 0
            ;;
    esac

    return 1
}

# note: all drivers inherit: _drv _fop _dev

fsck_drv_xfs() {
    # xfs fsck is not necessary... Either it mounts or not
    return 0
}

fsck_drv_btrfs() {
    # btrfs fsck is not necessary... Either it mounts or not
    return 0
}

# common code for checkers that follow usual subset of options and return codes
fsck_drv_com() {
    local _drv="$1"
    local _ret
    local _out

    if ! strglobin "$_fop" "-[ynap]"; then
        _fop="-a${_fop:+ "$_fop"}"
    fi

    info "issuing $_drv $_fop $_dev"
    # we enforce non-interactive run, so $() is fine
    _out=$($_drv "$_fop" "$_dev")
    _ret=$?
    fsck_tail

    return $_ret
}

# code for generic fsck, if the filesystem checked is "unknown" to us
fsck_drv_std() {
    local _ret
    local _out
    unset _out

    info "issuing fsck $_fop $_dev"
    # note, we don't enforce -a here, thus fsck is being run (in theory)
    # interactively; otherwise some tool might complain about lack of terminal
    # (and using -a might not be safe)
    # shellcheck disable=SC2086
    fsck $_fop "$_dev" > /dev/console 2>&1
    _ret=$?
    fsck_tail

    return $_ret
}

# checks single filesystem, relying on specific "driver"; we don't rely on
# automatic checking based on fstab, so empty one is passed;
# takes 4 arguments - device, filesystem, filesystem options, additional fsck options;
# first 2 arguments are mandatory (fs may be auto or "")
# returns 255 if filesystem wasn't checked at all (e.g. due to lack of
# necessary tools or insufficient options)
fsck_single() {
    local FSTAB_FILE=/etc/fstab.empty
    local _dev="$1"
    local _fs="${2:-auto}"
    local _fop="$4"
    local _drv

    [ $# -lt 2 ] && return 255
    # if UUID= marks more than one device, take only the first one
    [ -e "$_dev" ] || _dev=$(devnames "$_dev" | while read -r line || [ -n "$line" ]; do if [ -n "$line" ]; then
        echo "$line"
        break
    fi; done)
    [ -e "$_dev" ] || return 255
    _fs=$(det_fs "$_dev" "$_fs")
    fsck_able "$_fs" || return 255

    info "Checking $_fs: $_dev"
    export FSTAB_FILE
    eval "$_drv"
    return $?
}

# takes list of filesystems to check in parallel; we don't rely on automatic
# checking based on fstab, so empty one is passed
fsck_batch() {
    local FSTAB_FILE=/etc/fstab.empty
    local _drv=fsck
    local _dev
    local _ret
    local _out

    [ $# -eq 0 ] || ! type fsck > /dev/null 2>&1 && return 255

    info "Checking filesystems (fsck -M -T -a):"
    for _dev in "$@"; do
        info "    $_dev"
    done

    export FSTAB_FILE
    _out="$(fsck -M -T "$@" -- -a)"
    _ret=$?

    fsck_tail

    return $_ret
}

# verify supplied filesystem type:
# if user provided the fs and we couldn't find it, assume user is right
# if we found the fs, assume we're right
det_fs() {
    local _dev="$1"
    local _orig="${2:-auto}"
    local _fs

    _fs=$(udevadm info --query=env --name="$_dev" \
        | while read -r line || [ -n "$line" ]; do
            if str_starts "$line" "ID_FS_TYPE="; then
                echo "${line#ID_FS_TYPE=}"
                break
            fi
        done)
    _fs=${_fs:-auto}

    if [ "$_fs" = "ext2" ]; then
        _fs="$_orig"
    fi
    if [ "$_fs" = "auto" ]; then
        _fs="$_orig"
    fi
    echo "$_fs"
}

write_fs_tab() {
    local _o
    local _rw
    local _root
    local _rootfstype
    local _rootflags
    local _fspassno

    _fspassno="0"
    _root="$1"
    _rootfstype="$2"
    _rootflags="$3"
    [ -z "$_rootfstype" ] && _rootfstype=$(getarg rootfstype=)
    [ -z "$_rootflags" ] && _rootflags=$(getarg rootflags=)

    [ -z "$_rootfstype" ] && _rootfstype="auto"

    if [ -z "$_rootflags" ]; then
        _rootflags="ro,x-initrd.mount"
    else
        _rootflags="ro,$_rootflags,x-initrd.mount"
    fi

    _rw=0

    CMDLINE=$(getcmdline)
    for _o in $CMDLINE; do
        case $_o in
            rw)
                _rw=1
                ;;
            ro)
                _rw=0
                ;;
        esac
    done
    if [ "$_rw" = "1" ]; then
        _rootflags="$_rootflags,rw"
        if ! getargbool 0 rd.skipfsck; then
            _fspassno="1"
        fi
    fi

    if grep -q "$_root /sysroot" /etc/fstab; then
        echo "$_root /sysroot $_rootfstype $_rootflags $_fspassno 0" >> /etc/fstab
    else
        return
    fi

    if type systemctl > /dev/null 2> /dev/null; then
        systemctl daemon-reload
        systemctl --no-block start initrd-root-fs.target
    fi
}
