xref: /freebsd/usr.bin/rctl/rctl.c (revision 276da39af92f48350aa01091a2b8b3e735217eea)
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(&copy, ":");
157 	subject_id = strsep(&copy, ":");
158 	resource = strsep(&copy, ":");
159 	action = strsep(&copy, "=/");
160 	amount = strsep(&copy, "/");
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(&copy, ":");
255 	subject_id = strsep(&copy, ":");
256 	resource = strsep(&copy, ":");
257 	action = strsep(&copy, "=/");
258 	amount = strsep(&copy, "/");
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(&copy, "=");
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