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