1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2010 The FreeBSD Foundation 5 * All rights reserved. 6 * 7 * This software was developed by Edward Tomasz Napierala under sponsorship 8 * from the FreeBSD Foundation. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 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. 18 * 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 29 * SUCH DAMAGE. 30 * 31 * $FreeBSD$ 32 */ 33 34 #include <sys/cdefs.h> 35 __FBSDID("$FreeBSD$"); 36 37 #include <sys/types.h> 38 #include <sys/rctl.h> 39 #include <sys/sysctl.h> 40 #include <assert.h> 41 #include <ctype.h> 42 #include <err.h> 43 #include <errno.h> 44 #include <getopt.h> 45 #include <grp.h> 46 #include <libutil.h> 47 #include <pwd.h> 48 #include <stdbool.h> 49 #include <stdint.h> 50 #include <stdio.h> 51 #include <stdlib.h> 52 #include <string.h> 53 54 #define RCTL_DEFAULT_BUFSIZE 128 * 1024 55 56 static int 57 parse_user(const char *s, id_t *uidp, const char *unexpanded_rule) 58 { 59 char *end; 60 struct passwd *pwd; 61 62 pwd = getpwnam(s); 63 if (pwd != NULL) { 64 *uidp = pwd->pw_uid; 65 return (0); 66 } 67 68 if (!isnumber(s[0])) { 69 warnx("malformed rule '%s': unknown user '%s'", 70 unexpanded_rule, s); 71 return (1); 72 } 73 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); 78 return (1); 79 } 80 81 return (0); 82 } 83 84 static int 85 parse_group(const char *s, id_t *gidp, const char *unexpanded_rule) 86 { 87 char *end; 88 struct group *grp; 89 90 grp = getgrnam(s); 91 if (grp != NULL) { 92 *gidp = grp->gr_gid; 93 return (0); 94 } 95 96 if (!isnumber(s[0])) { 97 warnx("malformed rule '%s': unknown group '%s'", 98 unexpanded_rule, s); 99 return (1); 100 } 101 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); 106 return (1); 107 } 108 109 return (0); 110 } 111 112 /* 113 * Replace human-readable number with its expanded form. 114 */ 115 static char * 116 expand_amount(const char *rule, const char *unexpanded_rule) 117 { 118 uint64_t num; 119 const char *subject, *subject_id, *resource, *action, *amount, *per; 120 char *copy, *expanded, *tofree; 121 int ret; 122 123 tofree = copy = strdup(rule); 124 if (copy == NULL) { 125 warn("strdup"); 126 return (NULL); 127 } 128 129 subject = strsep(©, ":"); 130 subject_id = strsep(©, ":"); 131 resource = strsep(©, ":"); 132 action = strsep(©, "=/"); 133 amount = strsep(©, "/"); 134 per = copy; 135 136 if (amount == NULL || strlen(amount) == 0) { 137 /* 138 * The "copy" has already been tinkered with by strsep(). 139 */ 140 free(tofree); 141 copy = strdup(rule); 142 if (copy == NULL) { 143 warn("strdup"); 144 return (NULL); 145 } 146 return (copy); 147 } 148 149 assert(subject != NULL); 150 assert(subject_id != NULL); 151 assert(resource != NULL); 152 assert(action != NULL); 153 154 if (expand_number(amount, &num)) { 155 warnx("malformed rule '%s': invalid numeric value '%s'", 156 unexpanded_rule, amount); 157 free(tofree); 158 return (NULL); 159 } 160 161 if (per == NULL) { 162 ret = asprintf(&expanded, "%s:%s:%s:%s=%ju", 163 subject, subject_id, resource, action, (uintmax_t)num); 164 } else { 165 ret = asprintf(&expanded, "%s:%s:%s:%s=%ju/%s", 166 subject, subject_id, resource, action, (uintmax_t)num, per); 167 } 168 169 if (ret <= 0) { 170 warn("asprintf"); 171 free(tofree); 172 return (NULL); 173 } 174 175 free(tofree); 176 177 return (expanded); 178 } 179 180 static char * 181 expand_rule(const char *rule, bool resolve_ids) 182 { 183 id_t id; 184 const char *subject, *textid, *rest; 185 char *copy, *expanded, *resolved, *tofree; 186 int error, ret; 187 188 tofree = copy = strdup(rule); 189 if (copy == NULL) { 190 warn("strdup"); 191 return (NULL); 192 } 193 194 subject = strsep(©, ":"); 195 textid = strsep(©, ":"); 196 if (textid == NULL) { 197 warnx("malformed rule '%s': missing subject", rule); 198 return (NULL); 199 } 200 if (copy != NULL) 201 rest = copy; 202 else 203 rest = ""; 204 205 if (strcasecmp(subject, "u") == 0) 206 subject = "user"; 207 else if (strcasecmp(subject, "g") == 0) 208 subject = "group"; 209 else if (strcasecmp(subject, "p") == 0) 210 subject = "process"; 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) 216 subject = "jail"; 217 218 if (resolve_ids && 219 strcasecmp(subject, "user") == 0 && strlen(textid) > 0) { 220 error = parse_user(textid, &id, rule); 221 if (error != 0) { 222 free(tofree); 223 return (NULL); 224 } 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); 229 if (error != 0) { 230 free(tofree); 231 return (NULL); 232 } 233 ret = asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); 234 } else { 235 ret = asprintf(&resolved, "%s:%s:%s", subject, textid, rest); 236 } 237 238 if (ret <= 0) { 239 warn("asprintf"); 240 free(tofree); 241 return (NULL); 242 } 243 244 free(tofree); 245 246 expanded = expand_amount(resolved, rule); 247 free(resolved); 248 249 return (expanded); 250 } 251 252 static char * 253 humanize_ids(char *rule) 254 { 255 id_t id; 256 struct passwd *pwd; 257 struct group *grp; 258 const char *subject, *textid, *rest; 259 char *end, *humanized; 260 int ret; 261 262 subject = strsep(&rule, ":"); 263 textid = strsep(&rule, ":"); 264 if (textid == NULL) 265 errx(1, "rule passed from the kernel didn't contain subject"); 266 if (rule != NULL) 267 rest = rule; 268 else 269 rest = ""; 270 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); 276 pwd = getpwuid(id); 277 if (pwd != NULL) 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); 283 grp = getgrgid(id); 284 if (grp != NULL) 285 textid = grp->gr_name; 286 } 287 288 ret = asprintf(&humanized, "%s:%s:%s", subject, textid, rest); 289 if (ret <= 0) 290 err(1, "asprintf"); 291 292 return (humanized); 293 } 294 295 static int 296 str2int64(const char *str, int64_t *value) 297 { 298 char *end; 299 300 if (str == NULL) 301 return (EINVAL); 302 303 *value = strtoul(str, &end, 10); 304 if ((size_t)(end - str) != strlen(str)) 305 return (EINVAL); 306 307 return (0); 308 } 309 310 static char * 311 humanize_amount(char *rule) 312 { 313 int64_t num; 314 const char *subject, *subject_id, *resource, *action, *amount, *per; 315 char *copy, *humanized, buf[6], *tofree; 316 int ret; 317 318 tofree = copy = strdup(rule); 319 if (copy == NULL) 320 err(1, "strdup"); 321 322 subject = strsep(©, ":"); 323 subject_id = strsep(©, ":"); 324 resource = strsep(©, ":"); 325 action = strsep(©, "=/"); 326 amount = strsep(©, "/"); 327 per = copy; 328 329 if (amount == NULL || strlen(amount) == 0 || 330 str2int64(amount, &num) != 0) { 331 free(tofree); 332 return (rule); 333 } 334 335 assert(subject != NULL); 336 assert(subject_id != NULL); 337 assert(resource != NULL); 338 assert(action != NULL); 339 340 if (humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE, 341 HN_DECIMAL | HN_NOSPACE) == -1) 342 err(1, "humanize_number"); 343 344 if (per == NULL) { 345 ret = asprintf(&humanized, "%s:%s:%s:%s=%s", 346 subject, subject_id, resource, action, buf); 347 } else { 348 ret = asprintf(&humanized, "%s:%s:%s:%s=%s/%s", 349 subject, subject_id, resource, action, buf, per); 350 } 351 352 if (ret <= 0) 353 err(1, "asprintf"); 354 355 free(tofree); 356 return (humanized); 357 } 358 359 /* 360 * Print rules, one per line. 361 */ 362 static void 363 print_rules(char *rules, int hflag, int nflag) 364 { 365 char *rule; 366 367 while ((rule = strsep(&rules, ",")) != NULL) { 368 if (rule[0] == '\0') 369 break; /* XXX */ 370 if (nflag == 0) 371 rule = humanize_ids(rule); 372 if (hflag) 373 rule = humanize_amount(rule); 374 printf("%s\n", rule); 375 } 376 } 377 378 static void 379 enosys(void) 380 { 381 size_t racct_enable_len; 382 int error; 383 bool racct_enable; 384 385 racct_enable_len = sizeof(racct_enable); 386 error = sysctlbyname("kern.racct.enable", 387 &racct_enable, &racct_enable_len, NULL, 0); 388 389 if (error != 0) { 390 if (errno == ENOENT) 391 errx(1, "RACCT/RCTL support not present in kernel; see rctl(8) for details"); 392 393 err(1, "sysctlbyname"); 394 } 395 396 if (!racct_enable) 397 errx(1, "RACCT/RCTL present, but disabled; enable using kern.racct.enable=1 tunable"); 398 } 399 400 static int 401 add_rule(const char *rule, const char *unexpanded_rule) 402 { 403 int error; 404 405 error = rctl_add_rule(rule, strlen(rule) + 1, NULL, 0); 406 if (error != 0) { 407 if (errno == ENOSYS) 408 enosys(); 409 warn("failed to add rule '%s'", unexpanded_rule); 410 } 411 412 return (error); 413 } 414 415 static int 416 show_limits(const char *filter, const char *unexpanded_rule, 417 int hflag, int nflag) 418 { 419 int error; 420 char *outbuf = NULL; 421 size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4; 422 423 for (;;) { 424 outbuflen *= 4; 425 outbuf = realloc(outbuf, outbuflen); 426 if (outbuf == NULL) 427 err(1, "realloc"); 428 error = rctl_get_limits(filter, strlen(filter) + 1, 429 outbuf, outbuflen); 430 if (error == 0) 431 break; 432 if (errno == ERANGE) 433 continue; 434 if (errno == ENOSYS) 435 enosys(); 436 warn("failed to get limits for '%s'", unexpanded_rule); 437 free(outbuf); 438 439 return (error); 440 } 441 442 print_rules(outbuf, hflag, nflag); 443 free(outbuf); 444 445 return (error); 446 } 447 448 static int 449 remove_rule(const char *filter, const char *unexpanded_rule) 450 { 451 int error; 452 453 error = rctl_remove_rule(filter, strlen(filter) + 1, NULL, 0); 454 if (error != 0) { 455 if (errno == ENOSYS) 456 enosys(); 457 warn("failed to remove rule '%s'", unexpanded_rule); 458 } 459 460 return (error); 461 } 462 463 static char * 464 humanize_usage_amount(char *usage) 465 { 466 int64_t num; 467 const char *resource, *amount; 468 char *copy, *humanized, buf[6], *tofree; 469 int ret; 470 471 tofree = copy = strdup(usage); 472 if (copy == NULL) 473 err(1, "strdup"); 474 475 resource = strsep(©, "="); 476 amount = copy; 477 478 assert(resource != NULL); 479 assert(amount != NULL); 480 481 if (str2int64(amount, &num) != 0 || 482 humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE, 483 HN_DECIMAL | HN_NOSPACE) == -1) { 484 free(tofree); 485 return (usage); 486 } 487 488 ret = asprintf(&humanized, "%s=%s", resource, buf); 489 if (ret <= 0) 490 err(1, "asprintf"); 491 492 free(tofree); 493 return (humanized); 494 } 495 496 /* 497 * Query the kernel about a resource usage and print it out. 498 */ 499 static int 500 show_usage(const char *filter, const char *unexpanded_rule, int hflag) 501 { 502 int error; 503 char *copy, *outbuf = NULL, *tmp; 504 size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4; 505 506 for (;;) { 507 outbuflen *= 4; 508 outbuf = realloc(outbuf, outbuflen); 509 if (outbuf == NULL) 510 err(1, "realloc"); 511 error = rctl_get_racct(filter, strlen(filter) + 1, 512 outbuf, outbuflen); 513 if (error == 0) 514 break; 515 if (errno == ERANGE) 516 continue; 517 if (errno == ENOSYS) 518 enosys(); 519 warn("failed to show resource consumption for '%s'", 520 unexpanded_rule); 521 free(outbuf); 522 523 return (error); 524 } 525 526 copy = outbuf; 527 while ((tmp = strsep(©, ",")) != NULL) { 528 if (tmp[0] == '\0') 529 break; /* XXX */ 530 531 if (hflag) 532 tmp = humanize_usage_amount(tmp); 533 534 printf("%s\n", tmp); 535 } 536 537 free(outbuf); 538 539 return (error); 540 } 541 542 /* 543 * Query the kernel about resource limit rules and print them out. 544 */ 545 static int 546 show_rules(const char *filter, const char *unexpanded_rule, 547 int hflag, int nflag) 548 { 549 int error; 550 char *outbuf = NULL; 551 size_t filterlen, outbuflen = RCTL_DEFAULT_BUFSIZE / 4; 552 553 if (filter != NULL) 554 filterlen = strlen(filter) + 1; 555 else 556 filterlen = 0; 557 558 for (;;) { 559 outbuflen *= 4; 560 outbuf = realloc(outbuf, outbuflen); 561 if (outbuf == NULL) 562 err(1, "realloc"); 563 error = rctl_get_rules(filter, filterlen, outbuf, outbuflen); 564 if (error == 0) 565 break; 566 if (errno == ERANGE) 567 continue; 568 if (errno == ENOSYS) 569 enosys(); 570 warn("failed to show rules for '%s'", unexpanded_rule); 571 free(outbuf); 572 573 return (error); 574 } 575 576 print_rules(outbuf, hflag, nflag); 577 free(outbuf); 578 579 return (error); 580 } 581 582 static void 583 usage(void) 584 { 585 586 fprintf(stderr, "usage: rctl [ -h ] [-a rule | -l filter | -r filter " 587 "| -u filter | filter]\n"); 588 exit(1); 589 } 590 591 int 592 main(int argc, char **argv) 593 { 594 int ch, aflag = 0, hflag = 0, nflag = 0, lflag = 0, rflag = 0, 595 uflag = 0; 596 char *rule = NULL, *unexpanded_rule; 597 int i, cumulated_error, error; 598 599 while ((ch = getopt(argc, argv, "ahlnru")) != -1) { 600 switch (ch) { 601 case 'a': 602 aflag = 1; 603 break; 604 case 'h': 605 hflag = 1; 606 break; 607 case 'l': 608 lflag = 1; 609 break; 610 case 'n': 611 nflag = 1; 612 break; 613 case 'r': 614 rflag = 1; 615 break; 616 case 'u': 617 uflag = 1; 618 break; 619 620 case '?': 621 default: 622 usage(); 623 } 624 } 625 626 argc -= optind; 627 argv += optind; 628 629 if (aflag + lflag + rflag + uflag > 1) 630 errx(1, "at most one of -a, -l, -r, or -u may be specified"); 631 632 if (argc == 0) { 633 if (aflag + lflag + rflag + uflag == 0) { 634 rule = strdup("::"); 635 show_rules(rule, rule, hflag, nflag); 636 637 return (0); 638 } 639 640 usage(); 641 } 642 643 cumulated_error = 0; 644 645 for (i = 0; i < argc; i++) { 646 unexpanded_rule = argv[i]; 647 648 /* 649 * Skip resolving if passed -n _and_ -a. Ignore -n otherwise, 650 * so we can still do "rctl -n u:root" and see the rules without 651 * resolving the UID. 652 */ 653 if (aflag != 0 && nflag != 0) 654 rule = expand_rule(unexpanded_rule, false); 655 else 656 rule = expand_rule(unexpanded_rule, true); 657 658 if (rule == NULL) { 659 cumulated_error++; 660 continue; 661 } 662 663 /* 664 * The reason for passing the unexpanded_rule is to make 665 * it easier for the user to search for the problematic 666 * rule in the passed input. 667 */ 668 if (aflag) { 669 error = add_rule(rule, unexpanded_rule); 670 } else if (lflag) { 671 error = show_limits(rule, unexpanded_rule, 672 hflag, nflag); 673 } else if (rflag) { 674 error = remove_rule(rule, unexpanded_rule); 675 } else if (uflag) { 676 error = show_usage(rule, unexpanded_rule, hflag); 677 } else { 678 error = show_rules(rule, unexpanded_rule, 679 hflag, nflag); 680 } 681 682 if (error != 0) 683 cumulated_error++; 684 685 free(rule); 686 } 687 688 return (cumulated_error); 689 } 690