/* 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 = SDEV_FIRST_ENTRY(dir); dv; dv = SDEV_NEXT_ENTRY(dir, dv)) { 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); }
/*ARGSUSED3*/ static int devpts_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 *sdvp = VTOSDEV(dvp); struct sdev_node *dv; struct vnode *rvp = NULL; int error; error = devname_lookup_func(sdvp, nm, vpp, cred, devpts_create_rvp, SDEV_VATTR); if (error == 0) { switch ((*vpp)->v_type) { case VCHR: dv = VTOSDEV(VTOS(*vpp)->s_realvp); ASSERT(VOP_REALVP(SDEVTOV(dv), &rvp, NULL) == ENOSYS); break; case VDIR: dv = VTOSDEV(*vpp); break; default: cmn_err(CE_PANIC, "devpts_lookup: Unsupported node " "type: %p: %d", (void *)(*vpp), (*vpp)->v_type); break; } ASSERT(SDEV_HELD(dv)); } return (error); }
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, NULL, NULL, NULL) != 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); }
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); } }
static int devpts_set_id(struct sdev_node *dv, struct vattr *vap, int protocol) { ASSERT((protocol & AT_UID) || (protocol & AT_GID)); ptms_set_owner(getminor(SDEVTOV(dv)->v_rdev), vap->va_uid, vap->va_gid); return (0); }
static void prof_make_names_walk(struct sdev_node *ddv, int (*cb)(char *, void *)) { struct sdev_node *gdir; gdir = ddv->sdev_origin; if (gdir == NULL) return; walk_dir(SDEVTOV(gdir), (void *)ddv, cb); }
/* * 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) { 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); 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. * Note, mapped device is persisted under the new name */ prof_getattr(dir, rename, 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); } }
/*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)); }
/* * Last chance for a zone to see a node. If our parent dir is * SDEV_ZONED, then we look up the "zone" property for the node. If the * property is found and matches the current zone name, we allow it. * Note that this isn't quite correct for the global zone peeking inside * a zone's /dev - for that to work, we'd have to have a per-dev-mount * zone ref squirreled away. */ static int prof_zone_matched(char *name, struct sdev_node *dir) { vnode_t *gvn = SDEVTOV(dir->sdev_origin); struct pathname pn; vnode_t *vn = NULL; char zonename[ZONENAME_MAX]; int znlen = ZONENAME_MAX; int ret; ASSERT((dir->sdev_flags & SDEV_ZONED) != 0); sdcmn_err10(("sdev_node %p is zoned, looking for %s\n", (void *)dir, name)); if (pn_get(name, UIO_SYSSPACE, &pn)) return (0); VN_HOLD(gvn); ret = lookuppnvp(&pn, NULL, FOLLOW, NULLVPP, &vn, rootdir, gvn, kcred); pn_free(&pn); if (ret != 0) { sdcmn_err10(("prof_zone_matched: %s not found\n", name)); return (0); } /* * VBLK doesn't matter, and the property name is in fact treated * as a const char *. */ ret = e_ddi_getlongprop_buf(vn->v_rdev, VBLK, (char *)"zone", DDI_PROP_NOTPROM | DDI_PROP_DONTPASS, (caddr_t)zonename, &znlen); VN_RELE(vn); if (ret == DDI_PROP_NOT_FOUND) { sdcmn_err10(("vnode %p: no zone prop\n", (void *)vn)); return (0); } else if (ret != DDI_PROP_SUCCESS) { sdcmn_err10(("vnode %p: zone prop error: %d\n", (void *)vn, ret)); return (0); } sdcmn_err10(("vnode %p zone prop: %s\n", (void *)vn, zonename)); return (strcmp(zonename, curproc->p_zone->zone_name) == 0); }
/* * 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; rw_exit(&dir->sdev_contents); SDEV_RELE(dir); 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, NULL, NULL, NULL); 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); }
/* * Clean pts sdev_nodes that are no longer valid. */ static void devpts_prunedir(struct sdev_node *ddv) { struct vnode *vp; struct sdev_node *dv, *next = NULL; int (*vtor)(struct sdev_node *) = NULL; ASSERT(ddv->sdev_flags & SDEV_VTOR); vtor = (int (*)(struct sdev_node *))sdev_get_vtor(ddv); ASSERT(vtor); 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 (vtor(dv)) { case SDEV_VTOR_VALID: case SDEV_VTOR_SKIP: continue; case SDEV_VTOR_INVALID: sdcmn_err7(("prunedir: destroy invalid " "node: %s(%p)\n", dv->sdev_name, (void *)dv)); break; } vp = SDEVTOV(dv); if (vp->v_count > 0) continue; SDEV_HOLD(dv); /* remove the cache node */ (void) sdev_cache_update(ddv, &dv, dv->sdev_name, SDEV_CACHE_DELETE); } rw_downgrade(&ddv->sdev_contents); }
/* * Lookup: filter out entries in the negative cache * Return 1 if the lookup should not cause a reconfig. */ int sdev_lookup_filter(sdev_node_t *dv, char *nm) { int n; sdev_nc_list_t *ncl = sdev_ncache; sdev_nc_node_t *lp; char *path; int rval = 0; int changed = 0; ASSERT(i_ddi_io_initialized()); ASSERT(SDEVTOV(dv)->v_type == VDIR); if (sdev_nc_disable) return (0); n = strlen(dv->sdev_path) + strlen(nm) + 2; path = kmem_alloc(n, KM_SLEEP); (void) sprintf(path, "%s/%s", dv->sdev_path, nm); rw_enter(&ncl->ncl_lock, RW_READER); if ((lp = sdev_nc_findpath(ncl, path)) != NULL) { sdcmn_err5(("%s/%s: lookup by %s cached, no reconfig\n", dv->sdev_name, nm, curproc->p_user.u_comm)); if (sdev_nc_verbose) { cmn_err(CE_CONT, "?%s/%s: lookup by %s cached, no reconfig\n", dv->sdev_name, nm, curproc->p_user.u_comm); } mutex_enter(&ncl->ncl_mutex); lp->ncn_flags |= NCN_ACTIVE; if (sdev_nc_expirecnt > 0 && lp->ncn_expirecnt > 0 && lp->ncn_expirecnt < sdev_nc_expirecnt) { lp->ncn_expirecnt = sdev_nc_expirecnt; ncl->ncl_flags |= NCL_LIST_DIRTY; changed = 1; } mutex_exit(&ncl->ncl_mutex); rval = 1; } rw_exit(&ncl->ncl_lock); kmem_free(path, n); if (changed) sdev_nc_flush_boot_update(); return (rval); }
/*ARGSUSED3*/ static int devvt_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 *sdvp = VTOSDEV(dvp); struct sdev_node *dv; struct vnode *rvp = NULL; int type, error; if ((strcmp(nm, DEVVT_ACTIVE_NAME) == 0) || (strcmp(nm, DEVVT_CONSUSER_NAME) == 0)) { type = SDEV_VLINK; } else { type = SDEV_VATTR; } /* Give warlock a more clear call graph */ #ifndef __lock_lint error = devname_lookup_func(sdvp, nm, vpp, cred, devvt_create_rvp, type); #else devvt_create_rvp(0, 0, 0, 0, 0, 0); #endif if (error == 0) { switch ((*vpp)->v_type) { case VCHR: dv = VTOSDEV(VTOS(*vpp)->s_realvp); ASSERT(VOP_REALVP(SDEVTOV(dv), &rvp, NULL) == ENOSYS); break; case VDIR: case VLNK: dv = VTOSDEV(*vpp); break; default: cmn_err(CE_PANIC, "devvt_lookup: Unsupported node " "type: %p: %d", (void *)(*vpp), (*vpp)->v_type); break; } ASSERT(SDEV_HELD(dv)); } return (error); }
/* * 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); } 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); } else { sdev_cache_update(dir, &dv, name, SDEV_CACHE_DELETE); SDEV_RELE(dv); } return (rv); }
/* * First step in refreshing directory contents. * Remove each invalid entry and rebuild the link * reference for each stale entry. */ static void devvt_prunedir(struct sdev_node *ddv) { struct vnode *vp; struct sdev_node *dv, *next = NULL; int (*vtor)(struct sdev_node *) = NULL; ASSERT(ddv->sdev_flags & SDEV_VTOR); vtor = (int (*)(struct sdev_node *))sdev_get_vtor(ddv); ASSERT(vtor); for (dv = SDEV_FIRST_ENTRY(ddv); dv; dv = next) { next = SDEV_NEXT_ENTRY(ddv, dv); switch (vtor(dv)) { case SDEV_VTOR_VALID: break; case SDEV_VTOR_SKIP: break; case SDEV_VTOR_INVALID: vp = SDEVTOV(dv); if (vp->v_count != 0) break; /* remove the cached node */ SDEV_HOLD(dv); (void) sdev_cache_update(ddv, &dv, dv->sdev_name, SDEV_CACHE_DELETE); SDEV_RELE(dv); break; case SDEV_VTOR_STALE: devvt_rebuild_stale_link(ddv, dv); break; } } }
/* * 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; }; }