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