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 int error, racct_enable; 382 size_t racct_enable_len; 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 == 0) 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