2 * SPDX-License-Identifier: BSD-2-Clause
4 * Copyright (c) 2010 The FreeBSD Foundation
6 * This software was developed by Edward Tomasz Napierala under sponsorship
7 * from the FreeBSD Foundation.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 #include <sys/cdefs.h>
32 #include <sys/types.h>
34 #include <sys/sysctl.h>
49 #define RCTL_DEFAULT_BUFSIZE 128 * 1024
52 parse_user(const char *s, id_t *uidp, const char *unexpanded_rule)
63 if (!isnumber(s[0])) {
64 warnx("malformed rule '%s': unknown user '%s'",
69 *uidp = strtod(s, &end);
70 if ((size_t)(end - s) != strlen(s)) {
71 warnx("malformed rule '%s': trailing characters "
72 "after numerical id", unexpanded_rule);
80 parse_group(const char *s, id_t *gidp, const char *unexpanded_rule)
91 if (!isnumber(s[0])) {
92 warnx("malformed rule '%s': unknown group '%s'",
97 *gidp = strtod(s, &end);
98 if ((size_t)(end - s) != strlen(s)) {
99 warnx("malformed rule '%s': trailing characters "
100 "after numerical id", unexpanded_rule);
108 * Replace human-readable number with its expanded form.
111 expand_amount(const char *rule, const char *unexpanded_rule)
114 const char *subject, *subject_id, *resource, *action, *amount, *per;
115 char *copy, *expanded, *tofree;
118 tofree = copy = strdup(rule);
124 subject = strsep(©, ":");
125 subject_id = strsep(©, ":");
126 resource = strsep(©, ":");
127 action = strsep(©, "=/");
128 amount = strsep(©, "/");
131 if (amount == NULL || strlen(amount) == 0) {
133 * The "copy" has already been tinkered with by strsep().
144 assert(subject != NULL);
145 assert(subject_id != NULL);
146 assert(resource != NULL);
147 assert(action != NULL);
149 if (expand_number(amount, &num)) {
150 warnx("malformed rule '%s': invalid numeric value '%s'",
151 unexpanded_rule, amount);
157 ret = asprintf(&expanded, "%s:%s:%s:%s=%ju",
158 subject, subject_id, resource, action, (uintmax_t)num);
160 ret = asprintf(&expanded, "%s:%s:%s:%s=%ju/%s",
161 subject, subject_id, resource, action, (uintmax_t)num, per);
176 expand_rule(const char *rule, bool resolve_ids)
179 const char *subject, *textid, *rest;
180 char *copy, *expanded, *resolved, *tofree;
183 tofree = copy = strdup(rule);
189 subject = strsep(©, ":");
190 textid = strsep(©, ":");
191 if (textid == NULL) {
192 warnx("malformed rule '%s': missing subject", rule);
200 if (strcasecmp(subject, "u") == 0)
202 else if (strcasecmp(subject, "g") == 0)
204 else if (strcasecmp(subject, "p") == 0)
206 else if (strcasecmp(subject, "l") == 0 ||
207 strcasecmp(subject, "c") == 0 ||
208 strcasecmp(subject, "class") == 0)
209 subject = "loginclass";
210 else if (strcasecmp(subject, "j") == 0)
214 strcasecmp(subject, "user") == 0 && strlen(textid) > 0) {
215 error = parse_user(textid, &id, rule);
220 ret = asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest);
221 } else if (resolve_ids &&
222 strcasecmp(subject, "group") == 0 && strlen(textid) > 0) {
223 error = parse_group(textid, &id, rule);
228 ret = asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest);
230 ret = asprintf(&resolved, "%s:%s:%s", subject, textid, rest);
241 expanded = expand_amount(resolved, rule);
248 humanize_ids(char *rule)
253 const char *subject, *textid, *rest;
254 char *end, *humanized;
257 subject = strsep(&rule, ":");
258 textid = strsep(&rule, ":");
260 errx(1, "rule passed from the kernel didn't contain subject");
266 /* Replace numerical user and group ids with names. */
267 if (strcasecmp(subject, "user") == 0) {
268 id = strtod(textid, &end);
269 if ((size_t)(end - textid) != strlen(textid))
270 errx(1, "malformed uid '%s'", textid);
273 textid = pwd->pw_name;
274 } else if (strcasecmp(subject, "group") == 0) {
275 id = strtod(textid, &end);
276 if ((size_t)(end - textid) != strlen(textid))
277 errx(1, "malformed gid '%s'", textid);
280 textid = grp->gr_name;
283 ret = asprintf(&humanized, "%s:%s:%s", subject, textid, rest);
291 str2int64(const char *str, int64_t *value)
298 *value = strtoul(str, &end, 10);
299 if ((size_t)(end - str) != strlen(str))
306 humanize_amount(char *rule)
309 const char *subject, *subject_id, *resource, *action, *amount, *per;
310 char *copy, *humanized, buf[6], *tofree;
313 tofree = copy = strdup(rule);
317 subject = strsep(©, ":");
318 subject_id = strsep(©, ":");
319 resource = strsep(©, ":");
320 action = strsep(©, "=/");
321 amount = strsep(©, "/");
324 if (amount == NULL || strlen(amount) == 0 ||
325 str2int64(amount, &num) != 0) {
330 assert(subject != NULL);
331 assert(subject_id != NULL);
332 assert(resource != NULL);
333 assert(action != NULL);
335 if (humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE,
336 HN_DECIMAL | HN_NOSPACE) == -1)
337 err(1, "humanize_number");
340 ret = asprintf(&humanized, "%s:%s:%s:%s=%s",
341 subject, subject_id, resource, action, buf);
343 ret = asprintf(&humanized, "%s:%s:%s:%s=%s/%s",
344 subject, subject_id, resource, action, buf, per);
355 * Print rules, one per line.
358 print_rules(char *rules, int hflag, int nflag)
362 while ((rule = strsep(&rules, ",")) != NULL) {
366 rule = humanize_ids(rule);
368 rule = humanize_amount(rule);
369 printf("%s\n", rule);
376 size_t racct_enable_len;
380 racct_enable_len = sizeof(racct_enable);
381 error = sysctlbyname("kern.racct.enable",
382 &racct_enable, &racct_enable_len, NULL, 0);
386 errx(1, "RACCT/RCTL support not present in kernel; see rctl(8) for details");
388 err(1, "sysctlbyname");
392 errx(1, "RACCT/RCTL present, but disabled; enable using kern.racct.enable=1 tunable");
396 add_rule(const char *rule, const char *unexpanded_rule)
400 error = rctl_add_rule(rule, strlen(rule) + 1, NULL, 0);
404 warn("failed to add rule '%s'", unexpanded_rule);
411 show_limits(const char *filter, const char *unexpanded_rule,
412 int hflag, int nflag)
416 size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
420 outbuf = realloc(outbuf, outbuflen);
423 error = rctl_get_limits(filter, strlen(filter) + 1,
431 warn("failed to get limits for '%s'", unexpanded_rule);
437 print_rules(outbuf, hflag, nflag);
444 remove_rule(const char *filter, const char *unexpanded_rule)
448 error = rctl_remove_rule(filter, strlen(filter) + 1, NULL, 0);
452 warn("failed to remove rule '%s'", unexpanded_rule);
459 humanize_usage_amount(char *usage)
462 const char *resource, *amount;
463 char *copy, *humanized, buf[6], *tofree;
466 tofree = copy = strdup(usage);
470 resource = strsep(©, "=");
473 assert(resource != NULL);
474 assert(amount != NULL);
476 if (str2int64(amount, &num) != 0 ||
477 humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE,
478 HN_DECIMAL | HN_NOSPACE) == -1) {
483 ret = asprintf(&humanized, "%s=%s", resource, buf);
492 * Query the kernel about a resource usage and print it out.
495 show_usage(const char *filter, const char *unexpanded_rule, int hflag)
498 char *copy, *outbuf = NULL, *tmp;
499 size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
503 outbuf = realloc(outbuf, outbuflen);
506 error = rctl_get_racct(filter, strlen(filter) + 1,
514 warn("failed to show resource consumption for '%s'",
522 while ((tmp = strsep(©, ",")) != NULL) {
527 tmp = humanize_usage_amount(tmp);
538 * Query the kernel about resource limit rules and print them out.
541 show_rules(const char *filter, const char *unexpanded_rule,
542 int hflag, int nflag)
546 size_t filterlen, outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
549 filterlen = strlen(filter) + 1;
555 outbuf = realloc(outbuf, outbuflen);
558 error = rctl_get_rules(filter, filterlen, outbuf, outbuflen);
565 warn("failed to show rules for '%s'", unexpanded_rule);
571 print_rules(outbuf, hflag, nflag);
581 fprintf(stderr, "usage: rctl [ -h ] [-a rule | -l filter | -r filter "
582 "| -u filter | filter]\n");
587 main(int argc, char **argv)
589 int ch, aflag = 0, hflag = 0, nflag = 0, lflag = 0, rflag = 0,
591 char *rule = NULL, *unexpanded_rule;
592 int i, cumulated_error, error;
594 while ((ch = getopt(argc, argv, "ahlnru")) != -1) {
624 if (aflag + lflag + rflag + uflag > 1)
625 errx(1, "at most one of -a, -l, -r, or -u may be specified");
628 if (aflag + lflag + rflag + uflag == 0) {
630 show_rules(rule, rule, hflag, nflag);
640 for (i = 0; i < argc; i++) {
641 unexpanded_rule = argv[i];
644 * Skip resolving if passed -n _and_ -a. Ignore -n otherwise,
645 * so we can still do "rctl -n u:root" and see the rules without
648 if (aflag != 0 && nflag != 0)
649 rule = expand_rule(unexpanded_rule, false);
651 rule = expand_rule(unexpanded_rule, true);
659 * The reason for passing the unexpanded_rule is to make
660 * it easier for the user to search for the problematic
661 * rule in the passed input.
664 error = add_rule(rule, unexpanded_rule);
666 error = show_limits(rule, unexpanded_rule,
669 error = remove_rule(rule, unexpanded_rule);
671 error = show_usage(rule, unexpanded_rule, hflag);
673 error = show_rules(rule, unexpanded_rule,
683 return (cumulated_error);