#!/bin/sh -e
#
# Script for cleaning coredumps. The logic is:
# while (capacity of partition with coredumps) > <threshold> {
#	remove the oldest <dir>/<match_name>* file
# }
#
# $Id$


PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
export PATH


#-- Subroutines --------------------------------------------------------

usage()
{
	local _thiscmd

	_thiscmd=$(basename $0)

	echo 1>&2 "Usage: ${_thiscmd} [-d dir] [-t threshold]" \
		"[-m match_name] [-q]"
	echo 1>&2 "                 [-r [-R match_name [-R ...]]] [-n] [-i n]"
	echo 1>&2 "Options:"
	echo 1>&2 "  -d dir         Directory with coredumps (default:" \
		"directory part of"
	echo 1>&2 "                 ${sysctl_kern_corefile})"
	echo 1>&2 "  -i n           Match only cores which were last modified" \
		"n*24 hours ago." 
	echo 1>&2 "                 +- prefixes are supported."
	echo 1>&2 "  -c n           Prune coredumps which were created n*24" \
		"hours ago regardless" 
	echo 1>&2 "                 of FS capacity. +- prefixes are supported."
	echo 1>&2 "  -n             Don't touch timestamp file." 
	echo 1>&2 "  -t threshold   FS capacity threshold, % (default:" \
		"${default_threshold})"
	echo 1>&2 "  -m match_name  Remove only match_name* coredumps" \
		"(default: \"${default_match_name}\")"
	echo 1>&2 "  -p             Print new cores to stdout (default:" \
		"\"${default_print_new_cores}\")"
	echo 1>&2 "  -r             Make non-root coredumps readable by" \
		"everyone (default: \"${default_mk_ra}\")"
	echo 1>&2 "  -R match_name  Make match_name* root coredumps" \
		"readable by everyone."
	echo 1>&2 "                 May be given multiple times (default:" \
		"\"${default_mk_ra_root}\")"
	echo 1>&2 "  -q             Suppress any script's messages but leave" \
		"binary programs'"
	echo 1>&2 "                 ones (default: \"${default_quiet}\")"
	echo 1>&2 "  -s             Use for sort coredumps by creation time"

	exit 1
}

checkyesno()
{
	local _value

	eval _value=\$$1

	case $_value in
		[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) return 0 ;;
		[Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0) return 1 ;;
		*) return 1 ;;
	esac
}

err()
{
	local _exitval

	_exitval=$1
	shift

	echo 1>&2 "$0: ERROR: $*"
	exit $_exitval
}

quiet_err()
{
	if checkyesno quiet; then
		exit 1
	else
		err $*
	fi
}

get_default_coredumps_dir()
{
	local _kern_corefile _real_corefile _coredumps_dir

	_kern_corefile=$(sysctl -n $sysctl_kern_corefile)

	if [ -z "$(echo $_kern_corefile | egrep -o '^/[^/]+/')" ]; then
		quiet_err 1 "Coredumps place (${_kern_corefile}) is not" \
			"permanent or not safe !"
	fi

	# Linux's realpath does not support -q option.
	# It SHOULD be safe to omit it on FreeBSD too.
	#
	# Also Linux's realpath does not support nonexistent pathes
	# so we strip everything after last /.
	# UPD: realpath changed to readlink (native in bsd and linux)
	_real_corefile=$(readlink ${_kern_corefile%/*} || echo ${_kern_corefile%/*})

	if [ -z "${_real_corefile}" ]; then
		quiet_err 1 "Can't get real path of $sysctl_kern_corefile !"
	fi
	
	# If _real_corefile starts without / then add it
	# for create full path 
	if [ -z "$(echo ${_real_corefile} | grep '^/')" ]; then
	       _real_corefile=$(echo $_real_corefile | sed 's/^/\//')
	fi

	default_dir="${_real_corefile}"
}

get_options()
{
	local _opt

	while getopts "d:t:i:m:nprR:c:qs" _opt; do
		case "$_opt" in
			d) dir="${OPTARG}" ;;
			t) threshold="${OPTARG}" ;;
			i) match_mtime="${OPTARG}" ;;
			c) purge_mtime="${OPTARG}" ;;
			m) match_name="${OPTARG}" ;;
			n) touch_timestamp="NO" ;;
			p) print_new_cores="YES" ;;
			r) mk_ra="YES" ;;
			R) mk_ra="YES"	# "-R <opt>" depends on "-r"
			   mk_ra_root="${mk_ra_root}${mk_ra_root:+ -or }"
			   mk_ra_root="${mk_ra_root}-name ${OPTARG}*" ;;
			q) quiet="YES" ;;
			s) sort_core="YES" ;;
			*) usage ;;
		esac
	done

	shift $(($OPTIND - 1))

	if [ $# -ne 0 ]; then
		usage
	fi
}

check_options()
{

	if [ -z "${dir}" ]; then
		get_default_coredumps_dir
	fi

	: ${dir:=${default_dir}}
	: ${threshold:=${default_threshold}}
	: ${match_name:=${default_match_name}}
	: ${purge_mtime:=${default_purge_mtime}}
	: ${mk_ra:=${default_mk_ra}}
	: ${mk_ra_root:=${default_mk_ra_root}}
	: ${quiet:=${default_quiet}}
	: ${print_new_cores:=${default_print_new_cores}}
	: ${touch_timestamp:=${default_touch_timestamp}}
	: ${sort_core:=${default_sort_core}}
	: ${match_mtime:=''}

	if [ ! -d "${dir}" ]; then
		quiet_err 1 "Can't find coredumps directory ${dir} !"
	fi

	if ! echo "${threshold}" | grep -qE '^[0-9]{1,2}$'; then
		usage
	fi
}

set_find_lim_exp()
{
	if [ -n "$mk_ra_root" ]; then
		find_lim_exp="${find_lim_common_exp} ( \
			${find_lim_non_root_exp} -or ( ${mk_ra_root} ) \
			)"
	else
		find_lim_exp="${find_lim_common_exp} ${find_lim_non_root_exp}"
	fi
}

clean_coredumps()
{
	local _capacity _coredump _find_extra_options _list

	# First pass: Remove everything older than purge_mtime*24 hours
	if [ -n "${purge_mtime}" ]; then
		find $dir -mtime ${purge_mtime} -type f -name "${match_name}*" \
			-and -not -name "${timestamp_file}" -delete 2>/dev/null
	fi
	
	_find_extra_options=''

	if [ -n "${match_mtime}" ]; then
		_find_extra_options="${_find_extra_options} -mtime ${match_mtime} "
	fi

	if checkyesno sort_core; then
	    _list=$(find $dir ${_find_extra_options} -type f \
                -name "${match_name}*" -printf '%T@ %p\n' 2>/dev/null | sort -k1 -n | awk '{print $2}')
	else
	    _list=$(find $dir ${_find_extra_options} -type f \
                -name "${match_name}*" 2>/dev/null)
	fi

	# Second pass: Remove files until space_avail >= $threshold
	for _coredump in ${_list}
	do
		if [ $_coredump = "${dir}/${timestamp_file}" ]; then
			continue
		fi

		_capacity=$(df $dir | awk '
			NR == 2 { sub(/%/, "", $5); print $5 }
			')

		if [ -z "${_capacity}" ]; then
			quiet_err 1 "Can't get capacity of coredumps" \
				"directory !"
		fi

		if [ $_capacity -gt $threshold ]; then
			rm -f $_coredump ||
				quiet_err 1 "Can't remove ${_coredump} !"
		else
			break
		fi
	done
}

change_mode()
{
	if ! checkyesno mk_ra; then
		return
	fi

	if ! find ${dir}/ $find_lim_exp -exec chmod +r {} \;
	then
		quiet_err 1 "Can't change mode for files in ${dir} !"
	fi
}

report_new_cores()
{
	if ! checkyesno print_new_cores; then
		return
	fi

	if [ ! -e ${dir}/${timestamp_file} ]; then
		return
	fi

	local _new_cores_count

	_new_cores="$(find ${dir}/ ${find_lim_exp} \
		-newer ${dir}/${timestamp_file} -print | \
		grep -v \"${timestamp_file}\")"

	if [ -z "$_new_cores" ]; then
		return
	fi

	_new_cores_count=$(echo "$_new_cores" | wc -l)

	if [ $_new_cores_count -gt 0 ]; then
		echo "Fresh ${_new_cores_count} cores are available on" \
			"host $(hostname):"
		# FIXME(rbtz@): This won't handle coredumdps with spaces in name
		echo "$_new_cores" | xargs -n 50 ls -lh || true
		echo "Done."
	fi
}

touch_timestamp()
{
	if checkyesno touch_timestamp; then
		if ! touch ${dir}/${timestamp_file}; then
			quiet_err 1 "Can't touch timestamp file in ${dir}"
		fi
	fi
}


#-- Platform-dependend variables ---------------------------------------
# We also can source $ya_os from new /etc/ya.subr
ya_os="$( uname -s | tr '[:upper:]' '[:lower:]' )"
case "$ya_os" in
freebsd)
	sysctl_kern_corefile="kern.corefile"
	;;
linux)
	sysctl_kern_corefile="kernel.core_pattern"
	;;
*)
	err 1 "Can't determinate OS type. Exiting."
	;;
esac

#-- Variables ----------------------------------------------------------
timestamp_file="core_watcher.timestamp"

default_threshold=80
default_match_name=""
default_mk_ra="NO"
default_mk_ra_root=""
default_quiet="NO"
default_print_new_cores="NO"
default_purge_mtime=""
default_touch_timestamp="YES"
default_sort_core="NO"

find_lim_common_exp="-maxdepth 1 -mindepth 1 -type f"
find_lim_non_root_exp="! -user root"


#-- Main ---------------------------------------------------------------

get_options $*
check_options
set_find_lim_exp
report_new_cores
clean_coredumps
change_mode
touch_timestamp

