#!/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                  #
#                                                                         #
###########################################################################
#
# first get helper functions (e.g. DEBUG, load_scheme, ...)
. "${0%/*}/helper_functions"
. "${PUB_SCRIPT_DIR}/sleep_helper_functions"
. "${PUB_SCRIPT_DIR}/sleep_helper_messages"

set_variables "suspend2disk"
prepare_logs  "suspend2disk"
declare -i PERCENT
export PERCENT=0    # ugly global variable, but the easiest way to go.
export STEP=5

##EV_ID="$4" # event id, needed for SCRIPT_RETURN, set in helper_functions

progress ""

#############################################################################
# if the system is configured for 855resolution, we _must_ switch away from
# X before suspend
if [ -e /etc/sysconfig/videobios ]; then
    . /etc/sysconfig/videobios
    if [ "$VIDEOBIOS_PATCH" = "yes" ]; then
        SWITCH_VT=yes
        echo "VIDEOBIOS_PATCH is enabled" >> $LSMOD_LOG
    fi
fi

#############################################################################
# try to find a kernel image that matches the actually running kernel.
# We need this, if more than one kernel is installed. This works reasonably
# well with grub, if all kernels are named "vmlinuz-`uname -r`" and are
# located in /boot. If they are not, good luck ;-)

declare -a KERNELS MENU_ENTRIES
declare -i DEFAULT_BOOT
eval `get_kernels`  # works only with grub, but we have no other
                    # choice anyway for now.
NEXT_BOOT=""
RUNNING=`uname -r`
ARCH=`uname -m`
let PERCENT+=$STEP
if [ "${BOOT_LOADER:-GRUB}" == "GRUB" ]; then
    progress "${_M01}"
    declare -i I=0
    DEBUG "running kernel: $RUNNING" DIAG
    while [ -n "${KERNELS[$I]}" ]; do
        BOOTING="${KERNELS[$I]}"
        if IMAGE=`readlink /boot/$BOOTING` && [ -e "/boot/${IMAGE##*/}" ]; then
            DEBUG "Found kernel symlink $BOOTING => $IMAGE" INFO
            BOOTING=$IMAGE
        fi
        case $ARCH in
            ppc*)   BOOTING="${BOOTING#*vmlinux-}" ;;
            *)      BOOTING="${BOOTING#*vmlinuz-}" ;;
        esac
        if [ "$RUNNING" == "$BOOTING" ]; then
            NEXT_BOOT=${MENU_ENTRIES[$I]}
            DEBUG "running kernel corresponds to grub menu entry $NEXT_BOOT" DIAG
            DEBUG "kernel filename: '${KERNELS[$I]}'" DIAG
            echo "running kernel is grub menu entry $NEXT_BOOT (${KERNELS[$I]})" >> $LSMOD_LOG
            break
        fi
        let I++
    done
    # if we have not found a kernel, issue a warning.
    # if we have found a kernel, we'll do "grub-once" later, after
    # prepare_suspend finished.
    if [ -z "$NEXT_BOOT" ]; then
        DEBUG "no kernelfile matching the running kernel found" WARN
        echo "no kernelfile matching the running kernel found" >> $LSMOD_LOG
    fi
else
    echo "BOOT_LOADER != GRUB, no kernel selection for next boot." >> $LSMOD_LOG
fi

let PERCENT+=$STEP
progress "${_M01}"

#############################################################################
# if we did not find a kernel (or BOOT_LOADER is not GRUB) check,
# if the running kernel is still the one that will (probably) be booted for
# resume (default entry in menu.lst or if there is none the kernel file
# /boot/vmlinuz points to.)
# This will only work, if you use "original" SUSE kernels.
# you can always override with the config variable set to "yes"
if [ -z "$NEXT_BOOT" \
     -a "$SUSPEND2DISK_IGNORE_KERNEL_MISMATCH" != "yes" ]; then
    # which kernel is booted with the default entry?
    BOOTING="${KERNELS[$DEFAULT_BOOT]}"
    # if there is no default entry (no menu.lst? Using LILO?) we fall back to
    # the default of /boot/vmlinuz.
    [ -z "$BOOTING" ] && BOOTING="vmlinuz"
    if IMAGE=`readlink /boot/$BOOTING` && [ -e "/boot/${IMAGE##*/}" ]; then
        BOOTING=$IMAGE
    fi
    BOOTING="${BOOTING#*vmlinuz-}"
    DEBUG "running kernel: '$RUNNING', (probably) booting kernel: '$BOOTING'" DIAG
    echo  "running kernel: '$RUNNING', (probably) booting kernel: '$BOOTING'" >> $LSMOD_LOG
    if [ "$BOOTING" != "$RUNNING" ]; then
        notify 'The kernel version "'$BOOTING'" in /boot does not match the running kernel
version "'$RUNNING'". Resuming with this kernel will not work. If you know
what you are doing, you can override this in /etc/sysconfig/powersave/sleep
with the variable SUSPEND2DISK_IGNORE_KERNEL_MISMATCH=yes.' ERROR CONTINUE "$EV_ID"
        progress_finish
        echo "kernel version mismatch, cannot suspend to disk." >> $LSMOD_LOG
        $SCRIPT_RETURN $EV_ID 1 "kernel version mismatch, cannot suspend to disk."
        EXIT 1
    fi
fi

let PERCENT+=$STEP
progress "${_M02}"

################################################################
# this is a hack. We can swapon a dedicated partition before
# suspend. This partition will only be used by the suspend image
# after resume we swapoff it  again.
if [ -n "$SUSPEND2DISK_RESUME_DEVICE" ]; then
    DEBUG "activating suspend partition '$SUSPEND2DISK_RESUME_DEVICE'" DIAG
    echo "You have set SUSPEND2DISK_RESUME_DEVICE. This is for experts." >> $LSMOD_LOG
    echo "activating suspend partition '$SUSPEND2DISK_RESUME_DEVICE'" >> $LSMOD_LOG
    echo "swapon: $SUSPEND2DISK_RESUME_DEVICE" >> $STATE
    swapon "$SUSPEND2DISK_RESUME_DEVICE"
    RET=$?
    if [ $RET -ne 0 ]; then
        DEBUG "swapon $SUSPEND2DISK_RESUME_DEVICE failed. Error: $RET" WARN
        echo "could not activate $SUSPEND2DISK_RESUME_DEVICE. Suspend might fail." >> $LSMOD_LOG
    fi
fi

################################################################
# we need a swap partition, and it has to be given to the kernel
# with the resume= parameter
# Note: the suspend code can deal with multiple swap partitions,
#       but only the one given by resume= will be used.
# SUSPEND2DISK_SKIP_RESUME_CHECK is a "secret" parameter, since
# it should never be needed.
if [ "$SUSPEND2DISK_SKIP_RESUME_CHECK" != yes ]; then
    read RDEV < /sys/power/resume

    # no resume device set? Error.
    if [ "$RDEV" = "0:0" ]; then
        DEBUG "resume partition is not set up." ERROR
        echo "resume partition is not set up." >> $LSMOD_LOG
        notify "The resume partition is not set up. Probably you need to add
                a 'resume=...' option to your kernel command line and reboot.
                Suspend to disk and resume is not possible without a resume
                partition, please consult the documentation. You can skip this
                check by setting SUSPEND2DISK_SKIP_RESUME_CHECK to 'yes' in
                the sleep configuration file." ERROR CONTINUE "$EV_ID"
        progress_finish
        $SCRIPT_RETURN $EV_ID 1 "no resume parameter"
        EXIT 1
    fi
    #
    # is the resume= parameter correct?
    while read SDEV STYPE DUMMY; do
        [ "$STYPE" != "partition" ] && continue
        X=$(stat -c '$((0x%t)):$((0x%T))' $SDEV) # stat major:minor in HEX
        [ $? -ne 0 ] && continue
        SNUM=$(eval echo $X) # this converts "$((0x3)):$((0xf))" to "3:15"
        [ "$SNUM" = "$RDEV" ] && break
        SDEV=""              # not necessary, but for clarity :-)
    done < /proc/swaps
    if [ -z "$SDEV" ] ; then
        DEBUG "swap partition '$RDEV' not available" ERROR
        DEBUG "Contents of /proc/swaps:" DIAG
        while read LINE; do
            DEBUG "$LINE" DIAG
        done < /proc/swaps
        echo "swap partition '$RDEV' not available. Boom." >> $LSMOD_LOG
        echo "Content of /proc/swaps:" >> $LSMOD_LOG
        cat /proc/swaps >> $LSMOD_LOG
        notify "Swap partition '$RDEV' is not available, cannot suspend to disk." \
                ERROR CONTINUE "$EV_ID"
        progress_finish
        $SCRIPT_RETURN $EV_ID 1 "resume partition not available."
        EXIT 1
    fi
else
    echo "SUSPEND2DISK_SKIP_RESUME_CHECK=yes" >> $LSMOD_LOG
fi
# end of swap partition sanity check
################################################################

let PERCENT+=$STEP
progress ""
prepare_sleep "suspend2disk"

let PERCENT+=$STEP
progress ""

# set the bootloader to the running kernel
if [ -n "$NEXT_BOOT" ]; then
    progress "${_M09}"
    echo "preparing boot-loader: selecting entry $NEXT_BOOT, kernel /boot/$BOOTING" >> $LSMOD_LOG
    T1=`date +"%s%N"`
    sync; sync; sync # this is needed to speed up grub-once on reiserfs
    T2=`date +"%s%N"`
    let PERCENT+=$STEP
    progress "${_M10}"
    grub-once $NEXT_BOOT > /dev/null 2>&1
    T3=`date +"%s%N"`
    S=$(((T2-T1)/100000000)); S="$((S/10)).${S:0-1}"
    G=$(((T3-T2)/100000000)); G="$((G/10)).${G:0-1}"
    echo "  time needed for sync: $S seconds, time needed for grub: $G seconds." >> $LSMOD_LOG
else
    let PERCENT=+$STEP
fi

progress ""

# SET THE SHUTDOWN METHOD ######################################
# "platform" -> "real" S4, default.
# "shutdown" -> S5
# "firmware" -> S4bios, not really supported.
# "reboot" -> just reboot, only useful for debugging
# we do a sanity check after setting this since there are still
# kernels out there getting this wrong.
echo -n "${SUSPEND2DISK_SHUTDOWN_MODE:-platform}" > /sys/power/disk
RET=$?
if [ $RET -ne 0 ]; then
    DEBUG "could not set shutdown mode. errno: '$RET'" WARN
    DEBUG "a software-suspend enabled kernel is needed for suspend to disk" WARN
fi

# Set the image size ###########################################
# this is only valid for kernels from 2.6.16rc1 upwards
IMAGE=${SUSPEND2DISK_IMAGE_SIZE:-500}
SYSFS_IMGSZ="/sys/power/image_size"
if [ -r $SYSFS_IMGSZ -a -n "$SDEV" ]; then
    while read DEV TYPE SIZE USED PRI; do
        [ "$DEV" != "$SDEV" ] && continue # SDEV comes from above
        FREE=$[($SIZE-$USED)/1024]  # get free space on SDEV in MB
        if [ $FREE -lt $IMAGE ]; then
            IMAGE=$[$FREE-10]
        fi
        break   # we found the partition, no need to look further
    done < /proc/swaps
    DEBUG "calculated image_size: $IMAGE" DIAG
    echo "calculated image_size: $IMAGE" >> $LSMOD_LOG

    if [ $IMAGE -lt 0 ]; then
        IMAGE=0
    fi
    # SUSPEND2DISK_IMAGE_SIZE is in MB, but the kernel now expects bytes
    echo $[$IMAGE*1024*1024] > $SYSFS_IMGSZ
else
    DEBUG "no $SYSFS_IMGSZ found or suspend device ('$SDEV') empty." DIAG
fi

switch_to_vt

progress_finish
$SCRIPT_RETURN $EV_ID 0 "prepare_sleep finished successfully for $1"

EXIT 0
