1220166Strasz/*-
2220166Strasz * Copyright (c) 2010 The FreeBSD Foundation
3220166Strasz * All rights reserved.
4220166Strasz *
5220166Strasz * This software was developed by Edward Tomasz Napierala under sponsorship
6220166Strasz * from the FreeBSD Foundation.
7220166Strasz *
8220166Strasz * Redistribution and use in source and binary forms, with or without
9220166Strasz * modification, are permitted provided that the following conditions
10220166Strasz * are met:
11220166Strasz * 1. Redistributions of source code must retain the above copyright
12220166Strasz *    notice, this list of conditions and the following disclaimer.
13220166Strasz * 2. Redistributions in binary form must reproduce the above copyright
14220166Strasz *    notice, this list of conditions and the following disclaimer in the
15220166Strasz *    documentation and/or other materials provided with the distribution.
16220166Strasz *
17220166Strasz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18220166Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19220166Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20220166Strasz * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21220166Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22220166Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23220166Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24220166Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25220166Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26220166Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27220166Strasz * SUCH DAMAGE.
28220166Strasz *
29220166Strasz * $FreeBSD$
30220166Strasz */
31220166Strasz
32220166Strasz#include <sys/cdefs.h>
33220166Strasz__FBSDID("$FreeBSD$");
34220166Strasz
35220166Strasz#include <sys/types.h>
36220166Strasz#include <sys/rctl.h>
37220166Strasz#include <assert.h>
38220166Strasz#include <ctype.h>
39220166Strasz#include <err.h>
40220166Strasz#include <errno.h>
41220166Strasz#include <getopt.h>
42220166Strasz#include <grp.h>
43220166Strasz#include <libutil.h>
44220166Strasz#include <pwd.h>
45220166Strasz#include <stdint.h>
46220166Strasz#include <stdio.h>
47220166Strasz#include <stdlib.h>
48220166Strasz#include <string.h>
49220166Strasz
50220166Strasz#define	RCTL_DEFAULT_BUFSIZE	4096
51220166Strasz
52220166Straszstatic id_t
53220166Straszparse_user(const char *s)
54220166Strasz{
55220166Strasz	id_t id;
56220166Strasz	char *end;
57220166Strasz	struct passwd *pwd;
58220166Strasz
59220166Strasz	pwd = getpwnam(s);
60220166Strasz	if (pwd != NULL)
61220166Strasz		return (pwd->pw_uid);
62220166Strasz
63220166Strasz	if (!isnumber(s[0]))
64220166Strasz		errx(1, "uknown user '%s'", s);
65220166Strasz
66220166Strasz	id = strtod(s, &end);
67220166Strasz	if ((size_t)(end - s) != strlen(s))
68220166Strasz		errx(1, "trailing characters after numerical id");
69220166Strasz
70220166Strasz	return (id);
71220166Strasz}
72220166Strasz
73220166Straszstatic id_t
74220166Straszparse_group(const char *s)
75220166Strasz{
76220166Strasz	id_t id;
77220166Strasz	char *end;
78220166Strasz	struct group *grp;
79220166Strasz
80220166Strasz	grp = getgrnam(s);
81220166Strasz	if (grp != NULL)
82220166Strasz		return (grp->gr_gid);
83220166Strasz
84220166Strasz	if (!isnumber(s[0]))
85220166Strasz		errx(1, "uknown group '%s'", s);
86220166Strasz
87220166Strasz	id = strtod(s, &end);
88220166Strasz	if ((size_t)(end - s) != strlen(s))
89220166Strasz		errx(1, "trailing characters after numerical id");
90220166Strasz
91220166Strasz	return (id);
92220166Strasz}
93220166Strasz
94220166Strasz/*
95220166Strasz * This routine replaces user/group name with numeric id.
96220166Strasz */
97220166Straszstatic char *
98220166Straszresolve_ids(char *rule)
99220166Strasz{
100220166Strasz	id_t id;
101220166Strasz	const char *subject, *textid, *rest;
102220166Strasz	char *resolved;
103220166Strasz
104220166Strasz	subject = strsep(&rule, ":");
105220166Strasz	textid = strsep(&rule, ":");
106220166Strasz	if (textid == NULL)
107220166Strasz		errx(1, "error in rule specification -- no subject");
108220166Strasz	if (rule != NULL)
109220166Strasz		rest = rule;
110220166Strasz	else
111220166Strasz		rest = "";
112220166Strasz
113220166Strasz	if (strcasecmp(subject, "u") == 0)
114220166Strasz		subject = "user";
115220166Strasz	else if (strcasecmp(subject, "g") == 0)
116220166Strasz		subject = "group";
117220166Strasz	else if (strcasecmp(subject, "p") == 0)
118220166Strasz		subject = "process";
119220166Strasz	else if (strcasecmp(subject, "l") == 0 ||
120220166Strasz	    strcasecmp(subject, "c") == 0 ||
121220166Strasz	    strcasecmp(subject, "class") == 0)
122220166Strasz		subject = "loginclass";
123220166Strasz	else if (strcasecmp(subject, "j") == 0)
124220166Strasz		subject = "jail";
125220166Strasz
126220166Strasz	if (strcasecmp(subject, "user") == 0 && strlen(textid) > 0) {
127220166Strasz		id = parse_user(textid);
128220166Strasz		asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest);
129220166Strasz	} else if (strcasecmp(subject, "group") == 0 && strlen(textid) > 0) {
130220166Strasz		id = parse_group(textid);
131220166Strasz		asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest);
132220166Strasz	} else
133220166Strasz		asprintf(&resolved, "%s:%s:%s", subject, textid, rest);
134220166Strasz
135220166Strasz	if (resolved == NULL)
136220166Strasz		err(1, "asprintf");
137220166Strasz
138220166Strasz	return (resolved);
139220166Strasz}
140220166Strasz
141220166Strasz/*
142220166Strasz * This routine replaces "human-readable" number with its expanded form.
143220166Strasz */
144220166Straszstatic char *
145220166Straszexpand_amount(char *rule)
146220166Strasz{
147220166Strasz	uint64_t num;
148220166Strasz	const char *subject, *subject_id, *resource, *action, *amount, *per;
149220166Strasz	char *copy, *expanded;
150220166Strasz
151220166Strasz	copy = strdup(rule);
152220166Strasz	if (copy == NULL)
153220166Strasz		err(1, "strdup");
154220166Strasz
155220166Strasz	subject = strsep(&copy, ":");
156220166Strasz	subject_id = strsep(&copy, ":");
157220166Strasz	resource = strsep(&copy, ":");
158220166Strasz	action = strsep(&copy, "=/");
159220166Strasz	amount = strsep(&copy, "/");
160220166Strasz	per = copy;
161220166Strasz
162220166Strasz	if (amount == NULL || strlen(amount) == 0) {
163220166Strasz		free(copy);
164220166Strasz		return (rule);
165220166Strasz	}
166220166Strasz
167220166Strasz	assert(subject != NULL);
168220166Strasz	assert(subject_id != NULL);
169220166Strasz	assert(resource != NULL);
170220166Strasz	assert(action != NULL);
171220166Strasz
172220166Strasz	if (expand_number(amount, &num))
173220166Strasz		err(1, "expand_number");
174220166Strasz
175220166Strasz	if (per == NULL)
176220166Strasz		asprintf(&expanded, "%s:%s:%s:%s=%ju", subject, subject_id,
177220166Strasz		    resource, action, (uintmax_t)num);
178220166Strasz	else
179220166Strasz		asprintf(&expanded, "%s:%s:%s:%s=%ju/%s", subject, subject_id,
180220166Strasz		    resource, action, (uintmax_t)num, per);
181220166Strasz
182220166Strasz	if (expanded == NULL)
183220166Strasz		err(1, "asprintf");
184220166Strasz
185220166Strasz	return (expanded);
186220166Strasz}
187220166Strasz
188220166Straszstatic char *
189220166Straszhumanize_ids(char *rule)
190220166Strasz{
191220166Strasz	id_t id;
192220166Strasz	struct passwd *pwd;
193220166Strasz	struct group *grp;
194220166Strasz	const char *subject, *textid, *rest;
195220166Strasz	char *humanized;
196220166Strasz
197220166Strasz	subject = strsep(&rule, ":");
198220166Strasz	textid = strsep(&rule, ":");
199220166Strasz	if (textid == NULL)
200220166Strasz		errx(1, "rule passed from the kernel didn't contain subject");
201220166Strasz	if (rule != NULL)
202220166Strasz		rest = rule;
203220166Strasz	else
204220166Strasz		rest = "";
205220166Strasz
206220166Strasz	/* Replace numerical user and group ids with names. */
207220166Strasz	if (strcasecmp(subject, "user") == 0) {
208220166Strasz		id = parse_user(textid);
209220166Strasz		pwd = getpwuid(id);
210220166Strasz		if (pwd != NULL)
211220166Strasz			textid = pwd->pw_name;
212220166Strasz	} else if (strcasecmp(subject, "group") == 0) {
213220166Strasz		id = parse_group(textid);
214220166Strasz		grp = getgrgid(id);
215220166Strasz		if (grp != NULL)
216220166Strasz			textid = grp->gr_name;
217220166Strasz	}
218220166Strasz
219220166Strasz	asprintf(&humanized, "%s:%s:%s", subject, textid, rest);
220220166Strasz
221220166Strasz	if (humanized == NULL)
222220166Strasz		err(1, "asprintf");
223220166Strasz
224220166Strasz	return (humanized);
225220166Strasz}
226220166Strasz
227220166Straszstatic int
228220166Straszstr2int64(const char *str, int64_t *value)
229220166Strasz{
230220166Strasz	char *end;
231220166Strasz
232220166Strasz	if (str == NULL)
233220166Strasz		return (EINVAL);
234220166Strasz
235220166Strasz	*value = strtoul(str, &end, 10);
236220166Strasz	if ((size_t)(end - str) != strlen(str))
237220166Strasz		return (EINVAL);
238220166Strasz
239220166Strasz	return (0);
240220166Strasz}
241220166Strasz
242220166Straszstatic char *
243220166Straszhumanize_amount(char *rule)
244220166Strasz{
245220166Strasz	int64_t num;
246220166Strasz	const char *subject, *subject_id, *resource, *action, *amount, *per;
247220166Strasz	char *copy, *humanized, buf[6];
248220166Strasz
249220166Strasz	copy = strdup(rule);
250220166Strasz	if (copy == NULL)
251220166Strasz		err(1, "strdup");
252220166Strasz
253220166Strasz	subject = strsep(&copy, ":");
254220166Strasz	subject_id = strsep(&copy, ":");
255220166Strasz	resource = strsep(&copy, ":");
256220166Strasz	action = strsep(&copy, "=/");
257220166Strasz	amount = strsep(&copy, "/");
258220166Strasz	per = copy;
259220166Strasz
260220166Strasz	if (amount == NULL || strlen(amount) == 0 ||
261220166Strasz	    str2int64(amount, &num) != 0) {
262220166Strasz		free(copy);
263220166Strasz		return (rule);
264220166Strasz	}
265220166Strasz
266220166Strasz	assert(subject != NULL);
267220166Strasz	assert(subject_id != NULL);
268220166Strasz	assert(resource != NULL);
269220166Strasz	assert(action != NULL);
270220166Strasz
271220166Strasz	if (humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE,
272220166Strasz	    HN_DECIMAL | HN_NOSPACE) == -1)
273220166Strasz		err(1, "humanize_number");
274220166Strasz
275220166Strasz	if (per == NULL)
276220166Strasz		asprintf(&humanized, "%s:%s:%s:%s=%s", subject, subject_id,
277220166Strasz		    resource, action, buf);
278220166Strasz	else
279220166Strasz		asprintf(&humanized, "%s:%s:%s:%s=%s/%s", subject, subject_id,
280220166Strasz		    resource, action, buf, per);
281220166Strasz
282220166Strasz	if (humanized == NULL)
283220166Strasz		err(1, "asprintf");
284220166Strasz
285220166Strasz	return (humanized);
286220166Strasz}
287220166Strasz
288220166Strasz/*
289220166Strasz * Print rules, one per line.
290220166Strasz */
291220166Straszstatic void
292220166Straszprint_rules(char *rules, int hflag, int nflag)
293220166Strasz{
294220166Strasz	char *rule;
295220166Strasz
296220166Strasz	while ((rule = strsep(&rules, ",")) != NULL) {
297220166Strasz		if (rule[0] == '\0')
298220166Strasz			break; /* XXX */
299220166Strasz		if (nflag == 0)
300220166Strasz			rule = humanize_ids(rule);
301220166Strasz		if (hflag)
302220166Strasz			rule = humanize_amount(rule);
303220166Strasz		printf("%s\n", rule);
304220166Strasz	}
305220166Strasz}
306220166Strasz
307220166Straszstatic void
308220166Straszadd_rule(char *rule)
309220166Strasz{
310220166Strasz	int error;
311220166Strasz
312220166Strasz	error = rctl_add_rule(rule, strlen(rule) + 1, NULL, 0);
313220166Strasz	if (error != 0)
314220166Strasz		err(1, "rctl_add_rule");
315220166Strasz	free(rule);
316220166Strasz}
317220166Strasz
318220166Straszstatic void
319220166Straszshow_limits(char *filter, int hflag, int nflag)
320220166Strasz{
321220166Strasz	int error;
322220166Strasz	char *outbuf = NULL;
323220166Strasz	size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
324220166Strasz
325220166Strasz	do {
326220166Strasz		outbuflen *= 4;
327220166Strasz		outbuf = realloc(outbuf, outbuflen);
328220166Strasz		if (outbuf == NULL)
329220166Strasz			err(1, "realloc");
330220166Strasz
331220166Strasz		error = rctl_get_limits(filter, strlen(filter) + 1, outbuf,
332220166Strasz		    outbuflen);
333220166Strasz		if (error && errno != ERANGE)
334220166Strasz			err(1, "rctl_get_limits");
335220166Strasz	} while (error && errno == ERANGE);
336220166Strasz
337220166Strasz	print_rules(outbuf, hflag, nflag);
338220166Strasz	free(filter);
339220166Strasz	free(outbuf);
340220166Strasz}
341220166Strasz
342220166Straszstatic void
343220166Straszremove_rule(char *filter)
344220166Strasz{
345220166Strasz	int error;
346220166Strasz
347220166Strasz	error = rctl_remove_rule(filter, strlen(filter) + 1, NULL, 0);
348220166Strasz	if (error != 0)
349220166Strasz		err(1, "rctl_remove_rule");
350220166Strasz	free(filter);
351220166Strasz}
352220166Strasz
353220166Straszstatic char *
354220166Straszhumanize_usage_amount(char *usage)
355220166Strasz{
356220166Strasz	int64_t num;
357220166Strasz	const char *resource, *amount;
358220166Strasz	char *copy, *humanized, buf[6];
359220166Strasz
360220166Strasz	copy = strdup(usage);
361220166Strasz	if (copy == NULL)
362220166Strasz		err(1, "strdup");
363220166Strasz
364220166Strasz	resource = strsep(&copy, "=");
365220166Strasz	amount = copy;
366220166Strasz
367220166Strasz	assert(resource != NULL);
368220166Strasz	assert(amount != NULL);
369220166Strasz
370220166Strasz	if (str2int64(amount, &num) != 0 ||
371220166Strasz	    humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE,
372220166Strasz	    HN_DECIMAL | HN_NOSPACE) == -1) {
373220166Strasz		free(copy);
374220166Strasz		return (usage);
375220166Strasz	}
376220166Strasz
377220166Strasz	asprintf(&humanized, "%s=%s", resource, buf);
378220166Strasz	if (humanized == NULL)
379220166Strasz		err(1, "asprintf");
380220166Strasz
381220166Strasz	return (humanized);
382220166Strasz}
383220166Strasz
384220166Strasz/*
385220166Strasz * Query the kernel about a resource usage and print it out.
386220166Strasz */
387220166Straszstatic void
388220166Straszshow_usage(char *filter, int hflag)
389220166Strasz{
390220166Strasz	int error;
391220166Strasz	char *outbuf = NULL, *tmp;
392220166Strasz	size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
393220166Strasz
394220166Strasz	do {
395220166Strasz		outbuflen *= 4;
396220166Strasz		outbuf = realloc(outbuf, outbuflen);
397220166Strasz		if (outbuf == NULL)
398220166Strasz			err(1, "realloc");
399220166Strasz
400220166Strasz		error = rctl_get_racct(filter, strlen(filter) + 1, outbuf,
401220166Strasz		    outbuflen);
402220166Strasz		if (error && errno != ERANGE)
403220166Strasz			err(1, "rctl_get_racct");
404220166Strasz	} while (error && errno == ERANGE);
405220166Strasz
406220166Strasz	while ((tmp = strsep(&outbuf, ",")) != NULL) {
407220166Strasz		if (tmp[0] == '\0')
408220166Strasz			break; /* XXX */
409220166Strasz
410220166Strasz		if (hflag)
411220166Strasz			tmp = humanize_usage_amount(tmp);
412220166Strasz
413220166Strasz		printf("%s\n", tmp);
414220166Strasz	}
415220166Strasz
416220166Strasz	free(filter);
417220166Strasz	free(outbuf);
418220166Strasz}
419220166Strasz
420220166Strasz/*
421220166Strasz * Query the kernel about resource limit rules and print them out.
422220166Strasz */
423220166Straszstatic void
424220166Straszshow_rules(char *filter, int hflag, int nflag)
425220166Strasz{
426220166Strasz	int error;
427220166Strasz	char *outbuf = NULL;
428220166Strasz	size_t filterlen, outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
429220166Strasz
430220166Strasz	if (filter != NULL)
431220166Strasz		filterlen = strlen(filter) + 1;
432220166Strasz	else
433220166Strasz		filterlen = 0;
434220166Strasz
435220166Strasz	do {
436220166Strasz		outbuflen *= 4;
437220166Strasz		outbuf = realloc(outbuf, outbuflen);
438220166Strasz		if (outbuf == NULL)
439220166Strasz			err(1, "realloc");
440220166Strasz
441220166Strasz		error = rctl_get_rules(filter, filterlen, outbuf, outbuflen);
442220166Strasz		if (error && errno != ERANGE)
443220166Strasz			err(1, "rctl_get_rules");
444220166Strasz	} while (error && errno == ERANGE);
445220166Strasz
446220166Strasz	print_rules(outbuf, hflag, nflag);
447220166Strasz	free(outbuf);
448220166Strasz}
449220166Strasz
450220166Straszstatic void
451220166Straszusage(void)
452220166Strasz{
453220166Strasz
454220166Strasz	fprintf(stderr, "usage: rctl [ -h ] [-a rule | -l filter | -r filter "
455220166Strasz	    "| -u filter | filter]\n");
456220166Strasz	exit(1);
457220166Strasz}
458220166Strasz
459220166Straszint
460220166Straszmain(int argc __unused, char **argv __unused)
461220166Strasz{
462220166Strasz	int ch, aflag = 0, hflag = 0, nflag = 0, lflag = 0, rflag = 0,
463220166Strasz	    uflag = 0;
464220166Strasz	char *rule = NULL;
465220166Strasz
466220166Strasz	while ((ch = getopt(argc, argv, "a:hl:nr:u:")) != -1) {
467220166Strasz		switch (ch) {
468220166Strasz		case 'a':
469220166Strasz			aflag = 1;
470220166Strasz			rule = strdup(optarg);
471220166Strasz			break;
472220166Strasz		case 'h':
473220166Strasz			hflag = 1;
474220166Strasz			break;
475220166Strasz		case 'l':
476220166Strasz			lflag = 1;
477220166Strasz			rule = strdup(optarg);
478220166Strasz			break;
479220166Strasz		case 'n':
480220166Strasz			nflag = 1;
481220166Strasz			break;
482220166Strasz		case 'r':
483220166Strasz			rflag = 1;
484220166Strasz			rule = strdup(optarg);
485220166Strasz			break;
486220166Strasz		case 'u':
487220166Strasz			uflag = 1;
488220166Strasz			rule = strdup(optarg);
489220166Strasz			break;
490220166Strasz
491220166Strasz		case '?':
492220166Strasz		default:
493220166Strasz			usage();
494220166Strasz		}
495220166Strasz	}
496220166Strasz
497220166Strasz	argc -= optind;
498220166Strasz	argv += optind;
499220166Strasz
500220166Strasz	if (argc > 1)
501220166Strasz		usage();
502220166Strasz
503220166Strasz	if (rule == NULL) {
504220166Strasz		if (argc == 1)
505220166Strasz			rule = strdup(argv[0]);
506220166Strasz		else
507220166Strasz			rule = strdup("::");
508220166Strasz	}
509220166Strasz
510220166Strasz	if (aflag + lflag + rflag + uflag + argc > 1)
511220166Strasz		errx(1, "only one flag or argument may be specified "
512220166Strasz		    "at the same time");
513220166Strasz
514220166Strasz	rule = resolve_ids(rule);
515220166Strasz	rule = expand_amount(rule);
516220166Strasz
517220166Strasz	if (aflag) {
518220166Strasz		add_rule(rule);
519220166Strasz		return (0);
520220166Strasz	}
521220166Strasz
522220166Strasz	if (lflag) {
523220166Strasz		show_limits(rule, hflag, nflag);
524220166Strasz		return (0);
525220166Strasz	}
526220166Strasz
527220166Strasz	if (rflag) {
528220166Strasz		remove_rule(rule);
529220166Strasz		return (0);
530220166Strasz	}
531220166Strasz
532220166Strasz	if (uflag) {
533220166Strasz		show_usage(rule, hflag);
534220166Strasz		return (0);
535220166Strasz	}
536220166Strasz
537220166Strasz	show_rules(rule, hflag, nflag);
538220166Strasz	return (0);
539220166Strasz}
540