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