xref: /illumos-gate/usr/src/uts/common/fs/dev/sdev_profile.c (revision bfed486ad8de8b8ebc6345a8e10accae08bf2f45)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * This file implements /dev filesystem operations for non-global
31  * instances. Three major entry points:
32  * devname_profile_update()
33  *   Update matching rules determining which names to export
34  * prof_readdir()
35  *   Return the list of exported names
36  * prof_lookup()
37  *   Implements lookup
38  */
39 
40 #include <sys/types.h>
41 #include <sys/param.h>
42 #include <sys/sysmacros.h>
43 #include <sys/vnode.h>
44 #include <sys/uio.h>
45 #include <sys/dirent.h>
46 #include <sys/pathname.h>
47 #include <sys/fs/dv_node.h>
48 #include <sys/fs/sdev_impl.h>
49 #include <sys/sunndi.h>
50 #include <sys/modctl.h>
51 
52 enum {
53 	PROFILE_TYPE_INCLUDE,
54 	PROFILE_TYPE_EXCLUDE,
55 	PROFILE_TYPE_MAP,
56 	PROFILE_TYPE_SYMLINK
57 };
58 
59 enum {
60 	WALK_DIR_CONTINUE = 0,
61 	WALK_DIR_TERMINATE
62 };
63 
64 static const char *sdev_nvp_val_err = "nvpair_value error %d, %s\n";
65 
66 static void process_rule(struct sdev_node *, struct sdev_node *,
67     char *, char *, int);
68 static void walk_dir(struct vnode *, void *, int (*)(char *, void *));
69 
70 static void
71 prof_getattr(struct sdev_node *dir, char *name, struct vnode *gdv,
72     struct vattr *vap, struct vnode **avpp, int *no_fs_perm)
73 {
74 	struct vnode *advp;
75 
76 	/* get attribute from shadow, if present; else get default */
77 	advp = dir->sdev_attrvp;
78 	if (advp && VOP_LOOKUP(advp, name, avpp, NULL, 0, NULL, kcred,
79 	    NULL, NULL, NULL) == 0) {
80 		(void) VOP_GETATTR(*avpp, vap, 0, kcred, NULL);
81 	} else if (gdv == NULL || gdv->v_type == VDIR) {
82 		/* always create shadow directory */
83 		*vap = sdev_vattr_dir;
84 		if (advp && VOP_MKDIR(advp, name, &sdev_vattr_dir,
85 		    avpp, kcred, NULL, 0, NULL) != 0) {
86 			*avpp = NULLVP;
87 			sdcmn_err10(("prof_getattr: failed to create "
88 			    "shadow directory %s/%s\n", dir->sdev_path, name));
89 		}
90 	} else {
91 		/*
92 		 * get default permission from devfs
93 		 * Before calling devfs_get_defattr, we need to get
94 		 * the realvp (the dv_node). If realvp is not a dv_node,
95 		 * devfs_get_defattr() will return a system-wide default
96 		 * attr for device nodes.
97 		 */
98 		struct vnode *rvp;
99 		if (VOP_REALVP(gdv, &rvp, NULL) != 0)
100 			rvp = gdv;
101 		devfs_get_defattr(rvp, vap, no_fs_perm);
102 		*avpp = NULLVP;
103 	}
104 
105 	/* ignore dev_t and vtype from backing store */
106 	if (gdv) {
107 		vap->va_type = gdv->v_type;
108 		vap->va_rdev = gdv->v_rdev;
109 	}
110 }
111 
112 static void
113 apply_glob_pattern(struct sdev_node *pdir, struct sdev_node *cdir)
114 {
115 	char *name;
116 	nvpair_t *nvp = NULL;
117 	nvlist_t *nvl;
118 	struct vnode *vp = SDEVTOV(cdir);
119 	int rv = 0;
120 
121 	if (vp->v_type != VDIR)
122 		return;
123 	name = cdir->sdev_name;
124 	nvl = pdir->sdev_prof.dev_glob_incdir;
125 	while (nvp = nvlist_next_nvpair(nvl, nvp)) {
126 		char *pathleft;
127 		char *expr = nvpair_name(nvp);
128 		if (!gmatch(name, expr))
129 			continue;
130 		rv = nvpair_value_string(nvp, &pathleft);
131 		if (rv != 0) {
132 			cmn_err(CE_WARN, sdev_nvp_val_err,
133 			    rv, nvpair_name(nvp));
134 			break;
135 		}
136 		process_rule(cdir, cdir->sdev_origin,
137 		    pathleft, NULL, PROFILE_TYPE_INCLUDE);
138 	}
139 }
140 
141 /*
142  * Some commonality here with sdev_mknode(), could be simplified.
143  * NOTE: prof_mknode returns with *newdv held once, if success.
144  */
145 static int
146 prof_mknode(struct sdev_node *dir, char *name, struct sdev_node **newdv,
147     vattr_t *vap, vnode_t *avp, void *arg, cred_t *cred)
148 {
149 	struct sdev_node *dv;
150 	int rv;
151 
152 	ASSERT(RW_WRITE_HELD(&dir->sdev_contents));
153 
154 	/* check cache first */
155 	if (dv = sdev_cache_lookup(dir, name)) {
156 		*newdv = dv;
157 		return (0);
158 	}
159 
160 	/* allocate node and insert into cache */
161 	rv = sdev_nodeinit(dir, name, &dv, NULL);
162 	if (rv != 0) {
163 		*newdv = NULL;
164 		return (rv);
165 	}
166 
167 	rv = sdev_cache_update(dir, &dv, name, SDEV_CACHE_ADD);
168 	*newdv = dv;
169 
170 	/* put it in ready state */
171 	rv = sdev_nodeready(*newdv, vap, avp, arg, cred);
172 
173 	/* handle glob pattern in the middle of a path */
174 	if (rv == 0) {
175 		if (SDEVTOV(*newdv)->v_type == VDIR)
176 			sdcmn_err10(("sdev_origin for %s set to 0x%p\n",
177 			    name, arg));
178 		apply_glob_pattern(dir, *newdv);
179 	}
180 	return (rv);
181 }
182 
183 /*
184  * Create a directory node in a non-global dev instance.
185  * Always create shadow vnode. Set sdev_origin to the corresponding
186  * global directory sdev_node if it exists. This facilitates the
187  * lookup operation.
188  */
189 static int
190 prof_make_dir(char *name, struct sdev_node **gdirp, struct sdev_node **dirp)
191 {
192 	struct sdev_node *dir = *dirp;
193 	struct sdev_node *gdir = *gdirp;
194 	struct sdev_node *newdv;
195 	struct vnode *avp, *gnewdir = NULL;
196 	struct vattr vattr;
197 	int error;
198 
199 	/* see if name already exists */
200 	rw_enter(&dir->sdev_contents, RW_READER);
201 	if (newdv = sdev_cache_lookup(dir, name)) {
202 		*dirp = newdv;
203 		*gdirp = newdv->sdev_origin;
204 		SDEV_RELE(dir);
205 		rw_exit(&dir->sdev_contents);
206 		return (0);
207 	}
208 	rw_exit(&dir->sdev_contents);
209 
210 	/* find corresponding dir node in global dev */
211 	if (gdir) {
212 		error = VOP_LOOKUP(SDEVTOV(gdir), name, &gnewdir,
213 		    NULL, 0, NULL, kcred, NULL, NULL, NULL);
214 		if (error == 0) {
215 			*gdirp = VTOSDEV(gnewdir);
216 		} else { 	/* it's ok if there no global dir */
217 			*gdirp = NULL;
218 		}
219 	}
220 
221 	/* get attribute from shadow, also create shadow dir */
222 	prof_getattr(dir, name, gnewdir, &vattr, &avp, NULL);
223 
224 	/* create dev directory vnode */
225 	rw_enter(&dir->sdev_contents, RW_WRITER);
226 	error = prof_mknode(dir, name, &newdv, &vattr, avp, (void *)*gdirp,
227 	    kcred);
228 	rw_exit(&dir->sdev_contents);
229 	if (error == 0) {
230 		ASSERT(newdv);
231 		*dirp = newdv;
232 	}
233 	SDEV_RELE(dir);
234 	return (error);
235 }
236 
237 /*
238  * Look up a logical name in the global zone.
239  * Provides the ability to map the global zone's device name
240  * to an alternate name within a zone.  The primary example
241  * is the virtual console device /dev/zcons/[zonename]/zconsole
242  * mapped to /[zonename]/root/dev/zconsole.
243  */
244 static void
245 prof_lookup_globaldev(struct sdev_node *dir, struct sdev_node *gdir,
246     char *name, char *rename)
247 {
248 	/* global OS rootdir */
249 	extern vnode_t *rootdir;
250 
251 	int error;
252 	struct vnode *avp, *gdv, *gddv;
253 	struct sdev_node *newdv;
254 	struct vattr vattr = {0};
255 	struct pathname pn;
256 
257 	/* check if node already exists */
258 	newdv = sdev_cache_lookup(dir, rename);
259 	if (newdv) {
260 		ASSERT(newdv->sdev_state != SDEV_ZOMBIE);
261 		SDEV_SIMPLE_RELE(newdv);
262 		return;
263 	}
264 
265 	/* sanity check arguments */
266 	if (!gdir || pn_get(name, UIO_SYSSPACE, &pn))
267 		return;
268 
269 	/* perform a relative lookup of the global /dev instance */
270 	gddv = SDEVTOV(gdir);
271 	VN_HOLD(gddv);
272 	VN_HOLD(rootdir);
273 	error = lookuppnvp(&pn, NULL, FOLLOW, NULLVPP, &gdv,
274 	    rootdir, gddv, kcred);
275 	pn_free(&pn);
276 	if (error) {
277 		sdcmn_err10(("prof_lookup_globaldev: %s not found\n", name));
278 		return;
279 	}
280 	ASSERT(gdv && gdv->v_type != VLNK);
281 
282 	/*
283 	 * Found the entry in global /dev, figure out attributes
284 	 * by looking at backing store. Call into devfs for default.
285 	 * Note, mapped device is persisted under the new name
286 	 */
287 	prof_getattr(dir, rename, gdv, &vattr, &avp, NULL);
288 
289 	if (gdv->v_type != VDIR) {
290 		VN_RELE(gdv);
291 		gdir = NULL;
292 	} else
293 		gdir = VTOSDEV(gdv);
294 
295 	if (prof_mknode(dir, rename, &newdv, &vattr, avp,
296 	    (void *)gdir, kcred) == 0) {
297 		ASSERT(newdv->sdev_state != SDEV_ZOMBIE);
298 		SDEV_SIMPLE_RELE(newdv);
299 	}
300 }
301 
302 static void
303 prof_make_sym(struct sdev_node *dir, char *lnm, char *tgt)
304 {
305 	struct sdev_node *newdv;
306 
307 	if (prof_mknode(dir, lnm, &newdv, &sdev_vattr_lnk, NULL,
308 	    (void *)tgt, kcred) == 0) {
309 		ASSERT(newdv->sdev_state != SDEV_ZOMBIE);
310 		SDEV_SIMPLE_RELE(newdv);
311 	}
312 }
313 
314 /*
315  * Create symlinks in the current directory based on profile
316  */
317 static void
318 prof_make_symlinks(struct sdev_node *dir)
319 {
320 	char *tgt, *lnm;
321 	nvpair_t *nvp = NULL;
322 	nvlist_t *nvl = dir->sdev_prof.dev_symlink;
323 	int rv;
324 
325 	ASSERT(RW_WRITE_HELD(&dir->sdev_contents));
326 
327 	if (nvl == NULL)
328 		return;
329 
330 	while (nvp = nvlist_next_nvpair(nvl, nvp)) {
331 		lnm = nvpair_name(nvp);
332 		rv = nvpair_value_string(nvp, &tgt);
333 		if (rv != 0) {
334 			cmn_err(CE_WARN, sdev_nvp_val_err,
335 			    rv, nvpair_name(nvp));
336 			break;
337 		}
338 		prof_make_sym(dir, lnm, tgt);
339 	}
340 }
341 
342 static void
343 prof_make_maps(struct sdev_node *dir)
344 {
345 	nvpair_t *nvp = NULL;
346 	nvlist_t *nvl = dir->sdev_prof.dev_map;
347 	int rv;
348 
349 	ASSERT(RW_WRITE_HELD(&dir->sdev_contents));
350 
351 	if (nvl == NULL)
352 		return;
353 
354 	while (nvp = nvlist_next_nvpair(nvl, nvp)) {
355 		char *name;
356 		char *rename = nvpair_name(nvp);
357 		rv = nvpair_value_string(nvp, &name);
358 		if (rv != 0) {
359 			cmn_err(CE_WARN, sdev_nvp_val_err,
360 			    rv, nvpair_name(nvp));
361 			break;
362 		}
363 		sdcmn_err10(("map %s -> %s\n", name, rename));
364 		(void) prof_lookup_globaldev(dir, sdev_origins->sdev_root,
365 		    name, rename);
366 	}
367 }
368 
369 struct match_arg {
370 	char *expr;
371 	int match;
372 };
373 
374 static int
375 match_name(char *name, void *arg)
376 {
377 	struct match_arg *margp = (struct match_arg *)arg;
378 
379 	if (gmatch(name, margp->expr)) {
380 		margp->match = 1;
381 		return (WALK_DIR_TERMINATE);
382 	}
383 	return (WALK_DIR_CONTINUE);
384 }
385 
386 static int
387 is_nonempty_dir(char *name, char *pathleft, struct sdev_node *dir)
388 {
389 	struct match_arg marg;
390 	struct pathname pn;
391 	struct vnode *gvp;
392 	struct sdev_node *gdir = dir->sdev_origin;
393 
394 	if (VOP_LOOKUP(SDEVTOV(gdir), name, &gvp, NULL, 0, NULL, kcred,
395 	    NULL, NULL, NULL) != 0)
396 		return (0);
397 
398 	if (gvp->v_type != VDIR) {
399 		VN_RELE(gvp);
400 		return (0);
401 	}
402 
403 	if (pn_get(pathleft, UIO_SYSSPACE, &pn) != 0) {
404 		VN_RELE(gvp);
405 		return (0);
406 	}
407 
408 	marg.expr = kmem_alloc(MAXNAMELEN, KM_SLEEP);
409 	(void) pn_getcomponent(&pn, marg.expr);
410 	marg.match = 0;
411 
412 	walk_dir(gvp, &marg, match_name);
413 	VN_RELE(gvp);
414 	kmem_free(marg.expr, MAXNAMELEN);
415 	pn_free(&pn);
416 
417 	return (marg.match);
418 }
419 
420 
421 /* Check if name passes matching rules */
422 static int
423 prof_name_matched(char *name, struct sdev_node *dir)
424 {
425 	int type, match = 0;
426 	char *expr;
427 	nvlist_t *nvl;
428 	nvpair_t *nvp = NULL;
429 	int rv;
430 
431 	/* check against nvlist for leaf include/exclude */
432 	nvl = dir->sdev_prof.dev_name;
433 	while (nvp = nvlist_next_nvpair(nvl, nvp)) {
434 		expr = nvpair_name(nvp);
435 		rv = nvpair_value_int32(nvp, &type);
436 		if (rv != 0) {
437 			cmn_err(CE_WARN, sdev_nvp_val_err,
438 			    rv, nvpair_name(nvp));
439 			break;
440 		}
441 
442 		if (type == PROFILE_TYPE_EXCLUDE) {
443 			if (gmatch(name, expr))
444 				return (0);	/* excluded */
445 		} else if (!match) {
446 			match = gmatch(name, expr);
447 		}
448 	}
449 	if (match) {
450 		sdcmn_err10(("prof_name_matched: %s\n", name));
451 		return (match);
452 	}
453 
454 	/* check for match against directory globbing pattern */
455 	nvl = dir->sdev_prof.dev_glob_incdir;
456 	while (nvp = nvlist_next_nvpair(nvl, nvp)) {
457 		char *pathleft;
458 		expr = nvpair_name(nvp);
459 		if (gmatch(name, expr) == 0)
460 			continue;
461 		rv = nvpair_value_string(nvp, &pathleft);
462 		if (rv != 0) {
463 			cmn_err(CE_WARN, sdev_nvp_val_err,
464 			    rv, nvpair_name(nvp));
465 			break;
466 		}
467 		if (is_nonempty_dir(name, pathleft, dir)) {
468 			sdcmn_err10(("prof_name_matched: dir %s\n", name));
469 			return (1);
470 		}
471 	}
472 
473 	return (0);
474 }
475 
476 static void
477 walk_dir(struct vnode *dvp, void *arg, int (*callback)(char *, void *))
478 {
479 	char    *nm;
480 	int eof, error;
481 	struct iovec iov;
482 	struct uio uio;
483 	struct dirent64 *dp;
484 	dirent64_t *dbuf;
485 	size_t dbuflen, dlen;
486 
487 	ASSERT(dvp);
488 
489 	dlen = 4096;
490 	dbuf = kmem_zalloc(dlen, KM_SLEEP);
491 
492 	uio.uio_iov = &iov;
493 	uio.uio_iovcnt = 1;
494 	uio.uio_segflg = UIO_SYSSPACE;
495 	uio.uio_fmode = 0;
496 	uio.uio_extflg = UIO_COPY_CACHED;
497 	uio.uio_loffset = 0;
498 	uio.uio_llimit = MAXOFFSET_T;
499 
500 	eof = 0;
501 	error = 0;
502 	while (!error && !eof) {
503 		uio.uio_resid = dlen;
504 		iov.iov_base = (char *)dbuf;
505 		iov.iov_len = dlen;
506 		(void) VOP_RWLOCK(dvp, V_WRITELOCK_FALSE, NULL);
507 		error = VOP_READDIR(dvp, &uio, kcred, &eof, NULL, 0);
508 		VOP_RWUNLOCK(dvp, V_WRITELOCK_FALSE, NULL);
509 
510 		dbuflen = dlen - uio.uio_resid;
511 		if (error || dbuflen == 0)
512 			break;
513 		for (dp = dbuf; ((intptr_t)dp <
514 		    (intptr_t)dbuf + dbuflen);
515 		    dp = (dirent64_t *)((intptr_t)dp + dp->d_reclen)) {
516 			nm = dp->d_name;
517 
518 			if (strcmp(nm, ".") == 0 ||
519 			    strcmp(nm, "..") == 0)
520 				continue;
521 
522 			if (callback(nm, arg) == WALK_DIR_TERMINATE)
523 				goto end;
524 		}
525 	}
526 
527 end:
528 	kmem_free(dbuf, dlen);
529 }
530 
531 static int
532 prof_make_name(char *nm, void *arg)
533 {
534 	struct sdev_node *ddv = (struct sdev_node *)arg;
535 
536 	if (prof_name_matched(nm, ddv))
537 		prof_lookup_globaldev(ddv, ddv->sdev_origin, nm, nm);
538 	return (WALK_DIR_CONTINUE);
539 }
540 
541 static void
542 prof_make_names_glob(struct sdev_node *ddv)
543 {
544 	struct sdev_node *gdir;
545 
546 	gdir = ddv->sdev_origin;
547 	if (gdir == NULL)
548 		return;
549 	walk_dir(SDEVTOV(gdir), (void *)ddv, prof_make_name);
550 }
551 
552 static void
553 prof_make_names(struct sdev_node *dir)
554 {
555 	char *name;
556 	nvpair_t *nvp = NULL;
557 	nvlist_t *nvl = dir->sdev_prof.dev_name;
558 	int rv;
559 
560 	ASSERT(RW_WRITE_HELD(&dir->sdev_contents));
561 
562 	if (nvl == NULL)
563 		return;
564 
565 	if (dir->sdev_prof.has_glob) {
566 		prof_make_names_glob(dir);
567 		return;
568 	}
569 
570 	/* Walk nvlist and lookup corresponding device in global inst */
571 	while (nvp = nvlist_next_nvpair(nvl, nvp)) {
572 		int type;
573 		rv = nvpair_value_int32(nvp, &type);
574 		if (rv != 0) {
575 			cmn_err(CE_WARN, sdev_nvp_val_err,
576 			    rv, nvpair_name(nvp));
577 			break;
578 		}
579 		if (type == PROFILE_TYPE_EXCLUDE)
580 			continue;
581 		name = nvpair_name(nvp);
582 		(void) prof_lookup_globaldev(dir, dir->sdev_origin,
583 		    name, name);
584 	}
585 }
586 
587 /*
588  * Build directory vnodes based on the profile and the global
589  * dev instance.
590  */
591 void
592 prof_filldir(struct sdev_node *ddv)
593 {
594 	int firsttime = 1;
595 	struct sdev_node *gdir = ddv->sdev_origin;
596 
597 	ASSERT(RW_READ_HELD(&ddv->sdev_contents));
598 
599 	/*
600 	 * We need to rebuild the directory content if
601 	 * - SDEV_BUILD is set
602 	 * - The device tree generation number has changed
603 	 * - The corresponding /dev namespace has been updated
604 	 */
605 check_build:
606 	if ((ddv->sdev_flags & SDEV_BUILD) == 0 &&
607 	    ddv->sdev_devtree_gen == devtree_gen &&
608 	    (gdir == NULL || ddv->sdev_ldir_gen
609 	    == gdir->sdev_gdir_gen))
610 		return;		/* already up to date */
611 
612 	if (firsttime && rw_tryupgrade(&ddv->sdev_contents) == 0) {
613 		rw_exit(&ddv->sdev_contents);
614 		firsttime = 0;
615 		rw_enter(&ddv->sdev_contents, RW_WRITER);
616 		goto check_build;
617 	}
618 	sdcmn_err10(("devtree_gen (%s): %ld -> %ld\n",
619 	    ddv->sdev_path, ddv->sdev_devtree_gen, devtree_gen));
620 	if (gdir)
621 		sdcmn_err10(("sdev_dir_gen (%s): %ld -> %ld\n",
622 		    ddv->sdev_path, ddv->sdev_ldir_gen,
623 		    gdir->sdev_gdir_gen));
624 
625 	/* update flags and generation number so next filldir is quick */
626 	ddv->sdev_flags &= ~SDEV_BUILD;
627 	ddv->sdev_devtree_gen = devtree_gen;
628 	if (gdir)
629 		ddv->sdev_ldir_gen = gdir->sdev_gdir_gen;
630 
631 	prof_make_symlinks(ddv);
632 	prof_make_maps(ddv);
633 	prof_make_names(ddv);
634 	rw_downgrade(&ddv->sdev_contents);
635 }
636 
637 /* apply include/exclude pattern to existing directory content */
638 static void
639 apply_dir_pattern(struct sdev_node *dir, char *expr, char *pathleft, int type)
640 {
641 	struct sdev_node *dv;
642 
643 	/* leaf pattern */
644 	if (pathleft == NULL) {
645 		if (type == PROFILE_TYPE_INCLUDE)
646 			return;	/* nothing to do for include */
647 		(void) sdev_cleandir(dir, expr, SDEV_ENFORCE);
648 		return;
649 	}
650 
651 	/* directory pattern */
652 	rw_enter(&dir->sdev_contents, RW_WRITER);
653 
654 	for (dv = SDEV_FIRST_ENTRY(dir); dv; dv = SDEV_NEXT_ENTRY(dir, dv)) {
655 		if (gmatch(dv->sdev_name, expr) == 0 ||
656 		    SDEVTOV(dv)->v_type != VDIR)
657 			continue;
658 		process_rule(dv, dv->sdev_origin,
659 		    pathleft, NULL, type);
660 	}
661 	rw_exit(&dir->sdev_contents);
662 }
663 
664 /*
665  * Add a profile rule.
666  * tgt represents a device name matching expression,
667  * matching device names are to be either included or excluded.
668  */
669 static void
670 prof_add_rule(char *name, char *tgt, struct sdev_node *dir, int type)
671 {
672 	int error;
673 	nvlist_t **nvlp = NULL;
674 	int rv;
675 
676 	ASSERT(SDEVTOV(dir)->v_type == VDIR);
677 
678 	rw_enter(&dir->sdev_contents, RW_WRITER);
679 
680 	switch (type) {
681 	case PROFILE_TYPE_INCLUDE:
682 		if (tgt)
683 			nvlp = &(dir->sdev_prof.dev_glob_incdir);
684 		else
685 			nvlp = &(dir->sdev_prof.dev_name);
686 		break;
687 	case PROFILE_TYPE_EXCLUDE:
688 		if (tgt)
689 			nvlp = &(dir->sdev_prof.dev_glob_excdir);
690 		else
691 			nvlp = &(dir->sdev_prof.dev_name);
692 		break;
693 	case PROFILE_TYPE_MAP:
694 		nvlp = &(dir->sdev_prof.dev_map);
695 		break;
696 	case PROFILE_TYPE_SYMLINK:
697 		nvlp = &(dir->sdev_prof.dev_symlink);
698 		break;
699 	};
700 
701 	/* initialize nvlist */
702 	if (*nvlp == NULL) {
703 		error = nvlist_alloc(nvlp, NV_UNIQUE_NAME, KM_SLEEP);
704 		ASSERT(error == 0);
705 	}
706 
707 	if (tgt) {
708 		rv = nvlist_add_string(*nvlp, name, tgt);
709 	} else {
710 		rv = nvlist_add_int32(*nvlp, name, type);
711 	}
712 	ASSERT(rv == 0);
713 	/* rebuild directory content */
714 	dir->sdev_flags |= SDEV_BUILD;
715 
716 	if ((type == PROFILE_TYPE_INCLUDE) &&
717 	    (strpbrk(name, "*?[]") != NULL)) {
718 			dir->sdev_prof.has_glob = 1;
719 	}
720 
721 	rw_exit(&dir->sdev_contents);
722 
723 	/* additional details for glob pattern and exclusion */
724 	switch (type) {
725 	case PROFILE_TYPE_INCLUDE:
726 	case PROFILE_TYPE_EXCLUDE:
727 		apply_dir_pattern(dir, name, tgt, type);
728 		break;
729 	};
730 }
731 
732 /*
733  * Parse path components and apply requested matching rule at
734  * directory level.
735  */
736 static void
737 process_rule(struct sdev_node *dir, struct sdev_node *gdir,
738     char *path, char *tgt, int type)
739 {
740 	char *name;
741 	struct pathname	pn;
742 	int rv = 0;
743 
744 	if ((strlen(path) > 5) && (strncmp(path, "/dev/", 5) == 0)) {
745 		path += 5;
746 	}
747 
748 	if (pn_get(path, UIO_SYSSPACE, &pn) != 0)
749 		return;
750 
751 	name = kmem_alloc(MAXPATHLEN, KM_SLEEP);
752 	(void) pn_getcomponent(&pn, name);
753 	pn_skipslash(&pn);
754 	SDEV_HOLD(dir);
755 
756 	while (pn_pathleft(&pn)) {
757 		/* If this is pattern, just add the pattern */
758 		if (strpbrk(name, "*?[]") != NULL &&
759 		    (type == PROFILE_TYPE_INCLUDE ||
760 		    type == PROFILE_TYPE_EXCLUDE)) {
761 			ASSERT(tgt == NULL);
762 			tgt = pn.pn_path;
763 			break;
764 		}
765 		if ((rv = prof_make_dir(name, &gdir, &dir)) != 0) {
766 			cmn_err(CE_CONT, "process_rule: %s error %d\n",
767 			    path, rv);
768 			break;
769 		}
770 		(void) pn_getcomponent(&pn, name);
771 		pn_skipslash(&pn);
772 	}
773 
774 	/* process the leaf component */
775 	if (rv == 0) {
776 		prof_add_rule(name, tgt, dir, type);
777 		SDEV_SIMPLE_RELE(dir);
778 	}
779 
780 	kmem_free(name, MAXPATHLEN);
781 	pn_free(&pn);
782 }
783 
784 static int
785 copyin_nvlist(char *packed_usr, size_t packed_sz, nvlist_t **nvlp)
786 {
787 	int err = 0;
788 	char *packed;
789 	nvlist_t *profile = NULL;
790 
791 	/* simple sanity check */
792 	if (packed_usr == NULL || packed_sz == 0)
793 		return (NULL);
794 
795 	/* copyin packed profile nvlist */
796 	packed = kmem_alloc(packed_sz, KM_NOSLEEP);
797 	if (packed == NULL)
798 		return (ENOMEM);
799 	err = copyin(packed_usr, packed, packed_sz);
800 
801 	/* unpack packed profile nvlist */
802 	if (err)
803 		cmn_err(CE_WARN, "copyin_nvlist: copyin failed with "
804 		    "err %d\n", err);
805 	else if (err = nvlist_unpack(packed, packed_sz, &profile, KM_NOSLEEP))
806 		cmn_err(CE_WARN, "copyin_nvlist: nvlist_unpack "
807 		    "failed with err %d\n", err);
808 
809 	kmem_free(packed, packed_sz);
810 	if (err == 0)
811 		*nvlp = profile;
812 	return (err);
813 }
814 
815 /*
816  * Process profile passed down from libdevinfo. There are four types
817  * of matching rules:
818  *  include: export a name or names matching a pattern
819  *  exclude: exclude a name or names matching a pattern
820  *  symlink: create a local symlink
821  *  map:     export a device with a name different from the global zone
822  * Note: We may consider supporting VOP_SYMLINK in non-global instances,
823  *	because it does not present any security risk. For now, the fs
824  *	instance is read only.
825  */
826 static void
827 sdev_process_profile(struct sdev_data *sdev_data, nvlist_t *profile)
828 {
829 	nvpair_t *nvpair;
830 	char *nvname, *dname;
831 	struct sdev_node *dir, *gdir;
832 	char **pair;				/* for symlinks and maps */
833 	uint_t nelem;
834 	int rv;
835 
836 	gdir = sdev_origins->sdev_root;	/* root of global /dev */
837 	dir = sdev_data->sdev_root;	/* root of current instance */
838 
839 	ASSERT(profile);
840 
841 	/* process nvpairs in the list */
842 	nvpair = NULL;
843 	while (nvpair = nvlist_next_nvpair(profile, nvpair)) {
844 		nvname = nvpair_name(nvpair);
845 		ASSERT(nvname != NULL);
846 
847 		if (strcmp(nvname, SDEV_NVNAME_INCLUDE) == 0) {
848 			rv = nvpair_value_string(nvpair, &dname);
849 			if (rv != 0) {
850 				cmn_err(CE_WARN, sdev_nvp_val_err,
851 				    rv, nvpair_name(nvpair));
852 				break;
853 			}
854 			process_rule(dir, gdir, dname, NULL,
855 			    PROFILE_TYPE_INCLUDE);
856 		} else if (strcmp(nvname, SDEV_NVNAME_EXCLUDE) == 0) {
857 			rv = nvpair_value_string(nvpair, &dname);
858 			if (rv != 0) {
859 				cmn_err(CE_WARN, sdev_nvp_val_err,
860 				    rv, nvpair_name(nvpair));
861 				break;
862 			}
863 			process_rule(dir, gdir, dname, NULL,
864 			    PROFILE_TYPE_EXCLUDE);
865 		} else if (strcmp(nvname, SDEV_NVNAME_SYMLINK) == 0) {
866 			rv = nvpair_value_string_array(nvpair, &pair, &nelem);
867 			if (rv != 0) {
868 				cmn_err(CE_WARN, sdev_nvp_val_err,
869 				    rv, nvpair_name(nvpair));
870 				break;
871 			}
872 			ASSERT(nelem == 2);
873 			process_rule(dir, gdir, pair[0], pair[1],
874 			    PROFILE_TYPE_SYMLINK);
875 		} else if (strcmp(nvname, SDEV_NVNAME_MAP) == 0) {
876 			rv = nvpair_value_string_array(nvpair, &pair, &nelem);
877 			if (rv != 0) {
878 				cmn_err(CE_WARN, sdev_nvp_val_err,
879 				    rv, nvpair_name(nvpair));
880 				break;
881 			}
882 			process_rule(dir, gdir, pair[1], pair[0],
883 			    PROFILE_TYPE_MAP);
884 		} else if (strcmp(nvname, SDEV_NVNAME_MOUNTPT) != 0) {
885 			cmn_err(CE_WARN, "sdev_process_profile: invalid "
886 			    "nvpair %s\n", nvname);
887 		}
888 	}
889 }
890 
891 /*ARGSUSED*/
892 int
893 prof_lookup(vnode_t *dvp, char *nm, struct vnode **vpp, struct cred *cred)
894 {
895 	struct sdev_node *ddv = VTOSDEV(dvp);
896 	struct sdev_node *dv;
897 	int nmlen;
898 
899 	/*
900 	 * Empty name or ., return node itself.
901 	 */
902 	nmlen = strlen(nm);
903 	if ((nmlen == 0) || ((nmlen == 1) && (nm[0] == '.'))) {
904 		*vpp = SDEVTOV(ddv);
905 		VN_HOLD(*vpp);
906 		return (0);
907 	}
908 
909 	/*
910 	 * .., return the parent directory
911 	 */
912 	if ((nmlen == 2) && (strcmp(nm, "..") == 0)) {
913 		*vpp = SDEVTOV(ddv->sdev_dotdot);
914 		VN_HOLD(*vpp);
915 		return (0);
916 	}
917 
918 	rw_enter(&ddv->sdev_contents, RW_READER);
919 	dv = sdev_cache_lookup(ddv, nm);
920 	if (dv == NULL) {
921 		prof_filldir(ddv);
922 		dv = sdev_cache_lookup(ddv, nm);
923 	}
924 	rw_exit(&ddv->sdev_contents);
925 	if (dv == NULL) {
926 		sdcmn_err10(("prof_lookup: %s not found\n", nm));
927 		return (ENOENT);
928 	}
929 
930 	return (sdev_to_vp(dv, vpp));
931 }
932 
933 /*
934  * This is invoked after a new filesystem is mounted to define the
935  * name space. It is also invoked during normal system operation
936  * to update the name space.
937  *
938  * Applications call di_prof_commit() in libdevinfo, which invokes
939  * modctl(). modctl calls this function. The input is a packed nvlist.
940  */
941 int
942 devname_profile_update(char *packed, size_t packed_sz)
943 {
944 	char *mntpt;
945 	nvlist_t *nvl;
946 	nvpair_t *nvp;
947 	struct sdev_data *mntinfo;
948 	int err;
949 	int rv;
950 
951 	nvl = NULL;
952 	if ((err = copyin_nvlist(packed, packed_sz, &nvl)) != 0)
953 		return (err);
954 	ASSERT(nvl);
955 
956 	/* The first nvpair must be the mount point */
957 	nvp = nvlist_next_nvpair(nvl, NULL);
958 	if (strcmp(nvpair_name(nvp), SDEV_NVNAME_MOUNTPT) != 0) {
959 		cmn_err(CE_NOTE,
960 		    "devname_profile_update: mount point not specified");
961 		nvlist_free(nvl);
962 		return (EINVAL);
963 	}
964 
965 	/* find the matching filesystem instance */
966 	rv = nvpair_value_string(nvp, &mntpt);
967 	if (rv != 0) {
968 		cmn_err(CE_WARN, sdev_nvp_val_err,
969 		    rv, nvpair_name(nvp));
970 	} else {
971 		mntinfo = sdev_find_mntinfo(mntpt);
972 		if (mntinfo == NULL) {
973 			cmn_err(CE_NOTE, "devname_profile_update: "
974 			    " mount point %s not found", mntpt);
975 			nvlist_free(nvl);
976 			return (EINVAL);
977 		}
978 
979 		/* now do the hardwork to process the profile */
980 		sdev_process_profile(mntinfo, nvl);
981 
982 		sdev_mntinfo_rele(mntinfo);
983 	}
984 
985 	nvlist_free(nvl);
986 	return (0);
987 }
988