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