#!/bin/bash
#
# Authors Thomas Renninger (powersave@renninger.de)
#         Stefan Seyfried (seife@suse.de)
#
# This script is thought to be invoked by the powersave daemon
# It adjusts the machine to the settings of a specfic scheme

# At the moment only hard disk issues are managed by this script
# (e.g. setting standby/suspend timouts and acoustic settings),
# but there maybe added more in future.
# 

# The script might take following parameters:
# 
# 1) The powersave daemon event that caused the script to be invoked 
# 2) The scheme which settings should be executed
#    Be careful. It is not the scheme name as specified in SCHEME_NAME
#    inside the scheme configuration file.
#    It is the name of the scheme configuration file, without leading path.
#    E.g. scheme_powersave should refer (by default) to the scheme configurations 
#    that are stored in /etc/sysconfig/powersave/scheme_powersave


# shameless rip from the laptop_mode script
# this part is inspired by the laptop_mode script from
# Jens Axboe, Kiko Piris, Bart Samwel, Dax Kelson which is in the
# Linux kernel Documentation/ directory
laptop_mode_func() {

    DEBUG "do remounts?: '$HD_DO_REMOUNTS', set no atime: '$HD_NOATIME'" INFO 
    if [ ! -w /proc/sys/vm/laptop_mode ]; then
        DEBUG "Kernel is not patched with laptop_mode patch or not enough privileges." WARN
        return 1
    fi
    local AGE DEV MP FST OPTS DUMP PASS ACTIVE
    local DEF_AGE DEF_UPDATE DEF_DIRTY_BACKGROUND_RATIO DEF_DIRTY_RATIO

    # kernel default dirty buffer age; no need to alter.
    DEF_AGE=30; DEF_UPDATE=5; DEF_DIRTY_BACKGROUND_RATIO=10; DEF_DIRTY_RATIO=40

    read ACTIVE < /proc/sys/vm/laptop_mode
    case "$1" in
        start)
            if [ "$ACTIVE" -eq 0 ]; then
                AGE=$((100*$HD_MAX_AGE))
                DEBUG "Starting laptop_mode" DIAG
                case "$K_RELEASE" in
                    "2.4")
                        echo "1"                             > /proc/sys/vm/laptop_mode
                        echo "30 500 0 0 $AGE $AGE 60 20 0"  > /proc/sys/vm/bdflush
                        ;;
                    "2.6")
                        echo "1"               > /proc/sys/vm/laptop_mode
                        echo "$AGE"            > /proc/sys/vm/dirty_writeback_centisecs
                        echo "$AGE"            > /proc/sys/vm/dirty_expire_centisecs
                        echo "$HD_DIRTY_RATIO" > /proc/sys/vm/dirty_ratio
                        echo "$HD_DIRTY_RATIO" > /proc/sys/vm/dirty_background_ratio
                        ;;
                    *)  DEBUG "unhandled kernel release: '$K_RELEASE' in function laptop_mode_func" ERROR
                        ;;
                esac
                if [ "$HD_DO_REMOUNTS" != "no" ]; then
                    while read DEV MP FST OPTS DUMP PASS ; do
                        OPTS=${OPTS//commit=[0-9]*/}
                        OPTS=${OPTS//,,/,}
                        case "$FST" in
                            "ext3")     mount $DEV -t $FST $MP -o remount,$OPTS,commit=$HD_MAX_AGE ;;
                            "reiserfs") [ "$HD_NOATIME" = "yes" ] && OPTS="$OPTS,noatime"
                                        mount $DEV -t $FST $MP -o remount,$OPTS,commit=$HD_MAX_AGE ;;
                            "xfs")      mount $DEV -t $FST $MP -o remount,$OPTS ;;
                        esac
                    done < /etc/mtab
                fi
            else
                DEBUG "laptop_mode already active, ignoring" INFO
            fi
            ;;
        stop)
            if [ "$ACTIVE" -eq 1 ]; then
                U_AGE=$((100*$DEF_UPDATE))
                B_AGE=$((100*$DEF_AGE))
                DEBUG "Stopping laptop_mode" DIAG
                case "$K_RELEASE" in
                    "2.4")
                        echo "0"                                > /proc/sys/vm/laptop_mode
                        echo "30 500 0 0 $U_AGE $B_AGE 60 20 0" > /proc/sys/vm/bdflush
                        ;;
                    "2.6")
                        echo "0"                           > /proc/sys/vm/laptop_mode
                        echo "$U_AGE"                      > /proc/sys/vm/dirty_writeback_centisecs
                        echo "$B_AGE"                      > /proc/sys/vm/dirty_expire_centisecs
                        echo "$DEF_DIRTY_RATIO"            > /proc/sys/vm/dirty_ratio
                        echo "$DEF_DIRTY_BACKGROUND_RATIO" > /proc/sys/vm/dirty_background_ratio
                        ;;
                    *)  DEBUG "unhandled kernel release: '$K_RELEASE' in function laptop_mode_func" ERROR
                        ;;
                esac
                if [ "$HD_DO_REMOUNTS" != "no" ]; then
                    while read DEV MP FST OPTS DUMP PASS ; do
                        OPTS=${OPTS//commit=[0-9]*/}
                        OPTS=${OPTS//,,/,}
                        case "$FST" in
                            "ext3")     mount $DEV -t $FST $MP -o remount,$OPTS ;;
                            "reiserfs") [ "$HD_NOATIME" = "yes" ] && OPTS="${OPTS//noatime/}"
                                        mount $DEV -t $FST $MP -o remount,$OPTS ;;
                            "xfs")      mount $DEV -t $FST $MP -o remount,$OPTS ;;
                        esac
                    done < /etc/mtab
                fi
            else
                DEBUG "laptop_mode already inactive, ignoring" DIAG
            fi
            ;;
        *)  DEBUG "wrong call of function laptop_mode_func with argument '$1'" ERROR 
            return 1
            ;;
    esac
    return 0
}

#########################################################
# this function checks if the disk supports APM and AAM
# "return codes" are passed via global variables (ugly).
get_disk_aam_apm(){
    local DEV X
    declare -a X
    DISK_APM=false
    DISK_AAM=false
    DEV=$1  # the name of the device ("hda", "hdb", ...)
    [ -z "$DEV" ] && return 1
    X=(`cat /proc/ide/$DEV/identify`) || return 1 # this concatenates all lines...
    # ...and set ${X[0]}=word00, ${X[1]}=word01...
    # bit 9 of word 83 is AAM
    if [ $[0x${X[83]} & 0x0200 ] -ne 0 ] ; then
        DISK_AAM=true
    fi
    # bit 3 of word 83 is APM
    if [ $[0x${X[83]} & 0x08 ] -ne 0 ] ; then
        DISK_APM=true
    fi
}

# calls hdparm for all disks with the arguments given.
# arguments: 1. hdparm argument for AAM ('-M 128')
#            2. hdparm argument for APM ('-B 254')
#            3. raw hdparm arguments as provided by the user.
# 1 and 2 are only issued to disks that support them.
exec_hdparm() {
    local ARGS TYPE IDE DEV RET
    local ACOU="$1"
    local STBY="$2"
    local  RAW="$3"
    ARGS=""
    # Is this setting safe on all disks ?
    # get all ide disks, is this also working for 2.4.x?
    for IDE in /proc/ide/ide?/hd?; do
        TYPE=""
        DEV=${IDE##*/}
        read TYPE < $IDE/media
        case "$TYPE" in
            disk)
                get_disk_aam_apm $DEV
                $DISK_AAM && ARGS="$ARGS $ACOU"
                $DISK_APM && ARGS="$ARGS $STBY"
                ARGS="$ARGS $RAW"
                DEBUG "We have a disk: /dev/$DEV" INFO
                DEBUG "running /sbin/hdparm $ARGS /dev/$DEV" DIAG
                /sbin/hdparm $ARGS /dev/$DEV &>/dev/null || \
                        DEBUG "hdparm returned error '$?'" WARN
                ;;
            *) 
                DEBUG "This is not a disk: /dev/$DEV" DEBUG
                ;;
        esac
    done
    return 0
}

execute_disk_settings() {
    # when changing from AC to DC and back....
    local LAPTOP ACOUSTIC STBY RAW
    local N # numeric dummy
    declare -i N
    # ATA spec says it is valid to set both, Advanced Power Management
    # and standby timeout value, which will set the disk to stanby
    # will be managed by disk
    # note that the values for those modes can be tuned in
    # /etc/sysconfig/powersave/disk
    LAPTOP=""; ACOUSTIC=""; STBY=""; RAW=""
    case "$DISK_STANDBY_MODE" in
        performance)
            N="$HDPARM_STBY_PERF"
            RAW="$HDPARM_RAW_PERF"
            LAPTOP="stop" ;;
        powersave)
            N="$HDPARM_STBY_SAVE"
            RAW="$HDPARM_RAW_SAVE"
            LAPTOP="start" ;;
        aggressive_powersave|aggressiv_powersave)
            N="$HDPARM_STBY_AGGR"
            RAW="$HDPARM_RAW_AGGR"
            LAPTOP="start" ;;
        no|off)
            ;;
        *)
            DEBUG "unknown DISK_STANDBY_MODE 
                   Value: $DISK_STANDBY_MODE" ERROR ;;
    esac
    [ $N -ne 0 ] && STBY="-B $N"

    case "$DISK_ACOUSTIC" in
        performance)
            N="$HDPARM_ACOUSTIC_PERF" ;;
        low)
            N="$HDPARM_ACOUSTIC_LOW" ;;
        quiet)
            N="$HDPARM_ACOUSTIC_QUIET" ;;
        no|off)
            ;;
        *)
            DEBUG "unknown DISK_ACOUSTIC 
                   Value: '$DISK_ACOUSTIC'" ERROR ;;
    esac
    [ $N -ne 0 ] && ACOUSTIC="-M $N"

    [ -n "$LAPTOP" ] && laptop_mode_func $LAPTOP
    if [ -n "$ACOUSTIC" -o -n "$STBY" -o -n "$RAW" ]; then
        exec_hdparm "$ACOUSTIC" "$STBY" "$RAW"
    fi
    return 0
}

# first get helper functions (e.g. DEBUG, load_scheme, ...)
# this also sets EV_ID and traps for erroneous exit.
. "${0%/*}/helper_functions"

# get the config
. /etc/sysconfig/powersave/disk
# set the defaults. This is easier than changing all occurrences above
HD_MAX_AGE=${HD_MAX_AGE:-600}
HD_DIRTY_RATIO=${HD_DIRTY_RATIO:-75}

# then get scheme specific settings:
SCHEME_TO_EXECUTE=$2
if [ -n $SCHEME_TO_EXECUTE ];then
    load_scheme $SCHEME_TO_EXECUTE
else
    load_scheme
fi

if [ $? != 0 ]; then
    DEBUG "Could not execute scheme settings" ERROR
    $SCRIPT_RETURN $EV_ID 1 "execute_disk_settings finished"
    EXIT 1
fi

execute_disk_settings

$SCRIPT_RETURN $EV_ID 0 "execute_disk_settings finished"
EXIT 0
