diff options
Diffstat (limited to 'usr/src/uts/common/fs/dev/sdev_profile.c')
-rw-r--r-- | usr/src/uts/common/fs/dev/sdev_profile.c | 983 |
1 files changed, 983 insertions, 0 deletions
diff --git a/usr/src/uts/common/fs/dev/sdev_profile.c b/usr/src/uts/common/fs/dev/sdev_profile.c new file mode 100644 index 0000000000..009dc4f8d5 --- /dev/null +++ b/usr/src/uts/common/fs/dev/sdev_profile.c @@ -0,0 +1,983 @@ +/* + * 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 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This file implements /dev filesystem operations for non-global + * instances. Three major entry points: + * devname_profile_update() + * Update matching rules determining which names to export + * prof_readdir() + * Return the list of exported names + * prof_lookup() + * Implements lookup + */ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/sysmacros.h> +#include <sys/vnode.h> +#include <sys/uio.h> +#include <sys/dirent.h> +#include <sys/pathname.h> +#include <sys/fs/dv_node.h> +#include <sys/fs/sdev_impl.h> +#include <sys/sunndi.h> +#include <sys/modctl.h> + +enum { + PROFILE_TYPE_INCLUDE, + PROFILE_TYPE_EXCLUDE, + PROFILE_TYPE_MAP, + PROFILE_TYPE_SYMLINK +}; + +enum { + WALK_DIR_CONTINUE = 0, + WALK_DIR_TERMINATE +}; + +static const char *sdev_nvp_val_err = "nvpair_value error %d, %s\n"; + +static void process_rule(struct sdev_node *, struct sdev_node *, + char *, char *, int); +static void walk_dir(struct vnode *, void *, int (*)(char *, void *)); + +static void +prof_getattr(struct sdev_node *dir, char *name, struct vnode *gdv, + struct vattr *vap, struct vnode **avpp, int *no_fs_perm) +{ + struct vnode *advp; + + /* get attribute from shadow, if present; else get default */ + advp = dir->sdev_attrvp; + if (advp && VOP_LOOKUP(advp, name, avpp, NULL, 0, NULL, kcred) == 0) { + (void) VOP_GETATTR(*avpp, vap, 0, kcred); + } else if (gdv == NULL || gdv->v_type == VDIR) { + /* always create shadow directory */ + *vap = sdev_vattr_dir; + if (advp && VOP_MKDIR(advp, name, + &sdev_vattr_dir, avpp, kcred) != 0) { + *avpp = NULLVP; + sdcmn_err10(("prof_getattr: failed to create " + "shadow directory %s/%s\n", dir->sdev_path, name)); + } + } else { + /* + * get default permission from devfs + * Before calling devfs_get_defattr, we need to get + * the realvp (the dv_node). If realvp is not a dv_node, + * devfs_get_defattr() will return a system-wide default + * attr for device nodes. + */ + struct vnode *rvp; + if (VOP_REALVP(gdv, &rvp) != 0) + rvp = gdv; + devfs_get_defattr(rvp, vap, no_fs_perm); + *avpp = NULLVP; + } + + /* ignore dev_t and vtype from backing store */ + if (gdv) { + vap->va_type = gdv->v_type; + vap->va_rdev = gdv->v_rdev; + } +} + +static void +apply_glob_pattern(struct sdev_node *pdir, struct sdev_node *cdir) +{ + char *name; + nvpair_t *nvp = NULL; + nvlist_t *nvl; + struct vnode *vp = SDEVTOV(cdir); + int rv = 0; + + if (vp->v_type != VDIR) + return; + name = cdir->sdev_name; + nvl = pdir->sdev_prof.dev_glob_incdir; + while (nvp = nvlist_next_nvpair(nvl, nvp)) { + char *pathleft; + char *expr = nvpair_name(nvp); + if (!gmatch(name, expr)) + continue; + rv = nvpair_value_string(nvp, &pathleft); + if (rv != 0) { + cmn_err(CE_WARN, sdev_nvp_val_err, + rv, nvpair_name(nvp)); + break; + } + process_rule(cdir, cdir->sdev_origin, + pathleft, NULL, PROFILE_TYPE_INCLUDE); + } +} + +/* + * Some commonality here with sdev_mknode(), could be simplified. + * NOTE: prof_mknode returns with *newdv held once, if success. + */ +static int +prof_mknode(struct sdev_node *dir, char *name, struct sdev_node **newdv, + vattr_t *vap, vnode_t *avp, void *arg, cred_t *cred) +{ + struct sdev_node *dv; + int rv; + + ASSERT(RW_WRITE_HELD(&dir->sdev_contents)); + + /* check cache first */ + if (dv = sdev_cache_lookup(dir, name)) { + *newdv = dv; + return (0); + } + + /* allocate node and insert into cache */ + rv = sdev_nodeinit(dir, name, &dv, NULL); + if (rv != 0) { + *newdv = NULL; + return (rv); + } + + rv = sdev_cache_update(dir, &dv, name, SDEV_CACHE_ADD); + *newdv = dv; + + /* put it in ready state */ + rv = sdev_nodeready(*newdv, vap, avp, arg, cred); + + /* handle glob pattern in the middle of a path */ + if (rv == 0) { + if (SDEVTOV(*newdv)->v_type == VDIR) + sdcmn_err10(("sdev_origin for %s set to 0x%p\n", + name, arg)); + apply_glob_pattern(dir, *newdv); + } + return (rv); +} + +/* + * Create a directory node in a non-global dev instance. + * Always create shadow vnode. Set sdev_origin to the corresponding + * global directory sdev_node if it exists. This facilitates the + * lookup operation. + */ +static int +prof_make_dir(char *name, struct sdev_node **gdirp, struct sdev_node **dirp) +{ + struct sdev_node *dir = *dirp; + struct sdev_node *gdir = *gdirp; + struct sdev_node *newdv; + struct vnode *avp, *gnewdir = NULL; + struct vattr vattr; + int error; + + /* see if name already exists */ + rw_enter(&dir->sdev_contents, RW_READER); + if (newdv = sdev_cache_lookup(dir, name)) { + *dirp = newdv; + *gdirp = newdv->sdev_origin; + SDEV_RELE(dir); + rw_exit(&dir->sdev_contents); + return (0); + } + rw_exit(&dir->sdev_contents); + + /* find corresponding dir node in global dev */ + if (gdir) { + error = VOP_LOOKUP(SDEVTOV(gdir), name, &gnewdir, + NULL, 0, NULL, kcred); + if (error == 0) { + *gdirp = VTOSDEV(gnewdir); + } else { /* it's ok if there no global dir */ + *gdirp = NULL; + } + } + + /* get attribute from shadow, also create shadow dir */ + prof_getattr(dir, name, gnewdir, &vattr, &avp, NULL); + + /* create dev directory vnode */ + rw_enter(&dir->sdev_contents, RW_WRITER); + error = prof_mknode(dir, name, &newdv, &vattr, avp, (void *)*gdirp, + kcred); + rw_exit(&dir->sdev_contents); + if (error == 0) { + ASSERT(newdv); + *dirp = newdv; + } + SDEV_RELE(dir); + return (error); +} + +/* + * Look up a logical name in the global zone. + * Provides the ability to map the global zone's device name + * to an alternate name within a zone. The primary example + * is the virtual console device /dev/zcons/[zonename]/zconsole + * mapped to /[zonename]/root/dev/zconsole. + */ +static void +prof_lookup_globaldev(struct sdev_node *dir, struct sdev_node *gdir, + char *name, char *rename) +{ + /* global OS rootdir */ + extern vnode_t *rootdir; + + int error; + struct vnode *avp, *gdv, *gddv; + struct sdev_node *newdv; + struct vattr vattr = {0}; + struct pathname pn; + + /* check if node already exists */ + newdv = sdev_cache_lookup(dir, rename); + if (newdv) { + ASSERT(newdv->sdev_state != SDEV_ZOMBIE); + SDEV_SIMPLE_RELE(newdv); + return; + } + + /* sanity check arguments */ + if (!gdir || pn_get(name, UIO_SYSSPACE, &pn)) + return; + + /* perform a relative lookup of the global /dev instance */ + gddv = SDEVTOV(gdir); + VN_HOLD(gddv); + VN_HOLD(rootdir); + error = lookuppnvp(&pn, NULL, FOLLOW, NULLVPP, &gdv, + rootdir, gddv, kcred); + pn_free(&pn); + if (error) { + sdcmn_err10(("prof_lookup_globaldev: %s not found\n", name)); + return; + } + ASSERT(gdv && gdv->v_type != VLNK); + + /* + * Found the entry in global /dev, figure out attributes + * by looking at backing store. Call into devfs for default. + */ + prof_getattr(dir, name, gdv, &vattr, &avp, NULL); + + if (gdv->v_type != VDIR) { + VN_RELE(gdv); + gdir = NULL; + } else + gdir = VTOSDEV(gdv); + + if (prof_mknode(dir, rename, &newdv, &vattr, avp, + (void *)gdir, kcred) == 0) { + ASSERT(newdv->sdev_state != SDEV_ZOMBIE); + SDEV_SIMPLE_RELE(newdv); + } +} + +static void +prof_make_sym(struct sdev_node *dir, char *lnm, char *tgt) +{ + struct sdev_node *newdv; + + if (prof_mknode(dir, lnm, &newdv, &sdev_vattr_lnk, NULL, + (void *)tgt, kcred) == 0) { + ASSERT(newdv->sdev_state != SDEV_ZOMBIE); + SDEV_SIMPLE_RELE(newdv); + } +} + +/* + * Create symlinks in the current directory based on profile + */ +static void +prof_make_symlinks(struct sdev_node *dir) +{ + char *tgt, *lnm; + nvpair_t *nvp = NULL; + nvlist_t *nvl = dir->sdev_prof.dev_symlink; + int rv; + + ASSERT(RW_WRITE_HELD(&dir->sdev_contents)); + + if (nvl == NULL) + return; + + while (nvp = nvlist_next_nvpair(nvl, nvp)) { + lnm = nvpair_name(nvp); + rv = nvpair_value_string(nvp, &tgt); + if (rv != 0) { + cmn_err(CE_WARN, sdev_nvp_val_err, + rv, nvpair_name(nvp)); + break; + } + prof_make_sym(dir, lnm, tgt); + } +} + +static void +prof_make_maps(struct sdev_node *dir) +{ + nvpair_t *nvp = NULL; + nvlist_t *nvl = dir->sdev_prof.dev_map; + int rv; + + ASSERT(RW_WRITE_HELD(&dir->sdev_contents)); + + if (nvl == NULL) + return; + + while (nvp = nvlist_next_nvpair(nvl, nvp)) { + char *name; + char *rename = nvpair_name(nvp); + rv = nvpair_value_string(nvp, &name); + if (rv != 0) { + cmn_err(CE_WARN, sdev_nvp_val_err, + rv, nvpair_name(nvp)); + break; + } + sdcmn_err10(("map %s -> %s\n", name, rename)); + (void) prof_lookup_globaldev(dir, sdev_origins->sdev_root, + name, rename); + } +} + +struct match_arg { + char *expr; + int match; +}; + +static int +match_name(char *name, void *arg) +{ + struct match_arg *margp = (struct match_arg *)arg; + + if (gmatch(name, margp->expr)) { + margp->match = 1; + return (WALK_DIR_TERMINATE); + } + return (WALK_DIR_CONTINUE); +} + +static int +is_nonempty_dir(char *name, char *pathleft, struct sdev_node *dir) +{ + struct match_arg marg; + struct pathname pn; + struct vnode *gvp; + struct sdev_node *gdir = dir->sdev_origin; + + if (VOP_LOOKUP(SDEVTOV(gdir), name, &gvp, NULL, 0, NULL, kcred) != 0) + return (0); + + if (gvp->v_type != VDIR) { + VN_RELE(gvp); + return (0); + } + + if (pn_get(pathleft, UIO_SYSSPACE, &pn) != 0) { + VN_RELE(gvp); + return (0); + } + + marg.expr = kmem_alloc(MAXNAMELEN, KM_SLEEP); + (void) pn_getcomponent(&pn, marg.expr); + marg.match = 0; + + walk_dir(gvp, &marg, match_name); + VN_RELE(gvp); + kmem_free(marg.expr, MAXNAMELEN); + pn_free(&pn); + + return (marg.match); +} + + +/* Check if name passes matching rules */ +static int +prof_name_matched(char *name, struct sdev_node *dir) +{ + int type, match = 0; + char *expr; + nvlist_t *nvl; + nvpair_t *nvp = NULL; + int rv; + + /* check against nvlist for leaf include/exclude */ + nvl = dir->sdev_prof.dev_name; + while (nvp = nvlist_next_nvpair(nvl, nvp)) { + expr = nvpair_name(nvp); + rv = nvpair_value_int32(nvp, &type); + if (rv != 0) { + cmn_err(CE_WARN, sdev_nvp_val_err, + rv, nvpair_name(nvp)); + break; + } + + if (type == PROFILE_TYPE_EXCLUDE) { + if (gmatch(name, expr)) + return (0); /* excluded */ + } else if (!match) { + match = gmatch(name, expr); + } + } + if (match) { + sdcmn_err10(("prof_name_matched: %s\n", name)); + return (match); + } + + /* check for match against directory globbing pattern */ + nvl = dir->sdev_prof.dev_glob_incdir; + while (nvp = nvlist_next_nvpair(nvl, nvp)) { + char *pathleft; + expr = nvpair_name(nvp); + if (gmatch(name, expr) == 0) + continue; + rv = nvpair_value_string(nvp, &pathleft); + if (rv != 0) { + cmn_err(CE_WARN, sdev_nvp_val_err, + rv, nvpair_name(nvp)); + break; + } + if (is_nonempty_dir(name, pathleft, dir)) { + sdcmn_err10(("prof_name_matched: dir %s\n", name)); + return (1); + } + } + + return (0); +} + +static void +walk_dir(struct vnode *dvp, void *arg, int (*callback)(char *, void *)) +{ + char *nm; + int eof, error; + struct iovec iov; + struct uio uio; + struct dirent64 *dp; + dirent64_t *dbuf; + size_t dbuflen, dlen; + + ASSERT(dvp); + + dlen = 4096; + dbuf = kmem_zalloc(dlen, KM_SLEEP); + + uio.uio_iov = &iov; + uio.uio_iovcnt = 1; + uio.uio_segflg = UIO_SYSSPACE; + uio.uio_fmode = 0; + uio.uio_extflg = UIO_COPY_CACHED; + uio.uio_loffset = 0; + uio.uio_llimit = MAXOFFSET_T; + + eof = 0; + error = 0; + while (!error && !eof) { + uio.uio_resid = dlen; + iov.iov_base = (char *)dbuf; + iov.iov_len = dlen; + (void) VOP_RWLOCK(dvp, V_WRITELOCK_FALSE, NULL); + error = VOP_READDIR(dvp, &uio, kcred, &eof); + VOP_RWUNLOCK(dvp, V_WRITELOCK_FALSE, NULL); + + dbuflen = dlen - uio.uio_resid; + if (error || dbuflen == 0) + break; + for (dp = dbuf; ((intptr_t)dp < + (intptr_t)dbuf + dbuflen); + dp = (dirent64_t *)((intptr_t)dp + dp->d_reclen)) { + nm = dp->d_name; + + if (strcmp(nm, ".") == 0 || + strcmp(nm, "..") == 0) + continue; + + if (callback(nm, arg) == WALK_DIR_TERMINATE) + goto end; + } + } + +end: + kmem_free(dbuf, dlen); +} + +static int +prof_make_name(char *nm, void *arg) +{ + struct sdev_node *ddv = (struct sdev_node *)arg; + + if (prof_name_matched(nm, ddv)) + prof_lookup_globaldev(ddv, ddv->sdev_origin, nm, nm); + return (WALK_DIR_CONTINUE); +} + +static void +prof_make_names_glob(struct sdev_node *ddv) +{ + struct sdev_node *gdir; + + gdir = ddv->sdev_origin; + if (gdir == NULL) + return; + walk_dir(SDEVTOV(gdir), (void *)ddv, prof_make_name); +} + +static void +prof_make_names(struct sdev_node *dir) +{ + char *name; + nvpair_t *nvp = NULL; + nvlist_t *nvl = dir->sdev_prof.dev_name; + int rv; + + ASSERT(RW_WRITE_HELD(&dir->sdev_contents)); + + if (nvl == NULL) + return; + + if (dir->sdev_prof.has_glob) { + prof_make_names_glob(dir); + return; + } + + /* Walk nvlist and lookup corresponding device in global inst */ + while (nvp = nvlist_next_nvpair(nvl, nvp)) { + int type; + rv = nvpair_value_int32(nvp, &type); + if (rv != 0) { + cmn_err(CE_WARN, sdev_nvp_val_err, + rv, nvpair_name(nvp)); + break; + } + if (type == PROFILE_TYPE_EXCLUDE) + continue; + name = nvpair_name(nvp); + (void) prof_lookup_globaldev(dir, dir->sdev_origin, + name, name); + } +} + +/* + * Build directory vnodes based on the profile and the global + * dev instance. + */ +void +prof_filldir(struct sdev_node *ddv) +{ + int firsttime = 1; + struct sdev_node *gdir = ddv->sdev_origin; + + ASSERT(RW_READ_HELD(&ddv->sdev_contents)); + + /* + * We need to rebuild the directory content if + * - SDEV_BUILD is set + * - The device tree generation number has changed + * - The corresponding /dev namespace has been updated + */ +check_build: + if ((ddv->sdev_flags & SDEV_BUILD) == 0 && + ddv->sdev_devtree_gen == devtree_gen && + (gdir == NULL || ddv->sdev_ldir_gen + == gdir->sdev_gdir_gen)) + return; /* already up to date */ + + if (firsttime && rw_tryupgrade(&ddv->sdev_contents) == 0) { + rw_exit(&ddv->sdev_contents); + firsttime = 0; + rw_enter(&ddv->sdev_contents, RW_WRITER); + goto check_build; + } + sdcmn_err10(("devtree_gen (%s): %ld -> %ld\n", + ddv->sdev_path, ddv->sdev_devtree_gen, devtree_gen)); + if (gdir) + sdcmn_err10(("sdev_dir_gen (%s): %ld -> %ld\n", + ddv->sdev_path, ddv->sdev_ldir_gen, + gdir->sdev_gdir_gen)); + + /* update flags and generation number so next filldir is quick */ + ddv->sdev_flags &= ~SDEV_BUILD; + ddv->sdev_devtree_gen = devtree_gen; + if (gdir) + ddv->sdev_ldir_gen = gdir->sdev_gdir_gen; + + prof_make_symlinks(ddv); + prof_make_maps(ddv); + prof_make_names(ddv); + rw_downgrade(&ddv->sdev_contents); +} + +/* apply include/exclude pattern to existing directory content */ +static void +apply_dir_pattern(struct sdev_node *dir, char *expr, char *pathleft, int type) +{ + struct sdev_node *dv; + + /* leaf pattern */ + if (pathleft == NULL) { + if (type == PROFILE_TYPE_INCLUDE) + return; /* nothing to do for include */ + (void) sdev_cleandir(dir, expr, SDEV_ENFORCE); + return; + } + + /* directory pattern */ + rw_enter(&dir->sdev_contents, RW_WRITER); + for (dv = dir->sdev_dot; dv; dv = dv->sdev_next) { + if (gmatch(dv->sdev_name, expr) == 0 || + SDEVTOV(dv)->v_type != VDIR) + continue; + process_rule(dv, dv->sdev_origin, + pathleft, NULL, type); + } + rw_exit(&dir->sdev_contents); +} + +/* + * Add a profile rule. + * tgt represents a device name matching expression, + * matching device names are to be either included or excluded. + */ +static void +prof_add_rule(char *name, char *tgt, struct sdev_node *dir, int type) +{ + int error; + nvlist_t **nvlp = NULL; + int rv; + + ASSERT(SDEVTOV(dir)->v_type == VDIR); + + rw_enter(&dir->sdev_contents, RW_WRITER); + + switch (type) { + case PROFILE_TYPE_INCLUDE: + if (tgt) + nvlp = &(dir->sdev_prof.dev_glob_incdir); + else + nvlp = &(dir->sdev_prof.dev_name); + break; + case PROFILE_TYPE_EXCLUDE: + if (tgt) + nvlp = &(dir->sdev_prof.dev_glob_excdir); + else + nvlp = &(dir->sdev_prof.dev_name); + break; + case PROFILE_TYPE_MAP: + nvlp = &(dir->sdev_prof.dev_map); + break; + case PROFILE_TYPE_SYMLINK: + nvlp = &(dir->sdev_prof.dev_symlink); + break; + }; + + /* initialize nvlist */ + if (*nvlp == NULL) { + error = nvlist_alloc(nvlp, NV_UNIQUE_NAME, KM_SLEEP); + ASSERT(error == 0); + } + + if (tgt) { + rv = nvlist_add_string(*nvlp, name, tgt); + } else { + rv = nvlist_add_int32(*nvlp, name, type); + } + ASSERT(rv == 0); + /* rebuild directory content */ + dir->sdev_flags |= SDEV_BUILD; + + if ((type == PROFILE_TYPE_INCLUDE) && + (strpbrk(name, "*?[]") != NULL)) { + dir->sdev_prof.has_glob = 1; + } + + rw_exit(&dir->sdev_contents); + + /* additional details for glob pattern and exclusion */ + switch (type) { + case PROFILE_TYPE_INCLUDE: + case PROFILE_TYPE_EXCLUDE: + apply_dir_pattern(dir, name, tgt, type); + break; + }; +} + +/* + * Parse path components and apply requested matching rule at + * directory level. + */ +static void +process_rule(struct sdev_node *dir, struct sdev_node *gdir, + char *path, char *tgt, int type) +{ + char *name; + struct pathname pn; + int rv = 0; + + if ((strlen(path) > 5) && (strncmp(path, "/dev/", 5) == 0)) { + path += 5; + } + + if (pn_get(path, UIO_SYSSPACE, &pn) != 0) + return; + + name = kmem_alloc(MAXPATHLEN, KM_SLEEP); + (void) pn_getcomponent(&pn, name); + pn_skipslash(&pn); + SDEV_HOLD(dir); + + while (pn_pathleft(&pn)) { + /* If this is pattern, just add the pattern */ + if (strpbrk(name, "*?[]") != NULL && + (type == PROFILE_TYPE_INCLUDE || + type == PROFILE_TYPE_EXCLUDE)) { + ASSERT(tgt == NULL); + tgt = pn.pn_path; + break; + } + if ((rv = prof_make_dir(name, &gdir, &dir)) != 0) { + cmn_err(CE_CONT, "process_rule: %s error %d\n", + path, rv); + break; + } + (void) pn_getcomponent(&pn, name); + pn_skipslash(&pn); + } + + /* process the leaf component */ + if (rv == 0) { + prof_add_rule(name, tgt, dir, type); + SDEV_SIMPLE_RELE(dir); + } + + kmem_free(name, MAXPATHLEN); + pn_free(&pn); +} + +static int +copyin_nvlist(char *packed_usr, size_t packed_sz, nvlist_t **nvlp) +{ + int err = 0; + char *packed; + nvlist_t *profile = NULL; + + /* simple sanity check */ + if (packed_usr == NULL || packed_sz == 0) + return (NULL); + + /* copyin packed profile nvlist */ + packed = kmem_alloc(packed_sz, KM_NOSLEEP); + if (packed == NULL) + return (ENOMEM); + err = copyin(packed_usr, packed, packed_sz); + + /* unpack packed profile nvlist */ + if (err) + cmn_err(CE_WARN, "copyin_nvlist: copyin failed with " + "err %d\n", err); + else if (err = nvlist_unpack(packed, packed_sz, &profile, KM_NOSLEEP)) + cmn_err(CE_WARN, "copyin_nvlist: nvlist_unpack " + "failed with err %d\n", err); + + kmem_free(packed, packed_sz); + if (err == 0) + *nvlp = profile; + return (err); +} + +/* + * Process profile passed down from libdevinfo. There are four types + * of matching rules: + * include: export a name or names matching a pattern + * exclude: exclude a name or names matching a pattern + * symlink: create a local symlink + * map: export a device with a name different from the global zone + * Note: We may consider supporting VOP_SYMLINK in non-global instances, + * because it does not present any security risk. For now, the fs + * instance is read only. + */ +static void +sdev_process_profile(struct sdev_data *sdev_data, nvlist_t *profile) +{ + nvpair_t *nvpair; + char *nvname, *dname; + struct sdev_node *dir, *gdir; + char **pair; /* for symlinks and maps */ + uint_t nelem; + int rv; + + gdir = sdev_origins->sdev_root; /* root of global /dev */ + dir = sdev_data->sdev_root; /* root of current instance */ + + ASSERT(profile); + + /* process nvpairs in the list */ + nvpair = NULL; + while (nvpair = nvlist_next_nvpair(profile, nvpair)) { + nvname = nvpair_name(nvpair); + ASSERT(nvname != NULL); + + if (strcmp(nvname, SDEV_NVNAME_INCLUDE) == 0) { + rv = nvpair_value_string(nvpair, &dname); + if (rv != 0) { + cmn_err(CE_WARN, sdev_nvp_val_err, + rv, nvpair_name(nvpair)); + break; + } + process_rule(dir, gdir, dname, NULL, + PROFILE_TYPE_INCLUDE); + } else if (strcmp(nvname, SDEV_NVNAME_EXCLUDE) == 0) { + rv = nvpair_value_string(nvpair, &dname); + if (rv != 0) { + cmn_err(CE_WARN, sdev_nvp_val_err, + rv, nvpair_name(nvpair)); + break; + } + process_rule(dir, gdir, dname, NULL, + PROFILE_TYPE_EXCLUDE); + } else if (strcmp(nvname, SDEV_NVNAME_SYMLINK) == 0) { + rv = nvpair_value_string_array(nvpair, &pair, &nelem); + if (rv != 0) { + cmn_err(CE_WARN, sdev_nvp_val_err, + rv, nvpair_name(nvpair)); + break; + } + ASSERT(nelem == 2); + process_rule(dir, gdir, pair[0], pair[1], + PROFILE_TYPE_SYMLINK); + } else if (strcmp(nvname, SDEV_NVNAME_MAP) == 0) { + rv = nvpair_value_string_array(nvpair, &pair, &nelem); + if (rv != 0) { + cmn_err(CE_WARN, sdev_nvp_val_err, + rv, nvpair_name(nvpair)); + break; + } + process_rule(dir, gdir, pair[1], pair[0], + PROFILE_TYPE_MAP); + } else if (strcmp(nvname, SDEV_NVNAME_MOUNTPT) != 0) { + cmn_err(CE_WARN, "sdev_process_profile: invalid " + "nvpair %s\n", nvname); + } + } +} + +/*ARGSUSED*/ +int +prof_lookup(vnode_t *dvp, char *nm, struct vnode **vpp, struct cred *cred) +{ + struct sdev_node *ddv = VTOSDEV(dvp); + struct sdev_node *dv; + int nmlen; + + /* + * 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_READER); + dv = sdev_cache_lookup(ddv, nm); + if (dv == NULL) { + prof_filldir(ddv); + dv = sdev_cache_lookup(ddv, nm); + } + rw_exit(&ddv->sdev_contents); + if (dv == NULL) { + sdcmn_err10(("prof_lookup: %s not found\n", nm)); + return (ENOENT); + } + + return (sdev_to_vp(dv, vpp)); +} + +/* + * This is invoked after a new filesystem is mounted to define the + * name space. It is also invoked during normal system operation + * to update the name space. + * + * Applications call di_prof_commit() in libdevinfo, which invokes + * modctl(). modctl calls this function. The input is a packed nvlist. + */ +int +devname_profile_update(char *packed, size_t packed_sz) +{ + char *mntpt; + nvlist_t *nvl; + nvpair_t *nvp; + struct sdev_data *mntinfo; + int err; + int rv; + + nvl = NULL; + if ((err = copyin_nvlist(packed, packed_sz, &nvl)) != 0) + return (err); + ASSERT(nvl); + + /* The first nvpair must be the mount point */ + nvp = nvlist_next_nvpair(nvl, NULL); + if (strcmp(nvpair_name(nvp), SDEV_NVNAME_MOUNTPT) != 0) { + cmn_err(CE_NOTE, + "devname_profile_update: mount point not specified"); + nvlist_free(nvl); + return (EINVAL); + } + + /* find the matching filesystem instance */ + rv = nvpair_value_string(nvp, &mntpt); + if (rv != 0) { + cmn_err(CE_WARN, sdev_nvp_val_err, + rv, nvpair_name(nvp)); + } else { + mntinfo = sdev_find_mntinfo(mntpt); + if (mntinfo == NULL) { + cmn_err(CE_NOTE, "devname_profile_update: " + " mount point %s not found", mntpt); + nvlist_free(nvl); + return (EINVAL); + } + + /* now do the hardwork to process the profile */ + sdev_process_profile(mntinfo, nvl); + + sdev_mntinfo_rele(mntinfo); + } + + nvlist_free(nvl); + return (0); +} |