#!/bin/bash
#
# (c) Bernhard Walle <bwalle@suse.de>
#
# 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; You may only use version 2 of the License, you have no option to
# use any other 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., 675 Mass
# Ave, Cambridge, MA 02139, USA.
#

KDUMP_SHARED=/usr/share/kdump/kdump-shared_functions
MAKEDUMPFILE=makedumpfile
KDUMP_URL_PARSER=kdump-url_parser
KDUMP_HELPER=kdump-helper
READ_DEBUGLINK=read-debuglink
GET_KERNEL_VERSION=get_kernel_version
COPY_PROGRESS=kdump-copy_progress
CAT_PROGRESS=kdump-cat_progress
MOUNTPOINT=/mnt
NFS_OPTIONS="-o nolock"
VMCORE=/proc/vmcore
SUFFIX=$(date +"%Y-%m-%d-%H:%M")
SSH_ARGS="-o NumberOfPasswordPrompts=0 -o CheckHostIP=no -o StrictHostKeyChecking=no"

###############################################################################
# Checks if makedumpfile must be used
function use_makedumpfile()
{
    if [ "$KDUMP_DUMPLEVEL" -eq 0 -a "$KDUMP_DUMPFORMAT" = "ELF" ] ; then
        return 1
    else
        return 0
    fi
}


###############################################################################
# Checks the available binaries
function check_binaries
{
    if use_makedumpfile ; then
        if ! which $MAKEDUMPFILE >/dev/null 2>&1 ; then
            echo "makedumpfile is needed to use \$KDUMP_DUMPLEVEL != 0"
            exit 1
        fi
    fi

    if ! which $KDUMP_URL_PARSER >/dev/null 2>&1 ; then
        echo "kdump-url_parser is needed, install kdump"
        exit 1
    fi

    if ! which $KDUMP_HELPER >/dev/null 2>&1 ; then
        echo "kdump-helper is needed, install kdump"
        exit 1
    fi

    if ! which $COPY_PROGRESS >/dev/null 2>&1 ; then
        echo "kdump-copy_progress wasn't found, using cp"
        COPY_PROGRESS="cp --sparse=always"
    fi

    if ! which $READ_DEBUGLINK >/dev/null 2>&1 ; then
        echo "read-debuglink wasn't found, using cp"
        exit 1
    fi

    if [ -b "$VMCORE" ] ; then
        if ! which $CAT_PROGRESS >/dev/null 2>&1 ; then
            echo "kdump-cat_progress is needed to use initrd-based saving"
            exit 1
        fi
    fi
}

###############################################################################
# Gets the kernel version of the dump
function get_kernel_version_dump
{
    if [ -n "$ORIG_VER" ] ; then
        echo $ORIG_VER
        return
    fi

    get_kernel_version $VMCORE
}

###############################################################################
# Find the configuration of makedumpfile
function find_dumpfile_config
{
    local version=$(get_kernel_version_dump)
    local flavor=$(echo $version | sed -e 's#^.*-##')
    local arch=$(uname -m)
    local version_without_flavor=$(echo $version | sed -e "s#-$flavor##")
    local dir=/usr/src/linux-${version_without_flavor}-obj/${arch}/${flavor}/

    if [ -r $dir/makedumpfile.config ] ; then
        echo $dir/makedumpfile.config
        return 0
    else
        return 1
    fi
}

###############################################################################
# Find the debug information kernel
function find_debuginfo_kernel
{
    local version=$(get_kernel_version_dump)
    local debuginfo=

    if [ -r /boot/vmlinux-${version}.gz ] ; then
        debuginfo=$($READ_DEBUGLINK /boot/vmlinux-${version}.gz 2>/dev/null)
    elif [ -r /boot/vmlinux-${version} ] ; then
        debuginfo=$($READ_DEBUGLINK /boot/vmlinux-${version} 2>/dev/null)
    elif [ -r /boot/vmlinuz-${version} ] ; then
        debuginfo=$($READ_DEBUGLINK /boot/vmlinux-${version} 2>/dev/null)
    fi

    if [ -z "$debuginfo" ] ; then
        return 1
    else
        echo $debuginfo
        return 0
    fi
}

###############################################################################
# Gets the command line for makedumpfile
function get_makedumpfile_cmdline
{
    local cmdline=$MAKEDUMPFILE
    local config=$(find_dumpfile_config)

    if [ -n "$1" ] ; then
        cmdline="$cmdline $1"
    fi

    if [ -n "$config" ] ; then
        cmdline="$cmdline -i $config"
    else
        local debuginfo_kernel=$(find_debuginfo_kernel)

        if [ -n "$debuginfo_kernel" ] ; then
            cmdline="$cmdline -x $debuginfo_kernel"
        else
            local origkernel="/boot/vmlinux-$(get_kernel_version_dump)"

            if [ -r "$origkernel" ] ; then
                if contains_debuginfo $origkernel ; then
                    cmdline="$cmdline -x $origkernel"
                else
                    echo "There's no configuration file installed and no debug info"
                    echo "kernel. You cannot use makedumpfile then but you specified"
                    echo "\$KDUMP_DUMPLEVEL > 0. Giving up."
                    exit 1
                fi
            fi
        fi
    fi

    #
    # add the dump level
    if [ "$KDUMP_DUMPLEVEL" -gt 31 ] ; then
        echo "\$KDUMP_DUMPLEVEL must be between 0 and 31. Using 31 now."
        KDUMP_DUMPLEVEL=31
    fi
    cmdline="$cmdline -d $KDUMP_DUMPLEVEL"

    #
    # dump format
    case $KDUMP_DUMPFORMAT in
        *elf*)
            cmdline="$cmdline -E"
            ;;
        *compressed*)
            cmdline="$cmdline -c"
            ;;
    esac

    #
    # finally print it
    echo $cmdline
}

###############################################################################
# FILE
function do_copy_file
{
    local makedumpfile=$(get_makedumpfile_cmdline)
    local savedir=$1
    
    print_debug "Executing mkdir -p $savedir"
    mkdir -p $savedir || return 1
    if use_makedumpfile ; then
        print_debug "Executing $makedumpfile $VMCORE $savedir/vmcore"
        $makedumpfile $VMCORE $savedir/vmcore || return 1
    else
        print_debug "Executing $COPY_PROGRESS $VMCORE $savedir/vmcore"
        $COPY_PROGRESS $VMCORE $savedir/vmcore || return 1
    fi
}

###############################################################################
# CIFS
function copy_cifs
{
    local share=$($KDUMP_URL_PARSER --share $KDUMP_SAVEDIR)
    local host=$($KDUMP_URL_PARSER --host $KDUMP_SAVEDIR)
    local user=$($KDUMP_URL_PARSER --user $KDUMP_SAVEDIR)
    local password=$($KDUMP_URL_PARSER --password $KDUMP_SAVEDIR)
    local path=$($KDUMP_URL_PARSER --path $KDUMP_SAVEDIR)
    local mountcmd="mount"

    if [ -z "$share" -o -z "$host" -o -z "$path" ] ; then
        echo "URL invalid for CIFS"
        return 1
    fi

    path=$path/$SUFFIX

    #
    # mount the stuff
    mountcmd="$mountcmd //$host/$share $MOUNTPOINT"
    if [ -n "$user" ] ; then
        mountcmd="$mountcmd -o user=$user"
        if [ -n "$password" ] ; then
            mountcmd="$mountcmd -o password=$password"
        fi
    fi

    print_debug "Executing $mountcmd"
    $mountcmd || return 1

    #
    # create the directory and copy the stuff
    do_copy_file $MOUNTPOINT$path
    if [ "$?" -ne 0 ] ; then
        umount $MOUNTPOINT
        return 1
    fi

    #
    # get rid of the file system
    print_debug "Executing umount $MOUNTPOINT"
    umount $MOUNTPOINT || return 1

    return 0
}

###############################################################################
# CIFS
function copy_nfs()
{
    local host=$($KDUMP_URL_PARSER --host $KDUMP_SAVEDIR)
    local path=$($KDUMP_URL_PARSER --path $KDUMP_SAVEDIR)
    local mountpoint=$(dirname $path)
    local mountcmd="mount $NFS_OPTIONS"

    if [ -z "$path" -o -z "$host" ] ; then
        echo "URL invalid for NFS"
        return 1
    fi

    #
    # mount the stuff
    mountcmd="$mountcmd $host:$path $MOUNTPOINT"
    print_debug "Executing $mountcmd"
    $mountcmd || return 1

    #
    # create the directory and copy the stuff
    do_copy_file $MOUNTPOINT/$SUFFIX
    if [ "$?" -ne 0 ] ; then
        umount $MOUNTPOINT
        return 1
    fi

    #
    # get rid of the file system
    print_debug "Executing umount $MOUNTPOINT"
    umount $MOUNTPOINT || return 1

    return 0
}

###############################################################################
# FTP
function copy_ftp()
{
    local user=$($KDUMP_URL_PARSER --user $KDUMP_SAVEDIR)
    local password=$($KDUMP_URL_PARSER --password $KDUMP_SAVEDIR)
    local port=$($KDUMP_URL_PARSER --port $KDUMP_SAVEDIR)
    local host=$($KDUMP_URL_PARSER --host $KDUMP_SAVEDIR)
    local path=$($KDUMP_URL_PARSER --path $KDUMP_SAVEDIR)/$SUFFIX
    local curl="curl --ftp-create-dirs"
    local url=

    #
    # set default values if necessary
    user=${user:-anonymous}
    password=${password:-$USER@$HOSTNAME}

    #
    # use the default port if no port is given
    if [ -n "$port" ] ; then
        url="ftp://$host:$port$path"
    else
        url="ftp://$host$port$path"
    fi
    curl="$curl -u $user:$password"

    #
    # use makedumpfile in a pipe if we want filtering,
    # else use plain curl
    if use_makedumpfile ; then
        local makedumpfile=$(get_makedumpfile_cmdline -F)
        [ -z "$makedumpfile" ] && return 1

        # disable progress since makedumpfile displays it
        curl="$curl -s"

        print_debug "Executing $makedumpfile $VMCORE |" \
            "$curl -T - $url/vmcore"
        $makedumpfile $VMCORE | $curl -T - $url/vmcore.flattened || return 1

        #
        # copy the /bin/makedumpfile-R.pl script so that the admin
        # can re-assemble the flattened format without having to
        # install makedumpfile(1) on the FTP server
        if [ -r /bin/makedumpfile-R.pl ] ; then
            print_debug "Executing $curl -T " \
                "/bin/makedumpfile-R.pl $url/makedumpfile-R.pl"
            $curl -T /bin/makedumpfile-R.pl $url/makedumpfile-R.pl

            echo "Please execute 'makedumpfile-R.pl vmcore < vmcore.flattened' on "
            echo "the target host to reassemble the core file."
        fi
    else
        if [ -b "$VMCORE" ] ; then
            # disable progress
            curl="$curl -s"

            print_debug "$CAT_PROGRESS $VMCORE" \
                "| $curl -T - $url/vmcore"
            $CAT_PROGRESS $VMCORE | $curl -T - $url/vmcore || return 1
        else
            curl="$curl --progress-bar"
            print_debug "Executing $curl -T $VMCORE $url/vmcore"
            $curl -T $VMCORE $url/vmcore || return 1
        fi
    fi

    return 0
}

###############################################################################
# SSH
function copy_ssh()
{
    local user=$($KDUMP_URL_PARSER --user $KDUMP_SAVEDIR)
    local port=$($KDUMP_URL_PARSER --port $KDUMP_SAVEDIR)
    local host=$($KDUMP_URL_PARSER --host $KDUMP_SAVEDIR)
    local path=$($KDUMP_URL_PARSER --path $KDUMP_SAVEDIR)/$SUFFIX
    local scp="scp $SSH_ARGS"
    local ssh="ssh $SSH_ARGS"
    local scpurl=
    local sshurl=

    if [ -n "$port" ] ; then
        scp="$scp -P $port"
        ssh="$ssh -p $port"
    fi

    if [ -n "$user" ] ; then
        scpurl="$user@$host:$path"
        sshurl="$user@$host"
    else
        scpurl="$host:$path"
        sshurl="$host"
    fi

    #
    # create directory on the remote host
    print_debug "Creating directory $path on remote host"
    print_debug "Executing $ssh $sshurl mkdir -p $path"
    $ssh $sshurl mkdir -p $path
    if [ "$?" -ne 0 ] ; then
        return 1
    fi

    #
    # when using with dump level, use makedumpfile in a pipe --
    # otherwise just use scp
    if use_makedumpfile ; then
        local makedumpfile=$(get_makedumpfile_cmdline -F)
        [ -z "$makedumpfile" ] && return 1

        print_debug "Executing "$makedumpfile $VMCORE " \
            '| ' "$ssh $sshurl "cat - > $path/vmcore.flattened"
        $makedumpfile $VMCORE | $ssh $sshurl "cat - > $path/vmcore.flattened" || return 1

        #
        # copy the script
        if [ -r /bin/makedumpfile-R.pl ] ; then
            print_debug "Executing $scp /bin/makedumpfile-R.pl " \
                " $scpurl/makedumpfile-R.pl"
            $scp /bin/makedumpfile-R.pl $scpurl/makedumpfile-R.pl

            echo "Please execute 'makedumpfile-R.pl vmcore < vmcore.flattened' on "
            echo "the target host to reassemble the core file."
        fi
    else
        if [ -b "$VMCORE" ] ; then
            print_debug "$CAT_PROGRESS $VMCORE " \
                "| $ssh $sshurl \"cat - > $path/vmcore\""
            $CAT_PROGRESS $VMCORE | $ssh $sshurl "cat - > $path/vmcore"
        else
            print_debug "Executing $scp $VMCORE $scpurl/vmcore"
            $scp $VMCORE $scpurl/vmcore
        fi
    fi
}

###############################################################################
# File
function copy_file()
{
    local path=$($KDUMP_URL_PARSER --path $KDUMP_SAVEDIR)/$SUFFIX

    if [ -z "$path" ] ; then
        return 1
    fi

    do_copy_file $path

    if [ "$KDUMP_FREE_DISK_SIZE" -gt 0 ] ; then
        free=$(get_free_space_mb "$path")
        if [ "$free" -lt "$KDUMP_FREE_DISK_SIZE" ] ; then
            echo "Not enough space left after saving the dump ($free MB)"
            echo "Deleting the dump ..."
            rm -fr "$path"
            return 1
        fi
    fi
}

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

if [ ! -r "$KDUMP_SHARED" ] ; then
    echo "$KDUMP_SHARED missing"
    exit 1
fi
source $KDUMP_SHARED

if ! source_config ; then
    exit 1
fi

check_binaries

#
# use specified vmcore file
if [ -n "$1" ]  ; then
    if [ ! -r "$1" ] ; then
        echo "$1 is not readable"
        exit 1
    fi
    VMCORE="$1"
fi

KDUMP_SAVEDIR=${KDUMP_SAVEDIR:-/var/log/dump}

protocol=$($KDUMP_URL_PARSER --protocol $KDUMP_SAVEDIR)
if [ -z "$protocol" ] ;then
    echo "Invalid protocol in $KDUMP_SAVEDIR, exiting"
    exit 1
fi
size=$(stat -c "%s" $VMCORE)

#
# handle block devices, we need the size here
if [ -b "$VMCORE" ] ; then
    size=$($KDUMP_HELPER -l $VMCORE | sed -e 's/Length: //g')
    if [ -z "$size" ] || [ "$size" -eq 0 ] ; then
        echo "Error while retrieving file size from $VMCORE"
        exit 1
    fi
    COPY_PROGRESS="$COPY_PROGRESS -l $size"
    CAT_PROGRESS="$CAT_PROGRESS -l $size"
else
    if [ ! -r $VMCORE -o "$size" -eq 0 ] ;then
        echo "You are not in an kdump environment"
        exit 1
    fi
fi

if [ $(($KDUMP_VERBOSE & 1)) -gt 0 ] ; then
    mb_size=$(($size/1024/1024))
    logger -i -t kdump "Saving $mb_size MB crash dump to $KDUMP_SAVEDIR/$SUFFIX"
fi

#
# and now handle the protocols
case "$protocol" in

    *file*)
        copy_file || exit 1
        ;;
    *nfs*)
        copy_nfs || exit 1
        ;;
    *cifs*)
        copy_cifs || exit 1
        ;;
    *ftp*)
        copy_ftp || exit 1
        ;;
    *ssh*)
        copy_ssh || exit 1
        ;;
    *)
        echo "Invalid protocol"
        exit 1
esac


# vim: set sw=4 ts=4 et:
