1 /*- 2 * Copyright (c) 2010 The FreeBSD Foundation 3 * All rights reserved. 4 * 5 * This software was developed by Edward Tomasz Napierala under sponsorship 6 * from the FreeBSD Foundation. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 * 29 * $FreeBSD$ 30 */ 31 32 #include <sys/cdefs.h> 33 __FBSDID("$FreeBSD$"); 34 35 #include <sys/types.h> 36 #include <sys/rctl.h> 37 #include <sys/sysctl.h> 38 #include <assert.h> 39 #include <ctype.h> 40 #include <err.h> 41 #include <errno.h> 42 #include <getopt.h> 43 #include <grp.h> 44 #include <libutil.h> 45 #include <pwd.h> 46 #include <stdbool.h> 47 #include <stdint.h> 48 #include <stdio.h> 49 #include <stdlib.h> 50 #include <string.h> 51 52 #define RCTL_DEFAULT_BUFSIZE 128 * 1024 53 54 static int 55 parse_user(const char *s, id_t *uidp, const char *unexpanded_rule) 56 { 57 char *end; 58 struct passwd *pwd; 59 60 pwd = getpwnam(s); 61 if (pwd != NULL) { 62 *uidp = pwd->pw_uid; 63 return (0); 64 } 65 66 if (!isnumber(s[0])) { 67 warnx("malformed rule '%s': unknown user '%s'", 68 unexpanded_rule, s); 69 return (1); 70 } 71 72 *uidp = strtod(s, &end); 73 if ((size_t)(end - s) != strlen(s)) { 74 warnx("malformed rule '%s': trailing characters " 75 "after numerical id", unexpanded_rule); 76 return (1); 77 } 78 79 return (0); 80 } 81 82 static int 83 parse_group(const char *s, id_t *gidp, const char *unexpanded_rule) 84 { 85 char *end; 86 struct group *grp; 87 88 grp = getgrnam(s); 89 if (grp != NULL) { 90 *gidp = grp->gr_gid; 91 return (0); 92 } 93 94 if (!isnumber(s[0])) { 95 warnx("malformed rule '%s': unknown group '%s'", 96 unexpanded_rule, s); 97 return (1); 98 } 99 100 *gidp = strtod(s, &end); 101 if ((size_t)(end - s) != strlen(s)) { 102 warnx("malformed rule '%s': trailing characters " 103 "after numerical id", unexpanded_rule); 104 return (1); 105 } 106 107 return (0); 108 } 109 110 /* 111 * Replace human-readable number with its expanded form. 112 */ 113 static char * 114 expand_amount(const char *rule, const char *unexpanded_rule) 115 { 116 uint64_t num; 117 const char *subject, *subject_id, *resource, *action, *amount, *per; 118 char *copy, *expanded, *tofree; 119 int ret; 120 121 tofree = copy = strdup(rule); 122 if (copy == NULL) { 123 warn("strdup"); 124 return (NULL); 125 } 126 127 subject = strsep(©, ":"); 128 subject_id = strsep(©, ":"); 129 resource = strsep(©, ":"); 130 action = strsep(©, "=/"); 131 amount = strsep(©, "/"); 132 per = copy; 133 134 if (amount == NULL || strlen(amount) == 0) { 135 /* 136 * The "copy" has already been tinkered with by strsep(). 137 */ 138 free(tofree); 139 copy = strdup(rule); 140 if (copy == NULL) { 141 warn("strdup"); 142 return (NULL); 143 } 144 return (copy); 145 } 146 147 assert(subject != NULL); 148 assert(subject_id != NULL); 149 assert(resource != NULL); 150 assert(action != NULL); 151 152 if (expand_number(amount, &num)) { 153 warnx("malformed rule '%s': invalid numeric value '%s'", 154 unexpanded_rule, amount); 155 free(tofree); 156 return (NULL); 157 } 158 159 if (per == NULL) { 160 ret = asprintf(&expanded, "%s:%s:%s:%s=%ju", 161 subject, subject_id, resource, action, (uintmax_t)num); 162 } else { 163 ret = asprintf(&expanded, "%s:%s:%s:%s=%ju/%s", 164 subject, subject_id, resource, action, (uintmax_t)num, per); 165 } 166 167 if (ret <= 0) { 168 warn("asprintf"); 169 free(tofree); 170 return (NULL); 171 } 172 173 free(tofree); 174 175 return (expanded); 176 } 177 178 static char * 179 expand_rule(const char *rule, bool resolve_ids) 180 { 181 id_t id; 182 const char *subject, *textid, *rest; 183 char *copy, *expanded, *resolved, *tofree; 184 int error, ret; 185 186 tofree = copy = strdup(rule); 187 if (copy == NULL) { 188 warn("strdup"); 189 return (NULL); 190 } 191 192 subject = strsep(©, ":"); 193 textid = strsep(©, ":"); 194 if (textid == NULL) { 195 warnx("malformed rule '%s': missing subject", rule); 196 return (NULL); 197 } 198 if (copy != NULL) 199 rest = copy; 200 else 201 rest = ""; 202 203 if (strcasecmp(subject, "u") == 0) 204 subject = "user"; 205 else if (strcasecmp(subject, "g") == 0) 206 subject = "group"; 207 else if (strcasecmp(subject, "p") == 0) 208 subject = "process"; 209 else if (strcasecmp(subject, "l") == 0 || 210 strcasecmp(subject, "c") == 0 || 211 strcasecmp(subject, "class") == 0) 212 subject = "loginclass"; 213 else if (strcasecmp(subject, "j") == 0) 214 subject = "jail"; 215 216 if (resolve_ids && 217 strcasecmp(subject, "user") == 0 && strlen(textid) > 0) { 218 error = parse_user(textid, &id, rule); 219 if (error != 0) { 220 free(tofree); 221 return (NULL); 222 } 223 ret = asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); 224 } else if (resolve_ids && 225 strcasecmp(subject, "group") == 0 && strlen(textid) > 0) { 226 error = parse_group(textid, &id, rule); 227 if (error != 0) { 228 free(tofree); 229 return (NULL); 230 } 231 ret = asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); 232 } else { 233 ret = asprintf(&resolved, "%s:%s:%s", subject, textid, rest); 234 } 235 236 if (ret <= 0) { 237 warn("asprintf"); 238 free(tofree); 239 return (NULL); 240 } 241 242 free(tofree); 243 244 expanded = expand_amount(resolved, rule); 245 free(resolved); 246 247 return (expanded); 248 } 249 250 static char * 251 humanize_ids(char *rule) 252 { 253 id_t id; 254 struct passwd *pwd; 255 struct group *grp; 256 const char *subject, *textid, *rest; 257 char *end, *humanized; 258 int ret; 259 260 subject = strsep(&rule, ":"); 261 textid = strsep(&rule, ":"); 262 if (textid == NULL) 263 errx(1, "rule passed from the kernel didn't contain subject"); 264 if (rule != NULL) 265 rest = rule; 266 else 267 rest = ""; 268 269 /* Replace numerical user and group ids with names. */ 270 if (strcasecmp(subject, "user") == 0) { 271 id = strtod(textid, &end); 272 if ((size_t)(end - textid) != strlen(textid)) 273 errx(1, "malformed uid '%s'", textid); 274 pwd = getpwuid(id); 275 if (pwd != NULL) 276 textid = pwd->pw_name; 277 } else if (strcasecmp(subject, "group") == 0) { 278 id = strtod(textid, &end); 279 if ((size_t)(end - textid) != strlen(textid)) 280 errx(1, "malformed gid '%s'", textid); 281 grp = getgrgid(id); 282 if (grp != NULL) 283 textid = grp->gr_name; 284 } 285 286 ret = asprintf(&humanized, "%s:%s:%s", subject, textid, rest); 287 if (ret <= 0) 288 err(1, "asprintf"); 289 290 return (humanized); 291 } 292 293 static int 294 str2int64(const char *str, int64_t *value) 295 { 296 char *end; 297 298 if (str == NULL) 299 return (EINVAL); 300 301 *value = strtoul(str, &end, 10); 302 if ((size_t)(end - str) != strlen(str)) 303 return (EINVAL); 304 305 return (0); 306 } 307 308 static char * 309 humanize_amount(char *rule) 310 { 311 int64_t num; 312 const char *subject, *subject_id, *resource, *action, *amount, *per; 313 char *copy, *humanized, buf[6], *tofree; 314 int ret; 315 316 tofree = copy = strdup(rule); 317 if (copy == NULL) 318 err(1, "strdup"); 319 320 subject = strsep(©, ":"); 321 subject_id = strsep(©, ":"); 322 resource = strsep(©, ":"); 323 action = strsep(©, "=/"); 324 amount = strsep(©, "/"); 325 per = copy; 326 327 if (amount == NULL || strlen(amount) == 0 || 328 str2int64(amount, &num) != 0) { 329 free(tofree); 330 return (rule); 331 } 332 333 assert(subject != NULL); 334 assert(subject_id != NULL); 335 assert(resource != NULL); 336 assert(action != NULL); 337 338 if (humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE, 339 HN_DECIMAL | HN_NOSPACE) == -1) 340 err(1, "humanize_number"); 341 342 if (per == NULL) { 343 ret = asprintf(&humanized, "%s:%s:%s:%s=%s", 344 subject, subject_id, resource, action, buf); 345 } else { 346 ret = asprintf(&humanized, "%s:%s:%s:%s=%s/%s", 347 subject, subject_id, resource, action, buf, per); 348 } 349 350 if (ret <= 0) 351 err(1, "asprintf"); 352 353 free(tofree); 354 return (humanized); 355 } 356 357 /* 358 * Print rules, one per line. 359 */ 360 static void 361 print_rules(char *rules, int hflag, int nflag) 362 { 363 char *rule; 364 365 while ((rule = strsep(&rules, ",")) != NULL) { 366 if (rule[0] == '\0') 367 break; /* XXX */ 368 if (nflag == 0) 369 rule = humanize_ids(rule); 370 if (hflag) 371 rule = humanize_amount(rule); 372 printf("%s\n", rule); 373 } 374 } 375 376 static void 377 enosys(void) 378 { 379 int error, racct_enable; 380 size_t racct_enable_len; 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 == 0) 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 __unused, char **argv __unused) 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