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