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