xref: /freebsd/usr.bin/rctl/rctl.c (revision b1f92fa22938fe29ab7e53692ffe0ed7a0ecc4d0)
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(&copy, ":");
173 	textid = strsep(&copy, ":");
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(&copy, ":");
301 	subject_id = strsep(&copy, ":");
302 	resource = strsep(&copy, ":");
303 	action = strsep(&copy, "=/");
304 	amount = strsep(&copy, "/");
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(&copy, "=");
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(&copy, ",")) != 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