1166124Srafan/*-
2166124Srafan * Copyright (c) 2010 The FreeBSD Foundation
3166124Srafan * All rights reserved.
4166124Srafan *
5166124Srafan * This software was developed by Edward Tomasz Napierala under sponsorship
6166124Srafan * from the FreeBSD Foundation.
7166124Srafan *
8166124Srafan * Redistribution and use in source and binary forms, with or without
9166124Srafan * modification, are permitted provided that the following conditions
10166124Srafan * are met:
11166124Srafan * 1. Redistributions of source code must retain the above copyright
12166124Srafan *    notice, this list of conditions and the following disclaimer.
13166124Srafan * 2. Redistributions in binary form must reproduce the above copyright
14166124Srafan *    notice, this list of conditions and the following disclaimer in the
15166124Srafan *    documentation and/or other materials provided with the distribution.
16166124Srafan *
17166124Srafan * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18166124Srafan * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19166124Srafan * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20166124Srafan * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21166124Srafan * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22166124Srafan * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23166124Srafan * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24166124Srafan * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25166124Srafan * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26166124Srafan * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27166124Srafan * SUCH DAMAGE.
28166124Srafan *
29166124Srafan * $FreeBSD$
30166124Srafan */
31166124Srafan
32166124Srafan#include <sys/cdefs.h>
33166124Srafan__FBSDID("$FreeBSD$");
34166124Srafan
35166124Srafan#include <sys/types.h>
36166124Srafan#include <sys/rctl.h>
37166124Srafan#include <assert.h>
38166124Srafan#include <ctype.h>
39166124Srafan#include <err.h>
40166124Srafan#include <errno.h>
41166124Srafan#include <getopt.h>
42166124Srafan#include <grp.h>
43166124Srafan#include <libutil.h>
44166124Srafan#include <pwd.h>
45166124Srafan#include <stdint.h>
46166124Srafan#include <stdio.h>
47166124Srafan#include <stdlib.h>
48166124Srafan#include <string.h>
49166124Srafan
50166124Srafan#define	RCTL_DEFAULT_BUFSIZE	4096
51166124Srafan
52166124Srafanstatic id_t
53166124Srafanparse_user(const char *s)
54166124Srafan{
55166124Srafan	id_t id;
56166124Srafan	char *end;
57166124Srafan	struct passwd *pwd;
58166124Srafan
59166124Srafan	pwd = getpwnam(s);
60166124Srafan	if (pwd != NULL)
61166124Srafan		return (pwd->pw_uid);
62166124Srafan
63166124Srafan	if (!isnumber(s[0]))
64166124Srafan		errx(1, "uknown user '%s'", s);
65166124Srafan
66166124Srafan	id = strtod(s, &end);
67166124Srafan	if ((size_t)(end - s) != strlen(s))
68166124Srafan		errx(1, "trailing characters after numerical id");
69166124Srafan
70166124Srafan	return (id);
71166124Srafan}
72166124Srafan
73166124Srafanstatic id_t
74166124Srafanparse_group(const char *s)
75166124Srafan{
76166124Srafan	id_t id;
77166124Srafan	char *end;
78166124Srafan	struct group *grp;
79166124Srafan
80166124Srafan	grp = getgrnam(s);
81166124Srafan	if (grp != NULL)
82166124Srafan		return (grp->gr_gid);
83166124Srafan
84166124Srafan	if (!isnumber(s[0]))
85166124Srafan		errx(1, "uknown group '%s'", s);
86166124Srafan
87166124Srafan	id = strtod(s, &end);
88166124Srafan	if ((size_t)(end - s) != strlen(s))
89166124Srafan		errx(1, "trailing characters after numerical id");
90166124Srafan
91166124Srafan	return (id);
92166124Srafan}
93166124Srafan
94166124Srafan/*
95166124Srafan * This routine replaces user/group name with numeric id.
96166124Srafan */
97166124Srafanstatic char *
98166124Srafanresolve_ids(char *rule)
99166124Srafan{
100166124Srafan	id_t id;
101166124Srafan	const char *subject, *textid, *rest;
102166124Srafan	char *resolved;
103166124Srafan
104166124Srafan	subject = strsep(&rule, ":");
105166124Srafan	textid = strsep(&rule, ":");
106166124Srafan	if (textid == NULL)
107166124Srafan		errx(1, "error in rule specification -- no subject");
108166124Srafan	if (rule != NULL)
109166124Srafan		rest = rule;
110166124Srafan	else
111166124Srafan		rest = "";
112166124Srafan
113166124Srafan	if (strcasecmp(subject, "u") == 0)
114166124Srafan		subject = "user";
115166124Srafan	else if (strcasecmp(subject, "g") == 0)
116166124Srafan		subject = "group";
117166124Srafan	else if (strcasecmp(subject, "p") == 0)
118166124Srafan		subject = "process";
119166124Srafan	else if (strcasecmp(subject, "l") == 0 ||
120166124Srafan	    strcasecmp(subject, "c") == 0 ||
121166124Srafan	    strcasecmp(subject, "class") == 0)
122166124Srafan		subject = "loginclass";
123166124Srafan	else if (strcasecmp(subject, "j") == 0)
124166124Srafan		subject = "jail";
125166124Srafan
126166124Srafan	if (strcasecmp(subject, "user") == 0 && strlen(textid) > 0) {
127166124Srafan		id = parse_user(textid);
128166124Srafan		asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest);
129166124Srafan	} else if (strcasecmp(subject, "group") == 0 && strlen(textid) > 0) {
130166124Srafan		id = parse_group(textid);
131166124Srafan		asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest);
132166124Srafan	} else
133166124Srafan		asprintf(&resolved, "%s:%s:%s", subject, textid, rest);
134166124Srafan
135166124Srafan	if (resolved == NULL)
136166124Srafan		err(1, "asprintf");
137166124Srafan
138166124Srafan	return (resolved);
139166124Srafan}
140166124Srafan
141166124Srafan/*
142166124Srafan * This routine replaces "human-readable" number with its expanded form.
143166124Srafan */
144166124Srafanstatic char *
145166124Srafanexpand_amount(char *rule)
146166124Srafan{
147166124Srafan	uint64_t num;
148166124Srafan	const char *subject, *subject_id, *resource, *action, *amount, *per;
149166124Srafan	char *copy, *expanded;
150166124Srafan
151166124Srafan	copy = strdup(rule);
152166124Srafan	if (copy == NULL)
153166124Srafan		err(1, "strdup");
154166124Srafan
155166124Srafan	subject = strsep(&copy, ":");
156166124Srafan	subject_id = strsep(&copy, ":");
157166124Srafan	resource = strsep(&copy, ":");
158166124Srafan	action = strsep(&copy, "=/");
159166124Srafan	amount = strsep(&copy, "/");
160166124Srafan	per = copy;
161166124Srafan
162166124Srafan	if (amount == NULL || strlen(amount) == 0) {
163166124Srafan		free(copy);
164166124Srafan		return (rule);
165166124Srafan	}
166166124Srafan
167166124Srafan	assert(subject != NULL);
168166124Srafan	assert(subject_id != NULL);
169166124Srafan	assert(resource != NULL);
170166124Srafan	assert(action != NULL);
171166124Srafan
172166124Srafan	if (expand_number(amount, &num))
173166124Srafan		err(1, "expand_number");
174166124Srafan
175166124Srafan	if (per == NULL)
176166124Srafan		asprintf(&expanded, "%s:%s:%s:%s=%ju", subject, subject_id,
177166124Srafan		    resource, action, (uintmax_t)num);
178166124Srafan	else
179166124Srafan		asprintf(&expanded, "%s:%s:%s:%s=%ju/%s", subject, subject_id,
180166124Srafan		    resource, action, (uintmax_t)num, per);
181166124Srafan
182166124Srafan	if (expanded == NULL)
183166124Srafan		err(1, "asprintf");
184166124Srafan
185166124Srafan	return (expanded);
186166124Srafan}
187166124Srafan
188166124Srafanstatic char *
189166124Srafanhumanize_ids(char *rule)
190166124Srafan{
191166124Srafan	id_t id;
192166124Srafan	struct passwd *pwd;
193166124Srafan	struct group *grp;
194166124Srafan	const char *subject, *textid, *rest;
195166124Srafan	char *humanized;
196166124Srafan
197166124Srafan	subject = strsep(&rule, ":");
198166124Srafan	textid = strsep(&rule, ":");
199166124Srafan	if (textid == NULL)
200166124Srafan		errx(1, "rule passed from the kernel didn't contain subject");
201166124Srafan	if (rule != NULL)
202166124Srafan		rest = rule;
203166124Srafan	else
204166124Srafan		rest = "";
205166124Srafan
206166124Srafan	/* Replace numerical user and group ids with names. */
207166124Srafan	if (strcasecmp(subject, "user") == 0) {
208166124Srafan		id = parse_user(textid);
209166124Srafan		pwd = getpwuid(id);
210166124Srafan		if (pwd != NULL)
211166124Srafan			textid = pwd->pw_name;
212166124Srafan	} else if (strcasecmp(subject, "group") == 0) {
213166124Srafan		id = parse_group(textid);
214166124Srafan		grp = getgrgid(id);
215166124Srafan		if (grp != NULL)
216166124Srafan			textid = grp->gr_name;
217166124Srafan	}
218166124Srafan
219166124Srafan	asprintf(&humanized, "%s:%s:%s", subject, textid, rest);
220166124Srafan
221166124Srafan	if (humanized == NULL)
222166124Srafan		err(1, "asprintf");
223166124Srafan
224166124Srafan	return (humanized);
225166124Srafan}
226166124Srafan
227166124Srafanstatic int
228166124Srafanstr2int64(const char *str, int64_t *value)
229166124Srafan{
230166124Srafan	char *end;
231166124Srafan
232166124Srafan	if (str == NULL)
233166124Srafan		return (EINVAL);
234166124Srafan
235166124Srafan	*value = strtoul(str, &end, 10);
236166124Srafan	if ((size_t)(end - str) != strlen(str))
237166124Srafan		return (EINVAL);
238166124Srafan
239166124Srafan	return (0);
240166124Srafan}
241166124Srafan
242166124Srafanstatic char *
243166124Srafanhumanize_amount(char *rule)
244166124Srafan{
245166124Srafan	int64_t num;
246166124Srafan	const char *subject, *subject_id, *resource, *action, *amount, *per;
247166124Srafan	char *copy, *humanized, buf[6];
248166124Srafan
249166124Srafan	copy = strdup(rule);
250166124Srafan	if (copy == NULL)
251166124Srafan		err(1, "strdup");
252166124Srafan
253166124Srafan	subject = strsep(&copy, ":");
254166124Srafan	subject_id = strsep(&copy, ":");
255166124Srafan	resource = strsep(&copy, ":");
256166124Srafan	action = strsep(&copy, "=/");
257166124Srafan	amount = strsep(&copy, "/");
258166124Srafan	per = copy;
259166124Srafan
260166124Srafan	if (amount == NULL || strlen(amount) == 0 ||
261166124Srafan	    str2int64(amount, &num) != 0) {
262166124Srafan		free(copy);
263166124Srafan		return (rule);
264166124Srafan	}
265166124Srafan
266166124Srafan	assert(subject != NULL);
267166124Srafan	assert(subject_id != NULL);
268166124Srafan	assert(resource != NULL);
269166124Srafan	assert(action != NULL);
270166124Srafan
271166124Srafan	if (humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE,
272166124Srafan	    HN_DECIMAL | HN_NOSPACE) == -1)
273166124Srafan		err(1, "humanize_number");
274166124Srafan
275166124Srafan	if (per == NULL)
276166124Srafan		asprintf(&humanized, "%s:%s:%s:%s=%s", subject, subject_id,
277166124Srafan		    resource, action, buf);
278166124Srafan	else
279166124Srafan		asprintf(&humanized, "%s:%s:%s:%s=%s/%s", subject, subject_id,
280166124Srafan		    resource, action, buf, per);
281166124Srafan
282166124Srafan	if (humanized == NULL)
283166124Srafan		err(1, "asprintf");
284166124Srafan
285166124Srafan	return (humanized);
286166124Srafan}
287166124Srafan
288166124Srafan/*
289166124Srafan * Print rules, one per line.
290166124Srafan */
291166124Srafanstatic void
292166124Srafanprint_rules(char *rules, int hflag, int nflag)
293166124Srafan{
294166124Srafan	char *rule;
295166124Srafan
296166124Srafan	while ((rule = strsep(&rules, ",")) != NULL) {
297166124Srafan		if (rule[0] == '\0')
298166124Srafan			break; /* XXX */
299166124Srafan		if (nflag == 0)
300166124Srafan			rule = humanize_ids(rule);
301166124Srafan		if (hflag)
302166124Srafan			rule = humanize_amount(rule);
303166124Srafan		printf("%s\n", rule);
304166124Srafan	}
305166124Srafan}
306166124Srafan
307166124Srafanstatic void
308166124Srafanadd_rule(char *rule)
309166124Srafan{
310166124Srafan	int error;
311166124Srafan
312166124Srafan	error = rctl_add_rule(rule, strlen(rule) + 1, NULL, 0);
313166124Srafan	if (error != 0)
314166124Srafan		err(1, "rctl_add_rule");
315166124Srafan	free(rule);
316166124Srafan}
317166124Srafan
318166124Srafanstatic void
319166124Srafanshow_limits(char *filter, int hflag, int nflag)
320166124Srafan{
321166124Srafan	int error;
322166124Srafan	char *outbuf = NULL;
323166124Srafan	size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
324166124Srafan
325166124Srafan	do {
326166124Srafan		outbuflen *= 4;
327166124Srafan		outbuf = realloc(outbuf, outbuflen);
328166124Srafan		if (outbuf == NULL)
329166124Srafan			err(1, "realloc");
330166124Srafan
331166124Srafan		error = rctl_get_limits(filter, strlen(filter) + 1, outbuf,
332166124Srafan		    outbuflen);
333166124Srafan		if (error && errno != ERANGE)
334166124Srafan			err(1, "rctl_get_limits");
335166124Srafan	} while (error && errno == ERANGE);
336166124Srafan
337166124Srafan	print_rules(outbuf, hflag, nflag);
338166124Srafan	free(filter);
339166124Srafan	free(outbuf);
340166124Srafan}
341166124Srafan
342166124Srafanstatic void
343166124Srafanremove_rule(char *filter)
344166124Srafan{
345166124Srafan	int error;
346166124Srafan
347166124Srafan	error = rctl_remove_rule(filter, strlen(filter) + 1, NULL, 0);
348166124Srafan	if (error != 0)
349166124Srafan		err(1, "rctl_remove_rule");
350166124Srafan	free(filter);
351166124Srafan}
352166124Srafan
353166124Srafanstatic char *
354166124Srafanhumanize_usage_amount(char *usage)
355166124Srafan{
356166124Srafan	int64_t num;
357166124Srafan	const char *resource, *amount;
358166124Srafan	char *copy, *humanized, buf[6];
359166124Srafan
360166124Srafan	copy = strdup(usage);
361166124Srafan	if (copy == NULL)
362166124Srafan		err(1, "strdup");
363166124Srafan
364166124Srafan	resource = strsep(&copy, "=");
365166124Srafan	amount = copy;
366166124Srafan
367166124Srafan	assert(resource != NULL);
368166124Srafan	assert(amount != NULL);
369166124Srafan
370166124Srafan	if (str2int64(amount, &num) != 0 ||
371166124Srafan	    humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE,
372166124Srafan	    HN_DECIMAL | HN_NOSPACE) == -1) {
373166124Srafan		free(copy);
374166124Srafan		return (usage);
375166124Srafan	}
376166124Srafan
377166124Srafan	asprintf(&humanized, "%s=%s", resource, buf);
378166124Srafan	if (humanized == NULL)
379166124Srafan		err(1, "asprintf");
380166124Srafan
381166124Srafan	return (humanized);
382166124Srafan}
383166124Srafan
384166124Srafan/*
385166124Srafan * Query the kernel about a resource usage and print it out.
386166124Srafan */
387166124Srafanstatic void
388166124Srafanshow_usage(char *filter, int hflag)
389166124Srafan{
390166124Srafan	int error;
391166124Srafan	char *outbuf = NULL, *tmp;
392166124Srafan	size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
393166124Srafan
394166124Srafan	do {
395166124Srafan		outbuflen *= 4;
396166124Srafan		outbuf = realloc(outbuf, outbuflen);
397166124Srafan		if (outbuf == NULL)
398166124Srafan			err(1, "realloc");
399166124Srafan
400166124Srafan		error = rctl_get_racct(filter, strlen(filter) + 1, outbuf,
401166124Srafan		    outbuflen);
402166124Srafan		if (error && errno != ERANGE)
403166124Srafan			err(1, "rctl_get_racct");
404166124Srafan	} while (error && errno == ERANGE);
405166124Srafan
406166124Srafan	while ((tmp = strsep(&outbuf, ",")) != NULL) {
407166124Srafan		if (tmp[0] == '\0')
408166124Srafan			break; /* XXX */
409166124Srafan
410166124Srafan		if (hflag)
411166124Srafan			tmp = humanize_usage_amount(tmp);
412166124Srafan
413166124Srafan		printf("%s\n", tmp);
414166124Srafan	}
415166124Srafan
416166124Srafan	free(filter);
417166124Srafan	free(outbuf);
418166124Srafan}
419166124Srafan
420166124Srafan/*
421166124Srafan * Query the kernel about resource limit rules and print them out.
422166124Srafan */
423166124Srafanstatic void
424166124Srafanshow_rules(char *filter, int hflag, int nflag)
425166124Srafan{
426166124Srafan	int error;
427166124Srafan	char *outbuf = NULL;
428166124Srafan	size_t filterlen, outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
429166124Srafan
430166124Srafan	if (filter != NULL)
431166124Srafan		filterlen = strlen(filter) + 1;
432166124Srafan	else
433166124Srafan		filterlen = 0;
434166124Srafan
435166124Srafan	do {
436166124Srafan		outbuflen *= 4;
437166124Srafan		outbuf = realloc(outbuf, outbuflen);
438166124Srafan		if (outbuf == NULL)
439166124Srafan			err(1, "realloc");
440166124Srafan
441166124Srafan		error = rctl_get_rules(filter, filterlen, outbuf, outbuflen);
442166124Srafan		if (error && errno != ERANGE)
443166124Srafan			err(1, "rctl_get_rules");
444166124Srafan	} while (error && errno == ERANGE);
445166124Srafan
446166124Srafan	print_rules(outbuf, hflag, nflag);
447166124Srafan	free(outbuf);
448166124Srafan}
449166124Srafan
450166124Srafanstatic void
451166124Srafanusage(void)
452166124Srafan{
453166124Srafan
454166124Srafan	fprintf(stderr, "usage: rctl [ -h ] [-a rule | -l filter | -r filter "
455166124Srafan	    "| -u filter | filter]\n");
456166124Srafan	exit(1);
457166124Srafan}
458166124Srafan
459166124Srafanint
460166124Srafanmain(int argc __unused, char **argv __unused)
461166124Srafan{
462166124Srafan	int ch, aflag = 0, hflag = 0, nflag = 0, lflag = 0, rflag = 0,
463166124Srafan	    uflag = 0;
464166124Srafan	char *rule = NULL;
465166124Srafan
466166124Srafan	while ((ch = getopt(argc, argv, "a:hl:nr:u:")) != -1) {
467166124Srafan		switch (ch) {
468166124Srafan		case 'a':
469166124Srafan			aflag = 1;
470166124Srafan			rule = strdup(optarg);
471166124Srafan			break;
472166124Srafan		case 'h':
473166124Srafan			hflag = 1;
474166124Srafan			break;
475166124Srafan		case 'l':
476166124Srafan			lflag = 1;
477166124Srafan			rule = strdup(optarg);
478166124Srafan			break;
479166124Srafan		case 'n':
480166124Srafan			nflag = 1;
481166124Srafan			break;
482166124Srafan		case 'r':
483166124Srafan			rflag = 1;
484166124Srafan			rule = strdup(optarg);
485166124Srafan			break;
486166124Srafan		case 'u':
487166124Srafan			uflag = 1;
488166124Srafan			rule = strdup(optarg);
489166124Srafan			break;
490166124Srafan
491166124Srafan		case '?':
492166124Srafan		default:
493166124Srafan			usage();
494166124Srafan		}
495166124Srafan	}
496166124Srafan
497166124Srafan	argc -= optind;
498166124Srafan	argv += optind;
499166124Srafan
500166124Srafan	if (argc > 1)
501166124Srafan		usage();
502166124Srafan
503166124Srafan	if (rule == NULL) {
504166124Srafan		if (argc == 1)
505166124Srafan			rule = strdup(argv[0]);
506166124Srafan		else
507166124Srafan			rule = strdup("::");
508166124Srafan	}
509166124Srafan
510166124Srafan	if (aflag + lflag + rflag + uflag + argc > 1)
511166124Srafan		errx(1, "only one flag or argument may be specified "
512166124Srafan		    "at the same time");
513166124Srafan
514166124Srafan	rule = resolve_ids(rule);
515166124Srafan	rule = expand_amount(rule);
516166124Srafan
517166124Srafan	if (aflag) {
518166124Srafan		add_rule(rule);
519166124Srafan		return (0);
520166124Srafan	}
521166124Srafan
522166124Srafan	if (lflag) {
523166124Srafan		show_limits(rule, hflag, nflag);
524166124Srafan		return (0);
525166124Srafan	}
526166124Srafan
527166124Srafan	if (rflag) {
528166124Srafan		remove_rule(rule);
529166124Srafan		return (0);
530166124Srafan	}
531166124Srafan
532166124Srafan	if (uflag) {
533166124Srafan		show_usage(rule, hflag);
534166124Srafan		return (0);
535166124Srafan	}
536166124Srafan
537166124Srafan	show_rules(rule, hflag, nflag);
538166124Srafan	return (0);
539166124Srafan}
540166124Srafan