2 * Copyright (c) 2009 The DragonFly Project. All rights reserved.
4 * This code is derived from software contributed to The DragonFly Project
5 * by Alex Hornung <ahornung@gmail.com>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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
15 * the documentation and/or other materials provided with the
17 * 3. Neither the name of The DragonFly Project nor the names of its
18 * contributors may be used to endorse or promote products derived
19 * from this software without specific, prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 #include <sys/types.h>
36 #include <sys/cdefs.h>
37 #include <sys/syslimits.h>
38 #include <sys/ioctl.h>
39 #include <sys/device.h>
40 #include <sys/queue.h>
42 #include <sys/devfs_rules.h>
62 rule_parser_t *parser;
73 static int parser_include(char **);
74 static int parser_jail(char **);
75 static int parser_hide(char **);
76 static int parser_show(char **);
77 static int parser_link(char **);
78 static int parser_group(char **);
79 static int parser_perm(char **);
80 static int dump_config_entry(struct rule *, struct groupdevid *);
81 static int rule_id_iterate(struct groupdevid *, struct rule *,
82 rule_iterate_callback_t *);
83 static int rule_ioctl(unsigned long, struct devfs_rule_ioctl *);
84 static void rule_fill(struct devfs_rule_ioctl *, struct rule *,
86 static int rule_send(struct rule *, struct groupdevid *);
87 static int rule_check_num_args(char **, int);
88 static int process_line(FILE*, int);
89 static int rule_parser(char **tokens);
91 static int ruletab_parser(char **tokens);
93 static void usage(void);
97 const char *config_name = NULL, *mountp = NULL;
99 static int aflag = 0, cflag = 0, rflag = 0, tflag = 0;
100 static int line_stack[RULE_MAX_STACK];
101 static char *file_stack[RULE_MAX_STACK];
102 static char *cwd_stack[RULE_MAX_STACK];
103 static int line_stack_depth = 0;
106 static TAILQ_HEAD(, rule) rule_list =
107 TAILQ_HEAD_INITIALIZER(rule_list);
108 static TAILQ_HEAD(, rule_tab) rule_tab_list =
109 TAILQ_HEAD_INITIALIZER(rule_tab_list);
110 static TAILQ_HEAD(, groupdevid) group_list =
111 TAILQ_HEAD_INITIALIZER(group_list);
114 static const struct verb parsers[] = {
115 { "include", parser_include, 1 },
116 { "jail", parser_jail, 1 },
117 { "group", parser_group, 2 },
118 { "perm", parser_perm, 2 },
119 { "link", parser_link, 2 },
120 { "hide", parser_hide, 2 },
121 { "show", parser_show, 2 },
125 static const struct devtype devtypes[] = {
126 { "D_TAPE", D_TAPE },
127 { "D_DISK", D_DISK },
134 syntax_error(const char *fmt, ...)
140 vsnprintf(buf, sizeof(buf), fmt, ap);
142 errx(1, "%s: syntax error on line %d: %s\n",file_stack[line_stack_depth],
143 line_stack[line_stack_depth], buf);
147 parser_include(char **tokens)
152 error = stat(tokens[1], &sb);
155 syntax_error("could not stat %s on include, error: %s",
156 tokens[1], strerror(errno));
158 chdir(dirname(tokens[1]));
159 read_config(basename(tokens[1]), RULES_FILE);
165 parser_jail(char **tokens)
167 if (tokens[1][0] == 'y') {
169 } else if (tokens[1][0] == 'n') {
172 syntax_error("incorrect argument to 'jail'. Must be either y[es] or n[o]");
179 parser_hide(char **tokens)
181 struct groupdevid *id;
184 id = get_id(tokens[1]);
185 rule = new_rule(rHIDE, id);
192 parser_show(char **tokens)
194 struct groupdevid *id;
197 id = get_id(tokens[1]);
198 rule = new_rule(rSHOW, id);
205 parser_link(char **tokens)
207 struct groupdevid *id;
210 id = get_id(tokens[1]);
211 rule = new_rule(rLINK, id);
212 rule->dest = strdup(tokens[2]);
219 parser_group(char **tokens)
221 struct groupdevid *gid, *id;
225 gid = get_group(tokens[1], 1);
226 for (k = 0; gid->list[k] != NULL; k++)
228 for (i = 2; tokens[i] != NULL; i++) {
229 id = get_id(tokens[i]);
231 syntax_error("recursive group definition for group %s", gid->name);
233 if (k >= gid->listsize-1 ) {
234 gid->list = realloc(gid->list,
235 2*gid->listsize*sizeof(struct groupdevid *));
248 parser_perm(char **tokens)
252 struct groupdevid *id;
257 id = get_id(tokens[1]);
258 rule = new_rule(rPERM, id);
260 rule->mode = strtol(tokens[3], NULL, 8);
262 grname = strchr(tokens[2], ':');
264 syntax_error("invalid format for user/group (%s)", tokens[2]);
268 if ((pwd = getpwnam(uname)))
269 rule->uid = pwd->pw_uid;
271 syntax_error("invalid user name %s", uname);
273 if ((grp = getgrnam(grname)))
274 rule->gid = grp->gr_gid;
276 syntax_error("invalid group name %s", grname);
283 new_id(const char *name, int type_in)
285 struct groupdevid *id;
286 int type = (type_in != 0)?(type_in):(isNAME), i;
288 id = calloc(1, sizeof(*id));
293 for (i = 0; devtypes[i].name != NULL; i++) {
294 if (!strcmp(devtypes[i].name, name)) {
296 id->devtype = devtypes[i].value;
303 if ((type == isNAME) || (type == isGROUP)) {
304 id->name = strdup(name);
307 if (type == isGROUP) {
308 id->list = calloc(4, sizeof(struct groupdevid *));
309 memset(id->list, 0, 4 * sizeof(struct groupdevid *));
317 get_id(const char *name)
319 struct groupdevid *id;
321 if ((name[0] == '@') && (name[1] != '\0')) {
322 id = get_group(name+1, 0);
324 syntax_error("unknown group name '%s', you "
325 "have to use the 'group' verb first.", name+1);
328 id = new_id(name, 0);
334 get_group(const char *name, int expect)
336 struct groupdevid *g;
338 TAILQ_FOREACH(g, &group_list, link) {
339 if (strcmp(g->name, name) == 0)
343 /* Caller doesn't expect to get a group no matter what */
347 g = new_id(name, isGROUP);
348 TAILQ_INSERT_TAIL(&group_list, g, link);
353 new_rule(int type, struct groupdevid *id)
357 rule = calloc(1, sizeof(*rule));
368 add_rule(struct rule *rule)
370 TAILQ_INSERT_TAIL(&rule_list, rule, link);
374 dump_config_entry(struct rule *rule, struct groupdevid *id)
380 switch (rule->type) {
381 case rPERM: printf("perm "); break;
382 case rLINK: printf("link "); break;
383 case rHIDE: printf("hide "); break;
384 case rSHOW: printf("show "); break;
385 default: errx(1, "invalid rule type");
389 case isGROUP: printf("@"); /* FALLTHROUGH */
390 case isNAME: printf("%s", id->name); break;
392 for (i = 0; devtypes[i].name != NULL; i++) {
393 if (devtypes[i].value == id->devtype) {
394 printf("%s", devtypes[i].name);
399 default: errx(1, "invalid id type %d", id->type);
402 switch (rule->type) {
404 pwd = getpwuid(rule->uid);
405 grp = getgrgid(rule->gid);
407 printf(" %s:%s 0%.03o",
412 printf(" %d:%d 0%.03o",
419 printf(" %s", rule->dest);
421 default: /* NOTHING */;
425 printf("\t(only affects jails)");
433 rule_id_iterate(struct groupdevid *id, struct rule *rule,
434 rule_iterate_callback_t *callback)
439 if (id->type == isGROUP) {
440 for (i = 0; id->list[i] != NULL; i++) {
441 if ((error = rule_id_iterate(id->list[i], rule, callback)))
445 error = callback(rule, id);
456 TAILQ_FOREACH(rule, &rule_list, link) {
457 rule_id_iterate(rule->id, rule, dump_config_entry);
462 rule_ioctl(unsigned long cmd, struct devfs_rule_ioctl *rule)
464 if (ioctl(dev_fd, cmd, rule) == -1)
471 rule_fill(struct devfs_rule_ioctl *dr, struct rule *r, struct groupdevid *id)
478 errx(1, "invalid id type");
480 errx(1, "internal error: can not fill group rule");
483 dr->rule_type |= DEVFS_RULE_NAME;
484 strncpy(dr->name, id->name, PATH_MAX-1);
487 dr->rule_type |= DEVFS_RULE_TYPE;
488 dr->dev_type = id->devtype;
494 dr->rule_cmd |= DEVFS_RULE_PERM;
500 dr->rule_cmd |= DEVFS_RULE_LINK;
501 strncpy(dr->linkname, r->dest, PATH_MAX-1);
504 dr->rule_cmd |= DEVFS_RULE_HIDE;
507 dr->rule_cmd |= DEVFS_RULE_SHOW;
512 dr->rule_type |= DEVFS_RULE_JAIL;
516 rule_send(struct rule *rule, struct groupdevid *id)
518 struct devfs_rule_ioctl dr;
521 strncpy(dr.mntpoint, mountp, PATH_MAX-1);
523 rule_fill(&dr, rule, id);
524 r = rule_ioctl(DEVFS_RULE_ADD, &dr);
532 struct devfs_rule_ioctl dr;
536 strncpy(dr.mntpoint, mountp, PATH_MAX-1);
538 TAILQ_FOREACH(rule, &rule_list, link) {
539 r = rule_id_iterate(rule->id, rule, rule_send);
544 return (rule_ioctl(DEVFS_RULE_APPLY, &dr));
548 rule_check_num_args(char **tokens, int num)
552 for (i = 0; tokens[i] != NULL; i++)
556 syntax_error("at least %d tokens were expected but only %d were found", num, i);
563 read_config(const char *name, int ftype)
568 if ((fd = fopen(name, "r")) == NULL) {
569 printf("Error opening config file %s\n", name);
574 if (fstat(fileno(fd), &sb) != 0) {
575 errx(1, "file %s could not be fstat'ed, aborting", name);
579 errx(1, "file %s does not belong to root, aborting!", name);
581 if (++line_stack_depth >= RULE_MAX_STACK) {
583 syntax_error("Maximum include depth (%d) exceeded, "
584 "check for recursion.", RULE_MAX_STACK);
587 line_stack[line_stack_depth] = 1;
588 file_stack[line_stack_depth] = strdup(name);
589 cwd_stack[line_stack_depth] = getwd(NULL);
591 while (process_line(fd, ftype) == 0)
592 line_stack[line_stack_depth]++;
596 free(file_stack[line_stack_depth]);
597 free(cwd_stack[line_stack_depth]);
599 chdir(cwd_stack[line_stack_depth]);
605 process_line(FILE* fd, int ftype)
613 while (((c = fgetc(fd)) != EOF) && (c != '\n')) {
614 buffer[i++] = (char)c;
615 if (i == (sizeof(buffer) -1))
620 if (feof(fd) || ferror(fd))
623 while (((buffer[c] == ' ') || (buffer[c] == '\t')) && (c < i)) c++;
625 * If this line effectively (after indentation) begins with the comment
626 * character #, we ignore the rest of the line.
628 if (buffer[c] == '#')
631 tokens[0] = &buffer[c];
632 for (n = 1; c < i; c++) {
633 if (buffer[c] == '"') {
636 if ((c >= 1) && (&buffer[c] != tokens[n-1])) {
637 syntax_error("stray opening quote not at beginning of token");
640 tokens[n-1] = &buffer[c+1];
642 if ((c < i-1) && (!iswhitespace(buffer[c+1]))) {
643 syntax_error("stray closing quote not at end of token");
654 if ((buffer[c] == ' ') || (buffer[c] == '\t')) {
656 while ((iswhitespace(buffer[c])) && (c < i)) c++;
657 tokens[n++] = &buffer[c--];
663 * If there are not enough arguments for any function or it is
664 * a line full of whitespaces, we just return here. Or if a
665 * quote wasn't closed.
667 if ((quote) || (n < 2) || (tokens[0][0] == '\0'))
672 ret = rule_parser(tokens);
676 ret = ruletab_parser(tokens);
687 rule_parser(char **tokens)
692 /* Convert the command/verb to lowercase */
693 for (i = 0; tokens[0][i] != '\0'; i++)
694 tokens[0][i] = tolower(tokens[0][i]);
696 for (i = 0; parsers[i].verb; i++) {
697 if (rule_check_num_args(tokens, parsers[i].min_args) != 0)
700 if (!strcmp(tokens[0], parsers[i].verb)) {
701 parsers[i].parser(tokens);
707 syntax_error("unknown verb/command %s", tokens[0]);
715 ruletab_parser(char **tokens)
722 if (rule_check_num_args(tokens, 2) != 0)
725 error = stat(tokens[0], &sb);
727 printf("ruletab warning: could not stat %s: %s\n",
728 tokens[0], strerror(errno));
731 if (tokens[0][0] != '/') {
732 errx(1, "ruletab error: entry %s does not seem to be an absolute path",
736 for (i = 1; tokens[i] != NULL; i++) {
737 rt = calloc(1, sizeof(struct rule_tab));
738 rt->mntpoint = strdup(tokens[0]);
739 rt->rule_file = strdup(tokens[i]);
740 TAILQ_INSERT_TAIL(&rule_tab_list, rt, link);
754 error = read_config("ruletab", RULETAB_FILE);
757 errx(1, "could not read/process ruletab file (/etc/devfs/ruletab)");
759 if (!strcmp(mountp, "*")) {
761 } else if (!strcmp(mountp, "boot")) {
762 mode = RULETAB_ONLY_BOOT;
764 mode = RULETAB_SPECIFIC;
766 errx(1, "-t needs -m");
769 dev_fd = open("/dev/devfs", O_RDWR);
771 err(1, "open(/dev/devfs)");
773 TAILQ_FOREACH(rt, &rule_tab_list, link) {
775 case RULETAB_ONLY_BOOT:
776 if ((strcmp(rt->mntpoint, "*") != 0) &&
777 (strcmp(rt->mntpoint, "/dev") != 0)) {
781 case RULETAB_SPECIFIC:
782 if (strcmp(rt->mntpoint, mountp) != 0)
787 read_config(rt->rule_file, RULES_FILE);
788 mountp = rt->mntpoint;
801 struct groupdevid *gdp;
803 TAILQ_FOREACH(rp, &rule_list, link) {
804 TAILQ_REMOVE(&rule_list, rp, link);
807 TAILQ_FOREACH(gdp, &group_list, link) {
808 TAILQ_REMOVE(&group_list, gdp, link);
817 "Usage: devfsctl <commands> [options]\n"
818 "Valid commands are:\n"
820 "\t Loads all read rules into the kernel and applies them\n"
822 "\t Clears all rules stored in the kernel but does not reset the nodes\n"
824 "\t Dumps the rules that have been loaded to the screen to verify syntax\n"
826 "\t Resets all devfs_nodes but does not clear the rules stored\n"
828 "Valid options and its arguments are:\n"
829 " -f <config_file>\n"
830 "\t Specifies the configuration file to be used\n"
831 " -m <mount_point>\n"
832 "\t Specifies a mount point to which the command will apply. Defaults to *\n"
838 int main(int argc, char *argv[])
840 struct devfs_rule_ioctl dummy_rule;
844 while ((ch = getopt(argc, argv, "acdf:hm:r")) != -1) {
847 config_name = optarg;
878 * - need to use at least one mode
879 * - can not use -d with any other mode
880 * - can not use -t with any other mode or -f
882 if (!(aflag || rflag || cflag || dflag) ||
883 (dflag && (aflag || rflag || cflag || tflag))) {
890 else if (mountp[0] != '/') {
891 errx(1, "-m needs to be given an absolute path");
894 strncpy(dummy_rule.mntpoint, mountp, PATH_MAX-1);
896 if (config_name != NULL) {
897 error = stat(config_name, &sb);
901 error = stat(config_name, &sb);
905 err(1, "could not stat specified configuration file %s", config_name);
907 if (config_name[0] == '/')
908 chdir(dirname(config_name));
910 read_config(config_name, RULES_FILE);
918 dev_fd = open("/dev/devfs", O_RDWR);
920 err(1, "open(/dev/devfs)");
923 rule_ioctl(DEVFS_RULE_CLEAR, &dummy_rule);
926 rule_ioctl(DEVFS_RULE_RESET, &dummy_rule);