util-linux.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* Re: [PATCH 2/3] prlimit: new program
  2011-10-17 23:23 [PATCH 2/3] prlimit: new program Davidlohr Bueso
@ 2011-10-17 15:08 ` Karel Zak
  2011-10-19 20:02   ` Davidlohr Bueso
  0 siblings, 1 reply; 4+ messages in thread
From: Karel Zak @ 2011-10-17 15:08 UTC (permalink / raw)
  To: Davidlohr Bueso; +Cc: util-linux

On Mon, Oct 17, 2011 at 07:23:09PM -0400, Davidlohr Bueso wrote:
> +struct prlimit {
> +	char *name;
> +	char *help;
> +	int modify;          /* are we changing the limit? */
> +	int resource;        /* RLIMIT_* id */
> +	struct rlimit rlim;  /* soft,hard limits */
> +} default_lims[] = { /* when no --{resource_name} is passed */
> +	{"AS", "address space limit", 0, RLIMIT_AS, {0, 0}},

 N_("address space limit")

> +	{"CORE", "max core file size", 0, RLIMIT_CORE, {0, 0}},
> +	{"CPU", "CPU time in secs", 0, RLIMIT_CPU, {0, 0}},
> +	{"DATA", "max data size", 0, RLIMIT_DATA, {0, 0}},
> +	{"FSIZE", "max file size", 0, RLIMIT_FSIZE, {0, 0}},
> +	{"LOCKS", "max amount of file locks held", 0, RLIMIT_LOCKS, {0, 0}},
> +	{"MEMLOCK", "max locked-in-memory address space", 0, RLIMIT_MEMLOCK, {0, 0}},
> +	{"MSGQUEUE", "max bytes in POSIX mqueues", 0, RLIMIT_MSGQUEUE, {0, 0}},
> +	{"NICE", "max nice prio allowed to raise", 0, RLIMIT_NICE, {0, 0}},
> +	{"NOFILE", "max amount of open files", 0, RLIMIT_NOFILE, {0, 0}},
> +	{"NPROC", "max number of processes", 0, RLIMIT_NPROC, {0, 0}},
> +	{"RSS", "max resident set size", 0, RLIMIT_RSS, {0, 0}},
> +	{"RTPRIO", "max real-time priority", 0, RLIMIT_RTPRIO, {0, 0}},
> +	{"RTTIME", "timeout for real-time tasks", 0, RLIMIT_RTTIME, {0, 0}},
> +	{"SIGPENDING", "max amount of pending signals", 0, RLIMIT_SIGPENDING, {0, 0}},
> +	{"STACK", "max stack size", 0, RLIMIT_STACK, {0, 0}}
> +};
> +
> +#define MAX_RESOURCES ARRAY_SIZE(default_lims)
> +
> +/* to reuse data, maintain the same order used in default_lims[] */

 Please, define this enum before default_lims[] and use

   { 
     [enum] = { },
   }

 convention for the default_lims array. Anyway, see my note about
 default_lims below.

> +enum {
> +	AS,
> +	CORE,
> +	CPU,
> +	DATA,
> +	FSIZE,
> +	LOCKS,
> +	MEMLOCK,
> +	MSGQUEUE,
> +	NICE,
> +	NOFILE,
> +	NPROC,
> +	RSS,
> +	RTPRIO,
> +	RTTIME,
> +	SIGPENDING,
> +	STACK
> +};
> +
> +enum {
> +	COL_HELP,
> +	COL_RES,
> +	COL_SOFT,
> +	COL_HARD,
> +	__NCOLUMNS
> +};

 It would be better to use ARRAY_SIZE everywhere than __NCOLUMNS (yes,
 patch for lsblk is wanted :-)

> +/* column names */
> +struct colinfo {
> +	const char	*name;	/* header */
> +	double		whint;	/* width hint (N < 1 is in percent of termwidth) */
> +	int		flags;	/* TT_FL_* */
> +	const char      *help;
> +};
> +
> +/* columns descriptions */
> +struct colinfo infos[__NCOLUMNS] = {

  struct colinfo infos[] =

is enough.

> +	[COL_RES]     = { "RESOURCE",    0.25, TT_FL_RIGHT, N_("resource name") },
> +	[COL_HELP]    = { "DESCRIPTION", 0.1,  TT_FL_TRUNC, N_("resource description")},
> +	[COL_SOFT]    = { "SOFT",        0.1,  TT_FL_TRUNC, N_("soft limit")},

 Please, always use TT_FL_RIGHT for numbers.

> +	[COL_HARD]    = { "HARD",        1,    TT_FL_RIGHT, N_("hard limit (ceiling)")},
> +};
> +
> +/* array with IDs of enabled columns */
> +static int columns[__NCOLUMNS], ncolumns;
> +
> +static void __attribute__ ((__noreturn__)) usage(FILE * out)
> +{
> +	size_t i;
> +
> +	fputs(_("\nUsage:\n"), out);

 fputs(HELP_USAGE, out);  See c.h for HELP_* macros.

> +	fprintf(out,
> +		_(" %s [options]\n"), program_invocation_short_name);
> +
> +	fputs(_("\nGeneral Options:\n"), out);
> +	fputs(_(" -p, --pid <pid>        process id\n"
> +		" -o, --output <type>    define which output columns to use\n"
> +		" -h, --help             display this help and exit\n"
> +		" -V, --version          output version information and exit\n"), out);
> +
> +	fputs(_("\nResources Options:\n"), out);
> +	fputs(_(" -a, --as               address space limit\n"
> +		" -c, --core             max core file size\n"
> +		" -C, --cpu              CPU time in secs\n"
> +		" -d, --data             max data size\n"
> +		" -f, --fsize            max file size\n"
> +		" -l, --locks            max amount of file locks held\n"
> +		" -m, --memlock          max locked-in-memory address space\n"
> +		" -M, --msgqueue         max bytes in POSIX mqueues\n"
> +		" -n, --nice             max nice priority allowed to raise\n"
> +		" -N, --nofile           max amount of open files\n"
> +		" -P, --nproc            max number of processes\n"
> +		" -r, --rss              max resident set size\n"
> +		" -R, --rtprio           max real-time priority\n"
> +		" -t, --rttime           timeout for real-time teasks\n"
> +		" -s, --sigpending       max amount of pending signals\n"
> +		" -S, --stack            max stack size\n"), out);
> +
> +	fputs(_("\nAvailable columns (for --output):\n"), out);
> +
> +	for (i = 0; i < __NCOLUMNS; i++)
> +		fprintf(out, " %11s  %s\n", infos[i].name, _(infos[i].help));
> +
> +
> +	fprintf(out, _("\nFor more information see prlimit(1).\n\n"));

  fprintf(out, USAGE_MAN_TAIL("prlimit(1)");

 [...]

> +/*
> + * Obtain the soft and hard limits, from arguments in the form <soft>:<hard>
> + */
> +static int parse_prlim(pid_t pid, struct rlimit *lim, int res, const char *ops, const char *errmsg)
> +{
> +	int soft, hard;
> +
> +	if (parse_range(ops, &soft, &hard, -3))

please, don't use magic constants, use:

    if (parse_range(ops, &soft, &hard, PRLIMIT_UNKNOWN))

or so...

> +		errx(EXIT_FAILURE, "%s", errmsg);

It would be nice to support RLIM_INFINITY, something like:

   --core=unlimited or --core=10000:unlimited

btw it would be better to use RLIM_INFINITY macro rather than -1 in the code.

> +static void do_prlimit(pid_t pid, struct prlimit lims[], const int n)
> +{
> +	int i;
> +	struct rlimit *new;

int nshows = 0;

> +	for (i = 0; i < n; i++) {
> +		new = lims[i].modify ? &lims[i].rlim : NULL;

        struct rlimit *new = NULL;

        if (lims[i].modify)
            new = &lims[i].rlim;
        else
            nshows++;

> +		if (prlimit(pid, lims[i].resource, new, &lims[i].rlim) == -1)
> +			err(EXIT_FAILURE, _("failed to get resource limits for PID %d"), pid);
> +	}
> +
> +	show_limits(lims, 0, n);

 if (nshows)
    show_limits(lims, 0, n);

 .. so don't waste time with show_limits() for commands like

    prlimit --pid $$ --core=10000

 where nothing is expected on stdout ;-)

> +}
> +
> +/*
> + * Add a resource limit to the limits array
> + */
> +static int add_prlimit(pid_t pid, struct prlimit lims[], const char *ops,
> +		       int n, int id, const char *errmsg)
> +{
> +	/* setup basic data */
> +	lims[n].name = default_lims[id].name;
> +	lims[n].help = default_lims[id].help;
> +	lims[n].resource = default_lims[id].resource;

 Hmmm... this is poor design, the data structs are core of the well
 designed programs.
 
 What about:

 struct prlimit_desc {
    const char *name,
               *help;
    int        resource;
 };

 struct prlimit {
   struct prlimit_desc *desc;

   int           modify;
   struct rlimit rlim;
 };

I think it would be better to split resources description and the
limits.

> +	if (ops) { /* planning on modifying a limit? */
> +		lims[n].modify = 1;
> +		parse_prlim(pid, &lims[n].rlim, lims[n].resource, ops, errmsg);
> +	} else
> +		lims[n].modify = 0;
> +
> +	return 0;
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	int opt, n = 0;
> +	pid_t pid = 0; /* calling process (default) */
> +	struct prlimit lims[MAX_RESOURCES];
> +	static const struct option longopts[] = {
> +		{"pid", required_argument, NULL, 'p'},
> +		{"output", required_argument, NULL, 'o'},
> +		{"as", optional_argument, NULL, 'a'},
> +		{"core", optional_argument, NULL, 'c'},
> +		{"cpu", optional_argument, NULL, 'C'},
> +		{"data", optional_argument, NULL, 'd'},
> +		{"fsize", optional_argument, NULL, 'f'},
> +		{"locks", optional_argument, NULL, 'l'},
> +		{"memlock", optional_argument, NULL, 'm'},
> +		{"msgqueue", optional_argument, NULL, 'M'},
> +		{"nice", optional_argument, NULL, 'n'},
> +		{"nofile", optional_argument, NULL, 'N'},
> +		{"nproc", optional_argument, NULL, 'P'},
> +		{"rss", optional_argument, NULL, 'r'},
> +		{"rtprio", optional_argument, NULL, 'R'},
> +		{"rttime", optional_argument, NULL, 't'},
> +		{"sigpending", optional_argument, NULL, 's'},
> +		{"stack", optional_argument, NULL, 'S'},
> +		{"version", no_argument, NULL, 'V'},
> +		{"help", no_argument, NULL, 'h'},
> +		{NULL, 0, NULL, 0}
> +	};
> +
> +	setlocale(LC_ALL, "");
> +	bindtextdomain(PACKAGE, LOCALEDIR);
> +	textdomain(PACKAGE);
> +
> +	memset(lims, 0, sizeof(lims));
> +
> +	/*
> +	 * Something is very wrong if this doesn't succeed,
> +	 * assuming STACK is the last resource, of course.
> +	 */
> +	assert(MAX_RESOURCES == STACK + 1);
> +
> +	while((opt = getopt_long(argc, argv,
> +				 "a::c::C::d::f::l::m::M::n::N::P::r::R::t::s::S::p:o:vVh",
> +				 longopts, NULL)) != -1) {
> +		switch(opt) {
> +		case 'p':
> +			pid = strtol_or_err(optarg, _("cannot parse PID"));
> +			break;

 What will happen if:

    prlimit --pid 123 --code=10000 --pid 456 --cpu=1
    
> +		case 'a':
> +			add_prlimit(pid, lims, optarg, n++, AS, "failed to parse AS limit");

 The error message is unnecessary here. You can compose the message in
 the parse_prlim() function from resource ID. 

 It would be also better to use pointer to the prlimit struct in your
 parse_* function that use two options 'struct rlimit *lim' and 'int res'.

 So:

       case 'a':
           add_prlimit(pid, optarg, &lims[n++], AS);

 and in add_prlimit() you can initialize the pointer to desc:

    lims->desc = prlimit_desc[id];

 [...]

> +	n == 0 ? do_prlimit(pid, default_lims, MAX_RESOURCES) :
> +		 do_prlimit(pid, lims, n);

 or if you replace default_lims with prlimit_desc:

  if (!n) {
    for (n = 0; n < MAX_RESOURCES; n++)
        add_prlimit(pid, NULL, &lims[n], n);
  }

  do_prlimit(pid, lims, n);

 ;-)

    Karel

-- 
 Karel Zak  <kzak@redhat.com>
 http://karelzak.blogspot.com

^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH 2/3] prlimit: new program
@ 2011-10-17 23:23 Davidlohr Bueso
  2011-10-17 15:08 ` Karel Zak
  0 siblings, 1 reply; 4+ messages in thread
From: Davidlohr Bueso @ 2011-10-17 23:23 UTC (permalink / raw)
  To: Karel Zak; +Cc: util-linux

From: Davidlohr Bueso <dave@gnu.org>

This program uses the prlimit() system call to get and/or set resource limits for a given process.

Signed-off-by: Davidlohr Bueso <dave@gnu.org>
---
 sys-utils/prlimit.c |  454 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 454 insertions(+), 0 deletions(-)
 create mode 100644 sys-utils/prlimit.c

diff --git a/sys-utils/prlimit.c b/sys-utils/prlimit.c
new file mode 100644
index 0000000..33b47a4
--- /dev/null
+++ b/sys-utils/prlimit.c
@@ -0,0 +1,454 @@
+/*
+ *  prlimit - get/set process resource limits.
+ *
+ *  Copyright (C) 2011 Davidlohr Bueso <dave@gnu.org>
+ *
+ *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/resource.h>
+
+#include "c.h"
+#include "nls.h"
+#include "tt.h"
+#include "xalloc.h"
+#include "strutils.h"
+
+struct prlimit {
+	char *name;
+	char *help;
+	int modify;          /* are we changing the limit? */
+	int resource;        /* RLIMIT_* id */
+	struct rlimit rlim;  /* soft,hard limits */
+} default_lims[] = { /* when no --{resource_name} is passed */
+	{"AS", "address space limit", 0, RLIMIT_AS, {0, 0}},
+	{"CORE", "max core file size", 0, RLIMIT_CORE, {0, 0}},
+	{"CPU", "CPU time in secs", 0, RLIMIT_CPU, {0, 0}},
+	{"DATA", "max data size", 0, RLIMIT_DATA, {0, 0}},
+	{"FSIZE", "max file size", 0, RLIMIT_FSIZE, {0, 0}},
+	{"LOCKS", "max amount of file locks held", 0, RLIMIT_LOCKS, {0, 0}},
+	{"MEMLOCK", "max locked-in-memory address space", 0, RLIMIT_MEMLOCK, {0, 0}},
+	{"MSGQUEUE", "max bytes in POSIX mqueues", 0, RLIMIT_MSGQUEUE, {0, 0}},
+	{"NICE", "max nice prio allowed to raise", 0, RLIMIT_NICE, {0, 0}},
+	{"NOFILE", "max amount of open files", 0, RLIMIT_NOFILE, {0, 0}},
+	{"NPROC", "max number of processes", 0, RLIMIT_NPROC, {0, 0}},
+	{"RSS", "max resident set size", 0, RLIMIT_RSS, {0, 0}},
+	{"RTPRIO", "max real-time priority", 0, RLIMIT_RTPRIO, {0, 0}},
+	{"RTTIME", "timeout for real-time tasks", 0, RLIMIT_RTTIME, {0, 0}},
+	{"SIGPENDING", "max amount of pending signals", 0, RLIMIT_SIGPENDING, {0, 0}},
+	{"STACK", "max stack size", 0, RLIMIT_STACK, {0, 0}}
+};
+
+#define MAX_RESOURCES ARRAY_SIZE(default_lims)
+
+/* to reuse data, maintain the same order used in default_lims[] */
+enum {
+	AS,
+	CORE,
+	CPU,
+	DATA,
+	FSIZE,
+	LOCKS,
+	MEMLOCK,
+	MSGQUEUE,
+	NICE,
+	NOFILE,
+	NPROC,
+	RSS,
+	RTPRIO,
+	RTTIME,
+	SIGPENDING,
+	STACK
+};
+
+enum {
+	COL_HELP,
+	COL_RES,
+	COL_SOFT,
+	COL_HARD,
+	__NCOLUMNS
+};
+
+/* column names */
+struct colinfo {
+	const char	*name;	/* header */
+	double		whint;	/* width hint (N < 1 is in percent of termwidth) */
+	int		flags;	/* TT_FL_* */
+	const char      *help;
+};
+
+/* columns descriptions */
+struct colinfo infos[__NCOLUMNS] = {
+	[COL_RES]     = { "RESOURCE",    0.25, TT_FL_RIGHT, N_("resource name") },
+	[COL_HELP]    = { "DESCRIPTION", 0.1,  TT_FL_TRUNC, N_("resource description")},
+	[COL_SOFT]    = { "SOFT",        0.1,  TT_FL_TRUNC, N_("soft limit")},
+	[COL_HARD]    = { "HARD",        1,    TT_FL_RIGHT, N_("hard limit (ceiling)")},
+};
+
+/* array with IDs of enabled columns */
+static int columns[__NCOLUMNS], ncolumns;
+
+static void __attribute__ ((__noreturn__)) usage(FILE * out)
+{
+	size_t i;
+
+	fputs(_("\nUsage:\n"), out);
+	fprintf(out,
+		_(" %s [options]\n"), program_invocation_short_name);
+
+	fputs(_("\nGeneral Options:\n"), out);
+	fputs(_(" -p, --pid <pid>        process id\n"
+		" -o, --output <type>    define which output columns to use\n"
+		" -h, --help             display this help and exit\n"
+		" -V, --version          output version information and exit\n"), out);
+
+	fputs(_("\nResources Options:\n"), out);
+	fputs(_(" -a, --as               address space limit\n"
+		" -c, --core             max core file size\n"
+		" -C, --cpu              CPU time in secs\n"
+		" -d, --data             max data size\n"
+		" -f, --fsize            max file size\n"
+		" -l, --locks            max amount of file locks held\n"
+		" -m, --memlock          max locked-in-memory address space\n"
+		" -M, --msgqueue         max bytes in POSIX mqueues\n"
+		" -n, --nice             max nice priority allowed to raise\n"
+		" -N, --nofile           max amount of open files\n"
+		" -P, --nproc            max number of processes\n"
+		" -r, --rss              max resident set size\n"
+		" -R, --rtprio           max real-time priority\n"
+		" -t, --rttime           timeout for real-time teasks\n"
+		" -s, --sigpending       max amount of pending signals\n"
+		" -S, --stack            max stack size\n"), out);
+
+	fputs(_("\nAvailable columns (for --output):\n"), out);
+
+	for (i = 0; i < __NCOLUMNS; i++)
+		fprintf(out, " %11s  %s\n", infos[i].name, _(infos[i].help));
+
+
+	fprintf(out, _("\nFor more information see prlimit(1).\n\n"));
+
+	exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+static inline int get_column_id(int num)
+{
+	assert(ARRAY_SIZE(columns) == __NCOLUMNS);
+	assert(num < ncolumns);
+	assert(columns[num] < __NCOLUMNS);
+
+	return columns[num];
+}
+
+static inline struct colinfo *get_column_info(int num)
+{
+	return &infos[ get_column_id(num) ];
+}
+
+static void add_tt_line(struct tt *tt, struct prlimit l)
+{
+	int i;
+	struct tt_line *line;
+
+	assert(tt);
+	assert(&l);
+
+	line = tt_add_line(tt, NULL);
+	if (!line) {
+		warn(_("failed to add line to output"));
+		return;
+	}
+
+	for (i = 0; i < ncolumns; i++) {
+		char *str = NULL;
+		int rc = 0;
+
+		switch (get_column_id(i)) {
+		case COL_RES:
+			rc = asprintf(&str, "%s", l.name);
+			break;
+		case COL_HELP:
+			rc = asprintf(&str, "%s", l.help);
+			break;
+		case COL_SOFT:
+			rc = l.rlim.rlim_cur == -1 ? asprintf(&str, "%s", "unlimited") :
+						     asprintf(&str, "%lld", l.rlim.rlim_cur);
+			break;
+		case COL_HARD:
+			rc = l.rlim.rlim_max == -1 ? asprintf(&str, "%s", "unlimited") :
+						     asprintf(&str, "%lld", l.rlim.rlim_max);
+			break;
+		default:
+			break;
+		}
+
+		if (rc || str)
+			tt_line_set_data(line, i, str);
+	}
+}
+
+static int column_name_to_id(const char *name, size_t namesz)
+{
+	int i;
+
+	assert(name);
+
+	for (i = 0; i < __NCOLUMNS; i++) {
+		const char *cn = infos[i].name;
+
+		if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+			return i;
+	}
+	warnx(_("unknown column: %s"), name);
+	return -1;
+}
+
+static int show_limits(struct prlimit lims[], int tt_flags, int n)
+{
+	int i;
+	struct tt *tt;
+
+	tt = tt_new_table(tt_flags);
+	if (!tt) {
+		warn(_("failed to initialize output table"));
+		return -1;
+	}
+
+	for (i = 0; i < ncolumns; i++) {
+		struct colinfo *col = get_column_info(i);
+
+		if (!tt_define_column(tt, col->name, col->whint, col->flags)) {
+			warnx(_("failed to initialize output column"));
+			goto done;
+		}
+	}
+
+	for (i = 0; i < n; i++)
+		if (!lims[i].modify) /* only display old limits */
+			add_tt_line(tt, lims[i]);
+
+	tt_print_table(tt);
+done:
+	tt_free_table(tt);
+	return 0;
+}
+
+/*
+ * Obtain the soft and hard limits, from arguments in the form <soft>:<hard>
+ */
+static int parse_prlim(pid_t pid, struct rlimit *lim, int res, const char *ops, const char *errmsg)
+{
+	int soft, hard;
+
+	if (parse_range(ops, &soft, &hard, -3))
+		errx(EXIT_FAILURE, "%s", errmsg);
+
+	/*
+	 * If one of the limits is -3 (default value for not being passed), we need
+	 * to get the current limit and use it.
+	 */
+	if (soft == -3 || hard == -3) {
+		struct rlimit old;
+
+		if (prlimit(pid, res, NULL, &old) == -1)
+			errx(EXIT_FAILURE, _("failed to get old limit"));
+
+		if (soft == -3)
+			soft = old.rlim_cur;
+		else if (hard == -3)
+			hard = old.rlim_max;
+	}
+
+	if (soft > hard)
+		errx(EXIT_FAILURE, _("the soft limit cannot exceed the ceiling value"));
+
+	lim->rlim_cur = (rlim_t) soft;
+	lim->rlim_max = (rlim_t) hard;
+
+	return 0;
+}
+
+static void do_prlimit(pid_t pid, struct prlimit lims[], const int n)
+{
+	int i;
+	struct rlimit *new;
+
+	for (i = 0; i < n; i++) {
+		new = lims[i].modify ? &lims[i].rlim : NULL;
+
+		if (prlimit(pid, lims[i].resource, new, &lims[i].rlim) == -1)
+			err(EXIT_FAILURE, _("failed to get resource limits for PID %d"), pid);
+	}
+
+	show_limits(lims, 0, n);
+}
+
+/*
+ * Add a resource limit to the limits array
+ */
+static int add_prlimit(pid_t pid, struct prlimit lims[], const char *ops,
+		       int n, int id, const char *errmsg)
+{
+	/* setup basic data */
+	lims[n].name = default_lims[id].name;
+	lims[n].help = default_lims[id].help;
+	lims[n].resource = default_lims[id].resource;
+
+	if (ops) { /* planning on modifying a limit? */
+		lims[n].modify = 1;
+		parse_prlim(pid, &lims[n].rlim, lims[n].resource, ops, errmsg);
+	} else
+		lims[n].modify = 0;
+
+	return 0;
+}
+
+int main(int argc, char **argv)
+{
+	int opt, n = 0;
+	pid_t pid = 0; /* calling process (default) */
+	struct prlimit lims[MAX_RESOURCES];
+	static const struct option longopts[] = {
+		{"pid", required_argument, NULL, 'p'},
+		{"output", required_argument, NULL, 'o'},
+		{"as", optional_argument, NULL, 'a'},
+		{"core", optional_argument, NULL, 'c'},
+		{"cpu", optional_argument, NULL, 'C'},
+		{"data", optional_argument, NULL, 'd'},
+		{"fsize", optional_argument, NULL, 'f'},
+		{"locks", optional_argument, NULL, 'l'},
+		{"memlock", optional_argument, NULL, 'm'},
+		{"msgqueue", optional_argument, NULL, 'M'},
+		{"nice", optional_argument, NULL, 'n'},
+		{"nofile", optional_argument, NULL, 'N'},
+		{"nproc", optional_argument, NULL, 'P'},
+		{"rss", optional_argument, NULL, 'r'},
+		{"rtprio", optional_argument, NULL, 'R'},
+		{"rttime", optional_argument, NULL, 't'},
+		{"sigpending", optional_argument, NULL, 's'},
+		{"stack", optional_argument, NULL, 'S'},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 'h'},
+		{NULL, 0, NULL, 0}
+	};
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	textdomain(PACKAGE);
+
+	memset(lims, 0, sizeof(lims));
+
+	/*
+	 * Something is very wrong if this doesn't succeed,
+	 * assuming STACK is the last resource, of course.
+	 */
+	assert(MAX_RESOURCES == STACK + 1);
+
+	while((opt = getopt_long(argc, argv,
+				 "a::c::C::d::f::l::m::M::n::N::P::r::R::t::s::S::p:o:vVh",
+				 longopts, NULL)) != -1) {
+		switch(opt) {
+		case 'p':
+			pid = strtol_or_err(optarg, _("cannot parse PID"));
+			break;
+		case 'a':
+			add_prlimit(pid, lims, optarg, n++, AS, "failed to parse AS limit");
+			break;
+		case 'c':
+			add_prlimit(pid, lims, optarg, n++, CORE, "failed to parse CORE limit");
+			break;
+		case 'C':
+			add_prlimit(pid, lims, optarg, n++, CPU, "failed to parse CPU limit");
+			break;
+		case 'd':
+			add_prlimit(pid, lims, optarg, n++, DATA, "failed to parse DATA limit");
+			break;
+		case 'f':
+			add_prlimit(pid, lims, optarg, n++, FSIZE, "failed to parse FSIZE limit");
+			break;
+		case 'l':
+			add_prlimit(pid, lims, optarg, n++, LOCKS, "failed to parse LOCKS limit");
+			break;
+		case 'm':
+			add_prlimit(pid, lims, optarg, n++, MEMLOCK, "failed to parse MEMLOCK limit");
+			break;
+		case 'M':
+			add_prlimit(pid, lims, optarg, n++, MSGQUEUE, "failed to parse MSGQUEUE limit");
+			break;
+		case 'n':
+			add_prlimit(pid, lims, optarg, n++, NICE, "failed to parse NICE limit");
+			break;
+		case 'N':
+			add_prlimit(pid, lims, optarg, n++, NOFILE, "failed to parse NOFILE limit");
+			break;
+		case 'P':
+			add_prlimit(pid, lims, optarg, n++, NPROC, "failed to parse NPROC limit");
+			break;
+		case 'r':
+			add_prlimit(pid, lims, optarg, n++, RSS, "failed to parse RSS limit");
+			break;
+		case 'R':
+			add_prlimit(pid, lims, optarg, n++, RTPRIO, "failed to parse RTPRIO limit");
+			break;
+		case 't':
+			add_prlimit(pid, lims, optarg, n++, RTTIME, "failed to parse RTTIME limit");
+			break;
+		case 's':
+			add_prlimit(pid, lims, optarg, n++, SIGPENDING, "failed to parse SIGPENDING limit");
+			break;
+		case 'S':
+			add_prlimit(pid, lims, optarg, n++, STACK, "failed to parse STACK limit");
+			break;
+		case 'h':
+			usage(stdout);
+			break;
+		case 'o':
+			ncolumns = string_to_idarray(optarg,
+						     columns, ARRAY_SIZE(columns),
+						     column_name_to_id);
+			if (ncolumns < 0)
+				return EXIT_FAILURE;
+			break;
+		case 'V':
+			printf(UTIL_LINUX_VERSION);
+			return EXIT_SUCCESS;
+		default:
+			usage(stderr);
+			break;
+		}
+	}
+
+	if (argc == 1)
+		usage(stderr);
+
+	if (!ncolumns) {
+		columns[ncolumns++] = COL_RES;
+		columns[ncolumns++] = COL_HELP;
+		columns[ncolumns++] = COL_SOFT;
+		columns[ncolumns++] = COL_HARD;
+	}
+
+	n == 0 ? do_prlimit(pid, default_lims, MAX_RESOURCES) :
+		 do_prlimit(pid, lims, n);
+
+	return EXIT_SUCCESS;
+}
-- 
1.7.7

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH 2/3] prlimit: new program
  2011-10-17 15:08 ` Karel Zak
@ 2011-10-19 20:02   ` Davidlohr Bueso
  2011-10-21 21:29     ` Karel Zak
  0 siblings, 1 reply; 4+ messages in thread
From: Davidlohr Bueso @ 2011-10-19 20:02 UTC (permalink / raw)
  To: Karel Zak; +Cc: util-linux

On Mon, 2011-10-17 at 17:08 +0200, Karel Zak wrote:
> On Mon, Oct 17, 2011 at 07:23:09PM -0400, Davidlohr Bueso wrote:
> > +struct prlimit {
> > +	char *name;
> > +	char *help;
> > +	int modify;          /* are we changing the limit? */
> > +	int resource;        /* RLIMIT_* id */
> > +	struct rlimit rlim;  /* soft,hard limits */
> > +} default_lims[] = { /* when no --{resource_name} is passed */
> > +	{"AS", "address space limit", 0, RLIMIT_AS, {0, 0}},
> 
>  N_("address space limit")
> 
> > +	{"CORE", "max core file size", 0, RLIMIT_CORE, {0, 0}},
> > +	{"CPU", "CPU time in secs", 0, RLIMIT_CPU, {0, 0}},
> > +	{"DATA", "max data size", 0, RLIMIT_DATA, {0, 0}},
> > +	{"FSIZE", "max file size", 0, RLIMIT_FSIZE, {0, 0}},
> > +	{"LOCKS", "max amount of file locks held", 0, RLIMIT_LOCKS, {0, 0}},
> > +	{"MEMLOCK", "max locked-in-memory address space", 0, RLIMIT_MEMLOCK, {0, 0}},
> > +	{"MSGQUEUE", "max bytes in POSIX mqueues", 0, RLIMIT_MSGQUEUE, {0, 0}},
> > +	{"NICE", "max nice prio allowed to raise", 0, RLIMIT_NICE, {0, 0}},
> > +	{"NOFILE", "max amount of open files", 0, RLIMIT_NOFILE, {0, 0}},
> > +	{"NPROC", "max number of processes", 0, RLIMIT_NPROC, {0, 0}},
> > +	{"RSS", "max resident set size", 0, RLIMIT_RSS, {0, 0}},
> > +	{"RTPRIO", "max real-time priority", 0, RLIMIT_RTPRIO, {0, 0}},
> > +	{"RTTIME", "timeout for real-time tasks", 0, RLIMIT_RTTIME, {0, 0}},
> > +	{"SIGPENDING", "max amount of pending signals", 0, RLIMIT_SIGPENDING, {0, 0}},
> > +	{"STACK", "max stack size", 0, RLIMIT_STACK, {0, 0}}
> > +};
> > +
> > +#define MAX_RESOURCES ARRAY_SIZE(default_lims)
> > +
> > +/* to reuse data, maintain the same order used in default_lims[] */
> 
>  Please, define this enum before default_lims[] and use
> 
>    { 
>      [enum] = { },
>    }
> 
>  convention for the default_lims array. Anyway, see my note about
>  default_lims below.
> 
> > +enum {
> > +	AS,
> > +	CORE,
> > +	CPU,
> > +	DATA,
> > +	FSIZE,
> > +	LOCKS,
> > +	MEMLOCK,
> > +	MSGQUEUE,
> > +	NICE,
> > +	NOFILE,
> > +	NPROC,
> > +	RSS,
> > +	RTPRIO,
> > +	RTTIME,
> > +	SIGPENDING,
> > +	STACK
> > +};
> > +
> > +enum {
> > +	COL_HELP,
> > +	COL_RES,
> > +	COL_SOFT,
> > +	COL_HARD,
> > +	__NCOLUMNS
> > +};
> 
>  It would be better to use ARRAY_SIZE everywhere than __NCOLUMNS (yes,
>  patch for lsblk is wanted :-)

And partx too.

> > +/* column names */
> > +struct colinfo {
> > +	const char	*name;	/* header */
> > +	double		whint;	/* width hint (N < 1 is in percent of termwidth) */
> > +	int		flags;	/* TT_FL_* */
> > +	const char      *help;
> > +};
> > +
> > +/* columns descriptions */
> > +struct colinfo infos[__NCOLUMNS] = {
> 
>   struct colinfo infos[] =
> 
> is enough.
> 
> > +	[COL_RES]     = { "RESOURCE",    0.25, TT_FL_RIGHT, N_("resource name") },
> > +	[COL_HELP]    = { "DESCRIPTION", 0.1,  TT_FL_TRUNC, N_("resource description")},
> > +	[COL_SOFT]    = { "SOFT",        0.1,  TT_FL_TRUNC, N_("soft limit")},
> 
>  Please, always use TT_FL_RIGHT for numbers.
> 

Yes, much nicer output.

> > +	[COL_HARD]    = { "HARD",        1,    TT_FL_RIGHT, N_("hard limit (ceiling)")},
> > +};
> > +
> > +/* array with IDs of enabled columns */
> > +static int columns[__NCOLUMNS], ncolumns;
> > +
> > +static void __attribute__ ((__noreturn__)) usage(FILE * out)
> > +{
> > +	size_t i;
> > +
> > +	fputs(_("\nUsage:\n"), out);
> 
>  fputs(HELP_USAGE, out);  See c.h for HELP_* macros.
> 
> > +	fprintf(out,
> > +		_(" %s [options]\n"), program_invocation_short_name);
> > +
> > +	fputs(_("\nGeneral Options:\n"), out);
> > +	fputs(_(" -p, --pid <pid>        process id\n"
> > +		" -o, --output <type>    define which output columns to use\n"
> > +		" -h, --help             display this help and exit\n"
> > +		" -V, --version          output version information and exit\n"), out);
> > +
> > +	fputs(_("\nResources Options:\n"), out);
> > +	fputs(_(" -a, --as               address space limit\n"
> > +		" -c, --core             max core file size\n"
> > +		" -C, --cpu              CPU time in secs\n"
> > +		" -d, --data             max data size\n"
> > +		" -f, --fsize            max file size\n"
> > +		" -l, --locks            max amount of file locks held\n"
> > +		" -m, --memlock          max locked-in-memory address space\n"
> > +		" -M, --msgqueue         max bytes in POSIX mqueues\n"
> > +		" -n, --nice             max nice priority allowed to raise\n"
> > +		" -N, --nofile           max amount of open files\n"
> > +		" -P, --nproc            max number of processes\n"
> > +		" -r, --rss              max resident set size\n"
> > +		" -R, --rtprio           max real-time priority\n"
> > +		" -t, --rttime           timeout for real-time teasks\n"
> > +		" -s, --sigpending       max amount of pending signals\n"
> > +		" -S, --stack            max stack size\n"), out);
> > +
> > +	fputs(_("\nAvailable columns (for --output):\n"), out);
> > +
> > +	for (i = 0; i < __NCOLUMNS; i++)
> > +		fprintf(out, " %11s  %s\n", infos[i].name, _(infos[i].help));
> > +
> > +
> > +	fprintf(out, _("\nFor more information see prlimit(1).\n\n"));
> 
>   fprintf(out, USAGE_MAN_TAIL("prlimit(1)");
> 
>  [...]
> 
> > +/*
> > + * Obtain the soft and hard limits, from arguments in the form <soft>:<hard>
> > + */
> > +static int parse_prlim(pid_t pid, struct rlimit *lim, int res, const char *ops, const char *errmsg)
> > +{
> > +	int soft, hard;
> > +
> > +	if (parse_range(ops, &soft, &hard, -3))
> 
> please, don't use magic constants, use:
> 
>     if (parse_range(ops, &soft, &hard, PRLIMIT_UNKNOWN))
> 
> or so...
> 
> > +		errx(EXIT_FAILURE, "%s", errmsg);
> 
> It would be nice to support RLIM_INFINITY, something like:
> 
>    --core=unlimited or --core=10000:unlimited
> 
> btw it would be better to use RLIM_INFINITY macro rather than -1 in the code.
> 
> > +static void do_prlimit(pid_t pid, struct prlimit lims[], const int n)
> > +{
> > +	int i;
> > +	struct rlimit *new;
> 
> int nshows = 0;
> 
> > +	for (i = 0; i < n; i++) {
> > +		new = lims[i].modify ? &lims[i].rlim : NULL;
> 
>         struct rlimit *new = NULL;
> 
>         if (lims[i].modify)
>             new = &lims[i].rlim;
>         else
>             nshows++;
> 
> > +		if (prlimit(pid, lims[i].resource, new, &lims[i].rlim) == -1)
> > +			err(EXIT_FAILURE, _("failed to get resource limits for PID %d"), pid);
> > +	}
> > +
> > +	show_limits(lims, 0, n);
> 
>  if (nshows)
>     show_limits(lims, 0, n);
> 
>  .. so don't waste time with show_limits() for commands like
> 
>     prlimit --pid $$ --core=10000
> 
>  where nothing is expected on stdout ;-)
> 
> > +}
> > +
> > +/*
> > + * Add a resource limit to the limits array
> > + */
> > +static int add_prlimit(pid_t pid, struct prlimit lims[], const char *ops,
> > +		       int n, int id, const char *errmsg)
> > +{
> > +	/* setup basic data */
> > +	lims[n].name = default_lims[id].name;
> > +	lims[n].help = default_lims[id].help;
> > +	lims[n].resource = default_lims[id].resource;
> 
>  Hmmm... this is poor design, the data structs are core of the well
>  designed programs.
>  
>  What about:
> 
>  struct prlimit_desc {
>     const char *name,
>                *help;
>     int        resource;
>  };
> 
>  struct prlimit {
>    struct prlimit_desc *desc;
> 
>    int           modify;
>    struct rlimit rlim;
>  };
> 
> I think it would be better to split resources description and the
> limits.
> 
> > +	if (ops) { /* planning on modifying a limit? */
> > +		lims[n].modify = 1;
> > +		parse_prlim(pid, &lims[n].rlim, lims[n].resource, ops, errmsg);
> > +	} else
> > +		lims[n].modify = 0;
> > +
> > +	return 0;
> > +}
> > +
> > +int main(int argc, char **argv)
> > +{
> > +	int opt, n = 0;
> > +	pid_t pid = 0; /* calling process (default) */
> > +	struct prlimit lims[MAX_RESOURCES];
> > +	static const struct option longopts[] = {
> > +		{"pid", required_argument, NULL, 'p'},
> > +		{"output", required_argument, NULL, 'o'},
> > +		{"as", optional_argument, NULL, 'a'},
> > +		{"core", optional_argument, NULL, 'c'},
> > +		{"cpu", optional_argument, NULL, 'C'},
> > +		{"data", optional_argument, NULL, 'd'},
> > +		{"fsize", optional_argument, NULL, 'f'},
> > +		{"locks", optional_argument, NULL, 'l'},
> > +		{"memlock", optional_argument, NULL, 'm'},
> > +		{"msgqueue", optional_argument, NULL, 'M'},
> > +		{"nice", optional_argument, NULL, 'n'},
> > +		{"nofile", optional_argument, NULL, 'N'},
> > +		{"nproc", optional_argument, NULL, 'P'},
> > +		{"rss", optional_argument, NULL, 'r'},
> > +		{"rtprio", optional_argument, NULL, 'R'},
> > +		{"rttime", optional_argument, NULL, 't'},
> > +		{"sigpending", optional_argument, NULL, 's'},
> > +		{"stack", optional_argument, NULL, 'S'},
> > +		{"version", no_argument, NULL, 'V'},
> > +		{"help", no_argument, NULL, 'h'},
> > +		{NULL, 0, NULL, 0}
> > +	};
> > +
> > +	setlocale(LC_ALL, "");
> > +	bindtextdomain(PACKAGE, LOCALEDIR);
> > +	textdomain(PACKAGE);
> > +
> > +	memset(lims, 0, sizeof(lims));
> > +
> > +	/*
> > +	 * Something is very wrong if this doesn't succeed,
> > +	 * assuming STACK is the last resource, of course.
> > +	 */
> > +	assert(MAX_RESOURCES == STACK + 1);
> > +
> > +	while((opt = getopt_long(argc, argv,
> > +				 "a::c::C::d::f::l::m::M::n::N::P::r::R::t::s::S::p:o:vVh",
> > +				 longopts, NULL)) != -1) {
> > +		switch(opt) {
> > +		case 'p':
> > +			pid = strtol_or_err(optarg, _("cannot parse PID"));
> > +			break;
> 
>  What will happen if:
> 
>     prlimit --pid 123 --code=10000 --pid 456 --cpu=1

I set a restriction in the getopt loop to just allow 1 use of --pid, I
don't want prlimit to be used for several pids at once.

>     
> > +		case 'a':
> > +			add_prlimit(pid, lims, optarg, n++, AS, "failed to parse AS limit");
> 
>  The error message is unnecessary here. You can compose the message in
>  the parse_prlim() function from resource ID. 
> 
>  It would be also better to use pointer to the prlimit struct in your
>  parse_* function that use two options 'struct rlimit *lim' and 'int res'.
> 
>  So:
> 
>        case 'a':
>            add_prlimit(pid, optarg, &lims[n++], AS);
> 
>  and in add_prlimit() you can initialize the pointer to desc:
> 
>     lims->desc = prlimit_desc[id];
> 
>  [...]
> 
> > +	n == 0 ? do_prlimit(pid, default_lims, MAX_RESOURCES) :
> > +		 do_prlimit(pid, lims, n);
> 
>  or if you replace default_lims with prlimit_desc:
> 
>   if (!n) {
>     for (n = 0; n < MAX_RESOURCES; n++)
>         add_prlimit(pid, NULL, &lims[n], n);
>   }
> 
>   do_prlimit(pid, lims, n);
> 
>  ;-)
> 
>     Karel
> 


Below is a v2 version with the requested corrections, thanks for
reviewing.

- Davidlohr

>From 87b6c242fdf12a261aa86ccc7eb022bd2780290c Mon Sep 17 00:00:00 2001
From: Davidlohr Bueso <dave@gnu.org>
Date: Wed, 19 Oct 2011 15:58:42 -0400
Subject: [PATCH 1/2] prlimit: new command

This program uses the prlimit() system call to get and/or set resource limits for a given process.

Signed-off-by: Davidlohr Bueso <dave@gnu.org>
---
 sys-utils/prlimit.c |  537 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 537 insertions(+), 0 deletions(-)
 create mode 100644 sys-utils/prlimit.c

diff --git a/sys-utils/prlimit.c b/sys-utils/prlimit.c
new file mode 100644
index 0000000..4d4fd91
--- /dev/null
+++ b/sys-utils/prlimit.c
@@ -0,0 +1,537 @@
+/*
+ *  prlimit - get/set process resource limits.
+ *
+ *  Copyright (C) 2011 Davidlohr Bueso <dave@gnu.org>
+ *
+ *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/resource.h>
+
+#include "c.h"
+#include "nls.h"
+#include "tt.h"
+#include "xalloc.h"
+#include "strutils.h"
+
+enum {
+	AS,
+	CORE,
+	CPU,
+	DATA,
+	FSIZE,
+	LOCKS,
+	MEMLOCK,
+	MSGQUEUE,
+	NICE,
+	NOFILE,
+	NPROC,
+	RSS,
+	RTPRIO,
+	RTTIME,
+	SIGPENDING,
+	STACK
+};
+
+struct prlimit_desc {
+	const char *name;
+	const char *help;
+	const int resource;
+};
+
+static struct prlimit_desc prlimit_desc[] =
+{
+	[AS]         = { "AS",         N_("address space limit"),                RLIMIT_AS },
+	[CORE]       = { "CORE",       N_("max core file size"),                 RLIMIT_CORE },
+	[CPU]        = { "CPU",        N_("CPU time in secs"),                   RLIMIT_CPU },
+	[DATA]       = { "DATA",       N_("max data size"),                      RLIMIT_DATA },
+	[FSIZE]      = { "FSIZE",      N_("max file size"),                      RLIMIT_FSIZE },
+	[LOCKS]      = { "LOCKS",      N_("max amount of file locks held"),      RLIMIT_LOCKS },
+	[MEMLOCK]    = { "MEMLOCK",    N_("max locked-in-memory address space"), RLIMIT_MEMLOCK },
+	[MSGQUEUE]   = { "MSGQUEUE",   N_("max bytes in POSIX mqueues"),         RLIMIT_MSGQUEUE },
+	[NICE]       = { "NICE",       N_("max nice prio allowed to raise"),     RLIMIT_NICE },
+	[NOFILE]     = { "NOFILE",     N_("max amount of open files"),           RLIMIT_NOFILE },
+	[NPROC]      = { "NPROC",      N_("max number of processes"),            RLIMIT_NPROC },
+	[RSS]        = { "RSS",        N_("max resident set size"),              RLIMIT_RSS },
+	[RTPRIO]     = { "RTPRIO",     N_("max real-time priority"),             RLIMIT_RTPRIO },
+	[RTTIME]     = { "RTTIME",     N_("timeout for real-time tasks"),        RLIMIT_RTTIME },
+	[SIGPENDING] = { "SIGPENDING", N_("max amount of pending signals"),      RLIMIT_SIGPENDING },
+	[STACK]      = { "STACK",      N_("max stack size"),                     RLIMIT_STACK }
+
+
+};
+
+struct prlimit {
+	struct rlimit rlim;
+	struct prlimit_desc *desc;
+	int modify;
+};
+
+enum {
+	COL_HELP,
+	COL_RES,
+	COL_SOFT,
+	COL_HARD,
+};
+
+/* column names */
+struct colinfo {
+	const char	*name;	/* header */
+	double		whint;	/* width hint (N < 1 is in percent of termwidth) */
+	int		flags;	/* TT_FL_* */
+	const char      *help;
+};
+
+/* columns descriptions */
+struct colinfo infos[] = {
+	[COL_RES]     = { "RESOURCE",    0.25, TT_FL_TRUNC, N_("resource name") },
+	[COL_HELP]    = { "DESCRIPTION", 0.1,  TT_FL_TRUNC, N_("resource description")},
+	[COL_SOFT]    = { "SOFT",        0.1,  TT_FL_RIGHT, N_("soft limit")},
+	[COL_HARD]    = { "HARD",        1,    TT_FL_RIGHT, N_("hard limit (ceiling)")},
+};
+
+#define NCOLS ARRAY_SIZE(infos)
+
+#define MAX_RESOURCES ARRAY_SIZE(prlimit_desc)
+
+/* when soft or hard limits isn't specified */
+#define PRLIMIT_UNKNOWN    -3
+
+/* array with IDs of enabled columns */
+static int columns[NCOLS], ncolumns = 0;
+static pid_t pid = 0; /* calling process (default) */
+
+static void __attribute__ ((__noreturn__)) usage(FILE * out)
+{
+	size_t i;
+
+	fputs(USAGE_HEADER, out);
+
+	fprintf(out,
+		_(" %s [options]\n"), program_invocation_short_name);
+
+	fputs(_("\nGeneral Options:\n"), out);
+	fputs(_(" -p, --pid <pid>        process id\n"
+		" -o, --output <type>    define which output columns to use\n"
+		" -h, --help             display this help and exit\n"
+		" -V, --version          output version information and exit\n"), out);
+
+	fputs(_("\nResources Options:\n"), out);
+	fputs(_(" -a, --as               address space limit\n"
+		" -c, --core             max core file size\n"
+		" -C, --cpu              CPU time in secs\n"
+		" -d, --data             max data size\n"
+		" -f, --fsize            max file size\n"
+		" -l, --locks            max amount of file locks held\n"
+		" -m, --memlock          max locked-in-memory address space\n"
+		" -M, --msgqueue         max bytes in POSIX mqueues\n"
+		" -n, --nice             max nice priority allowed to raise\n"
+		" -N, --nofile           max amount of open files\n"
+		" -P, --nproc            max number of processes\n"
+		" -r, --rss              max resident set size\n"
+		" -R, --rtprio           max real-time priority\n"
+		" -t, --rttime           timeout for real-time teasks\n"
+		" -s, --sigpending       max amount of pending signals\n"
+		" -S, --stack            max stack size\n"), out);
+
+	fputs(_("\nAvailable columns (for --output):\n"), out);
+
+	for (i = 0; i < NCOLS; i++)
+		fprintf(out, " %11s  %s\n", infos[i].name, _(infos[i].help));
+
+	fprintf(out, USAGE_MAN_TAIL("prlimit(1)"));
+
+	exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+static inline int get_column_id(int num)
+{
+	assert(ARRAY_SIZE(columns) == NCOLS);
+	assert(num < ncolumns);
+	assert(columns[num] < NCOLS);
+
+	return columns[num];
+}
+
+static inline struct colinfo *get_column_info(int num)
+{
+	return &infos[ get_column_id(num) ];
+}
+
+static void add_tt_line(struct tt *tt, struct prlimit l)
+{
+	int i;
+	struct tt_line *line;
+
+	assert(tt);
+	assert(&l);
+
+	line = tt_add_line(tt, NULL);
+	if (!line) {
+		warn(_("failed to add line to output"));
+		return;
+	}
+
+	for (i = 0; i < ncolumns; i++) {
+		char *str = NULL;
+		int rc = 0;
+
+		switch (get_column_id(i)) {
+		case COL_RES:
+			rc = asprintf(&str, "%s", l.desc->name);
+			break;
+		case COL_HELP:
+			rc = asprintf(&str, "%s", l.desc->help);
+			break;
+		case COL_SOFT:
+			rc = l.rlim.rlim_cur == -1 ? asprintf(&str, "%s", "unlimited") :
+						     asprintf(&str, "%lld", l.rlim.rlim_cur);
+			break;
+		case COL_HARD:
+			rc = l.rlim.rlim_max == -1 ? asprintf(&str, "%s", "unlimited") :
+						     asprintf(&str, "%lld", l.rlim.rlim_max);
+			break;
+		default:
+			break;
+		}
+
+		if (rc || str)
+			tt_line_set_data(line, i, str);
+	}
+}
+
+static int column_name_to_id(const char *name, size_t namesz)
+{
+	int i;
+
+	assert(name);
+
+	for (i = 0; i < NCOLS; i++) {
+		const char *cn = infos[i].name;
+
+		if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+			return i;
+	}
+	warnx(_("unknown column: %s"), name);
+	return -1;
+}
+
+static int show_limits(struct prlimit lims[], int tt_flags, int n)
+{
+	int i;
+	struct tt *tt;
+
+	tt = tt_new_table(tt_flags);
+	if (!tt) {
+		warn(_("failed to initialize output table"));
+		return -1;
+	}
+
+	for (i = 0; i < ncolumns; i++) {
+		struct colinfo *col = get_column_info(i);
+
+		if (!tt_define_column(tt, col->name, col->whint, col->flags)) {
+			warnx(_("failed to initialize output column"));
+			goto done;
+		}
+	}
+
+	for (i = 0; i < n; i++)
+		if (!lims[i].modify) /* only display old limits */
+			add_tt_line(tt, lims[i]);
+
+	tt_print_table(tt);
+done:
+	tt_free_table(tt);
+	return 0;
+}
+
+
+static void do_prlimit(struct prlimit lims[], const int n)
+{
+	int i, nshows = 0;
+
+	for (i = 0; i < n; i++) {
+		struct rlimit *new = NULL;
+
+		if (lims[i].modify)
+			new = &lims[i].rlim;
+		else
+			nshows++;
+
+		if (prlimit(pid, lims[i].desc->resource, new, &lims[i].rlim) == -1)
+			err(EXIT_FAILURE, _("failed to get resource limits for PID %d"), pid);
+	}
+
+	if (nshows)
+		show_limits(lims, 0, n);
+}
+
+static int get_range(char *str, rlim_t *lower, rlim_t *upper)
+{
+	char *end = NULL;
+	const char *ulimit = "unlimited";
+	const size_t len = strlen(ulimit);
+
+	if (!str)
+		return 0;
+
+	*upper = *lower = PRLIMIT_UNKNOWN;
+	errno = 0;
+
+	if (!strcmp(str, ulimit)) {
+		*upper = *lower = RLIM_INFINITY;
+		goto out;
+	}
+
+	else if (*str == ':') {				/* <:N> */
+		str++;
+
+		if (!strcmp(str, ulimit)) {
+			*upper =  RLIM_INFINITY;
+			goto out;
+		}
+		else {
+			*upper = strtol(str, &end, 10);
+
+			if (errno || !end || *end || end == str)
+				goto err;
+		}
+
+	} else {
+		if (!strncmp(str, ulimit, len)) {
+			*lower =  RLIM_INFINITY;
+			/* we can be sure this won't overflow */
+			end = str + len;
+		}
+		else {
+			*upper = *lower = strtol(str, &end, 10);
+			if (errno || !end || end == str)
+				goto err;
+		}
+
+		if (*end == ':' && !*(end + 1)) 	/* <M:> */
+			*upper = PRLIMIT_UNKNOWN;
+		
+		else if (*end == ':') {	/* <M:N> */
+			str = end + 1;
+			
+			if (!strcmp(str, ulimit))
+				*upper =  RLIM_INFINITY;
+			else {
+				end = NULL;
+				errno = 0;
+				*upper = strtol(str, &end, 10);
+
+				if (errno || !end || *end || end == str)
+					goto err;
+			}
+		}
+	}
+
+out:
+	return 0;
+err:
+	return -1;
+}
+
+
+static int parse_prlim(struct rlimit *lim, char *ops, const int id)
+{
+	rlim_t soft = 0, hard = 0;
+
+	if (get_range(ops, &soft, &hard))
+ 		errx(EXIT_FAILURE, _("failed to parse %s limit"), prlimit_desc[id].name);
+
+	/*
+	 * If one of the limits is unknown (default value for not being passed), we need
+	 * to get the current limit and use it.
+	 * I see no other way other than using prlimit(2).
+	 */
+	if (soft == PRLIMIT_UNKNOWN || hard == PRLIMIT_UNKNOWN) {
+		struct rlimit old;
+
+		if (prlimit(pid, prlimit_desc[id].resource, NULL, &old) == -1)
+			errx(EXIT_FAILURE, _("failed to get old %s limit"),
+			     prlimit_desc[id].name);
+
+		if (soft == PRLIMIT_UNKNOWN)
+			soft = old.rlim_cur;
+		else if (hard == PRLIMIT_UNKNOWN)
+			hard = old.rlim_max;
+	}
+
+	if (soft > hard && (soft != RLIM_INFINITY || hard != RLIM_INFINITY))
+		errx(EXIT_FAILURE, _("the soft limit cannot exceed the ceiling value"));
+
+	lim->rlim_cur = soft;
+	lim->rlim_max = hard;
+
+	return 0;
+}
+
+/*
+ * Add a resource limit to the limits array
+ */
+static int add_prlim(char *ops, struct prlimit *lim, const int id)
+{
+	lim->desc = &prlimit_desc[id];
+
+	if (ops) { /* planning on modifying a limit? */
+		lim->modify = 1;
+		parse_prlim(&lim->rlim, ops, id);
+	}
+
+	return 0;
+}
+
+int main(int argc, char **argv)
+{
+	int opt, n = 0;
+	struct prlimit lims[MAX_RESOURCES] = {0};
+
+	static const struct option longopts[] = {
+		{"pid", required_argument, NULL, 'p'},
+		{"output", required_argument, NULL, 'o'},
+		{"as", optional_argument, NULL, 'a'},
+		{"core", optional_argument, NULL, 'c'},
+		{"cpu", optional_argument, NULL, 'C'},
+		{"data", optional_argument, NULL, 'd'},
+		{"fsize", optional_argument, NULL, 'f'},
+		{"locks", optional_argument, NULL, 'l'},
+		{"memlock", optional_argument, NULL, 'm'},
+		{"msgqueue", optional_argument, NULL, 'M'},
+		{"nice", optional_argument, NULL, 'n'},
+		{"nofile", optional_argument, NULL, 'N'},
+		{"nproc", optional_argument, NULL, 'P'},
+		{"rss", optional_argument, NULL, 'r'},
+		{"rtprio", optional_argument, NULL, 'R'},
+		{"rttime", optional_argument, NULL, 't'},
+		{"sigpending", optional_argument, NULL, 's'},
+		{"stack", optional_argument, NULL, 'S'},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 'h'},
+		{NULL, 0, NULL, 0}
+	};
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	textdomain(PACKAGE);
+
+	/*
+	 * Something is very wrong if this doesn't succeed,
+	 * assuming STACK is the last resource, of course.
+	 */
+	assert(MAX_RESOURCES == STACK + 1);
+
+	while((opt = getopt_long(argc, argv,
+				 "a::c::C::d::f::l::m::M::n::N::P::r::R::t::s::S::p:o:vVh",
+				 longopts, NULL)) != -1) {
+		switch(opt) {
+		case 'p':
+			if (pid) /* we only work one pid at a time */
+				errx(EXIT_FAILURE, _("only use one PID at a time"));
+			
+			pid = strtol_or_err(optarg, _("cannot parse PID"));
+			break;
+		case 'a':
+			add_prlim(optarg, &lims[n++], AS);
+			break;
+		case 'c':
+			add_prlim(optarg, &lims[n++], CORE);
+			break;
+		case 'C':
+			add_prlim(optarg, &lims[n++], CPU);
+			break;
+		case 'd':
+			add_prlim(optarg, &lims[n++], DATA);
+			break;
+		case 'f':
+			add_prlim(optarg, &lims[n++], FSIZE);
+			break;
+		case 'l':
+			add_prlim(optarg, &lims[n++], LOCKS);
+			break;
+		case 'm':
+			add_prlim(optarg, &lims[n++], MEMLOCK);
+			break;
+		case 'M':
+			add_prlim(optarg, &lims[n++], MSGQUEUE);
+			break;
+		case 'n':
+			add_prlim(optarg, &lims[n++], NICE);
+			break;
+		case 'N':
+			add_prlim(optarg, &lims[n++], NOFILE);
+			break;
+		case 'P':
+			add_prlim(optarg, &lims[n++], NPROC);
+			break;
+		case 'r':
+			add_prlim(optarg, &lims[n++], RSS);
+			break;
+		case 't':
+			add_prlim(optarg, &lims[n++], NOFILE);
+			break;
+		case 's':
+			add_prlim(optarg, &lims[n++], SIGPENDING);
+			break;
+		case 'S':
+			add_prlim(optarg, &lims[n++], STACK);
+			break;
+		case 'h':
+			usage(stdout);
+			break;
+		case 'o':
+			ncolumns = string_to_idarray(optarg,
+						     columns, ARRAY_SIZE(columns),
+						     column_name_to_id);
+			if (ncolumns < 0)
+				return EXIT_FAILURE;
+			break;
+		case 'V':
+			printf(UTIL_LINUX_VERSION);
+			return EXIT_SUCCESS;
+		default:
+			usage(stderr);
+			break;
+		}
+	}
+
+	if (argc == 1)
+		usage(stderr);
+
+	if (!ncolumns) {
+		columns[ncolumns++] = COL_RES;
+		columns[ncolumns++] = COL_HELP;
+		columns[ncolumns++] = COL_SOFT;
+		columns[ncolumns++] = COL_HARD;
+	}
+
+	if (!n)
+		for (; n < MAX_RESOURCES; n++)
+			add_prlim(NULL, &lims[n], n);
+
+	do_prlimit(lims, n);
+
+	return EXIT_SUCCESS;
+}
-- 
1.7.7

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH 2/3] prlimit: new program
  2011-10-19 20:02   ` Davidlohr Bueso
@ 2011-10-21 21:29     ` Karel Zak
  0 siblings, 0 replies; 4+ messages in thread
From: Karel Zak @ 2011-10-21 21:29 UTC (permalink / raw)
  To: Davidlohr Bueso; +Cc: util-linux

On Wed, Oct 19, 2011 at 04:02:35PM -0400, Davidlohr Bueso wrote:
> Below is a v2 version with the requested corrections, thanks for
> reviewing.

 Applied with some changes:

    - sync short options with ulimits(1)
    - remove PRLIMIT_UNKNOWN (limits are unsigned)

 BTW, what about to add another column "UNIT" (bytes, seconds, ms,
 ...)?
   
 Thanks!
    Karel

-- 
 Karel Zak  <kzak@redhat.com>
 http://karelzak.blogspot.com

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2011-10-21 21:29 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-10-17 23:23 [PATCH 2/3] prlimit: new program Davidlohr Bueso
2011-10-17 15:08 ` Karel Zak
2011-10-19 20:02   ` Davidlohr Bueso
2011-10-21 21:29     ` Karel Zak

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).