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