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