diff options
-rw-r--r-- | sys-utils/.gitignore | 1 | ||||
-rw-r--r-- | sys-utils/Makefile.am | 7 | ||||
-rw-r--r-- | sys-utils/prlimit.1 | 108 | ||||
-rw-r--r-- | sys-utils/prlimit.c | 571 |
4 files changed, 685 insertions, 2 deletions
diff --git a/sys-utils/.gitignore b/sys-utils/.gitignore index febeb3805..01a3df986 100644 --- a/sys-utils/.gitignore +++ b/sys-utils/.gitignore @@ -26,6 +26,7 @@ pivot_root ppc32.8 ppc64.8 ppc.8 +prlimit readprofile renice rtcwake diff --git a/sys-utils/Makefile.am b/sys-utils/Makefile.am index 7da9ff0cf..48595dab5 100644 --- a/sys-utils/Makefile.am +++ b/sys-utils/Makefile.am @@ -2,11 +2,11 @@ include $(top_srcdir)/config/include-Makefile.am bin_PROGRAMS = sbin_PROGRAMS = -usrbin_exec_PROGRAMS = flock ipcrm ipcs ipcmk renice setsid +usrbin_exec_PROGRAMS = flock ipcrm ipcs ipcmk renice setsid prlimit usrsbin_exec_PROGRAMS = readprofile dist_man_MANS = flock.1 ipcrm.1 ipcs.1 ipcmk.1 renice.1 setsid.1 \ - readprofile.8 + readprofile.8 prlimit.1 if LINUX bin_PROGRAMS += dmesg @@ -41,6 +41,9 @@ dmesg_SOURCES = dmesg.c $(top_srcdir)/lib/strutils.c ipcmk_SOURCES = ipcmk.c $(top_srcdir)/lib/strutils.c ipcrm_SOURCES = ipcrm.c $(top_srcdir)/lib/strutils.c flock_SOURCES = flock.c $(top_srcdir)/lib/strutils.c +prlimit_SOURCES = prlimit.c $(top_srcdir)/lib/strutils.c \ + $(top_srcdir)/lib/mbsalign.c \ + $(top_srcdir)/lib/tt.c if BUILD_MOUNTPOINT bin_PROGRAMS += mountpoint diff --git a/sys-utils/prlimit.1 b/sys-utils/prlimit.1 new file mode 100644 index 000000000..5c2001200 --- /dev/null +++ b/sys-utils/prlimit.1 @@ -0,0 +1,108 @@ +.\" prlimit.1 -- +.\" Copyright 2011 Davidlohr Bueso <dave@gnu.org> +.\" May be distributed under the GNU General Public License + +.TH PRLIMIT 1 "October 2011" "util-linux" "User Commands" +.SH NAME +prlimit \- +get and set a process resource limits. +.SH SYNOPSIS +.B prlimit +.RB [options] +.RB [ \-\-{resource_name}[=limits] ] + +.SH DESCRIPTION +Given a process id and one or more resources, \fBprlimit\fP tries to retrieve +and/or modify the limits. + +The \fIlimits\fP format is composed by a soft and a hard (ceiling) value, separated +by a semicolon (:), in order to modify the existing value(s). If no limits are +used, \fBprlimit\fP will only display the current values. If one of the values +is not used, then the existing one will be used. To specify the unlimited or +infinity limit (RLIM_INFINITY), the -1 or 'unlimited' string can be passed. + +Because of the nature of limits, the soft value must be lower or equal to the +high limit. To see all the available resource limits, refer to the RESOURCE +OPTIONS section. + +.IP "\fB<soft>:<hard>\fP Specify both limits" +.IP "\fB<soft>:\fP Specify only the soft limit" +.IP "\fB:<hard>\fP Specify only the hard limit" +.IP "\fB<value>\fP Specify both soft and hard limits to the same value" + +.SH GENERAL OPTIONS +.IP "\fB\-p, \-\-pid\fP" +Specify the process id, if none is given, it will use the running process. +.IP "\fB\-o, \-\-output \fIlist\fP" +Define the output columns to use. If no output arrangement is specified, then a default set is used. +Use \fB\-\-help\fP to get list of all supported columns. +.IP "\fB\-V, \-\-version\fP" +Output version information and exit. +.IP "\fB\-v, \-\-verbose\fP" +Vebose mode. +.IP "\fB\-h, \-\-help\fP" +Print a help text and exit. + +.SH RESOURCE OPTIONS +.IP "\fB\-c, \-\-core\fP[=limits]" +Maximum size of a core file. +.IP "\fB\-d, \-\-data\fP[=limits]" +Maximum data size. +.IP "\fB\-e, \-\-nice\fP[=limits]" +Maximum nice priority allowed to raise. +.IP "\fB\-f, \-\-fsize\fP[=limits]" +Maximum file size. +.IP "\fB\-i, \-\-sigpending\fP[=limits]" +Maximum amount of pending signals. +.IP "\fB\-l, \-\-memlock\fP[=limits]" +Maximum locked-in-memory address space. +.IP "\fB\-m, \-\-rss\fP[=limits]" +Maximum Resident Set Size (RSS). +.IP "\fB\-n, \-\-nofile\fP[=limits]" +Maximum amount of open files. +.IP "\fB\-q, \-\-msgqueue\fP[=limits]" +Maximum amount of bytes in POSIX message queues. +.IP "\fB\-r, \-\-rtprio\fP[=limits]" +Maximum real-time priority. +.IP "\fB\-s, \-\-stack\fP[=limits]" +Maximum size of the stack. +.IP "\fB\-t, \-\-cpu\fP[=limits]" +CPU time, in seconds. +.IP "\fB\-u, \-\-nproc\fP[=limits]" +Maximum amount of processes. +.IP "\fB\-v, \-\-as\fP[=limits]" +Address space limit. +.IP "\fB\-x, \-\-locks\fP[=limits]" +Maximum amount of file locks held. +.IP "\fB\-y, \-\-rttime\fP[=limits]" +Timeout for real-time tasks. + +.RE +.SH EXAMPLES +.IP "\fBprlimit \-\-pid 13134\fP" +Display limit values for all current resources. +.IP "\fBprlimit \-\-pid 13134 \--rss --nofile=1024:4095\fP" +Display the limits of the RSS and set the soft and hard limits for the amount +of open files to 1024 and 4095, respectively. +.IP "\fBprlimit \-\-pid 13134 --nproc=512:\fP" +Modify only the soft limit for the amount of processes. +.IP "\fBprlimit \-\-pid $$ --nproc=unlimited\fP" +Set the amount of processes for both soft and ceiling values to unlimited. + +.SH "SEE ALSO" +.BR prlimit (2), +.BR ulimit (1) + +.SH NOTES +.nf +The prlimit system call is supported since Linux 2.6.36, previous kernels will +break this program. +.fi + +.SH AUTHORS +.nf +Davidlohr Bueso <dave@gnu.org> - In memory of Dennis M. Ritchie. +.fi +.SH AVAILABILITY +The prlimit command is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. diff --git a/sys-utils/prlimit.c b/sys-utils/prlimit.c new file mode 100644 index 000000000..74c7fc98e --- /dev/null +++ b/sys-utils/prlimit.c @@ -0,0 +1,571 @@ +/* + * 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; + 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; +}; + +#define PRLIMIT_EMPTY_LIMIT {{ 0, 0, }, NULL, 0 } + +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) + +#define INFINITY_STR "unlimited" +#define INFINITY_STRLEN (sizeof(INFINITY_STR) - 1) + +#define PRLIMIT_SOFT (1 << 1) +#define PRLIMIT_HARD (1 << 2) + +/* array with IDs of enabled columns */ +static int columns[NCOLS], ncolumns; +static pid_t pid; /* calling process (default) */ +static int verbose; + +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" + " --verbose verbose output\n" + " -h, --help display this help and exit\n" + " -V, --version output version information and exit\n"), out); + + fputs(_("\nResources Options:\n"), out); + fputs(_(" -c, --core maximum size of core files created\n" + " -d, --data maximum size of a process's data segment\n" + " -e, --nice maximum nice priority allowed to raise\n" + " -f, --fsize maximum size of files written by the process\n" + " -i, --sigpending maximum amount of pending signals\n" + " -l, --memlock maximum size a process may lock into memory\n" + " -m, --rss maximum resident set size\n" + " -n, --nofile maximum amount of open files\n" + " -q, --msgqueue maximum bytes in POSIX message queues\n" + " -r, --rtprio maximum real-time scheduling priority\n" + " -s, --stack maximum stack size\n" + " -t, --cpu maximum amount of CPU time in seconds\n" + " -u, --nproc maximum number of user processes\n" + " -v, --as size of virtual memory\n" + " -x, --locks maximum number of file locks\n" + " -y, --rttime CPU time in microseconds a process scheduled\n" + " under real-time scheduling\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] < (int) NCOLS); + + return columns[num]; +} + +static inline struct colinfo *get_column_info(unsigned 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 == RLIM_INFINITY ? + asprintf(&str, "%s", "unlimited") : + asprintf(&str, "%llu", (unsigned long long) l->rlim.rlim_cur); + break; + case COL_HARD: + rc = l->rlim.rlim_max == RLIM_INFINITY ? + asprintf(&str, "%s", "unlimited") : + asprintf(&str, "%llu", (unsigned long long) 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) +{ + size_t 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, size_t 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; (size_t) 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[], size_t n) +{ + size_t i, nshows = 0; + + for (i = 0; i < n; i++) { + struct rlimit *new = NULL; + + if (lims[i].modify) + new = &lims[i].rlim; + else + nshows++; + + if (verbose && new) { + printf(_("New %s limit: "), lims[i].desc->name); + if (new->rlim_cur == RLIM_INFINITY) + printf("<%s", _("unlimited")); + else + printf("<%ju", new->rlim_cur); + + if (new->rlim_max == RLIM_INFINITY) + printf(":%s>\n", _("unlimited")); + else + printf(":%ju>\n", new->rlim_max); + } + + 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 *soft, rlim_t *hard, int *found) +{ + char *end = NULL; + + if (!str) + return 0; + + *found = errno = 0; + *soft = *hard = RLIM_INFINITY; + + if (!strcmp(str, INFINITY_STR)) { /* <unlimited> */ + *found |= PRLIMIT_SOFT | PRLIMIT_HARD; + return 0; + + } else if (*str == ':') { /* <:hard> */ + str++; + + if (strcmp(str, INFINITY_STR) != 0) { + *hard = strtoull(str, &end, 10); + + if (errno || !end || *end || end == str) + return -1; + } + *found |= PRLIMIT_HARD; + return 0; + + } + + if (strncmp(str, INFINITY_STR, INFINITY_STRLEN) == 0) { + /* <unlimited> or <unlimited:> */ + end = str + INFINITY_STRLEN; + } else { + /* <value> or <soft:> */ + *hard = *soft = strtoull(str, &end, 10); + if (errno || !end || end == str) + return -1; + } + + if (*end == ':' && !*(end + 1)) /* <soft:> */ + *found |= PRLIMIT_SOFT; + + else if (*end == ':') { /* <soft:hard> */ + str = end + 1; + + if (!strcmp(str, INFINITY_STR)) + *hard = RLIM_INFINITY; + else { + end = NULL; + errno = 0; + *hard = strtoull(str, &end, 10); + + if (errno || !end || *end || end == str) + return -1; + } + *found |= PRLIMIT_SOFT | PRLIMIT_HARD; + + } else /* <value> */ + *found |= PRLIMIT_SOFT | PRLIMIT_HARD; + + return 0; +} + + +static int parse_prlim(struct rlimit *lim, char *ops, size_t id) +{ + rlim_t soft, hard; + int found = 0; + + if (get_range(ops, &soft, &hard, &found)) + 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 (found != (PRLIMIT_HARD | PRLIMIT_SOFT)) { + 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 (!(found & PRLIMIT_SOFT)) + soft = old.rlim_cur; + else if (!(found & PRLIMIT_HARD)) + 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, size_t 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; + size_t n = 0; + struct prlimit lims[MAX_RESOURCES] = { PRLIMIT_EMPTY_LIMIT }; + + enum { + VERBOSE_OPTION = CHAR_MAX + 1 + }; + + static const struct option longopts[] = { + { "pid", required_argument, NULL, 'p' }, + { "output", required_argument, NULL, 'o' }, + { "as", optional_argument, NULL, 'v' }, + { "core", optional_argument, NULL, 'c' }, + { "cpu", optional_argument, NULL, 't' }, + { "data", optional_argument, NULL, 'd' }, + { "fsize", optional_argument, NULL, 'f' }, + { "locks", optional_argument, NULL, 'x' }, + { "memlock", optional_argument, NULL, 'l' }, + { "msgqueue", optional_argument, NULL, 'q' }, + { "nice", optional_argument, NULL, 'e' }, + { "nofile", optional_argument, NULL, 'n' }, + { "nproc", optional_argument, NULL, 'u' }, + { "rss", optional_argument, NULL, 'm' }, + { "rtprio", optional_argument, NULL, 'r' }, + { "rttime", optional_argument, NULL, 'y' }, + { "sigpending", optional_argument, NULL, 'i' }, + { "stack", optional_argument, NULL, 's' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { "verbose", no_argument, NULL, VERBOSE_OPTION }, + { 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, + "c::d::e::f::i::l::m::n::q::r::s::t::u::v::x::y::p:o:vVh", + longopts, NULL)) != -1) { + switch(opt) { + case 'c': + add_prlim(optarg, &lims[n++], CORE); + break; + case 'd': + add_prlim(optarg, &lims[n++], DATA); + break; + case 'e': + add_prlim(optarg, &lims[n++], NICE); + break; + case 'f': + add_prlim(optarg, &lims[n++], FSIZE); + break; + case 'i': + add_prlim(optarg, &lims[n++], SIGPENDING); + break; + case 'l': + add_prlim(optarg, &lims[n++], MEMLOCK); + break; + case 'm': + add_prlim(optarg, &lims[n++], RSS); + break; + case 'n': + add_prlim(optarg, &lims[n++], NOFILE); + break; + case 'q': + add_prlim(optarg, &lims[n++], MSGQUEUE); + break; + case 'r': + add_prlim(optarg, &lims[n++], RTPRIO); + break; + case 's': + add_prlim(optarg, &lims[n++], STACK); + break; + case 't': + add_prlim(optarg, &lims[n++], CPU); + break; + case 'u': + add_prlim(optarg, &lims[n++], NPROC); + break; + case 'v': + add_prlim(optarg, &lims[n++], AS); + break; + case 'x': + add_prlim(optarg, &lims[n++], LOCKS); + break; + case 'y': + add_prlim(optarg, &lims[n++], RTTIME); + break; + + 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 '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; + case VERBOSE_OPTION: + verbose++; + break; + default: + usage(stderr); + break; + } + } + + if (argc == 1) + usage(stderr); + + if (!ncolumns) { + /* default columns */ + columns[ncolumns++] = COL_RES; + columns[ncolumns++] = COL_HELP; + columns[ncolumns++] = COL_SOFT; + columns[ncolumns++] = COL_HARD; + } + + if (!n) { + /* default is to print all resources */ + for (; n < MAX_RESOURCES; n++) + add_prlim(NULL, &lims[n], n); + } + + do_prlimit(lims, n); + + return EXIT_SUCCESS; +} |