#!/bin/bash . "/etc/sysconfig/powersave/sleep" # the statefiles are used to record the system state before the sleep state # and restore it after resume. Stopped services, unloaded modules etc. are # recorded in these files. STATE1=/var/lib/suspend2disk-state # statefile for suspend-to-disk STATE2=/var/lib/suspend2ram-state # statefile for suspend-to-ram STATE3=/var/lib/standby-state # statefile for standby # the logfiles log information (such as the output of rc-scripts) in addition # to the statefiles to aid users in debugging suspend failures. LSMOD_LOG1=/var/log/suspend2disk.log # logfile for suspend-to-disk LSMOD_LOG2=/var/log/suspend2ram.log # logfile for suspend-to-ram LSMOD_LOG3=/var/log/standby.log # logfile for standby POWERSAVE_BIN="/usr/bin/powersave" # are we APM or ACPI? # $POWERSAVE_BIN -S &> /dev/null # APM_ACPI=$? # ACPI: 1, APM: 2, don't know: -1 ###### not needed now ######## # this recursively unloads the given modules and all that depend on it # first parameter is the module to be unloaded # second parameter is the state file, where all unloaded modules are # recorded for reloading after resume. # third parameter is the log file for the user. unload_module(){ local LOGFILE STATEFILE local MOD D C USED MODS I local UNL=$1 RET=1 # we need the statefile to be set [ -z "$2" ] && DEBUG "no STATEFILE set in unload_module" ERROR && return 255 [ -n "$3" ] && LOGFILE="$3" || LOGFILE=/dev/null STATEFILE="$2" # RET is the return code. If at least one module was unloaded, return 0. # if no module was unloaded successfully, return 1 # if there was no module to unload, SUCCESS!, return 0 while read MOD D C USED D; do [ "$MOD" != "$UNL" ] && continue if [ "$USED" == "-" ]; then if [ $C -eq 0 ]; then echo "# trying to unload: $UNL" >> $STATEFILE echo "# trying to unload: $UNL" >> $LOGFILE if rmmod $UNL; then echo "unloaded: $UNL" >> $STATEFILE echo "unloaded: $UNL" >> $LOGFILE RET=0 else DEBUG "could not unload module '$UNL'" WARN echo "could not unload module '$UNL', usage count was 0." >> $LOGFILE fi else echo "could not unload module '$UNL', usage count: $C" >> $LOGFILE fi else USED=${USED//,/ } MODS=($USED) # it is slightly more likely to rmmod in one pass, if we try backwards. # is this really true? I don't know, but it doesn't hurt either. for I in `seq $[${#MODS[@]}-1] -1 0`; do MOD=${MODS[$I]} unload_module $MOD $STATEFILE $LOGFILE && RET=0 done # if we unloaded at least one module, then let's try again! [ $RET -eq 0 ] && unload_module $UNL $STATEFILE $LOGFILE RET=$? fi return $RET done < /proc/modules # if we came this far, there was nothing to do, the module is no longer loaded. return 0 } ####################################################### # unmount_fatfs function # unmounts all (v)fat/ntfs partitions if possible. # unmount_fatfs(){ local I J K # counters local D E # dummies local RET DEV MNT OPT TYPE MESSAGE MTAB declare -a DEV MNT OPT TYPE # arrays for the mtab entries echo "== Unmounting FAT/NTFS filesystems: ==" >> $LSMOD_LOG MESSAGE=""; K=0 MTAB=/etc/mtab # or /proc/mounts? which one is better? exec 2>&1 # why? # ugh, this is ugly. But remember, we could be having a mountpoint named # "/mnt/mount point name with whitespace in it" # yes, ugly quoting hell. eval `awk ' BEGIN {X=0} { if (($3=="ntfs")||($3=="vfat")||($3==fat)) { Y=$2; gsub("\\\\\\\\", "\\\\\\\\\\\\", Y); print "DEV["X"]="$1; print "MNT["X"]=$(echo -e "Y")"; print "TYP["X"]="$3; print "OPT["X"]="$4; X++} }' $MTAB` echo "Checking for mounted fat/ntfs filesystems:" # now do something with the list of devices / mountpoints... for (( I=0; ${#DEV[$I]}; I++ )); do echo " device ${DEV[$I]} mounted on '${MNT[$I]}'" >> $LSMOD_LOG # stderr is additional information from fuser which we have to drop. # We need only the PIDs. They are on stdout. for J in $(fuser -m "${MNT[$I]}" 2>/dev/null);do while read D E; do case $D in Name:) if [ "$E" != "prepare_suspend" ]; then MESSAGE=$MESSAGE" $E($J)" echo " is accessed by $E($J)" >> $LSMOD_LOG let K++ fi break ;; *) ;; esac done < /proc/$J/status done done [ $I -eq 0 ] && echo " none found in $MTAB" >> $LSMOD_LOG exec 3>&2 # why? if [ $K -ne 0 ];then # one or more filesystems in use echo " filesystems in use, asking user..." >> $LSMOD_LOG case $K in 1) MESSAGE="Process $MESSAGE is" ;; *) MESSAGE="Processes $MESSAGE are" ;; esac MESSAGE=$MESSAGE" accessing an ntfs/fat mountpoint. \ Please make sure fat/ntfs mountpoints are unmountable before proceeding." question "$MESSAGE" "1" RET=$? if [ $RET -ne 0 ]; then echo " user does not want to continue. aborting suspend" >> $LSMOD_LOG $SCRIPT_RETURN "$EV_ID|4|101" $SCRIPT_RETURN "$EV_ID|1|prepare_sleep failed ($1): $MESSAGE" restore_after_sleep "$1" exit 1; fi echo " user wants to continue anyway. Good luck." >> $LSMOD_LOG fi # no filesystem in use. Or user answered "proceed anyway". # umount ntfs/fat mount points ... for (( I=0; ${#DEV[$I]}; I++ )); do echo "# trying to umount device: ${DEV[$I]}" >> $STATE echo "trying to umount device: '${DEV[$I]}' '${MNT[$I]}' -t '${TYP[$I]}' -o '${OPT[$I]}'" >> $LSMOD_LOG umount ${MNT[$I]}; RET=$? if [ $RET -eq 0 ]; then # very verbose, but we have the whole mount command line ready for remount. # every variable in one line, makes it easier to re-parse (remember: # whitespace danger!) echo "unmounted: ${DEV[$I]}" >> $STATE echo "# mountpoint unmounted: ${MNT[$I]}" >> $STATE echo "# fstype unmounted: -t ${TYP[$I]}" >> $STATE echo "# options unmounted: -o ${OPT[$I]}" >> $STATE echo " success." >> $LSMOD_LOG else echo "umount failed. Asking user what to do." >> $LSMOD_LOG MESSAGE="Unable to unmount device ${DEV[$I]}." DEBUG "$MESSAGE Asking the user" ERROR question "$MESSAGE Proceed anyway?" "1" RET=$? if [ $RET -ne 0 ]; then echo "user does not want to continue (good). Aborting suspend." >> $LSMOD_LOG $SCRIPT_RETURN "$EV_ID|4|101" $SCRIPT_RETURN "$EV_ID|1|prepare_sleep failed ($1): $MESSAGE" restore_after_sleep "$1" exit 1; fi echo "user wants to continue anyway. Good luck." >> $LSMOD_LOG fi done echo "== FAT/NTFS filesystems unmounted ==" >> $LSMOD_LOG } ##### unmount_fatfs() ######################################################## # remount_fatfs function # remounts the filesystems that were unmounted by # unmount_fatfs() remount_fatfs(){ local D I # dummy, counter local DEV MNT TYP OPT declare -a DEV MNT TYP OPT echo >> $LSMOD_LOG echo "Remounting filesystems:" >> $LSMOD_LOG I=0 while read DEV[$I]; do read MNT[$I] read TYP[$I] read OPT[$I] let I++ done < <(awk -F 'unmounted: ' '/^unmounted:/ { print $2; getline; print $2; getline; print $2; getline; print $2 }' ${STATE}.resume ) [ $I -eq 0 ] && echo " not necessary." >> $LSMOD_LOG let I-- # incremented once too many times # we remount in reverse order... for (( ; I>=0 ; I-- )) ; do mount ${DEV[$I]} ${MNT[$I]} ${OPT[$I]} ; D=$? if [ $D -eq 0 ];then DEBUG "remounted ${DEV[$I]}" DIAG echo " mounted '${DEV[$I]}' to '${MNT[$I]}', options '${OPT[$I]}'" >> $LSMOD_LOG else DEBUG "unable to remount ${DEV[$I]}" WARN echo " could not mount '${DEV[$I]}' to '${MNT[$I]}', options '${OPT[$I]}', error $D" >> $LSMOD_LOG fi done } # remount_fatfs ####################################################### # set_variables func # # function to set the variables according to what # we are going to do. # internally, use only in sleep_helper_functions set_variables(){ case "$1" in suspend2disk) STATE=$STATE1 LSMOD_LOG=$LSMOD_LOG1 MODULES_TO_UNLOAD="$POWERSAVE_UNLOAD_MODULES_BEFORE_SUSPEND2DISK" SERVICES_TO_RESTART="$POWERSAVE_SUSPEND2DISK_RESTART_SERVICES" RESTORE_CLOCK="$POWERSAVE_SUSPEND2DISK_RESTORE_CLOCK" UNMOUNT_FATFS="$POWERSAVE_SUSPEND2DISK_UNMOUNT_FATFS" ;; suspend2ram) STATE=$STATE2 LSMOD_LOG=$LSMOD_LOG2 MODULES_TO_UNLOAD="$POWERSAVE_UNLOAD_MODULES_BEFORE_SUSPEND2RAM" SERVICES_TO_RESTART="$POWERSAVE_SUSPEND2RAM_RESTART_SERVICES" RESTORE_CLOCK="$POWERSAVE_SUSPEND2RAM_RESTORE_CLOCK" UNMOUNT_FATFS="$POWERSAVE_SUSPEND2RAM_UNMOUNT_FATFS" ;; standby) STATE=$STATE3 LSMOD_LOG=$LSMOD_LOG3 MODULES_TO_UNLOAD="$POWERSAVE_UNLOAD_MODULES_BEFORE_STANDBY" SERVICES_TO_RESTART="$POWERSAVE_STANDBY_RESTART_SERVICES" RESTORE_CLOCK="$POWERSAVE_STANDBY_RESTORE_CLOCK" UNMOUNT_FATFS="$POWERSAVE_STANDBY_UNMOUNT_FATFS" ;; *) echo "Wrong parameter '$1' in set_variables function in sleep_helper_functions script" ;; esac } ####################################################### # # PREPARE_SLEEP FUNC # # Function to unload modules and stop services for # a soon triggered/coming sleep mode # # give first param: # # suspend2disk # suspend2ram # standby # # second param is the id prepare_sleep(){ local D E X # dummies,counters local RET TYPE ide [ -z $1 ] && echo "Do not invoke this script from console, it is automatically invoked by the powersave daemon" && exit 1 # set_variables "$1" prepare_sleep rm -f $STATE rm -f $LSMOD_LOG DEBUG "Stop services: $SERVICES_TO_RESTART" DEBUG DEBUG "Modules to unload: $MODULES_TO_UNLOAD" DEBUG # there should be at least one line in $STATE or we will get an # harmless but ugly error in restore_after_sleep. echo "# this file records the system state at entering $1." > $STATE echo "$1 initiated: `date +'%F %X'`" > $LSMOD_LOG echo "Loaded modules:" >> $LSMOD_LOG lsmod >> $LSMOD_LOG echo >> $LSMOD_LOG echo "Memory info:" >> $LSMOD_LOG free >> $LSMOD_LOG echo >> $LSMOD_LOG echo "------------------------------------------------------------------------------" >> $LSMOD_LOG echo "========we are going to sleep, preparing.========" >> $LSMOD_LOG ####### Switch to console and remember settings ########## # number of the current console rm -f /var/run/sleep_remember_consolenumber /bin/fgconsole > /var/run/sleep_remember_consolenumber # switch to console 2 /bin/chvt 2 # save video state rm -f /var/run/sleep_remember_vbestate /usr/sbin/vbetool vbestate save > /var/run/sleep_remember_vbestate ####### Switch to console and remember settings ########## ####### Check for devices which need to be remounted after suspend ####### [ "$UNMOUNT_FATFS" == "yes" ] && unmount_fatfs "$1" "$EV_ID" $SCRIPT_RETURN "$EV_ID|4|20" D="$SERVICES_TO_RESTART" E=${D:+\'}${D:-none}${D:+\'} echo "Stopping services: ($E configured)" >> $LSMOD_LOG ####### S t o p S e r v i c e s ########## if [ "$SERVICES_TO_RESTART" ]; then D=false for X in $SERVICES_TO_RESTART; do /etc/init.d/$X status >/dev/null 2>&1 ; RET=$? # redirect needed to workaround initscript bug. if [ $RET -eq 0 ]; then echo "stopping $X:" >> $LSMOD_LOG /etc/init.d/$X stop 2>&1 | awk '{print "## "$0}' >> $LSMOD_LOG echo "stopped: $X" >> $STATE DEBUG "Service $X stopped" DIAG D=true else DEBUG "Service $X stop requested but was not running. Return code: '$RET'" INFO fi done fi $D || echo "none running." >> $LSMOD_LOG ####### S t o p S e r v i c e s ########## $SCRIPT_RETURN "$EV_ID|4|25" echo >> $LSMOD_LOG D="$MODULES_TO_UNLOAD" echo "------------------------------------------------------------------------------" >> $LSMOD_LOG echo "Unloading modules: (${D:+´}${D:-none}${D:+´} configured)" >> $LSMOD_LOG ####### U n l o a d M o d u l e s ########## if [ "$MODULES_TO_UNLOAD" ]; then for module in $MODULES_TO_UNLOAD; do echo "checking $module" >> $LSMOD_LOG # unload_module handles not-loaded modules gracefully. if ! unload_module $module $STATE $LSMOD_LOG; then MESSAGE="$1 failed on unloading '$module'." if [ "$UNL" != "$module" -a -n "$UNL" ]; then MESSAGE="$MESSAGE The module that refused to unload was '$UNL'." fi MESSAGE="$MESSAGE Trying to recover..." DEBUG "$MESSAGE" ERROR notify "$MESSAGE" ERROR CONTINUE $EV_ID $SCRIPT_RETURN "$EV_ID|4|101" $SCRIPT_RETURN "$EV_ID|1|$MESSAGE" restore_after_sleep "$1" exit 1 fi done DEBUG "Modules unloaded" DIAG fi ####### U n l o a d M o d u l e s ########## $SCRIPT_RETURN "$EV_ID|4|35" sync ### TODO: implement nicer, what about non-IDE disks? ### is this really still needed? ...better safe than sorry. for ide in /proc/ide/ide?/hd?; do TYPE="" read TYPE < $ide/media case "$TYPE" in disk) DEBUG "We have a disk: /dev/${ide##*/}, Execute: blockdev --flushbufs /dev/${ide##*/}" INFO /sbin/blockdev --flushbufs /dev/${ide##*/} &>/dev/null [ $? != 0 ] && DEBUG "blockdev returned an error" WARN ;; *) DEBUG "We have no disk: /dev/${ide##*/}" DEBUG ;; esac done echo "------------------------------------------------------------------------------" >> $LSMOD_LOG echo "prepare_sleep finished for $1" >> $LSMOD_LOG echo "------------------------------------------------------------------------------" >> $LSMOD_LOG } ######################################################## # restore_after_sleep FUNC # # Function to load previously unloaded modules and start # the stopped services for after resuming from a sleep # sleep mode. Modules are loaded in reverse unloading # order, services are started in reverse stopping order. # Only hotplug is an exception: it is always started # first to handle hotplug events generated by module loading. # # give first param: # suspend2disk # suspend2ram # standby restore_after_sleep() { local D X [ -z $1 ] && echo "Do not invoke this script from console, it is automatically invoked by the powersave daemon" && exit 1 echo >> $LSMOD_LOG echo "== restore_after_sleep: restart and reload everything ==" >> $LSMOD_LOG # first: set the clock... [ "$RESTORE_CLOCK" = "yes" ] && restore_clock # clean up after ourselves.. touch $STATE # if it is not there for any reason, create it. mv -f $STATE ${STATE}.resume # now move it out of the way. echo >> $LSMOD_LOG echo "Resuming:" >> $LSMOD_LOG echo "---------" >> $LSMOD_LOG # special case: hotplug should be started before inserting modules if D=`awk 'BEGIN {X=1} /^stopped: (boot\.|)hotplug/ {print $2; X=0} END {exit X}' ${STATE}.resume`; then echo "first starting $D:" >> $LSMOD_LOG /etc/init.d/$D start 2>&1 | awk '{print "## "$0}' >> $LSMOD_LOG DEBUG "Service $D started again" DIAG fi # echo $STATE echo >> $LSMOD_LOG echo "Reloading modules:" >> $LSMOD_LOG ####### L o a d M o d u l e s ########## if [ "$MODULES_TO_UNLOAD" -a -s "${STATE}.resume" ]; then for module in `tac ${STATE}.resume| awk '/^unloaded:/ { print $2 }'`; do echo " $module" >> $LSMOD_LOG modprobe $module done DEBUG "Modules loaded" DIAG fi # rm ${STATE}.resume # disabled for debugging ####### L o a d M o d u l e s ########## echo >> $LSMOD_LOG echo "Restarting services:" >> $LSMOD_LOG ####### S t a r t S e r v i c e s (but not hotplug or boot.hotplug) ########## if [ "$SERVICES_TO_RESTART" -a -s "${STATE}.resume" ]; then for X in `tac ${STATE}.resume | awk '/^stopped:/ { if (($2!="hotplug")&&($2!="boot.hotplug")) print $2 }'`; do echo "starting $X:" >> $LSMOD_LOG /etc/init.d/$X start 2>&1 | awk '{print "## "$0}' >> $LSMOD_LOG DEBUG "Service $X started again" DIAG done fi ####### S t a r t S e r v i c e s ########## # # powersave workaround requires an initial socket request after suspend #$POWERSAVE_BIN -c >/dev/null ####### remount previously unmounted devices ####### remount_fatfs ####### Switch back to X and restore settings ########## # restore vbetool configs /usr/sbin/vbetool post /usr/sbin/vbetool vbestate restore < /var/run/sleep_remember_vbestate rm -f /var/run/sleep_remember_vbestate # get old consolenumber and switch to it CONSOLENUMBER=`cat /var/run/sleep_remember_consolenumber` rm -f /var/run/sleep_remember_consolenumber if test "x${CONSOLENUMBER}" != "x"; then /bin/chvt ${CONSOLENUMBER} fi ####### Switch back to X and restore settings ########## $SCRIPT_RETURN "$EV_ID|0|restore_after_$1 finished" return 0 } ######################################################## # restore_clock function # restores the system clock from the hardware clock. # variables are already set, so no magic needed here. # restore_clock(){ echo -n "Restoring system clock. From: $(date +%D_%T), " >> $LSMOD_LOG /sbin/hwclock --hctosys echo "To: $(date +%D_%T)" >> $LSMOD_LOG return 0 } ##################################################################### # get_kernels # gets a list of available kernels from /boot/grub/menu.lst # kernels are in the array $KERNELS, output to stdout to be eval-ed. get_kernels(){ DEBUG "Running get_kernels()" INFO local MENU_LST="/boot/grub/menu.lst" local I DUMMY declare -i I=0 J=-1 # build an array KERNELS with all the kernels in /boot/grub/menu.lst # the array MENU_ENTRIES contains the corresponding menu entry numbers # DEFAULT_BOOT contains the default entry. while read LINE; do case $LINE in title*) let J++ # increase for every menu entry, even for non-linux DEBUG "Found grub menu entry #${J}: '${LINE}'" INFO ;; default*) DUMMY=($LINE) # "default 0 #maybe a comment" echo "DEFAULT_BOOT=${DUMMY[1]}" # ^^[0]^^ 1 ^^[2]^ 3 ^^[4]^^ DEBUG "Default boot entry is '${DUMMY[1]}'" INFO ;; kernel*) DUMMY=($LINE) # kernel (hd0,1)/boot/vmlinuz-ABC root=/dev/hda2 echo "KERNELS[$I]='${DUMMY[1]##*/}'" # vmlinuz-ABC echo "MENU_ENTRIES[$I]=$J" DEBUG "Found kernel entry #${I}: '${DUMMY[1]##*/}'" INFO let I++ ;; *) ;; esac done < $MENU_LST } ############################################################# # grub-once() # does the same as the grubonce script from the grub package: # selects which menu entry to boot next. grub-once() { DEBUG "running grub-once $1" DIAG echo -e "savedefault --stage2=/boot/grub/stage2 --default=$1 --once\nquit"\ | grub --batch }