1220166Strasz/*- 2220166Strasz * Copyright (c) 2010 The FreeBSD Foundation 3220166Strasz * All rights reserved. 4220166Strasz * 5220166Strasz * This software was developed by Edward Tomasz Napierala under sponsorship 6220166Strasz * from the FreeBSD Foundation. 7220166Strasz * 8220166Strasz * Redistribution and use in source and binary forms, with or without 9220166Strasz * modification, are permitted provided that the following conditions 10220166Strasz * are met: 11220166Strasz * 1. Redistributions of source code must retain the above copyright 12220166Strasz * notice, this list of conditions and the following disclaimer. 13220166Strasz * 2. Redistributions in binary form must reproduce the above copyright 14220166Strasz * notice, this list of conditions and the following disclaimer in the 15220166Strasz * documentation and/or other materials provided with the distribution. 16220166Strasz * 17220166Strasz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18220166Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19220166Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20220166Strasz * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21220166Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22220166Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23220166Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24220166Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25220166Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26220166Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27220166Strasz * SUCH DAMAGE. 28220166Strasz * 29220166Strasz * $FreeBSD$ 30220166Strasz */ 31220166Strasz 32220166Strasz#include <sys/cdefs.h> 33220166Strasz__FBSDID("$FreeBSD$"); 34220166Strasz 35220166Strasz#include <sys/types.h> 36220166Strasz#include <sys/rctl.h> 37220166Strasz#include <assert.h> 38220166Strasz#include <ctype.h> 39220166Strasz#include <err.h> 40220166Strasz#include <errno.h> 41220166Strasz#include <getopt.h> 42220166Strasz#include <grp.h> 43220166Strasz#include <libutil.h> 44220166Strasz#include <pwd.h> 45220166Strasz#include <stdint.h> 46220166Strasz#include <stdio.h> 47220166Strasz#include <stdlib.h> 48220166Strasz#include <string.h> 49220166Strasz 50220166Strasz#define RCTL_DEFAULT_BUFSIZE 4096 51220166Strasz 52220166Straszstatic id_t 53220166Straszparse_user(const char *s) 54220166Strasz{ 55220166Strasz id_t id; 56220166Strasz char *end; 57220166Strasz struct passwd *pwd; 58220166Strasz 59220166Strasz pwd = getpwnam(s); 60220166Strasz if (pwd != NULL) 61220166Strasz return (pwd->pw_uid); 62220166Strasz 63220166Strasz if (!isnumber(s[0])) 64220166Strasz errx(1, "uknown user '%s'", s); 65220166Strasz 66220166Strasz id = strtod(s, &end); 67220166Strasz if ((size_t)(end - s) != strlen(s)) 68220166Strasz errx(1, "trailing characters after numerical id"); 69220166Strasz 70220166Strasz return (id); 71220166Strasz} 72220166Strasz 73220166Straszstatic id_t 74220166Straszparse_group(const char *s) 75220166Strasz{ 76220166Strasz id_t id; 77220166Strasz char *end; 78220166Strasz struct group *grp; 79220166Strasz 80220166Strasz grp = getgrnam(s); 81220166Strasz if (grp != NULL) 82220166Strasz return (grp->gr_gid); 83220166Strasz 84220166Strasz if (!isnumber(s[0])) 85220166Strasz errx(1, "uknown group '%s'", s); 86220166Strasz 87220166Strasz id = strtod(s, &end); 88220166Strasz if ((size_t)(end - s) != strlen(s)) 89220166Strasz errx(1, "trailing characters after numerical id"); 90220166Strasz 91220166Strasz return (id); 92220166Strasz} 93220166Strasz 94220166Strasz/* 95220166Strasz * This routine replaces user/group name with numeric id. 96220166Strasz */ 97220166Straszstatic char * 98220166Straszresolve_ids(char *rule) 99220166Strasz{ 100220166Strasz id_t id; 101220166Strasz const char *subject, *textid, *rest; 102220166Strasz char *resolved; 103220166Strasz 104220166Strasz subject = strsep(&rule, ":"); 105220166Strasz textid = strsep(&rule, ":"); 106220166Strasz if (textid == NULL) 107220166Strasz errx(1, "error in rule specification -- no subject"); 108220166Strasz if (rule != NULL) 109220166Strasz rest = rule; 110220166Strasz else 111220166Strasz rest = ""; 112220166Strasz 113220166Strasz if (strcasecmp(subject, "u") == 0) 114220166Strasz subject = "user"; 115220166Strasz else if (strcasecmp(subject, "g") == 0) 116220166Strasz subject = "group"; 117220166Strasz else if (strcasecmp(subject, "p") == 0) 118220166Strasz subject = "process"; 119220166Strasz else if (strcasecmp(subject, "l") == 0 || 120220166Strasz strcasecmp(subject, "c") == 0 || 121220166Strasz strcasecmp(subject, "class") == 0) 122220166Strasz subject = "loginclass"; 123220166Strasz else if (strcasecmp(subject, "j") == 0) 124220166Strasz subject = "jail"; 125220166Strasz 126220166Strasz if (strcasecmp(subject, "user") == 0 && strlen(textid) > 0) { 127220166Strasz id = parse_user(textid); 128220166Strasz asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); 129220166Strasz } else if (strcasecmp(subject, "group") == 0 && strlen(textid) > 0) { 130220166Strasz id = parse_group(textid); 131220166Strasz asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); 132220166Strasz } else 133220166Strasz asprintf(&resolved, "%s:%s:%s", subject, textid, rest); 134220166Strasz 135220166Strasz if (resolved == NULL) 136220166Strasz err(1, "asprintf"); 137220166Strasz 138220166Strasz return (resolved); 139220166Strasz} 140220166Strasz 141220166Strasz/* 142220166Strasz * This routine replaces "human-readable" number with its expanded form. 143220166Strasz */ 144220166Straszstatic char * 145220166Straszexpand_amount(char *rule) 146220166Strasz{ 147220166Strasz uint64_t num; 148220166Strasz const char *subject, *subject_id, *resource, *action, *amount, *per; 149220166Strasz char *copy, *expanded; 150220166Strasz 151220166Strasz copy = strdup(rule); 152220166Strasz if (copy == NULL) 153220166Strasz err(1, "strdup"); 154220166Strasz 155220166Strasz subject = strsep(©, ":"); 156220166Strasz subject_id = strsep(©, ":"); 157220166Strasz resource = strsep(©, ":"); 158220166Strasz action = strsep(©, "=/"); 159220166Strasz amount = strsep(©, "/"); 160220166Strasz per = copy; 161220166Strasz 162220166Strasz if (amount == NULL || strlen(amount) == 0) { 163220166Strasz free(copy); 164220166Strasz return (rule); 165220166Strasz } 166220166Strasz 167220166Strasz assert(subject != NULL); 168220166Strasz assert(subject_id != NULL); 169220166Strasz assert(resource != NULL); 170220166Strasz assert(action != NULL); 171220166Strasz 172220166Strasz if (expand_number(amount, &num)) 173220166Strasz err(1, "expand_number"); 174220166Strasz 175220166Strasz if (per == NULL) 176220166Strasz asprintf(&expanded, "%s:%s:%s:%s=%ju", subject, subject_id, 177220166Strasz resource, action, (uintmax_t)num); 178220166Strasz else 179220166Strasz asprintf(&expanded, "%s:%s:%s:%s=%ju/%s", subject, subject_id, 180220166Strasz resource, action, (uintmax_t)num, per); 181220166Strasz 182220166Strasz if (expanded == NULL) 183220166Strasz err(1, "asprintf"); 184220166Strasz 185220166Strasz return (expanded); 186220166Strasz} 187220166Strasz 188220166Straszstatic char * 189220166Straszhumanize_ids(char *rule) 190220166Strasz{ 191220166Strasz id_t id; 192220166Strasz struct passwd *pwd; 193220166Strasz struct group *grp; 194220166Strasz const char *subject, *textid, *rest; 195220166Strasz char *humanized; 196220166Strasz 197220166Strasz subject = strsep(&rule, ":"); 198220166Strasz textid = strsep(&rule, ":"); 199220166Strasz if (textid == NULL) 200220166Strasz errx(1, "rule passed from the kernel didn't contain subject"); 201220166Strasz if (rule != NULL) 202220166Strasz rest = rule; 203220166Strasz else 204220166Strasz rest = ""; 205220166Strasz 206220166Strasz /* Replace numerical user and group ids with names. */ 207220166Strasz if (strcasecmp(subject, "user") == 0) { 208220166Strasz id = parse_user(textid); 209220166Strasz pwd = getpwuid(id); 210220166Strasz if (pwd != NULL) 211220166Strasz textid = pwd->pw_name; 212220166Strasz } else if (strcasecmp(subject, "group") == 0) { 213220166Strasz id = parse_group(textid); 214220166Strasz grp = getgrgid(id); 215220166Strasz if (grp != NULL) 216220166Strasz textid = grp->gr_name; 217220166Strasz } 218220166Strasz 219220166Strasz asprintf(&humanized, "%s:%s:%s", subject, textid, rest); 220220166Strasz 221220166Strasz if (humanized == NULL) 222220166Strasz err(1, "asprintf"); 223220166Strasz 224220166Strasz return (humanized); 225220166Strasz} 226220166Strasz 227220166Straszstatic int 228220166Straszstr2int64(const char *str, int64_t *value) 229220166Strasz{ 230220166Strasz char *end; 231220166Strasz 232220166Strasz if (str == NULL) 233220166Strasz return (EINVAL); 234220166Strasz 235220166Strasz *value = strtoul(str, &end, 10); 236220166Strasz if ((size_t)(end - str) != strlen(str)) 237220166Strasz return (EINVAL); 238220166Strasz 239220166Strasz return (0); 240220166Strasz} 241220166Strasz 242220166Straszstatic char * 243220166Straszhumanize_amount(char *rule) 244220166Strasz{ 245220166Strasz int64_t num; 246220166Strasz const char *subject, *subject_id, *resource, *action, *amount, *per; 247220166Strasz char *copy, *humanized, buf[6]; 248220166Strasz 249220166Strasz copy = strdup(rule); 250220166Strasz if (copy == NULL) 251220166Strasz err(1, "strdup"); 252220166Strasz 253220166Strasz subject = strsep(©, ":"); 254220166Strasz subject_id = strsep(©, ":"); 255220166Strasz resource = strsep(©, ":"); 256220166Strasz action = strsep(©, "=/"); 257220166Strasz amount = strsep(©, "/"); 258220166Strasz per = copy; 259220166Strasz 260220166Strasz if (amount == NULL || strlen(amount) == 0 || 261220166Strasz str2int64(amount, &num) != 0) { 262220166Strasz free(copy); 263220166Strasz return (rule); 264220166Strasz } 265220166Strasz 266220166Strasz assert(subject != NULL); 267220166Strasz assert(subject_id != NULL); 268220166Strasz assert(resource != NULL); 269220166Strasz assert(action != NULL); 270220166Strasz 271220166Strasz if (humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE, 272220166Strasz HN_DECIMAL | HN_NOSPACE) == -1) 273220166Strasz err(1, "humanize_number"); 274220166Strasz 275220166Strasz if (per == NULL) 276220166Strasz asprintf(&humanized, "%s:%s:%s:%s=%s", subject, subject_id, 277220166Strasz resource, action, buf); 278220166Strasz else 279220166Strasz asprintf(&humanized, "%s:%s:%s:%s=%s/%s", subject, subject_id, 280220166Strasz resource, action, buf, per); 281220166Strasz 282220166Strasz if (humanized == NULL) 283220166Strasz err(1, "asprintf"); 284220166Strasz 285220166Strasz return (humanized); 286220166Strasz} 287220166Strasz 288220166Strasz/* 289220166Strasz * Print rules, one per line. 290220166Strasz */ 291220166Straszstatic void 292220166Straszprint_rules(char *rules, int hflag, int nflag) 293220166Strasz{ 294220166Strasz char *rule; 295220166Strasz 296220166Strasz while ((rule = strsep(&rules, ",")) != NULL) { 297220166Strasz if (rule[0] == '\0') 298220166Strasz break; /* XXX */ 299220166Strasz if (nflag == 0) 300220166Strasz rule = humanize_ids(rule); 301220166Strasz if (hflag) 302220166Strasz rule = humanize_amount(rule); 303220166Strasz printf("%s\n", rule); 304220166Strasz } 305220166Strasz} 306220166Strasz 307220166Straszstatic void 308220166Straszadd_rule(char *rule) 309220166Strasz{ 310220166Strasz int error; 311220166Strasz 312220166Strasz error = rctl_add_rule(rule, strlen(rule) + 1, NULL, 0); 313220166Strasz if (error != 0) 314220166Strasz err(1, "rctl_add_rule"); 315220166Strasz free(rule); 316220166Strasz} 317220166Strasz 318220166Straszstatic void 319220166Straszshow_limits(char *filter, int hflag, int nflag) 320220166Strasz{ 321220166Strasz int error; 322220166Strasz char *outbuf = NULL; 323220166Strasz size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4; 324220166Strasz 325220166Strasz do { 326220166Strasz outbuflen *= 4; 327220166Strasz outbuf = realloc(outbuf, outbuflen); 328220166Strasz if (outbuf == NULL) 329220166Strasz err(1, "realloc"); 330220166Strasz 331220166Strasz error = rctl_get_limits(filter, strlen(filter) + 1, outbuf, 332220166Strasz outbuflen); 333220166Strasz if (error && errno != ERANGE) 334220166Strasz err(1, "rctl_get_limits"); 335220166Strasz } while (error && errno == ERANGE); 336220166Strasz 337220166Strasz print_rules(outbuf, hflag, nflag); 338220166Strasz free(filter); 339220166Strasz free(outbuf); 340220166Strasz} 341220166Strasz 342220166Straszstatic void 343220166Straszremove_rule(char *filter) 344220166Strasz{ 345220166Strasz int error; 346220166Strasz 347220166Strasz error = rctl_remove_rule(filter, strlen(filter) + 1, NULL, 0); 348220166Strasz if (error != 0) 349220166Strasz err(1, "rctl_remove_rule"); 350220166Strasz free(filter); 351220166Strasz} 352220166Strasz 353220166Straszstatic char * 354220166Straszhumanize_usage_amount(char *usage) 355220166Strasz{ 356220166Strasz int64_t num; 357220166Strasz const char *resource, *amount; 358220166Strasz char *copy, *humanized, buf[6]; 359220166Strasz 360220166Strasz copy = strdup(usage); 361220166Strasz if (copy == NULL) 362220166Strasz err(1, "strdup"); 363220166Strasz 364220166Strasz resource = strsep(©, "="); 365220166Strasz amount = copy; 366220166Strasz 367220166Strasz assert(resource != NULL); 368220166Strasz assert(amount != NULL); 369220166Strasz 370220166Strasz if (str2int64(amount, &num) != 0 || 371220166Strasz humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE, 372220166Strasz HN_DECIMAL | HN_NOSPACE) == -1) { 373220166Strasz free(copy); 374220166Strasz return (usage); 375220166Strasz } 376220166Strasz 377220166Strasz asprintf(&humanized, "%s=%s", resource, buf); 378220166Strasz if (humanized == NULL) 379220166Strasz err(1, "asprintf"); 380220166Strasz 381220166Strasz return (humanized); 382220166Strasz} 383220166Strasz 384220166Strasz/* 385220166Strasz * Query the kernel about a resource usage and print it out. 386220166Strasz */ 387220166Straszstatic void 388220166Straszshow_usage(char *filter, int hflag) 389220166Strasz{ 390220166Strasz int error; 391220166Strasz char *outbuf = NULL, *tmp; 392220166Strasz size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4; 393220166Strasz 394220166Strasz do { 395220166Strasz outbuflen *= 4; 396220166Strasz outbuf = realloc(outbuf, outbuflen); 397220166Strasz if (outbuf == NULL) 398220166Strasz err(1, "realloc"); 399220166Strasz 400220166Strasz error = rctl_get_racct(filter, strlen(filter) + 1, outbuf, 401220166Strasz outbuflen); 402220166Strasz if (error && errno != ERANGE) 403220166Strasz err(1, "rctl_get_racct"); 404220166Strasz } while (error && errno == ERANGE); 405220166Strasz 406220166Strasz while ((tmp = strsep(&outbuf, ",")) != NULL) { 407220166Strasz if (tmp[0] == '\0') 408220166Strasz break; /* XXX */ 409220166Strasz 410220166Strasz if (hflag) 411220166Strasz tmp = humanize_usage_amount(tmp); 412220166Strasz 413220166Strasz printf("%s\n", tmp); 414220166Strasz } 415220166Strasz 416220166Strasz free(filter); 417220166Strasz free(outbuf); 418220166Strasz} 419220166Strasz 420220166Strasz/* 421220166Strasz * Query the kernel about resource limit rules and print them out. 422220166Strasz */ 423220166Straszstatic void 424220166Straszshow_rules(char *filter, int hflag, int nflag) 425220166Strasz{ 426220166Strasz int error; 427220166Strasz char *outbuf = NULL; 428220166Strasz size_t filterlen, outbuflen = RCTL_DEFAULT_BUFSIZE / 4; 429220166Strasz 430220166Strasz if (filter != NULL) 431220166Strasz filterlen = strlen(filter) + 1; 432220166Strasz else 433220166Strasz filterlen = 0; 434220166Strasz 435220166Strasz do { 436220166Strasz outbuflen *= 4; 437220166Strasz outbuf = realloc(outbuf, outbuflen); 438220166Strasz if (outbuf == NULL) 439220166Strasz err(1, "realloc"); 440220166Strasz 441220166Strasz error = rctl_get_rules(filter, filterlen, outbuf, outbuflen); 442220166Strasz if (error && errno != ERANGE) 443220166Strasz err(1, "rctl_get_rules"); 444220166Strasz } while (error && errno == ERANGE); 445220166Strasz 446220166Strasz print_rules(outbuf, hflag, nflag); 447220166Strasz free(outbuf); 448220166Strasz} 449220166Strasz 450220166Straszstatic void 451220166Straszusage(void) 452220166Strasz{ 453220166Strasz 454220166Strasz fprintf(stderr, "usage: rctl [ -h ] [-a rule | -l filter | -r filter " 455220166Strasz "| -u filter | filter]\n"); 456220166Strasz exit(1); 457220166Strasz} 458220166Strasz 459220166Straszint 460220166Straszmain(int argc __unused, char **argv __unused) 461220166Strasz{ 462220166Strasz int ch, aflag = 0, hflag = 0, nflag = 0, lflag = 0, rflag = 0, 463220166Strasz uflag = 0; 464220166Strasz char *rule = NULL; 465220166Strasz 466220166Strasz while ((ch = getopt(argc, argv, "a:hl:nr:u:")) != -1) { 467220166Strasz switch (ch) { 468220166Strasz case 'a': 469220166Strasz aflag = 1; 470220166Strasz rule = strdup(optarg); 471220166Strasz break; 472220166Strasz case 'h': 473220166Strasz hflag = 1; 474220166Strasz break; 475220166Strasz case 'l': 476220166Strasz lflag = 1; 477220166Strasz rule = strdup(optarg); 478220166Strasz break; 479220166Strasz case 'n': 480220166Strasz nflag = 1; 481220166Strasz break; 482220166Strasz case 'r': 483220166Strasz rflag = 1; 484220166Strasz rule = strdup(optarg); 485220166Strasz break; 486220166Strasz case 'u': 487220166Strasz uflag = 1; 488220166Strasz rule = strdup(optarg); 489220166Strasz break; 490220166Strasz 491220166Strasz case '?': 492220166Strasz default: 493220166Strasz usage(); 494220166Strasz } 495220166Strasz } 496220166Strasz 497220166Strasz argc -= optind; 498220166Strasz argv += optind; 499220166Strasz 500220166Strasz if (argc > 1) 501220166Strasz usage(); 502220166Strasz 503220166Strasz if (rule == NULL) { 504220166Strasz if (argc == 1) 505220166Strasz rule = strdup(argv[0]); 506220166Strasz else 507220166Strasz rule = strdup("::"); 508220166Strasz } 509220166Strasz 510220166Strasz if (aflag + lflag + rflag + uflag + argc > 1) 511220166Strasz errx(1, "only one flag or argument may be specified " 512220166Strasz "at the same time"); 513220166Strasz 514220166Strasz rule = resolve_ids(rule); 515220166Strasz rule = expand_amount(rule); 516220166Strasz 517220166Strasz if (aflag) { 518220166Strasz add_rule(rule); 519220166Strasz return (0); 520220166Strasz } 521220166Strasz 522220166Strasz if (lflag) { 523220166Strasz show_limits(rule, hflag, nflag); 524220166Strasz return (0); 525220166Strasz } 526220166Strasz 527220166Strasz if (rflag) { 528220166Strasz remove_rule(rule); 529220166Strasz return (0); 530220166Strasz } 531220166Strasz 532220166Strasz if (uflag) { 533220166Strasz show_usage(rule, hflag); 534220166Strasz return (0); 535220166Strasz } 536220166Strasz 537220166Strasz show_rules(rule, hflag, nflag); 538220166Strasz return (0); 539220166Strasz} 540