#!/bin/sh -efu
#
# Copyright (C) 2008-2009  Alexey Gladkov <legion@altlinux.org>
# Copyright (C) 2008  Dmitry V. Levin <ldv@altlinux.org>
#
# Run program with limited resources
#
# 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 your 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.

. shell-error
. shell-args

show_help()
{
	cat <<EOF
limited - run program with limited resources.

Usage: $PROG [options] [--] [command [arg]...]

Options:
  -n, --name=NAME   apply limits configured for this program name;
  -V, --version     print program version and exit;
  -h, --help        show this text and exit.

Report bugs to http://bugzilla.altlinux.org/

EOF
	exit
}

print_version()
{
	cat <<EOF
$PROG version 0.5.22
Written by Alexey Gladkov <legion@altlinux.org>

Copyright (C) 2008-2009  Alexey Gladkov <legion@altlinux.org>
Copyright (C) 2008  Dmitry V. Levin <ldv@altlinux.org>
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
EOF
	exit
}

TEMP=`getopt -n $PROG -o "n:,$getopt_common_opts" -l "name:,$getopt_common_longopts" -- "$@"` ||
	show_usage
eval set -- "$TEMP"

progname=
while :; do
	case "$1" in
		-n|--name) shift; progname="$1"
			;;
		--) shift; break
			;;
		*) parse_common_option "$1"
			;;
	esac
	shift
done

[ "$#" -gt 0 ] ||
	show_usage 'Insufficient arguments.'

[ -n "$progname" ] ||
	progname="${1##*/}"

for conf in /etc/sysconfig/limits "/etc/sysconfig/limits.d/$progname"; do
	[ ! -s "$conf" ] || . "$conf"
done

# The same as /usr/bin/which - in order to make "which" available before
# /usr is mounted
absolute()
{
	local IFS=:
	for i in $PATH; do
		[ -x "$i/$1" ] ||
			continue
		printf '%s' "$i/$1"
		return 0
	done
	return 1
}

check_ulimit() {
	[ "$2" != 'unlimited' ] ||
		return 0
        [ -n "${2##*[!0-9]*}" ] &&
        [ "$2" -ge 0 ] 2>/dev/null ||
                fatal "$1: $2: invalid number."
}

set_ulimit() {
	local opt="$1" name="$2"
	local prev_slimit prev_hlimit slimit hlimit

	prev_slimit="$(ulimit -S$opt)"
	prev_hlimit="$(ulimit -H$opt)"

	eval "slimit=\"\${RLIMIT_SOFT_$name:-$prev_slimit}\";
	      hlimit=\"\${RLIMIT_HARD_$name:-$prev_hlimit}\";"

	[ "$prev_slimit" != "$slimit" -o "$prev_hlimit" != "$hlimit" ] ||
		return 0

	check_ulimit "RLIMIT_SOFT_$name" "$slimit"
	check_ulimit "RLIMIT_HARD_$name" "$hlimit"

	{ ulimit -H$opt $hlimit && ulimit -S$opt $slimit; } 2>/dev/null ||
	{ ulimit -S$opt $slimit && ulimit -H$opt $hlimit; } 2>/dev/null ||
		fatal "$name: Unable to set limit"
}

for p in \
	c:CORE \
	d:DATA \
	e:NICE \
	f:FSIZE \
	i:SIGPENDING \
	l:MEMLOCK \
	m:RSS \
	n:NOFILE \
	q:MSGQUEUE \
	r:RTPRIO \
	s:STACK \
	t:CPU \
	u:NPROC \
	v:AS \
	x:LOCKS \
	;
do
	# Attention: ulimit options are shell-specific.
	set_ulimit "${p%:*}" "${p#*:}"
done

cmd=
if [ -n "${NICE_PRIORITY-}" ] && nice_cmd="$(absolute nice)"; then
	$nice_cmd -n "$NICE_PRIORITY" true 2>/dev/null &&
		cmd="$nice_cmd -n $NICE_PRIORITY --"
fi

if [ -n "${IONICE_PRIORITY-}" ] && ionice_cmd="$(absolute ionice)"; then
	$ionice_cmd -t -c $IONICE_PRIORITY true 2>/dev/null &&
		cmd="$cmd $ionice_cmd -t -c $IONICE_PRIORITY --"
fi

exec $cmd "$@"
