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