1166124Srafan/*- 2166124Srafan * Copyright (c) 2010 The FreeBSD Foundation 3166124Srafan * All rights reserved. 4166124Srafan * 5166124Srafan * This software was developed by Edward Tomasz Napierala under sponsorship 6166124Srafan * from the FreeBSD Foundation. 7166124Srafan * 8166124Srafan * Redistribution and use in source and binary forms, with or without 9166124Srafan * modification, are permitted provided that the following conditions 10166124Srafan * are met: 11166124Srafan * 1. Redistributions of source code must retain the above copyright 12166124Srafan * notice, this list of conditions and the following disclaimer. 13166124Srafan * 2. Redistributions in binary form must reproduce the above copyright 14166124Srafan * notice, this list of conditions and the following disclaimer in the 15166124Srafan * documentation and/or other materials provided with the distribution. 16166124Srafan * 17166124Srafan * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18166124Srafan * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19166124Srafan * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20166124Srafan * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21166124Srafan * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22166124Srafan * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23166124Srafan * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24166124Srafan * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25166124Srafan * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26166124Srafan * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27166124Srafan * SUCH DAMAGE. 28166124Srafan * 29166124Srafan * $FreeBSD$ 30166124Srafan */ 31166124Srafan 32166124Srafan#include <sys/cdefs.h> 33166124Srafan__FBSDID("$FreeBSD$"); 34166124Srafan 35166124Srafan#include <sys/types.h> 36166124Srafan#include <sys/rctl.h> 37166124Srafan#include <assert.h> 38166124Srafan#include <ctype.h> 39166124Srafan#include <err.h> 40166124Srafan#include <errno.h> 41166124Srafan#include <getopt.h> 42166124Srafan#include <grp.h> 43166124Srafan#include <libutil.h> 44166124Srafan#include <pwd.h> 45166124Srafan#include <stdint.h> 46166124Srafan#include <stdio.h> 47166124Srafan#include <stdlib.h> 48166124Srafan#include <string.h> 49166124Srafan 50166124Srafan#define RCTL_DEFAULT_BUFSIZE 4096 51166124Srafan 52166124Srafanstatic id_t 53166124Srafanparse_user(const char *s) 54166124Srafan{ 55166124Srafan id_t id; 56166124Srafan char *end; 57166124Srafan struct passwd *pwd; 58166124Srafan 59166124Srafan pwd = getpwnam(s); 60166124Srafan if (pwd != NULL) 61166124Srafan return (pwd->pw_uid); 62166124Srafan 63166124Srafan if (!isnumber(s[0])) 64166124Srafan errx(1, "uknown user '%s'", s); 65166124Srafan 66166124Srafan id = strtod(s, &end); 67166124Srafan if ((size_t)(end - s) != strlen(s)) 68166124Srafan errx(1, "trailing characters after numerical id"); 69166124Srafan 70166124Srafan return (id); 71166124Srafan} 72166124Srafan 73166124Srafanstatic id_t 74166124Srafanparse_group(const char *s) 75166124Srafan{ 76166124Srafan id_t id; 77166124Srafan char *end; 78166124Srafan struct group *grp; 79166124Srafan 80166124Srafan grp = getgrnam(s); 81166124Srafan if (grp != NULL) 82166124Srafan return (grp->gr_gid); 83166124Srafan 84166124Srafan if (!isnumber(s[0])) 85166124Srafan errx(1, "uknown group '%s'", s); 86166124Srafan 87166124Srafan id = strtod(s, &end); 88166124Srafan if ((size_t)(end - s) != strlen(s)) 89166124Srafan errx(1, "trailing characters after numerical id"); 90166124Srafan 91166124Srafan return (id); 92166124Srafan} 93166124Srafan 94166124Srafan/* 95166124Srafan * This routine replaces user/group name with numeric id. 96166124Srafan */ 97166124Srafanstatic char * 98166124Srafanresolve_ids(char *rule) 99166124Srafan{ 100166124Srafan id_t id; 101166124Srafan const char *subject, *textid, *rest; 102166124Srafan char *resolved; 103166124Srafan 104166124Srafan subject = strsep(&rule, ":"); 105166124Srafan textid = strsep(&rule, ":"); 106166124Srafan if (textid == NULL) 107166124Srafan errx(1, "error in rule specification -- no subject"); 108166124Srafan if (rule != NULL) 109166124Srafan rest = rule; 110166124Srafan else 111166124Srafan rest = ""; 112166124Srafan 113166124Srafan if (strcasecmp(subject, "u") == 0) 114166124Srafan subject = "user"; 115166124Srafan else if (strcasecmp(subject, "g") == 0) 116166124Srafan subject = "group"; 117166124Srafan else if (strcasecmp(subject, "p") == 0) 118166124Srafan subject = "process"; 119166124Srafan else if (strcasecmp(subject, "l") == 0 || 120166124Srafan strcasecmp(subject, "c") == 0 || 121166124Srafan strcasecmp(subject, "class") == 0) 122166124Srafan subject = "loginclass"; 123166124Srafan else if (strcasecmp(subject, "j") == 0) 124166124Srafan subject = "jail"; 125166124Srafan 126166124Srafan if (strcasecmp(subject, "user") == 0 && strlen(textid) > 0) { 127166124Srafan id = parse_user(textid); 128166124Srafan asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); 129166124Srafan } else if (strcasecmp(subject, "group") == 0 && strlen(textid) > 0) { 130166124Srafan id = parse_group(textid); 131166124Srafan asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); 132166124Srafan } else 133166124Srafan asprintf(&resolved, "%s:%s:%s", subject, textid, rest); 134166124Srafan 135166124Srafan if (resolved == NULL) 136166124Srafan err(1, "asprintf"); 137166124Srafan 138166124Srafan return (resolved); 139166124Srafan} 140166124Srafan 141166124Srafan/* 142166124Srafan * This routine replaces "human-readable" number with its expanded form. 143166124Srafan */ 144166124Srafanstatic char * 145166124Srafanexpand_amount(char *rule) 146166124Srafan{ 147166124Srafan uint64_t num; 148166124Srafan const char *subject, *subject_id, *resource, *action, *amount, *per; 149166124Srafan char *copy, *expanded; 150166124Srafan 151166124Srafan copy = strdup(rule); 152166124Srafan if (copy == NULL) 153166124Srafan err(1, "strdup"); 154166124Srafan 155166124Srafan subject = strsep(©, ":"); 156166124Srafan subject_id = strsep(©, ":"); 157166124Srafan resource = strsep(©, ":"); 158166124Srafan action = strsep(©, "=/"); 159166124Srafan amount = strsep(©, "/"); 160166124Srafan per = copy; 161166124Srafan 162166124Srafan if (amount == NULL || strlen(amount) == 0) { 163166124Srafan free(copy); 164166124Srafan return (rule); 165166124Srafan } 166166124Srafan 167166124Srafan assert(subject != NULL); 168166124Srafan assert(subject_id != NULL); 169166124Srafan assert(resource != NULL); 170166124Srafan assert(action != NULL); 171166124Srafan 172166124Srafan if (expand_number(amount, &num)) 173166124Srafan err(1, "expand_number"); 174166124Srafan 175166124Srafan if (per == NULL) 176166124Srafan asprintf(&expanded, "%s:%s:%s:%s=%ju", subject, subject_id, 177166124Srafan resource, action, (uintmax_t)num); 178166124Srafan else 179166124Srafan asprintf(&expanded, "%s:%s:%s:%s=%ju/%s", subject, subject_id, 180166124Srafan resource, action, (uintmax_t)num, per); 181166124Srafan 182166124Srafan if (expanded == NULL) 183166124Srafan err(1, "asprintf"); 184166124Srafan 185166124Srafan return (expanded); 186166124Srafan} 187166124Srafan 188166124Srafanstatic char * 189166124Srafanhumanize_ids(char *rule) 190166124Srafan{ 191166124Srafan id_t id; 192166124Srafan struct passwd *pwd; 193166124Srafan struct group *grp; 194166124Srafan const char *subject, *textid, *rest; 195166124Srafan char *humanized; 196166124Srafan 197166124Srafan subject = strsep(&rule, ":"); 198166124Srafan textid = strsep(&rule, ":"); 199166124Srafan if (textid == NULL) 200166124Srafan errx(1, "rule passed from the kernel didn't contain subject"); 201166124Srafan if (rule != NULL) 202166124Srafan rest = rule; 203166124Srafan else 204166124Srafan rest = ""; 205166124Srafan 206166124Srafan /* Replace numerical user and group ids with names. */ 207166124Srafan if (strcasecmp(subject, "user") == 0) { 208166124Srafan id = parse_user(textid); 209166124Srafan pwd = getpwuid(id); 210166124Srafan if (pwd != NULL) 211166124Srafan textid = pwd->pw_name; 212166124Srafan } else if (strcasecmp(subject, "group") == 0) { 213166124Srafan id = parse_group(textid); 214166124Srafan grp = getgrgid(id); 215166124Srafan if (grp != NULL) 216166124Srafan textid = grp->gr_name; 217166124Srafan } 218166124Srafan 219166124Srafan asprintf(&humanized, "%s:%s:%s", subject, textid, rest); 220166124Srafan 221166124Srafan if (humanized == NULL) 222166124Srafan err(1, "asprintf"); 223166124Srafan 224166124Srafan return (humanized); 225166124Srafan} 226166124Srafan 227166124Srafanstatic int 228166124Srafanstr2int64(const char *str, int64_t *value) 229166124Srafan{ 230166124Srafan char *end; 231166124Srafan 232166124Srafan if (str == NULL) 233166124Srafan return (EINVAL); 234166124Srafan 235166124Srafan *value = strtoul(str, &end, 10); 236166124Srafan if ((size_t)(end - str) != strlen(str)) 237166124Srafan return (EINVAL); 238166124Srafan 239166124Srafan return (0); 240166124Srafan} 241166124Srafan 242166124Srafanstatic char * 243166124Srafanhumanize_amount(char *rule) 244166124Srafan{ 245166124Srafan int64_t num; 246166124Srafan const char *subject, *subject_id, *resource, *action, *amount, *per; 247166124Srafan char *copy, *humanized, buf[6]; 248166124Srafan 249166124Srafan copy = strdup(rule); 250166124Srafan if (copy == NULL) 251166124Srafan err(1, "strdup"); 252166124Srafan 253166124Srafan subject = strsep(©, ":"); 254166124Srafan subject_id = strsep(©, ":"); 255166124Srafan resource = strsep(©, ":"); 256166124Srafan action = strsep(©, "=/"); 257166124Srafan amount = strsep(©, "/"); 258166124Srafan per = copy; 259166124Srafan 260166124Srafan if (amount == NULL || strlen(amount) == 0 || 261166124Srafan str2int64(amount, &num) != 0) { 262166124Srafan free(copy); 263166124Srafan return (rule); 264166124Srafan } 265166124Srafan 266166124Srafan assert(subject != NULL); 267166124Srafan assert(subject_id != NULL); 268166124Srafan assert(resource != NULL); 269166124Srafan assert(action != NULL); 270166124Srafan 271166124Srafan if (humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE, 272166124Srafan HN_DECIMAL | HN_NOSPACE) == -1) 273166124Srafan err(1, "humanize_number"); 274166124Srafan 275166124Srafan if (per == NULL) 276166124Srafan asprintf(&humanized, "%s:%s:%s:%s=%s", subject, subject_id, 277166124Srafan resource, action, buf); 278166124Srafan else 279166124Srafan asprintf(&humanized, "%s:%s:%s:%s=%s/%s", subject, subject_id, 280166124Srafan resource, action, buf, per); 281166124Srafan 282166124Srafan if (humanized == NULL) 283166124Srafan err(1, "asprintf"); 284166124Srafan 285166124Srafan return (humanized); 286166124Srafan} 287166124Srafan 288166124Srafan/* 289166124Srafan * Print rules, one per line. 290166124Srafan */ 291166124Srafanstatic void 292166124Srafanprint_rules(char *rules, int hflag, int nflag) 293166124Srafan{ 294166124Srafan char *rule; 295166124Srafan 296166124Srafan while ((rule = strsep(&rules, ",")) != NULL) { 297166124Srafan if (rule[0] == '\0') 298166124Srafan break; /* XXX */ 299166124Srafan if (nflag == 0) 300166124Srafan rule = humanize_ids(rule); 301166124Srafan if (hflag) 302166124Srafan rule = humanize_amount(rule); 303166124Srafan printf("%s\n", rule); 304166124Srafan } 305166124Srafan} 306166124Srafan 307166124Srafanstatic void 308166124Srafanadd_rule(char *rule) 309166124Srafan{ 310166124Srafan int error; 311166124Srafan 312166124Srafan error = rctl_add_rule(rule, strlen(rule) + 1, NULL, 0); 313166124Srafan if (error != 0) 314166124Srafan err(1, "rctl_add_rule"); 315166124Srafan free(rule); 316166124Srafan} 317166124Srafan 318166124Srafanstatic void 319166124Srafanshow_limits(char *filter, int hflag, int nflag) 320166124Srafan{ 321166124Srafan int error; 322166124Srafan char *outbuf = NULL; 323166124Srafan size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4; 324166124Srafan 325166124Srafan do { 326166124Srafan outbuflen *= 4; 327166124Srafan outbuf = realloc(outbuf, outbuflen); 328166124Srafan if (outbuf == NULL) 329166124Srafan err(1, "realloc"); 330166124Srafan 331166124Srafan error = rctl_get_limits(filter, strlen(filter) + 1, outbuf, 332166124Srafan outbuflen); 333166124Srafan if (error && errno != ERANGE) 334166124Srafan err(1, "rctl_get_limits"); 335166124Srafan } while (error && errno == ERANGE); 336166124Srafan 337166124Srafan print_rules(outbuf, hflag, nflag); 338166124Srafan free(filter); 339166124Srafan free(outbuf); 340166124Srafan} 341166124Srafan 342166124Srafanstatic void 343166124Srafanremove_rule(char *filter) 344166124Srafan{ 345166124Srafan int error; 346166124Srafan 347166124Srafan error = rctl_remove_rule(filter, strlen(filter) + 1, NULL, 0); 348166124Srafan if (error != 0) 349166124Srafan err(1, "rctl_remove_rule"); 350166124Srafan free(filter); 351166124Srafan} 352166124Srafan 353166124Srafanstatic char * 354166124Srafanhumanize_usage_amount(char *usage) 355166124Srafan{ 356166124Srafan int64_t num; 357166124Srafan const char *resource, *amount; 358166124Srafan char *copy, *humanized, buf[6]; 359166124Srafan 360166124Srafan copy = strdup(usage); 361166124Srafan if (copy == NULL) 362166124Srafan err(1, "strdup"); 363166124Srafan 364166124Srafan resource = strsep(©, "="); 365166124Srafan amount = copy; 366166124Srafan 367166124Srafan assert(resource != NULL); 368166124Srafan assert(amount != NULL); 369166124Srafan 370166124Srafan if (str2int64(amount, &num) != 0 || 371166124Srafan humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE, 372166124Srafan HN_DECIMAL | HN_NOSPACE) == -1) { 373166124Srafan free(copy); 374166124Srafan return (usage); 375166124Srafan } 376166124Srafan 377166124Srafan asprintf(&humanized, "%s=%s", resource, buf); 378166124Srafan if (humanized == NULL) 379166124Srafan err(1, "asprintf"); 380166124Srafan 381166124Srafan return (humanized); 382166124Srafan} 383166124Srafan 384166124Srafan/* 385166124Srafan * Query the kernel about a resource usage and print it out. 386166124Srafan */ 387166124Srafanstatic void 388166124Srafanshow_usage(char *filter, int hflag) 389166124Srafan{ 390166124Srafan int error; 391166124Srafan char *outbuf = NULL, *tmp; 392166124Srafan size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4; 393166124Srafan 394166124Srafan do { 395166124Srafan outbuflen *= 4; 396166124Srafan outbuf = realloc(outbuf, outbuflen); 397166124Srafan if (outbuf == NULL) 398166124Srafan err(1, "realloc"); 399166124Srafan 400166124Srafan error = rctl_get_racct(filter, strlen(filter) + 1, outbuf, 401166124Srafan outbuflen); 402166124Srafan if (error && errno != ERANGE) 403166124Srafan err(1, "rctl_get_racct"); 404166124Srafan } while (error && errno == ERANGE); 405166124Srafan 406166124Srafan while ((tmp = strsep(&outbuf, ",")) != NULL) { 407166124Srafan if (tmp[0] == '\0') 408166124Srafan break; /* XXX */ 409166124Srafan 410166124Srafan if (hflag) 411166124Srafan tmp = humanize_usage_amount(tmp); 412166124Srafan 413166124Srafan printf("%s\n", tmp); 414166124Srafan } 415166124Srafan 416166124Srafan free(filter); 417166124Srafan free(outbuf); 418166124Srafan} 419166124Srafan 420166124Srafan/* 421166124Srafan * Query the kernel about resource limit rules and print them out. 422166124Srafan */ 423166124Srafanstatic void 424166124Srafanshow_rules(char *filter, int hflag, int nflag) 425166124Srafan{ 426166124Srafan int error; 427166124Srafan char *outbuf = NULL; 428166124Srafan size_t filterlen, outbuflen = RCTL_DEFAULT_BUFSIZE / 4; 429166124Srafan 430166124Srafan if (filter != NULL) 431166124Srafan filterlen = strlen(filter) + 1; 432166124Srafan else 433166124Srafan filterlen = 0; 434166124Srafan 435166124Srafan do { 436166124Srafan outbuflen *= 4; 437166124Srafan outbuf = realloc(outbuf, outbuflen); 438166124Srafan if (outbuf == NULL) 439166124Srafan err(1, "realloc"); 440166124Srafan 441166124Srafan error = rctl_get_rules(filter, filterlen, outbuf, outbuflen); 442166124Srafan if (error && errno != ERANGE) 443166124Srafan err(1, "rctl_get_rules"); 444166124Srafan } while (error && errno == ERANGE); 445166124Srafan 446166124Srafan print_rules(outbuf, hflag, nflag); 447166124Srafan free(outbuf); 448166124Srafan} 449166124Srafan 450166124Srafanstatic void 451166124Srafanusage(void) 452166124Srafan{ 453166124Srafan 454166124Srafan fprintf(stderr, "usage: rctl [ -h ] [-a rule | -l filter | -r filter " 455166124Srafan "| -u filter | filter]\n"); 456166124Srafan exit(1); 457166124Srafan} 458166124Srafan 459166124Srafanint 460166124Srafanmain(int argc __unused, char **argv __unused) 461166124Srafan{ 462166124Srafan int ch, aflag = 0, hflag = 0, nflag = 0, lflag = 0, rflag = 0, 463166124Srafan uflag = 0; 464166124Srafan char *rule = NULL; 465166124Srafan 466166124Srafan while ((ch = getopt(argc, argv, "a:hl:nr:u:")) != -1) { 467166124Srafan switch (ch) { 468166124Srafan case 'a': 469166124Srafan aflag = 1; 470166124Srafan rule = strdup(optarg); 471166124Srafan break; 472166124Srafan case 'h': 473166124Srafan hflag = 1; 474166124Srafan break; 475166124Srafan case 'l': 476166124Srafan lflag = 1; 477166124Srafan rule = strdup(optarg); 478166124Srafan break; 479166124Srafan case 'n': 480166124Srafan nflag = 1; 481166124Srafan break; 482166124Srafan case 'r': 483166124Srafan rflag = 1; 484166124Srafan rule = strdup(optarg); 485166124Srafan break; 486166124Srafan case 'u': 487166124Srafan uflag = 1; 488166124Srafan rule = strdup(optarg); 489166124Srafan break; 490166124Srafan 491166124Srafan case '?': 492166124Srafan default: 493166124Srafan usage(); 494166124Srafan } 495166124Srafan } 496166124Srafan 497166124Srafan argc -= optind; 498166124Srafan argv += optind; 499166124Srafan 500166124Srafan if (argc > 1) 501166124Srafan usage(); 502166124Srafan 503166124Srafan if (rule == NULL) { 504166124Srafan if (argc == 1) 505166124Srafan rule = strdup(argv[0]); 506166124Srafan else 507166124Srafan rule = strdup("::"); 508166124Srafan } 509166124Srafan 510166124Srafan if (aflag + lflag + rflag + uflag + argc > 1) 511166124Srafan errx(1, "only one flag or argument may be specified " 512166124Srafan "at the same time"); 513166124Srafan 514166124Srafan rule = resolve_ids(rule); 515166124Srafan rule = expand_amount(rule); 516166124Srafan 517166124Srafan if (aflag) { 518166124Srafan add_rule(rule); 519166124Srafan return (0); 520166124Srafan } 521166124Srafan 522166124Srafan if (lflag) { 523166124Srafan show_limits(rule, hflag, nflag); 524166124Srafan return (0); 525166124Srafan } 526166124Srafan 527166124Srafan if (rflag) { 528166124Srafan remove_rule(rule); 529166124Srafan return (0); 530166124Srafan } 531166124Srafan 532166124Srafan if (uflag) { 533166124Srafan show_usage(rule, hflag); 534166124Srafan return (0); 535166124Srafan } 536166124Srafan 537166124Srafan show_rules(rule, hflag, nflag); 538166124Srafan return (0); 539166124Srafan} 540166124Srafan