]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - usr.bin/rctl/rctl.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / usr.bin / rctl / rctl.c
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 }