#!/bin/bash # start or stop laptop_mode, best run by a power management daemon when # ac gets connected/disconnected from a laptop # # install as /usr/sbin/laptop_mode # # Contributors to this script: Kiko Piris # Bart Samwel # Micha Feigin # Andrew Morton # Herve Eychenne # Dax Kelson # # Original Linux 2.4 version by: Jens Axboe ############################################################################# # Source config. if [ -f /etc/laptop-mode/laptop-mode.conf ] ; then . /etc/laptop-mode/laptop-mode.conf else echo $0: Configuration file /etc/laptop-mode/laptop-mode.conf not present. exit 1 fi # Support for old config settings if [ "$AC_HD" != "" ] ; then AC_HD_WITHOUT_LM="$AC_HD" AC_HD_WITH_LM="$AC_HD" fi ############################################################################# KLEVEL="$(uname -r | { IFS='.' read a b c echo $a.$b } )" case "$KLEVEL" in "2.4"|"2.6") ;; *) echo "Unhandled kernel version: $KLEVEL ('uname -r' = '$(uname -r)')" >&2 exit 1 ;; esac if [ ! -e /proc/sys/vm/laptop_mode ] ; then echo "Kernel is not patched with laptop_mode patch." >&2 exit 1 fi if [ ! -w /proc/sys/vm/laptop_mode ] ; then echo "You do not have enough privileges to enable laptop_mode." >&2 exit 1 fi # Remove an option (the first parameter) of the form option= from # a mount options string (the rest of the parameters). parse_mount_opts () { OPT="$1" shift echo ",$*," | sed \ -e 's|,'"$OPT"'=[0-9]*,|,|g' \ -e 's/,,*/,/g' \ -e 's/^,//' \ -e 's/,$//' } # Remove an option (the first parameter) without any arguments from # a mount option string (the rest of the parameters). parse_nonumber_mount_opts () { OPT="$1" shift echo ",$*," | sed \ -e 's|,'"$OPT"',|,|g' \ -e 's/,,*/,/g' \ -e 's/^,//' \ -e 's/,$//' } # Find out the state of a yes/no option (e.g. "atime"/"noatime") in # fstab for a given filesystem, and use this state to replace the # value of the option in another mount options string. The device # is the first argument, the option name the second, and the default # value the third. The remainder is the mount options string. # # Example: # parse_yesno_opts_wfstab /dev/hda1 atime atime defaults,noatime # # If fstab contains, say, "rw" for this filesystem, then the result # will be "defaults,atime". parse_yesno_opts_wfstab () { L_DEV="$1" OPT="$2" DEF_OPT="$3" shift 3 L_OPTS="$*" PARSEDOPTS1="$(parse_nonumber_mount_opts $OPT $L_OPTS)" PARSEDOPTS1="$(parse_nonumber_mount_opts no$OPT $PARSEDOPTS1)" # Watch for a default atime in fstab FSTAB_OPTS="$(awk '$1 == "'$L_DEV'" { print $4 }' /etc/fstab)" if echo "$FSTAB_OPTS" | grep "$OPT" > /dev/null ; then # option specified in fstab: extract the value and use it if echo "$FSTAB_OPTS" | grep "no$OPT" > /dev/null ; then echo "$PARSEDOPTS1,no$OPT" else # no$OPT not found -- so we must have $OPT. echo "$PARSEDOPTS1,$OPT" fi else # option not specified in fstab -- choose the default. echo "$PARSEDOPTS1,$DEF_OPT" fi } # Find out the state of a numbered option (e.g. "commit=NNN") in # fstab for a given filesystem, and use this state to replace the # value of the option in another mount options string. The device # is the first argument, and the option name the second. The # remainder is the mount options string in which the replacement # must be done. # # Example: # parse_mount_opts_wfstab /dev/hda1 commit defaults,commit=7 # # If fstab contains, say, "commit=3,rw" for this filesystem, then the # result will be "rw,commit=3". parse_mount_opts_wfstab () { L_DEV="$1" OPT="$2" shift 2 L_OPTS="$*" PARSEDOPTS1="$(parse_mount_opts $OPT $L_OPTS)" # Watch for a default commit in fstab FSTAB_OPTS="$(awk '$1 == "'$L_DEV'" { print $4 }' /etc/fstab)" if echo "$FSTAB_OPTS" | grep "$OPT=" > /dev/null ; then # option specified in fstab: extract the value, and use it echo -n "$PARSEDOPTS1,$OPT=" echo ",$FSTAB_OPTS," | sed \ -e 's/.*,'"$OPT"'=//' \ -e 's/,.*//' else # option not specified in fstab: set it to 0 echo "$PARSEDOPTS1,$OPT=0" fi } deduce_fstype () { MP="$1" # My root filesystem unfortunately has # type "unknown" in /etc/mtab. If we encounter # "unknown", we try to get the type from fstab. cat /etc/fstab | grep -v '^#' | while read FSTAB_DEV FSTAB_MP FSTAB_FST FSTAB_OPTS FSTAB_DUMP FSTAB_DUMP ; do if [ "$FSTAB_MP" = "$MP" ]; then echo $FSTAB_FST exit 0 fi done } if [ $DO_REMOUNT_NOATIME -eq 1 ] ; then NOATIME_OPT=",noatime" fi ACTION="$1" # Determine the power state. ON_AC=1 if [ -d /proc/acpi/ac_adapter ] ; then ADAPTERS_FOUND=0 ON_AC=0 echo 1 > /proc/acpi/thermal_zone/THM0/cooling_mode # COMMENTS on battery, passive mode for ADAPTER in /proc/acpi/ac_adapter/* ; do ADAPTERS_FOUND=1 STATUS=`awk '/^state: / { print $2 }' $ADAPTER/state` if [ "$STATUS" = "on-line" ] ; then ON_AC=1 echo 0 > /proc/acpi/thermal_zone/THM0/cooling_mode # when AC-online, active mode fi done elif [ -f /proc/apm ] ; then read D1 D2 D3 APM_AC_STATE D0 /proc/sys/vm/pagebuf/lm_flush_age echo $XFS_AGE > /proc/sys/fs/xfs/lm_sync_interval elif [ -f /proc/sys/fs/xfs/lm_age_buffer ] ; then # (A couple of early 2.6 laptop mode patches had these.) # The same goes for these. echo $XFS_AGE > /proc/sys/fs/xfs/lm_age_buffer echo $XFS_AGE > /proc/sys/fs/xfs/lm_sync_interval elif [ -f /proc/sys/fs/xfs/age_buffer ] ; then # (2.6.6) # But not for these -- they are also used in normal # operation. echo $XFS_AGE > /proc/sys/fs/xfs/age_buffer echo $XFS_AGE > /proc/sys/fs/xfs/sync_interval elif [ -f /proc/sys/fs/xfs/age_buffer_centisecs ] ; then # (2.6.7 upwards) # And not for these either. These are in centisecs, # not USER_HZ, so we have to use $AGE, not $XFS_AGE. echo $AGE > /proc/sys/fs/xfs/age_buffer_centisecs echo $AGE > /proc/sys/fs/xfs/xfssyncd_centisecs echo 3000 > /proc/sys/fs/xfs/xfsbufd_centisecs fi case "$KLEVEL" 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 "$LM_SECONDS_BEFORE_SYNC" > /proc/sys/vm/laptop_mode echo "$AGE" > /proc/sys/vm/dirty_writeback_centisecs echo "$AGE" > /proc/sys/vm/dirty_expire_centisecs echo "$DIRTY_RATIO" > /proc/sys/vm/dirty_ratio echo "$DIRTY_BACKGROUND_RATIO" > /proc/sys/vm/dirty_background_ratio ;; esac if [ $DO_REMOUNTS -eq 1 ]; then cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do PARSEDOPTS="$(parse_mount_opts "$OPTS")" if [ "$FST" = 'unknown' ]; then FST=$(deduce_fstype $MP) fi case "$FST" in "ext3"|"reiserfs") PARSEDOPTS="$(parse_mount_opts commit "$OPTS")" mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE$NOATIME_OPT ;; "xfs") mount $DEV -t $FST $MP -o remount,$OPTS$NOATIME_OPT ;; esac if [ -b $DEV ] ; then blockdev --setra $(($READAHEAD * 2)) $DEV > /dev/null 2>&1 fi done fi if [ $DO_CPU -eq 1 -a -e /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq ]; then if [ $CPU_MAXFREQ = 'slowest' ]; then CPU_MAXFREQ=`cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq` fi echo $CPU_MAXFREQ > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq fi echo "." ;; stop) if [ "`cat /proc/sys/vm/laptop_mode`" = "0" -a $DO_FORCE -eq 0 ] ; then echo Laptop mode was already disabled, not disabling. exit 0 fi U_AGE=$((100*$DEF_UPDATE)) B_AGE=$((100*$DEF_AGE)) echo -n "Stopping laptop_mode" echo 0 > /proc/sys/vm/laptop_mode if [ -f /proc/sys/fs/xfs/age_buffer -a ! -f /proc/sys/fs/xfs/lm_age_buffer ] ; then # These need to be restored, if there are no lm_*. echo $(($XFS_HZ*$DEF_XFS_AGE_BUFFER)) > /proc/sys/fs/xfs/age_buffer echo $(($XFS_HZ*$DEF_XFS_SYNC_INTERVAL)) > /proc/sys/fs/xfs/sync_interval elif [ -f /proc/sys/fs/xfs/age_buffer_centisecs ] ; then # These need to be restored as well. echo $((100*$DEF_XFS_AGE_BUFFER)) > /proc/sys/fs/xfs/age_buffer_centisecs echo $((100*$DEF_XFS_SYNC_INTERVAL)) > /proc/sys/fs/xfs/xfssyncd_centisecs echo $((100*$DEF_XFS_BUFD_INTERVAL)) > /proc/sys/fs/xfs/xfsbufd_centisecs fi case "$KLEVEL" in "2.4") echo "30 500 0 0 $U_AGE $B_AGE 60 20 0" > /proc/sys/vm/bdflush ;; "2.6") 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 ;; esac if [ $DO_REMOUNTS -eq 1 ] ; then cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do # Reset commit and atime options to defaults. if [ "$FST" = 'unknown' ]; then FST=$(deduce_fstype $MP) fi case "$FST" in "ext3"|"reiserfs") PARSEDOPTS="$(parse_mount_opts_wfstab $DEV commit $OPTS)" PARSEDOPTS="$(parse_yesno_opts_wfstab $DEV atime atime $PARSEDOPTS)" mount $DEV -t $FST $MP -o remount,$PARSEDOPTS ;; "xfs") PARSEDOPTS="$(parse_yesno_opts_wfstab $DEV atime atime $OPTS)" mount $DEV -t $FST $MP -o remount,$PARSEDOPTS ;; esac if [ -b $DEV ] ; then blockdev --setra 256 $DEV > /dev/null 2>&1 fi done fi if [ $DO_CPU -eq 1 -a -e /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq ]; then echo `cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq` > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq fi echo "." ;; *) echo "Usage: $0 {start|stop|auto}" 2>&1 exit 1 ;; esac # Adjust hard drive parameters HDPARM_POWERMGMT=$BATT_HDPARM_POWERMGMT HDPARM_SPINDOWN=$BATT_HD if [ $ON_AC -eq 1 ] ; then if [ "$ACTION" = "start" ] ; then HDPARM_POWERMGMT=$AC_HDPARM_POWERMGMT_WITH_LM HDPARM_SPINDOWN=$AC_HD_WITH_LM else HDPARM_POWERMGMT=$AC_HDPARM_POWERMGMT_WITHOUT_LM HDPARM_SPINDOWN=$AC_HD_WITHOUT_LM fi fi if [ $DO_HD_POWERMGMT -eq 1 ] ; then for THISHD in $HD ; do hdparm -B $HDPARM_POWERMGMT $THISHD > /dev/null 2>&1 done fi if [ $DO_HD -eq 1 ] ; then for THISHD in $HD ; do hdparm -S $HDPARM_SPINDOWN $THISHD > /dev/null 2>&1 done fi if [ $DO_SYSLOG -eq 1 ] ; then if [ $ON_AC -eq 1 ] ; then if [ "$ACTION" = "start" ] ; then ln -fs $AC_SYSLOG_WITH_LM $SYSLOG_CONF elif [ "$ACTION" = "stop" ] ; then ln -fs $AC_SYSLOG_WITHOUT_LM $SYSLOG_CONF fi else ln -fs $BATT_SYSLOG $SYSLOG_CONF fi # Notify syslogd of configuration change. killall -HUP syslogd fi exit 0