Use proper fields to check for interrupt trigger mode.
[freebsd.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 <stdbool.h>
47 #include <stdint.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51
52 #define RCTL_DEFAULT_BUFSIZE    128 * 1024
53
54 static int
55 parse_user(const char *s, id_t *uidp, const char *unexpanded_rule)
56 {
57         char *end;
58         struct passwd *pwd;
59
60         pwd = getpwnam(s);
61         if (pwd != NULL) {
62                 *uidp = pwd->pw_uid;
63                 return (0);
64         }
65
66         if (!isnumber(s[0])) {
67                 warnx("malformed rule '%s': unknown user '%s'",
68                     unexpanded_rule, s);
69                 return (1);
70         }
71
72         *uidp = strtod(s, &end);
73         if ((size_t)(end - s) != strlen(s)) {
74                 warnx("malformed rule '%s': trailing characters "
75                     "after numerical id", unexpanded_rule);
76                 return (1);
77         }
78
79         return (0);
80 }
81
82 static int
83 parse_group(const char *s, id_t *gidp, const char *unexpanded_rule)
84 {
85         char *end;
86         struct group *grp;
87
88         grp = getgrnam(s);
89         if (grp != NULL) {
90                 *gidp = grp->gr_gid;
91                 return (0);
92         }
93
94         if (!isnumber(s[0])) {
95                 warnx("malformed rule '%s': unknown group '%s'",
96                     unexpanded_rule, s);
97                 return (1);
98         }
99
100         *gidp = strtod(s, &end);
101         if ((size_t)(end - s) != strlen(s)) {
102                 warnx("malformed rule '%s': trailing characters "
103                     "after numerical id", unexpanded_rule);
104                 return (1);
105         }
106
107         return (0);
108 }
109
110 /*
111  * Replace human-readable number with its expanded form.
112  */
113 static char *
114 expand_amount(const char *rule, const char *unexpanded_rule)
115 {
116         uint64_t num;
117         const char *subject, *subject_id, *resource, *action, *amount, *per;
118         char *copy, *expanded, *tofree;
119         int ret;
120
121         tofree = copy = strdup(rule);
122         if (copy == NULL) {
123                 warn("strdup");
124                 return (NULL);
125         }
126
127         subject = strsep(&copy, ":");
128         subject_id = strsep(&copy, ":");
129         resource = strsep(&copy, ":");
130         action = strsep(&copy, "=/");
131         amount = strsep(&copy, "/");
132         per = copy;
133
134         if (amount == NULL || strlen(amount) == 0) {
135                 /*
136                  * The "copy" has already been tinkered with by strsep().
137                  */
138                 free(tofree);
139                 copy = strdup(rule);
140                 if (copy == NULL) {
141                         warn("strdup");
142                         return (NULL);
143                 }
144                 return (copy);
145         }
146
147         assert(subject != NULL);
148         assert(subject_id != NULL);
149         assert(resource != NULL);
150         assert(action != NULL);
151
152         if (expand_number(amount, &num)) {
153                 warnx("malformed rule '%s': invalid numeric value '%s'",
154                     unexpanded_rule, amount);
155                 free(tofree);
156                 return (NULL);
157         }
158
159         if (per == NULL) {
160                 ret = asprintf(&expanded, "%s:%s:%s:%s=%ju",
161                     subject, subject_id, resource, action, (uintmax_t)num);
162         } else {
163                 ret = asprintf(&expanded, "%s:%s:%s:%s=%ju/%s",
164                     subject, subject_id, resource, action, (uintmax_t)num, per);
165         }
166
167         if (ret <= 0) {
168                 warn("asprintf");
169                 free(tofree);
170                 return (NULL);
171         }
172
173         free(tofree);
174
175         return (expanded);
176 }
177
178 static char *
179 expand_rule(const char *rule, bool resolve_ids)
180 {
181         id_t id;
182         const char *subject, *textid, *rest;
183         char *copy, *expanded, *resolved, *tofree;
184         int error, ret;
185
186         tofree = copy = strdup(rule);
187         if (copy == NULL) {
188                 warn("strdup");
189                 return (NULL);
190         }
191
192         subject = strsep(&copy, ":");
193         textid = strsep(&copy, ":");
194         if (textid == NULL) {
195                 warnx("malformed rule '%s': missing subject", rule);
196                 return (NULL);
197         }
198         if (copy != NULL)
199                 rest = copy;
200         else
201                 rest = "";
202
203         if (strcasecmp(subject, "u") == 0)
204                 subject = "user";
205         else if (strcasecmp(subject, "g") == 0)
206                 subject = "group";
207         else if (strcasecmp(subject, "p") == 0)
208                 subject = "process";
209         else if (strcasecmp(subject, "l") == 0 ||
210             strcasecmp(subject, "c") == 0 ||
211             strcasecmp(subject, "class") == 0)
212                 subject = "loginclass";
213         else if (strcasecmp(subject, "j") == 0)
214                 subject = "jail";
215
216         if (resolve_ids &&
217             strcasecmp(subject, "user") == 0 && strlen(textid) > 0) {
218                 error = parse_user(textid, &id, rule);
219                 if (error != 0) {
220                         free(tofree);
221                         return (NULL);
222                 }
223                 ret = asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest);
224         } else if (resolve_ids &&
225             strcasecmp(subject, "group") == 0 && strlen(textid) > 0) {
226                 error = parse_group(textid, &id, rule);
227                 if (error != 0) {
228                         free(tofree);
229                         return (NULL);
230                 }
231                 ret = asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest);
232         } else {
233                 ret = asprintf(&resolved, "%s:%s:%s", subject, textid, rest);
234         }
235
236         if (ret <= 0) {
237                 warn("asprintf");
238                 free(tofree);
239                 return (NULL);
240         }
241
242         free(tofree);
243
244         expanded = expand_amount(resolved, rule);
245         free(resolved);
246
247         return (expanded);
248 }
249
250 static char *
251 humanize_ids(char *rule)
252 {
253         id_t id;
254         struct passwd *pwd;
255         struct group *grp;
256         const char *subject, *textid, *rest;
257         char *end, *humanized;
258         int ret;
259
260         subject = strsep(&rule, ":");
261         textid = strsep(&rule, ":");
262         if (textid == NULL)
263                 errx(1, "rule passed from the kernel didn't contain subject");
264         if (rule != NULL)
265                 rest = rule;
266         else
267                 rest = "";
268
269         /* Replace numerical user and group ids with names. */
270         if (strcasecmp(subject, "user") == 0) {
271                 id = strtod(textid, &end);
272                 if ((size_t)(end - textid) != strlen(textid))
273                         errx(1, "malformed uid '%s'", textid);
274                 pwd = getpwuid(id);
275                 if (pwd != NULL)
276                         textid = pwd->pw_name;
277         } else if (strcasecmp(subject, "group") == 0) {
278                 id = strtod(textid, &end);
279                 if ((size_t)(end - textid) != strlen(textid))
280                         errx(1, "malformed gid '%s'", textid);
281                 grp = getgrgid(id);
282                 if (grp != NULL)
283                         textid = grp->gr_name;
284         }
285
286         ret = asprintf(&humanized, "%s:%s:%s", subject, textid, rest);
287         if (ret <= 0)
288                 err(1, "asprintf");
289
290         return (humanized);
291 }
292
293 static int
294 str2int64(const char *str, int64_t *value)
295 {
296         char *end;
297
298         if (str == NULL)
299                 return (EINVAL);
300
301         *value = strtoul(str, &end, 10);
302         if ((size_t)(end - str) != strlen(str))
303                 return (EINVAL);
304
305         return (0);
306 }
307
308 static char *
309 humanize_amount(char *rule)
310 {
311         int64_t num;
312         const char *subject, *subject_id, *resource, *action, *amount, *per;
313         char *copy, *humanized, buf[6], *tofree;
314         int ret;
315
316         tofree = copy = strdup(rule);
317         if (copy == NULL)
318                 err(1, "strdup");
319
320         subject = strsep(&copy, ":");
321         subject_id = strsep(&copy, ":");
322         resource = strsep(&copy, ":");
323         action = strsep(&copy, "=/");
324         amount = strsep(&copy, "/");
325         per = copy;
326
327         if (amount == NULL || strlen(amount) == 0 ||
328             str2int64(amount, &num) != 0) {
329                 free(tofree);
330                 return (rule);
331         }
332
333         assert(subject != NULL);
334         assert(subject_id != NULL);
335         assert(resource != NULL);
336         assert(action != NULL);
337
338         if (humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE,
339             HN_DECIMAL | HN_NOSPACE) == -1)
340                 err(1, "humanize_number");
341
342         if (per == NULL) {
343                 ret = asprintf(&humanized, "%s:%s:%s:%s=%s",
344                     subject, subject_id, resource, action, buf);
345         } else {
346                 ret = asprintf(&humanized, "%s:%s:%s:%s=%s/%s",
347                     subject, subject_id, resource, action, buf, per);
348         }
349
350         if (ret <= 0)
351                 err(1, "asprintf");
352
353         free(tofree);
354         return (humanized);
355 }
356
357 /*
358  * Print rules, one per line.
359  */
360 static void
361 print_rules(char *rules, int hflag, int nflag)
362 {
363         char *rule;
364
365         while ((rule = strsep(&rules, ",")) != NULL) {
366                 if (rule[0] == '\0')
367                         break; /* XXX */
368                 if (nflag == 0)
369                         rule = humanize_ids(rule);
370                 if (hflag)
371                         rule = humanize_amount(rule);
372                 printf("%s\n", rule);
373         }
374 }
375
376 static void
377 enosys(void)
378 {
379         int error, racct_enable;
380         size_t racct_enable_len;
381
382         racct_enable_len = sizeof(racct_enable);
383         error = sysctlbyname("kern.racct.enable",
384             &racct_enable, &racct_enable_len, NULL, 0);
385
386         if (error != 0) {
387                 if (errno == ENOENT)
388                         errx(1, "RACCT/RCTL support not present in kernel; see rctl(8) for details");
389
390                 err(1, "sysctlbyname");
391         }
392
393         if (racct_enable == 0)
394                 errx(1, "RACCT/RCTL present, but disabled; enable using kern.racct.enable=1 tunable");
395 }
396
397 static int
398 add_rule(const char *rule, const char *unexpanded_rule)
399 {
400         int error;
401
402         error = rctl_add_rule(rule, strlen(rule) + 1, NULL, 0);
403         if (error != 0) {
404                 if (errno == ENOSYS)
405                         enosys();
406                 warn("failed to add rule '%s'", unexpanded_rule);
407         }
408
409         return (error);
410 }
411
412 static int
413 show_limits(const char *filter, const char *unexpanded_rule,
414     int hflag, int nflag)
415 {
416         int error;
417         char *outbuf = NULL;
418         size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
419
420         for (;;) {
421                 outbuflen *= 4;
422                 outbuf = realloc(outbuf, outbuflen);
423                 if (outbuf == NULL)
424                         err(1, "realloc");
425                 error = rctl_get_limits(filter, strlen(filter) + 1,
426                     outbuf, outbuflen);
427                 if (error == 0)
428                         break;
429                 if (errno == ERANGE)
430                         continue;
431                 if (errno == ENOSYS)
432                         enosys();
433                 warn("failed to get limits for '%s'", unexpanded_rule);
434                 free(outbuf);
435
436                 return (error);
437         }
438
439         print_rules(outbuf, hflag, nflag);
440         free(outbuf);
441
442         return (error);
443 }
444
445 static int
446 remove_rule(const char *filter, const char *unexpanded_rule)
447 {
448         int error;
449
450         error = rctl_remove_rule(filter, strlen(filter) + 1, NULL, 0);
451         if (error != 0) {
452                 if (errno == ENOSYS)
453                         enosys();
454                 warn("failed to remove rule '%s'", unexpanded_rule);
455         }
456
457         return (error);
458 }
459
460 static char *
461 humanize_usage_amount(char *usage)
462 {
463         int64_t num;
464         const char *resource, *amount;
465         char *copy, *humanized, buf[6], *tofree;
466         int ret;
467
468         tofree = copy = strdup(usage);
469         if (copy == NULL)
470                 err(1, "strdup");
471
472         resource = strsep(&copy, "=");
473         amount = copy;
474
475         assert(resource != NULL);
476         assert(amount != NULL);
477
478         if (str2int64(amount, &num) != 0 || 
479             humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE,
480             HN_DECIMAL | HN_NOSPACE) == -1) {
481                 free(tofree);
482                 return (usage);
483         }
484
485         ret = asprintf(&humanized, "%s=%s", resource, buf);
486         if (ret <= 0)
487                 err(1, "asprintf");
488
489         free(tofree);
490         return (humanized);
491 }
492
493 /*
494  * Query the kernel about a resource usage and print it out.
495  */
496 static int
497 show_usage(const char *filter, const char *unexpanded_rule, int hflag)
498 {
499         int error;
500         char *copy, *outbuf = NULL, *tmp;
501         size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
502
503         for (;;) {
504                 outbuflen *= 4;
505                 outbuf = realloc(outbuf, outbuflen);
506                 if (outbuf == NULL)
507                         err(1, "realloc");
508                 error = rctl_get_racct(filter, strlen(filter) + 1,
509                     outbuf, outbuflen);
510                 if (error == 0)
511                         break;
512                 if (errno == ERANGE)
513                         continue;
514                 if (errno == ENOSYS)
515                         enosys();
516                 warn("failed to show resource consumption for '%s'",
517                     unexpanded_rule);
518                 free(outbuf);
519
520                 return (error);
521         }
522
523         copy = outbuf;
524         while ((tmp = strsep(&copy, ",")) != NULL) {
525                 if (tmp[0] == '\0')
526                         break; /* XXX */
527
528                 if (hflag)
529                         tmp = humanize_usage_amount(tmp);
530
531                 printf("%s\n", tmp);
532         }
533
534         free(outbuf);
535
536         return (error);
537 }
538
539 /*
540  * Query the kernel about resource limit rules and print them out.
541  */
542 static int
543 show_rules(const char *filter, const char *unexpanded_rule,
544     int hflag, int nflag)
545 {
546         int error;
547         char *outbuf = NULL;
548         size_t filterlen, outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
549
550         if (filter != NULL)
551                 filterlen = strlen(filter) + 1;
552         else
553                 filterlen = 0;
554
555         for (;;) {
556                 outbuflen *= 4;
557                 outbuf = realloc(outbuf, outbuflen);
558                 if (outbuf == NULL)
559                         err(1, "realloc");
560                 error = rctl_get_rules(filter, filterlen, outbuf, outbuflen);
561                 if (error == 0)
562                         break;
563                 if (errno == ERANGE)
564                         continue;
565                 if (errno == ENOSYS)
566                         enosys();
567                 warn("failed to show rules for '%s'", unexpanded_rule);
568                 free(outbuf);
569
570                 return (error);
571         }
572
573         print_rules(outbuf, hflag, nflag);
574         free(outbuf);
575
576         return (error);
577 }
578
579 static void
580 usage(void)
581 {
582
583         fprintf(stderr, "usage: rctl [ -h ] [-a rule | -l filter | -r filter "
584             "| -u filter | filter]\n");
585         exit(1);
586 }
587
588 int
589 main(int argc __unused, char **argv __unused)
590 {
591         int ch, aflag = 0, hflag = 0, nflag = 0, lflag = 0, rflag = 0,
592             uflag = 0;
593         char *rule = NULL, *unexpanded_rule;
594         int i, cumulated_error, error;
595
596         while ((ch = getopt(argc, argv, "ahlnru")) != -1) {
597                 switch (ch) {
598                 case 'a':
599                         aflag = 1;
600                         break;
601                 case 'h':
602                         hflag = 1;
603                         break;
604                 case 'l':
605                         lflag = 1;
606                         break;
607                 case 'n':
608                         nflag = 1;
609                         break;
610                 case 'r':
611                         rflag = 1;
612                         break;
613                 case 'u':
614                         uflag = 1;
615                         break;
616
617                 case '?':
618                 default:
619                         usage();
620                 }
621         }
622
623         argc -= optind;
624         argv += optind;
625         
626         if (aflag + lflag + rflag + uflag > 1)
627                 errx(1, "at most one of -a, -l, -r, or -u may be specified");
628
629         if (argc == 0) {
630                 if (aflag + lflag + rflag + uflag == 0) {
631                         rule = strdup("::");
632                         show_rules(rule, rule, hflag, nflag);
633
634                         return (0);
635                 }
636
637                 usage();
638         }
639
640         cumulated_error = 0;
641
642         for (i = 0; i < argc; i++) {
643                 unexpanded_rule = argv[i];
644
645                 /*
646                  * Skip resolving if passed -n _and_ -a.  Ignore -n otherwise,
647                  * so we can still do "rctl -n u:root" and see the rules without
648                  * resolving the UID.
649                  */
650                 if (aflag != 0 && nflag != 0)
651                         rule = expand_rule(unexpanded_rule, false);
652                 else
653                         rule = expand_rule(unexpanded_rule, true);
654
655                 if (rule == NULL) {
656                         cumulated_error++;
657                         continue;
658                 }
659
660                 /*
661                  * The reason for passing the unexpanded_rule is to make
662                  * it easier for the user to search for the problematic
663                  * rule in the passed input.
664                  */
665                 if (aflag) {
666                         error = add_rule(rule, unexpanded_rule);
667                 } else if (lflag) {
668                         error = show_limits(rule, unexpanded_rule,
669                             hflag, nflag);
670                 } else if (rflag) {
671                         error = remove_rule(rule, unexpanded_rule);
672                 } else if (uflag) {
673                         error = show_usage(rule, unexpanded_rule, hflag);
674                 } else  {
675                         error = show_rules(rule, unexpanded_rule,
676                             hflag, nflag);
677                 }
678
679                 if (error != 0)
680                         cumulated_error++;
681
682                 free(rule);
683         }
684
685         return (cumulated_error);
686 }