diff --git a/sbin/Makefile b/sbin/Makefile index b9c8473680a1..1dd8fdcbbf07 100644 --- a/sbin/Makefile +++ b/sbin/Makefile @@ -12,6 +12,7 @@ SUBDIR= adjkerntz \ clri \ comcontrol \ conscontrol \ + devfs \ dhclient \ disklabel \ dmesg \ diff --git a/sbin/devfs/Makefile b/sbin/devfs/Makefile new file mode 100644 index 000000000000..59c0178ea2b6 --- /dev/null +++ b/sbin/devfs/Makefile @@ -0,0 +1,8 @@ +# $FreeBSD$ + +PROG= devfs +SRCS= devfs.c rule.c +MAN= devfs.8 +WARNS?= 4 + +.include diff --git a/sbin/devfs/devfs.8 b/sbin/devfs/devfs.8 new file mode 100644 index 000000000000..48e63c6c0cdf --- /dev/null +++ b/sbin/devfs/devfs.8 @@ -0,0 +1,326 @@ +.\" +.\" Copyright (c) 2002 Dima Dorfman. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dt DEVFS 8 +.Dd July 1, 2002 +.Os +.Sh NAME +.Nm devfs +.Nd "DEVFS control" +.Sh SYNOPSIS +.Nm +.Op Fl m Ar mount-point +.Cm keyword +.Ar argument ... +.Sh DESCRIPTION +The +.Nm +utility provides an interface to manipulate properties of +.Xr devfs 5 +mounts. +.Pp +The first keyword after the program name determines the context for +the rest of the arguments. +For example, +most of the commands related to the rule subsystem must be preceded by the +.Cm rule +keyword. +The following flags are common to all keywords: +.Bl -tag -offset indent +.It Fl m Ar mount-point +Operate on +.Ar mount-point , +which is expected to be a +.Xr devfs 5 +mount. +If this option is not specified, +.Nm +operates on +.Pa /dev . +.El +.Sh RULE SUBSYSTEM +The +.Xr devfs 5 +rule subsystem provides a way for the administrator of a system to control +the attributes of DEVFS nodes. +.\" XXX devfs node? entry? what? +Each DEVFS mount-point has a +.Dq ruleset , +or a list of rules, +associated with it. +When a device driver creates a new node, +all the rules in the ruleset associated with each mount-point are applied +(see below) before the node becomes visible to the userland. +This permits the administrator to change the properties, +including the visibility, +of certain nodes. +For example, one might want to hide all disk nodes in a +.Xr jail 2 Ns 's +.Pa /dev . +.Ss Rule Manipulation +Rule manipulation commands follow the +.Cm rule +keyword. +The following flags are common to all of the rule manipulation commands: +.Bl -tag -offset indent +.It Fl s Ar ruleset +Operate on the ruleset with the number +.Ar ruleset . +If this is not specified, +the commands operate on the ruleset currently associated with the +specified mount-point. +.El +.Pp +The following commands are recognized: +.Bl -tag -offset indent +.It Cm rule add Oo Ar rulenum Oc Ar rulespec +Add the rule described by +.Ar rulespec +(defined below) +to the ruleset. +The rule has the number +.Ar rulenum +if it is explicitly specified; +otherwise, the rule number is automatically determined by the kernel. +.It Cm rule apply Ar rulenum | Ar rulespec +Apply rule number +.Ar rulenum +or the rule described by +.Ar rulespec +to the mount-point. +Rules that are "applied" have their conditions checked against all nodes +in the mount-point, and the actions taken if they match. +.It Cm rule applyset +Apply all the rules in the ruleset to the mount-point +(see above for the definition of "apply"). +.It Cm rule del Ar rulenum +Delete rule number +.Ar rulenum +from the ruleset. +.It Cm rule delset +Delete all rules from the ruleset. +.It Cm rule show Op Ar rulenum +Display the rule number +.Ar rulenum , +or all the rules in the ruleset. +The output lines (one line per rule) are expected to be valid +.Ar rulespec Ns s . +.It Cm rule showsets +Report the numbers of existing rulesets. +.It Cm ruleset Ar ruleset +Set ruleset number +.Ar ruleset +as the current ruleset for the mount-point. +.El +.Ss Rule Specification +Rules have two parts: the conditions and the actions. +The conditions determine which DEVFS nodes the rule matches, +and the actions determine what should be done when a rule matches a node. +For example, a rule can be written that sets the GID to +.Li games +for all devices with major number 53. +.Pp +The following conditions are recognized. +Conditions are ANDed together when matching a device; +if OR is desired, multiple rules can be written. +.Bl -tag -offset indent +.It Cm major Ar majdev +Matches any node with a major number equal to +.Ar majdev . +.It Cm path Ar pattern +Matches any node with a path that matches +.Ar pattern . +The latter is interpreted as a +.Xr glob 3 Ns -style +pattern. +(Note: Pattern matching is currently unimplemented; +the only wildcard recognized is an asterisk at the end of the string. +This will be corrected in the future.) +.It Cm type Ar devtype +Matches any node that is of type +.Ar devtype . +Valid types are +.Li disk , mem , tape +and +.Li tty . +.El +.Pp +The following actions are recognized. +Although there is no explicit delimiter between conditions and actions, +they may not be intermixed. +.Bl -tag -offset indent +.It Cm group Ar gid +Set the GID of the node to +.Ar gid , +which may be a group name +(looked up in +.Pa /etc/group ) +or number. +.It Cm hide +Hide the node. +Nodes may later be revived manually with +.Xr mknod 8 , +or with the +.Cm unhide +action. +.It Cm include Ar ruleset +Apply all the rules in ruleset number +.Ar ruleset +to the node. +This does not necessarily result in any changes to the node +(e.g., if none of the rules in the included ruleset match). +.It Cm mode Ar filemode +Set the file mode to +.Ar filemode , +which is interpreted in octal. +.It Cm user Ar uid +Set the UID to +.Ar uid , +which may be a user name +(looked up in +.Pa /etc/passwd ) +or number. +.It Cm unhide +Unhide the node. +.El +.Ss Notes +.Bl -bullet -offset indent +.It +Rulesets are created by the kernel at the first reference, +and destroyed when the last reference disappears. +E.g., a ruleset is created when a rule is added to it or when it is set +as the current ruleset for a mount-point; +a ruleset is destroyed when the last rule in it is deleted, +and no other references to it exist +(i.e., it is not included by any rules, and it is not the current ruleset +for any mount-point). +.It +Ruleset number 0 is the default ruleset for all new mount-points. +It is always empty, cannot be modified or deleted, and does not show up +in the output of +.Cm showsets . +.It +Rules and rulesets are unique to the entire system, +not a particular mount-point. +I.e., a +.Cm showsets +will return the same information regardless of the mount-point specified with +.Fl m . +The mount-point is only relevant when changing what its current ruleset is, +or when using one of the apply commands. +.El +.Ss Examples +When the system boots, +the only ruleset that exists is ruleset number 0; +since the latter may not be modified, we have to create another ruleset +before adding rules. +Note that since most of the following examples don't specify +.Fl m , +the operations are performed on +.Pa /dev +(this only matters for things that might change the properties of nodes). +.Pp +.Dl devfs ruleset 10 +.Pp +Specify that ruleset 10 should be the current ruleset for +.Pa /dev +(if it does not already exist, it is created). +.Pp +.Dl devfs rule add path speaker mode 666 +.Pp +Add a rule that causes all nodes that have a path that matches +"speaker" +(this is only +.Pa /dev/speaker ) +to have the file mode 666 (read and write for all). +Note that if any such nodes already exist, their mode will not be changed +unless this rule (or ruleset) is explicitly applied (see below). +The mode +.Em will +be changed if the node is created +.Em after +the rule is added +(e.g., the +.Pa atspeaker +module is loaded after the above rule is added). +.Pp +.Dl devfs rule applyset +.Pp +Apply all the rules in the current ruleset to all the existing nodes. +E.g., if the above rule was added after +.Pa /dev/speaker +was created, +this command will cause its file mode to be changed to 666, +as rule rule prescribes. +.Pp +.Dl devfs rule add path "snp*" mode 660 group snoopers +.Pp +(Quoting the argument to +.Cm path +is often necessary to disable the shell's globbing features.) +For all devices with a path that matches "snp*", +set the file more to 660, and the GID to +.Li snoopers . +This permits users in the +.Li snoopers +group to use the +.Xr snp 4 +devices. +.Pp +.Dl devfs rule -s 20 add major 53 group games +.Pp +Add a rule to ruleset number 20. +Since this ruleset is not the current ruleset for any mount-points, +this rule is never applied automatically (unless ruleset 20 becomes +a current ruleset for some mount-point at a later time). +However, it can be applied explicitly, as such: +.Pp +.Dl devfs -m /my/jail/dev rule -s 20 applyset +.Pp +This will apply all rules in ruleset number 20 to the DEVFS mount on +.Pa /my/jail/dev . +It doesn't matter that ruleset 20 is not the current ruleset for that +mount-point; the rules are applied regardless. +.Pp +.Dl devfs rule apply hide +.Pp +Since this rule has no conditions, the action +.Pq Cm hide +will be applied to all nodes. +Since hiding all nodes isn't very useful, we can undo like so: +.Pp +.Dl devfs rule apply unhide +.Sh SEE ALSO +.Xr jail 2 , +.Xr glob 3 , +.Xr devfs 5 , +.Xr chmod 8 , +.Xr chown 8 , +.Xr jail 8 , +.Xr mknod 8 +.Sh AUTHORS +.An Dima Dorfman diff --git a/sbin/devfs/devfs.c b/sbin/devfs/devfs.c new file mode 100644 index 000000000000..4622bb9de40c --- /dev/null +++ b/sbin/devfs/devfs.c @@ -0,0 +1,140 @@ +/*- + * Copyright (c) 2002 Dima Dorfman. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * DEVFS control. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "extern.h" + +int mpfd; + +static ctbl_t ctbl_main = { + { "rule", rule_main }, + { "ruleset", ruleset_main }, + { NULL, NULL } +}; + +int +main(int ac, char **av) +{ + const char *mountpt; + struct cmd *c; + char ch; + + mountpt = NULL; + while ((ch = getopt(ac, av, "m:")) != -1) + switch (ch) { + case 'm': + mountpt = optarg; + break; + default: + usage(); + } + ac -= optind; + av += optind; + if (ac < 1) + usage(); + + if (mountpt == NULL) + mountpt = _PATH_DEV; + mpfd = open(mountpt, O_RDONLY); + if (mpfd == -1) + err(1, "open: %s", mountpt); + + for (c = ctbl_main; c->name != NULL; ++c) + if (strcmp(c->name, av[0]) == 0) + exit((*c->handler)(ac, av)); + errx(1, "unknown command: %s", av[0]); +} + +/* + * Convert an integer to a "number" (ruleset numbers and rule numbers + * are 16-bit). If the conversion is successful, num contains the + * integer representation of s and 1 is returned; otherwise, 0 is + * returned and num is unchanged. + */ +int +atonum(const char *s, uint16_t *num) +{ + unsigned long ul; + char *cp; + + ul = strtoul(s, &cp, 10); + if (ul > UINT16_MAX || *cp != '\0') + return (0); + *num = (uint16_t)ul; + return (1); +} + +/* + * Convert user input in ASCII to an integer. + */ +int +eatoi(const char *s) +{ + char *cp; + long l; + + l = strtol(s, &cp, 10); + if (l > INT_MAX || *cp != '\0') + errx(1, "error converting to integer: %s", s); + return ((int)l); +} + +/* + * As atonum(), but the result of failure is death. + */ +uint16_t +eatonum(const char *s) +{ + uint16_t num; + + if (!atonum(s, &num)) + errx(1, "error converting to number: %s", s); /* XXX clarify */ + return (num); +} + +void +usage(void) +{ + + fprintf(stderr, "usage: devfs rule|ruleset arguments\n"); + exit(1); +} diff --git a/sbin/devfs/extern.h b/sbin/devfs/extern.h new file mode 100644 index 000000000000..9814b1561ce8 --- /dev/null +++ b/sbin/devfs/extern.h @@ -0,0 +1,55 @@ +/*- + * Copyright (c) 2002 Dima Dorfman. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef __DEVFS_H__ +#define __DEVFS_H__ + +#include + +struct intstr { + const char *s; + int i; +}; + +typedef int (command_t)(int, char **); +typedef struct cmd ctbl_t[]; +struct cmd { + const char *name; + command_t *handler; +}; + +command_t rule_main, ruleset_main; + +int atonum(const char *, uint16_t *); +int eatoi(const char *); +uint16_t eatonum(const char *); +void usage(void) __dead2; + +extern int mpfd; /* Mount-point file descriptor. */ + +#endif /* !__DEVFS_H__ */ diff --git a/sbin/devfs/rule.c b/sbin/devfs/rule.c new file mode 100644 index 000000000000..27ad2a600364 --- /dev/null +++ b/sbin/devfs/rule.c @@ -0,0 +1,419 @@ +/*- + * Copyright (c) 2002 Dima Dorfman. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Rule subsystem manipulation. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "extern.h" + +static void rulespec_intok(struct devfs_rule *dr, int ac, char **av, + devfs_rsnum rsnum); +static void rulespec_outfp(FILE *fp, struct devfs_rule *dr); + +static command_t rule_add, rule_apply, rule_applyset; +static command_t rule_del, rule_delset, rule_show, rule_showsets; + +static ctbl_t ctbl_rule = { + { "add", rule_add }, + { "apply", rule_apply }, + { "applyset", rule_applyset }, + { "del", rule_del }, + { "delset", rule_delset }, + { "show", rule_show }, + { "showsets", rule_showsets }, + { NULL, NULL } +}; + +static struct intstr ist_type[] = { + { "disk", D_DISK }, + { "mem", D_MEM }, + { "tape", D_TAPE }, + { "tty", D_TTY }, + { NULL, -1 } +}; + +devfs_rsnum in_rsnum; + +int +rule_main(int ac, char **av) +{ + struct cmd *c; + char ch; + + setprogname("devfs rule"); + optreset = optind = 1; + while ((ch = getopt(ac, av, "s:")) != -1) + switch (ch) { + case 's': + in_rsnum = eatonum(optarg); + break; + default: + usage(); + } + ac -= optind; + av += optind; + if (ac < 1) + usage(); + + for (c = ctbl_rule; c->name != NULL; ++c) + if (strcmp(c->name, av[0]) == 0) + exit((*c->handler)(ac, av)); + errx(1, "unknown command: %s", av[0]); +} + +static int +rule_add(int ac, char **av) +{ + struct devfs_rule dr; + int rv; + + if (ac < 2) + usage(); + rulespec_intok(&dr, ac - 1, av + 1, in_rsnum); + rv = ioctl(mpfd, DEVFSIO_RADD, &dr); + if (rv == -1) + err(1, "ioctl DEVFSIO_RADD"); + return (0); +} + +static int +rule_apply(int ac __unused, char **av __unused) +{ + struct devfs_rule dr; + devfs_rnum rnum; + devfs_rid rid; + int rv; + + if (ac < 2) + usage(); + if (!atonum(av[1], &rnum)) { + rulespec_intok(&dr, ac - 1, av + 1, in_rsnum); + rv = ioctl(mpfd, DEVFSIO_RAPPLY, &dr); + if (rv == -1) + err(1, "ioctl DEVFSIO_RAPPLY"); + } else { + rid = mkrid(in_rsnum, rnum); + rv = ioctl(mpfd, DEVFSIO_RAPPLYID, &rid); + if (rv == -1) + err(1, "ioctl DEVFSIO_RAPPLYID"); + } + return (0); +} + +static int +rule_applyset(int ac, char **av __unused) +{ + int rv; + + if (ac != 1) + usage(); + rv = ioctl(mpfd, DEVFSIO_SAPPLY, &in_rsnum); + if (rv == -1) + err(1, "ioctl DEVFSIO_SAPPLY"); + return (0); +} + +static int +rule_del(int ac __unused, char **av) +{ + devfs_rid rid; + int rv; + + if (av[1] == NULL) + usage(); + rid = mkrid(in_rsnum, eatoi(av[1])); + rv = ioctl(mpfd, DEVFSIO_RDEL, &rid); + if (rv == -1) + err(1, "ioctl DEVFSIO_RDEL"); + return (0); +} + +static int +rule_delset(int ac, char **av __unused) +{ + struct devfs_rule dr; + int rv; + + if (ac != 1) + usage(); + memset(&dr, '\0', sizeof(dr)); + dr.dr_magic = DEVFS_MAGIC; + dr.dr_id = mkrid(in_rsnum, 0); + while (ioctl(mpfd, DEVFSIO_RGETNEXT, &dr) != -1) { + rv = ioctl(mpfd, DEVFSIO_RDEL, &dr.dr_id); + if (rv == -1) + err(1, "ioctl DEVFSIO_RDEL"); + } + if (errno != ENOENT) + err(1, "ioctl DEVFSIO_RGETNEXT"); + return (0); +} + +static int +rule_show(int ac __unused, char **av) +{ + struct devfs_rule dr; + devfs_rnum rnum; + int rv; + + memset(&dr, '\0', sizeof(dr)); + dr.dr_magic = DEVFS_MAGIC; + if (av[1] != NULL) { + rnum = eatoi(av[1]); + dr.dr_id = mkrid(in_rsnum, rnum - 1); + rv = ioctl(mpfd, DEVFSIO_RGETNEXT, &dr); + if (rv == -1) + err(1, "ioctl DEVFSIO_RGETNEXT"); + if (rid2rn(dr.dr_id) == rnum) + rulespec_outfp(stdout, &dr); + } else { + dr.dr_id = mkrid(in_rsnum, 0); + while (ioctl(mpfd, DEVFSIO_RGETNEXT, &dr) != -1) + rulespec_outfp(stdout, &dr); + if (errno != ENOENT) + err(1, "ioctl DEVFSIO_RGETNEXT"); + } + return (0); +} + +static int +rule_showsets(int ac, char **av __unused) +{ + devfs_rsnum rsnum; + + if (ac != 1) + usage(); + rsnum = 0; + while (ioctl(mpfd, DEVFSIO_SGETNEXT, &rsnum) != -1) + printf("%d\n", rsnum); + if (errno != ENOENT) + err(1, "ioctl DEVFSIO_SGETNEXT"); + return (0); +} + +int +ruleset_main(int ac, char **av) +{ + devfs_rsnum rsnum; + int rv; + + setprogname("devfs ruleset"); + if (ac < 2) + usage(); + rsnum = eatonum(av[1]); + rv = ioctl(mpfd, DEVFSIO_SUSE, &rsnum); + if (rv == -1) + err(1, "ioctl DEVFSIO_SUSE"); + return (0); +} + + +/* + * Construct a /struct devfs_rule/ from ac and av. + */ +static void +rulespec_intok(struct devfs_rule *dr, int ac __unused, char **av, + devfs_rsnum rsnum) +{ + struct intstr *is; + struct passwd *pw; + struct group *gr; + devfs_rnum rnum; + char *cp; + long l; + + memset(dr, '\0', sizeof(*dr)); + + /* + * We don't maintain ac hereinafter. + */ + if (av[0] == NULL) + errx(1, "unexpected end of rulespec"); + + /* If the first argument is an integer, treat it as a rule number. */ + if (!atonum(av[0], &rnum)) + rnum = 0; /* auto-number */ + else + ++av; + + /* + * These aren't table-driven since that would result in more + * tiny functions than I care to deal with. + */ + for (;;) { + if (av[0] == NULL) + break; + else if (strcmp(av[0], "type") == 0) { + if (av[1] == NULL) + errx(1, "expecting argument for type"); + for (is = ist_type; is->s != NULL; ++is) + if (strcmp(av[1], is->s) == 0) { + dr->dr_dswflags |= is->i; + break; + } + if (is->s == NULL) + errx(1, "unknown type: %s", av[1]); + dr->dr_icond |= DRC_DSWFLAGS; + av += 2; + } else if (strcmp(av[0], "path") == 0) { + if (av[1] == NULL) + errx(1, "expecting argument for path"); + if (strlcpy(dr->dr_pathptrn, av[1], DEVFS_MAXPTRNLEN) + >= DEVFS_MAXPTRNLEN) + warnx("pattern specified too long; truncated"); + dr->dr_icond |= DRC_PATHPTRN; + av += 2; + } else if (strcmp(av[0], "major") == 0) { + if (av[1] == NULL) + errx(1, "expecting argument for major"); + dr->dr_major = eatoi(av[1]); + dr->dr_icond |= DRC_MAJOR; + av += 2; + } else + break; + } + for (;;) { + if (av[0] == NULL) + break; + else if (strcmp(av[0], "hide") == 0) { + dr->dr_iacts |= DRA_BACTS; + dr->dr_bacts |= DRB_HIDE; + ++av; + } else if (strcmp(av[0], "unhide") == 0) { + dr->dr_iacts |= DRA_BACTS; + dr->dr_bacts |= DRB_UNHIDE; + ++av; + } else if (strcmp(av[0], "user") == 0) { + if (av[1] == NULL) + errx(1, "expecting argument for user"); + dr->dr_iacts |= DRA_UID; + pw = getpwnam(av[1]); + if (pw != NULL) + dr->dr_uid = pw->pw_uid; + else + dr->dr_uid = eatoi(av[1]); /* XXX overflow */ + av += 2; + } else if (strcmp(av[0], "group") == 0) { + if (av[1] == NULL) + errx(1, "expecting argument for group"); + dr->dr_iacts |= DRA_GID; + gr = getgrnam(av[1]); + if (gr != NULL) + dr->dr_gid = gr->gr_gid; + else + dr->dr_gid = eatoi(av[1]); /* XXX overflow */ + av += 2; + } else if (strcmp(av[0], "mode") == 0) { + if (av[1] == NULL) + errx(1, "expecting argument for mode"); + dr->dr_iacts |= DRA_MODE; + l = strtol(av[1], &cp, 8); + if (l > (1 << (sizeof(dr->dr_mode) * 8)) - 1 || + *cp != '\0') + errx(1, "invalid mode: %s", av[1]); + dr->dr_mode = l; + av += 2; + } else if (strcmp(av[0], "include") == 0) { + if (av[1] == NULL) + errx(1, "expecting argument for include"); + dr->dr_iacts |= DRA_INCSET; + dr->dr_incset = eatonum(av[1]); + av += 2; + } else + errx(1, "unknown argument: %s", av[0]); + } + + dr->dr_id = mkrid(rsnum, rnum); + dr->dr_magic = DEVFS_MAGIC; +} + +/* + * Write a human-readable (and machine-parsable, by rulespec_in*()) + * representation of dr to bufp. *bufp should be free(3)'d when the + * caller is finished with it. + */ +static void +rulespec_outfp(FILE *fp, struct devfs_rule *dr) +{ + struct intstr *is; + struct passwd *pw; + struct group *gr; + + fprintf(fp, "%d", rid2rn(dr->dr_id)); + + if (dr->dr_icond & DRC_DSWFLAGS) + for (is = ist_type; is->s != NULL; ++is) + if (dr->dr_dswflags & is->i) + fprintf(fp, " type %s", is->s); + if (dr->dr_icond & DRC_PATHPTRN) + fprintf(fp, " path %s", dr->dr_pathptrn); + if (dr->dr_icond & DRC_MAJOR) + fprintf(fp, " major %d", dr->dr_major); + + if (dr->dr_iacts & DRA_BACTS) { + if (dr->dr_bacts & DRB_HIDE) + fprintf(fp, " hide"); + if (dr->dr_bacts & DRB_UNHIDE) + fprintf(fp, " unhide"); + } + if (dr->dr_iacts & DRA_UID) { + pw = getpwuid(dr->dr_uid); + if (pw == NULL) + fprintf(fp, " user %d", dr->dr_uid); + else + fprintf(fp, " user %s", pw->pw_name); + } + if (dr->dr_iacts & DRA_GID) { + gr = getgrgid(dr->dr_gid); + if (gr == NULL) + fprintf(fp, " group %d", dr->dr_gid); + else + fprintf(fp, " group %s", gr->gr_name); + } + if (dr->dr_iacts & DRA_MODE) + fprintf(fp, " mode %o", dr->dr_mode); + if (dr->dr_iacts & DRA_INCSET) + fprintf(fp, " include %d", dr->dr_incset); + + fprintf(fp, "\n"); +} diff --git a/sys/conf/files b/sys/conf/files index 97ae16efe46f..7a596ce96ec3 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -677,6 +677,7 @@ dev/xe/if_xe_pccard.c optional xe card dev/xe/if_xe_pccard.c optional xe pccard fs/deadfs/dead_vnops.c standard fs/devfs/devfs_devs.c standard +fs/devfs/devfs_rule.c standard fs/devfs/devfs_vfsops.c standard fs/devfs/devfs_vnops.c standard fs/fdescfs/fdesc_vfsops.c optional fdescfs diff --git a/sys/fs/devfs/devfs.h b/sys/fs/devfs/devfs.h index 137797f4c8db..2f0dc762018b 100644 --- a/sys/fs/devfs/devfs.h +++ b/sys/fs/devfs/devfs.h @@ -3,6 +3,8 @@ * The Regents of the University of California. All rights reserved. * Copyright (c) 2000 * Poul-Henning Kamp. All rights reserved. + * Copyright (c) 2002 + * Dima Dorfman. All rights reserved. * * This code is derived from software donated to Berkeley by * Jan-Simon Pendry. @@ -37,7 +39,86 @@ #ifndef _FS_DEVFS_DEVFS_H_ #define _FS_DEVFS_DEVFS_H_ -#ifdef _KERNEL /* No userland stuff in here... */ +#define DEVFS_MAGIC 0xdb0a087a + +/* + * Identifiers. The ruleset and rule numbers are 16-bit values. The + * "rule ID" is a combination of the ruleset and rule number; it + * should be able to univocally describe a rule in the system. In + * this implementation, the upper 16 bits of the rule ID is the + * ruleset number; the lower 16 bits, the rule number within the + * aforementioned ruleset. + */ +typedef uint16_t devfs_rnum; +typedef uint16_t devfs_rsnum; +typedef uint32_t devfs_rid; + +/* + * Identifier manipulators. + */ +#define rid2rsn(rid) ((rid) >> 16) +#define rid2rn(rid) ((rid) & 0xffff) +#define mkrid(rsn, rn) ((rn) | ((rsn) << 16)) + +/* + * Plain DEVFS rule. This gets shared between kernel and userland + * verbatim, so it shouldn't contain any pointers or other kernel- or + * userland-specific values. + */ +struct devfs_rule { + uint32_t dr_magic; /* Magic number. */ + devfs_rid dr_id; /* Identifier. */ + + /* + * Conditions under which this rule should be applied. These + * are ANDed together since OR can be simulated by using + * multiple rules. dr_icond determines which of the other + * variables we should process. + */ + int dr_icond; +#define DRC_DSWFLAGS 0x001 +#define DRC_PATHPTRN 0x002 +#define DRC_MAJOR 0x004 + int dr_dswflags; /* cdevsw flags to match. */ +#define DEVFS_MAXPTRNLEN 200 + char dr_pathptrn[DEVFS_MAXPTRNLEN]; /* Pattern to match path. */ + int dr_major; /* Device major number. */ + + /* + * Things to change. dr_iacts determines which of the other + * variables we should process. + */ + int dr_iacts; +#define DRA_BACTS 0x001 +#define DRA_UID 0x002 +#define DRA_GID 0x004 +#define DRA_MODE 0x008 +#define DRA_INCSET 0x010 + int dr_bacts; /* Boolean (on/off) action. */ +#define DRB_HIDE 0x001 /* Hide entry (DE_WHITEOUT). */ +#define DRB_UNHIDE 0x002 /* Unhide entry. */ + uid_t dr_uid; + gid_t dr_gid; + mode_t dr_mode; + devfs_rsnum dr_incset; /* Included ruleset. */ +}; + +/* + * Rule-related ioctls. + */ +#define DEVFSIO_RADD _IOWR('D', 0, struct devfs_rule) +#define DEVFSIO_RDEL _IOW('D', 1, devfs_rid) +#define DEVFSIO_RAPPLY _IOW('D', 2, struct devfs_rule) +#define DEVFSIO_RAPPLYID _IOW('D', 3, devfs_rid) +#define DEVFSIO_RGETNEXT _IOWR('D', 4, struct devfs_rule) + +#define DEVFSIO_SUSE _IOW('D', 10, devfs_rsnum) +#define DEVFSIO_SAPPLY _IOW('D', 11, devfs_rsnum) +#define DEVFSIO_SGETNEXT _IOWR('D', 12, devfs_rsnum) + +/* XXX: DEVFSIO_RS_GET_INFO for refcount, active if any, etc. */ + +#ifdef _KERNEL /* * These are default sizes for the DEVFS inode table and the overflow @@ -94,6 +175,7 @@ struct devfs_mount { struct devfs_dirent **dm_overflow; int dm_inode; struct lock dm_lock; + devfs_rsnum dm_ruleset; }; /* @@ -106,6 +188,10 @@ struct devfs_mount { extern vop_t **devfs_vnodeop_p; extern vop_t **devfs_specop_p; +void devfs_rules_apply(struct devfs_mount *dm, struct devfs_dirent *de); +void devfs_rules_init(void); +int devfs_rules_ioctl(struct mount *mp, int cmd, caddr_t data, struct thread *td); +void devfs_rules_newmount(struct devfs_mount *dm, struct thread *td); int devfs_allocv (struct devfs_dirent *de, struct mount *mp, struct vnode **vpp, struct thread *td); dev_t *devfs_itod (int inode); struct devfs_dirent **devfs_itode (struct devfs_mount *dm, int inode); diff --git a/sys/fs/devfs/devfs_devs.c b/sys/fs/devfs/devfs_devs.c index ef9827cf5e45..fd378b8c0ca2 100644 --- a/sys/fs/devfs/devfs_devs.c +++ b/sys/fs/devfs/devfs_devs.c @@ -352,6 +352,7 @@ devfs_populate(struct devfs_mount *dm) } *dep = de; de->de_dir = dd; + devfs_rules_apply(dm, de); TAILQ_INSERT_TAIL(&dd->de_dlist, de, de_list); #if 0 printf("Add ino%d %s\n", i, dev->si_name); @@ -435,6 +436,7 @@ devfs_init(void *junk) devfs_create_hook = devfs_create; devfs_destroy_hook = devfs_destroy; devfs_present = 1; + devfs_rules_init(); } SYSINIT(devfs, SI_SUB_DEVFS, SI_ORDER_FIRST, devfs_init, NULL); diff --git a/sys/fs/devfs/devfs_rule.c b/sys/fs/devfs/devfs_rule.c new file mode 100644 index 000000000000..3f19da2c3054 --- /dev/null +++ b/sys/fs/devfs/devfs_rule.c @@ -0,0 +1,839 @@ +/*- + * Copyright (c) 2002 Dima Dorfman. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * DEVFS ruleset implementation. + * + * A note on terminology: To "run" a rule on a dirent is to take the + * prescribed action; to "apply" a rule is to check whether it matches + * a dirent and run if if it does. + * + * A note on locking: Only foreign entry points (non-static functions) + * should deal with locking. Everything else assumes we already hold + * the required kind of lock. + * + * A note on namespace: devfs_rules_* are the non-static functions for + * the entire "ruleset" subsystem, devfs_rule_* are the static + * functions that operate on rules, and devfs_ruleset_* are the static + * functions that operate on rulesets. The line between the last two + * isn't always clear, but the guideline is still useful. + * + * A note on "special" identifiers: Ruleset 0 is the NULL, or empty, + * ruleset; it cannot be deleted or changed in any way. This may be + * assumed inside the code; e.g., a ruleset of 0 may be interpeted to + * mean "no ruleset". The interpretation of rule 0 is + * command-dependent, but in no case is there a real rule with number + * 0. + * + * A note on errno codes: To make it easier for the userland to tell + * what went wrong, we sometimes use errno codes that are not entirely + * appropriate for the error but that would be less ambiguous than the + * appropriate "generic" code. For example, when we can't find a + * ruleset, we return ESRCH instead of ENOENT (except in + * DEVFSIO_{R,S}GETNEXT, where a nonexistent ruleset means "end of + * list", and the userland expects ENOENT to be this indicator); this + * way, when an operation fails, it's clear that what couldn't be + * found is a ruleset and not a rule (well, it's clear to those who + * know the convention). + */ + +#include "opt_devfs.h" +#ifndef NODEVFS + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +/* + * Kernel version of devfs_rule. + */ +struct devfs_krule { + SLIST_ENTRY(devfs_krule) dk_list; + struct devfs_ruleset *dk_ruleset; + struct devfs_rule dk_rule; +}; + +/* + * Structure to describe a ruleset. + */ +struct devfs_ruleset { + SLIST_ENTRY(devfs_ruleset) ds_list; + devfs_rsnum ds_number; + SLIST_HEAD(, devfs_krule) ds_rules; + int ds_refcount; + int ds_flags; +#define DS_IMMUTABLE 0x001 +}; + +static devfs_rid devfs_rid_input(devfs_rid rid, struct devfs_mount *dm); + +static void devfs_rule_applyde(struct devfs_krule *dk,struct devfs_dirent *de); +static void devfs_rule_applyde_recursive(struct devfs_krule *dk, + struct devfs_dirent *de); +static void devfs_rule_applydm(struct devfs_krule *dk, struct devfs_mount *dm); +static int devfs_rule_autonumber(struct devfs_ruleset *ds, devfs_rnum *rnp); +static struct devfs_krule *devfs_rule_byid(devfs_rid rid); +static int devfs_rule_delete(struct devfs_krule **dkp); +static dev_t devfs_rule_getdev(struct devfs_dirent *de); +static int devfs_rule_input(struct devfs_rule *dr, struct devfs_mount *dm); +static int devfs_rule_insert(struct devfs_rule *dr); +static int devfs_rule_match(struct devfs_krule *dk, struct devfs_dirent *de); +static int devfs_rule_matchpath(struct devfs_krule *dk, + struct devfs_dirent *de); +static void devfs_rule_run(struct devfs_krule *dk, struct devfs_dirent *de); + +static void devfs_ruleset_applyde(struct devfs_ruleset *ds, + struct devfs_dirent *de); +static void devfs_ruleset_applydm(struct devfs_ruleset *ds, + struct devfs_mount *dm); +static struct devfs_ruleset *devfs_ruleset_bynum(devfs_rsnum rsnum); +static struct devfs_ruleset *devfs_ruleset_create(devfs_rsnum rsnum); +static void devfs_ruleset_destroy(struct devfs_ruleset **dsp); +static void devfs_ruleset_reap(struct devfs_ruleset **dsp); +static int devfs_ruleset_use(devfs_rsnum rsnum, struct devfs_mount *dm); + +static SLIST_HEAD(, devfs_ruleset) devfs_rulesets; + +/* + * Called to apply the proper rules for de before the latter can be + * exposed to the userland. This should be called with an exclusive + * lock on dm in case we need to run anything. + */ +void +devfs_rules_apply(struct devfs_mount *dm, struct devfs_dirent *de) +{ + struct devfs_ruleset *ds; + + ds = devfs_ruleset_bynum(dm->dm_ruleset); + KASSERT(ds != NULL, ("mount-point has NULL ruleset")); + devfs_ruleset_applyde(ds, de); +} + +/* + * Rule subsystem SYSINIT hook. + */ +void +devfs_rules_init(void) +{ + struct devfs_ruleset *ds; + + SLIST_INIT(&devfs_rulesets); + + ds = devfs_ruleset_create(0); + ds->ds_flags |= DS_IMMUTABLE; + ds->ds_refcount = 1; /* Prevent reaping. */ +} + +/* + * Rule subsystem ioctl hook. + */ +int +devfs_rules_ioctl(struct mount *mp, int cmd, caddr_t data, struct thread *td) +{ + struct devfs_mount *dm = VFSTODEVFS(mp); + struct devfs_ruleset *ds; + struct devfs_krule *dk; + struct devfs_rule *dr; + devfs_rsnum rsnum; + devfs_rnum rnum; + devfs_rid rid; + int error; + + /* + * XXX: This returns an error regardless of whether we + * actually support the cmd or not. + */ + error = suser(td); + if (error != 0) + return (error); + + lockmgr(&dm->dm_lock, LK_SHARED, 0, td); + + switch (cmd) { + case DEVFSIO_RADD: + dr = (struct devfs_rule *)data; + error = devfs_rule_input(dr, dm); + if (error != 0) + goto out; + dk = devfs_rule_byid(dr->dr_id); + if (dk != NULL) { + error = EEXIST; + goto out; + } + lockmgr(&dm->dm_lock, LK_UPGRADE, 0, td); + error = devfs_rule_insert(dr); + break; + case DEVFSIO_RAPPLY: + dr = (struct devfs_rule *)data; + error = devfs_rule_input(dr, dm); + if (error != 0) + goto out; + + /* + * This is one of many possible hackish + * implementations. The primary contender is an + * implementation where the rule we read in is + * temporarily inserted into some ruleset, perhaps + * with a hypothetical DRO_NOAUTO flag so that it + * doesn't get used where it isn't intended, and + * applied in the normal way. This can be done in the + * userland (DEVFSIO_ADD, DEVFSIO_APPLYID, + * DEVFSIO_DEL) or in the kernel; either way it breaks + * some corner case assumptions in other parts of the + * code (not that this implementation doesn't do + * that). + */ + if (dr->dr_iacts & DRA_INCSET && + devfs_ruleset_bynum(dr->dr_incset) == NULL) { + error = ESRCH; + goto out; + } + dk = malloc(sizeof(*dk), M_TEMP, M_WAITOK | M_ZERO); + memcpy(&dk->dk_rule, dr, sizeof(*dr)); + lockmgr(&dm->dm_lock, LK_UPGRADE, 0, td); + devfs_rule_applydm(dk, dm); + lockmgr(&dm->dm_lock, LK_DOWNGRADE, 0, td); + free(dk, M_TEMP); + error = 0; + break; + case DEVFSIO_RAPPLYID: + rid = *(devfs_rid *)data; + rid = devfs_rid_input(rid, dm); + dk = devfs_rule_byid(rid); + if (dk == NULL) { + error = ENOENT; + goto out; + } + lockmgr(&dm->dm_lock, LK_UPGRADE, 0, td); + devfs_rule_applydm(dk, dm); + error = 0; + break; + case DEVFSIO_RDEL: + rid = *(devfs_rid *)data; + rid = devfs_rid_input(rid, dm); + dk = devfs_rule_byid(rid); + if (dk == NULL) { + error = ENOENT; + goto out; + } + ds = dk->dk_ruleset; + lockmgr(&dm->dm_lock, LK_UPGRADE, 0, td); + error = devfs_rule_delete(&dk); + devfs_ruleset_reap(&ds); + break; + case DEVFSIO_RGETNEXT: + dr = (struct devfs_rule *)data; + error = devfs_rule_input(dr, dm); + if (error != 0) + goto out; + /* + * We can't use devfs_rule_byid() here since that + * requires the rule specified to exist, but we want + * getnext(N) to work whether there is a rule N or not + * (specifically, getnext(0) must work, but we should + * never have a rule 0 since the add command + * interprets 0 to mean "auto-number"). + */ + ds = devfs_ruleset_bynum(rid2rsn(dr->dr_id)); + if (ds == NULL) { + error = ENOENT; + goto out; + } + rnum = rid2rn(dr->dr_id); + SLIST_FOREACH(dk, &ds->ds_rules, dk_list) { + if (rid2rn(dk->dk_rule.dr_id) > rnum) + break; + } + if (dk == NULL) { + error = ENOENT; + goto out; + } + memcpy(dr, &dk->dk_rule, sizeof(*dr)); + error = 0; + break; + case DEVFSIO_SUSE: + rsnum = *(devfs_rsnum *)data; + lockmgr(&dm->dm_lock, LK_UPGRADE, 0, td); + error = devfs_ruleset_use(rsnum, dm); + break; + case DEVFSIO_SAPPLY: + rsnum = *(devfs_rsnum *)data; + rsnum = rid2rsn(devfs_rid_input(mkrid(rsnum, 0), dm)); + ds = devfs_ruleset_bynum(rsnum); + if (ds == NULL) { + error = ESRCH; + goto out; + } + lockmgr(&dm->dm_lock, LK_UPGRADE, 0, td); + devfs_ruleset_applydm(ds, dm); + error = 0; + break; + case DEVFSIO_SGETNEXT: + rsnum = *(devfs_rsnum *)data; + SLIST_FOREACH(ds, &devfs_rulesets, ds_list) { + if (ds->ds_number > rsnum) + break; + } + if (ds == NULL) + error = ENOENT; + else { + *(devfs_rsnum *)data = ds->ds_number; + error = 0; + } + break; + default: + error = ENOIOCTL; + break; + } + +out: + lockmgr(&dm->dm_lock, LK_RELEASE, 0, td); + return (error); +} + +/* + * Called to initialize dm_ruleset when there is a new mount-point. + */ +void +devfs_rules_newmount(struct devfs_mount *dm, struct thread *td) +{ + struct devfs_ruleset *ds; + + lockmgr(&dm->dm_lock, LK_EXCLUSIVE, 0, td); + /* + * We can't use devfs_ruleset_use() since it will try to + * decrement the refcount for the old ruleset, and there is no + * old ruleset. Making some value of ds_ruleset "special" to + * mean "don't decrement refcount" is uglier than this. + */ + ds = devfs_ruleset_bynum(0); + KASSERT(ds != NULL, ("no ruleset 0")); + ++ds->ds_refcount; + dm->dm_ruleset = 0; + lockmgr(&dm->dm_lock, LK_RELEASE, 0, td); +} + +/* + * Adjust the rule identifier to use the ruleset of dm if one isn't + * explicitly specified. + * + * Note that after this operation, rid2rsn(rid) might still be 0, and + * that's okay; ruleset 0 is a valid ruleset, but when it's read in + * from the userland, it means "current ruleset for this mount-point". + */ +static devfs_rid +devfs_rid_input(devfs_rid rid, struct devfs_mount *dm) +{ + + if (rid2rsn(rid) == 0) + return (mkrid(dm->dm_ruleset, rid2rn(rid))); + else + return (rid); +} + +/* + * Apply dk to de. + */ +static void +devfs_rule_applyde(struct devfs_krule *dk, struct devfs_dirent *de) +{ + + if (devfs_rule_match(dk, de)) + devfs_rule_run(dk, de); +} + +/* + * Apply dk to de and everything under de. + * + * XXX: This method needs a function call for every nested + * subdirectory in a devfs mount. If we plan to have many of these, + * we might eventually run out of kernel stack space. + */ +static void +devfs_rule_applyde_recursive(struct devfs_krule *dk, struct devfs_dirent *de) +{ + struct devfs_dirent *de2; + + /* XXX: Should we apply to ourselves first or last? Does it matter? */ + TAILQ_FOREACH(de2, &de->de_dlist, de_list) { + devfs_rule_applyde_recursive(dk, de2); + } + devfs_rule_applyde(dk, de); +} + +/* + * Apply dk to all entires in dm. + */ +static void +devfs_rule_applydm(struct devfs_krule *dk, struct devfs_mount *dm) +{ + + devfs_rule_applyde_recursive(dk, dm->dm_basedir); +} + +/* + * Automatically select a number for a new rule in ds, and write the + * result into rnump. + */ +static int +devfs_rule_autonumber(struct devfs_ruleset *ds, devfs_rnum *rnump) +{ + struct devfs_krule *dk; + + /* Find the last rule. */ + SLIST_FOREACH(dk, &ds->ds_rules, dk_list) { + if (SLIST_NEXT(dk, dk_list) == NULL) + break; + } + if (dk == NULL) + *rnump = 100; + else { + *rnump = rid2rn(dk->dk_rule.dr_id) + 100; + /* Detect overflow. */ + if (*rnump < rid2rn(dk->dk_rule.dr_id)) + return (ERANGE); + } + KASSERT(devfs_rule_byid(mkrid(ds->ds_number, *rnump)) == NULL, + ("autonumbering resulted in an already existing rule")); + return (0); +} + +/* + * Find a krule by id. + */ +static struct devfs_krule * +devfs_rule_byid(devfs_rid rid) +{ + struct devfs_ruleset *ds; + struct devfs_krule *dk; + devfs_rnum rn; + + rn = rid2rn(rid); + ds = devfs_ruleset_bynum(rid2rsn(rid)); + if (ds == NULL) + return (NULL); + SLIST_FOREACH(dk, &ds->ds_rules, dk_list) { + if (rid2rn(dk->dk_rule.dr_id) == rn) + return (dk); + else if (rid2rn(dk->dk_rule.dr_id) > rn) + break; + } + return (NULL); +} + +/* + * Remove dkp from any lists it may be on and remove memory associated + * with it. + */ +static int +devfs_rule_delete(struct devfs_krule **dkp) +{ + struct devfs_krule *dk = *dkp; + struct devfs_ruleset *ds; + + if (dk->dk_rule.dr_iacts & DRA_INCSET) { + ds = devfs_ruleset_bynum(dk->dk_rule.dr_incset); + KASSERT(ds != NULL, ("DRA_INCSET but bad dr_incset")); + --ds->ds_refcount; + devfs_ruleset_reap(&ds); + } + SLIST_REMOVE(&dk->dk_ruleset->ds_rules, dk, devfs_krule, dk_list); + free(dk, M_DEVFS); + *dkp = NULL; + return (0); +} + +/* + * Get a dev_t corresponding to de so we can try to match rules based + * on it. If this routine returns NULL, there is no dev_t associated + * with the dirent (symlinks and directories don't have dev_ts), and + * the caller should assume that any critera dependent on a dev_t + * don't match. + */ +static dev_t +devfs_rule_getdev(struct devfs_dirent *de) +{ + dev_t *devp, dev; + + devp = devfs_itod(de->de_inode); + if (devp != NULL) + dev = *devp; + else + dev = NULL; + /* If we think this dirent should have a dev_t, alert the user. */ + if (dev == NULL && de->de_dirent->d_type != DT_LNK && + de->de_dirent->d_type != DT_DIR) + printf("Warning: no dev_t for %s\n", de->de_dirent->d_name); + return (dev); +} + +/* + * Do what we need to do to a rule that we just loaded from the + * userland. In particular, we need to check the magic, and adjust + * the ruleset appropriate if desired. + */ +static int +devfs_rule_input(struct devfs_rule *dr, struct devfs_mount *dm) +{ + + if (dr->dr_magic != DEVFS_MAGIC) + return (ERPCMISMATCH); + dr->dr_id = devfs_rid_input(dr->dr_id, dm); + return (0); +} + +/* + * Import dr into the appropriate place in the kernel (i.e., make a + * krule). The value of dr is copied, so the pointer may be destroyed + * after this call completes. + */ +static int +devfs_rule_insert(struct devfs_rule *dr) +{ + struct devfs_ruleset *ds, *dsi; + struct devfs_krule *k1, *k2; + struct devfs_krule *dk; + devfs_rsnum rsnum; + devfs_rnum dkrn; + int error; + + /* + * This stuff seems out of place here, but we want to do it as + * soon as possible so that if it fails, we don't have to roll + * back any changes we already made (e.g., ruleset creation). + */ + if (dr->dr_iacts & DRA_INCSET) { + dsi = devfs_ruleset_bynum(dr->dr_incset); + if (dsi == NULL) + return (ESRCH); + } else + dsi = NULL; + + rsnum = rid2rsn(dr->dr_id); + ds = devfs_ruleset_bynum(rsnum); + if (ds == NULL) + ds = devfs_ruleset_create(rsnum); + if (ds->ds_flags & DS_IMMUTABLE) + return (EIO); + dkrn = rid2rn(dr->dr_id); + if (dkrn == 0) { + error = devfs_rule_autonumber(ds, &dkrn); + if (error != 0) + return (error); + } + + dk = malloc(sizeof(*dk), M_DEVFS, M_WAITOK); + dk->dk_ruleset = ds; + if (dsi != NULL) + ++dsi->ds_refcount; + /* XXX: Inspect dr? */ + memcpy(&dk->dk_rule, dr, sizeof(*dr)); + dk->dk_rule.dr_id = mkrid(rid2rsn(dk->dk_rule.dr_id), dkrn); + + k1 = SLIST_FIRST(&ds->ds_rules); + if (k1 == NULL || rid2rn(k1->dk_rule.dr_id) > dkrn) + SLIST_INSERT_HEAD(&ds->ds_rules, dk, dk_list); + else { + SLIST_FOREACH(k1, &ds->ds_rules, dk_list) { + k2 = SLIST_NEXT(k1, dk_list); + if (k2 == NULL || rid2rn(k2->dk_rule.dr_id) > dkrn) { + SLIST_INSERT_AFTER(k1, dk, dk_list); + break; + } + } + } + + return (0); +} + +/* + * Determine whether dk matches de. Returns 1 if dk should be run on + * de; 0, otherwise. + */ +static int +devfs_rule_match(struct devfs_krule *dk, struct devfs_dirent *de) +{ + struct devfs_rule *dr = &dk->dk_rule; + dev_t dev; + + dev = devfs_rule_getdev(de); + /* + * At this point, if dev is NULL, we should assume that any + * criteria that depend on it don't match. We should *not* + * just ignore them (i.e., act like they weren't specified), + * since that makes a rule that only has criteria dependent on + * the dev_t match all symlinks and directories. + * + * Note also that the following tests are somewhat reversed: + * They're actually testing to see whether the condition does + * *not* match, since the default is to assume the rule should + * be run (such as if there are no conditions). + */ + if (dr->dr_icond & DRC_DSWFLAGS) + if (dev == NULL || + (dev->si_devsw->d_flags & dr->dr_dswflags) == 0) + goto nomatch; + if (dr->dr_icond & DRC_PATHPTRN) + if (!devfs_rule_matchpath(dk, de)) + goto nomatch; + if (dr->dr_icond & DRC_MAJOR) + if (dev == NULL || major(dev) != dr->dr_major) + goto nomatch; + + return (1); + +nomatch: + return (0); +} + +/* + * Determine whether dk matches de on account of dr_pathptrn. + */ +static int +devfs_rule_matchpath(struct devfs_krule *dk, struct devfs_dirent *de) +{ + struct devfs_rule *dr = &dk->dk_rule; + char *pname; + dev_t dev; + int plen; + + dev = devfs_rule_getdev(de); + if (dev != NULL) + pname = dev->si_name; + /* XXX: Support symlinks (check d_type == DT_LNK here). */ + else + return (0); + KASSERT(pname != NULL, ("devfs_rule_matchpath: NULL pname")); + + /* + * XXX: Interpret dr_pathptrn as a real pattern (support '*', + * '?', and perhaps brace expansion). For now, we only + * support one trailing asterisk. + */ + plen = strlen(dr->dr_pathptrn); + if (dr->dr_pathptrn[plen - 1] == '*') { + if (strlen(pname) >= plen - 1 && + strncmp(dr->dr_pathptrn, pname, plen - 1) == 0) + return (1); + } else + if (strcmp(dr->dr_pathptrn, pname) == 0) + return (1); + return (0); +} + +/* + * Run dk on de. + */ +static void +devfs_rule_run(struct devfs_krule *dk, struct devfs_dirent *de) +{ + struct devfs_rule *dr = &dk->dk_rule; + struct devfs_ruleset *ds; + + if (dr->dr_iacts & DRA_BACTS) { + if (dr->dr_bacts & DRB_HIDE) + de->de_flags |= DE_WHITEOUT; + if (dr->dr_bacts & DRB_UNHIDE) + de->de_flags &= ~DE_WHITEOUT; + } + if (dr->dr_iacts & DRA_UID) + de->de_uid = dr->dr_uid; + if (dr->dr_iacts & DRA_GID) + de->de_gid = dr->dr_gid; + if (dr->dr_iacts & DRA_MODE) + de->de_mode = dr->dr_mode; + if (dr->dr_iacts & DRA_INCSET) { + ds = devfs_ruleset_bynum(dk->dk_rule.dr_incset); + KASSERT(ds != NULL, ("DRA_INCSET but bad dr_incset")); + if (dk->dk_ruleset == ds) { + /* XXX: Do a better job of detecting loops. */ + printf("Warning: Ruleset %d including itself!\n", + dk->dk_ruleset->ds_number); + } else + devfs_ruleset_applyde(ds, de); + } +} + +/* + * Apply all the rules in ds to de. + */ +static void +devfs_ruleset_applyde(struct devfs_ruleset *ds, struct devfs_dirent *de) +{ + struct devfs_krule *dk; + + SLIST_FOREACH(dk, &ds->ds_rules, dk_list) { + devfs_rule_applyde(dk, de); + } +} + +/* + * Apply all the rules in ds to all the entires in dm. + */ +static void +devfs_ruleset_applydm(struct devfs_ruleset *ds, struct devfs_mount *dm) +{ + struct devfs_krule *dk; + + /* + * XXX: Does it matter whether we do + * + * foreach(dk in ds) + * foreach(de in dm) + * apply(dk to de) + * + * as opposed to + * + * foreach(de in dm) + * foreach(dk in ds) + * apply(dk to de) + * + * The end result is obviously the same, but does the order + * matter? + */ + SLIST_FOREACH(dk, &ds->ds_rules, dk_list) { + devfs_rule_applydm(dk, dm); + } +} + +/* + * Find a ruleset by number. + */ +static struct devfs_ruleset * +devfs_ruleset_bynum(devfs_rsnum rsnum) +{ + struct devfs_ruleset *ds; + + SLIST_FOREACH(ds, &devfs_rulesets, ds_list) { + if (ds->ds_number == rsnum) + return (ds); + } + return (NULL); +} + +/* + * Create a new ruleset. + */ +static struct devfs_ruleset * +devfs_ruleset_create(devfs_rsnum rsnum) +{ + struct devfs_ruleset *s1, *s2; + struct devfs_ruleset *ds; + + KASSERT(devfs_ruleset_bynum(rsnum) == NULL, + ("creating already existent ruleset %d", rsnum)); + + ds = malloc(sizeof(*ds), M_DEVFS, M_WAITOK | M_ZERO); + ds->ds_number = rsnum; + ds->ds_refcount = ds->ds_flags = 0; + SLIST_INIT(&ds->ds_rules); + + s1 = SLIST_FIRST(&devfs_rulesets); + if (s1 == NULL || s1->ds_number > rsnum) + SLIST_INSERT_HEAD(&devfs_rulesets, ds, ds_list); + else { + SLIST_FOREACH(s1, &devfs_rulesets, ds_list) { + s2 = SLIST_NEXT(s1, ds_list); + if (s2 == NULL || s2->ds_number > rsnum) { + SLIST_INSERT_AFTER(s1, ds, ds_list); + break; + } + } + } + + return (ds); +} + +/* + * Remove a ruleset form the system. The ruleset specified must be + * empty and not in use. + */ +static void +devfs_ruleset_destroy(struct devfs_ruleset **dsp) +{ + struct devfs_ruleset *ds = *dsp; + + KASSERT(SLIST_EMPTY(&ds->ds_rules), ("destroying non-empty ruleset")); + KASSERT(ds->ds_refcount == 0, ("destroying busy ruleset")); + KASSERT((ds->ds_flags & DS_IMMUTABLE) == 0, + ("destroying immutable ruleset")); + + SLIST_REMOVE(&devfs_rulesets, ds, devfs_ruleset, ds_list); + free(ds, M_DEVFS); + *dsp = NULL; +} + +/* + * Remove a ruleset from the system if it's empty and not used + * anywhere. This should be called after every time a rule is deleted + * from this ruleset or the reference count is decremented. + */ +static void +devfs_ruleset_reap(struct devfs_ruleset **dsp) +{ + struct devfs_ruleset *ds = *dsp; + + if (SLIST_EMPTY(&ds->ds_rules) && ds->ds_refcount == 0) { + devfs_ruleset_destroy(&ds); + *dsp = ds; + } +} + +/* + * Make rsnum the active ruleset for dm. + */ +static int +devfs_ruleset_use(devfs_rsnum rsnum, struct devfs_mount *dm) +{ + struct devfs_ruleset *cds, *ds; + + ds = devfs_ruleset_bynum(rsnum); + if (ds == NULL) + ds = devfs_ruleset_create(rsnum); + cds = devfs_ruleset_bynum(dm->dm_ruleset); + KASSERT(cds != NULL, ("mount-point has NULL ruleset")); + + /* These should probably be made atomic somehow. */ + --cds->ds_refcount; + ++ds->ds_refcount; + dm->dm_ruleset = rsnum; + + devfs_ruleset_reap(&cds); + return (0); +} + +#endif /* !NODEVFS */ diff --git a/sys/fs/devfs/devfs_vfsops.c b/sys/fs/devfs/devfs_vfsops.c index 4b2d7701be60..a46b0c4c49a6 100644 --- a/sys/fs/devfs/devfs_vfsops.c +++ b/sys/fs/devfs/devfs_vfsops.c @@ -91,6 +91,7 @@ devfs_mount(mp, ndp, td) fmp->dm_rootdir = devfs_vmkdir("(root)", 6, NULL); fmp->dm_rootdir->de_inode = 2; fmp->dm_basedir = fmp->dm_rootdir; + devfs_rules_newmount(fmp, td); error = devfs_root(mp, &rvp); if (error) { diff --git a/sys/fs/devfs/devfs_vnops.c b/sys/fs/devfs/devfs_vnops.c index 8779c96381f7..069d22469166 100644 --- a/sys/fs/devfs/devfs_vnops.c +++ b/sys/fs/devfs/devfs_vnops.c @@ -62,6 +62,7 @@ static int devfs_access(struct vop_access_args *ap); static int devfs_getattr(struct vop_getattr_args *ap); +static int devfs_ioctl(struct vop_ioctl_args *ap); static int devfs_lookupx(struct vop_lookup_args *ap); static int devfs_mknod(struct vop_mknod_args *ap); static int devfs_pathconf(struct vop_pathconf_args *ap); @@ -246,6 +247,24 @@ devfs_getattr(ap) return (error); } +static int +devfs_ioctl(ap) + struct vop_ioctl_args /* { + struct vnode *a_vp; + int a_command; + caddr_t a_data; + int a_fflag; + struct ucred *a_cred; + struct thread *a_td; + } */ *ap; +{ + int error; + + error = devfs_rules_ioctl(ap->a_vp->v_mount, ap->a_command, ap->a_data, + ap->a_td); + return (error); +} + static int devfs_lookupx(ap) struct vop_lookup_args /* { @@ -808,6 +827,7 @@ static struct vnodeopv_entry_desc devfs_vnodeop_entries[] = { { &vop_default_desc, (vop_t *) vop_defaultop }, { &vop_access_desc, (vop_t *) devfs_access }, { &vop_getattr_desc, (vop_t *) devfs_getattr }, + { &vop_ioctl_desc, (vop_t *) devfs_ioctl }, { &vop_islocked_desc, (vop_t *) vop_stdislocked }, { &vop_lock_desc, (vop_t *) vop_stdlock }, { &vop_lookup_desc, (vop_t *) devfs_lookup },