rctl.c revision 284667
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: stable/10/usr.bin/rctl/rctl.c 284667 2015-06-21 06:40:43Z trasz $
30 */
31
32#include <sys/cdefs.h>
33__FBSDID("$FreeBSD: stable/10/usr.bin/rctl/rctl.c 284667 2015-06-21 06:40:43Z trasz $");
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
53static id_t
54parse_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
74static id_t
75parse_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 */
98static char *
99resolve_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 */
145static char *
146expand_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
189static char *
190humanize_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
228static int
229str2int64(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
243static char *
244humanize_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 */
292static void
293print_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
308static void
309enosys(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
329static void
330add_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
343static void
344show_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
370static void
371remove_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
384static char *
385humanize_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 */
418static void
419show_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 */
457static void
458show_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
487static void
488usage(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
496int
497main(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