#!/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.
# 

# where do we store if _we_ remounted a device?
STORE="/var/run/powersaved.remount"

# 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
                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
            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
                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
            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 OLDOPTS DUMP PASS REMOUNT LINE

    REMOUNT=$1

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

    case $REMOUNT in
        yes)
            rm -f $STORE    # better safe than sorry.
            while read DEV MP FST OPTS DUMP PASS ; do
                OLDOPTS=$OPTS
                OPTS=${OPTS//commit=[0-9]*/}
                OPTS=${OPTS//,,/,}
                case "$FST" in
                    "ext3")
                        DEBUG "mount $DEV -t $FST $MP -o remount,$OPTS,commit=$HD_MAX_AGE" DIAG
                        mount $DEV -t $FST $MP -o remount,$OPTS,commit=$HD_MAX_AGE
                        echo "$DEV -t $FST $MP -o remount,$OLDOPTS" >> $STORE
                        ;;
                    "reiserfs")
                        [ "$HD_NOATIME" = "yes" ] && OPTS="$OPTS,noatime"
                        DEBUG "mount $DEV -t $FST $MP -o remount,$OPTS,commit=$HD_MAX_AGE" DIAG
                        mount $DEV -t $FST $MP -o remount,$OPTS,commit=$HD_MAX_AGE
                        echo "$DEV -t $FST $MP -o remount,$OLDOPTS" >> $STORE
                        ;;
                    "xfs")
                        DEBUG "mount $DEV -t $FST $MP -o remount,$OPTS" DIAG
                        mount $DEV -t $FST $MP -o remount,$OPTS
                        echo "$DEV -t $FST $MP -o remount,$OLDOPTS" >> $STORE
                        ;;
                esac
            done < /etc/mtab
            ;;
        no)
            if [ -f $STORE ]; then
                while read LINE; do
                    DEBUG "remounting with original options: 'mount $LINE'" DIAG
                    mount $LINE
                done < $STORE
                rm $STORE
            fi
            ;;
        *)  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 DEV DISKS DRIVE BUS
    local ACOU="$1"
    local STBY="$2"
    local  RAW="$3"
    local PROC="/proc/ide" # where to find all the IDE settings
    ARGS=""
    # Is this setting safe on all disks ?
    # we get the disks from HAL.
    DISKS=`hal-find-by-property --key storage.drive_type --string disk 2>&1`
    if [ $? -ne 0 ]; then
        DEBUG "could not retrieve list of disks from HAL: $DISKS" WARN
        return 0
    fi

    for DRIVE in $DISKS; do
        BUS=`hal-get-property --udi $DRIVE --key storage.bus`
        case $BUS in
            ide)
                DEV=`hal-get-property --udi $DRIVE --key block.device 2>&1`
                if [ $? -ne 0 ]; then
                    DEBUG "could not retrieve block device from HAL for udi '$DRIVE': $DEV" WARN
                    continue
                fi
                DEV=${DEV#/dev/}  # strip leading /dev/
                ############################################################
                # this case...esac can be taken out as soon as we know for #
                # sure that we do not get any false results from HAL.      #
                read TYPE < $PROC/$DEV/media
                case "$TYPE" in         # this should no longer be necessary with HAL.
                    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" WARN  # should never happen
                        ;;
                esac
                # take me out ASAP                                         #
                ############################################################
                ;;
            *)
                # other buses not handled yet...
                DEBUG "non-ide drive (udi='$DRIVE') ignored" DIAG
                ;;
        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)
            return 0 ;;
        *)
            DEBUG "unknown DISK_STANDBY_MODE 
                   Value: $DISK_STANDBY_MODE" ERROR
            return 1 ;;
    esac

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

    [ $N -ne 0 ] && STBY="-B $N"

    N=0
    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
