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