1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2010 The FreeBSD Foundation
5 *
6 * This software was developed by Edward Tomasz Napierala under sponsorship
7 * from the FreeBSD Foundation.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31 #include <sys/types.h>
32 #include <sys/rctl.h>
33 #include <sys/sysctl.h>
34 #include <assert.h>
35 #include <ctype.h>
36 #include <err.h>
37 #include <errno.h>
38 #include <getopt.h>
39 #include <grp.h>
40 #include <libutil.h>
41 #include <pwd.h>
42 #include <stdbool.h>
43 #include <stdint.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47
48 #define RCTL_DEFAULT_BUFSIZE 128 * 1024
49
50 static int
parse_user(const char * s,id_t * uidp,const char * unexpanded_rule)51 parse_user(const char *s, id_t *uidp, const char *unexpanded_rule)
52 {
53 char *end;
54 struct passwd *pwd;
55
56 pwd = getpwnam(s);
57 if (pwd != NULL) {
58 *uidp = pwd->pw_uid;
59 return (0);
60 }
61
62 if (!isnumber(s[0])) {
63 warnx("malformed rule '%s': unknown user '%s'",
64 unexpanded_rule, s);
65 return (1);
66 }
67
68 *uidp = strtod(s, &end);
69 if ((size_t)(end - s) != strlen(s)) {
70 warnx("malformed rule '%s': trailing characters "
71 "after numerical id", unexpanded_rule);
72 return (1);
73 }
74
75 return (0);
76 }
77
78 static int
parse_group(const char * s,id_t * gidp,const char * unexpanded_rule)79 parse_group(const char *s, id_t *gidp, const char *unexpanded_rule)
80 {
81 char *end;
82 struct group *grp;
83
84 grp = getgrnam(s);
85 if (grp != NULL) {
86 *gidp = grp->gr_gid;
87 return (0);
88 }
89
90 if (!isnumber(s[0])) {
91 warnx("malformed rule '%s': unknown group '%s'",
92 unexpanded_rule, s);
93 return (1);
94 }
95
96 *gidp = strtod(s, &end);
97 if ((size_t)(end - s) != strlen(s)) {
98 warnx("malformed rule '%s': trailing characters "
99 "after numerical id", unexpanded_rule);
100 return (1);
101 }
102
103 return (0);
104 }
105
106 /*
107 * Replace human-readable number with its expanded form.
108 */
109 static char *
expand_amount(const char * rule,const char * unexpanded_rule)110 expand_amount(const char *rule, const char *unexpanded_rule)
111 {
112 uint64_t num;
113 const char *subject, *subject_id, *resource, *action, *amount, *per;
114 char *copy, *expanded, *tofree;
115 int ret;
116
117 tofree = copy = strdup(rule);
118 if (copy == NULL) {
119 warn("strdup");
120 return (NULL);
121 }
122
123 subject = strsep(©, ":");
124 subject_id = strsep(©, ":");
125 resource = strsep(©, ":");
126 action = strsep(©, "=/");
127 amount = strsep(©, "/");
128 per = copy;
129
130 if (amount == NULL || strlen(amount) == 0) {
131 /*
132 * The "copy" has already been tinkered with by strsep().
133 */
134 free(tofree);
135 copy = strdup(rule);
136 if (copy == NULL) {
137 warn("strdup");
138 return (NULL);
139 }
140 return (copy);
141 }
142
143 assert(subject != NULL);
144 assert(subject_id != NULL);
145 assert(resource != NULL);
146 assert(action != NULL);
147
148 if (expand_number(amount, &num)) {
149 warnx("malformed rule '%s': invalid numeric value '%s'",
150 unexpanded_rule, amount);
151 free(tofree);
152 return (NULL);
153 }
154
155 if (per == NULL) {
156 ret = asprintf(&expanded, "%s:%s:%s:%s=%ju",
157 subject, subject_id, resource, action, (uintmax_t)num);
158 } else {
159 ret = asprintf(&expanded, "%s:%s:%s:%s=%ju/%s",
160 subject, subject_id, resource, action, (uintmax_t)num, per);
161 }
162
163 if (ret <= 0) {
164 warn("asprintf");
165 free(tofree);
166 return (NULL);
167 }
168
169 free(tofree);
170
171 return (expanded);
172 }
173
174 static char *
expand_rule(const char * rule,bool resolve_ids)175 expand_rule(const char *rule, bool resolve_ids)
176 {
177 id_t id;
178 const char *subject, *textid, *rest;
179 char *copy, *expanded, *resolved, *tofree;
180 int error, ret;
181
182 tofree = copy = strdup(rule);
183 if (copy == NULL) {
184 warn("strdup");
185 return (NULL);
186 }
187
188 subject = strsep(©, ":");
189 textid = strsep(©, ":");
190 if (textid == NULL) {
191 warnx("malformed rule '%s': missing subject", rule);
192 return (NULL);
193 }
194 if (copy != NULL)
195 rest = copy;
196 else
197 rest = "";
198
199 if (strcasecmp(subject, "u") == 0)
200 subject = "user";
201 else if (strcasecmp(subject, "g") == 0)
202 subject = "group";
203 else if (strcasecmp(subject, "p") == 0)
204 subject = "process";
205 else if (strcasecmp(subject, "l") == 0 ||
206 strcasecmp(subject, "c") == 0 ||
207 strcasecmp(subject, "class") == 0)
208 subject = "loginclass";
209 else if (strcasecmp(subject, "j") == 0)
210 subject = "jail";
211
212 if (resolve_ids &&
213 strcasecmp(subject, "user") == 0 && strlen(textid) > 0) {
214 error = parse_user(textid, &id, rule);
215 if (error != 0) {
216 free(tofree);
217 return (NULL);
218 }
219 ret = asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest);
220 } else if (resolve_ids &&
221 strcasecmp(subject, "group") == 0 && strlen(textid) > 0) {
222 error = parse_group(textid, &id, rule);
223 if (error != 0) {
224 free(tofree);
225 return (NULL);
226 }
227 ret = asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest);
228 } else {
229 ret = asprintf(&resolved, "%s:%s:%s", subject, textid, rest);
230 }
231
232 if (ret <= 0) {
233 warn("asprintf");
234 free(tofree);
235 return (NULL);
236 }
237
238 free(tofree);
239
240 expanded = expand_amount(resolved, rule);
241 free(resolved);
242
243 return (expanded);
244 }
245
246 static char *
humanize_ids(char * rule)247 humanize_ids(char *rule)
248 {
249 id_t id;
250 struct passwd *pwd;
251 struct group *grp;
252 const char *subject, *textid, *rest;
253 char *end, *humanized;
254 int ret;
255
256 subject = strsep(&rule, ":");
257 textid = strsep(&rule, ":");
258 if (textid == NULL)
259 errx(1, "rule passed from the kernel didn't contain subject");
260 if (rule != NULL)
261 rest = rule;
262 else
263 rest = "";
264
265 /* Replace numerical user and group ids with names. */
266 if (strcasecmp(subject, "user") == 0) {
267 id = strtod(textid, &end);
268 if ((size_t)(end - textid) != strlen(textid))
269 errx(1, "malformed uid '%s'", textid);
270 pwd = getpwuid(id);
271 if (pwd != NULL)
272 textid = pwd->pw_name;
273 } else if (strcasecmp(subject, "group") == 0) {
274 id = strtod(textid, &end);
275 if ((size_t)(end - textid) != strlen(textid))
276 errx(1, "malformed gid '%s'", textid);
277 grp = getgrgid(id);
278 if (grp != NULL)
279 textid = grp->gr_name;
280 }
281
282 ret = asprintf(&humanized, "%s:%s:%s", subject, textid, rest);
283 if (ret <= 0)
284 err(1, "asprintf");
285
286 return (humanized);
287 }
288
289 static int
str2int64(const char * str,int64_t * value)290 str2int64(const char *str, int64_t *value)
291 {
292 char *end;
293
294 if (str == NULL)
295 return (EINVAL);
296
297 *value = strtoul(str, &end, 10);
298 if ((size_t)(end - str) != strlen(str))
299 return (EINVAL);
300
301 return (0);
302 }
303
304 static char *
humanize_amount(char * rule)305 humanize_amount(char *rule)
306 {
307 int64_t num;
308 const char *subject, *subject_id, *resource, *action, *amount, *per;
309 char *copy, *humanized, buf[6], *tofree;
310 int ret;
311
312 tofree = copy = strdup(rule);
313 if (copy == NULL)
314 err(1, "strdup");
315
316 subject = strsep(©, ":");
317 subject_id = strsep(©, ":");
318 resource = strsep(©, ":");
319 action = strsep(©, "=/");
320 amount = strsep(©, "/");
321 per = copy;
322
323 if (amount == NULL || strlen(amount) == 0 ||
324 str2int64(amount, &num) != 0) {
325 free(tofree);
326 return (rule);
327 }
328
329 assert(subject != NULL);
330 assert(subject_id != NULL);
331 assert(resource != NULL);
332 assert(action != NULL);
333
334 if (humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE,
335 HN_DECIMAL | HN_NOSPACE) == -1)
336 err(1, "humanize_number");
337
338 if (per == NULL) {
339 ret = asprintf(&humanized, "%s:%s:%s:%s=%s",
340 subject, subject_id, resource, action, buf);
341 } else {
342 ret = asprintf(&humanized, "%s:%s:%s:%s=%s/%s",
343 subject, subject_id, resource, action, buf, per);
344 }
345
346 if (ret <= 0)
347 err(1, "asprintf");
348
349 free(tofree);
350 return (humanized);
351 }
352
353 /*
354 * Print rules, one per line.
355 */
356 static void
print_rules(char * rules,int hflag,int nflag)357 print_rules(char *rules, int hflag, int nflag)
358 {
359 char *rule;
360
361 while ((rule = strsep(&rules, ",")) != NULL) {
362 if (rule[0] == '\0')
363 break; /* XXX */
364 if (nflag == 0)
365 rule = humanize_ids(rule);
366 if (hflag)
367 rule = humanize_amount(rule);
368 printf("%s\n", rule);
369 }
370 }
371
372 static void
enosys(void)373 enosys(void)
374 {
375 size_t racct_enable_len;
376 int error;
377 bool racct_enable;
378
379 racct_enable_len = sizeof(racct_enable);
380 error = sysctlbyname("kern.racct.enable",
381 &racct_enable, &racct_enable_len, NULL, 0);
382
383 if (error != 0) {
384 if (errno == ENOENT)
385 errx(1, "RACCT/RCTL support not present in kernel; see rctl(8) for details");
386
387 err(1, "sysctlbyname");
388 }
389
390 if (!racct_enable)
391 errx(1, "RACCT/RCTL present, but disabled; enable using kern.racct.enable=1 tunable");
392 }
393
394 static int
add_rule(const char * rule,const char * unexpanded_rule)395 add_rule(const char *rule, const char *unexpanded_rule)
396 {
397 int error;
398
399 error = rctl_add_rule(rule, strlen(rule) + 1, NULL, 0);
400 if (error != 0) {
401 if (errno == ENOSYS)
402 enosys();
403 warn("failed to add rule '%s'", unexpanded_rule);
404 }
405
406 return (error);
407 }
408
409 static int
show_limits(const char * filter,const char * unexpanded_rule,int hflag,int nflag)410 show_limits(const char *filter, const char *unexpanded_rule,
411 int hflag, int nflag)
412 {
413 int error;
414 char *outbuf = NULL;
415 size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
416
417 for (;;) {
418 outbuflen *= 4;
419 outbuf = realloc(outbuf, outbuflen);
420 if (outbuf == NULL)
421 err(1, "realloc");
422 error = rctl_get_limits(filter, strlen(filter) + 1,
423 outbuf, outbuflen);
424 if (error == 0)
425 break;
426 if (errno == ERANGE)
427 continue;
428 if (errno == ENOSYS)
429 enosys();
430 warn("failed to get limits for '%s'", unexpanded_rule);
431 free(outbuf);
432
433 return (error);
434 }
435
436 print_rules(outbuf, hflag, nflag);
437 free(outbuf);
438
439 return (error);
440 }
441
442 static int
remove_rule(const char * filter,const char * unexpanded_rule)443 remove_rule(const char *filter, const char *unexpanded_rule)
444 {
445 int error;
446
447 error = rctl_remove_rule(filter, strlen(filter) + 1, NULL, 0);
448 if (error != 0) {
449 if (errno == ENOSYS)
450 enosys();
451 warn("failed to remove rule '%s'", unexpanded_rule);
452 }
453
454 return (error);
455 }
456
457 static char *
humanize_usage_amount(char * usage)458 humanize_usage_amount(char *usage)
459 {
460 int64_t num;
461 const char *resource, *amount;
462 char *copy, *humanized, buf[6], *tofree;
463 int ret;
464
465 tofree = copy = strdup(usage);
466 if (copy == NULL)
467 err(1, "strdup");
468
469 resource = strsep(©, "=");
470 amount = copy;
471
472 assert(resource != NULL);
473 assert(amount != NULL);
474
475 if (str2int64(amount, &num) != 0 ||
476 humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE,
477 HN_DECIMAL | HN_NOSPACE) == -1) {
478 free(tofree);
479 return (usage);
480 }
481
482 ret = asprintf(&humanized, "%s=%s", resource, buf);
483 if (ret <= 0)
484 err(1, "asprintf");
485
486 free(tofree);
487 return (humanized);
488 }
489
490 /*
491 * Query the kernel about a resource usage and print it out.
492 */
493 static int
show_usage(const char * filter,const char * unexpanded_rule,int hflag)494 show_usage(const char *filter, const char *unexpanded_rule, int hflag)
495 {
496 int error;
497 char *copy, *outbuf = NULL, *tmp;
498 size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
499
500 for (;;) {
501 outbuflen *= 4;
502 outbuf = realloc(outbuf, outbuflen);
503 if (outbuf == NULL)
504 err(1, "realloc");
505 error = rctl_get_racct(filter, strlen(filter) + 1,
506 outbuf, outbuflen);
507 if (error == 0)
508 break;
509 if (errno == ERANGE)
510 continue;
511 if (errno == ENOSYS)
512 enosys();
513 warn("failed to show resource consumption for '%s'",
514 unexpanded_rule);
515 free(outbuf);
516
517 return (error);
518 }
519
520 copy = outbuf;
521 while ((tmp = strsep(©, ",")) != NULL) {
522 if (tmp[0] == '\0')
523 break; /* XXX */
524
525 if (hflag)
526 tmp = humanize_usage_amount(tmp);
527
528 printf("%s\n", tmp);
529 }
530
531 free(outbuf);
532
533 return (error);
534 }
535
536 /*
537 * Query the kernel about resource limit rules and print them out.
538 */
539 static int
show_rules(const char * filter,const char * unexpanded_rule,int hflag,int nflag)540 show_rules(const char *filter, const char *unexpanded_rule,
541 int hflag, int nflag)
542 {
543 int error;
544 char *outbuf = NULL;
545 size_t filterlen, outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
546
547 if (filter != NULL)
548 filterlen = strlen(filter) + 1;
549 else
550 filterlen = 0;
551
552 for (;;) {
553 outbuflen *= 4;
554 outbuf = realloc(outbuf, outbuflen);
555 if (outbuf == NULL)
556 err(1, "realloc");
557 error = rctl_get_rules(filter, filterlen, outbuf, outbuflen);
558 if (error == 0)
559 break;
560 if (errno == ERANGE)
561 continue;
562 if (errno == ENOSYS)
563 enosys();
564 warn("failed to show rules for '%s'", unexpanded_rule);
565 free(outbuf);
566
567 return (error);
568 }
569
570 print_rules(outbuf, hflag, nflag);
571 free(outbuf);
572
573 return (error);
574 }
575
576 static void
usage(void)577 usage(void)
578 {
579
580 fprintf(stderr, "usage: rctl [ -h ] [-a rule | -l filter | -r filter "
581 "| -u filter | filter]\n");
582 exit(1);
583 }
584
585 int
main(int argc,char ** argv)586 main(int argc, char **argv)
587 {
588 int ch, aflag = 0, hflag = 0, nflag = 0, lflag = 0, rflag = 0,
589 uflag = 0;
590 char *rule = NULL, *unexpanded_rule;
591 int i, cumulated_error, error;
592
593 while ((ch = getopt(argc, argv, "ahlnru")) != -1) {
594 switch (ch) {
595 case 'a':
596 aflag = 1;
597 break;
598 case 'h':
599 hflag = 1;
600 break;
601 case 'l':
602 lflag = 1;
603 break;
604 case 'n':
605 nflag = 1;
606 break;
607 case 'r':
608 rflag = 1;
609 break;
610 case 'u':
611 uflag = 1;
612 break;
613
614 case '?':
615 default:
616 usage();
617 }
618 }
619
620 argc -= optind;
621 argv += optind;
622
623 if (aflag + lflag + rflag + uflag > 1)
624 errx(1, "at most one of -a, -l, -r, or -u may be specified");
625
626 if (argc == 0) {
627 if (aflag + lflag + rflag + uflag == 0) {
628 rule = strdup("::");
629 show_rules(rule, rule, hflag, nflag);
630
631 return (0);
632 }
633
634 usage();
635 }
636
637 cumulated_error = 0;
638
639 for (i = 0; i < argc; i++) {
640 unexpanded_rule = argv[i];
641
642 /*
643 * Skip resolving if passed -n _and_ -a. Ignore -n otherwise,
644 * so we can still do "rctl -n u:root" and see the rules without
645 * resolving the UID.
646 */
647 if (aflag != 0 && nflag != 0)
648 rule = expand_rule(unexpanded_rule, false);
649 else
650 rule = expand_rule(unexpanded_rule, true);
651
652 if (rule == NULL) {
653 cumulated_error++;
654 continue;
655 }
656
657 /*
658 * The reason for passing the unexpanded_rule is to make
659 * it easier for the user to search for the problematic
660 * rule in the passed input.
661 */
662 if (aflag) {
663 error = add_rule(rule, unexpanded_rule);
664 } else if (lflag) {
665 error = show_limits(rule, unexpanded_rule,
666 hflag, nflag);
667 } else if (rflag) {
668 error = remove_rule(rule, unexpanded_rule);
669 } else if (uflag) {
670 error = show_usage(rule, unexpanded_rule, hflag);
671 } else {
672 error = show_rules(rule, unexpanded_rule,
673 hflag, nflag);
674 }
675
676 if (error != 0)
677 cumulated_error++;
678
679 free(rule);
680 }
681
682 return (cumulated_error);
683 }
684