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 <stdint.h> 47 #include <stdio.h> 48 #include <stdlib.h> 49 #include <string.h> 50 51 #define RCTL_DEFAULT_BUFSIZE 4096 52 53 static id_t 54 parse_user(const char *s) 55 { 56 id_t id; 57 char *end; 58 struct passwd *pwd; 59 60 pwd = getpwnam(s); 61 if (pwd != NULL) 62 return (pwd->pw_uid); 63 64 if (!isnumber(s[0])) 65 errx(1, "uknown user '%s'", s); 66 67 id = strtod(s, &end); 68 if ((size_t)(end - s) != strlen(s)) 69 errx(1, "trailing characters after numerical id"); 70 71 return (id); 72 } 73 74 static id_t 75 parse_group(const char *s) 76 { 77 id_t id; 78 char *end; 79 struct group *grp; 80 81 grp = getgrnam(s); 82 if (grp != NULL) 83 return (grp->gr_gid); 84 85 if (!isnumber(s[0])) 86 errx(1, "uknown group '%s'", s); 87 88 id = strtod(s, &end); 89 if ((size_t)(end - s) != strlen(s)) 90 errx(1, "trailing characters after numerical id"); 91 92 return (id); 93 } 94 95 /* 96 * This routine replaces user/group name with numeric id. 97 */ 98 static char * 99 resolve_ids(char *rule) 100 { 101 id_t id; 102 const char *subject, *textid, *rest; 103 char *resolved; 104 105 subject = strsep(&rule, ":"); 106 textid = strsep(&rule, ":"); 107 if (textid == NULL) 108 errx(1, "error in rule specification -- no subject"); 109 if (rule != NULL) 110 rest = rule; 111 else 112 rest = ""; 113 114 if (strcasecmp(subject, "u") == 0) 115 subject = "user"; 116 else if (strcasecmp(subject, "g") == 0) 117 subject = "group"; 118 else if (strcasecmp(subject, "p") == 0) 119 subject = "process"; 120 else if (strcasecmp(subject, "l") == 0 || 121 strcasecmp(subject, "c") == 0 || 122 strcasecmp(subject, "class") == 0) 123 subject = "loginclass"; 124 else if (strcasecmp(subject, "j") == 0) 125 subject = "jail"; 126 127 if (strcasecmp(subject, "user") == 0 && strlen(textid) > 0) { 128 id = parse_user(textid); 129 asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); 130 } else if (strcasecmp(subject, "group") == 0 && strlen(textid) > 0) { 131 id = parse_group(textid); 132 asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); 133 } else 134 asprintf(&resolved, "%s:%s:%s", subject, textid, rest); 135 136 if (resolved == NULL) 137 err(1, "asprintf"); 138 139 return (resolved); 140 } 141 142 /* 143 * This routine replaces "human-readable" number with its expanded form. 144 */ 145 static char * 146 expand_amount(char *rule) 147 { 148 uint64_t num; 149 const char *subject, *subject_id, *resource, *action, *amount, *per; 150 char *copy, *expanded; 151 152 copy = strdup(rule); 153 if (copy == NULL) 154 err(1, "strdup"); 155 156 subject = strsep(©, ":"); 157 subject_id = strsep(©, ":"); 158 resource = strsep(©, ":"); 159 action = strsep(©, "=/"); 160 amount = strsep(©, "/"); 161 per = copy; 162 163 if (amount == NULL || strlen(amount) == 0) { 164 free(copy); 165 return (rule); 166 } 167 168 assert(subject != NULL); 169 assert(subject_id != NULL); 170 assert(resource != NULL); 171 assert(action != NULL); 172 173 if (expand_number(amount, &num)) 174 err(1, "expand_number"); 175 176 if (per == NULL) 177 asprintf(&expanded, "%s:%s:%s:%s=%ju", subject, subject_id, 178 resource, action, (uintmax_t)num); 179 else 180 asprintf(&expanded, "%s:%s:%s:%s=%ju/%s", subject, subject_id, 181 resource, action, (uintmax_t)num, per); 182 183 if (expanded == NULL) 184 err(1, "asprintf"); 185 186 return (expanded); 187 } 188 189 static char * 190 humanize_ids(char *rule) 191 { 192 id_t id; 193 struct passwd *pwd; 194 struct group *grp; 195 const char *subject, *textid, *rest; 196 char *humanized; 197 198 subject = strsep(&rule, ":"); 199 textid = strsep(&rule, ":"); 200 if (textid == NULL) 201 errx(1, "rule passed from the kernel didn't contain subject"); 202 if (rule != NULL) 203 rest = rule; 204 else 205 rest = ""; 206 207 /* Replace numerical user and group ids with names. */ 208 if (strcasecmp(subject, "user") == 0) { 209 id = parse_user(textid); 210 pwd = getpwuid(id); 211 if (pwd != NULL) 212 textid = pwd->pw_name; 213 } else if (strcasecmp(subject, "group") == 0) { 214 id = parse_group(textid); 215 grp = getgrgid(id); 216 if (grp != NULL) 217 textid = grp->gr_name; 218 } 219 220 asprintf(&humanized, "%s:%s:%s", subject, textid, rest); 221 222 if (humanized == NULL) 223 err(1, "asprintf"); 224 225 return (humanized); 226 } 227 228 static int 229 str2int64(const char *str, int64_t *value) 230 { 231 char *end; 232 233 if (str == NULL) 234 return (EINVAL); 235 236 *value = strtoul(str, &end, 10); 237 if ((size_t)(end - str) != strlen(str)) 238 return (EINVAL); 239 240 return (0); 241 } 242 243 static char * 244 humanize_amount(char *rule) 245 { 246 int64_t num; 247 const char *subject, *subject_id, *resource, *action, *amount, *per; 248 char *copy, *humanized, buf[6]; 249 250 copy = strdup(rule); 251 if (copy == NULL) 252 err(1, "strdup"); 253 254 subject = strsep(©, ":"); 255 subject_id = strsep(©, ":"); 256 resource = strsep(©, ":"); 257 action = strsep(©, "=/"); 258 amount = strsep(©, "/"); 259 per = copy; 260 261 if (amount == NULL || strlen(amount) == 0 || 262 str2int64(amount, &num) != 0) { 263 free(copy); 264 return (rule); 265 } 266 267 assert(subject != NULL); 268 assert(subject_id != NULL); 269 assert(resource != NULL); 270 assert(action != NULL); 271 272 if (humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE, 273 HN_DECIMAL | HN_NOSPACE) == -1) 274 err(1, "humanize_number"); 275 276 if (per == NULL) 277 asprintf(&humanized, "%s:%s:%s:%s=%s", subject, subject_id, 278 resource, action, buf); 279 else 280 asprintf(&humanized, "%s:%s:%s:%s=%s/%s", subject, subject_id, 281 resource, action, buf, per); 282 283 if (humanized == NULL) 284 err(1, "asprintf"); 285 286 return (humanized); 287 } 288 289 /* 290 * Print rules, one per line. 291 */ 292 static void 293 print_rules(char *rules, int hflag, int nflag) 294 { 295 char *rule; 296 297 while ((rule = strsep(&rules, ",")) != NULL) { 298 if (rule[0] == '\0') 299 break; /* XXX */ 300 if (nflag == 0) 301 rule = humanize_ids(rule); 302 if (hflag) 303 rule = humanize_amount(rule); 304 printf("%s\n", rule); 305 } 306 } 307 308 static void 309 enosys(void) 310 { 311 int error, racct_enable; 312 size_t racct_enable_len; 313 314 racct_enable_len = sizeof(racct_enable); 315 error = sysctlbyname("kern.racct.enable", 316 &racct_enable, &racct_enable_len, NULL, 0); 317 318 if (error != 0) { 319 if (errno == ENOENT) 320 errx(1, "RACCT/RCTL support not present in kernel; see rctl(8) for details"); 321 322 err(1, "sysctlbyname"); 323 } 324 325 if (racct_enable == 0) 326 errx(1, "RACCT/RCTL present, but disabled; enable using kern.racct.enable=1 tunable"); 327 } 328 329 static void 330 add_rule(char *rule) 331 { 332 int error; 333 334 error = rctl_add_rule(rule, strlen(rule) + 1, NULL, 0); 335 if (error != 0) { 336 if (errno == ENOSYS) 337 enosys(); 338 err(1, "rctl_add_rule"); 339 } 340 free(rule); 341 } 342 343 static void 344 show_limits(char *filter, int hflag, int nflag) 345 { 346 int error; 347 char *outbuf = NULL; 348 size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4; 349 350 do { 351 outbuflen *= 4; 352 outbuf = realloc(outbuf, outbuflen); 353 if (outbuf == NULL) 354 err(1, "realloc"); 355 356 error = rctl_get_limits(filter, strlen(filter) + 1, outbuf, 357 outbuflen); 358 if (error && errno != ERANGE) { 359 if (errno == ENOSYS) 360 enosys(); 361 err(1, "rctl_get_limits"); 362 } 363 } while (error && errno == ERANGE); 364 365 print_rules(outbuf, hflag, nflag); 366 free(filter); 367 free(outbuf); 368 } 369 370 static void 371 remove_rule(char *filter) 372 { 373 int error; 374 375 error = rctl_remove_rule(filter, strlen(filter) + 1, NULL, 0); 376 if (error != 0) { 377 if (errno == ENOSYS) 378 enosys(); 379 err(1, "rctl_remove_rule"); 380 } 381 free(filter); 382 } 383 384 static char * 385 humanize_usage_amount(char *usage) 386 { 387 int64_t num; 388 const char *resource, *amount; 389 char *copy, *humanized, buf[6]; 390 391 copy = strdup(usage); 392 if (copy == NULL) 393 err(1, "strdup"); 394 395 resource = strsep(©, "="); 396 amount = copy; 397 398 assert(resource != NULL); 399 assert(amount != NULL); 400 401 if (str2int64(amount, &num) != 0 || 402 humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE, 403 HN_DECIMAL | HN_NOSPACE) == -1) { 404 free(copy); 405 return (usage); 406 } 407 408 asprintf(&humanized, "%s=%s", resource, buf); 409 if (humanized == NULL) 410 err(1, "asprintf"); 411 412 return (humanized); 413 } 414 415 /* 416 * Query the kernel about a resource usage and print it out. 417 */ 418 static void 419 show_usage(char *filter, int hflag) 420 { 421 int error; 422 char *outbuf = NULL, *tmp; 423 size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4; 424 425 do { 426 outbuflen *= 4; 427 outbuf = realloc(outbuf, outbuflen); 428 if (outbuf == NULL) 429 err(1, "realloc"); 430 431 error = rctl_get_racct(filter, strlen(filter) + 1, outbuf, 432 outbuflen); 433 if (error && errno != ERANGE) { 434 if (errno == ENOSYS) 435 enosys(); 436 err(1, "rctl_get_racct"); 437 } 438 } while (error && errno == ERANGE); 439 440 while ((tmp = strsep(&outbuf, ",")) != NULL) { 441 if (tmp[0] == '\0') 442 break; /* XXX */ 443 444 if (hflag) 445 tmp = humanize_usage_amount(tmp); 446 447 printf("%s\n", tmp); 448 } 449 450 free(filter); 451 free(outbuf); 452 } 453 454 /* 455 * Query the kernel about resource limit rules and print them out. 456 */ 457 static void 458 show_rules(char *filter, int hflag, int nflag) 459 { 460 int error; 461 char *outbuf = NULL; 462 size_t filterlen, outbuflen = RCTL_DEFAULT_BUFSIZE / 4; 463 464 if (filter != NULL) 465 filterlen = strlen(filter) + 1; 466 else 467 filterlen = 0; 468 469 do { 470 outbuflen *= 4; 471 outbuf = realloc(outbuf, outbuflen); 472 if (outbuf == NULL) 473 err(1, "realloc"); 474 475 error = rctl_get_rules(filter, filterlen, outbuf, outbuflen); 476 if (error && errno != ERANGE) { 477 if (errno == ENOSYS) 478 enosys(); 479 err(1, "rctl_get_rules"); 480 } 481 } while (error && errno == ERANGE); 482 483 print_rules(outbuf, hflag, nflag); 484 free(outbuf); 485 } 486 487 static void 488 usage(void) 489 { 490 491 fprintf(stderr, "usage: rctl [ -h ] [-a rule | -l filter | -r filter " 492 "| -u filter | filter]\n"); 493 exit(1); 494 } 495 496 int 497 main(int argc __unused, char **argv __unused) 498 { 499 int ch, aflag = 0, hflag = 0, nflag = 0, lflag = 0, rflag = 0, 500 uflag = 0; 501 char *rule = NULL; 502 503 while ((ch = getopt(argc, argv, "a:hl:nr:u:")) != -1) { 504 switch (ch) { 505 case 'a': 506 aflag = 1; 507 rule = strdup(optarg); 508 break; 509 case 'h': 510 hflag = 1; 511 break; 512 case 'l': 513 lflag = 1; 514 rule = strdup(optarg); 515 break; 516 case 'n': 517 nflag = 1; 518 break; 519 case 'r': 520 rflag = 1; 521 rule = strdup(optarg); 522 break; 523 case 'u': 524 uflag = 1; 525 rule = strdup(optarg); 526 break; 527 528 case '?': 529 default: 530 usage(); 531 } 532 } 533 534 argc -= optind; 535 argv += optind; 536 537 if (argc > 1) 538 usage(); 539 540 if (rule == NULL) { 541 if (argc == 1) 542 rule = strdup(argv[0]); 543 else 544 rule = strdup("::"); 545 } 546 547 if (aflag + lflag + rflag + uflag + argc > 1) 548 errx(1, "only one flag or argument may be specified " 549 "at the same time"); 550 551 rule = resolve_ids(rule); 552 rule = expand_amount(rule); 553 554 if (aflag) { 555 add_rule(rule); 556 return (0); 557 } 558 559 if (lflag) { 560 show_limits(rule, hflag, nflag); 561 return (0); 562 } 563 564 if (rflag) { 565 remove_rule(rule); 566 return (0); 567 } 568 569 if (uflag) { 570 show_usage(rule, hflag); 571 return (0); 572 } 573 574 show_rules(rule, hflag, nflag); 575 return (0); 576 } 577