#!/bin/sh
#
# Copyright 2010-2015 (C) VMware, Inc.  All rights reserved.
#
# rotatecore:
#  Rotate core files to keep from filling up the disk.
#
# This script is invoked when a core dump happens with the
# arguments filename, pid, and dump time in seconds since the epoch.
# The coredump data is passed in on stdin.
#
PROG_NAME=$(basename $0)

CONFIG_FILE="/etc/rotatecore.config"
LOCK_FILE="/var/run/rotatecore/lock"
TIMEOUT_SEC=120

# Defaults
CORE_DIR="/var/core"
ROTATE=""
COMPRESS=""

Log() {
   LEVEL=$1
   shift
   logger -p daemon.${LEVEL} -t "${PROG_NAME}" "$*"
}

GetConfigOptions() {
   # Use defaults if there's no config file.
   if [ ! -r "${CONFIG_FILE}" ]
   then
      return
   fi

   . ${CONFIG_FILE}
}

DeleteOldCores() {
   # Check if there are enough items to bother rotating.
   NUM_CORES=$(find ${CORE_DIR} -maxdepth 1 -type f \
               -name "core-${DUMPER_BASENAME}*" | wc -l)
   if [ ${NUM_CORES} -lt ${ROTATE} ]
   then
      return
   fi

   # Remove the extras + 1 for the new core
   NUM_RMS=$(expr ${NUM_CORES} - ${ROTATE} + 1)
   if [ "${NUM_RMS}" -le "0" ]
   then
      return
   fi

   # Remove the oldest cores that fall outside the rotation.
   find ${CORE_DIR} -maxdepth 1 -type f -name "core-${DUMPER_BASENAME}*" \
         -printf "%T@ %p\n" | sort | head -n ${NUM_RMS} | cut -d ' ' -f 2- | \
   while read f
   do
      Log info "Rotating out old core $f"
      rm -f "$f"
   done

}

CompressCores() {
   find ${CORE_DIR} -maxdepth 1 -type f -name "core-${DUMPER_BASENAME}*" \
         -not -name '*.gz' -exec gzip -f '{}' ';' \
         -exec chown root:dumpers '{}.gz' ';' \
         -exec chmod ug+rw '{}.gz' ';'
}

##
## Main
##

#
# Weirdly, the kernel seems to reproduce some of the other arguments
# in each of the cmdline arguments to a core_pattern handler. This is probably
# a kernel bug. When it's fixed we can get rid of the unecessary calls
# to "cut".
#
DUMPER_BASENAME=$(echo "$1" | cut -d' ' -f 1-1)
DUMPER_PID=$(echo "$2" | cut -d' ' -f 1-1)
DUMPER_TIME=$3

GetConfigOptions

PARTIAL_CORE="${CORE_DIR}/.core-${DUMPER_BASENAME}-${DUMPER_PID}-${DUMPER_TIME}"
CORE_FILENAME="${CORE_DIR}/core-${DUMPER_BASENAME}-${DUMPER_PID}-${DUMPER_TIME}"

old_umask=$(umask)
# Allow others to contend for the lock.
umask u=rw,g=rw,o=rw
(
   # Reset the umask once the lock is written
   eval "umask ${old_umask}"

   # Take a lock since many core dumps can happen at once
   flock -w ${TIMEOUT_SEC} -x 200
   if [ "$?" -eq "0" ]
   then
      #
      # Rotate-out and compress old cores before dumping so that
      # if we're short on disk-space, the rotation and compression
      # will hopefully make enough room to fit the new core file.
      #

      # See if there are enough core files from this program to rotate
      if [ -n "${ROTATE}" ]
      then
         DeleteOldCores
      fi

      if [ -n "${COMPRESS}" ]
      then
         CompressCores
      fi
   else
      #
      # Couldn't get the lock. Note this in the syslog, break the lock,
      # and dump the core without rotation.
      #
      # If we have to wait over 2 minutes to get the lock something
      # is seriously wrong and if we continue to let it go wrong
      # we will just keep ignoring the lock and writing cores which
      # defeats the purpose of core rotation. So we break the lock
      # and hope any further core rotations work smoothly.
      #
      LINE="Could not acquire file lock. Breaking lock and writing core"
      LINE="${LINE} ${CORE_FILENAME} without rotation."
      Log err "${LINE}"

      rm -f ${LOCK_FILE}
      flock -u 200
   fi

   #
   # Dump to a temporary file first and then move the temporary file to the
   # final name so if there's a lock break we only see whole core files
   # when doing a compression.
   #
   cat > ${PARTIAL_CORE}
   ERR="$?"
   if [ "${ERR}" -ne "0" ]
   then
      LINE="Exited with error ${ERR} when attempting to write core to"
      LINE="${LINE} ${PARTIAL_CORE}"
      Log err "${LINE}"
   else
      Log info "Wrote core to ${PARTIAL_CORE}"
   fi

   chown root:dumpers ${PARTIAL_CORE}
   chmod g+r ${PARTIAL_CORE}
   mv ${PARTIAL_CORE} ${CORE_FILENAME}
   ERR="$?"
   if [ "${ERR}" -ne "0" ]
   then
      LINE="Exited with error ${ERR} when attempting to move finished core"
      LINE="${LINE} from ${PARTIAL_CORE} to ${CORE_FILENAME}"
   else
      Log info "Moved finished core ${PARTIAL_CORE} to ${CORE_FILENAME}"
   fi

) 200>${LOCK_FILE}
