#!/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="/usr/bin/powersave" PCCARDCTL="/sbin/pccardctl" # if you still have cardmgr, use "/sbin/cardctl" GRUBONCE="/usr/sbin/grubonce" # are we APM or ACPI? # $POWERSAVE -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 progress "${_M08} $UNL" 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 progress "${_M04}" 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)||($3=="smbfs")) { 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 progress_finish $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 progress_finish $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 if [ "${TYP[$I]}" = "-t smbfs" ]; then echo "Attempting: mount ${DEV[$I]}" >> $LSMOD_LOG mount ${DEV[$I]} ; D=$? else mount ${DEV[$I]} ${MNT[$I]} ${OPT[$I]} ; D=$? fi 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 DEFAULT_S2D_UNLOAD="usb_storage sbp2 ohci_hcd uhci_hcd stir4200 ohci1394 ipw2200 rt2500 prism54 lt_modem Intel536 Intel537" DEFAULT_S2R_UNLOAD="usb_storage sbp2 ohci_hcd uhci_hcd stir4200 ohci1394 ipw2200 rt2500 prism54 lt_modem Intel536 Intel537" DEFAULT_STB_UNLOAD="usb_storage sbp2 ohci_hcd uhci_hcd stir4200 ohci1394 ipw2200 rt2500 prism54 lt_modem Intel536 Intel537" DEFAULT_S2D_RESTART="slmodemd irda" DEFAULT_S2R_RESTART="slmodemd irda" DEFAULT_STB_RESTART="slmodemd irda" set_variables(){ case "$1" in suspend2disk) STATE=$STATE1 LSMOD_LOG=$LSMOD_LOG1 EJECT_PCMCIA=$SUSPEND2DISK_EJECT_PCMCIA MODULES_TO_UNLOAD="${UNLOAD_MODULES_BEFORE_SUSPEND2DISK:-$DEFAULT_S2D_UNLOAD}" SERVICES_TO_RESTART="${SUSPEND2DISK_RESTART_SERVICES:-$DEFAULT_S2D_RESTART}" RESTORE_CLOCK="$SUSPEND2DISK_RESTORE_CLOCK" UNMOUNT_FATFS="${SUSPEND2DISK_UNMOUNT_FATFS:-yes}" SWITCH_VT="$SUSPEND2DISK_SWITCH_VT" ;; suspend2ram) STATE=$STATE2 LSMOD_LOG=$LSMOD_LOG2 EJECT_PCMCIA=$SUSPEND2RAM_EJECT_PCMCIA MODULES_TO_UNLOAD="${UNLOAD_MODULES_BEFORE_SUSPEND2RAM:-$DEFAULT_S2R_UNLOAD}" SERVICES_TO_RESTART="${SUSPEND2RAM_RESTART_SERVICES:-$DEFAULT_S2R_RESTART}" RESTORE_CLOCK="$SUSPEND2RAM_RESTORE_CLOCK" UNMOUNT_FATFS="${SUSPEND2RAM_UNMOUNT_FATFS:-no}" SWITCH_VT="$SUSPEND2RAM_SWITCH_VT" ;; standby) STATE=$STATE3 LSMOD_LOG=$LSMOD_LOG3 EJECT_PCMCIA=$STANDBY_EJECT_PCMCIA MODULES_TO_UNLOAD="${UNLOAD_MODULES_BEFORE_STANDBY:-$DEFAULT_STB_UNLOAD}" SERVICES_TO_RESTART="${STANDBY_RESTART_SERVICES:-$DEFAULT_STB_RESTART}" RESTORE_CLOCK="$STANDBY_RESTORE_CLOCK" UNMOUNT_FATFS="${STANDBY_UNMOUNT_FATFS:-no}" SWITCH_VT="$STANDBY_SWITCH_VT" ;; *) 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 ####### Check for devices which need to be remounted after suspend ####### let PERCENT+=$STEP [ "$UNMOUNT_FATFS" == "yes" ] && unmount_fatfs "$1" "$EV_ID" ####### S t o p S e r v i c e s ########## let PERCENT+=$STEP progress "${_M05}" D="$SERVICES_TO_RESTART" E=${D:+\'}${D:-none}${D:+\'} echo "Stopping services: ($E configured)" >> $LSMOD_LOG D=true if [ "$SERVICES_TO_RESTART" != "NONE" ]; 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 progress "${_M06} $X" /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 ########## ####### Eject PCMCIA cards ########## let PERCENT+=$STEP if [ "$EJECT_PCMCIA" == "yes" ]; then progress "${_M03}" echo "ejecting PCMCIA cards..." >> $LSMOD_LOG $PCCARDCTL eject fi ####### U n l o a d M o d u l e s ########## let PERCENT+=$STEP progress "${_M07}" echo >> $LSMOD_LOG D="$MODULES_TO_UNLOAD" E=${D:+\'}${D:-none}${D:+\'} echo "------------------------------------------------------------------------------" >> $LSMOD_LOG echo "Unloading modules: ($E configured)" >> $LSMOD_LOG if [ "$MODULES_TO_UNLOAD" ]; then for module in $MODULES_TO_UNLOAD; do [ "$module" == "NONE" ] && continue 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 progress_finish $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 ########## let PERCENT+=$STEP progress "" # sync #we sync later, so comment this out. ### TODO: implement nicer, what about non-IDE disks? ### is this really still needed? ...better safe than sorry. ######################################################################## # seife: i am commenting this out for now. # we do lots of stuff afterwards, so this is probably not worth anything # but may slow down things considerably. # i will talk to the kernel guys if it is needed or not. ######################################################################## # 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 # sanity check. We had this once... if [ ! -e $STATE ]; then echo "WARNING: Statefile '$STATE' disappeared during suspend!" >> $LSMOD_LOG DEBUG "Statefile '$STATE' disappeared during suspend." ERROR fi # 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 switch_to_X # 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 ########## ####### reinsert PCMCIA cards ########## if [ "$EJECT_PCMCIA" == "yes" ]; then echo "inserting PCMCIA cards..." >> $LSMOD_LOG $PCCARDCTL insert fi 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 -c >/dev/null ####### remount previously unmounted devices ####### remount_fatfs $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() { if [ -x "$GRUBONCE" ]; then DEBUG "running '$GRUBONCE $1'" DIAG $GRUBONCE $1 else DEBUG "$GRUBONCE not found, not preparing bootloader" DIAG fi } ############################################################# # progress # pops up and updates a progress bar. # needs global variables: # PERCENT # EV_ID # takes one argument: the message. progress() { if [ -z "$PERCENT" -o -z "$EV_ID" -o -z "$SCRIPT_RETURN" ]; then DEBUG "progress called without variables set." ERROR return 1 fi $SCRIPT_RETURN $EV_ID 4 "${PERCENT}${1:+|$1}" } ############################################################# # progress_finish # closes the progress bar progress_finish() { PERCENT=101 progress "" } ############################################################# # switch_to_vt # switches to text console 1 switch_to_vt() { if [ "$SWITCH_VT" = "yes" ]; then echo "console no: `fgconsole`" >> $STATE chvt 1 fi } ############################################################# # switch_to_X # switches back to the console we were before suspend switch_to_X() { if [ "$SWITCH_VT" = "yes" ]; then local CONS CONS=$(awk -F : '/^console no:/ {print $2}' ${STATE}.resume) [ -n "$CONS" ] && chvt $CONS echo "switched back to console: '$CONS'" >> $LSMOD_LOG fi }