1 /*
2  *  prlimit - get/set process resource limits.
3  *
4  *  Copyright (C) 2011 Davidlohr Bueso <dave@gnu.org>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License along
17  *  with this program; if not, write to the Free Software Foundation, Inc.,
18  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20 
21 #include <errno.h>
22 #include <getopt.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <ctype.h>
26 #include <assert.h>
27 #include <unistd.h>
28 #include <sys/resource.h>
29 
30 #include <libsmartcols.h>
31 
32 #include "c.h"
33 #include "nls.h"
34 #include "xalloc.h"
35 #include "strutils.h"
36 #include "list.h"
37 #include "closestream.h"
38 
39 #ifndef RLIMIT_RTTIME
40 # define RLIMIT_RTTIME 15
41 #endif
42 
43 enum {
44 	AS,
45 	CORE,
46 	CPU,
47 	DATA,
48 	FSIZE,
49 	LOCKS,
50 	MEMLOCK,
51 	MSGQUEUE,
52 	NICE,
53 	NOFILE,
54 	NPROC,
55 	RSS,
56 	RTPRIO,
57 	RTTIME,
58 	SIGPENDING,
59 	STACK
60 };
61 
62 /* basic output flags */
63 static int no_headings;
64 static int raw;
65 
66 struct prlimit_desc {
67 	const char *name;
68 	const char *help;
69 	const char *unit;
70 	int resource;
71 };
72 
73 static struct prlimit_desc prlimit_desc[] =
74 {
75 	[AS]         = { "AS",         N_("address space limit"),                N_("bytes"),     RLIMIT_AS },
76 	[CORE]       = { "CORE",       N_("max core file size"),                 N_("blocks"),    RLIMIT_CORE },
77 	[CPU]        = { "CPU",        N_("CPU time"),                           N_("seconds"),   RLIMIT_CPU },
78 	[DATA]       = { "DATA",       N_("max data size"),                      N_("bytes"),     RLIMIT_DATA },
79 	[FSIZE]      = { "FSIZE",      N_("max file size"),                      N_("blocks"),    RLIMIT_FSIZE },
80 	[LOCKS]      = { "LOCKS",      N_("max number of file locks held"),      NULL,            RLIMIT_LOCKS },
81 	[MEMLOCK]    = { "MEMLOCK",    N_("max locked-in-memory address space"), N_("bytes"),     RLIMIT_MEMLOCK },
82 	[MSGQUEUE]   = { "MSGQUEUE",   N_("max bytes in POSIX mqueues"),         N_("bytes"),     RLIMIT_MSGQUEUE },
83 	[NICE]       = { "NICE",       N_("max nice prio allowed to raise"),     NULL,            RLIMIT_NICE },
84 	[NOFILE]     = { "NOFILE",     N_("max number of open files"),           NULL,            RLIMIT_NOFILE },
85 	[NPROC]      = { "NPROC",      N_("max number of processes"),            NULL,            RLIMIT_NPROC },
86 	[RSS]        = { "RSS",        N_("max resident set size"),              N_("pages"),     RLIMIT_RSS },
87 	[RTPRIO]     = { "RTPRIO",     N_("max real-time priority"),             NULL,            RLIMIT_RTPRIO },
88 	[RTTIME]     = { "RTTIME",     N_("timeout for real-time tasks"),        N_("microsecs"), RLIMIT_RTTIME },
89 	[SIGPENDING] = { "SIGPENDING", N_("max number of pending signals"),      NULL,            RLIMIT_SIGPENDING },
90 	[STACK]      = { "STACK",      N_("max stack size"),                     N_("bytes"),     RLIMIT_STACK }
91 };
92 
93 #define MAX_RESOURCES ARRAY_SIZE(prlimit_desc)
94 
95 struct prlimit {
96 	struct list_head lims;
97 
98 	struct rlimit rlim;
99 	struct prlimit_desc *desc;
100 	int modify;			/* PRLIMIT_{SOFT,HARD} mask */
101 };
102 
103 #define PRLIMIT_EMPTY_LIMIT	{{ 0, 0, }, NULL, 0 }
104 
105 enum {
106 	COL_HELP,
107 	COL_RES,
108 	COL_SOFT,
109 	COL_HARD,
110 	COL_UNITS,
111 };
112 
113 /* column names */
114 struct colinfo {
115 	const char	*name;	/* header */
116 	double		whint;	/* width hint (N < 1 is in percent of termwidth) */
117 	int		flags;	/* SCOLS_FL_* */
118 	const char      *help;
119 };
120 
121 /* columns descriptions */
122 struct colinfo infos[] = {
123 	[COL_RES]     = { "RESOURCE",    0.25, SCOLS_FL_TRUNC, N_("resource name") },
124 	[COL_HELP]    = { "DESCRIPTION", 0.1,  SCOLS_FL_TRUNC, N_("resource description")},
125 	[COL_SOFT]    = { "SOFT",        0.1,  SCOLS_FL_RIGHT, N_("soft limit")},
126 	[COL_HARD]    = { "HARD",        1,    SCOLS_FL_RIGHT, N_("hard limit (ceiling)")},
127 	[COL_UNITS]   = { "UNITS",       0.1,  SCOLS_FL_TRUNC, N_("units")},
128 };
129 
130 static int columns[ARRAY_SIZE(infos) * 2];
131 static int ncolumns;
132 
133 
134 
135 #define INFINITY_STR	"unlimited"
136 #define INFINITY_STRLEN	(sizeof(INFINITY_STR) - 1)
137 
138 #define PRLIMIT_SOFT	(1 << 1)
139 #define PRLIMIT_HARD	(1 << 2)
140 
141 static pid_t pid; /* calling process (default) */
142 static int verbose;
143 
144 #ifndef HAVE_PRLIMIT
145 # include <sys/syscall.h>
prlimit(pid_t p,int resource,const struct rlimit * new_limit,struct rlimit * old_limit)146 static int prlimit(pid_t p, int resource,
147 		   const struct rlimit *new_limit,
148 		   struct rlimit *old_limit)
149 {
150 	return syscall(SYS_prlimit64, p, resource, new_limit, old_limit);
151 }
152 #endif
153 
usage(FILE * out)154 static void __attribute__ ((__noreturn__)) usage(FILE * out)
155 {
156 	size_t i;
157 
158 	fputs(USAGE_HEADER, out);
159 
160 	fprintf(out,
161 		_(" %s [options] [-p PID]\n"), program_invocation_short_name);
162 	fprintf(out,
163 		_(" %s [options] COMMAND\n"), program_invocation_short_name);
164 
165 	fputs(_("\nGeneral Options:\n"), out);
166 	fputs(_(" -p, --pid <pid>        process id\n"
167 		" -o, --output <list>    define which output columns to use\n"
168 		"     --noheadings       don't print headings\n"
169 		"     --raw              use the raw output format\n"
170 		"     --verbose          verbose output\n"
171 		" -h, --help             display this help and exit\n"
172 		" -V, --version          output version information and exit\n"), out);
173 
174 	fputs(_("\nResources Options:\n"), out);
175 	fputs(_(" -c, --core             maximum size of core files created\n"
176 		" -d, --data             maximum size of a process's data segment\n"
177 		" -e, --nice             maximum nice priority allowed to raise\n"
178 		" -f, --fsize            maximum size of files written by the process\n"
179 		" -i, --sigpending       maximum number of pending signals\n"
180 		" -l, --memlock          maximum size a process may lock into memory\n"
181 		" -m, --rss              maximum resident set size\n"
182 		" -n, --nofile           maximum number of open files\n"
183 		" -q, --msgqueue         maximum bytes in POSIX message queues\n"
184 		" -r, --rtprio           maximum real-time scheduling priority\n"
185 		" -s, --stack            maximum stack size\n"
186 		" -t, --cpu              maximum amount of CPU time in seconds\n"
187 		" -u, --nproc            maximum number of user processes\n"
188 		" -v, --as               size of virtual memory\n"
189 		" -x, --locks            maximum number of file locks\n"
190 		" -y, --rttime           CPU time in microseconds a process scheduled\n"
191 		"                        under real-time scheduling\n"), out);
192 
193 	fputs(_("\nAvailable columns (for --output):\n"), out);
194 
195 	for (i = 0; i < ARRAY_SIZE(infos); i++)
196 		fprintf(out, " %11s  %s\n", infos[i].name, _(infos[i].help));
197 
198 	fprintf(out, USAGE_MAN_TAIL("prlimit(1)"));
199 
200 	exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
201 }
202 
get_column_id(int num)203 static inline int get_column_id(int num)
204 {
205 	assert(num < ncolumns);
206 	assert(columns[num] < (int) ARRAY_SIZE(infos));
207 
208 	return columns[num];
209 }
210 
get_column_info(unsigned num)211 static inline struct colinfo *get_column_info(unsigned num)
212 {
213 	return &infos[ get_column_id(num) ];
214 }
215 
add_scols_line(struct libscols_table * table,struct prlimit * l)216 static void add_scols_line(struct libscols_table *table, struct prlimit *l)
217 {
218 	int i;
219 	struct libscols_line *line;
220 
221 	assert(table);
222 	assert(l);
223 
224 	line = scols_table_new_line(table, NULL);
225 	if (!line)
226 		err(EXIT_FAILURE, _("failed to initialize output line"));
227 
228 	for (i = 0; i < ncolumns; i++) {
229 		char *str = NULL;
230 
231 		switch (get_column_id(i)) {
232 		case COL_RES:
233 			str = xstrdup(l->desc->name);
234 			break;
235 		case COL_HELP:
236 			str = xstrdup(l->desc->help);
237 			break;
238 		case COL_SOFT:
239 			if (l->rlim.rlim_cur == RLIM_INFINITY)
240 				str = xstrdup(_("unlimited"));
241 			else
242 				xasprintf(&str, "%llu", (unsigned long long) l->rlim.rlim_cur);
243 			break;
244 		case COL_HARD:
245 			if (l->rlim.rlim_max == RLIM_INFINITY)
246 				str = xstrdup(_("unlimited"));
247 			else
248 				xasprintf(&str, "%llu", (unsigned long long) l->rlim.rlim_max);
249 			break;
250 		case COL_UNITS:
251 			str = l->desc->unit ? xstrdup(_(l->desc->unit)) : NULL;
252 			break;
253 		default:
254 			break;
255 		}
256 
257 		if (str)
258 			scols_line_refer_data(line, i, str);
259 	}
260 }
261 
column_name_to_id(const char * name,size_t namesz)262 static int column_name_to_id(const char *name, size_t namesz)
263 {
264 	size_t i;
265 
266 	assert(name);
267 
268 	for (i = 0; i < ARRAY_SIZE(infos); i++) {
269 		const char *cn = infos[i].name;
270 
271 		if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
272 			return i;
273 	}
274 	warnx(_("unknown column: %s"), name);
275 	return -1;
276 }
277 
rem_prlim(struct prlimit * lim)278 static void rem_prlim(struct prlimit *lim)
279 {
280 	if (!lim)
281 		return;
282 	list_del(&lim->lims);
283 	free(lim);
284 }
285 
show_limits(struct list_head * lims)286 static int show_limits(struct list_head *lims)
287 {
288 	int i;
289 	struct list_head *p, *pnext;
290 	struct libscols_table *table;
291 
292 	table = scols_new_table();
293 	if (!table)
294 		err(EXIT_FAILURE, _("failed to initialize output table"));
295 
296 	scols_table_enable_raw(table, raw);
297 	scols_table_enable_noheadings(table, no_headings);
298 
299 	for (i = 0; i < ncolumns; i++) {
300 		struct colinfo *col = get_column_info(i);
301 
302 		if (!scols_table_new_column(table, col->name, col->whint, col->flags))
303 			err(EXIT_FAILURE, _("failed to initialize output column"));
304 	}
305 
306 
307 	list_for_each_safe(p, pnext, lims) {
308 		struct prlimit *lim = list_entry(p, struct prlimit, lims);
309 
310 		add_scols_line(table, lim);
311 		rem_prlim(lim);
312 	}
313 
314 	scols_print_table(table);
315 	scols_unref_table(table);
316 	return 0;
317 }
318 
319 /*
320  * If one of the limits is unknown (default value for not being passed), we
321  * need to get the current limit and use it.  I see no other way other than
322  * using prlimit(2).
323  */
get_unknown_hardsoft(struct prlimit * lim)324 static void get_unknown_hardsoft(struct prlimit *lim)
325 {
326 	struct rlimit old;
327 
328 	if (prlimit(pid, lim->desc->resource, NULL, &old) == -1)
329 		err(EXIT_FAILURE, _("failed to get old %s limit"),
330 				lim->desc->name);
331 
332 	if (!(lim->modify & PRLIMIT_SOFT))
333 		lim->rlim.rlim_cur = old.rlim_cur;
334 	else if (!(lim->modify & PRLIMIT_HARD))
335 		lim->rlim.rlim_max = old.rlim_max;
336 }
337 
do_prlimit(struct list_head * lims)338 static void do_prlimit(struct list_head *lims)
339 {
340 	struct list_head *p, *pnext;
341 
342 	list_for_each_safe(p, pnext, lims) {
343 		struct rlimit *new = NULL, *old = NULL;
344 		struct prlimit *lim = list_entry(p, struct prlimit, lims);
345 
346 		if (lim->modify) {
347 			if (lim->modify != (PRLIMIT_HARD | PRLIMIT_SOFT))
348 				get_unknown_hardsoft(lim);
349 
350 			if ((lim->rlim.rlim_cur > lim->rlim.rlim_max) &&
351 				(lim->rlim.rlim_cur != RLIM_INFINITY ||
352 				 lim->rlim.rlim_max != RLIM_INFINITY))
353 				errx(EXIT_FAILURE, _("the soft limit %s cannot exceed the hard limit"),
354 						lim->desc->name);
355 			new = &lim->rlim;
356 		} else
357 			old = &lim->rlim;
358 
359 		if (verbose && new) {
360 			printf(_("New %s limit: "), lim->desc->name);
361 			if (new->rlim_cur == RLIM_INFINITY)
362 				printf("<%s", _("unlimited"));
363 			else
364 				printf("<%ju", new->rlim_cur);
365 
366 			if (new->rlim_max == RLIM_INFINITY)
367 				printf(":%s>\n", _("unlimited"));
368 			else
369 				printf(":%ju>\n", new->rlim_max);
370 		}
371 
372 		if (prlimit(pid, lim->desc->resource, new, old) == -1)
373 			err(EXIT_FAILURE, lim->modify ?
374 				_("failed to set the %s resource limit") :
375 				_("failed to get the %s resource limit"),
376 				lim->desc->name);
377 
378 		if (lim->modify)
379 			rem_prlim(lim);		/* modify only; don't show */
380 	}
381 }
382 
get_range(char * str,rlim_t * soft,rlim_t * hard,int * found)383 static int get_range(char *str, rlim_t *soft, rlim_t *hard, int *found)
384 {
385 	char *end = NULL;
386 
387 	if (!str)
388 		return 0;
389 
390 	*found = errno = 0;
391 	*soft = *hard = RLIM_INFINITY;
392 
393 	if (!strcmp(str, INFINITY_STR)) {		/* <unlimited> */
394 		*found |= PRLIMIT_SOFT | PRLIMIT_HARD;
395 		return 0;
396 
397 	} else if (*str == ':') {			/* <:hard> */
398 		str++;
399 
400 		if (strcmp(str, INFINITY_STR) != 0) {
401 			*hard = strtoull(str, &end, 10);
402 
403 			if (errno || !end || *end || end == str)
404 				return -1;
405 		}
406 		*found |= PRLIMIT_HARD;
407 		return 0;
408 
409 	}
410 
411 	if (strncmp(str, INFINITY_STR, INFINITY_STRLEN) == 0) {
412 		/* <unlimited> or <unlimited:> */
413 		end = str + INFINITY_STRLEN;
414 	} else {
415 		/* <value> or <soft:> */
416 		*hard = *soft = strtoull(str, &end, 10);
417 		if (errno || !end || end == str)
418 			return -1;
419 	}
420 
421 	if (*end == ':' && !*(end + 1))			/* <soft:> */
422 		*found |= PRLIMIT_SOFT;
423 
424 	else if (*end == ':') {				/* <soft:hard> */
425 		str = end + 1;
426 
427 		if (!strcmp(str, INFINITY_STR))
428 			*hard =  RLIM_INFINITY;
429 		else {
430 			end = NULL;
431 			errno = 0;
432 			*hard = strtoull(str, &end, 10);
433 
434 			if (errno || !end || *end || end == str)
435 				return -1;
436 		}
437 		*found |= PRLIMIT_SOFT | PRLIMIT_HARD;
438 
439 	} else						/* <value> */
440 		*found |= PRLIMIT_SOFT | PRLIMIT_HARD;
441 
442 	return 0;
443 }
444 
445 
parse_prlim(struct rlimit * lim,char * ops,size_t id)446 static int parse_prlim(struct rlimit *lim, char *ops, size_t id)
447 {
448 	rlim_t soft, hard;
449 	int found = 0;
450 
451 	if (get_range(ops, &soft, &hard, &found))
452 		errx(EXIT_FAILURE, _("failed to parse %s limit"),
453 		     prlimit_desc[id].name);
454 
455 	lim->rlim_cur = soft;
456 	lim->rlim_max = hard;
457 
458 	return found;
459 }
460 
add_prlim(char * ops,struct list_head * lims,size_t id)461 static int add_prlim(char *ops, struct list_head *lims, size_t id)
462 {
463 	struct prlimit *lim = xcalloc(1, sizeof(*lim));
464 
465 	INIT_LIST_HEAD(&lim->lims);
466 	lim->desc = &prlimit_desc[id];
467 
468 	if (ops)
469 		lim->modify = parse_prlim(&lim->rlim, ops, id);
470 
471 	list_add_tail(&lim->lims, lims);
472 	return 0;
473 }
474 
main(int argc,char ** argv)475 int main(int argc, char **argv)
476 {
477 	int opt;
478 	struct list_head lims;
479 
480 	enum {
481 		VERBOSE_OPTION = CHAR_MAX + 1,
482 		RAW_OPTION,
483 		NOHEADINGS_OPTION
484 	};
485 
486 	static const struct option longopts[] = {
487 		{ "pid",	required_argument, NULL, 'p' },
488 		{ "output",     required_argument, NULL, 'o' },
489 		{ "as",         optional_argument, NULL, 'v' },
490 		{ "core",       optional_argument, NULL, 'c' },
491 		{ "cpu",        optional_argument, NULL, 't' },
492 		{ "data",       optional_argument, NULL, 'd' },
493 		{ "fsize",      optional_argument, NULL, 'f' },
494 		{ "locks",      optional_argument, NULL, 'x' },
495 		{ "memlock",    optional_argument, NULL, 'l' },
496 		{ "msgqueue",   optional_argument, NULL, 'q' },
497 		{ "nice",       optional_argument, NULL, 'e' },
498 		{ "nofile",     optional_argument, NULL, 'n' },
499 		{ "nproc",      optional_argument, NULL, 'u' },
500 		{ "rss",        optional_argument, NULL, 'm' },
501 		{ "rtprio",     optional_argument, NULL, 'r' },
502 		{ "rttime",     optional_argument, NULL, 'y' },
503 		{ "sigpending", optional_argument, NULL, 'i' },
504 		{ "stack",      optional_argument, NULL, 's' },
505 		{ "version",    no_argument, NULL, 'V' },
506 		{ "help",       no_argument, NULL, 'h' },
507 		{ "noheadings", no_argument, NULL, NOHEADINGS_OPTION },
508 		{ "raw",        no_argument, NULL, RAW_OPTION },
509 		{ "verbose",    no_argument, NULL, VERBOSE_OPTION },
510 		{ NULL, 0, NULL, 0 }
511 	};
512 
513 	setlocale(LC_ALL, "");
514 	bindtextdomain(PACKAGE, LOCALEDIR);
515 	textdomain(PACKAGE);
516 	atexit(close_stdout);
517 
518 	INIT_LIST_HEAD(&lims);
519 
520 	/*
521 	 * Something is very wrong if this doesn't succeed,
522 	 * assuming STACK is the last resource, of course.
523 	 */
524 	assert(MAX_RESOURCES == STACK + 1);
525 
526 	while((opt = getopt_long(argc, argv,
527 				 "+c::d::e::f::i::l::m::n::q::r::s::t::u::v::x::y::p:o:vVh",
528 				 longopts, NULL)) != -1) {
529 		switch(opt) {
530 		case 'c':
531 			add_prlim(optarg, &lims, CORE);
532 			break;
533 		case 'd':
534 			add_prlim(optarg, &lims, DATA);
535 			break;
536 		case 'e':
537 			add_prlim(optarg, &lims, NICE);
538 			break;
539 		case 'f':
540 			add_prlim(optarg, &lims, FSIZE);
541 			break;
542 		case 'i':
543 			add_prlim(optarg, &lims, SIGPENDING);
544 			break;
545 		case 'l':
546 			add_prlim(optarg, &lims, MEMLOCK);
547 			break;
548 		case 'm':
549 			add_prlim(optarg, &lims, RSS);
550 			break;
551 		case 'n':
552 			add_prlim(optarg, &lims, NOFILE);
553 			break;
554 		case 'q':
555 			add_prlim(optarg, &lims, MSGQUEUE);
556 			break;
557 		case 'r':
558 			add_prlim(optarg, &lims, RTPRIO);
559 			break;
560 		case 's':
561 			add_prlim(optarg, &lims, STACK);
562 			break;
563 		case 't':
564 			add_prlim(optarg, &lims, CPU);
565 			break;
566 		case 'u':
567 			add_prlim(optarg, &lims, NPROC);
568 			break;
569 		case 'v':
570 			add_prlim(optarg, &lims, AS);
571 			break;
572 		case 'x':
573 			add_prlim(optarg, &lims, LOCKS);
574 			break;
575 		case 'y':
576 			add_prlim(optarg, &lims, RTTIME);
577 			break;
578 
579 		case 'p':
580 			if (pid)
581 				errx(EXIT_FAILURE, _("option --pid may be specified only once"));
582 			pid = strtos32_or_err(optarg, _("invalid PID argument"));
583 			break;
584 		case 'h':
585 			usage(stdout);
586 		case 'o':
587 			ncolumns = string_to_idarray(optarg,
588 						     columns, ARRAY_SIZE(columns),
589 						     column_name_to_id);
590 			if (ncolumns < 0)
591 				return EXIT_FAILURE;
592 			break;
593 		case 'V':
594 			printf(UTIL_LINUX_VERSION);
595 			return EXIT_SUCCESS;
596 
597 		case NOHEADINGS_OPTION:
598 			no_headings = 1;
599 			break;
600 		case VERBOSE_OPTION:
601 			verbose++;
602 			break;
603 		case RAW_OPTION:
604 			raw = 1;
605 			break;
606 
607 		default:
608 			usage(stderr);
609 		}
610 	}
611 	if (argc > optind && pid)
612 		errx(EXIT_FAILURE, _("options --pid and COMMAND are mutually exclusive"));
613 	if (!ncolumns) {
614 		/* default columns */
615 		columns[ncolumns++] = COL_RES;
616 		columns[ncolumns++] = COL_HELP;
617 		columns[ncolumns++] = COL_SOFT;
618 		columns[ncolumns++] = COL_HARD;
619 		columns[ncolumns++] = COL_UNITS;
620 	}
621 
622 	scols_init_debug(0);
623 
624 	if (list_empty(&lims)) {
625 		/* default is to print all resources */
626 		size_t n;
627 
628 		for (n = 0; n < MAX_RESOURCES; n++)
629 			add_prlim(NULL, &lims, n);
630 	}
631 
632 	do_prlimit(&lims);
633 
634 	if (!list_empty(&lims))
635 		show_limits(&lims);
636 
637 	if (argc > optind) {
638 		/* prlimit [options] COMMAND */
639 		execvp(argv[optind], &argv[optind]);
640 		err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]);
641 	}
642 
643 	return EXIT_SUCCESS;
644 }
645