xref: /titanic_52/usr/src/uts/common/fs/dev/sdev_profile.c (revision 210938ebfcf386cf4819f16c23979173b0d78097)
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 	 */
285 	prof_getattr(dir, name, gdv, &vattr, &avp, NULL);
286 
287 	if (gdv->v_type != VDIR) {
288 		VN_RELE(gdv);
289 		gdir = NULL;
290 	} else
291 		gdir = VTOSDEV(gdv);
292 
293 	if (prof_mknode(dir, rename, &newdv, &vattr, avp,
294 	    (void *)gdir, kcred) == 0) {
295 		ASSERT(newdv->sdev_state != SDEV_ZOMBIE);
296 		SDEV_SIMPLE_RELE(newdv);
297 	}
298 }
299 
300 static void
301 prof_make_sym(struct sdev_node *dir, char *lnm, char *tgt)
302 {
303 	struct sdev_node *newdv;
304 
305 	if (prof_mknode(dir, lnm, &newdv, &sdev_vattr_lnk, NULL,
306 	    (void *)tgt, kcred) == 0) {
307 		ASSERT(newdv->sdev_state != SDEV_ZOMBIE);
308 		SDEV_SIMPLE_RELE(newdv);
309 	}
310 }
311 
312 /*
313  * Create symlinks in the current directory based on profile
314  */
315 static void
316 prof_make_symlinks(struct sdev_node *dir)
317 {
318 	char *tgt, *lnm;
319 	nvpair_t *nvp = NULL;
320 	nvlist_t *nvl = dir->sdev_prof.dev_symlink;
321 	int rv;
322 
323 	ASSERT(RW_WRITE_HELD(&dir->sdev_contents));
324 
325 	if (nvl == NULL)
326 		return;
327 
328 	while (nvp = nvlist_next_nvpair(nvl, nvp)) {
329 		lnm = nvpair_name(nvp);
330 		rv = nvpair_value_string(nvp, &tgt);
331 		if (rv != 0) {
332 			cmn_err(CE_WARN, sdev_nvp_val_err,
333 			    rv, nvpair_name(nvp));
334 			break;
335 		}
336 		prof_make_sym(dir, lnm, tgt);
337 	}
338 }
339 
340 static void
341 prof_make_maps(struct sdev_node *dir)
342 {
343 	nvpair_t *nvp = NULL;
344 	nvlist_t *nvl = dir->sdev_prof.dev_map;
345 	int rv;
346 
347 	ASSERT(RW_WRITE_HELD(&dir->sdev_contents));
348 
349 	if (nvl == NULL)
350 		return;
351 
352 	while (nvp = nvlist_next_nvpair(nvl, nvp)) {
353 		char *name;
354 		char *rename = nvpair_name(nvp);
355 		rv = nvpair_value_string(nvp, &name);
356 		if (rv != 0) {
357 			cmn_err(CE_WARN, sdev_nvp_val_err,
358 			    rv, nvpair_name(nvp));
359 			break;
360 		}
361 		sdcmn_err10(("map %s -> %s\n", name, rename));
362 		(void) prof_lookup_globaldev(dir, sdev_origins->sdev_root,
363 		    name, rename);
364 	}
365 }
366 
367 struct match_arg {
368 	char *expr;
369 	int match;
370 };
371 
372 static int
373 match_name(char *name, void *arg)
374 {
375 	struct match_arg *margp = (struct match_arg *)arg;
376 
377 	if (gmatch(name, margp->expr)) {
378 		margp->match = 1;
379 		return (WALK_DIR_TERMINATE);
380 	}
381 	return (WALK_DIR_CONTINUE);
382 }
383 
384 static int
385 is_nonempty_dir(char *name, char *pathleft, struct sdev_node *dir)
386 {
387 	struct match_arg marg;
388 	struct pathname pn;
389 	struct vnode *gvp;
390 	struct sdev_node *gdir = dir->sdev_origin;
391 
392 	if (VOP_LOOKUP(SDEVTOV(gdir), name, &gvp, NULL, 0, NULL, kcred) != 0)
393 		return (0);
394 
395 	if (gvp->v_type != VDIR) {
396 		VN_RELE(gvp);
397 		return (0);
398 	}
399 
400 	if (pn_get(pathleft, UIO_SYSSPACE, &pn) != 0) {
401 		VN_RELE(gvp);
402 		return (0);
403 	}
404 
405 	marg.expr = kmem_alloc(MAXNAMELEN, KM_SLEEP);
406 	(void) pn_getcomponent(&pn, marg.expr);
407 	marg.match = 0;
408 
409 	walk_dir(gvp, &marg, match_name);
410 	VN_RELE(gvp);
411 	kmem_free(marg.expr, MAXNAMELEN);
412 	pn_free(&pn);
413 
414 	return (marg.match);
415 }
416 
417 
418 /* Check if name passes matching rules */
419 static int
420 prof_name_matched(char *name, struct sdev_node *dir)
421 {
422 	int type, match = 0;
423 	char *expr;
424 	nvlist_t *nvl;
425 	nvpair_t *nvp = NULL;
426 	int rv;
427 
428 	/* check against nvlist for leaf include/exclude */
429 	nvl = dir->sdev_prof.dev_name;
430 	while (nvp = nvlist_next_nvpair(nvl, nvp)) {
431 		expr = nvpair_name(nvp);
432 		rv = nvpair_value_int32(nvp, &type);
433 		if (rv != 0) {
434 			cmn_err(CE_WARN, sdev_nvp_val_err,
435 			    rv, nvpair_name(nvp));
436 			break;
437 		}
438 
439 		if (type == PROFILE_TYPE_EXCLUDE) {
440 			if (gmatch(name, expr))
441 				return (0);	/* excluded */
442 		} else if (!match) {
443 			match = gmatch(name, expr);
444 		}
445 	}
446 	if (match) {
447 		sdcmn_err10(("prof_name_matched: %s\n", name));
448 		return (match);
449 	}
450 
451 	/* check for match against directory globbing pattern */
452 	nvl = dir->sdev_prof.dev_glob_incdir;
453 	while (nvp = nvlist_next_nvpair(nvl, nvp)) {
454 		char *pathleft;
455 		expr = nvpair_name(nvp);
456 		if (gmatch(name, expr) == 0)
457 			continue;
458 		rv = nvpair_value_string(nvp, &pathleft);
459 		if (rv != 0) {
460 			cmn_err(CE_WARN, sdev_nvp_val_err,
461 			    rv, nvpair_name(nvp));
462 			break;
463 		}
464 		if (is_nonempty_dir(name, pathleft, dir)) {
465 			sdcmn_err10(("prof_name_matched: dir %s\n", name));
466 			return (1);
467 		}
468 	}
469 
470 	return (0);
471 }
472 
473 static void
474 walk_dir(struct vnode *dvp, void *arg, int (*callback)(char *, void *))
475 {
476 	char    *nm;
477 	int eof, error;
478 	struct iovec iov;
479 	struct uio uio;
480 	struct dirent64 *dp;
481 	dirent64_t *dbuf;
482 	size_t dbuflen, dlen;
483 
484 	ASSERT(dvp);
485 
486 	dlen = 4096;
487 	dbuf = kmem_zalloc(dlen, KM_SLEEP);
488 
489 	uio.uio_iov = &iov;
490 	uio.uio_iovcnt = 1;
491 	uio.uio_segflg = UIO_SYSSPACE;
492 	uio.uio_fmode = 0;
493 	uio.uio_extflg = UIO_COPY_CACHED;
494 	uio.uio_loffset = 0;
495 	uio.uio_llimit = MAXOFFSET_T;
496 
497 	eof = 0;
498 	error = 0;
499 	while (!error && !eof) {
500 		uio.uio_resid = dlen;
501 		iov.iov_base = (char *)dbuf;
502 		iov.iov_len = dlen;
503 		(void) VOP_RWLOCK(dvp, V_WRITELOCK_FALSE, NULL);
504 		error = VOP_READDIR(dvp, &uio, kcred, &eof);
505 		VOP_RWUNLOCK(dvp, V_WRITELOCK_FALSE, NULL);
506 
507 		dbuflen = dlen - uio.uio_resid;
508 		if (error || dbuflen == 0)
509 			break;
510 		for (dp = dbuf; ((intptr_t)dp <
511 		    (intptr_t)dbuf + dbuflen);
512 		    dp = (dirent64_t *)((intptr_t)dp + dp->d_reclen)) {
513 			nm = dp->d_name;
514 
515 			if (strcmp(nm, ".") == 0 ||
516 			    strcmp(nm, "..") == 0)
517 				continue;
518 
519 			if (callback(nm, arg) == WALK_DIR_TERMINATE)
520 				goto end;
521 		}
522 	}
523 
524 end:
525 	kmem_free(dbuf, dlen);
526 }
527 
528 static int
529 prof_make_name(char *nm, void *arg)
530 {
531 	struct sdev_node *ddv = (struct sdev_node *)arg;
532 
533 	if (prof_name_matched(nm, ddv))
534 		prof_lookup_globaldev(ddv, ddv->sdev_origin, nm, nm);
535 	return (WALK_DIR_CONTINUE);
536 }
537 
538 static void
539 prof_make_names_glob(struct sdev_node *ddv)
540 {
541 	struct sdev_node *gdir;
542 
543 	gdir = ddv->sdev_origin;
544 	if (gdir == NULL)
545 		return;
546 	walk_dir(SDEVTOV(gdir), (void *)ddv, prof_make_name);
547 }
548 
549 static void
550 prof_make_names(struct sdev_node *dir)
551 {
552 	char *name;
553 	nvpair_t *nvp = NULL;
554 	nvlist_t *nvl = dir->sdev_prof.dev_name;
555 	int rv;
556 
557 	ASSERT(RW_WRITE_HELD(&dir->sdev_contents));
558 
559 	if (nvl == NULL)
560 		return;
561 
562 	if (dir->sdev_prof.has_glob) {
563 		prof_make_names_glob(dir);
564 		return;
565 	}
566 
567 	/* Walk nvlist and lookup corresponding device in global inst */
568 	while (nvp = nvlist_next_nvpair(nvl, nvp)) {
569 		int type;
570 		rv = nvpair_value_int32(nvp, &type);
571 		if (rv != 0) {
572 			cmn_err(CE_WARN, sdev_nvp_val_err,
573 			    rv, nvpair_name(nvp));
574 			break;
575 		}
576 		if (type == PROFILE_TYPE_EXCLUDE)
577 			continue;
578 		name = nvpair_name(nvp);
579 		(void) prof_lookup_globaldev(dir, dir->sdev_origin,
580 		    name, name);
581 	}
582 }
583 
584 /*
585  * Build directory vnodes based on the profile and the global
586  * dev instance.
587  */
588 void
589 prof_filldir(struct sdev_node *ddv)
590 {
591 	int firsttime = 1;
592 	struct sdev_node *gdir = ddv->sdev_origin;
593 
594 	ASSERT(RW_READ_HELD(&ddv->sdev_contents));
595 
596 	/*
597 	 * We need to rebuild the directory content if
598 	 * - SDEV_BUILD is set
599 	 * - The device tree generation number has changed
600 	 * - The corresponding /dev namespace has been updated
601 	 */
602 check_build:
603 	if ((ddv->sdev_flags & SDEV_BUILD) == 0 &&
604 	    ddv->sdev_devtree_gen == devtree_gen &&
605 	    (gdir == NULL || ddv->sdev_ldir_gen
606 	    == gdir->sdev_gdir_gen))
607 		return;		/* already up to date */
608 
609 	if (firsttime && rw_tryupgrade(&ddv->sdev_contents) == 0) {
610 		rw_exit(&ddv->sdev_contents);
611 		firsttime = 0;
612 		rw_enter(&ddv->sdev_contents, RW_WRITER);
613 		goto check_build;
614 	}
615 	sdcmn_err10(("devtree_gen (%s): %ld -> %ld\n",
616 	    ddv->sdev_path, ddv->sdev_devtree_gen, devtree_gen));
617 	if (gdir)
618 		sdcmn_err10(("sdev_dir_gen (%s): %ld -> %ld\n",
619 		    ddv->sdev_path, ddv->sdev_ldir_gen,
620 		    gdir->sdev_gdir_gen));
621 
622 	/* update flags and generation number so next filldir is quick */
623 	ddv->sdev_flags &= ~SDEV_BUILD;
624 	ddv->sdev_devtree_gen = devtree_gen;
625 	if (gdir)
626 		ddv->sdev_ldir_gen = gdir->sdev_gdir_gen;
627 
628 	prof_make_symlinks(ddv);
629 	prof_make_maps(ddv);
630 	prof_make_names(ddv);
631 	rw_downgrade(&ddv->sdev_contents);
632 }
633 
634 /* apply include/exclude pattern to existing directory content */
635 static void
636 apply_dir_pattern(struct sdev_node *dir, char *expr, char *pathleft, int type)
637 {
638 	struct sdev_node *dv;
639 
640 	/* leaf pattern */
641 	if (pathleft == NULL) {
642 		if (type == PROFILE_TYPE_INCLUDE)
643 			return;	/* nothing to do for include */
644 		(void) sdev_cleandir(dir, expr, SDEV_ENFORCE);
645 		return;
646 	}
647 
648 	/* directory pattern */
649 	rw_enter(&dir->sdev_contents, RW_WRITER);
650 	for (dv = dir->sdev_dot; dv; dv = dv->sdev_next) {
651 		if (gmatch(dv->sdev_name, expr) == 0 ||
652 		    SDEVTOV(dv)->v_type != VDIR)
653 			continue;
654 		process_rule(dv, dv->sdev_origin,
655 		    pathleft, NULL, type);
656 	}
657 	rw_exit(&dir->sdev_contents);
658 }
659 
660 /*
661  * Add a profile rule.
662  * tgt represents a device name matching expression,
663  * matching device names are to be either included or excluded.
664  */
665 static void
666 prof_add_rule(char *name, char *tgt, struct sdev_node *dir, int type)
667 {
668 	int error;
669 	nvlist_t **nvlp = NULL;
670 	int rv;
671 
672 	ASSERT(SDEVTOV(dir)->v_type == VDIR);
673 
674 	rw_enter(&dir->sdev_contents, RW_WRITER);
675 
676 	switch (type) {
677 	case PROFILE_TYPE_INCLUDE:
678 		if (tgt)
679 			nvlp = &(dir->sdev_prof.dev_glob_incdir);
680 		else
681 			nvlp = &(dir->sdev_prof.dev_name);
682 		break;
683 	case PROFILE_TYPE_EXCLUDE:
684 		if (tgt)
685 			nvlp = &(dir->sdev_prof.dev_glob_excdir);
686 		else
687 			nvlp = &(dir->sdev_prof.dev_name);
688 		break;
689 	case PROFILE_TYPE_MAP:
690 		nvlp = &(dir->sdev_prof.dev_map);
691 		break;
692 	case PROFILE_TYPE_SYMLINK:
693 		nvlp = &(dir->sdev_prof.dev_symlink);
694 		break;
695 	};
696 
697 	/* initialize nvlist */
698 	if (*nvlp == NULL) {
699 		error = nvlist_alloc(nvlp, NV_UNIQUE_NAME, KM_SLEEP);
700 		ASSERT(error == 0);
701 	}
702 
703 	if (tgt) {
704 		rv = nvlist_add_string(*nvlp, name, tgt);
705 	} else {
706 		rv = nvlist_add_int32(*nvlp, name, type);
707 	}
708 	ASSERT(rv == 0);
709 	/* rebuild directory content */
710 	dir->sdev_flags |= SDEV_BUILD;
711 
712 	if ((type == PROFILE_TYPE_INCLUDE) &&
713 	    (strpbrk(name, "*?[]") != NULL)) {
714 			dir->sdev_prof.has_glob = 1;
715 	}
716 
717 	rw_exit(&dir->sdev_contents);
718 
719 	/* additional details for glob pattern and exclusion */
720 	switch (type) {
721 	case PROFILE_TYPE_INCLUDE:
722 	case PROFILE_TYPE_EXCLUDE:
723 		apply_dir_pattern(dir, name, tgt, type);
724 		break;
725 	};
726 }
727 
728 /*
729  * Parse path components and apply requested matching rule at
730  * directory level.
731  */
732 static void
733 process_rule(struct sdev_node *dir, struct sdev_node *gdir,
734     char *path, char *tgt, int type)
735 {
736 	char *name;
737 	struct pathname	pn;
738 	int rv = 0;
739 
740 	if ((strlen(path) > 5) && (strncmp(path, "/dev/", 5) == 0)) {
741 		path += 5;
742 	}
743 
744 	if (pn_get(path, UIO_SYSSPACE, &pn) != 0)
745 		return;
746 
747 	name = kmem_alloc(MAXPATHLEN, KM_SLEEP);
748 	(void) pn_getcomponent(&pn, name);
749 	pn_skipslash(&pn);
750 	SDEV_HOLD(dir);
751 
752 	while (pn_pathleft(&pn)) {
753 		/* If this is pattern, just add the pattern */
754 		if (strpbrk(name, "*?[]") != NULL &&
755 		    (type == PROFILE_TYPE_INCLUDE ||
756 		    type == PROFILE_TYPE_EXCLUDE)) {
757 			ASSERT(tgt == NULL);
758 			tgt = pn.pn_path;
759 			break;
760 		}
761 		if ((rv = prof_make_dir(name, &gdir, &dir)) != 0) {
762 			cmn_err(CE_CONT, "process_rule: %s error %d\n",
763 			    path, rv);
764 			break;
765 		}
766 		(void) pn_getcomponent(&pn, name);
767 		pn_skipslash(&pn);
768 	}
769 
770 	/* process the leaf component */
771 	if (rv == 0) {
772 		prof_add_rule(name, tgt, dir, type);
773 		SDEV_SIMPLE_RELE(dir);
774 	}
775 
776 	kmem_free(name, MAXPATHLEN);
777 	pn_free(&pn);
778 }
779 
780 static int
781 copyin_nvlist(char *packed_usr, size_t packed_sz, nvlist_t **nvlp)
782 {
783 	int err = 0;
784 	char *packed;
785 	nvlist_t *profile = NULL;
786 
787 	/* simple sanity check */
788 	if (packed_usr == NULL || packed_sz == 0)
789 		return (NULL);
790 
791 	/* copyin packed profile nvlist */
792 	packed = kmem_alloc(packed_sz, KM_NOSLEEP);
793 	if (packed == NULL)
794 		return (ENOMEM);
795 	err = copyin(packed_usr, packed, packed_sz);
796 
797 	/* unpack packed profile nvlist */
798 	if (err)
799 		cmn_err(CE_WARN, "copyin_nvlist: copyin failed with "
800 		    "err %d\n", err);
801 	else if (err = nvlist_unpack(packed, packed_sz, &profile, KM_NOSLEEP))
802 		cmn_err(CE_WARN, "copyin_nvlist: nvlist_unpack "
803 		    "failed with err %d\n", err);
804 
805 	kmem_free(packed, packed_sz);
806 	if (err == 0)
807 		*nvlp = profile;
808 	return (err);
809 }
810 
811 /*
812  * Process profile passed down from libdevinfo. There are four types
813  * of matching rules:
814  *  include: export a name or names matching a pattern
815  *  exclude: exclude a name or names matching a pattern
816  *  symlink: create a local symlink
817  *  map:     export a device with a name different from the global zone
818  * Note: We may consider supporting VOP_SYMLINK in non-global instances,
819  *	because it does not present any security risk. For now, the fs
820  *	instance is read only.
821  */
822 static void
823 sdev_process_profile(struct sdev_data *sdev_data, nvlist_t *profile)
824 {
825 	nvpair_t *nvpair;
826 	char *nvname, *dname;
827 	struct sdev_node *dir, *gdir;
828 	char **pair;				/* for symlinks and maps */
829 	uint_t nelem;
830 	int rv;
831 
832 	gdir = sdev_origins->sdev_root;	/* root of global /dev */
833 	dir = sdev_data->sdev_root;	/* root of current instance */
834 
835 	ASSERT(profile);
836 
837 	/* process nvpairs in the list */
838 	nvpair = NULL;
839 	while (nvpair = nvlist_next_nvpair(profile, nvpair)) {
840 		nvname = nvpair_name(nvpair);
841 		ASSERT(nvname != NULL);
842 
843 		if (strcmp(nvname, SDEV_NVNAME_INCLUDE) == 0) {
844 			rv = nvpair_value_string(nvpair, &dname);
845 			if (rv != 0) {
846 				cmn_err(CE_WARN, sdev_nvp_val_err,
847 				    rv, nvpair_name(nvpair));
848 				break;
849 			}
850 			process_rule(dir, gdir, dname, NULL,
851 			    PROFILE_TYPE_INCLUDE);
852 		} else if (strcmp(nvname, SDEV_NVNAME_EXCLUDE) == 0) {
853 			rv = nvpair_value_string(nvpair, &dname);
854 			if (rv != 0) {
855 				cmn_err(CE_WARN, sdev_nvp_val_err,
856 				    rv, nvpair_name(nvpair));
857 				break;
858 			}
859 			process_rule(dir, gdir, dname, NULL,
860 			    PROFILE_TYPE_EXCLUDE);
861 		} else if (strcmp(nvname, SDEV_NVNAME_SYMLINK) == 0) {
862 			rv = nvpair_value_string_array(nvpair, &pair, &nelem);
863 			if (rv != 0) {
864 				cmn_err(CE_WARN, sdev_nvp_val_err,
865 				    rv, nvpair_name(nvpair));
866 				break;
867 			}
868 			ASSERT(nelem == 2);
869 			process_rule(dir, gdir, pair[0], pair[1],
870 			    PROFILE_TYPE_SYMLINK);
871 		} else if (strcmp(nvname, SDEV_NVNAME_MAP) == 0) {
872 			rv = nvpair_value_string_array(nvpair, &pair, &nelem);
873 			if (rv != 0) {
874 				cmn_err(CE_WARN, sdev_nvp_val_err,
875 				    rv, nvpair_name(nvpair));
876 				break;
877 			}
878 			process_rule(dir, gdir, pair[1], pair[0],
879 			    PROFILE_TYPE_MAP);
880 		} else if (strcmp(nvname, SDEV_NVNAME_MOUNTPT) != 0) {
881 			cmn_err(CE_WARN, "sdev_process_profile: invalid "
882 			    "nvpair %s\n", nvname);
883 		}
884 	}
885 }
886 
887 /*ARGSUSED*/
888 int
889 prof_lookup(vnode_t *dvp, char *nm, struct vnode **vpp, struct cred *cred)
890 {
891 	struct sdev_node *ddv = VTOSDEV(dvp);
892 	struct sdev_node *dv;
893 	int nmlen;
894 
895 	/*
896 	 * Empty name or ., return node itself.
897 	 */
898 	nmlen = strlen(nm);
899 	if ((nmlen == 0) || ((nmlen == 1) && (nm[0] == '.'))) {
900 		*vpp = SDEVTOV(ddv);
901 		VN_HOLD(*vpp);
902 		return (0);
903 	}
904 
905 	/*
906 	 * .., return the parent directory
907 	 */
908 	if ((nmlen == 2) && (strcmp(nm, "..") == 0)) {
909 		*vpp = SDEVTOV(ddv->sdev_dotdot);
910 		VN_HOLD(*vpp);
911 		return (0);
912 	}
913 
914 	rw_enter(&ddv->sdev_contents, RW_READER);
915 	dv = sdev_cache_lookup(ddv, nm);
916 	if (dv == NULL) {
917 		prof_filldir(ddv);
918 		dv = sdev_cache_lookup(ddv, nm);
919 	}
920 	rw_exit(&ddv->sdev_contents);
921 	if (dv == NULL) {
922 		sdcmn_err10(("prof_lookup: %s not found\n", nm));
923 		return (ENOENT);
924 	}
925 
926 	return (sdev_to_vp(dv, vpp));
927 }
928 
929 /*
930  * This is invoked after a new filesystem is mounted to define the
931  * name space. It is also invoked during normal system operation
932  * to update the name space.
933  *
934  * Applications call di_prof_commit() in libdevinfo, which invokes
935  * modctl(). modctl calls this function. The input is a packed nvlist.
936  */
937 int
938 devname_profile_update(char *packed, size_t packed_sz)
939 {
940 	char *mntpt;
941 	nvlist_t *nvl;
942 	nvpair_t *nvp;
943 	struct sdev_data *mntinfo;
944 	int err;
945 	int rv;
946 
947 	nvl = NULL;
948 	if ((err = copyin_nvlist(packed, packed_sz, &nvl)) != 0)
949 		return (err);
950 	ASSERT(nvl);
951 
952 	/* The first nvpair must be the mount point */
953 	nvp = nvlist_next_nvpair(nvl, NULL);
954 	if (strcmp(nvpair_name(nvp), SDEV_NVNAME_MOUNTPT) != 0) {
955 		cmn_err(CE_NOTE,
956 		    "devname_profile_update: mount point not specified");
957 		nvlist_free(nvl);
958 		return (EINVAL);
959 	}
960 
961 	/* find the matching filesystem instance */
962 	rv = nvpair_value_string(nvp, &mntpt);
963 	if (rv != 0) {
964 		cmn_err(CE_WARN, sdev_nvp_val_err,
965 		    rv, nvpair_name(nvp));
966 	} else {
967 		mntinfo = sdev_find_mntinfo(mntpt);
968 		if (mntinfo == NULL) {
969 			cmn_err(CE_NOTE, "devname_profile_update: "
970 			    " mount point %s not found", mntpt);
971 			nvlist_free(nvl);
972 			return (EINVAL);
973 		}
974 
975 		/* now do the hardwork to process the profile */
976 		sdev_process_profile(mntinfo, nvl);
977 
978 		sdev_mntinfo_rele(mntinfo);
979 	}
980 
981 	nvlist_free(nvl);
982 	return (0);
983 }
984