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