/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2010 The FreeBSD Foundation * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define RCTL_DEFAULT_BUFSIZE 128 * 1024 static int parse_user(const char *s, id_t *uidp, const char *unexpanded_rule) { char *end; struct passwd *pwd; pwd = getpwnam(s); if (pwd != NULL) { *uidp = pwd->pw_uid; return (0); } if (!isnumber(s[0])) { warnx("malformed rule '%s': unknown user '%s'", unexpanded_rule, s); return (1); } *uidp = strtod(s, &end); if ((size_t)(end - s) != strlen(s)) { warnx("malformed rule '%s': trailing characters " "after numerical id", unexpanded_rule); return (1); } return (0); } static int parse_group(const char *s, id_t *gidp, const char *unexpanded_rule) { char *end; struct group *grp; grp = getgrnam(s); if (grp != NULL) { *gidp = grp->gr_gid; return (0); } if (!isnumber(s[0])) { warnx("malformed rule '%s': unknown group '%s'", unexpanded_rule, s); return (1); } *gidp = strtod(s, &end); if ((size_t)(end - s) != strlen(s)) { warnx("malformed rule '%s': trailing characters " "after numerical id", unexpanded_rule); return (1); } return (0); } /* * Replace human-readable number with its expanded form. */ static char * expand_amount(const char *rule, const char *unexpanded_rule) { uint64_t num; const char *subject, *subject_id, *resource, *action, *amount, *per; char *copy, *expanded, *tofree; int ret; tofree = copy = strdup(rule); if (copy == NULL) { warn("strdup"); return (NULL); } subject = strsep(©, ":"); subject_id = strsep(©, ":"); resource = strsep(©, ":"); action = strsep(©, "=/"); amount = strsep(©, "/"); per = copy; if (amount == NULL || strlen(amount) == 0) { /* * The "copy" has already been tinkered with by strsep(). */ free(tofree); copy = strdup(rule); if (copy == NULL) { warn("strdup"); return (NULL); } return (copy); } assert(subject != NULL); assert(subject_id != NULL); assert(resource != NULL); assert(action != NULL); if (expand_number(amount, &num)) { warnx("malformed rule '%s': invalid numeric value '%s'", unexpanded_rule, amount); free(tofree); return (NULL); } if (per == NULL) { ret = asprintf(&expanded, "%s:%s:%s:%s=%ju", subject, subject_id, resource, action, (uintmax_t)num); } else { ret = asprintf(&expanded, "%s:%s:%s:%s=%ju/%s", subject, subject_id, resource, action, (uintmax_t)num, per); } if (ret <= 0) { warn("asprintf"); free(tofree); return (NULL); } free(tofree); return (expanded); } static char * expand_rule(const char *rule, bool resolve_ids) { id_t id; const char *subject, *textid, *rest; char *copy, *expanded, *resolved, *tofree; int error, ret; tofree = copy = strdup(rule); if (copy == NULL) { warn("strdup"); return (NULL); } subject = strsep(©, ":"); textid = strsep(©, ":"); if (textid == NULL) { warnx("malformed rule '%s': missing subject", rule); return (NULL); } if (copy != NULL) rest = copy; 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 (resolve_ids && strcasecmp(subject, "user") == 0 && strlen(textid) > 0) { error = parse_user(textid, &id, rule); if (error != 0) { free(tofree); return (NULL); } ret = asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); } else if (resolve_ids && strcasecmp(subject, "group") == 0 && strlen(textid) > 0) { error = parse_group(textid, &id, rule); if (error != 0) { free(tofree); return (NULL); } ret = asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); } else { ret = asprintf(&resolved, "%s:%s:%s", subject, textid, rest); } if (ret <= 0) { warn("asprintf"); free(tofree); return (NULL); } free(tofree); expanded = expand_amount(resolved, rule); free(resolved); return (expanded); } static char * humanize_ids(char *rule) { id_t id; struct passwd *pwd; struct group *grp; const char *subject, *textid, *rest; char *end, *humanized; int ret; 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 = strtod(textid, &end); if ((size_t)(end - textid) != strlen(textid)) errx(1, "malformed uid '%s'", textid); pwd = getpwuid(id); if (pwd != NULL) textid = pwd->pw_name; } else if (strcasecmp(subject, "group") == 0) { id = strtod(textid, &end); if ((size_t)(end - textid) != strlen(textid)) errx(1, "malformed gid '%s'", textid); grp = getgrgid(id); if (grp != NULL) textid = grp->gr_name; } ret = asprintf(&humanized, "%s:%s:%s", subject, textid, rest); if (ret <= 0) 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], *tofree; int ret; tofree = 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(tofree); 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) { ret = asprintf(&humanized, "%s:%s:%s:%s=%s", subject, subject_id, resource, action, buf); } else { ret = asprintf(&humanized, "%s:%s:%s:%s=%s/%s", subject, subject_id, resource, action, buf, per); } if (ret <= 0) err(1, "asprintf"); free(tofree); 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 enosys(void) { size_t racct_enable_len; int error; bool racct_enable; racct_enable_len = sizeof(racct_enable); error = sysctlbyname("kern.racct.enable", &racct_enable, &racct_enable_len, NULL, 0); if (error != 0) { if (errno == ENOENT) errx(1, "RACCT/RCTL support not present in kernel; see rctl(8) for details"); err(1, "sysctlbyname"); } if (!racct_enable) errx(1, "RACCT/RCTL present, but disabled; enable using kern.racct.enable=1 tunable"); } static int add_rule(const char *rule, const char *unexpanded_rule) { int error; error = rctl_add_rule(rule, strlen(rule) + 1, NULL, 0); if (error != 0) { if (errno == ENOSYS) enosys(); warn("failed to add rule '%s'", unexpanded_rule); } return (error); } static int show_limits(const char *filter, const char *unexpanded_rule, int hflag, int nflag) { int error; char *outbuf = NULL; size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4; for (;;) { outbuflen *= 4; outbuf = realloc(outbuf, outbuflen); if (outbuf == NULL) err(1, "realloc"); error = rctl_get_limits(filter, strlen(filter) + 1, outbuf, outbuflen); if (error == 0) break; if (errno == ERANGE) continue; if (errno == ENOSYS) enosys(); warn("failed to get limits for '%s'", unexpanded_rule); free(outbuf); return (error); } print_rules(outbuf, hflag, nflag); free(outbuf); return (error); } static int remove_rule(const char *filter, const char *unexpanded_rule) { int error; error = rctl_remove_rule(filter, strlen(filter) + 1, NULL, 0); if (error != 0) { if (errno == ENOSYS) enosys(); warn("failed to remove rule '%s'", unexpanded_rule); } return (error); } static char * humanize_usage_amount(char *usage) { int64_t num; const char *resource, *amount; char *copy, *humanized, buf[6], *tofree; int ret; tofree = 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(tofree); return (usage); } ret = asprintf(&humanized, "%s=%s", resource, buf); if (ret <= 0) err(1, "asprintf"); free(tofree); return (humanized); } /* * Query the kernel about a resource usage and print it out. */ static int show_usage(const char *filter, const char *unexpanded_rule, int hflag) { int error; char *copy, *outbuf = NULL, *tmp; size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4; for (;;) { outbuflen *= 4; outbuf = realloc(outbuf, outbuflen); if (outbuf == NULL) err(1, "realloc"); error = rctl_get_racct(filter, strlen(filter) + 1, outbuf, outbuflen); if (error == 0) break; if (errno == ERANGE) continue; if (errno == ENOSYS) enosys(); warn("failed to show resource consumption for '%s'", unexpanded_rule); free(outbuf); return (error); } copy = outbuf; while ((tmp = strsep(©, ",")) != NULL) { if (tmp[0] == '\0') break; /* XXX */ if (hflag) tmp = humanize_usage_amount(tmp); printf("%s\n", tmp); } free(outbuf); return (error); } /* * Query the kernel about resource limit rules and print them out. */ static int show_rules(const char *filter, const char *unexpanded_rule, 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; for (;;) { outbuflen *= 4; outbuf = realloc(outbuf, outbuflen); if (outbuf == NULL) err(1, "realloc"); error = rctl_get_rules(filter, filterlen, outbuf, outbuflen); if (error == 0) break; if (errno == ERANGE) continue; if (errno == ENOSYS) enosys(); warn("failed to show rules for '%s'", unexpanded_rule); free(outbuf); return (error); } print_rules(outbuf, hflag, nflag); free(outbuf); return (error); } 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, char **argv) { int ch, aflag = 0, hflag = 0, nflag = 0, lflag = 0, rflag = 0, uflag = 0; char *rule = NULL, *unexpanded_rule; int i, cumulated_error, error; while ((ch = getopt(argc, argv, "ahlnru")) != -1) { switch (ch) { case 'a': aflag = 1; break; case 'h': hflag = 1; break; case 'l': lflag = 1; break; case 'n': nflag = 1; break; case 'r': rflag = 1; break; case 'u': uflag = 1; break; case '?': default: usage(); } } argc -= optind; argv += optind; if (aflag + lflag + rflag + uflag > 1) errx(1, "at most one of -a, -l, -r, or -u may be specified"); if (argc == 0) { if (aflag + lflag + rflag + uflag == 0) { rule = strdup("::"); show_rules(rule, rule, hflag, nflag); return (0); } usage(); } cumulated_error = 0; for (i = 0; i < argc; i++) { unexpanded_rule = argv[i]; /* * Skip resolving if passed -n _and_ -a. Ignore -n otherwise, * so we can still do "rctl -n u:root" and see the rules without * resolving the UID. */ if (aflag != 0 && nflag != 0) rule = expand_rule(unexpanded_rule, false); else rule = expand_rule(unexpanded_rule, true); if (rule == NULL) { cumulated_error++; continue; } /* * The reason for passing the unexpanded_rule is to make * it easier for the user to search for the problematic * rule in the passed input. */ if (aflag) { error = add_rule(rule, unexpanded_rule); } else if (lflag) { error = show_limits(rule, unexpanded_rule, hflag, nflag); } else if (rflag) { error = remove_rule(rule, unexpanded_rule); } else if (uflag) { error = show_usage(rule, unexpanded_rule, hflag); } else { error = show_rules(rule, unexpanded_rule, hflag, nflag); } if (error != 0) cumulated_error++; free(rule); } return (cumulated_error); }