/* * prlimit - get/set process resource limits. * * Copyright (C) 2011 Davidlohr Bueso * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include "c.h" #include "nls.h" #include "xalloc.h" #include "strutils.h" #include "list.h" #include "closestream.h" #ifndef RLIMIT_RTTIME # define RLIMIT_RTTIME 15 #endif enum { AS, CORE, CPU, DATA, FSIZE, LOCKS, MEMLOCK, MSGQUEUE, NICE, NOFILE, NPROC, RSS, RTPRIO, RTTIME, SIGPENDING, STACK }; /* basic output flags */ static int no_headings; static int raw; struct prlimit_desc { const char *name; const char *help; const char *unit; int resource; }; static struct prlimit_desc prlimit_desc[] = { [AS] = { "AS", N_("address space limit"), N_("bytes"), RLIMIT_AS }, [CORE] = { "CORE", N_("max core file size"), N_("bytes"), RLIMIT_CORE }, [CPU] = { "CPU", N_("CPU time"), N_("seconds"), RLIMIT_CPU }, [DATA] = { "DATA", N_("max data size"), N_("bytes"), RLIMIT_DATA }, [FSIZE] = { "FSIZE", N_("max file size"), N_("bytes"), RLIMIT_FSIZE }, [LOCKS] = { "LOCKS", N_("max number of file locks held"), N_("locks"), RLIMIT_LOCKS }, [MEMLOCK] = { "MEMLOCK", N_("max locked-in-memory address space"), N_("bytes"), RLIMIT_MEMLOCK }, [MSGQUEUE] = { "MSGQUEUE", N_("max bytes in POSIX mqueues"), N_("bytes"), RLIMIT_MSGQUEUE }, [NICE] = { "NICE", N_("max nice prio allowed to raise"), NULL, RLIMIT_NICE }, [NOFILE] = { "NOFILE", N_("max number of open files"), N_("files"), RLIMIT_NOFILE }, [NPROC] = { "NPROC", N_("max number of processes"), N_("processes"), RLIMIT_NPROC }, [RSS] = { "RSS", N_("max resident set size"), N_("bytes"), RLIMIT_RSS }, [RTPRIO] = { "RTPRIO", N_("max real-time priority"), NULL, RLIMIT_RTPRIO }, [RTTIME] = { "RTTIME", N_("timeout for real-time tasks"), N_("microsecs"), RLIMIT_RTTIME }, [SIGPENDING] = { "SIGPENDING", N_("max number of pending signals"), N_("signals"), RLIMIT_SIGPENDING }, [STACK] = { "STACK", N_("max stack size"), N_("bytes"), RLIMIT_STACK } }; #define MAX_RESOURCES ARRAY_SIZE(prlimit_desc) struct prlimit { struct list_head lims; struct rlimit rlim; struct prlimit_desc *desc; int modify; /* PRLIMIT_{SOFT,HARD} mask */ }; #define PRLIMIT_EMPTY_LIMIT {{ 0, 0, }, NULL, 0 } enum { COL_HELP, COL_RES, COL_SOFT, COL_HARD, COL_UNITS, }; /* column names */ struct colinfo { const char *name; /* header */ double whint; /* width hint (N < 1 is in percent of termwidth) */ int flags; /* SCOLS_FL_* */ const char *help; }; /* columns descriptions */ static struct colinfo infos[] = { [COL_RES] = { "RESOURCE", 0.25, SCOLS_FL_TRUNC, N_("resource name") }, [COL_HELP] = { "DESCRIPTION", 0.1, SCOLS_FL_TRUNC, N_("resource description")}, [COL_SOFT] = { "SOFT", 0.1, SCOLS_FL_RIGHT, N_("soft limit")}, [COL_HARD] = { "HARD", 1, SCOLS_FL_RIGHT, N_("hard limit (ceiling)")}, [COL_UNITS] = { "UNITS", 0.1, SCOLS_FL_TRUNC, N_("units")}, }; static int columns[ARRAY_SIZE(infos) * 2]; static int ncolumns; #define INFINITY_STR "unlimited" #define INFINITY_STRLEN (sizeof(INFINITY_STR) - 1) #define PRLIMIT_SOFT (1 << 1) #define PRLIMIT_HARD (1 << 2) static pid_t pid; /* calling process (default) */ static int verbose; #ifndef HAVE_PRLIMIT # include static int prlimit(pid_t p, int resource, const struct rlimit *new_limit, struct rlimit *old_limit) { return syscall(SYS_prlimit64, p, resource, new_limit, old_limit); } #endif static void __attribute__((__noreturn__)) usage(void) { FILE *out = stdout; size_t i; fputs(USAGE_HEADER, out); fprintf(out, _(" %s [options] [-p PID]\n"), program_invocation_short_name); fprintf(out, _(" %s [options] COMMAND\n"), program_invocation_short_name); fputs(USAGE_SEPARATOR, out); fputs(_("Show or change the resource limits of a process.\n"), out); fputs(_("\nGeneral Options:\n"), out); fputs(_(" -p, --pid process id\n" " -o, --output define which output columns to use\n" " --noheadings don't print headings\n" " --raw use the raw output format\n" " --verbose verbose output\n" ), out); printf(USAGE_HELP_OPTIONS(24)); 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 number 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 number 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(USAGE_COLUMNS, out); for (i = 0; i < ARRAY_SIZE(infos); i++) fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help)); printf(USAGE_MAN_TAIL("prlimit(1)")); exit(EXIT_SUCCESS); } static inline int get_column_id(int num) { assert(num < ncolumns); assert(columns[num] < (int) ARRAY_SIZE(infos)); return columns[num]; } static inline struct colinfo *get_column_info(unsigned num) { return &infos[ get_column_id(num) ]; } static void add_scols_line(struct libscols_table *table, struct prlimit *l) { int i; struct libscols_line *line; assert(table); assert(l); line = scols_table_new_line(table, NULL); if (!line) err(EXIT_FAILURE, _("failed to allocate output line")); for (i = 0; i < ncolumns; i++) { char *str = NULL; switch (get_column_id(i)) { case COL_RES: str = xstrdup(l->desc->name); break; case COL_HELP: str = xstrdup(l->desc->help); break; case COL_SOFT: if (l->rlim.rlim_cur == RLIM_INFINITY) str = xstrdup(_("unlimited")); else xasprintf(&str, "%llu", (unsigned long long) l->rlim.rlim_cur); break; case COL_HARD: if (l->rlim.rlim_max == RLIM_INFINITY) str = xstrdup(_("unlimited")); else xasprintf(&str, "%llu", (unsigned long long) l->rlim.rlim_max); break; case COL_UNITS: str = l->desc->unit ? xstrdup(_(l->desc->unit)) : NULL; break; default: break; } if (str && scols_line_refer_data(line, i, str)) err(EXIT_FAILURE, _("failed to add output data")); } } static int column_name_to_id(const char *name, size_t namesz) { size_t i; assert(name); for (i = 0; i < ARRAY_SIZE(infos); i++) { const char *cn = infos[i].name; if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) return i; } warnx(_("unknown column: %s"), name); return -1; } static void rem_prlim(struct prlimit *lim) { if (!lim) return; list_del(&lim->lims); free(lim); } static int show_limits(struct list_head *lims) { int i; struct list_head *p, *pnext; struct libscols_table *table; table = scols_new_table(); if (!table) err(EXIT_FAILURE, _("failed to allocate output table")); scols_table_enable_raw(table, raw); scols_table_enable_noheadings(table, no_headings); for (i = 0; i < ncolumns; i++) { struct colinfo *col = get_column_info(i); if (!scols_table_new_column(table, col->name, col->whint, col->flags)) err(EXIT_FAILURE, _("failed to allocate output column")); } list_for_each_safe(p, pnext, lims) { struct prlimit *lim = list_entry(p, struct prlimit, lims); add_scols_line(table, lim); rem_prlim(lim); } scols_print_table(table); scols_unref_table(table); return 0; } /* * 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). */ static void get_unknown_hardsoft(struct prlimit *lim) { struct rlimit old; if (prlimit(pid, lim->desc->resource, NULL, &old) == -1) err(EXIT_FAILURE, _("failed to get old %s limit"), lim->desc->name); if (!(lim->modify & PRLIMIT_SOFT)) lim->rlim.rlim_cur = old.rlim_cur; else if (!(lim->modify & PRLIMIT_HARD)) lim->rlim.rlim_max = old.rlim_max; } static void do_prlimit(struct list_head *lims) { struct list_head *p, *pnext; list_for_each_safe(p, pnext, lims) { struct rlimit *new = NULL, *old = NULL; struct prlimit *lim = list_entry(p, struct prlimit, lims); if (lim->modify) { if (lim->modify != (PRLIMIT_HARD | PRLIMIT_SOFT)) get_unknown_hardsoft(lim); if ((lim->rlim.rlim_cur > lim->rlim.rlim_max) && (lim->rlim.rlim_cur != RLIM_INFINITY || lim->rlim.rlim_max != RLIM_INFINITY)) errx(EXIT_FAILURE, _("the soft limit %s cannot exceed the hard limit"), lim->desc->name); new = &lim->rlim; } else old = &lim->rlim; if (verbose && new) { printf(_("New %s limit for pid %d: "), lim->desc->name, pid ? pid : getpid()); if (new->rlim_cur == RLIM_INFINITY) printf("<%s", _("unlimited")); else printf("<%ju", (uintmax_t)new->rlim_cur); if (new->rlim_max == RLIM_INFINITY) printf(":%s>\n", _("unlimited")); else printf(":%ju>\n", (uintmax_t)new->rlim_max); } if (prlimit(pid, lim->desc->resource, new, old) == -1) err(EXIT_FAILURE, lim->modify ? _("failed to set the %s resource limit") : _("failed to get the %s resource limit"), lim->desc->name); if (lim->modify) rem_prlim(lim); /* modify only; don't show */ } } 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)) { /* */ *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) { /* or */ end = str + INFINITY_STRLEN; } else { /* or */ *hard = *soft = strtoull(str, &end, 10); if (errno || !end || end == str) return -1; } if (*end == ':' && !*(end + 1)) /* */ *found |= PRLIMIT_SOFT; else if (*end == ':') { /* */ 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 /* */ *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); lim->rlim_cur = soft; lim->rlim_max = hard; return found; } static int add_prlim(char *ops, struct list_head *lims, size_t id) { struct prlimit *lim = xcalloc(1, sizeof(*lim)); INIT_LIST_HEAD(&lim->lims); lim->desc = &prlimit_desc[id]; if (ops) lim->modify = parse_prlim(&lim->rlim, ops, id); list_add_tail(&lim->lims, lims); return 0; } int main(int argc, char **argv) { int opt; struct list_head lims; enum { VERBOSE_OPTION = CHAR_MAX + 1, RAW_OPTION, NOHEADINGS_OPTION }; 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' }, { "noheadings", no_argument, NULL, NOHEADINGS_OPTION }, { "raw", no_argument, NULL, RAW_OPTION }, { "verbose", no_argument, NULL, VERBOSE_OPTION }, { NULL, 0, NULL, 0 } }; setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); close_stdout_atexit(); INIT_LIST_HEAD(&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, "+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, CORE); break; case 'd': add_prlim(optarg, &lims, DATA); break; case 'e': add_prlim(optarg, &lims, NICE); break; case 'f': add_prlim(optarg, &lims, FSIZE); break; case 'i': add_prlim(optarg, &lims, SIGPENDING); break; case 'l': add_prlim(optarg, &lims, MEMLOCK); break; case 'm': add_prlim(optarg, &lims, RSS); break; case 'n': add_prlim(optarg, &lims, NOFILE); break; case 'q': add_prlim(optarg, &lims, MSGQUEUE); break; case 'r': add_prlim(optarg, &lims, RTPRIO); break; case 's': add_prlim(optarg, &lims, STACK); break; case 't': add_prlim(optarg, &lims, CPU); break; case 'u': add_prlim(optarg, &lims, NPROC); break; case 'v': add_prlim(optarg, &lims, AS); break; case 'x': add_prlim(optarg, &lims, LOCKS); break; case 'y': add_prlim(optarg, &lims, RTTIME); break; case 'p': if (pid) errx(EXIT_FAILURE, _("option --pid may be specified only once")); pid = strtos32_or_err(optarg, _("invalid PID argument")); break; case 'o': ncolumns = string_to_idarray(optarg, columns, ARRAY_SIZE(columns), column_name_to_id); if (ncolumns < 0) return EXIT_FAILURE; break; case NOHEADINGS_OPTION: no_headings = 1; break; case VERBOSE_OPTION: verbose++; break; case RAW_OPTION: raw = 1; break; case 'h': usage(); case 'V': print_version(EXIT_SUCCESS); default: errtryhelp(EXIT_FAILURE); } } if (argc > optind && pid) errx(EXIT_FAILURE, _("options --pid and COMMAND are mutually exclusive")); if (!ncolumns) { /* default columns */ columns[ncolumns++] = COL_RES; columns[ncolumns++] = COL_HELP; columns[ncolumns++] = COL_SOFT; columns[ncolumns++] = COL_HARD; columns[ncolumns++] = COL_UNITS; } scols_init_debug(0); if (list_empty(&lims)) { /* default is to print all resources */ size_t n; for (n = 0; n < MAX_RESOURCES; n++) add_prlim(NULL, &lims, n); } do_prlimit(&lims); if (!list_empty(&lims)) show_limits(&lims); if (argc > optind) { /* prlimit [options] COMMAND */ execvp(argv[optind], &argv[optind]); errexec(argv[optind]); } return EXIT_SUCCESS; }