From b43344bc6ae37a8faa817ff1e253d86c768097b2 Mon Sep 17 00:00:00 2001 From: trasz Date: Wed, 30 Mar 2011 18:27:52 +0000 Subject: Add rctl(8), the utility to manage rctl rules. Sponsored by: The FreeBSD Foundation Reviewed by: kib (earlier version) --- usr.bin/Makefile | 1 + usr.bin/rctl/Makefile | 11 ++ usr.bin/rctl/rctl.8 | 200 +++++++++++++++++++ usr.bin/rctl/rctl.c | 539 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 751 insertions(+) create mode 100644 usr.bin/rctl/Makefile create mode 100644 usr.bin/rctl/rctl.8 create mode 100644 usr.bin/rctl/rctl.c (limited to 'usr.bin') diff --git a/usr.bin/Makefile b/usr.bin/Makefile index 44408ba..85a9180 100644 --- a/usr.bin/Makefile +++ b/usr.bin/Makefile @@ -126,6 +126,7 @@ SUBDIR= alias \ printenv \ printf \ procstat \ + rctl \ renice \ rev \ revoke \ diff --git a/usr.bin/rctl/Makefile b/usr.bin/rctl/Makefile new file mode 100644 index 0000000..1088cf1 --- /dev/null +++ b/usr.bin/rctl/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD$ + +PROG= rctl +MAN= rctl.8 + +DPADD= ${LIBUTIL} +LDADD= -lutil + +WARNS?= 6 + +.include diff --git a/usr.bin/rctl/rctl.8 b/usr.bin/rctl/rctl.8 new file mode 100644 index 0000000..dc9b36d --- /dev/null +++ b/usr.bin/rctl/rctl.8 @@ -0,0 +1,200 @@ +.\"- +.\" Copyright (c) 2009 Edward Tomasz Napierala +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR THE VOICES IN HIS HEAD BE +.\" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd February 13, 2011 +.Dt RCTL 8 +.Os +.Sh NAME +.Nm rctl +.Nd display and update resource limits database +.Sh SYNOPSIS +.Nm +.Op Fl h +.Op Fl n +.Op Ar filter +.Nm +.Fl a +.Op Ar rule +.Nm +.Op Fl h +.Op Fl n +.Fl l +.Op Ar filter +.Nm +.Fl r +.Op Ar filter +.Nm +.Op Fl h +.Fl u +.Op Ar filter +.Sh DESCRIPTION +When called without options, the +.Nm +command writes currently defined RCTL rules to standard output. +.Pp +If a +.Ar filter +argument is specified, only rules matching the filter are displayed. +The options are as follows: +.Bl -tag -width indent +.It Fl a Ar rule +Add +.Ar rule +to the RCTL database. +.It Fl l Ar filter +Display rules applicable to the process defined by +.Ar filter . +.It Fl r Ar filter +Remove rules matching +.Ar filter +from the RCTL database. +.It Fl u Ar filter +Display resource usage for a subject (process, user, login class +or jail) matching the +.Ar filter . +.It Fl h +"Human-readable" output. +Use unit suffixes: Byte, Kilobyte, Megabyte, +Gigabyte, Terabyte and Petabyte. +.It Fl n +Display user IDs numerically rather than converting them to a user name. +.Pp +.Sh RULE SYNTAX +Syntax for a rule is subject:subject-id:resource:action=amount/per. +.Pp +Subject defines the kind of entity the rule applies to. +It can be either process, user, login class, or jail. +.Pp +Subject ID identifies the subject. It can be user name, +numerical user ID, login class name, jail name, or numerical jail ID. +.Pp +Resource identifies the resource the rule controls. +.Pp +Action defines what will happen when a process exceeds the allowed amount. +.Pp +Amount defines how much of the resource a process can use before +the defined action triggers. +.Pp +The per field defines what entity the amount gets accounted for. +For example, rule "loginclass:users:vmem:deny=100M/process" means +that each process of any user belonging to login class "users" may allocate +up to 100MB of virtual memory. +Rule "loginclass:users:vmem:deny=100M/user" would mean that for each +user belonging to the login class "users", the sum of virtual memory allocated +by all the processes of a that user will not exceed 100MB. +Rule "loginclass:users:vmem:deny=100M/loginclass" would mean that the sum of +virtual memory allocated by all processes of all users belonging to that login +class will not exceed 100MB. +.Pp +Valid rule has all those fields specified, except for the per, which defaults +to the value of subject. +.Pp +A filter is a rule for which one of more fields other than per is left empty. +For example, a filter that matches every rule could be written as ":::=/", +or, in short, ":". +A filter that matches all the login classes would be "loginclass:". +A filter that matches all defined rules for nproc resource would be +"::nproc". +.Pp +.Sh RESOURCES +.Bl -column -offset 3n "msgqqueued" +.It cpu CPU time, in milliseconds +.It fsize maximum file size, in bytes +.It data data size, in bytes +.It stack stack size, in bytes +.It core core dump size, in bytes +.It rss resident set size, in bytes +.It memlock locked memory, in bytes +.It nproc number of processes +.It nofile file descriptor table size +.It sbsize memory consumed by socket buffers, in bytes +.It vmem address space limit, in bytes +.It npts number of PTYs +.It swap swap usage, in bytes +.It nthr number of threads +.It msgqqueued number of queued SysV messages +.It msgqsize SysV message queue size, in bytes +.It nmsgq number of SysV message queues +.It nsem number of SysV semaphores +.It nsemop number of SysV semaphores modified in a single semop(2) call +.It nshm number of SysV shared memory segments +.It shmsize SysV shared memory size, in bytes +.It wallclock wallclock time, in milliseconds +.It pctcpu %cpu time +.El +.Pp +.Sh ACTIONS +.Bl -column -offset 3n "msgqqueued" +.It deny deny the allocation; not supported for cpu and wallclock +.It log log a warning to the console +.It devctl send notification to +.Xr devd 8 +.It sig* e.g. sigterm; send a signal to the offending process +.El +.Pp +See +.Xr signal 3 +for a list of supported signals. +.Pp +Not all actions are supported for all resources. +Attempt to add rule with action not supported by a given resouce will result +in error. +.Pp +Note that limiting RSS may kill the machine due to thrashing. +.Pp +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +.Dl rctl -a user:joe:vmem:deny=1g +.Pp +Prevent user "joe" from allocating more than 1GB of virtual memory. +.Pp +.Dl rctl -r : +.Pp +Remove all RCTL rules. +.Pp +.Dl rctl -hu jail:5 +.Pp +Display resource usage information for jail with JID 5. +.Pp +.Dl rctl -l process:512 +.Pp +Display all the rules applicable to process with PID 512. +.Sh SEE ALSO +.Xr jailstat 8 , +.Xr userstat 8 +.Sh HISTORY +The +.Nm +command appeared in +.Fx 9.0. +.Sh AUTHORS +.An -nosplit +The +.Nm +command was written by +.An Edward Tomasz Napierala Aq trasz@FreeBSD.org . diff --git a/usr.bin/rctl/rctl.c b/usr.bin/rctl/rctl.c new file mode 100644 index 0000000..185788d --- /dev/null +++ b/usr.bin/rctl/rctl.c @@ -0,0 +1,539 @@ +/*- + * Copyright (c) 2010 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RCTL_DEFAULT_BUFSIZE 4096 + +static id_t +parse_user(const char *s) +{ + id_t id; + char *end; + struct passwd *pwd; + + pwd = getpwnam(s); + if (pwd != NULL) + return (pwd->pw_uid); + + if (!isnumber(s[0])) + errx(1, "uknown user '%s'", s); + + id = strtod(s, &end); + if ((size_t)(end - s) != strlen(s)) + errx(1, "trailing characters after numerical id"); + + return (id); +} + +static id_t +parse_group(const char *s) +{ + id_t id; + char *end; + struct group *grp; + + grp = getgrnam(s); + if (grp != NULL) + return (grp->gr_gid); + + if (!isnumber(s[0])) + errx(1, "uknown group '%s'", s); + + id = strtod(s, &end); + if ((size_t)(end - s) != strlen(s)) + errx(1, "trailing characters after numerical id"); + + return (id); +} + +/* + * This routine replaces user/group name with numeric id. + */ +static char * +resolve_ids(char *rule) +{ + id_t id; + const char *subject, *textid, *rest; + char *resolved; + + subject = strsep(&rule, ":"); + textid = strsep(&rule, ":"); + if (textid == NULL) + errx(1, "error in rule specification -- no subject"); + if (rule != NULL) + rest = rule; + else + rest = ""; + + if (strcasecmp(subject, "u") == 0) + subject = "user"; + else if (strcasecmp(subject, "g") == 0) + subject = "group"; + else if (strcasecmp(subject, "p") == 0) + subject = "process"; + else if (strcasecmp(subject, "l") == 0 || + strcasecmp(subject, "c") == 0 || + strcasecmp(subject, "class") == 0) + subject = "loginclass"; + else if (strcasecmp(subject, "j") == 0) + subject = "jail"; + + if (strcasecmp(subject, "user") == 0 && strlen(textid) > 0) { + id = parse_user(textid); + asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); + } else if (strcasecmp(subject, "group") == 0 && strlen(textid) > 0) { + id = parse_group(textid); + asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); + } else + asprintf(&resolved, "%s:%s:%s", subject, textid, rest); + + if (resolved == NULL) + err(1, "asprintf"); + + return (resolved); +} + +/* + * This routine replaces "human-readable" number with its expanded form. + */ +static char * +expand_amount(char *rule) +{ + uint64_t num; + const char *subject, *subject_id, *resource, *action, *amount, *per; + char *copy, *expanded; + + copy = strdup(rule); + if (copy == NULL) + err(1, "strdup"); + + subject = strsep(©, ":"); + subject_id = strsep(©, ":"); + resource = strsep(©, ":"); + action = strsep(©, "=/"); + amount = strsep(©, "/"); + per = copy; + + if (amount == NULL || strlen(amount) == 0) { + free(copy); + return (rule); + } + + assert(subject != NULL); + assert(subject_id != NULL); + assert(resource != NULL); + assert(action != NULL); + + if (expand_number(amount, &num)) + err(1, "expand_number"); + + if (per == NULL) + asprintf(&expanded, "%s:%s:%s:%s=%ju", subject, subject_id, + resource, action, (uintmax_t)num); + else + asprintf(&expanded, "%s:%s:%s:%s=%ju/%s", subject, subject_id, + resource, action, (uintmax_t)num, per); + + if (expanded == NULL) + err(1, "asprintf"); + + return (expanded); +} + +static char * +humanize_ids(char *rule) +{ + id_t id; + struct passwd *pwd; + struct group *grp; + const char *subject, *textid, *rest; + char *humanized; + + subject = strsep(&rule, ":"); + textid = strsep(&rule, ":"); + if (textid == NULL) + errx(1, "rule passed from the kernel didn't contain subject"); + if (rule != NULL) + rest = rule; + else + rest = ""; + + /* Replace numerical user and group ids with names. */ + if (strcasecmp(subject, "user") == 0) { + id = parse_user(textid); + pwd = getpwuid(id); + if (pwd != NULL) + textid = pwd->pw_name; + } else if (strcasecmp(subject, "group") == 0) { + id = parse_group(textid); + grp = getgrgid(id); + if (grp != NULL) + textid = grp->gr_name; + } + + asprintf(&humanized, "%s:%s:%s", subject, textid, rest); + + if (humanized == NULL) + err(1, "asprintf"); + + return (humanized); +} + +static int +str2int64(const char *str, int64_t *value) +{ + char *end; + + if (str == NULL) + return (EINVAL); + + *value = strtoul(str, &end, 10); + if ((size_t)(end - str) != strlen(str)) + return (EINVAL); + + return (0); +} + +static char * +humanize_amount(char *rule) +{ + int64_t num; + const char *subject, *subject_id, *resource, *action, *amount, *per; + char *copy, *humanized, buf[6]; + + copy = strdup(rule); + if (copy == NULL) + err(1, "strdup"); + + subject = strsep(©, ":"); + subject_id = strsep(©, ":"); + resource = strsep(©, ":"); + action = strsep(©, "=/"); + amount = strsep(©, "/"); + per = copy; + + if (amount == NULL || strlen(amount) == 0 || + str2int64(amount, &num) != 0) { + free(copy); + return (rule); + } + + assert(subject != NULL); + assert(subject_id != NULL); + assert(resource != NULL); + assert(action != NULL); + + if (humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE, + HN_DECIMAL | HN_NOSPACE) == -1) + err(1, "humanize_number"); + + if (per == NULL) + asprintf(&humanized, "%s:%s:%s:%s=%s", subject, subject_id, + resource, action, buf); + else + asprintf(&humanized, "%s:%s:%s:%s=%s/%s", subject, subject_id, + resource, action, buf, per); + + if (humanized == NULL) + err(1, "asprintf"); + + return (humanized); +} + +/* + * Print rules, one per line. + */ +static void +print_rules(char *rules, int hflag, int nflag) +{ + char *rule; + + while ((rule = strsep(&rules, ",")) != NULL) { + if (rule[0] == '\0') + break; /* XXX */ + if (nflag == 0) + rule = humanize_ids(rule); + if (hflag) + rule = humanize_amount(rule); + printf("%s\n", rule); + } +} + +static void +add_rule(char *rule) +{ + int error; + + error = rctl_add_rule(rule, strlen(rule) + 1, NULL, 0); + if (error != 0) + err(1, "rctl_add_rule"); + free(rule); +} + +static void +show_limits(char *filter, int hflag, int nflag) +{ + int error; + char *outbuf = NULL; + size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4; + + do { + outbuflen *= 4; + outbuf = realloc(outbuf, outbuflen); + if (outbuf == NULL) + err(1, "realloc"); + + error = rctl_get_limits(filter, strlen(filter) + 1, outbuf, + outbuflen); + if (error && errno != ERANGE) + err(1, "rctl_get_limits"); + } while (error && errno == ERANGE); + + print_rules(outbuf, hflag, nflag); + free(filter); + free(outbuf); +} + +static void +remove_rule(char *filter) +{ + int error; + + error = rctl_remove_rule(filter, strlen(filter) + 1, NULL, 0); + if (error != 0) + err(1, "rctl_remove_rule"); + free(filter); +} + +static char * +humanize_usage_amount(char *usage) +{ + int64_t num; + const char *resource, *amount; + char *copy, *humanized, buf[6]; + + copy = strdup(usage); + if (copy == NULL) + err(1, "strdup"); + + resource = strsep(©, "="); + amount = copy; + + assert(resource != NULL); + assert(amount != NULL); + + if (str2int64(amount, &num) != 0 || + humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE, + HN_DECIMAL | HN_NOSPACE) == -1) { + free(copy); + return (usage); + } + + asprintf(&humanized, "%s=%s", resource, buf); + if (humanized == NULL) + err(1, "asprintf"); + + return (humanized); +} + +/* + * Query the kernel about a resource usage and print it out. + */ +static void +show_usage(char *filter, int hflag) +{ + int error; + char *outbuf = NULL, *tmp; + size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4; + + do { + outbuflen *= 4; + outbuf = realloc(outbuf, outbuflen); + if (outbuf == NULL) + err(1, "realloc"); + + error = rctl_get_racct(filter, strlen(filter) + 1, outbuf, + outbuflen); + if (error && errno != ERANGE) + err(1, "rctl_get_racct"); + } while (error && errno == ERANGE); + + while ((tmp = strsep(&outbuf, ",")) != NULL) { + if (tmp[0] == '\0') + break; /* XXX */ + + if (hflag) + tmp = humanize_usage_amount(tmp); + + printf("%s\n", tmp); + } + + free(filter); + free(outbuf); +} + +/* + * Query the kernel about resource limit rules and print them out. + */ +static void +show_rules(char *filter, int hflag, int nflag) +{ + int error; + char *outbuf = NULL; + size_t filterlen, outbuflen = RCTL_DEFAULT_BUFSIZE / 4; + + if (filter != NULL) + filterlen = strlen(filter) + 1; + else + filterlen = 0; + + do { + outbuflen *= 4; + outbuf = realloc(outbuf, outbuflen); + if (outbuf == NULL) + err(1, "realloc"); + + error = rctl_get_rules(filter, filterlen, outbuf, outbuflen); + if (error && errno != ERANGE) + err(1, "rctl_get_rules"); + } while (error && errno == ERANGE); + + print_rules(outbuf, hflag, nflag); + free(outbuf); +} + +static void +usage(void) +{ + + fprintf(stderr, "usage: rctl [ -h ] [-a rule | -l filter | -r filter " + "| -u filter | filter]\n"); + exit(1); +} + +int +main(int argc __unused, char **argv __unused) +{ + int ch, aflag = 0, hflag = 0, nflag = 0, lflag = 0, rflag = 0, + uflag = 0; + char *rule = NULL; + + while ((ch = getopt(argc, argv, "a:hl:nr:u:")) != -1) { + switch (ch) { + case 'a': + aflag = 1; + rule = strdup(optarg); + break; + case 'h': + hflag = 1; + break; + case 'l': + lflag = 1; + rule = strdup(optarg); + break; + case 'n': + nflag = 1; + break; + case 'r': + rflag = 1; + rule = strdup(optarg); + break; + case 'u': + uflag = 1; + rule = strdup(optarg); + break; + + case '?': + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (argc > 1) + usage(); + + if (rule == NULL) { + if (argc == 1) + rule = strdup(argv[0]); + else + rule = strdup("::"); + } + + if (aflag + lflag + rflag + uflag + argc > 1) + errx(1, "only one flag or argument may be specified " + "at the same time"); + + rule = resolve_ids(rule); + rule = expand_amount(rule); + + if (aflag) { + add_rule(rule); + return (0); + } + + if (lflag) { + show_limits(rule, hflag, nflag); + return (0); + } + + if (rflag) { + remove_rule(rule); + return (0); + } + + if (uflag) { + show_usage(rule, hflag); + return (0); + } + + show_rules(rule, hflag, nflag); + return (0); +} -- cgit v1.1