/* vi: set sw=4 ts=4: */
/*
 * ulimit builtin
 *
 * Adapted from ash applet code
 *
 * This code, originally by Doug Gwyn, Doug Kingston, Eric Gisin, and
 * Michael Rendell was ripped from pdksh 5.0.8 and hacked for use with
 * ash by J.T. Conklin.
 *
 * Public domain.
 *
 * Copyright (c) 2010 Tobias Klauser
 * Split from ash.c and slightly adapted.
 *
 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
 */
#include "libbb.h"
#include "builtin_ulimit.h"


struct limits {
	uint8_t cmd;            /* RLIMIT_xxx fit into it */
	uint8_t factor_shift;   /* shift by to get rlim_{cur,max} values */
	char option;
	const char *name;
};

static const struct limits limits_tbl[] = {
#ifdef RLIMIT_FSIZE
	{ RLIMIT_FSIZE,		9,	'f',	"file size (blocks)" },
#endif
#ifdef RLIMIT_CPU
	{ RLIMIT_CPU,		0,	't',	"cpu time (seconds)" },
#endif
#ifdef RLIMIT_DATA
	{ RLIMIT_DATA,		10,	'd',	"data seg size (kb)" },
#endif
#ifdef RLIMIT_STACK
	{ RLIMIT_STACK,		10,	's',	"stack size (kb)" },
#endif
#ifdef RLIMIT_CORE
	{ RLIMIT_CORE,		9,	'c',	"core file size (blocks)" },
#endif
#ifdef RLIMIT_RSS
	{ RLIMIT_RSS,		10,	'm',	"resident set size (kb)" },
#endif
#ifdef RLIMIT_MEMLOCK
	{ RLIMIT_MEMLOCK,	10,	'l',	"locked memory (kb)" },
#endif
#ifdef RLIMIT_NPROC
	{ RLIMIT_NPROC,		0,	'p',	"processes" },
#endif
#ifdef RLIMIT_NOFILE
	{ RLIMIT_NOFILE,	0,	'n',	"file descriptors" },
#endif
#ifdef RLIMIT_AS
	{ RLIMIT_AS,		10,	'v',	"address space (kb)" },
#endif
#ifdef RLIMIT_LOCKS
	{ RLIMIT_LOCKS,		0,	'w',	"locks" },
#endif
};

enum {
	OPT_hard = (1 << 0),
	OPT_soft = (1 << 1),
};

/* "-": treat args as parameters of option with ASCII code 1 */
static const char ulimit_opt_string[] = "-HSa"
#ifdef RLIMIT_FSIZE
			"f::"
#endif
#ifdef RLIMIT_CPU
			"t::"
#endif
#ifdef RLIMIT_DATA
			"d::"
#endif
#ifdef RLIMIT_STACK
			"s::"
#endif
#ifdef RLIMIT_CORE
			"c::"
#endif
#ifdef RLIMIT_RSS
			"m::"
#endif
#ifdef RLIMIT_MEMLOCK
			"l::"
#endif
#ifdef RLIMIT_NPROC
			"p::"
#endif
#ifdef RLIMIT_NOFILE
			"n::"
#endif
#ifdef RLIMIT_AS
			"v::"
#endif
#ifdef RLIMIT_LOCKS
			"w::"
#endif
			;

static void printlim(unsigned opts, const struct rlimit *limit,
			const struct limits *l)
{
	rlim_t val;

	val = limit->rlim_max;
	if (!(opts & OPT_hard))
		val = limit->rlim_cur;

	if (val == RLIM_INFINITY)
		printf("unlimited\n");
	else {
		val >>= l->factor_shift;
		printf("%llu\n", (long long) val);
	}
}

int FAST_FUNC shell_builtin_ulimit(char **argv)
{
	unsigned opts;
	unsigned argc;

	/* We can't use getopt32: need to handle commands like
	 * ulimit 123 -c2 -l 456
	 */

	/* In case getopt was already called:
	 * reset the libc getopt() function, which keeps internal state.
	 */
#ifdef __GLIBC__
	optind = 0;
#else /* BSD style */
	optind = 1;
	/* optreset = 1; */
#endif
	/* optarg = NULL; opterr = 0; optopt = 0; - do we need this?? */

        argc = 1;
        while (argv[argc])
                argc++;

	opts = 0;
	while (1) {
		struct rlimit limit;
		const struct limits *l;
		int opt_char = getopt(argc, argv, ulimit_opt_string);

		if (opt_char == -1)
			break;
		if (opt_char == 'H') {
			opts |= OPT_hard;
			continue;
		}
		if (opt_char == 'S') {
			opts |= OPT_soft;
			continue;
		}

		if (opt_char == 'a') {
			for (l = limits_tbl; l != &limits_tbl[ARRAY_SIZE(limits_tbl)]; l++) {
				getrlimit(l->cmd, &limit);
				printf("-%c: %-30s ", l->option, l->name);
				printlim(opts, &limit, l);
			}
			continue;
		}

		if (opt_char == 1)
			opt_char = 'f';
		for (l = limits_tbl; l != &limits_tbl[ARRAY_SIZE(limits_tbl)]; l++) {
			if (opt_char == l->option) {
				char *val_str;

				getrlimit(l->cmd, &limit);

				val_str = optarg;
				if (!val_str && argv[optind] && argv[optind][0] != '-')
					val_str = argv[optind++]; /* ++ skips NN in "-c NN" case */
				if (val_str) {
					rlim_t val;

					if (strcmp(val_str, "unlimited") == 0)
						val = RLIM_INFINITY;
					else {
						if (sizeof(val) == sizeof(int))
							val = bb_strtou(val_str, NULL, 10);
						else if (sizeof(val) == sizeof(long))
							val = bb_strtoul(val_str, NULL, 10);
						else
							val = bb_strtoull(val_str, NULL, 10);
						if (errno) {
							bb_error_msg("bad number");
							return EXIT_FAILURE;
						}
						val <<= l->factor_shift;
					}
//bb_error_msg("opt %c val_str:'%s' val:%lld", opt_char, val_str, (long long)val);
					if (opts & OPT_hard)
						limit.rlim_max = val;
					if ((opts & OPT_soft) || opts == 0)
						limit.rlim_cur = val;
//bb_error_msg("setrlimit(%d, %lld, %lld)", l->cmd, (long long)limit.rlim_cur, (long long)limit.rlim_max);
					if (setrlimit(l->cmd, &limit) < 0) {
						bb_perror_msg("error setting limit");
						return EXIT_FAILURE;
					}
				} else {
					printlim(opts, &limit, l);
				}
				break;
			}
		} /* for (every possible opt) */

		if (l == &limits_tbl[ARRAY_SIZE(limits_tbl)]) {
			/* bad option. getopt already complained. */
			break;
		}

	} /* while (there are options) */

	return 0;
}