xref: /titanic_41/usr/src/uts/common/fs/dev/sdev_profile.c (revision 6c939cbd512e56af4e6b111b45d83e8b8b9974fe)
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
prof_getattr(struct sdev_node * dir,char * name,struct vnode * gdv,struct vattr * vap,struct vnode ** avpp,int * no_fs_perm)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
apply_glob_pattern(struct sdev_node * pdir,struct sdev_node * cdir)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
prof_mknode(struct sdev_node * dir,char * name,struct sdev_node ** newdv,vattr_t * vap,vnode_t * avp,void * arg,cred_t * cred)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 	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 	} else {
177 		sdev_cache_update(dir, &dv, name, SDEV_CACHE_DELETE);
178 		SDEV_RELE(dv);
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
prof_make_dir(char * name,struct sdev_node ** gdirp,struct sdev_node ** dirp)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 		rw_exit(&dir->sdev_contents);
205 		SDEV_RELE(dir);
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
prof_lookup_globaldev(struct sdev_node * dir,struct sdev_node * gdir,char * name,char * rename)245 prof_lookup_globaldev(struct sdev_node *dir, struct sdev_node *gdir,
246     char *name, char *rename)
247 {
248 	int error;
249 	struct vnode *avp, *gdv, *gddv;
250 	struct sdev_node *newdv;
251 	struct vattr vattr = {0};
252 	struct pathname pn;
253 
254 	/* check if node already exists */
255 	newdv = sdev_cache_lookup(dir, rename);
256 	if (newdv) {
257 		ASSERT(newdv->sdev_state != SDEV_ZOMBIE);
258 		SDEV_SIMPLE_RELE(newdv);
259 		return;
260 	}
261 
262 	/* sanity check arguments */
263 	if (!gdir || pn_get(name, UIO_SYSSPACE, &pn))
264 		return;
265 
266 	/* perform a relative lookup of the global /dev instance */
267 	gddv = SDEVTOV(gdir);
268 	VN_HOLD(gddv);
269 	error = lookuppnvp(&pn, NULL, FOLLOW, NULLVPP, &gdv,
270 	    rootdir, gddv, kcred);
271 	pn_free(&pn);
272 	if (error) {
273 		sdcmn_err10(("prof_lookup_globaldev: %s not found\n", name));
274 		return;
275 	}
276 	ASSERT(gdv && gdv->v_type != VLNK);
277 
278 	/*
279 	 * Found the entry in global /dev, figure out attributes
280 	 * by looking at backing store. Call into devfs for default.
281 	 * Note, mapped device is persisted under the new name
282 	 */
283 	prof_getattr(dir, rename, gdv, &vattr, &avp, NULL);
284 
285 	if (gdv->v_type != VDIR) {
286 		VN_RELE(gdv);
287 		gdir = NULL;
288 	} else
289 		gdir = VTOSDEV(gdv);
290 
291 	if (prof_mknode(dir, rename, &newdv, &vattr, avp,
292 	    (void *)gdir, kcred) == 0) {
293 		ASSERT(newdv->sdev_state != SDEV_ZOMBIE);
294 		SDEV_SIMPLE_RELE(newdv);
295 	}
296 }
297 
298 static void
prof_make_sym(struct sdev_node * dir,char * lnm,char * tgt)299 prof_make_sym(struct sdev_node *dir, char *lnm, char *tgt)
300 {
301 	struct sdev_node *newdv;
302 
303 	if (prof_mknode(dir, lnm, &newdv, &sdev_vattr_lnk, NULL,
304 	    (void *)tgt, kcred) == 0) {
305 		ASSERT(newdv->sdev_state != SDEV_ZOMBIE);
306 		SDEV_SIMPLE_RELE(newdv);
307 	}
308 }
309 
310 /*
311  * Create symlinks in the current directory based on profile
312  */
313 static void
prof_make_symlinks(struct sdev_node * dir)314 prof_make_symlinks(struct sdev_node *dir)
315 {
316 	char *tgt, *lnm;
317 	nvpair_t *nvp = NULL;
318 	nvlist_t *nvl = dir->sdev_prof.dev_symlink;
319 	int rv;
320 
321 	ASSERT(RW_WRITE_HELD(&dir->sdev_contents));
322 
323 	if (nvl == NULL)
324 		return;
325 
326 	while (nvp = nvlist_next_nvpair(nvl, nvp)) {
327 		lnm = nvpair_name(nvp);
328 		rv = nvpair_value_string(nvp, &tgt);
329 		if (rv != 0) {
330 			cmn_err(CE_WARN, sdev_nvp_val_err,
331 			    rv, nvpair_name(nvp));
332 			break;
333 		}
334 		prof_make_sym(dir, lnm, tgt);
335 	}
336 }
337 
338 static void
prof_make_maps(struct sdev_node * dir)339 prof_make_maps(struct sdev_node *dir)
340 {
341 	nvpair_t *nvp = NULL;
342 	nvlist_t *nvl = dir->sdev_prof.dev_map;
343 	int rv;
344 
345 	ASSERT(RW_WRITE_HELD(&dir->sdev_contents));
346 
347 	if (nvl == NULL)
348 		return;
349 
350 	while (nvp = nvlist_next_nvpair(nvl, nvp)) {
351 		char *name;
352 		char *rename = nvpair_name(nvp);
353 		rv = nvpair_value_string(nvp, &name);
354 		if (rv != 0) {
355 			cmn_err(CE_WARN, sdev_nvp_val_err,
356 			    rv, nvpair_name(nvp));
357 			break;
358 		}
359 		sdcmn_err10(("map %s -> %s\n", name, rename));
360 		(void) prof_lookup_globaldev(dir, sdev_origins->sdev_root,
361 		    name, rename);
362 	}
363 }
364 
365 struct match_arg {
366 	char *expr;
367 	int match;
368 };
369 
370 static int
match_name(char * name,void * arg)371 match_name(char *name, void *arg)
372 {
373 	struct match_arg *margp = (struct match_arg *)arg;
374 
375 	if (gmatch(name, margp->expr)) {
376 		margp->match = 1;
377 		return (WALK_DIR_TERMINATE);
378 	}
379 	return (WALK_DIR_CONTINUE);
380 }
381 
382 static int
is_nonempty_dir(char * name,char * pathleft,struct sdev_node * dir)383 is_nonempty_dir(char *name, char *pathleft, struct sdev_node *dir)
384 {
385 	struct match_arg marg;
386 	struct pathname pn;
387 	struct vnode *gvp;
388 	struct sdev_node *gdir = dir->sdev_origin;
389 
390 	if (VOP_LOOKUP(SDEVTOV(gdir), name, &gvp, NULL, 0, NULL, kcred,
391 	    NULL, NULL, NULL) != 0)
392 		return (0);
393 
394 	if (gvp->v_type != VDIR) {
395 		VN_RELE(gvp);
396 		return (0);
397 	}
398 
399 	if (pn_get(pathleft, UIO_SYSSPACE, &pn) != 0) {
400 		VN_RELE(gvp);
401 		return (0);
402 	}
403 
404 	marg.expr = kmem_alloc(MAXNAMELEN, KM_SLEEP);
405 	(void) pn_getcomponent(&pn, marg.expr);
406 	marg.match = 0;
407 
408 	walk_dir(gvp, &marg, match_name);
409 	VN_RELE(gvp);
410 	kmem_free(marg.expr, MAXNAMELEN);
411 	pn_free(&pn);
412 
413 	return (marg.match);
414 }
415 
416 
417 /* Check if name passes matching rules */
418 static int
prof_name_matched(char * name,struct sdev_node * dir)419 prof_name_matched(char *name, struct sdev_node *dir)
420 {
421 	int type, match = 0;
422 	char *expr;
423 	nvlist_t *nvl;
424 	nvpair_t *nvp = NULL;
425 	int rv;
426 
427 	/* check against nvlist for leaf include/exclude */
428 	nvl = dir->sdev_prof.dev_name;
429 	while (nvp = nvlist_next_nvpair(nvl, nvp)) {
430 		expr = nvpair_name(nvp);
431 		rv = nvpair_value_int32(nvp, &type);
432 		if (rv != 0) {
433 			cmn_err(CE_WARN, sdev_nvp_val_err,
434 			    rv, nvpair_name(nvp));
435 			break;
436 		}
437 
438 		if (type == PROFILE_TYPE_EXCLUDE) {
439 			if (gmatch(name, expr))
440 				return (0);	/* excluded */
441 		} else if (!match) {
442 			match = gmatch(name, expr);
443 		}
444 	}
445 	if (match) {
446 		sdcmn_err10(("prof_name_matched: %s\n", name));
447 		return (match);
448 	}
449 
450 	/* check for match against directory globbing pattern */
451 	nvl = dir->sdev_prof.dev_glob_incdir;
452 	while (nvp = nvlist_next_nvpair(nvl, nvp)) {
453 		char *pathleft;
454 		expr = nvpair_name(nvp);
455 		if (gmatch(name, expr) == 0)
456 			continue;
457 		rv = nvpair_value_string(nvp, &pathleft);
458 		if (rv != 0) {
459 			cmn_err(CE_WARN, sdev_nvp_val_err,
460 			    rv, nvpair_name(nvp));
461 			break;
462 		}
463 		if (is_nonempty_dir(name, pathleft, dir)) {
464 			sdcmn_err10(("prof_name_matched: dir %s\n", name));
465 			return (1);
466 		}
467 	}
468 
469 	return (0);
470 }
471 
472 static void
walk_dir(struct vnode * dvp,void * arg,int (* callback)(char *,void *))473 walk_dir(struct vnode *dvp, void *arg, int (*callback)(char *, void *))
474 {
475 	char    *nm;
476 	int eof, error;
477 	struct iovec iov;
478 	struct uio uio;
479 	struct dirent64 *dp;
480 	dirent64_t *dbuf;
481 	size_t dbuflen, dlen;
482 
483 	ASSERT(dvp);
484 
485 	dlen = 4096;
486 	dbuf = kmem_zalloc(dlen, KM_SLEEP);
487 
488 	uio.uio_iov = &iov;
489 	uio.uio_iovcnt = 1;
490 	uio.uio_segflg = UIO_SYSSPACE;
491 	uio.uio_fmode = 0;
492 	uio.uio_extflg = UIO_COPY_CACHED;
493 	uio.uio_loffset = 0;
494 	uio.uio_llimit = MAXOFFSET_T;
495 
496 	eof = 0;
497 	error = 0;
498 	while (!error && !eof) {
499 		uio.uio_resid = dlen;
500 		iov.iov_base = (char *)dbuf;
501 		iov.iov_len = dlen;
502 		(void) VOP_RWLOCK(dvp, V_WRITELOCK_FALSE, NULL);
503 		error = VOP_READDIR(dvp, &uio, kcred, &eof, NULL, 0);
504 		VOP_RWUNLOCK(dvp, V_WRITELOCK_FALSE, NULL);
505 
506 		dbuflen = dlen - uio.uio_resid;
507 		if (error || dbuflen == 0)
508 			break;
509 		for (dp = dbuf; ((intptr_t)dp <
510 		    (intptr_t)dbuf + dbuflen);
511 		    dp = (dirent64_t *)((intptr_t)dp + dp->d_reclen)) {
512 			nm = dp->d_name;
513 
514 			if (strcmp(nm, ".") == 0 ||
515 			    strcmp(nm, "..") == 0)
516 				continue;
517 
518 			if (callback(nm, arg) == WALK_DIR_TERMINATE)
519 				goto end;
520 		}
521 	}
522 
523 end:
524 	kmem_free(dbuf, dlen);
525 }
526 
527 /*
528  * Last chance for a zone to see a node.  If our parent dir is
529  * SDEV_ZONED, then we look up the "zone" property for the node.  If the
530  * property is found and matches the current zone name, we allow it.
531  * Note that this isn't quite correct for the global zone peeking inside
532  * a zone's /dev - for that to work, we'd have to have a per-dev-mount
533  * zone ref squirreled away.
534  */
535 static int
prof_zone_matched(char * name,struct sdev_node * dir)536 prof_zone_matched(char *name, struct sdev_node *dir)
537 {
538 	vnode_t *gvn = SDEVTOV(dir->sdev_origin);
539 	struct pathname pn;
540 	vnode_t *vn = NULL;
541 	char zonename[ZONENAME_MAX];
542 	int znlen = ZONENAME_MAX;
543 	int ret;
544 
545 	ASSERT((dir->sdev_flags & SDEV_ZONED) != 0);
546 
547 	sdcmn_err10(("sdev_node %p is zoned, looking for %s\n",
548 	    (void *)dir, name));
549 
550 	if (pn_get(name, UIO_SYSSPACE, &pn))
551 		return (0);
552 
553 	VN_HOLD(gvn);
554 
555 	ret = lookuppnvp(&pn, NULL, FOLLOW, NULLVPP, &vn, rootdir, gvn, kcred);
556 
557 	pn_free(&pn);
558 
559 	if (ret != 0) {
560 		sdcmn_err10(("prof_zone_matched: %s not found\n", name));
561 		return (0);
562 	}
563 
564 	/*
565 	 * VBLK doesn't matter, and the property name is in fact treated
566 	 * as a const char *.
567 	 */
568 	ret = e_ddi_getlongprop_buf(vn->v_rdev, VBLK, (char *)"zone",
569 	    DDI_PROP_NOTPROM | DDI_PROP_DONTPASS, (caddr_t)zonename, &znlen);
570 
571 	VN_RELE(vn);
572 
573 	if (ret == DDI_PROP_NOT_FOUND) {
574 		sdcmn_err10(("vnode %p: no zone prop\n", (void *)vn));
575 		return (0);
576 	} else if (ret != DDI_PROP_SUCCESS) {
577 		sdcmn_err10(("vnode %p: zone prop error: %d\n",
578 		    (void *)vn, ret));
579 		return (0);
580 	}
581 
582 	sdcmn_err10(("vnode %p zone prop: %s\n", (void *)vn, zonename));
583 	return (strcmp(zonename, curproc->p_zone->zone_name) == 0);
584 }
585 
586 static int
prof_make_name_glob(char * nm,void * arg)587 prof_make_name_glob(char *nm, void *arg)
588 {
589 	struct sdev_node *ddv = (struct sdev_node *)arg;
590 
591 	if (prof_name_matched(nm, ddv))
592 		prof_lookup_globaldev(ddv, ddv->sdev_origin, nm, nm);
593 
594 	return (WALK_DIR_CONTINUE);
595 }
596 
597 static int
prof_make_name_zone(char * nm,void * arg)598 prof_make_name_zone(char *nm, void *arg)
599 {
600 	struct sdev_node *ddv = (struct sdev_node *)arg;
601 
602 	if (prof_zone_matched(nm, ddv))
603 		prof_lookup_globaldev(ddv, ddv->sdev_origin, nm, nm);
604 
605 	return (WALK_DIR_CONTINUE);
606 }
607 
608 static void
prof_make_names_walk(struct sdev_node * ddv,int (* cb)(char *,void *))609 prof_make_names_walk(struct sdev_node *ddv, int (*cb)(char *, void *))
610 {
611 	struct sdev_node *gdir;
612 
613 	gdir = ddv->sdev_origin;
614 	if (gdir == NULL)
615 		return;
616 	walk_dir(SDEVTOV(gdir), (void *)ddv, cb);
617 }
618 
619 static void
prof_make_names(struct sdev_node * dir)620 prof_make_names(struct sdev_node *dir)
621 {
622 	char *name;
623 	nvpair_t *nvp = NULL;
624 	nvlist_t *nvl = dir->sdev_prof.dev_name;
625 	int rv;
626 
627 	ASSERT(RW_WRITE_HELD(&dir->sdev_contents));
628 
629 	if ((dir->sdev_flags & SDEV_ZONED) != 0)
630 		prof_make_names_walk(dir, prof_make_name_zone);
631 
632 	if (nvl == NULL)
633 		return;
634 
635 	if (dir->sdev_prof.has_glob) {
636 		prof_make_names_walk(dir, prof_make_name_glob);
637 		return;
638 	}
639 
640 	/* Walk nvlist and lookup corresponding device in global inst */
641 	while (nvp = nvlist_next_nvpair(nvl, nvp)) {
642 		int type;
643 		rv = nvpair_value_int32(nvp, &type);
644 		if (rv != 0) {
645 			cmn_err(CE_WARN, sdev_nvp_val_err,
646 			    rv, nvpair_name(nvp));
647 			break;
648 		}
649 		if (type == PROFILE_TYPE_EXCLUDE)
650 			continue;
651 		name = nvpair_name(nvp);
652 		(void) prof_lookup_globaldev(dir, dir->sdev_origin,
653 		    name, name);
654 	}
655 }
656 
657 /*
658  * Return True if directory cache is out of date and should be updated.
659  */
660 static boolean_t
prof_dev_needupdate(sdev_node_t * ddv)661 prof_dev_needupdate(sdev_node_t *ddv)
662 {
663 	sdev_node_t *gdir = ddv->sdev_origin;
664 
665 	/*
666 	 * Caller can have either reader or writer lock
667 	 */
668 	ASSERT(RW_LOCK_HELD(&ddv->sdev_contents));
669 
670 	/*
671 	 * We need to rebuild the directory content if
672 	 * - ddv is not in a SDEV_ZOMBIE state
673 	 * - SDEV_BUILD is set OR
674 	 * - The device tree generation number has changed OR
675 	 * - The corresponding /dev namespace has been updated
676 	 */
677 	return ((ddv->sdev_state != SDEV_ZOMBIE) &&
678 	    (((ddv->sdev_flags & SDEV_BUILD) != 0) ||
679 	    (ddv->sdev_devtree_gen != devtree_gen) ||
680 	    ((gdir != NULL) &&
681 	    (ddv->sdev_ldir_gen != gdir->sdev_gdir_gen))));
682 }
683 
684 /*
685  * Build directory vnodes based on the profile and the global
686  * dev instance.
687  */
688 void
prof_filldir(sdev_node_t * ddv)689 prof_filldir(sdev_node_t *ddv)
690 {
691 	sdev_node_t *gdir;
692 
693 	ASSERT(RW_READ_HELD(&ddv->sdev_contents));
694 
695 	if (!prof_dev_needupdate(ddv)) {
696 		ASSERT(RW_READ_HELD(&ddv->sdev_contents));
697 		return;
698 	}
699 	/*
700 	 * Upgrade to writer lock
701 	 */
702 	if (rw_tryupgrade(&ddv->sdev_contents) == 0) {
703 		/*
704 		 * We need to drop the read lock and re-acquire it as a
705 		 * write lock. While we do this the condition may change so we
706 		 * need to re-check condition
707 		 */
708 		rw_exit(&ddv->sdev_contents);
709 		rw_enter(&ddv->sdev_contents, RW_WRITER);
710 		if (!prof_dev_needupdate(ddv)) {
711 			/* Downgrade back to the read lock before returning */
712 			rw_downgrade(&ddv->sdev_contents);
713 			return;
714 		}
715 	}
716 	/* At this point we should have a write lock */
717 	ASSERT(RW_WRITE_HELD(&ddv->sdev_contents));
718 
719 	sdcmn_err10(("devtree_gen (%s): %ld -> %ld\n",
720 	    ddv->sdev_path, ddv->sdev_devtree_gen, devtree_gen));
721 
722 	gdir = ddv->sdev_origin;
723 
724 	if (gdir != NULL)
725 		sdcmn_err10(("sdev_dir_gen (%s): %ld -> %ld\n",
726 		    ddv->sdev_path, ddv->sdev_ldir_gen,
727 		    gdir->sdev_gdir_gen));
728 
729 	/* update flags and generation number so next filldir is quick */
730 	if ((ddv->sdev_flags & SDEV_BUILD) == SDEV_BUILD) {
731 		ddv->sdev_flags &= ~SDEV_BUILD;
732 	}
733 	ddv->sdev_devtree_gen = devtree_gen;
734 	if (gdir != NULL)
735 		ddv->sdev_ldir_gen = gdir->sdev_gdir_gen;
736 
737 	prof_make_symlinks(ddv);
738 	prof_make_maps(ddv);
739 	prof_make_names(ddv);
740 	rw_downgrade(&ddv->sdev_contents);
741 }
742 
743 /* apply include/exclude pattern to existing directory content */
744 static void
apply_dir_pattern(struct sdev_node * dir,char * expr,char * pathleft,int type)745 apply_dir_pattern(struct sdev_node *dir, char *expr, char *pathleft, int type)
746 {
747 	struct sdev_node *dv;
748 
749 	/* leaf pattern */
750 	if (pathleft == NULL) {
751 		if (type == PROFILE_TYPE_INCLUDE)
752 			return;	/* nothing to do for include */
753 		(void) sdev_cleandir(dir, expr, SDEV_ENFORCE);
754 		return;
755 	}
756 
757 	/* directory pattern */
758 	rw_enter(&dir->sdev_contents, RW_WRITER);
759 
760 	for (dv = SDEV_FIRST_ENTRY(dir); dv; dv = SDEV_NEXT_ENTRY(dir, dv)) {
761 		if (gmatch(dv->sdev_name, expr) == 0 ||
762 		    SDEVTOV(dv)->v_type != VDIR)
763 			continue;
764 		process_rule(dv, dv->sdev_origin,
765 		    pathleft, NULL, type);
766 	}
767 	rw_exit(&dir->sdev_contents);
768 }
769 
770 /*
771  * Add a profile rule.
772  * tgt represents a device name matching expression,
773  * matching device names are to be either included or excluded.
774  */
775 static void
prof_add_rule(char * name,char * tgt,struct sdev_node * dir,int type)776 prof_add_rule(char *name, char *tgt, struct sdev_node *dir, int type)
777 {
778 	int error;
779 	nvlist_t **nvlp = NULL;
780 	int rv;
781 
782 	ASSERT(SDEVTOV(dir)->v_type == VDIR);
783 
784 	rw_enter(&dir->sdev_contents, RW_WRITER);
785 
786 	switch (type) {
787 	case PROFILE_TYPE_INCLUDE:
788 		if (tgt)
789 			nvlp = &(dir->sdev_prof.dev_glob_incdir);
790 		else
791 			nvlp = &(dir->sdev_prof.dev_name);
792 		break;
793 	case PROFILE_TYPE_EXCLUDE:
794 		if (tgt)
795 			nvlp = &(dir->sdev_prof.dev_glob_excdir);
796 		else
797 			nvlp = &(dir->sdev_prof.dev_name);
798 		break;
799 	case PROFILE_TYPE_MAP:
800 		nvlp = &(dir->sdev_prof.dev_map);
801 		break;
802 	case PROFILE_TYPE_SYMLINK:
803 		nvlp = &(dir->sdev_prof.dev_symlink);
804 		break;
805 	};
806 
807 	/* initialize nvlist */
808 	if (*nvlp == NULL) {
809 		error = nvlist_alloc(nvlp, NV_UNIQUE_NAME, KM_SLEEP);
810 		ASSERT(error == 0);
811 	}
812 
813 	if (tgt) {
814 		rv = nvlist_add_string(*nvlp, name, tgt);
815 	} else {
816 		rv = nvlist_add_int32(*nvlp, name, type);
817 	}
818 	ASSERT(rv == 0);
819 	/* rebuild directory content */
820 	dir->sdev_flags |= SDEV_BUILD;
821 
822 	if ((type == PROFILE_TYPE_INCLUDE) &&
823 	    (strpbrk(name, "*?[]") != NULL)) {
824 			dir->sdev_prof.has_glob = 1;
825 	}
826 
827 	rw_exit(&dir->sdev_contents);
828 
829 	/* additional details for glob pattern and exclusion */
830 	switch (type) {
831 	case PROFILE_TYPE_INCLUDE:
832 	case PROFILE_TYPE_EXCLUDE:
833 		apply_dir_pattern(dir, name, tgt, type);
834 		break;
835 	};
836 }
837 
838 /*
839  * Parse path components and apply requested matching rule at
840  * directory level.
841  */
842 static void
process_rule(struct sdev_node * dir,struct sdev_node * gdir,char * path,char * tgt,int type)843 process_rule(struct sdev_node *dir, struct sdev_node *gdir,
844     char *path, char *tgt, int type)
845 {
846 	char *name;
847 	struct pathname	pn;
848 	int rv = 0;
849 
850 	if ((strlen(path) > 5) && (strncmp(path, "/dev/", 5) == 0)) {
851 		path += 5;
852 	}
853 
854 	if (pn_get(path, UIO_SYSSPACE, &pn) != 0)
855 		return;
856 
857 	name = kmem_alloc(MAXPATHLEN, KM_SLEEP);
858 	(void) pn_getcomponent(&pn, name);
859 	pn_skipslash(&pn);
860 	SDEV_HOLD(dir);
861 
862 	while (pn_pathleft(&pn)) {
863 		/* If this is pattern, just add the pattern */
864 		if (strpbrk(name, "*?[]") != NULL &&
865 		    (type == PROFILE_TYPE_INCLUDE ||
866 		    type == PROFILE_TYPE_EXCLUDE)) {
867 			ASSERT(tgt == NULL);
868 			tgt = pn.pn_path;
869 			break;
870 		}
871 		if ((rv = prof_make_dir(name, &gdir, &dir)) != 0) {
872 			cmn_err(CE_CONT, "process_rule: %s error %d\n",
873 			    path, rv);
874 			break;
875 		}
876 		(void) pn_getcomponent(&pn, name);
877 		pn_skipslash(&pn);
878 	}
879 
880 	/* process the leaf component */
881 	if (rv == 0) {
882 		prof_add_rule(name, tgt, dir, type);
883 		SDEV_SIMPLE_RELE(dir);
884 	}
885 
886 	kmem_free(name, MAXPATHLEN);
887 	pn_free(&pn);
888 }
889 
890 static int
copyin_nvlist(char * packed_usr,size_t packed_sz,nvlist_t ** nvlp)891 copyin_nvlist(char *packed_usr, size_t packed_sz, nvlist_t **nvlp)
892 {
893 	int err = 0;
894 	char *packed;
895 	nvlist_t *profile = NULL;
896 
897 	/* simple sanity check */
898 	if (packed_usr == NULL || packed_sz == 0)
899 		return (NULL);
900 
901 	/* copyin packed profile nvlist */
902 	packed = kmem_alloc(packed_sz, KM_NOSLEEP);
903 	if (packed == NULL)
904 		return (ENOMEM);
905 	err = copyin(packed_usr, packed, packed_sz);
906 
907 	/* unpack packed profile nvlist */
908 	if (err)
909 		cmn_err(CE_WARN, "copyin_nvlist: copyin failed with "
910 		    "err %d\n", err);
911 	else if (err = nvlist_unpack(packed, packed_sz, &profile, KM_NOSLEEP))
912 		cmn_err(CE_WARN, "copyin_nvlist: nvlist_unpack "
913 		    "failed with err %d\n", err);
914 
915 	kmem_free(packed, packed_sz);
916 	if (err == 0)
917 		*nvlp = profile;
918 	return (err);
919 }
920 
921 /*
922  * Process profile passed down from libdevinfo. There are four types
923  * of matching rules:
924  *  include: export a name or names matching a pattern
925  *  exclude: exclude a name or names matching a pattern
926  *  symlink: create a local symlink
927  *  map:     export a device with a name different from the global zone
928  * Note: We may consider supporting VOP_SYMLINK in non-global instances,
929  *	because it does not present any security risk. For now, the fs
930  *	instance is read only.
931  */
932 static void
sdev_process_profile(struct sdev_data * sdev_data,nvlist_t * profile)933 sdev_process_profile(struct sdev_data *sdev_data, nvlist_t *profile)
934 {
935 	nvpair_t *nvpair;
936 	char *nvname, *dname;
937 	struct sdev_node *dir, *gdir;
938 	char **pair;				/* for symlinks and maps */
939 	uint_t nelem;
940 	int rv;
941 
942 	gdir = sdev_origins->sdev_root;	/* root of global /dev */
943 	dir = sdev_data->sdev_root;	/* root of current instance */
944 
945 	ASSERT(profile);
946 
947 	/* process nvpairs in the list */
948 	nvpair = NULL;
949 	while (nvpair = nvlist_next_nvpair(profile, nvpair)) {
950 		nvname = nvpair_name(nvpair);
951 		ASSERT(nvname != NULL);
952 
953 		if (strcmp(nvname, SDEV_NVNAME_INCLUDE) == 0) {
954 			rv = nvpair_value_string(nvpair, &dname);
955 			if (rv != 0) {
956 				cmn_err(CE_WARN, sdev_nvp_val_err,
957 				    rv, nvpair_name(nvpair));
958 				break;
959 			}
960 			process_rule(dir, gdir, dname, NULL,
961 			    PROFILE_TYPE_INCLUDE);
962 		} else if (strcmp(nvname, SDEV_NVNAME_EXCLUDE) == 0) {
963 			rv = nvpair_value_string(nvpair, &dname);
964 			if (rv != 0) {
965 				cmn_err(CE_WARN, sdev_nvp_val_err,
966 				    rv, nvpair_name(nvpair));
967 				break;
968 			}
969 			process_rule(dir, gdir, dname, NULL,
970 			    PROFILE_TYPE_EXCLUDE);
971 		} else if (strcmp(nvname, SDEV_NVNAME_SYMLINK) == 0) {
972 			rv = nvpair_value_string_array(nvpair, &pair, &nelem);
973 			if (rv != 0) {
974 				cmn_err(CE_WARN, sdev_nvp_val_err,
975 				    rv, nvpair_name(nvpair));
976 				break;
977 			}
978 			ASSERT(nelem == 2);
979 			process_rule(dir, gdir, pair[0], pair[1],
980 			    PROFILE_TYPE_SYMLINK);
981 		} else if (strcmp(nvname, SDEV_NVNAME_MAP) == 0) {
982 			rv = nvpair_value_string_array(nvpair, &pair, &nelem);
983 			if (rv != 0) {
984 				cmn_err(CE_WARN, sdev_nvp_val_err,
985 				    rv, nvpair_name(nvpair));
986 				break;
987 			}
988 			process_rule(dir, gdir, pair[1], pair[0],
989 			    PROFILE_TYPE_MAP);
990 		} else if (strcmp(nvname, SDEV_NVNAME_MOUNTPT) != 0) {
991 			cmn_err(CE_WARN, "sdev_process_profile: invalid "
992 			    "nvpair %s\n", nvname);
993 		}
994 	}
995 }
996 
997 /*ARGSUSED*/
998 int
prof_lookup(vnode_t * dvp,char * nm,struct vnode ** vpp,struct cred * cred)999 prof_lookup(vnode_t *dvp, char *nm, struct vnode **vpp, struct cred *cred)
1000 {
1001 	struct sdev_node *ddv = VTOSDEV(dvp);
1002 	struct sdev_node *dv;
1003 	int nmlen;
1004 
1005 	/*
1006 	 * Empty name or ., return node itself.
1007 	 */
1008 	nmlen = strlen(nm);
1009 	if ((nmlen == 0) || ((nmlen == 1) && (nm[0] == '.'))) {
1010 		*vpp = SDEVTOV(ddv);
1011 		VN_HOLD(*vpp);
1012 		return (0);
1013 	}
1014 
1015 	/*
1016 	 * .., return the parent directory
1017 	 */
1018 	if ((nmlen == 2) && (strcmp(nm, "..") == 0)) {
1019 		*vpp = SDEVTOV(ddv->sdev_dotdot);
1020 		VN_HOLD(*vpp);
1021 		return (0);
1022 	}
1023 
1024 	rw_enter(&ddv->sdev_contents, RW_READER);
1025 	dv = sdev_cache_lookup(ddv, nm);
1026 	if (dv == NULL) {
1027 		prof_filldir(ddv);
1028 		dv = sdev_cache_lookup(ddv, nm);
1029 	}
1030 	rw_exit(&ddv->sdev_contents);
1031 	if (dv == NULL) {
1032 		sdcmn_err10(("prof_lookup: %s not found\n", nm));
1033 		return (ENOENT);
1034 	}
1035 
1036 	return (sdev_to_vp(dv, vpp));
1037 }
1038 
1039 /*
1040  * This is invoked after a new filesystem is mounted to define the
1041  * name space. It is also invoked during normal system operation
1042  * to update the name space.
1043  *
1044  * Applications call di_prof_commit() in libdevinfo, which invokes
1045  * modctl(). modctl calls this function. The input is a packed nvlist.
1046  */
1047 int
devname_profile_update(char * packed,size_t packed_sz)1048 devname_profile_update(char *packed, size_t packed_sz)
1049 {
1050 	char *mntpt;
1051 	nvlist_t *nvl;
1052 	nvpair_t *nvp;
1053 	struct sdev_data *mntinfo;
1054 	int err;
1055 	int rv;
1056 
1057 	nvl = NULL;
1058 	if ((err = copyin_nvlist(packed, packed_sz, &nvl)) != 0)
1059 		return (err);
1060 	ASSERT(nvl);
1061 
1062 	/* The first nvpair must be the mount point */
1063 	nvp = nvlist_next_nvpair(nvl, NULL);
1064 	if (strcmp(nvpair_name(nvp), SDEV_NVNAME_MOUNTPT) != 0) {
1065 		cmn_err(CE_NOTE,
1066 		    "devname_profile_update: mount point not specified");
1067 		nvlist_free(nvl);
1068 		return (EINVAL);
1069 	}
1070 
1071 	/* find the matching filesystem instance */
1072 	rv = nvpair_value_string(nvp, &mntpt);
1073 	if (rv != 0) {
1074 		cmn_err(CE_WARN, sdev_nvp_val_err,
1075 		    rv, nvpair_name(nvp));
1076 	} else {
1077 		mntinfo = sdev_find_mntinfo(mntpt);
1078 		if (mntinfo == NULL) {
1079 			cmn_err(CE_NOTE, "devname_profile_update: "
1080 			    " mount point %s not found", mntpt);
1081 			nvlist_free(nvl);
1082 			return (EINVAL);
1083 		}
1084 
1085 		/* now do the hardwork to process the profile */
1086 		sdev_process_profile(mntinfo, nvl);
1087 
1088 		sdev_mntinfo_rele(mntinfo);
1089 	}
1090 
1091 	nvlist_free(nvl);
1092 	return (0);
1093 }
1094