#! /bin/sh
##### DO NOT ADD LINE BEFORE THIS ONE: --install depends on it

VERSION=0.3

# suspend - invokes software suspend
# ChangeLog:
# 0.3: - Add version, fix typos (french message :-) and add changelog.
#      - Modify hw clock use. Always read on resume, but write before
#        suspend is configurable (to fix drift amplification problem)
#      - Add a diff in install phase when configuration file already exists
#      - Change gpm place in default start/stop services.
#      - Add a test for root id
#      - Add some udelays for proper unloading of modules
#      - Add a lock file to prevent two scripts from running together
#      - Add a configuration option for using acpi interface or
#        enable reboot instead of halt
#      - Change alsa place in default start/stop services, since usb
#        and pcmcia can use snd module to notify events.
#      - Add a check on running of some incompatible programs.
#      - Restore console font
#      - Add a --nosuspend option
#      - Modify --install procedure to suit different distros
# 0.2: - Modification by Florent (fchabaud@free.fr): first version
#        published at sourceforge.net
#      - Add a --install option that installs the script and its
#        configuration file /etc/suspend.conf
#      - Add state control to exactly recover session when suspending
#        is aborted.
#      - Completely reorganize script (sorry Doug :-)
#        - suppress the smbfs and nfs unmounts (rationale: this
#          should be left to configuration - the user can set its samba
#          mount points if he/she wants them to be unmounted)
#        - suppress the grub/lilo alteration (rationale: I don't like
#          the idea that swsusp can modify the boot configuration of a machine ;-)
#        - modify the modules loading/unloading. The script now try to
#          unload all unused modules. On resume the specified modules
#          are loaded, whether they were unloaded or not (rationale:
#          this should be more robust).
# 0.1: First script posted by Doug (doug@tunl.duke.edu)
#

PATH=/bin:/sbin:/usr/bin
TMP=/dev/shm/suspend.$$
if ! (touch $TMP) ;then
    TMP=/tmp/suspend.$$
    if ! (touch $TMP) ;then
	echo "Can't open a temporary file in /dev/shm nor /tmp"
	echo "Aborting !"
	exit 1
    fi
fi

ID=`id -u`
if [ "$ID" != "0" ]; then
    echo "You must be root to use this script."
    echo "Use sudo to enable it for users."
    exit 1
fi

if ( echo $@ | grep -e "--silent" ) 1> /dev/null; then
    VERBOSE='/dev/null'
    HWCLKDEBUG=""
else
    VERBOSE='/dev/tty'
    HWCLKDEBUG="--debug"
fi

# This overides failsafe cancellation and forces suspend or installation
if ( echo $@ | grep -e "--force" ) 1> /dev/null; then
    FORCE='yes'
else
    FORCE='no'
fi

######################################### INSTALL #####################################################
if ( echo $@ | grep -e "--install" ) 1> /dev/null; then
    DISTRIB=Unknown
    if [ -d /etc/rc.config.d ]
    then
	DISTRIB=SuSE
    elif [ -x /usr/X11R6/bin/drakconf ]
    then
	DISTRIB=Mandrake
    elif [ -f /etc/debian_version ]
    then
  DISTRIB=Debian
    fi
    BINNAME=suspend
    case "$DISTRIB" in
	SuSE)
	    CONFIGFILE=/etc/rc.config.d/suspend.rc.config
	    INSTALLDIR=/usr/sbin
	    IFDOWN=/etc/init.d/network
	    OPTDOWN=stop
	    IFUP=/etc/init.d/network
	    OPTUP=start
	    SHELL=/bin/bash
	    LOCKFILE=/var/lock/subsys/swsusp
	    SETSYSFONT=/etc/init.d/kbd
	    OPTSYSFONT=start
	    DEFAULTSSERVICES="alsasound vmware irda gpm xntpd inetd dhclient network pcmcia usbmgr"
	    SWSUSP_UMOUNTS="/floppy /cdrom /var/adm/mount"
	;;
	Mandrake)
	    CONFIGFILE=/etc/suspend.conf
	    INSTALLDIR=/usr/local/sbin
	    IFDOWN=/etc/sysconfig/network-scripts/ifdown
	    OPTDOWN=
	    IFUP=/etc/sysconfig/network-scripts/ifup
	    OPTUP=
	    SHELL=/bin/bash
	    LOCKFILE=/var/lock/subsys/swsusp
	    SETSYSFONT=/sbin/setsysfont
	    OPTSYSFONT=
	    DEFAULTSSERVICES="vmware xntpd xinetd netfs gpm pcmcia irda sound alsa usb"
	    SWSUSP_UMOUNTS="/mnt/floppy /mnt/cdrom"
	;;
	Debian) 
	    CONFIGFILE=/etc/suspend.conf
	    INSTALLDIR=/usr/sbin
	    IFDOWN=/sbin/ifdown
	    OPTDOWN=
	    IFUP=/sbin/ifup
	    OPTUP=
	    SHELL=/bin/sh
	    LOCKFILE=/var/lock/swsusp
	    SETSYSFONT=/sbin/setsysfont
	    OPTSYSFONT=
	    DEFAULTSSERVICES="vmware xntpd inetd xinetd network gpm pcmcia irda sound alsa alsasound kbd usb usbmgr"
	    SWSUSP_UMOUNTS="/misc/floppy /misc/cd /mnt/floppy /mnt/cdrom /floppy /cdrom"
	;;
	*) 
	    CONFIGFILE=/etc/suspend.conf
	    INSTALLDIR=/usr/local/sbin
	    IFDOWN=/etc/sysconfig/network-scripts/ifdown
	    OPTDOWN=
	    IFUP=/etc/sysconfig/network-scripts/ifup
	    OPTUP=
	    SHELL=/bin/sh
	    LOCKFILE=/var/lock/swsusp
	    SETSYSFONT=/sbin/setsysfont
	    OPTSYSFONT=
	    DEFAULTSSERVICES="vmware xntpd inetd xinetd network gpm pcmcia irda sound alsa alsasound kbd usb usbmgr"
	    SWSUSP_UMOUNTS="/misc/floppy /misc/cd /mnt/floppy /mnt/cdrom /floppy /cdrom"
	;;
    esac

    DATE=`date`
    cat > $TMP <<EOF
#! $SHELL

# Installation: $DATE
# Script:       $0
# Distro:       $DISTRIB
CONFIGFILE=$CONFIGFILE
IFDOWN=$IFDOWN
OPTDOWN=$OPTDOWN
IFUP=$IFUP
OPTUP=$OPTUP
LOCKFILE=$LOCKFILE
SETSYSFONT=$SETSYSFONT
OPTSYSFONT=$OPTSYSFONT
# DO NOT EDIT THIS FILE (change the above script and reinstall)
EOF
    nb=`wc -l < $0`
    nb=$[ $nb - 2 ]
    tail -$nb $0 >> $TMP
    
    if [ ! -d $INSTALLDIR ] ;then
	echo "Directory $INSTALLDIR doesn't exist: installation aborted"
	rm $TMP
	exit 2
    fi
   
    echo "Creating $INSTALLDIR/$BINNAME" > $VERBOSE
    if ! (mv -f $TMP $INSTALLDIR/$BINNAME) ;then
	echo "Cannot install $INSTALLDIR/$BINNAME"
	rm $TMP
	exit 1
    fi
    chmod 700 $INSTALLDIR/$BINNAME
    SWSUSP_STOP_SERVICES_BEFORE_SUSPEND=
    for service in $DEFAULTSSERVICES ;do
	if [ -f /etc/init.d/$service ]
	then
	    SWSUSP_STOP_SERVICES_BEFORE_SUSPEND="$SWSUSP_STOP_SERVICES_BEFORE_SUSPEND $service"
	fi
    done
    cat > $TMP <<EOF
#-*-mode: sh-*-
# Configuration of software suspension
#

# If you have problems with hardware clock drift amplified by
# suspension, try to set SAVE_CLOCK_ON_SUSPEND to "no". The
# kerneltime will keep being set on resume, but the hardware
# clock will not be affected before suspending. If you have
# network access, best is to set this vriable to yes and add
# xntpd in services to start/stop below.
# Default: "yes"
SWSUSP_SAVE_CLOCK_ON_SUSPEND="yes"

# If your graphic device is not able to return properly from suspend
# you can switch to textconsole before suspend and return to your
# X-console after resume.
# Default: "no"
SWSUSP_LEAVE_X_BEFORE_SUSPEND="no"

# Some services (e.g. network) may cause some hangs if they are not
# stopped before a suspend/resume cycle. You can set
# STOP_SERVICES_BEFORE_SUSPEND to a list of services to stop 
# before suspend.
# If suspending results in killing some application because of lack
# of memory, you may also add here some of your launched services,
# so as to save memory. Good candidates are httpd, nfsserver, sendmail, etc.
# Default: "$SWSUSP_STOP_SERVICES_BEFORE_SUSPEND"
SWSUSP_STOP_SERVICES_BEFORE_SUSPEND="$SWSUSP_STOP_SERVICES_BEFORE_SUSPEND"

# Conversely, you can set START_SERVICES_AFTER_RESUME to a list
# of services to start after resuming.
# With "auto" the services stopped before suspension will be started
# in reverse order.
# Default: "auto"
SWSUSP_START_SERVICES_AFTER_RESUME="auto"

# Some modules should be unloaded before a suspend/resume cycle. You
# can set UNLOAD_MODULES_BEFORE_SUSPEND to "yes" if you want
# unused modules to be removed from kernel space before suspend. 
# This will be done after stopping services.
# With "no", nothing will be done before suspension.
# Default: "yes"
SWSUSP_UNLOAD_MODULES_BEFORE_SUSPEND="yes"

# If the following mount points cannot be unmounted, 
# then suspension is aborted unless --force or --kill
# option is used on command line
# Default: "$SWSUSP_UMOUNTS"
SWSUSP_UMOUNTS="$SWSUSP_UMOUNTS"

# These mount points should be mounted after suspend
# They should appear in /etc/fstab
# Default: none
SWSUSP_REMOUNTS=""

# If the following interfaces cannot be stopped, 
# then suspension is aborted unless --force or --kill
# option is used on command line
# Default: "eth0"
SWSUSP_DOWNIFS="eth0"

# These interfaces should be started after suspend
# With "auto" the interfaces stopped before suspension
# will be started in reverse order.
# Default: "auto"
SWSUSP_UPIFS="auto"

# These modules should be loaded after suspend
# Default: none
SWSUSP_INSERTMODS=""

# Use FORCE_SUSPEND_MODE to reset the behaviour of
# suspension. If empty, this let the suspension
# behaviour unchanged. "0" will force shut off after
# suspension. "1" will force reboot. You can add an optional
# second parameter to tune the suspension display (see swsusp
# documentation for more information).
# Alternatively, you can use the keyword "acpi" to use the
# /proc/acpi/sleep interface in place of /proc/sys/kernel/swsusp
# Default: "0 2"
SWSUSP_FORCE_SUSPEND_MODE="0 2"

# Those are programs that prevent from suspending. If they are
# running and --force or --kill option aren't used, suspension
# is aborted.
# Default: none
SWSUSP_NON_COMPATIBLE_PROGS=""
EOF
    if [ ! -f $CONFIGFILE -o "$FORCE" = "yes" ] ;then
	echo "Création du fichier $CONFIGFILE" > $VERBOSE
	mv $TMP $CONFIGFILE 
    else
	echo "Configuration file $CONFIGFILE exists: not overwritten" > $VERBOSE
	diff -u $CONFIGFILE $TMP > $VERBOSE
	rm $TMP
    fi

    exit 0
fi
############################### END OF INSTALL ###################################################

EXE=`basename $0`
cat > $TMP <<EOF
The script file $EXE uses a configuration file $CONFIGFILE.
It is intended to cleanly call software suspension stuff. Its 
options are as follows:
--help		Prints this help.
--install	Install this file in $INSTALLDIR and a configuration
		template as $CONFIGFILE. Existing script will be 
		overwritten, but not configuration file, unless --force
		is used. 
--force		Ignore devices that cannot be stopped or unmounted.
                Overwrite existing configuration file in installation
		procedure. 
--kill		Kill processes that prevent from suspending.
--silent	Be silent on processing
--nosuspend	Performs everything except actually suspend (configuration
                testing and debugging purpose)
To help suspension, it is strongly advisable to use automount for 
devices like cdrom floppy fat partitions, so that remount will
automatically take place when needed. For the same purpose
autoload of modules in kernel is a good point.
EOF

if [ -f "$CONFIGFILE" ]
then
    . $CONFIGFILE
else
    echo "No configuration file $CONFIGFILE !!!"
    cat $TMP
    rm $TMP
    exit 1
fi

if ( echo $@ | grep -e "--help" ) 1> /dev/null; then
    cat $TMP
    rm $TMP
    exit 0
fi
rm $TMP

#Defining a few more command-line options...

if ( echo $@ | grep -e "--kill" ) 1> /dev/null; then
    KILL_PROCS='yes'
else
    KILL_PROCS='no'
fi


# This does everything except suspension
if ( echo $@ | grep -e "--nosuspend" ) 1> /dev/null; then
    NOSUSPEND='yes'
else
    NOSUSPEND='no'
fi
#############################################################
# Function definitions
#############################################################
SERVICES=
T_IFS=

CheckProgs() {
    ret=0;
    if [ $KILL_PROCS = "yes" ]; then
	for sig in 15 9 ;do
	    for prog in $SWSUSP_NON_COMPATIBLE_PROGS; do
		ps ax > $TMP
		pids=`awk '($5 == "'$prog'"){print $1}' $TMP`
		for pid in $pids; do
		    echo "Killing($sig) $prog($pid)..." > $VERBOSE
		    kill -$sig $pid 1> $VERBOSE 2> /dev/null
		    sleep 1
		done
	    done
	done
    fi
    for prog in $SWSUSP_NON_COMPATIBLE_PROGS; do
	ps ax > $TMP
	pids=`awk '($5 == "'$prog'"){print $1}' $TMP`
	if [ "$pids" != "" ];then
	    echo "Exception program $prog: $pids" > $VERBOSE
	    ret=1
	fi	
    done
    rm $TMP
    return $ret
}

SwitchFromX() {
    if [ "$SWSUSP_LEAVE_X_BEFORE_SUSPEND" != "yes" ]; then
	return 0;
    fi
    echo "Switching to console..." > $VERBOSE
    chvt 1
    sleep 1 # This to ensure usb mouse is released by X
    return $?
}

SwitchToX() {
    if [ "$SWSUSP_LEAVE_X_BEFORE_SUSPEND" != "yes" -a "$1" != "force" ]; then
	return 0;
    fi
    echo "Switching to X..." > $VERBOSE
    # This idea is extracted from the SuSE script /usr/bin/wttyhx
    # +k will make the list by ascending time usage
    ps axO+k | grep "X " > $TMP
    pids=`grep -v grep $TMP | awk '{print $1}'`
    rm $TMP
    tty=1 # if we don't find an X we remain on VT1
    for pid in $pids ;do
	  tty=`ls -l /proc/${pid}/fd | sed -ne '/^.*\/dev\/tty/s///p'`
    done
    # The last tty should be the X server with larger time usage
    chvt $tty
    return $?
}

StopServices() {
    for service in $SWSUSP_STOP_SERVICES_BEFORE_SUSPEND; do
	script=/etc/init.d/$service
	if [ -x $script ]; then
	    echo "Stopping $script..." > $VERBOSE
	    $script stop
	    SERVICES="$service $SERVICES" # memorizing in reverse order for auto option
	fi
    done
    return 0 # we don't want to abort if one of the services is already stopped
}

RestartServices() {
    ret=0
    if [ "$SWSUSP_START_SERVICES_AFTER_RESUME" = "auto" ] ;then
	SWSUSP_START_SERVICES_AFTER_RESUME="$SERVICES"
    fi
    for service in $SWSUSP_START_SERVICES_AFTER_RESUME; do
	script=/etc/init.d/$service
	if [ -x $script ]; then
	    $script start
	else
	    ret=1
	fi
    done
    return $ret
}

UmountDevices() {
    ret=0
    if [ $KILL_PROCS = "yes" ]; then
	for MNT in $SWSUSP_UMOUNTS; do
	    if [ `grep -c " $MNT " /proc/mounts` != 0 ]; then
		echo "Terminating users of $MNT..." > $VERBOSE
		fuser -s -k -15 $MNT 1> $VERBOSE 2> /dev/null
	    fi
	done
#sleep a few seconds before SIGKILL and umount
	sleep 3
    fi

#Now kill procs and do umount
    for MNT in $SWSUSP_UMOUNTS; do
	if [ `grep -c " $MNT " /proc/mounts` != 0 ]; then
	    if [ $KILL_PROCS = "yes" ]; then
		echo "Sending SIGKILL to remaining users of $MNT" > $VERBOSE
		fuser -s -k -9 $MNT 1> $VERBOSE 2> /dev/null
	    fi  
	    echo "Unmounting $MNT" > $VERBOSE
# make sure everything really is being unmounted
	    if ! ( umount $MNT ); then
		ret=1
	    fi
	fi
    done
    return $ret
}

RemountDevices() {
    ret=0
    for MNT in $SWSUSP_REMOUNT ;do
	if [ `grep -c " $MNT " /proc/mounts` = 0 ]; then	    
	    echo "Mounting $MNT..." > $VERBOSE
	    /bin/mount $MNT
	fi
    done
}

StopInterfaces() {
    ret=0
    for IF in $SWSUSP_DOWNIFS; do
	if [ `ifconfig | grep -c "^$IF "` != 0 ]; then
	    if [ ! -x $IFDOWN ] ;then
		echo "No script $IFDOWN !!!"
		return 2
	    else
		echo "Bringing $IF down..." > $VERBOSE
		if ! ($IFDOWN $OPTDOWN $IF) ;then
		    echo "Bringing $IF down failed !"
		    ret=1
		else
		    T_IFS="$IF $T_IFS"
		fi
	    fi
	fi
    done
    return $ret
}

RestartInterfaces() {
    ret=0
    if [ "$SWSUSP_UPIFS" = "auto" ] ;then
	SWSUSP_UPIFS="$T_IFS"
    fi
    for IF in $SWSUSP_UPIFS ;do
	echo "Bringing up $IF..." > $VERBOSE
	if ! ($IFUP $OPTUP $IF) ;then
	    ret=1
	fi
    done
    return $ret
}

UnloadModules() {
    if [ "$SWSUSP_UNLOAD_MODULES_BEFORE_SUSPEND" != "yes" ] ;then
	return 0
    fi
    cat /proc/modules > $VERBOSE
    modules=`awk '($3==0){print $1}' /proc/modules`
    for module in $modules ;do
	echo "Removing $module" > $VERBOSE
	modprobe -r $module
    done
    modaft=`awk '($3==0){print $1}' /proc/modules`
    while [ "$modaft" != "$modules" ] ; do
	cat /proc/modules > $VERBOSE
	modules=$modaft
	for module in $modules ;do
	    echo "Removing $module" > $VERBOSE
	    modprobe -r $module
	done
	modaft=`awk '($3==0){print $1}' /proc/modules`	
    done
    echo "Loaded modules:" > $VERBOSE
    cat /proc/modules > $VERBOSE
}

ReloadModules() {
    ret=0
    for MOD in $SWSUSP_INSERTMODS ;do
	echo "Loading $MOD..." > $VERBOSE
	if ! (modprobe $MOD) ;then
	    ret=1
	fi
    done
    return $ret
}

Suspend() {
    ret=0
    if [ "$SWSUSP_SAVE_CLOCK_ON_SUSPEND" = "yes" ] ;then
	echo "Saving clock state..." > $VERBOSE
	hwclock --systohc $HWCLKDEBUG
	sleep 1 #This is to be sure that the clock syncing is done
    fi
    if [ -x $SETSYSFONT ];then
	chvt 1
    fi

    if [ "$NOSUSPEND" = "yes" ] ;then
	echo "Suspension system call not performed..." > $VERBOSE
	if [ "$SWSUSP_FORCE_SUSPEND_MODE" = "acpi" -o "$SWSUSP_FORCE_SUSPEND_MODE" = "ACPI" ];then
	    echo "echo 4 > /proc/acpi/sleep" > $VERBOSE
	else
	    echo "echo \"1 $SWSUSP_FORCE_SUSPEND_MODE\" > /proc/sys/kernel/swsusp"
	fi
    else
	echo "Suspension system call in progress..." > $VERBOSE
	if [ "$SWSUSP_FORCE_SUSPEND_MODE" = "acpi" -o "$SWSUSP_FORCE_SUSPEND_MODE" = "ACPI" ];then
	    if ! (echo 4 > /proc/acpi/sleep) ;then
		ret=1
	    fi
	else
	    if ! (echo "1 $SWSUSP_FORCE_SUSPEND_MODE" > /proc/sys/kernel/swsusp) ;then
		ret=1
	    fi
	fi
    fi
    sleep 5
    if [ -x $SETSYSFONT ];then
	chvt 1
	$SETSYSFONT $OPTSYSFONT
	if [ "$SWSUSP_LEAVE_X_BEFORE_SUSPEND" != "yes" ];then
	    SwitchToX force
	fi
    fi
    return $ret
}

RestoreClock() {
    ret=0
    echo "Restoring clock..." > $VERBOSE
    hwclock --hctosys $HWCLKDEBUG
    ret=$?
    sleep 1 #This is to be sure that the clock syncing is done
    return $ret
}


RESUME() {
    state=$1
    resumestate=6
    
    if [ $state -ge $resumestate ];then
	echo "Resuming (suspend script vers. $VERSION)..." > $VERBOSE

	RestoreClock
    fi

    resumestate=$[ $resumestate - 1 ]
    
    if [ $state -ge $resumestate ];then
	#echo "Reload modules..." > $VERBOSE
	ReloadModules
    fi

    resumestate=$[ $resumestate - 1 ]
    
    if [ $state -ge $resumestate ];then
	#echo "Starting interfaces..." > $VERBOSE
	RestartInterfaces
    fi

    resumestate=$[ $resumestate - 1 ]
    
    if [ $state -ge $resumestate ];then
	#echo "Remounting devices..." > $VERBOSE
	RemountDevices
    fi

    resumestate=$[ $resumestate - 1 ]
    
    if [ $state -ge $resumestate ];then
	#echo "Restarting services..." > $VERBOSE
	RestartServices
    fi

    resumestate=$[ $resumestate - 1 ]

    if [ $state -ge $resumestate ];then
	SwitchToX
    fi
    
    resumestate=$[ $resumestate - 1 ]
    # resumestate should be zero at this point
    return $resumestate
}

FAIL(){
   if [ $FORCE = "yes" ]; then
#  Then continue anyway.
     echo
     echo "WARNING...FAILSAFES OVERIDDEN..."
     echo "Do not mount shared drives from other OS's while suspended"
     echo
   else
     echo
     echo "         ********* SUSPEND CANCELLED *********"
     echo
     RESUME $1
     rm $LOCKFILE
     exit 255
   fi 
}





SUSPEND() {
    state=0
    echo "Preparing to suspend (suspend script vers. $VERSION)..." > $VERBOSE

    if ! CheckProgs ;then
	FAIL $state
    fi

    if ! SwitchFromX ;then
	FAIL $state
    fi

    state=$[$state + 1]

    #echo "Stopping services..." > $VERBOSE
    if ! StopServices ;then
	FAIL $state
    fi

    state=$[$state + 1]

    #echo "Unmounting devices..." > $VERBOSE
    if ! UmountDevices ;then
	FAIL $state
    fi

    state=$[$state + 1]

    #echo "Stopping interfaces..." > $VERBOSE
    if ! StopInterfaces ;then
	FAIL $state
    fi

    state=$[$state + 1]

    #echo "Unloading modules..." > $VERBOSE
    if ! UnloadModules ;then
	FAIL $state
    fi

    state=$[$state + 1]
    
    if ! Suspend ;then
	FAIL $state
    fi
    state=$[$state + 1]
    return $state
}

#############################################################
# Main 
#############################################################

if [ -f $LOCKFILE ] ;then
    pid=`cat $LOCKFILE`
    if (ps $pid > /dev/null 2> /dev/null) ;then
	echo "File $LOCKFILE is present. Suspension script `cat $LOCKFILE`"
	echo "should be in progress. If this is not the case, then erase"
	echo "this file"
	exit 1
    fi
fi
echo $$ > $LOCKFILE

SUSPEND
state=$?
RESUME $state
ret=$?
rm $LOCKFILE

exit $ret
