dhcpcd: Note update to 9.4.1
[dragonfly.git] / sbin / devfsctl / devfsctl.c
blobda4b0014d47b8d46230f23ed62c306cfbe84a715
1 /*
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
9 * 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
15 * the documentation and/or other materials provided with the
16 * distribution.
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
32 * SUCH DAMAGE.
35 #include <sys/param.h>
36 #include <sys/ioctl.h>
37 #include <sys/device.h>
38 #include <sys/queue.h>
39 #include <sys/stat.h>
40 #include <sys/devfs_rules.h>
42 #include <ctype.h>
43 #include <err.h>
44 #include <errno.h>
45 #include <fcntl.h>
46 #include <grp.h>
47 #include <libgen.h>
48 #include <pwd.h>
49 #include <stdarg.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <unistd.h>
56 #include "devfsctl.h"
58 struct verb {
59 const char *verb;
60 rule_parser_t *parser;
61 int min_args;
64 struct devtype {
65 const char *name;
66 int value;
71 static int parser_include(char **);
72 static int parser_jail(char **);
73 static int parser_hide(char **);
74 static int parser_show(char **);
75 static int parser_link(char **);
76 static int parser_group(char **);
77 static int parser_perm(char **);
78 static int dump_config_entry(struct rule *, struct groupdevid *);
79 static int rule_id_iterate(struct groupdevid *, struct rule *,
80 rule_iterate_callback_t *);
81 static int rule_ioctl(unsigned long, struct devfs_rule_ioctl *);
82 static void rule_fill(struct devfs_rule_ioctl *, struct rule *,
83 struct groupdevid *);
84 static int rule_send(struct rule *, struct groupdevid *);
85 static int rule_check_num_args(char **, int);
86 static int process_line(FILE*, int);
87 static int rule_parser(char **tokens);
88 #if 0
89 static int ruletab_parser(char **tokens);
90 #endif
91 static void usage(void);
93 static int dev_fd;
95 static const char *config_name = NULL, *mountp = NULL;
96 static int dflag = 0;
97 static int aflag = 0, cflag = 0, rflag = 0, tflag = 0;
98 static int line_stack[RULE_MAX_STACK];
99 static char *file_stack[RULE_MAX_STACK];
100 static char *cwd_stack[RULE_MAX_STACK];
101 static int line_stack_depth = 0;
102 static int jail = 0;
104 static TAILQ_HEAD(, rule) rule_list =
105 TAILQ_HEAD_INITIALIZER(rule_list);
106 static TAILQ_HEAD(, rule_tab) rule_tab_list =
107 TAILQ_HEAD_INITIALIZER(rule_tab_list);
108 static TAILQ_HEAD(, groupdevid) group_list =
109 TAILQ_HEAD_INITIALIZER(group_list);
112 static const struct verb parsers[] = {
113 { "include", parser_include, 1 },
114 { "jail", parser_jail, 1 },
115 { "group", parser_group, 2 },
116 { "perm", parser_perm, 2 },
117 { "link", parser_link, 2 },
118 { "hide", parser_hide, 2 },
119 { "show", parser_show, 2 },
120 { NULL, NULL, 0 }
123 static const struct devtype devtypes[] = {
124 { "D_TAPE", D_TAPE },
125 { "D_DISK", D_DISK },
126 { "D_TTY", D_TTY },
127 { "D_MEM", D_MEM },
128 { NULL, 0 }
131 void
132 syntax_error(const char *fmt, ...)
134 char buf[1024];
135 va_list ap;
137 va_start(ap, fmt);
138 vsnprintf(buf, sizeof(buf), fmt, ap);
139 va_end(ap);
140 errx(1, "%s: syntax error on line %d: %s\n",
141 file_stack[line_stack_depth], line_stack[line_stack_depth], buf);
144 static int
145 parser_include(char **tokens)
147 struct stat sb;
148 int error;
150 error = stat(tokens[1], &sb);
152 if (error)
153 syntax_error("could not stat %s on include, error: %s",
154 tokens[1], strerror(errno));
156 chdir(dirname(tokens[1]));
157 read_config(basename(tokens[1]), RULES_FILE);
159 return 0;
162 static int
163 parser_jail(char **tokens)
165 if (tokens[1][0] == 'y') {
166 jail = 1;
167 } else if (tokens[1][0] == 'n') {
168 jail = 0;
169 } else {
170 syntax_error("incorrect argument to 'jail'. Must be either y[es] or n[o]");
173 return 0;
176 static int
177 parser_hide(char **tokens)
179 struct groupdevid *id;
180 struct rule *rule;
182 id = get_id(tokens[1]);
183 rule = new_rule(rHIDE, id);
184 add_rule(rule);
186 return 0;
189 static int
190 parser_show(char **tokens)
192 struct groupdevid *id;
193 struct rule *rule;
195 id = get_id(tokens[1]);
196 rule = new_rule(rSHOW, id);
197 add_rule(rule);
199 return 0;
202 static int
203 parser_link(char **tokens)
205 struct groupdevid *id;
206 struct rule *rule;
208 id = get_id(tokens[1]);
209 rule = new_rule(rLINK, id);
210 rule->dest = strdup(tokens[2]);
211 add_rule(rule);
213 return 0;
216 static int
217 parser_group(char **tokens)
219 struct groupdevid *gid, *id;
220 int i;
221 size_t k;
223 gid = get_group(tokens[1], 1);
224 for (k = 0; gid->list[k] != NULL; k++)
225 /* Do nothing */;
226 for (i = 2; tokens[i] != NULL; i++) {
227 id = get_id(tokens[i]);
228 if (id == gid) {
229 syntax_error("recursive group definition for group %s", gid->name);
230 } else {
231 if (k >= gid->listsize-1 ) {
232 gid->list = realloc(gid->list,
233 2*gid->listsize*sizeof(struct groupdevid *));
234 gid->listsize *= 2;
237 gid->list[k++] = id;
240 gid->list[k] = NULL;
242 return 0;
245 static int
246 parser_perm(char **tokens)
248 struct passwd *pwd;
249 struct group *grp;
250 struct groupdevid *id;
251 struct rule *rule;
252 char *uname;
253 char *grname;
255 id = get_id(tokens[1]);
256 rule = new_rule(rPERM, id);
258 rule->mode = strtol(tokens[3], NULL, 8);
259 uname = tokens[2];
260 grname = strchr(tokens[2], ':');
261 if (grname == NULL)
262 syntax_error("invalid format for user/group (%s)", tokens[2]);
264 *grname = '\0';
265 ++grname;
266 if ((pwd = getpwnam(uname)))
267 rule->uid = pwd->pw_uid;
268 else
269 syntax_error("invalid user name %s", uname);
271 if ((grp = getgrnam(grname)))
272 rule->gid = grp->gr_gid;
273 else
274 syntax_error("invalid group name %s", grname);
276 add_rule(rule);
277 return 0;
280 struct groupdevid *
281 new_id(const char *name, int type_in)
283 struct groupdevid *id;
284 int type = (type_in != 0)?(type_in):(isNAME), i;
286 id = calloc(1, sizeof(*id));
287 if (id == NULL)
288 err(1, NULL);
290 if (type_in == 0) {
291 for (i = 0; devtypes[i].name != NULL; i++) {
292 if (!strcmp(devtypes[i].name, name)) {
293 type = isTYPE;
294 id->devtype = devtypes[i].value;
295 break;
299 id->type = type;
301 if ((type == isNAME) || (type == isGROUP)) {
302 id->name = strdup(name);
305 if (type == isGROUP) {
306 id->list = calloc(4, sizeof(struct groupdevid *));
307 memset(id->list, 0, 4 * sizeof(struct groupdevid *));
308 id->listsize = 4;
311 return (id);
314 struct groupdevid *
315 get_id(const char *name)
317 struct groupdevid *id;
319 if ((name[0] == '@') && (name[1] != '\0')) {
320 id = get_group(name+1, 0);
321 if (id == NULL)
322 syntax_error("unknown group name '%s', you "
323 "have to use the 'group' verb first.", name+1);
325 else
326 id = new_id(name, 0);
328 return id;
331 struct groupdevid *
332 get_group(const char *name, int expect)
334 struct groupdevid *g;
336 TAILQ_FOREACH(g, &group_list, link) {
337 if (strcmp(g->name, name) == 0)
338 return (g);
341 /* Caller doesn't expect to get a group no matter what */
342 if (!expect)
343 return NULL;
345 g = new_id(name, isGROUP);
346 TAILQ_INSERT_TAIL(&group_list, g, link);
347 return (g);
350 struct rule *
351 new_rule(int type, struct groupdevid *id)
353 struct rule *rule;
355 rule = calloc(1, sizeof(*rule));
356 if (rule == NULL)
357 err(1, NULL);
359 rule->type = type;
360 rule->id = id;
361 rule->jail = jail;
362 return (rule);
365 void
366 add_rule(struct rule *rule)
368 TAILQ_INSERT_TAIL(&rule_list, rule, link);
371 static int
372 dump_config_entry(struct rule *rule, struct groupdevid *id)
374 struct passwd *pwd;
375 struct group *grp;
376 int i;
378 switch (rule->type) {
379 case rPERM: printf("perm "); break;
380 case rLINK: printf("link "); break;
381 case rHIDE: printf("hide "); break;
382 case rSHOW: printf("show "); break;
383 default: errx(1, "invalid rule type");
386 switch (id->type) {
387 case isGROUP: printf("@"); /* FALLTHROUGH */
388 case isNAME: printf("%s", id->name); break;
389 case isTYPE:
390 for (i = 0; devtypes[i].name != NULL; i++) {
391 if (devtypes[i].value == id->devtype) {
392 printf("%s", devtypes[i].name);
393 break;
396 break;
397 default: errx(1, "invalid id type %d", id->type);
400 switch (rule->type) {
401 case rPERM:
402 pwd = getpwuid(rule->uid);
403 grp = getgrgid(rule->gid);
404 if (pwd && grp) {
405 printf(" %s:%s 0%.03o",
406 pwd->pw_name,
407 grp->gr_name,
408 rule->mode);
409 } else {
410 printf(" %d:%d 0%.03o",
411 rule->uid,
412 rule->gid,
413 rule->mode);
415 break;
416 case rLINK:
417 printf(" %s", rule->dest);
418 break;
419 default: /* NOTHING */;
422 if (rule->jail)
423 printf("\t(only affects jails)");
425 printf("\n");
427 return 0;
430 static int
431 rule_id_iterate(struct groupdevid *id, struct rule *rule,
432 rule_iterate_callback_t *callback)
434 int error = 0;
435 int i;
437 if (id->type == isGROUP) {
438 for (i = 0; id->list[i] != NULL; i++) {
439 if ((error = rule_id_iterate(id->list[i], rule, callback)))
440 return error;
442 } else {
443 error = callback(rule, id);
446 return error;
449 void
450 dump_config(void)
452 struct rule *rule;
454 TAILQ_FOREACH(rule, &rule_list, link) {
455 rule_id_iterate(rule->id, rule, dump_config_entry);
459 static int
460 rule_ioctl(unsigned long cmd, struct devfs_rule_ioctl *rule)
462 if (ioctl(dev_fd, cmd, rule) == -1)
463 err(1, "ioctl");
465 return 0;
468 static void
469 rule_fill(struct devfs_rule_ioctl *dr, struct rule *r, struct groupdevid *id)
471 dr->rule_type = 0;
472 dr->rule_cmd = 0;
474 switch (id->type) {
475 default:
476 errx(1, "invalid id type");
477 case isGROUP:
478 errx(1, "internal error: can not fill group rule");
479 /* NOTREACHED */
480 case isNAME:
481 dr->rule_type |= DEVFS_RULE_NAME;
482 strncpy(dr->name, id->name, PATH_MAX-1);
483 break;
484 case isTYPE:
485 dr->rule_type |= DEVFS_RULE_TYPE;
486 dr->dev_type = id->devtype;
487 break;
490 switch (r->type) {
491 case rPERM:
492 dr->rule_cmd |= DEVFS_RULE_PERM;
493 dr->uid = r->uid;
494 dr->gid = r->gid;
495 dr->mode = r->mode;
496 break;
497 case rLINK:
498 dr->rule_cmd |= DEVFS_RULE_LINK;
499 strncpy(dr->linkname, r->dest, PATH_MAX-1);
500 break;
501 case rHIDE:
502 dr->rule_cmd |= DEVFS_RULE_HIDE;
503 break;
504 case rSHOW:
505 dr->rule_cmd |= DEVFS_RULE_SHOW;
506 break;
509 if (r->jail)
510 dr->rule_type |= DEVFS_RULE_JAIL;
513 static int
514 rule_send(struct rule *rule, struct groupdevid *id)
516 struct devfs_rule_ioctl dr;
517 int r = 0;
519 strncpy(dr.mntpoint, mountp, PATH_MAX-1);
521 rule_fill(&dr, rule, id);
522 r = rule_ioctl(DEVFS_RULE_ADD, &dr);
524 return r;
528 rule_apply(void)
530 struct devfs_rule_ioctl dr;
531 struct rule *rule;
532 int r = 0;
534 strncpy(dr.mntpoint, mountp, PATH_MAX-1);
536 TAILQ_FOREACH(rule, &rule_list, link) {
537 r = rule_id_iterate(rule->id, rule, rule_send);
538 if (r != 0)
539 return (-1);
542 return (rule_ioctl(DEVFS_RULE_APPLY, &dr));
545 static int
546 rule_check_num_args(char **tokens, int num)
548 int i;
550 for (i = 0; tokens[i] != NULL; i++)
553 if (i < num) {
554 syntax_error("at least %d tokens were expected but only %d were found", num, i);
555 return 1;
557 return 0;
561 read_config(const char *name, int ftype)
563 FILE *fd;
564 struct stat sb;
566 if ((fd = fopen(name, "r")) == NULL) {
567 printf("Error opening config file %s\n", name);
568 perror("fopen");
569 return 1;
572 if (fstat(fileno(fd), &sb) != 0) {
573 errx(1, "file %s could not be fstat'ed, aborting", name);
576 if (sb.st_uid != 0)
577 errx(1, "file %s does not belong to root, aborting!", name);
579 if (++line_stack_depth >= RULE_MAX_STACK) {
580 --line_stack_depth;
581 syntax_error("Maximum include depth (%d) exceeded, "
582 "check for recursion.", RULE_MAX_STACK);
585 line_stack[line_stack_depth] = 1;
586 file_stack[line_stack_depth] = strdup(name);
587 cwd_stack[line_stack_depth] = getwd(NULL);
589 while (process_line(fd, ftype) == 0)
590 line_stack[line_stack_depth]++;
592 fclose(fd);
594 free(file_stack[line_stack_depth]);
595 free(cwd_stack[line_stack_depth]);
596 --line_stack_depth;
597 chdir(cwd_stack[line_stack_depth]);
599 return 0;
602 static int
603 process_line(FILE* fd, int ftype)
605 char buffer[4096];
606 char *tokens[256];
607 int c, n, i = 0;
608 int quote = 0;
609 int ret = 0;
611 while (((c = fgetc(fd)) != EOF) && (c != '\n')) {
612 buffer[i++] = (char)c;
613 if (i == (sizeof(buffer) -1))
614 break;
616 buffer[i] = '\0';
618 if (feof(fd) || ferror(fd))
619 ret = 1;
620 c = 0;
621 while (((buffer[c] == ' ') || (buffer[c] == '\t')) && (c < i)) c++;
623 * If this line effectively (after indentation) begins with the comment
624 * character #, we ignore the rest of the line.
626 if (buffer[c] == '#')
627 return 0;
629 tokens[0] = &buffer[c];
630 for (n = 1; c < i; c++) {
631 if (buffer[c] == '"') {
632 quote = !quote;
633 if (quote) {
634 if ((c >= 1) && (&buffer[c] != tokens[n-1])) {
635 syntax_error("stray opening quote not at beginning of token");
636 /* NOTREACHED */
638 tokens[n-1] = &buffer[c+1];
639 } else {
640 if ((c < i-1) && (!iswhitespace(buffer[c+1]))) {
641 syntax_error("stray closing quote not at end of token");
642 /* NOTREACHED */
644 buffer[c] = '\0';
648 if (quote) {
649 continue;
652 if ((buffer[c] == ' ') || (buffer[c] == '\t')) {
653 buffer[c++] = '\0';
654 while ((iswhitespace(buffer[c])) && (c < i)) c++;
655 tokens[n++] = &buffer[c--];
658 tokens[n] = NULL;
661 * If there are not enough arguments for any function or it is
662 * a line full of whitespaces, we just return here. Or if a
663 * quote wasn't closed.
665 if ((quote) || (n < 2) || (tokens[0][0] == '\0'))
666 return ret;
668 switch (ftype) {
669 case RULES_FILE:
670 ret = rule_parser(tokens);
671 break;
672 #if 0
673 case RULETAB_FILE:
674 ret = ruletab_parser(tokens);
675 break;
676 #endif
677 default:
678 ret = 1;
681 return ret;
684 static int
685 rule_parser(char **tokens)
687 int i;
688 int parsed = 0;
690 /* Convert the command/verb to lowercase */
691 for (i = 0; tokens[0][i] != '\0'; i++)
692 tokens[0][i] = tolower(tokens[0][i]);
694 for (i = 0; parsers[i].verb; i++) {
695 if (rule_check_num_args(tokens, parsers[i].min_args) != 0)
696 continue;
698 if (!strcmp(tokens[0], parsers[i].verb)) {
699 parsers[i].parser(tokens);
700 parsed = 1;
701 break;
704 if (parsed == 0) {
705 syntax_error("unknown verb/command %s", tokens[0]);
708 return 0;
711 #if 0
712 static int
713 ruletab_parser(char **tokens)
715 struct rule_tab *rt;
716 struct stat sb;
717 int i;
718 int error;
720 if (rule_check_num_args(tokens, 2) != 0)
721 return 0;
723 error = stat(tokens[0], &sb);
724 if (error) {
725 printf("ruletab warning: could not stat %s: %s\n",
726 tokens[0], strerror(errno));
729 if (tokens[0][0] != '/') {
730 errx(1, "ruletab error: entry %s does not seem to be an absolute path",
731 tokens[0]);
734 for (i = 1; tokens[i] != NULL; i++) {
735 rt = calloc(1, sizeof(struct rule_tab));
736 rt->mntpoint = strdup(tokens[0]);
737 rt->rule_file = strdup(tokens[i]);
738 TAILQ_INSERT_TAIL(&rule_tab_list, rt, link);
741 return 0;
744 void
745 rule_tab(void)
747 struct rule_tab *rt;
748 int error;
749 int mode;
751 chdir("/etc/devfs");
752 error = read_config("ruletab", RULETAB_FILE);
754 if (error)
755 errx(1, "could not read/process ruletab file (/etc/devfs/ruletab)");
757 if (!strcmp(mountp, "*")) {
758 mode = RULETAB_ALL;
759 } else if (!strcmp(mountp, "boot")) {
760 mode = RULETAB_ONLY_BOOT;
761 } else if (mountp) {
762 mode = RULETAB_SPECIFIC;
763 } else {
764 errx(1, "-t needs -m");
767 dev_fd = open("/dev/devfs", O_RDWR);
768 if (dev_fd == -1)
769 err(1, "open(/dev/devfs)");
771 TAILQ_FOREACH(rt, &rule_tab_list, link) {
772 switch(mode) {
773 case RULETAB_ONLY_BOOT:
774 if ((strcmp(rt->mntpoint, "*") != 0) &&
775 (strcmp(rt->mntpoint, "/dev") != 0)) {
776 continue;
778 break;
779 case RULETAB_SPECIFIC:
780 if (strcmp(rt->mntpoint, mountp) != 0)
781 continue;
782 break;
784 delete_rules();
785 read_config(rt->rule_file, RULES_FILE);
786 mountp = rt->mntpoint;
787 rule_apply();
790 close(dev_fd);
792 return;
795 void
796 delete_rules(void)
798 struct rule *rp;
799 struct groupdevid *gdp;
801 TAILQ_FOREACH(rp, &rule_list, link) {
802 TAILQ_REMOVE(&rule_list, rp, link);
805 TAILQ_FOREACH(gdp, &group_list, link) {
806 TAILQ_REMOVE(&group_list, gdp, link);
809 #endif
811 static void
812 usage(void)
814 fprintf(stderr,
815 "Usage: devfsctl <commands> [options]\n"
816 "Valid commands are:\n"
817 " -a\n"
818 "\t Loads all read rules into the kernel and applies them\n"
819 " -c\n"
820 "\t Clears all rules stored in the kernel but does not reset the nodes\n"
821 " -d\n"
822 "\t Dumps the rules that have been loaded to the screen to verify syntax\n"
823 " -r\n"
824 "\t Resets all devfs_nodes but does not clear the rules stored\n"
825 "\n"
826 "Valid options and its arguments are:\n"
827 " -f <config_file>\n"
828 "\t Specifies the configuration file to be used\n"
829 " -m <mount_point>\n"
830 "\t Specifies a mount point to which the command will apply. Defaults to *\n"
833 exit(1);
836 int main(int argc, char *argv[])
838 struct devfs_rule_ioctl dummy_rule;
839 struct stat sb;
840 int ch, error;
842 while ((ch = getopt(argc, argv, "acdf:hm:r")) != -1) {
843 switch (ch) {
844 case 'f':
845 config_name = optarg;
846 break;
847 case 'm':
848 mountp = optarg;
849 break;
850 case 'a':
851 aflag = 1;
852 break;
853 case 'c':
854 cflag = 1;
855 break;
856 case 'r':
857 rflag = 1;
858 break;
859 case 'd':
860 dflag = 1;
861 break;
863 case 'h':
864 case '?':
865 default:
866 usage();
867 /* NOT REACHED */
871 argc -= optind;
872 argv += optind;
875 * Check arguments:
876 * - need to use at least one mode
877 * - can not use -d with any other mode
878 * - can not use -t with any other mode or -f
880 if (!(aflag || rflag || cflag || dflag) ||
881 (dflag && (aflag || rflag || cflag || tflag))) {
882 usage();
883 /* NOT REACHED */
886 if (mountp == NULL)
887 mountp = "*";
888 else if (mountp[0] != '/') {
889 errx(1, "-m needs to be given an absolute path");
892 strncpy(dummy_rule.mntpoint, mountp, PATH_MAX-1);
894 if (config_name != NULL) {
895 error = stat(config_name, &sb);
897 if (error) {
898 chdir("/etc/devfs");
899 error = stat(config_name, &sb);
902 if (error)
903 err(1, "could not stat specified configuration file %s", config_name);
905 if (config_name[0] == '/')
906 chdir(dirname(strdup(config_name)));
908 read_config(config_name, RULES_FILE);
911 if (dflag) {
912 dump_config();
913 exit(0);
916 dev_fd = open("/dev/devfs", O_RDWR);
917 if (dev_fd == -1)
918 err(1, "open(/dev/devfs)");
920 if (cflag)
921 rule_ioctl(DEVFS_RULE_CLEAR, &dummy_rule);
923 if (rflag)
924 rule_ioctl(DEVFS_RULE_RESET, &dummy_rule);
926 if (aflag)
927 rule_apply();
929 close(dev_fd);
931 return 0;