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