#!/bin/sh
# tlp-func-bay - Bay Functions
#
# Copyright (c) 2024 Thomas Koch <linrunner at gmx.net> and others.
# This software is licensed under the GPL v2 or later.

# SPDX-License-Identifier: GPL-2.0-or-later

# ----------------------------------------------------------------------------
# Constants

readonly DOCK_GLOB="/sys/devices/platform/dock.?"

readonly BAYSTATEFILE=$RUNDIR/bay_saved

# ----------------------------------------------------------------------------
# Functions

# --- Drive Bay

get_drivebay_device () { # Find generic dock interface for drive bay
                         # rc: 0; retval: $dock

    # shellcheck disable=SC2086
    dock=$(grep -l 'ata_bay' $DOCK_GLOB/type 2> /dev/null)
    dock=${dock%%/type}
    if [ ! -d "$dock" ]; then
        dock=""
    fi

    return 0
}

check_is_docked() { # check if $dock is docked;
                    # rc: 0 if docked, else 1

   local dock_status dock_info_file

   # return 0 if any sysfs file indicates "docked"
   for dock_info_file in docked firmware_node/status; do
        if [ -f "$dock/$dock_info_file" ] && \
            read -r dock_status < "$dock/$dock_info_file" 2>/dev/null; then
            # catch empty $dock_status (safety check, unlikely case)
            [ "${dock_status:-0}" != "0" ] && return 0
        fi
   done

   # otherwise assume "not docked"
   return 1
}

poweroff_drivebay () { # power off optical drive in drive bay
    # $1: 0=ac mode, 1=battery mode
    # $2: 0=conditional+quiet mode, 1=force+verbose mode
    # Some code adapted from https://www.thinkwiki.org/wiki/How_to_hotswap_UltraBay_devices

    local pwr optdrv syspath

    if [ "$1" = "1" ]; then
        pwr="$BAY_POWEROFF_ON_BAT"
    else
        pwr="$BAY_POWEROFF_ON_AC"
    fi

    # Run only if forced or enabled
    if [ "$2" != "1" ]; then
        case "$pwr" in
            1) # enabled --> proceed
                ;;

            0) # disabled
                echo_debug "pm" "poweroff_drivebay($1).disabled"
                return 0
                ;;

            *) # not configured or invalid parameter
                echo_debug "pm" "poweroff_drivebay($1).not_configured"
                return 0
                ;;
        esac
    fi

    get_drivebay_device
    if [ -z "$dock" ] || [ ! -d "$dock" ]; then
        echo_debug "pm" "poweroff_drivebay($1).no_bay_device"
        [ "$2" = "1" ] && cecho "Error: cannot locate bay device." 1>&2
        return 1
    fi
    echo_debug "pm" "poweroff_drivebay($1): dock=$dock"

    # Check if bay is occupied
    if ! check_is_docked; then
        echo_debug "pm" "poweroff_drivebay($1).drive_already_off"
        [ "$2" = "1" ] && echo "No drive in bay (or power already off)."
    else
        # Check for optical drive
        optdrv="$BAY_DEVICE"
        if [ -z "$optdrv" ]; then
            echo_debug "pm" "poweroff_drivebay($1).opt_drive_not_configured"
            [ "$2" = "1" ] && cecho "Error: no optical drive configured (BAY_DEVICE=\"\")." 1>&2
            return 1
        elif [ ! -b "/dev/$optdrv" ]; then
            echo_debug "pm" "poweroff_drivebay($1).no_opt_drive: /dev/$optdrv"
            [ "$2" = "1" ] && echo "No optical drive in bay (/dev/$optdrv)."
            return 0
        else
            echo_debug "pm" "poweroff_drivebay($1): optdrv=$optdrv"
            [ "$2" = "1" ] && echo -n "Powering off drive bay..."

            # Unmount media
            umount -l "$optdrv" > /dev/null 2>&1

            # Sync drive
            sync
            sleep 1

            # Power off drive
            $HDPARM -Y "$optdrv" > /dev/null 2>&1
            sleep 5

            # Unregister scsi device
            if syspath="$($UDEVADM info --query=path --name="$optdrv" 2> /dev/null)"; then
                syspath="/sys${syspath%/block/*}"

                if [ "$syspath" != "/sys" ]; then
                    write_sysf "1" "$syspath/delete"
                    echo_debug "pm" "poweroff_drivebay($1): syspath=$syspath; rc=$?"
                else
                    echo_debug "pm" "poweroff_drivebay($1): got empty/invalid syspath for $optdrv"
                fi
            else
                echo_debug "pm" "poweroff_drivebay($1): failed to get syspath (udevadm returned $?)"
            fi

            # Turn power off
            write_sysf "1" "$dock/undock"
            echo_debug "pm" "poweroff_drivebay($1).bay_powered_off: rc=$?"
            [ "$2" = "1" ] && echo "done."
        fi
    fi

    return 0
}

suspend_drivebay () { # Save power state of drive bay before suspend
                      # $1: 0=ac mode, 1=battery mode

    if [ "$1" = "1" ] && [ "$BAY_POWEROFF_ON_BAT" = "1" ] || \
       [ "$1" = "0" ] && [ "$BAY_POWEROFF_ON_AC"  = "1" ]; then
        # setting corresponding to mode is active -> save state
        get_drivebay_device

        if [ -n "$dock" ]; then
            create_rundir
            if ! check_is_docked; then
                write_sysf "off" "$BAYSTATEFILE"
                echo_debug "pm" "suspend_drivebay($1): bay=off; rc=$?"
            else
                write_sysf "on" "$BAYSTATEFILE"
                echo_debug "pm" "suspend_drivebay($1): bay=on; rc=$?"
            fi
        fi
    else
        # setting not active -> remove state file
        rm -f "$BAYSTATEFILE" 2> /dev/null
    fi

    return 0
}

resume_drivebay () { #
    # $1: 0=ac mode, 1=battery mode
    local cnt rc

    if [ "$(read_sysf "$BAYSTATEFILE")" = "off" ]; then
        # saved state = off
        get_drivebay_device

        if [ -n "$dock" ]; then
            if check_is_docked; then
                # device active -> deactivate
                if [ -e "$dock/undock" ]; then
                    cnt=5
                    rc=1
                    until [ $rc = 0 ] || [ $cnt = 0 ]; do
                        cnt=$((cnt - 1))
                        { printf '%s\n' "1" > "$dock/undock"; } 2> /dev/null
                        rc=$?
                        [ $rc = 0 ] || sleep 0.5
                    done
                    echo_debug "pm" "resume_drivebay.bay_off: rc=$rc"
                fi
            else
                echo_debug "pm" "resume_drivebay.already_off"
            fi
        fi
    else
        # No saved state or state != off --> apply settings
        poweroff_drivebay "$1" 0
    fi

    rm -f "$BAYSTATEFILE" 2> /dev/null

    return 0
}
