sdev_profile.c revision facf4a8d7b59fde89a8662b4f4c73a758e6c402c
/*
* 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);
}