1220166Strasz/*- 2220166Strasz * Copyright (c) 2010 The FreeBSD Foundation 3220166Strasz * All rights reserved. 4220166Strasz * 5220166Strasz * This software was developed by Edward Tomasz Napierala under sponsorship 6220166Strasz * from the FreeBSD Foundation. 7220166Strasz * 8220166Strasz * Redistribution and use in source and binary forms, with or without 9220166Strasz * modification, are permitted provided that the following conditions 10220166Strasz * are met: 11220166Strasz * 1. Redistributions of source code must retain the above copyright 12220166Strasz * notice, this list of conditions and the following disclaimer. 13220166Strasz * 2. Redistributions in binary form must reproduce the above copyright 14220166Strasz * notice, this list of conditions and the following disclaimer in the 15220166Strasz * documentation and/or other materials provided with the distribution. 16220166Strasz * 17220166Strasz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18220166Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19220166Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20220166Strasz * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21220166Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22220166Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23220166Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24220166Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25220166Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26220166Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27220166Strasz * SUCH DAMAGE. 28220166Strasz * 29220166Strasz * $FreeBSD: stable/10/usr.bin/rctl/rctl.c 325847 2017-11-15 12:21:06Z bapt $ 30220166Strasz */ 31220166Strasz 32220166Strasz#include <sys/cdefs.h> 33220166Strasz__FBSDID("$FreeBSD: stable/10/usr.bin/rctl/rctl.c 325847 2017-11-15 12:21:06Z bapt $"); 34220166Strasz 35220166Strasz#include <sys/types.h> 36220166Strasz#include <sys/rctl.h> 37284667Strasz#include <sys/sysctl.h> 38220166Strasz#include <assert.h> 39220166Strasz#include <ctype.h> 40220166Strasz#include <err.h> 41220166Strasz#include <errno.h> 42220166Strasz#include <getopt.h> 43220166Strasz#include <grp.h> 44220166Strasz#include <libutil.h> 45220166Strasz#include <pwd.h> 46220166Strasz#include <stdint.h> 47220166Strasz#include <stdio.h> 48220166Strasz#include <stdlib.h> 49220166Strasz#include <string.h> 50220166Strasz 51293686Strasz#define RCTL_DEFAULT_BUFSIZE 128 * 1024 52220166Strasz 53220166Straszstatic id_t 54220166Straszparse_user(const char *s) 55220166Strasz{ 56220166Strasz id_t id; 57220166Strasz char *end; 58220166Strasz struct passwd *pwd; 59220166Strasz 60220166Strasz pwd = getpwnam(s); 61220166Strasz if (pwd != NULL) 62220166Strasz return (pwd->pw_uid); 63220166Strasz 64220166Strasz if (!isnumber(s[0])) 65220166Strasz errx(1, "uknown user '%s'", s); 66220166Strasz 67220166Strasz id = strtod(s, &end); 68220166Strasz if ((size_t)(end - s) != strlen(s)) 69220166Strasz errx(1, "trailing characters after numerical id"); 70220166Strasz 71220166Strasz return (id); 72220166Strasz} 73220166Strasz 74220166Straszstatic id_t 75220166Straszparse_group(const char *s) 76220166Strasz{ 77220166Strasz id_t id; 78220166Strasz char *end; 79220166Strasz struct group *grp; 80220166Strasz 81220166Strasz grp = getgrnam(s); 82220166Strasz if (grp != NULL) 83220166Strasz return (grp->gr_gid); 84220166Strasz 85220166Strasz if (!isnumber(s[0])) 86220166Strasz errx(1, "uknown group '%s'", s); 87220166Strasz 88220166Strasz id = strtod(s, &end); 89220166Strasz if ((size_t)(end - s) != strlen(s)) 90220166Strasz errx(1, "trailing characters after numerical id"); 91220166Strasz 92220166Strasz return (id); 93220166Strasz} 94220166Strasz 95220166Strasz/* 96220166Strasz * This routine replaces user/group name with numeric id. 97220166Strasz */ 98220166Straszstatic char * 99220166Straszresolve_ids(char *rule) 100220166Strasz{ 101220166Strasz id_t id; 102220166Strasz const char *subject, *textid, *rest; 103220166Strasz char *resolved; 104220166Strasz 105220166Strasz subject = strsep(&rule, ":"); 106220166Strasz textid = strsep(&rule, ":"); 107220166Strasz if (textid == NULL) 108220166Strasz errx(1, "error in rule specification -- no subject"); 109220166Strasz if (rule != NULL) 110220166Strasz rest = rule; 111220166Strasz else 112220166Strasz rest = ""; 113220166Strasz 114220166Strasz if (strcasecmp(subject, "u") == 0) 115220166Strasz subject = "user"; 116220166Strasz else if (strcasecmp(subject, "g") == 0) 117220166Strasz subject = "group"; 118220166Strasz else if (strcasecmp(subject, "p") == 0) 119220166Strasz subject = "process"; 120220166Strasz else if (strcasecmp(subject, "l") == 0 || 121220166Strasz strcasecmp(subject, "c") == 0 || 122220166Strasz strcasecmp(subject, "class") == 0) 123220166Strasz subject = "loginclass"; 124220166Strasz else if (strcasecmp(subject, "j") == 0) 125220166Strasz subject = "jail"; 126220166Strasz 127220166Strasz if (strcasecmp(subject, "user") == 0 && strlen(textid) > 0) { 128220166Strasz id = parse_user(textid); 129220166Strasz asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); 130220166Strasz } else if (strcasecmp(subject, "group") == 0 && strlen(textid) > 0) { 131220166Strasz id = parse_group(textid); 132220166Strasz asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); 133220166Strasz } else 134220166Strasz asprintf(&resolved, "%s:%s:%s", subject, textid, rest); 135220166Strasz 136220166Strasz if (resolved == NULL) 137220166Strasz err(1, "asprintf"); 138220166Strasz 139220166Strasz return (resolved); 140220166Strasz} 141220166Strasz 142220166Strasz/* 143220166Strasz * This routine replaces "human-readable" number with its expanded form. 144220166Strasz */ 145220166Straszstatic char * 146220166Straszexpand_amount(char *rule) 147220166Strasz{ 148220166Strasz uint64_t num; 149220166Strasz const char *subject, *subject_id, *resource, *action, *amount, *per; 150220166Strasz char *copy, *expanded; 151220166Strasz 152220166Strasz copy = strdup(rule); 153220166Strasz if (copy == NULL) 154220166Strasz err(1, "strdup"); 155220166Strasz 156220166Strasz subject = strsep(©, ":"); 157220166Strasz subject_id = strsep(©, ":"); 158220166Strasz resource = strsep(©, ":"); 159220166Strasz action = strsep(©, "=/"); 160220166Strasz amount = strsep(©, "/"); 161220166Strasz per = copy; 162220166Strasz 163220166Strasz if (amount == NULL || strlen(amount) == 0) { 164220166Strasz free(copy); 165220166Strasz return (rule); 166220166Strasz } 167220166Strasz 168220166Strasz assert(subject != NULL); 169220166Strasz assert(subject_id != NULL); 170220166Strasz assert(resource != NULL); 171220166Strasz assert(action != NULL); 172220166Strasz 173220166Strasz if (expand_number(amount, &num)) 174220166Strasz err(1, "expand_number"); 175220166Strasz 176220166Strasz if (per == NULL) 177220166Strasz asprintf(&expanded, "%s:%s:%s:%s=%ju", subject, subject_id, 178220166Strasz resource, action, (uintmax_t)num); 179220166Strasz else 180220166Strasz asprintf(&expanded, "%s:%s:%s:%s=%ju/%s", subject, subject_id, 181220166Strasz resource, action, (uintmax_t)num, per); 182220166Strasz 183220166Strasz if (expanded == NULL) 184220166Strasz err(1, "asprintf"); 185220166Strasz 186220166Strasz return (expanded); 187220166Strasz} 188220166Strasz 189220166Straszstatic char * 190220166Straszhumanize_ids(char *rule) 191220166Strasz{ 192220166Strasz id_t id; 193220166Strasz struct passwd *pwd; 194220166Strasz struct group *grp; 195220166Strasz const char *subject, *textid, *rest; 196220166Strasz char *humanized; 197220166Strasz 198220166Strasz subject = strsep(&rule, ":"); 199220166Strasz textid = strsep(&rule, ":"); 200220166Strasz if (textid == NULL) 201220166Strasz errx(1, "rule passed from the kernel didn't contain subject"); 202220166Strasz if (rule != NULL) 203220166Strasz rest = rule; 204220166Strasz else 205220166Strasz rest = ""; 206220166Strasz 207220166Strasz /* Replace numerical user and group ids with names. */ 208220166Strasz if (strcasecmp(subject, "user") == 0) { 209220166Strasz id = parse_user(textid); 210220166Strasz pwd = getpwuid(id); 211220166Strasz if (pwd != NULL) 212220166Strasz textid = pwd->pw_name; 213220166Strasz } else if (strcasecmp(subject, "group") == 0) { 214220166Strasz id = parse_group(textid); 215220166Strasz grp = getgrgid(id); 216220166Strasz if (grp != NULL) 217220166Strasz textid = grp->gr_name; 218220166Strasz } 219220166Strasz 220220166Strasz asprintf(&humanized, "%s:%s:%s", subject, textid, rest); 221220166Strasz 222220166Strasz if (humanized == NULL) 223220166Strasz err(1, "asprintf"); 224220166Strasz 225220166Strasz return (humanized); 226220166Strasz} 227220166Strasz 228220166Straszstatic int 229220166Straszstr2int64(const char *str, int64_t *value) 230220166Strasz{ 231220166Strasz char *end; 232220166Strasz 233220166Strasz if (str == NULL) 234220166Strasz return (EINVAL); 235220166Strasz 236220166Strasz *value = strtoul(str, &end, 10); 237220166Strasz if ((size_t)(end - str) != strlen(str)) 238220166Strasz return (EINVAL); 239220166Strasz 240220166Strasz return (0); 241220166Strasz} 242220166Strasz 243220166Straszstatic char * 244220166Straszhumanize_amount(char *rule) 245220166Strasz{ 246220166Strasz int64_t num; 247220166Strasz const char *subject, *subject_id, *resource, *action, *amount, *per; 248220166Strasz char *copy, *humanized, buf[6]; 249220166Strasz 250220166Strasz copy = strdup(rule); 251220166Strasz if (copy == NULL) 252220166Strasz err(1, "strdup"); 253220166Strasz 254220166Strasz subject = strsep(©, ":"); 255220166Strasz subject_id = strsep(©, ":"); 256220166Strasz resource = strsep(©, ":"); 257220166Strasz action = strsep(©, "=/"); 258220166Strasz amount = strsep(©, "/"); 259220166Strasz per = copy; 260220166Strasz 261220166Strasz if (amount == NULL || strlen(amount) == 0 || 262220166Strasz str2int64(amount, &num) != 0) { 263220166Strasz free(copy); 264220166Strasz return (rule); 265220166Strasz } 266220166Strasz 267220166Strasz assert(subject != NULL); 268220166Strasz assert(subject_id != NULL); 269220166Strasz assert(resource != NULL); 270220166Strasz assert(action != NULL); 271220166Strasz 272220166Strasz if (humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE, 273220166Strasz HN_DECIMAL | HN_NOSPACE) == -1) 274220166Strasz err(1, "humanize_number"); 275220166Strasz 276220166Strasz if (per == NULL) 277220166Strasz asprintf(&humanized, "%s:%s:%s:%s=%s", subject, subject_id, 278220166Strasz resource, action, buf); 279220166Strasz else 280220166Strasz asprintf(&humanized, "%s:%s:%s:%s=%s/%s", subject, subject_id, 281220166Strasz resource, action, buf, per); 282220166Strasz 283220166Strasz if (humanized == NULL) 284220166Strasz err(1, "asprintf"); 285220166Strasz 286220166Strasz return (humanized); 287220166Strasz} 288220166Strasz 289220166Strasz/* 290220166Strasz * Print rules, one per line. 291220166Strasz */ 292220166Straszstatic void 293220166Straszprint_rules(char *rules, int hflag, int nflag) 294220166Strasz{ 295220166Strasz char *rule; 296220166Strasz 297220166Strasz while ((rule = strsep(&rules, ",")) != NULL) { 298220166Strasz if (rule[0] == '\0') 299220166Strasz break; /* XXX */ 300220166Strasz if (nflag == 0) 301220166Strasz rule = humanize_ids(rule); 302220166Strasz if (hflag) 303220166Strasz rule = humanize_amount(rule); 304220166Strasz printf("%s\n", rule); 305220166Strasz } 306220166Strasz} 307220166Strasz 308220166Straszstatic void 309284667Straszenosys(void) 310284667Strasz{ 311284667Strasz int error, racct_enable; 312284667Strasz size_t racct_enable_len; 313284667Strasz 314284667Strasz racct_enable_len = sizeof(racct_enable); 315284667Strasz error = sysctlbyname("kern.racct.enable", 316284667Strasz &racct_enable, &racct_enable_len, NULL, 0); 317284667Strasz 318284667Strasz if (error != 0) { 319284667Strasz if (errno == ENOENT) 320284668Strasz errx(1, "RACCT/RCTL support not present in kernel; see rctl(8) for details"); 321284667Strasz 322284667Strasz err(1, "sysctlbyname"); 323284667Strasz } 324284667Strasz 325284667Strasz if (racct_enable == 0) 326284667Strasz errx(1, "RACCT/RCTL present, but disabled; enable using kern.racct.enable=1 tunable"); 327284667Strasz} 328284667Strasz 329284667Straszstatic void 330220166Straszadd_rule(char *rule) 331220166Strasz{ 332220166Strasz int error; 333220166Strasz 334220166Strasz error = rctl_add_rule(rule, strlen(rule) + 1, NULL, 0); 335284667Strasz if (error != 0) { 336284667Strasz if (errno == ENOSYS) 337284667Strasz enosys(); 338220166Strasz err(1, "rctl_add_rule"); 339284667Strasz } 340220166Strasz free(rule); 341220166Strasz} 342220166Strasz 343220166Straszstatic void 344220166Straszshow_limits(char *filter, int hflag, int nflag) 345220166Strasz{ 346220166Strasz int error; 347220166Strasz char *outbuf = NULL; 348220166Strasz size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4; 349220166Strasz 350220166Strasz do { 351220166Strasz outbuflen *= 4; 352220166Strasz outbuf = realloc(outbuf, outbuflen); 353220166Strasz if (outbuf == NULL) 354220166Strasz err(1, "realloc"); 355220166Strasz 356220166Strasz error = rctl_get_limits(filter, strlen(filter) + 1, outbuf, 357220166Strasz outbuflen); 358284667Strasz if (error && errno != ERANGE) { 359284667Strasz if (errno == ENOSYS) 360284667Strasz enosys(); 361220166Strasz err(1, "rctl_get_limits"); 362284667Strasz } 363220166Strasz } while (error && errno == ERANGE); 364220166Strasz 365220166Strasz print_rules(outbuf, hflag, nflag); 366220166Strasz free(filter); 367220166Strasz free(outbuf); 368220166Strasz} 369220166Strasz 370220166Straszstatic void 371220166Straszremove_rule(char *filter) 372220166Strasz{ 373220166Strasz int error; 374220166Strasz 375220166Strasz error = rctl_remove_rule(filter, strlen(filter) + 1, NULL, 0); 376284667Strasz if (error != 0) { 377284667Strasz if (errno == ENOSYS) 378284667Strasz enosys(); 379220166Strasz err(1, "rctl_remove_rule"); 380284667Strasz } 381220166Strasz free(filter); 382220166Strasz} 383220166Strasz 384220166Straszstatic char * 385220166Straszhumanize_usage_amount(char *usage) 386220166Strasz{ 387220166Strasz int64_t num; 388220166Strasz const char *resource, *amount; 389220166Strasz char *copy, *humanized, buf[6]; 390220166Strasz 391220166Strasz copy = strdup(usage); 392220166Strasz if (copy == NULL) 393220166Strasz err(1, "strdup"); 394220166Strasz 395220166Strasz resource = strsep(©, "="); 396220166Strasz amount = copy; 397220166Strasz 398220166Strasz assert(resource != NULL); 399220166Strasz assert(amount != NULL); 400220166Strasz 401220166Strasz if (str2int64(amount, &num) != 0 || 402220166Strasz humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE, 403220166Strasz HN_DECIMAL | HN_NOSPACE) == -1) { 404220166Strasz free(copy); 405220166Strasz return (usage); 406220166Strasz } 407220166Strasz 408220166Strasz asprintf(&humanized, "%s=%s", resource, buf); 409220166Strasz if (humanized == NULL) 410220166Strasz err(1, "asprintf"); 411220166Strasz 412220166Strasz return (humanized); 413220166Strasz} 414220166Strasz 415220166Strasz/* 416220166Strasz * Query the kernel about a resource usage and print it out. 417220166Strasz */ 418220166Straszstatic void 419220166Straszshow_usage(char *filter, int hflag) 420220166Strasz{ 421220166Strasz int error; 422220166Strasz char *outbuf = NULL, *tmp; 423220166Strasz size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4; 424220166Strasz 425220166Strasz do { 426220166Strasz outbuflen *= 4; 427220166Strasz outbuf = realloc(outbuf, outbuflen); 428220166Strasz if (outbuf == NULL) 429220166Strasz err(1, "realloc"); 430220166Strasz 431220166Strasz error = rctl_get_racct(filter, strlen(filter) + 1, outbuf, 432220166Strasz outbuflen); 433284667Strasz if (error && errno != ERANGE) { 434284667Strasz if (errno == ENOSYS) 435284667Strasz enosys(); 436220166Strasz err(1, "rctl_get_racct"); 437284667Strasz } 438220166Strasz } while (error && errno == ERANGE); 439220166Strasz 440220166Strasz while ((tmp = strsep(&outbuf, ",")) != NULL) { 441220166Strasz if (tmp[0] == '\0') 442220166Strasz break; /* XXX */ 443220166Strasz 444220166Strasz if (hflag) 445220166Strasz tmp = humanize_usage_amount(tmp); 446220166Strasz 447220166Strasz printf("%s\n", tmp); 448220166Strasz } 449220166Strasz 450220166Strasz free(filter); 451220166Strasz free(outbuf); 452220166Strasz} 453220166Strasz 454220166Strasz/* 455220166Strasz * Query the kernel about resource limit rules and print them out. 456220166Strasz */ 457220166Straszstatic void 458220166Straszshow_rules(char *filter, int hflag, int nflag) 459220166Strasz{ 460220166Strasz int error; 461220166Strasz char *outbuf = NULL; 462220166Strasz size_t filterlen, outbuflen = RCTL_DEFAULT_BUFSIZE / 4; 463220166Strasz 464220166Strasz if (filter != NULL) 465220166Strasz filterlen = strlen(filter) + 1; 466220166Strasz else 467220166Strasz filterlen = 0; 468220166Strasz 469220166Strasz do { 470220166Strasz outbuflen *= 4; 471220166Strasz outbuf = realloc(outbuf, outbuflen); 472220166Strasz if (outbuf == NULL) 473220166Strasz err(1, "realloc"); 474220166Strasz 475220166Strasz error = rctl_get_rules(filter, filterlen, outbuf, outbuflen); 476284667Strasz if (error && errno != ERANGE) { 477284667Strasz if (errno == ENOSYS) 478284667Strasz enosys(); 479220166Strasz err(1, "rctl_get_rules"); 480284667Strasz } 481220166Strasz } while (error && errno == ERANGE); 482220166Strasz 483220166Strasz print_rules(outbuf, hflag, nflag); 484220166Strasz free(outbuf); 485220166Strasz} 486220166Strasz 487220166Straszstatic void 488220166Straszusage(void) 489220166Strasz{ 490220166Strasz 491220166Strasz fprintf(stderr, "usage: rctl [ -h ] [-a rule | -l filter | -r filter " 492220166Strasz "| -u filter | filter]\n"); 493220166Strasz exit(1); 494220166Strasz} 495220166Strasz 496220166Straszint 497325847Sbaptmain(int argc, char **argv) 498220166Strasz{ 499220166Strasz int ch, aflag = 0, hflag = 0, nflag = 0, lflag = 0, rflag = 0, 500220166Strasz uflag = 0; 501220166Strasz char *rule = NULL; 502220166Strasz 503220166Strasz while ((ch = getopt(argc, argv, "a:hl:nr:u:")) != -1) { 504220166Strasz switch (ch) { 505220166Strasz case 'a': 506220166Strasz aflag = 1; 507220166Strasz rule = strdup(optarg); 508220166Strasz break; 509220166Strasz case 'h': 510220166Strasz hflag = 1; 511220166Strasz break; 512220166Strasz case 'l': 513220166Strasz lflag = 1; 514220166Strasz rule = strdup(optarg); 515220166Strasz break; 516220166Strasz case 'n': 517220166Strasz nflag = 1; 518220166Strasz break; 519220166Strasz case 'r': 520220166Strasz rflag = 1; 521220166Strasz rule = strdup(optarg); 522220166Strasz break; 523220166Strasz case 'u': 524220166Strasz uflag = 1; 525220166Strasz rule = strdup(optarg); 526220166Strasz break; 527220166Strasz 528220166Strasz case '?': 529220166Strasz default: 530220166Strasz usage(); 531220166Strasz } 532220166Strasz } 533220166Strasz 534220166Strasz argc -= optind; 535220166Strasz argv += optind; 536220166Strasz 537220166Strasz if (argc > 1) 538220166Strasz usage(); 539220166Strasz 540220166Strasz if (rule == NULL) { 541220166Strasz if (argc == 1) 542220166Strasz rule = strdup(argv[0]); 543220166Strasz else 544220166Strasz rule = strdup("::"); 545220166Strasz } 546220166Strasz 547220166Strasz if (aflag + lflag + rflag + uflag + argc > 1) 548220166Strasz errx(1, "only one flag or argument may be specified " 549220166Strasz "at the same time"); 550220166Strasz 551220166Strasz rule = resolve_ids(rule); 552220166Strasz rule = expand_amount(rule); 553220166Strasz 554220166Strasz if (aflag) { 555220166Strasz add_rule(rule); 556220166Strasz return (0); 557220166Strasz } 558220166Strasz 559220166Strasz if (lflag) { 560220166Strasz show_limits(rule, hflag, nflag); 561220166Strasz return (0); 562220166Strasz } 563220166Strasz 564220166Strasz if (rflag) { 565220166Strasz remove_rule(rule); 566220166Strasz return (0); 567220166Strasz } 568220166Strasz 569220166Strasz if (uflag) { 570220166Strasz show_usage(rule, hflag); 571220166Strasz return (0); 572220166Strasz } 573220166Strasz 574220166Strasz show_rules(rule, hflag, nflag); 575220166Strasz return (0); 576220166Strasz} 577