2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2010 The FreeBSD Foundation
7 * This software was developed by Edward Tomasz Napierala under sponsorship
8 * from the FreeBSD Foundation.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 #include <sys/cdefs.h>
35 __FBSDID("$FreeBSD$");
37 #include <sys/types.h>
39 #include <sys/sysctl.h>
54 #define RCTL_DEFAULT_BUFSIZE 128 * 1024
57 parse_user(const char *s, id_t *uidp, const char *unexpanded_rule)
68 if (!isnumber(s[0])) {
69 warnx("malformed rule '%s': unknown user '%s'",
74 *uidp = strtod(s, &end);
75 if ((size_t)(end - s) != strlen(s)) {
76 warnx("malformed rule '%s': trailing characters "
77 "after numerical id", unexpanded_rule);
85 parse_group(const char *s, id_t *gidp, const char *unexpanded_rule)
96 if (!isnumber(s[0])) {
97 warnx("malformed rule '%s': unknown group '%s'",
102 *gidp = strtod(s, &end);
103 if ((size_t)(end - s) != strlen(s)) {
104 warnx("malformed rule '%s': trailing characters "
105 "after numerical id", unexpanded_rule);
113 * Replace human-readable number with its expanded form.
116 expand_amount(const char *rule, const char *unexpanded_rule)
119 const char *subject, *subject_id, *resource, *action, *amount, *per;
120 char *copy, *expanded, *tofree;
123 tofree = copy = strdup(rule);
129 subject = strsep(©, ":");
130 subject_id = strsep(©, ":");
131 resource = strsep(©, ":");
132 action = strsep(©, "=/");
133 amount = strsep(©, "/");
136 if (amount == NULL || strlen(amount) == 0) {
138 * The "copy" has already been tinkered with by strsep().
149 assert(subject != NULL);
150 assert(subject_id != NULL);
151 assert(resource != NULL);
152 assert(action != NULL);
154 if (expand_number(amount, &num)) {
155 warnx("malformed rule '%s': invalid numeric value '%s'",
156 unexpanded_rule, amount);
162 ret = asprintf(&expanded, "%s:%s:%s:%s=%ju",
163 subject, subject_id, resource, action, (uintmax_t)num);
165 ret = asprintf(&expanded, "%s:%s:%s:%s=%ju/%s",
166 subject, subject_id, resource, action, (uintmax_t)num, per);
181 expand_rule(const char *rule, bool resolve_ids)
184 const char *subject, *textid, *rest;
185 char *copy, *expanded, *resolved, *tofree;
188 tofree = copy = strdup(rule);
194 subject = strsep(©, ":");
195 textid = strsep(©, ":");
196 if (textid == NULL) {
197 warnx("malformed rule '%s': missing subject", rule);
205 if (strcasecmp(subject, "u") == 0)
207 else if (strcasecmp(subject, "g") == 0)
209 else if (strcasecmp(subject, "p") == 0)
211 else if (strcasecmp(subject, "l") == 0 ||
212 strcasecmp(subject, "c") == 0 ||
213 strcasecmp(subject, "class") == 0)
214 subject = "loginclass";
215 else if (strcasecmp(subject, "j") == 0)
219 strcasecmp(subject, "user") == 0 && strlen(textid) > 0) {
220 error = parse_user(textid, &id, rule);
225 ret = asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest);
226 } else if (resolve_ids &&
227 strcasecmp(subject, "group") == 0 && strlen(textid) > 0) {
228 error = parse_group(textid, &id, rule);
233 ret = asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest);
235 ret = asprintf(&resolved, "%s:%s:%s", subject, textid, rest);
246 expanded = expand_amount(resolved, rule);
253 humanize_ids(char *rule)
258 const char *subject, *textid, *rest;
259 char *end, *humanized;
262 subject = strsep(&rule, ":");
263 textid = strsep(&rule, ":");
265 errx(1, "rule passed from the kernel didn't contain subject");
271 /* Replace numerical user and group ids with names. */
272 if (strcasecmp(subject, "user") == 0) {
273 id = strtod(textid, &end);
274 if ((size_t)(end - textid) != strlen(textid))
275 errx(1, "malformed uid '%s'", textid);
278 textid = pwd->pw_name;
279 } else if (strcasecmp(subject, "group") == 0) {
280 id = strtod(textid, &end);
281 if ((size_t)(end - textid) != strlen(textid))
282 errx(1, "malformed gid '%s'", textid);
285 textid = grp->gr_name;
288 ret = asprintf(&humanized, "%s:%s:%s", subject, textid, rest);
296 str2int64(const char *str, int64_t *value)
303 *value = strtoul(str, &end, 10);
304 if ((size_t)(end - str) != strlen(str))
311 humanize_amount(char *rule)
314 const char *subject, *subject_id, *resource, *action, *amount, *per;
315 char *copy, *humanized, buf[6], *tofree;
318 tofree = copy = strdup(rule);
322 subject = strsep(©, ":");
323 subject_id = strsep(©, ":");
324 resource = strsep(©, ":");
325 action = strsep(©, "=/");
326 amount = strsep(©, "/");
329 if (amount == NULL || strlen(amount) == 0 ||
330 str2int64(amount, &num) != 0) {
335 assert(subject != NULL);
336 assert(subject_id != NULL);
337 assert(resource != NULL);
338 assert(action != NULL);
340 if (humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE,
341 HN_DECIMAL | HN_NOSPACE) == -1)
342 err(1, "humanize_number");
345 ret = asprintf(&humanized, "%s:%s:%s:%s=%s",
346 subject, subject_id, resource, action, buf);
348 ret = asprintf(&humanized, "%s:%s:%s:%s=%s/%s",
349 subject, subject_id, resource, action, buf, per);
360 * Print rules, one per line.
363 print_rules(char *rules, int hflag, int nflag)
367 while ((rule = strsep(&rules, ",")) != NULL) {
371 rule = humanize_ids(rule);
373 rule = humanize_amount(rule);
374 printf("%s\n", rule);
381 int error, racct_enable;
382 size_t racct_enable_len;
384 racct_enable_len = sizeof(racct_enable);
385 error = sysctlbyname("kern.racct.enable",
386 &racct_enable, &racct_enable_len, NULL, 0);
390 errx(1, "RACCT/RCTL support not present in kernel; see rctl(8) for details");
392 err(1, "sysctlbyname");
395 if (racct_enable == 0)
396 errx(1, "RACCT/RCTL present, but disabled; enable using kern.racct.enable=1 tunable");
400 add_rule(const char *rule, const char *unexpanded_rule)
404 error = rctl_add_rule(rule, strlen(rule) + 1, NULL, 0);
408 warn("failed to add rule '%s'", unexpanded_rule);
415 show_limits(const char *filter, const char *unexpanded_rule,
416 int hflag, int nflag)
420 size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
424 outbuf = realloc(outbuf, outbuflen);
427 error = rctl_get_limits(filter, strlen(filter) + 1,
435 warn("failed to get limits for '%s'", unexpanded_rule);
441 print_rules(outbuf, hflag, nflag);
448 remove_rule(const char *filter, const char *unexpanded_rule)
452 error = rctl_remove_rule(filter, strlen(filter) + 1, NULL, 0);
456 warn("failed to remove rule '%s'", unexpanded_rule);
463 humanize_usage_amount(char *usage)
466 const char *resource, *amount;
467 char *copy, *humanized, buf[6], *tofree;
470 tofree = copy = strdup(usage);
474 resource = strsep(©, "=");
477 assert(resource != NULL);
478 assert(amount != NULL);
480 if (str2int64(amount, &num) != 0 ||
481 humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE,
482 HN_DECIMAL | HN_NOSPACE) == -1) {
487 ret = asprintf(&humanized, "%s=%s", resource, buf);
496 * Query the kernel about a resource usage and print it out.
499 show_usage(const char *filter, const char *unexpanded_rule, int hflag)
502 char *copy, *outbuf = NULL, *tmp;
503 size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
507 outbuf = realloc(outbuf, outbuflen);
510 error = rctl_get_racct(filter, strlen(filter) + 1,
518 warn("failed to show resource consumption for '%s'",
526 while ((tmp = strsep(©, ",")) != NULL) {
531 tmp = humanize_usage_amount(tmp);
542 * Query the kernel about resource limit rules and print them out.
545 show_rules(const char *filter, const char *unexpanded_rule,
546 int hflag, int nflag)
550 size_t filterlen, outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
553 filterlen = strlen(filter) + 1;
559 outbuf = realloc(outbuf, outbuflen);
562 error = rctl_get_rules(filter, filterlen, outbuf, outbuflen);
569 warn("failed to show rules for '%s'", unexpanded_rule);
575 print_rules(outbuf, hflag, nflag);
585 fprintf(stderr, "usage: rctl [ -h ] [-a rule | -l filter | -r filter "
586 "| -u filter | filter]\n");
591 main(int argc, char **argv)
593 int ch, aflag = 0, hflag = 0, nflag = 0, lflag = 0, rflag = 0,
595 char *rule = NULL, *unexpanded_rule;
596 int i, cumulated_error, error;
598 while ((ch = getopt(argc, argv, "ahlnru")) != -1) {
628 if (aflag + lflag + rflag + uflag > 1)
629 errx(1, "at most one of -a, -l, -r, or -u may be specified");
632 if (aflag + lflag + rflag + uflag == 0) {
634 show_rules(rule, rule, hflag, nflag);
644 for (i = 0; i < argc; i++) {
645 unexpanded_rule = argv[i];
648 * Skip resolving if passed -n _and_ -a. Ignore -n otherwise,
649 * so we can still do "rctl -n u:root" and see the rules without
652 if (aflag != 0 && nflag != 0)
653 rule = expand_rule(unexpanded_rule, false);
655 rule = expand_rule(unexpanded_rule, true);
663 * The reason for passing the unexpanded_rule is to make
664 * it easier for the user to search for the problematic
665 * rule in the passed input.
668 error = add_rule(rule, unexpanded_rule);
670 error = show_limits(rule, unexpanded_rule,
673 error = remove_rule(rule, unexpanded_rule);
675 error = show_usage(rule, unexpanded_rule, hflag);
677 error = show_rules(rule, unexpanded_rule,
687 return (cumulated_error);