diff options
Diffstat (limited to 'usr/src/uts/common/fs/dev/sdev_netops.c')
-rw-r--r-- | usr/src/uts/common/fs/dev/sdev_netops.c | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/usr/src/uts/common/fs/dev/sdev_netops.c b/usr/src/uts/common/fs/dev/sdev_netops.c new file mode 100644 index 0000000000..b51b155344 --- /dev/null +++ b/usr/src/uts/common/fs/dev/sdev_netops.c @@ -0,0 +1,397 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * vnode ops for the /dev/net directory + * + * The lookup is based on the internal vanity naming node table. We also + * override readdir in order to delete net nodes no longer in-use. + */ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/sysmacros.h> +#include <sys/sunndi.h> +#include <fs/fs_subr.h> +#include <sys/fs/dv_node.h> +#include <sys/fs/sdev_impl.h> +#include <sys/policy.h> +#include <sys/zone.h> +#include <sys/dls.h> + +struct vnodeops *devnet_vnodeops; + +/* + * Called by zone_walk_datalink() to see if the given link name belongs to the + * given zone. Returns 0 to continue the walk, -1 if the link name is found. + */ +static int +devnet_validate_name(const char *link, void *arg) +{ + return ((strcmp(link, arg) == 0) ? -1 : 0); +} + +/* + * Check if a net sdev_node is still valid - i.e. it represents a current + * network link. + * This serves two purposes + * - only valid net nodes are returned during lookup() and readdir(). + * - since net sdev_nodes are not actively destroyed when a network link + * goes away, we use the validator to do deferred cleanup i.e. when such + * nodes are encountered during subsequent lookup() and readdir(). + */ +int +devnet_validate(struct sdev_node *dv) +{ + char *nm = dv->sdev_name; + datalink_id_t linkid; + + ASSERT(!(dv->sdev_flags & SDEV_STALE)); + ASSERT(dv->sdev_state == SDEV_READY); + + if (SDEV_IS_GLOBAL(dv)) { + return ((dls_mgmt_get_linkid(nm, &linkid) != 0) ? + SDEV_VTOR_INVALID : SDEV_VTOR_VALID); + } else { + return ((zone_datalink_walk(getzoneid(), devnet_validate_name, + nm) == -1) ? SDEV_VTOR_VALID : SDEV_VTOR_INVALID); + } +} + +/* + * This callback is invoked from devname_lookup_func() to create + * a net entry when the node is not found in the cache. + */ +static int +devnet_create_rvp(const char *nm, struct vattr *vap, dls_dl_handle_t *ddhp) +{ + timestruc_t now; + dev_t dev; + int error; + + if ((error = dls_devnet_open(nm, ddhp, &dev)) != 0) { + sdcmn_err12(("devnet_create_rvp: not a valid vanity name " + "network node: %s\n", nm)); + return (error); + } + + /* + * This is a valid network device (at least at this point in time). + * Create the node by setting the attribute; the rest is taken care + * of by devname_lookup_func(). + */ + *vap = sdev_vattr_chr; + vap->va_mode |= 0666; + vap->va_rdev = dev; + + gethrestime(&now); + vap->va_atime = now; + vap->va_mtime = now; + vap->va_ctime = now; + return (0); +} + +/* + * Lookup for /dev/net directory + * If the entry does not exist, the devnet_create_rvp() callback + * is invoked to create it. Nodes do not persist across reboot. + */ +/*ARGSUSED3*/ +static int +devnet_lookup(struct vnode *dvp, char *nm, struct vnode **vpp, + struct pathname *pnp, int flags, struct vnode *rdir, struct cred *cred, + caller_context_t *ct, int *direntflags, pathname_t *realpnp) +{ + struct sdev_node *ddv = VTOSDEV(dvp); + struct sdev_node *dv = NULL; + dls_dl_handle_t ddh = NULL; + struct vattr vattr; + int nmlen; + int error = ENOENT; + + if (SDEVTOV(ddv)->v_type != VDIR) + return (ENOTDIR); + + /* + * Empty name or ., return node itself. + */ + nmlen = strlen(nm); + if ((nmlen == 0) || ((nmlen == 1) && (nm[0] == '.'))) { + *vpp = SDEVTOV(ddv); + VN_HOLD(*vpp); + return (0); + } + + /* + * .., return the parent directory + */ + if ((nmlen == 2) && (strcmp(nm, "..") == 0)) { + *vpp = SDEVTOV(ddv->sdev_dotdot); + VN_HOLD(*vpp); + return (0); + } + + rw_enter(&ddv->sdev_contents, RW_WRITER); + + /* + * directory cache lookup: + */ + if ((dv = sdev_cache_lookup(ddv, nm)) != NULL) { + if (dv->sdev_state == SDEV_READY) { + if (!(dv->sdev_flags & SDEV_ATTR_INVALID)) + goto found; + } else { + ASSERT(dv->sdev_state == SDEV_ZOMBIE); + goto failed; + } + } + + /* + * ZOMBIED parent does not allow new node creation, bail out early. + */ + if (ddv->sdev_state == SDEV_ZOMBIE) + goto failed; + + error = devnet_create_rvp(nm, &vattr, &ddh); + if (error != 0) + goto failed; + + error = sdev_mknode(ddv, nm, &dv, &vattr, NULL, NULL, cred, SDEV_READY); + if (error != 0) { + ASSERT(dv == NULL); + dls_devnet_close(ddh); + goto failed; + } + + ASSERT(dv != NULL); + + rw_enter(&dv->sdev_contents, RW_WRITER); + if (dv->sdev_flags & SDEV_ATTR_INVALID) { + /* + * SDEV_ATTR_INVALID means that this device has been + * detached, and its dev_t might've been changed too. + * Therefore, sdev_node's 'vattr' needs to be updated. + */ + SDEVTOV(dv)->v_rdev = vattr.va_rdev; + ASSERT(dv->sdev_attr != NULL); + dv->sdev_attr->va_rdev = vattr.va_rdev; + dv->sdev_flags &= ~SDEV_ATTR_INVALID; + } + ASSERT(dv->sdev_private == NULL); + dv->sdev_private = ddh; + rw_exit(&dv->sdev_contents); + +found: + ASSERT(SDEV_HELD(dv)); + rw_exit(&ddv->sdev_contents); + return (sdev_to_vp(dv, vpp)); + +failed: + rw_exit(&ddv->sdev_contents); + + if (dv != NULL) + SDEV_RELE(dv); + + *vpp = NULL; + return (error); +} + +static int +devnet_filldir_datalink(const char *link, void *arg) +{ + struct sdev_node *ddv = arg; + struct vattr vattr; + struct sdev_node *dv; + dls_dl_handle_t ddh = NULL; + + ASSERT(RW_WRITE_HELD(&ddv->sdev_contents)); + if ((dv = sdev_cache_lookup(ddv, (char *)link)) != NULL) + goto found; + + if (devnet_create_rvp(link, &vattr, &ddh) != 0) + return (0); + + ASSERT(ddh != NULL); + dls_devnet_close(ddh); + + if (sdev_mknode(ddv, (char *)link, &dv, &vattr, NULL, NULL, kcred, + SDEV_READY) != 0) { + return (0); + } + + /* + * As there is no reference holding the network device, it could be + * detached. Set SDEV_ATTR_INVALID so that the 'vattr' will be updated + * later. + */ + rw_enter(&dv->sdev_contents, RW_WRITER); + dv->sdev_flags |= SDEV_ATTR_INVALID; + rw_exit(&dv->sdev_contents); + +found: + SDEV_SIMPLE_RELE(dv); + return (0); +} + +static void +devnet_filldir(struct sdev_node *ddv) +{ + sdev_node_t *dv, *next; + char link[MAXLINKNAMELEN]; + datalink_id_t linkid; + + ASSERT(RW_READ_HELD(&ddv->sdev_contents)); + if (rw_tryupgrade(&ddv->sdev_contents) == NULL) { + rw_exit(&ddv->sdev_contents); + rw_enter(&ddv->sdev_contents, RW_WRITER); + } + + for (dv = ddv->sdev_dot; dv; dv = next) { + next = dv->sdev_next; + + /* skip stale nodes */ + if (dv->sdev_flags & SDEV_STALE) + continue; + + /* validate and prune only ready nodes */ + if (dv->sdev_state != SDEV_READY) + continue; + + switch (devnet_validate(dv)) { + case SDEV_VTOR_VALID: + case SDEV_VTOR_SKIP: + continue; + case SDEV_VTOR_INVALID: + sdcmn_err12(("devnet_filldir: destroy invalid " + "node: %s(%p)\n", dv->sdev_name, (void *)dv)); + break; + } + + if (SDEVTOV(dv)->v_count > 0) + continue; + SDEV_HOLD(dv); + /* remove the cache node */ + (void) sdev_cache_update(ddv, &dv, dv->sdev_name, + SDEV_CACHE_DELETE); + } + + if (((ddv->sdev_flags & SDEV_BUILD) == 0) && !dls_devnet_rebuild()) + goto done; + + if (SDEV_IS_GLOBAL(ddv)) { + linkid = DATALINK_INVALID_LINKID; + do { + linkid = dls_mgmt_get_next(linkid, DATALINK_CLASS_ALL, + DATALINK_ANY_MEDIATYPE, DLMGMT_ACTIVE); + + if ((linkid != DATALINK_INVALID_LINKID) && + (dls_mgmt_get_linkinfo(linkid, link, + NULL, NULL, NULL) == 0)) { + (void) devnet_filldir_datalink(link, ddv); + } + } while (linkid != DATALINK_INVALID_LINKID); + } else { + (void) zone_datalink_walk(getzoneid(), + devnet_filldir_datalink, ddv); + } + + ddv->sdev_flags &= ~SDEV_BUILD; + +done: + rw_downgrade(&ddv->sdev_contents); +} + +/* + * Display all instantiated network datalink device nodes. + * A /dev/net entry will be created only after the first lookup of + * the network datalink device succeeds. + */ +/*ARGSUSED4*/ +static int +devnet_readdir(struct vnode *dvp, struct uio *uiop, struct cred *cred, + int *eofp, caller_context_t *ct, int flags) +{ + struct sdev_node *sdvp = VTOSDEV(dvp); + + ASSERT(sdvp); + + if (uiop->uio_offset == 0) + devnet_filldir(sdvp); + + return (devname_readdir_func(dvp, uiop, cred, eofp, 0)); +} + +/* + * This callback is invoked from devname_inactive_func() to release + * the net entry which was held in devnet_create_rvp(). + */ +static void +devnet_inactive_callback(struct vnode *dvp) +{ + struct sdev_node *sdvp = VTOSDEV(dvp); + dls_dl_handle_t ddh; + + if (dvp->v_type == VDIR) + return; + + ASSERT(dvp->v_type == VCHR); + rw_enter(&sdvp->sdev_contents, RW_WRITER); + ddh = sdvp->sdev_private; + sdvp->sdev_private = NULL; + sdvp->sdev_flags |= SDEV_ATTR_INVALID; + rw_exit(&sdvp->sdev_contents); + + /* + * "ddh" (sdev_private) could be NULL if devnet_lookup fails. + */ + if (ddh != NULL) + dls_devnet_close(ddh); +} + +/*ARGSUSED*/ +static void +devnet_inactive(struct vnode *dvp, struct cred *cred, caller_context_t *ct) +{ + devname_inactive_func(dvp, cred, devnet_inactive_callback); +} + +/* + * We override lookup and readdir to build entries based on the + * in kernel vanity naming node table. + */ +const fs_operation_def_t devnet_vnodeops_tbl[] = { + VOPNAME_READDIR, { .vop_readdir = devnet_readdir }, + VOPNAME_LOOKUP, { .vop_lookup = devnet_lookup }, + VOPNAME_INACTIVE, { .vop_inactive = devnet_inactive }, + VOPNAME_CREATE, { .error = fs_nosys }, + VOPNAME_REMOVE, { .error = fs_nosys }, + VOPNAME_MKDIR, { .error = fs_nosys }, + VOPNAME_RMDIR, { .error = fs_nosys }, + VOPNAME_SYMLINK, { .error = fs_nosys }, + VOPNAME_SETSECATTR, { .error = fs_nosys }, + NULL, NULL +}; |