#!/bin/bash
###########################################################################
#                                                                         #
#                         Powersave Daemon                                #
#                                                                         #
#          Copyright (C) 2004,2005 SUSE Linux Products GmbH               #
#                                                                         #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the   #
# Free Software Foundation; either version 2 of the License, or (at you   #
# option) any later version.                                              #
#                                                                         #
# This program is distributed in the hope that it will be useful, but     #
# WITHOUT ANY WARRANTY; without even the implied warranty of              #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       #
# General Public License for more details.                                #
#                                                                         #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., #
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA                  #
#                                                                         #
#                                                                         #
# 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() {

    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 ACTIVE START
    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

    START=$1
    read ACTIVE < /proc/sys/vm/laptop_mode
    case "$START" in
        yes)
            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
            else
                DEBUG "laptop_mode already active, ignoring" INFO
            fi
            ;;
        no)
            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
            else
                DEBUG "laptop_mode already inactive, ignoring" DIAG
            fi
            ;;
        *)  DEBUG "invalid function call: laptop_mode_func '$START' '$REMOUNT'" ERROR
            return 1
            ;;
    esac
    return 0
}

#########################################################
# this function remounts filesystems with noatime and
# proper commit=XXX settings if necessary
do_remounts() {

    local DEV MP FST OPTS DUMP PASS REMOUNT

    REMOUNT=$1

    DEBUG "do remounts?: '$REMOUNT', set noatime: '$HD_NOATIME'" DIAG

    case $REMOUNT in
        yes)
            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
            ;;
        no)
            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
            ;;
        *)  DEBUG "invalid function call: do_remounts '$REMOUNT'" ERROR
            return 1
            ;;
    esac
    return 0
}

#########################################################
# this function checks if the disk supports APM and AAM
# "return codes" are passed via global variables (ugly).
#
# FIXME: what about SATA disks? 
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)
                DEBUG "We have a disk: /dev/$DEV" INFO
                get_disk_aam_apm $DEV
                $DISK_AAM && ARGS="$ARGS $ACOU" || \
                        DEBUG "  /dev/$DEV does not support AAM" DIAG
                $DISK_APM && ARGS="$ARGS $STBY" || \
                        DEBUG "  /dev/$DEV does not support APM" DIAG
                ARGS="$ARGS $RAW"
                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 REMOUNT
    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"
            REMOUNT="$HD_DO_REMOUNTS_PERF"
            LAPTOP="${HD_LAPTOP_MODE_PERF:-no}" ;;
        powersave)
            N="$HDPARM_STBY_SAVE"
            RAW="$HDPARM_RAW_SAVE"
            REMOUNT="$HD_DO_REMOUNTS_SAVE"
            LAPTOP="${HD_LAPTOP_MODE_SAVE:-no}" ;;
        aggressive_powersave|aggressiv_powersave)
            N="$HDPARM_STBY_AGGR"
            RAW="$HDPARM_RAW_AGGR"
            REMOUNT="$HD_DO_REMOUNTS_AGGR"
            LAPTOP="${HD_LAPTOP_MODE_AGGR:-yes}" ;;
        no|off)
            ;;
        *)
            DEBUG "unknown DISK_STANDBY_MODE 
                   Value: $DISK_STANDBY_MODE" ERROR ;;
    esac

    # if $HD_DO_REMOUNT_* is empty, fill in the value of $HD_LAPTOP_MODE_*
    REMOUNT=${REMOUNT:-$LAPTOP}

    [ $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

    do_remounts $REMOUNT

    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
. $SYSCONF_DIR/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
