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 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 /* 29 * vnode ops for the /dev filesystem 30 * 31 * - VDIR, VCHR, CBLK, and VLNK are considered must supported files 32 * - VREG and VDOOR are used for some internal implementations in 33 * the global zone, e.g. devname and devfsadm communication 34 * - other file types are unusual in this namespace and 35 * not supported for now 36 */ 37 38 #include <sys/types.h> 39 #include <sys/param.h> 40 #include <sys/t_lock.h> 41 #include <sys/systm.h> 42 #include <sys/sysmacros.h> 43 #include <sys/user.h> 44 #include <sys/time.h> 45 #include <sys/vfs.h> 46 #include <sys/vnode.h> 47 #include <sys/file.h> 48 #include <sys/fcntl.h> 49 #include <sys/flock.h> 50 #include <sys/kmem.h> 51 #include <sys/uio.h> 52 #include <sys/errno.h> 53 #include <sys/stat.h> 54 #include <sys/cred.h> 55 #include <sys/cred_impl.h> 56 #include <sys/dirent.h> 57 #include <sys/pathname.h> 58 #include <sys/cmn_err.h> 59 #include <sys/debug.h> 60 #include <sys/policy.h> 61 #include <vm/hat.h> 62 #include <vm/seg_vn.h> 63 #include <vm/seg_map.h> 64 #include <vm/seg.h> 65 #include <vm/as.h> 66 #include <vm/page.h> 67 #include <sys/proc.h> 68 #include <sys/mode.h> 69 #include <sys/sunndi.h> 70 #include <sys/ptms.h> 71 #include <fs/fs_subr.h> 72 #include <sys/fs/dv_node.h> 73 #include <sys/fs/sdev_impl.h> 74 #include <sys/fs/sdev_node.h> 75 76 /*ARGSUSED*/ 77 static int 78 sdev_open(struct vnode **vpp, int flag, struct cred *cred) 79 { 80 struct sdev_node *dv = VTOSDEV(*vpp); 81 struct sdev_node *ddv = dv->sdev_dotdot; 82 int error = 0; 83 84 if ((*vpp)->v_type == VDIR) 85 return (0); 86 87 if (!SDEV_IS_GLOBAL(dv)) 88 return (ENOTSUP); 89 90 ASSERT((*vpp)->v_type == VREG); 91 if ((*vpp)->v_type != VREG) 92 return (ENOTSUP); 93 94 ASSERT(ddv); 95 rw_enter(&ddv->sdev_contents, RW_READER); 96 if (dv->sdev_attrvp == NULL) { 97 rw_exit(&ddv->sdev_contents); 98 return (ENOENT); 99 } 100 error = VOP_OPEN(&(dv->sdev_attrvp), flag, cred); 101 rw_exit(&ddv->sdev_contents); 102 return (error); 103 } 104 105 /*ARGSUSED1*/ 106 static int 107 sdev_close(struct vnode *vp, int flag, int count, 108 offset_t offset, struct cred *cred) 109 { 110 struct sdev_node *dv = VTOSDEV(vp); 111 112 if (vp->v_type == VDIR) { 113 cleanlocks(vp, ttoproc(curthread)->p_pid, 0); 114 cleanshares(vp, ttoproc(curthread)->p_pid); 115 return (0); 116 } 117 118 if (!SDEV_IS_GLOBAL(dv)) 119 return (ENOTSUP); 120 121 ASSERT(vp->v_type == VREG); 122 if (vp->v_type != VREG) 123 return (ENOTSUP); 124 125 ASSERT(dv->sdev_attrvp); 126 return (VOP_CLOSE(dv->sdev_attrvp, flag, count, offset, cred)); 127 } 128 129 /*ARGSUSED*/ 130 static int 131 sdev_read(struct vnode *vp, struct uio *uio, int ioflag, struct cred *cred, 132 struct caller_context *ct) 133 { 134 struct sdev_node *dv = (struct sdev_node *)VTOSDEV(vp); 135 int error; 136 137 if (!SDEV_IS_GLOBAL(dv)) 138 return (EINVAL); 139 140 if (vp->v_type == VDIR) 141 return (EISDIR); 142 143 /* only supporting regular files in /dev */ 144 ASSERT(vp->v_type == VREG); 145 if (vp->v_type != VREG) 146 return (EINVAL); 147 148 ASSERT(RW_READ_HELD(&VTOSDEV(vp)->sdev_contents)); 149 ASSERT(dv->sdev_attrvp); 150 (void) VOP_RWLOCK(dv->sdev_attrvp, 0, NULL); 151 error = VOP_READ(dv->sdev_attrvp, uio, ioflag, cred, ct); 152 VOP_RWUNLOCK(dv->sdev_attrvp, 0, NULL); 153 return (error); 154 } 155 156 /*ARGSUSED*/ 157 static int 158 sdev_write(struct vnode *vp, struct uio *uio, int ioflag, struct cred *cred, 159 struct caller_context *ct) 160 { 161 struct sdev_node *dv = VTOSDEV(vp); 162 int error = 0; 163 164 if (!SDEV_IS_GLOBAL(dv)) 165 return (EINVAL); 166 167 if (vp->v_type == VDIR) 168 return (EISDIR); 169 170 /* only supporting regular files in /dev */ 171 ASSERT(vp->v_type == VREG); 172 if (vp->v_type != VREG) 173 return (EINVAL); 174 175 ASSERT(dv->sdev_attrvp); 176 177 (void) VOP_RWLOCK(dv->sdev_attrvp, 1, NULL); 178 error = VOP_WRITE(dv->sdev_attrvp, uio, ioflag, cred, ct); 179 VOP_RWUNLOCK(dv->sdev_attrvp, 1, NULL); 180 if (error == 0) { 181 sdev_update_timestamps(dv->sdev_attrvp, kcred, 182 AT_MTIME); 183 } 184 return (error); 185 } 186 187 /*ARGSUSED*/ 188 static int 189 sdev_ioctl(struct vnode *vp, int cmd, intptr_t arg, int flag, 190 struct cred *cred, int *rvalp) 191 { 192 struct sdev_node *dv = VTOSDEV(vp); 193 194 if (!SDEV_IS_GLOBAL(dv) || (vp->v_type == VDIR)) 195 return (ENOTTY); 196 197 ASSERT(vp->v_type == VREG); 198 if (vp->v_type != VREG) 199 return (EINVAL); 200 201 ASSERT(dv->sdev_attrvp); 202 return (VOP_IOCTL(dv->sdev_attrvp, cmd, arg, flag, cred, rvalp)); 203 } 204 205 static int 206 sdev_getattr(struct vnode *vp, struct vattr *vap, int flags, struct cred *cr) 207 { 208 int error = 0; 209 struct sdev_node *dv = VTOSDEV(vp); 210 struct sdev_node *parent = dv->sdev_dotdot; 211 struct devname_nsmap *map = NULL; 212 struct devname_ops *dirops = NULL; 213 int (*fn)(devname_handle_t *, struct vattr *, struct cred *); 214 215 ASSERT(parent); 216 217 rw_enter(&parent->sdev_contents, RW_READER); 218 ASSERT(dv->sdev_attr || dv->sdev_attrvp); 219 if (SDEV_IS_GLOBAL(dv) && (dv->sdev_state != SDEV_ZOMBIE)) { 220 map = sdev_get_map(parent, 0); 221 dirops = map ? map->dir_ops : NULL; 222 } 223 224 /* 225 * search order: 226 * - for persistent nodes (SDEV_PERSIST): backstore 227 * - for non-persistent nodes: module ops if global, then memory 228 */ 229 if (dv->sdev_attrvp) { 230 rw_exit(&parent->sdev_contents); 231 error = VOP_GETATTR(dv->sdev_attrvp, vap, flags, cr); 232 sdev_vattr_merge(dv, vap); 233 } else if (dirops && (fn = dirops->devnops_getattr)) { 234 sdev_vattr_merge(dv, vap); 235 rw_exit(&parent->sdev_contents); 236 error = (*fn)(&(dv->sdev_handle), vap, cr); 237 } else { 238 ASSERT(dv->sdev_attr); 239 *vap = *dv->sdev_attr; 240 sdev_vattr_merge(dv, vap); 241 rw_exit(&parent->sdev_contents); 242 } 243 244 return (error); 245 } 246 247 static int 248 sdev_setattr(struct vnode *vp, struct vattr *vap, int flags, struct cred *cred) 249 { 250 return (devname_setattr_func(vp, vap, flags, cred, NULL, 0)); 251 } 252 253 static int 254 sdev_getsecattr(struct vnode *vp, struct vsecattr *vsap, int flags, 255 struct cred *cr) 256 { 257 int error; 258 struct sdev_node *dv = VTOSDEV(vp); 259 struct vnode *avp = dv->sdev_attrvp; 260 261 if (avp == NULL) { 262 /* return fs_fab_acl() if flavor matches, else do nothing */ 263 if ((SDEV_ACL_FLAVOR(vp) == _ACL_ACLENT_ENABLED && 264 (vsap->vsa_mask & (VSA_ACLCNT | VSA_DFACLCNT))) || 265 (SDEV_ACL_FLAVOR(vp) == _ACL_ACE_ENABLED && 266 (vsap->vsa_mask & (VSA_ACECNT | VSA_ACE)))) 267 return (fs_fab_acl(vp, vsap, flags, cr)); 268 269 return (ENOSYS); 270 } 271 272 (void) VOP_RWLOCK(avp, 1, NULL); 273 error = VOP_GETSECATTR(avp, vsap, flags, cr); 274 VOP_RWUNLOCK(avp, 1, NULL); 275 return (error); 276 } 277 278 static int 279 sdev_setsecattr(struct vnode *vp, struct vsecattr *vsap, int flags, 280 struct cred *cr) 281 { 282 int error; 283 struct sdev_node *dv = VTOSDEV(vp); 284 struct vnode *avp = dv->sdev_attrvp; 285 286 if (dv->sdev_state == SDEV_ZOMBIE) 287 return (0); 288 289 if (avp == NULL) { 290 if (SDEV_IS_GLOBAL(dv) && !SDEV_IS_PERSIST(dv)) 291 return (fs_nosys()); 292 293 ASSERT(dv->sdev_attr); 294 /* 295 * if coming in directly, the acl system call will 296 * have held the read-write lock via VOP_RWLOCK() 297 * If coming in via specfs, specfs will have 298 * held the rw lock on the realvp i.e. us. 299 */ 300 ASSERT(RW_WRITE_HELD(&dv->sdev_contents)); 301 sdev_vattr_merge(dv, dv->sdev_attr); 302 error = sdev_shadow_node(dv, cr); 303 if (error) { 304 return (fs_nosys()); 305 } 306 307 ASSERT(dv->sdev_attrvp); 308 /* clean out the memory copy if any */ 309 if (dv->sdev_attr) { 310 kmem_free(dv->sdev_attr, sizeof (struct vattr)); 311 dv->sdev_attr = NULL; 312 } 313 avp = dv->sdev_attrvp; 314 } 315 ASSERT(avp); 316 317 (void) VOP_RWLOCK(avp, V_WRITELOCK_TRUE, NULL); 318 error = VOP_SETSECATTR(avp, vsap, flags, cr); 319 VOP_RWUNLOCK(avp, V_WRITELOCK_TRUE, NULL); 320 return (error); 321 } 322 323 int 324 sdev_unlocked_access(void *vdv, int mode, struct cred *cr) 325 { 326 struct sdev_node *dv = vdv; 327 int shift = 0; 328 uid_t owner = dv->sdev_attr->va_uid; 329 330 if (crgetuid(cr) != owner) { 331 shift += 3; 332 if (groupmember(dv->sdev_attr->va_gid, cr) == 0) 333 shift += 3; 334 } 335 336 mode &= ~(dv->sdev_attr->va_mode << shift); 337 if (mode == 0) 338 return (0); 339 340 return (secpolicy_vnode_access(cr, SDEVTOV(dv), owner, mode)); 341 } 342 343 static int 344 sdev_access(struct vnode *vp, int mode, int flags, struct cred *cr) 345 { 346 struct sdev_node *dv = VTOSDEV(vp); 347 int ret = 0; 348 349 ASSERT(dv->sdev_attr || dv->sdev_attrvp); 350 351 if (dv->sdev_attrvp) { 352 ret = VOP_ACCESS(dv->sdev_attrvp, mode, flags, cr); 353 } else if (dv->sdev_attr) { 354 rw_enter(&dv->sdev_contents, RW_READER); 355 ret = sdev_unlocked_access(dv, mode, cr); 356 if (ret) 357 ret = EACCES; 358 rw_exit(&dv->sdev_contents); 359 } 360 361 return (ret); 362 } 363 364 /* 365 * Lookup 366 */ 367 /*ARGSUSED3*/ 368 static int 369 sdev_lookup(struct vnode *dvp, char *nm, struct vnode **vpp, 370 struct pathname *pnp, int flags, struct vnode *rdir, struct cred *cred) 371 { 372 struct sdev_node *parent; 373 374 parent = VTOSDEV(dvp); 375 ASSERT(parent); 376 377 if (!SDEV_IS_GLOBAL(parent)) 378 return (prof_lookup(dvp, nm, vpp, cred)); 379 return (devname_lookup_func(parent, nm, vpp, cred, NULL, 0)); 380 } 381 382 /*ARGSUSED2*/ 383 static int 384 sdev_create(struct vnode *dvp, char *nm, struct vattr *vap, vcexcl_t excl, 385 int mode, struct vnode **vpp, struct cred *cred, int flag) 386 { 387 struct vnode *vp = NULL; 388 struct vnode *avp; 389 struct sdev_node *parent; 390 struct sdev_node *self = NULL; 391 int error = 0; 392 vtype_t type = vap->va_type; 393 394 ASSERT(vap->va_type != VNON && 395 vap->va_type != VBAD); 396 397 if ((type == VFIFO) || (type == VSOCK) || 398 (type == VPROC) || (type == VPORT)) 399 return (ENOTSUP); 400 401 parent = VTOSDEV(dvp); 402 ASSERT(parent); 403 404 rw_enter(&parent->sdev_dotdot->sdev_contents, RW_READER); 405 if (parent->sdev_state == SDEV_ZOMBIE) { 406 rw_exit(&parent->sdev_dotdot->sdev_contents); 407 return (ENOENT); 408 } 409 410 /* non-global do not allow pure node creation */ 411 if (!SDEV_IS_GLOBAL(parent)) { 412 rw_exit(&parent->sdev_dotdot->sdev_contents); 413 return (prof_lookup(dvp, nm, vpp, cred)); 414 } 415 rw_exit(&parent->sdev_dotdot->sdev_contents); 416 417 again: 418 /* check existing name */ 419 error = VOP_LOOKUP(dvp, nm, &vp, NULL, 0, NULL, cred); 420 421 /* name found */ 422 if (error == 0) { 423 ASSERT(vp); 424 if (excl == EXCL) { 425 error = EEXIST; 426 } else if ((vp->v_type == VDIR) && (mode & VWRITE)) { 427 /* allowing create/read-only an existing directory */ 428 error = EISDIR; 429 } else { 430 error = VOP_ACCESS(vp, mode, flag, cred); 431 } 432 433 if (error) { 434 VN_RELE(vp); 435 return (error); 436 } 437 438 /* truncation first */ 439 if ((vp->v_type == VREG) && (vap->va_mask & AT_SIZE) && 440 (vap->va_size == 0)) { 441 ASSERT(parent->sdev_attrvp); 442 ASSERT(VTOSDEV(vp)->sdev_attrvp); 443 error = VOP_CREATE(parent->sdev_attrvp, 444 nm, vap, excl, mode, &avp, cred, flag); 445 446 if (error) { 447 VN_RELE(vp); 448 return (error); 449 } 450 } 451 452 sdev_update_timestamps(vp, kcred, 453 AT_CTIME|AT_MTIME|AT_ATIME); 454 *vpp = vp; 455 return (0); 456 } 457 458 /* bail out early */ 459 if (error != ENOENT) 460 return (error); 461 462 /* 463 * For memory-based (ROFS) directory: 464 * - either disallow node creation; 465 * - or implement VOP_CREATE of its own 466 */ 467 rw_enter(&parent->sdev_contents, RW_WRITER); 468 if (!SDEV_IS_PERSIST(parent)) { 469 rw_exit(&parent->sdev_contents); 470 return (ENOTSUP); 471 } 472 ASSERT(parent->sdev_attrvp); 473 error = sdev_mknode(parent, nm, &self, vap, NULL, NULL, 474 cred, SDEV_READY); 475 if (error) { 476 rw_exit(&parent->sdev_contents); 477 if (self) 478 SDEV_RELE(self); 479 return (error); 480 } 481 rw_exit(&parent->sdev_contents); 482 483 ASSERT(self); 484 /* take care the timestamps for the node and its parent */ 485 sdev_update_timestamps(SDEVTOV(self), kcred, 486 AT_CTIME|AT_MTIME|AT_ATIME); 487 sdev_update_timestamps(dvp, kcred, AT_MTIME|AT_ATIME); 488 if (SDEV_IS_GLOBAL(parent)) 489 atomic_inc_ulong(&parent->sdev_gdir_gen); 490 491 /* wake up other threads blocked on looking up this node */ 492 mutex_enter(&self->sdev_lookup_lock); 493 SDEV_UNBLOCK_OTHERS(self, SDEV_LOOKUP); 494 mutex_exit(&self->sdev_lookup_lock); 495 error = sdev_to_vp(self, vpp); 496 return (error); 497 } 498 499 static int 500 sdev_remove(struct vnode *dvp, char *nm, struct cred *cred) 501 { 502 int error; 503 struct sdev_node *parent = (struct sdev_node *)VTOSDEV(dvp); 504 struct vnode *vp = NULL; 505 struct sdev_node *dv = NULL; 506 struct devname_nsmap *map = NULL; 507 struct devname_ops *dirops = NULL; 508 int (*fn)(devname_handle_t *); 509 int len; 510 int bkstore = 0; 511 512 /* bail out early */ 513 len = strlen(nm); 514 if (nm[0] == '.') { 515 if (len == 1) { 516 return (EINVAL); 517 } else if (len == 2 && nm[1] == '.') { 518 return (EEXIST); 519 } 520 } 521 522 ASSERT(parent); 523 rw_enter(&parent->sdev_contents, RW_READER); 524 if (!SDEV_IS_GLOBAL(parent)) { 525 rw_exit(&parent->sdev_contents); 526 return (ENOTSUP); 527 } 528 529 /* check existence first */ 530 dv = sdev_cache_lookup(parent, nm); 531 if (dv == NULL) { 532 rw_exit(&parent->sdev_contents); 533 return (ENOENT); 534 } 535 536 vp = SDEVTOV(dv); 537 if ((dv->sdev_state == SDEV_INIT) || 538 (dv->sdev_state == SDEV_ZOMBIE)) { 539 rw_exit(&parent->sdev_contents); 540 VN_RELE(vp); 541 return (ENOENT); 542 } 543 544 /* the module may record/reject removing a device node */ 545 map = sdev_get_map(parent, 0); 546 dirops = map ? map->dir_ops : NULL; 547 if (dirops && ((fn = dirops->devnops_remove) != NULL)) { 548 error = (*fn)(&(dv->sdev_handle)); 549 if (error) { 550 rw_exit(&parent->sdev_contents); 551 VN_RELE(vp); 552 return (error); 553 } 554 } 555 556 /* 557 * sdev_dirdelete does the real job of: 558 * - make sure no open ref count 559 * - destroying the sdev_node 560 * - releasing the hold on attrvp 561 */ 562 bkstore = SDEV_IS_PERSIST(dv) ? 1 : 0; 563 if (!rw_tryupgrade(&parent->sdev_contents)) { 564 rw_exit(&parent->sdev_contents); 565 rw_enter(&parent->sdev_contents, RW_WRITER); 566 } 567 error = sdev_cache_update(parent, &dv, nm, SDEV_CACHE_DELETE); 568 rw_exit(&parent->sdev_contents); 569 570 sdcmn_err2(("sdev_remove: cache_update error %d\n", error)); 571 if (error && (error != EBUSY)) { 572 /* report errors other than EBUSY */ 573 VN_RELE(vp); 574 } else { 575 sdcmn_err2(("sdev_remove: cleaning node %s from cache " 576 " with error %d\n", nm, error)); 577 578 /* 579 * best efforts clean up the backing store 580 */ 581 if (bkstore) { 582 ASSERT(parent->sdev_attrvp); 583 error = VOP_REMOVE(parent->sdev_attrvp, nm, cred); 584 /* 585 * do not report BUSY error 586 * because the backing store ref count is released 587 * when the last ref count on the sdev_node is 588 * released. 589 */ 590 if (error == EBUSY) { 591 sdcmn_err2(("sdev_remove: device %s is still on" 592 "disk %s\n", nm, parent->sdev_path)); 593 error = 0; 594 } 595 } 596 597 if (error == EBUSY) 598 error = 0; 599 } 600 601 return (error); 602 } 603 604 /* 605 * Some restrictions for this file system: 606 * - both oldnm and newnm are in the scope of /dev file system, 607 * to simply the namespace management model. 608 */ 609 static int 610 sdev_rename(struct vnode *odvp, char *onm, struct vnode *ndvp, char *nnm, 611 struct cred *cred) 612 { 613 struct sdev_node *fromparent = NULL; 614 struct vattr vattr; 615 struct sdev_node *toparent; 616 struct sdev_node *fromdv = NULL; /* source node */ 617 struct vnode *ovp; /* source vnode */ 618 struct sdev_node *todv = NULL; /* destination node */ 619 struct vnode *nvp; /* destination vnode */ 620 int samedir = 0; /* set if odvp == ndvp */ 621 struct vnode *realvp; 622 int len; 623 char nnm_path[MAXPATHLEN]; 624 struct devname_nsmap *omap = NULL; 625 struct devname_ops *odirops = NULL; 626 int (*fn)(devname_handle_t *, char *); 627 int (*rmfn)(devname_handle_t *); 628 int error = 0; 629 dev_t fsid; 630 int bkstore = 0; 631 632 /* prevent modifying "." and ".." */ 633 if ((onm[0] == '.' && 634 (onm[1] == '\0' || (onm[1] == '.' && onm[2] == '\0')))) { 635 return (EINVAL); 636 } 637 638 fromparent = VTOSDEV(odvp); 639 toparent = VTOSDEV(ndvp); 640 641 /* ZOMBIE parent doesn't allow new node creation */ 642 rw_enter(&fromparent->sdev_dotdot->sdev_contents, RW_READER); 643 if (fromparent->sdev_state == SDEV_ZOMBIE) { 644 rw_exit(&fromparent->sdev_dotdot->sdev_contents); 645 return (ENOENT); 646 } 647 648 /* renaming only supported for global device nodes */ 649 if (!SDEV_IS_GLOBAL(fromparent)) { 650 rw_exit(&fromparent->sdev_dotdot->sdev_contents); 651 return (ENOTSUP); 652 } 653 rw_exit(&fromparent->sdev_dotdot->sdev_contents); 654 655 rw_enter(&toparent->sdev_dotdot->sdev_contents, RW_READER); 656 if (toparent->sdev_state == SDEV_ZOMBIE) { 657 rw_exit(&toparent->sdev_dotdot->sdev_contents); 658 return (ENOENT); 659 } 660 rw_exit(&toparent->sdev_dotdot->sdev_contents); 661 662 /* check existence of the source node */ 663 error = VOP_LOOKUP(odvp, onm, &ovp, NULL, 0, NULL, cred); 664 if (error) { 665 sdcmn_err2(("sdev_rename: the source node %s exists\n", 666 onm)); 667 return (error); 668 } 669 670 if (VOP_REALVP(ovp, &realvp) == 0) { 671 VN_HOLD(realvp); 672 VN_RELE(ovp); 673 ovp = realvp; 674 } 675 676 /* check existence of destination */ 677 error = VOP_LOOKUP(ndvp, nnm, &nvp, NULL, 0, NULL, cred); 678 if (error && (error != ENOENT)) { 679 VN_RELE(ovp); 680 return (error); 681 } 682 683 if (nvp && (VOP_REALVP(nvp, &realvp) == 0)) { 684 VN_HOLD(realvp); 685 VN_RELE(nvp); 686 nvp = realvp; 687 } 688 689 /* 690 * For now, if both exist, they must be the same type. 691 * Changing the type of a node probably needs some special 692 * handling. 693 */ 694 if (ovp && nvp) { 695 if (ovp->v_type != nvp->v_type) { 696 VN_RELE(ovp); 697 VN_RELE(nvp); 698 return (EINVAL); 699 } 700 } 701 702 /* make sure the source and the destination are in /dev */ 703 if (odvp != ndvp) { 704 vattr.va_mask = AT_FSID; 705 if (error = VOP_GETATTR(odvp, &vattr, 0, cred)) { 706 VN_RELE(ovp); 707 return (error); 708 } 709 fsid = vattr.va_fsid; 710 vattr.va_mask = AT_FSID; 711 if (error = VOP_GETATTR(ndvp, &vattr, 0, cred)) { 712 VN_RELE(ovp); 713 return (error); 714 } 715 if (fsid != vattr.va_fsid) { 716 VN_RELE(ovp); 717 return (EXDEV); 718 } 719 } 720 721 /* make sure the old entry can be deleted */ 722 error = VOP_ACCESS(odvp, VWRITE, 0, cred); 723 if (error) { 724 VN_RELE(ovp); 725 return (error); 726 } 727 728 /* make sure the destination allows creation */ 729 samedir = (fromparent == toparent); 730 if (!samedir) { 731 error = VOP_ACCESS(ndvp, VEXEC|VWRITE, 0, cred); 732 if (error) { 733 VN_RELE(ovp); 734 return (error); 735 } 736 } 737 738 fromdv = VTOSDEV(ovp); 739 ASSERT(fromdv); 740 741 /* check with the plug-in modules for the source directory */ 742 rw_enter(&fromparent->sdev_contents, RW_READER); 743 omap = sdev_get_map(fromparent, 0); 744 rw_exit(&fromparent->sdev_contents); 745 odirops = omap ? omap->dir_ops : NULL; 746 if (odirops && ((fn = odirops->devnops_rename) != NULL)) { 747 if (samedir) { 748 error = (*fn)(&(fromdv->sdev_handle), nnm); 749 } else { 750 len = strlen(nnm) + strlen(toparent->sdev_name) + 2; 751 (void) snprintf(nnm_path, len, "%s/%s", 752 toparent->sdev_name, nnm); 753 error = (*fn)(&(fromdv->sdev_handle), nnm); 754 } 755 756 if (error) { 757 VN_RELE(ovp); 758 return (error); 759 } 760 } 761 762 /* 763 * Remove the destination if exist 764 * On failure, we should attempt to restore the current state 765 * before returning error. 766 */ 767 if (nvp) { 768 switch (nvp->v_type) { 769 case VDIR: 770 error = VOP_RMDIR(ndvp, nnm, ndvp, cred); 771 break; 772 default: 773 error = VOP_REMOVE(ndvp, nnm, cred); 774 break; 775 } 776 777 if (error) { 778 sdcmn_err2(("sdev_rename: removing existing destination" 779 " %s failed, error %d\n", nnm, error)); 780 VN_RELE(ovp); 781 VN_RELE(nvp); 782 return (error); 783 } 784 } 785 786 /* 787 * link source to new target in the memory 788 */ 789 error = VOP_LOOKUP(ndvp, nnm, &nvp, NULL, 0, NULL, cred); 790 if (error && (error != ENOENT)) { 791 VN_RELE(ovp); 792 return (error); 793 } else if (error == ENOENT) { 794 /* make a new node from the old node */ 795 error = sdev_rnmnode(fromparent, fromdv, toparent, &todv, 796 nnm, cred); 797 } else { 798 ASSERT(nvp); 799 if (VOP_REALVP(nvp, &realvp) == 0) { 800 VN_HOLD(realvp); 801 VN_RELE(nvp); 802 nvp = realvp; 803 } 804 805 /* destination file exists */ 806 todv = VTOSDEV(nvp); 807 ASSERT(todv); 808 error = sdev_rnmnode(fromparent, fromdv, toparent, &todv, 809 nnm, cred); 810 if (error) { 811 sdcmn_err2(("sdev_rename: renaming %s to %s failed " 812 " with existing destination error %d\n", 813 onm, nnm, error)); 814 VN_RELE(nvp); 815 VN_RELE(ovp); 816 return (error); 817 } 818 } 819 820 /* unlink from source */ 821 if (error == 0) { 822 /* 823 * check with the plug-in module whether source can be 824 * re-used or not 825 */ 826 if (odirops && ((rmfn = odirops->devnops_remove) != NULL)) { 827 error = (*rmfn)(&(fromdv->sdev_handle)); 828 } 829 830 if (error == 0) { 831 bkstore = SDEV_IS_PERSIST(fromdv) ? 1 : 0; 832 rw_enter(&fromparent->sdev_contents, RW_WRITER); 833 error = sdev_cache_update(fromparent, &fromdv, onm, 834 SDEV_CACHE_DELETE); 835 rw_exit(&fromparent->sdev_contents); 836 837 /* best effforts clean up the backing store */ 838 if (bkstore) { 839 ASSERT(fromparent->sdev_attrvp); 840 error = VOP_REMOVE(fromparent->sdev_attrvp, 841 onm, kcred); 842 if (error) { 843 sdcmn_err2(("sdev_rename: device %s is " 844 "still on disk %s\n", onm, 845 fromparent->sdev_path)); 846 error = 0; 847 } 848 } 849 850 if (error == EBUSY) { 851 error = 0; 852 } 853 } 854 } 855 856 /* book keeping the ovp v_count */ 857 if (error) { 858 sdcmn_err2(("sdev_rename: renaming %s to %s failed " 859 " with error %d\n", onm, nnm, error)); 860 VN_RELE(ovp); 861 } 862 863 return (error); 864 } 865 866 /* 867 * dev-fs version of "ln -s path dev-name" 868 * tnm - path, e.g. /devices/... or /dev/... 869 * lnm - dev_name 870 */ 871 static int 872 sdev_symlink(struct vnode *dvp, char *lnm, struct vattr *tva, 873 char *tnm, struct cred *cred) 874 { 875 int error; 876 struct vnode *vp = NULL; 877 struct sdev_node *parent = (struct sdev_node *)VTOSDEV(dvp); 878 struct sdev_node *self = (struct sdev_node *)NULL; 879 880 ASSERT(parent); 881 rw_enter(&parent->sdev_dotdot->sdev_contents, RW_READER); 882 if (parent->sdev_state == SDEV_ZOMBIE) { 883 rw_exit(&parent->sdev_dotdot->sdev_contents); 884 sdcmn_err2(("sdev_symlink: parent %s is ZOMBIED \n", 885 parent->sdev_name)); 886 return (ENOENT); 887 } 888 889 if (!SDEV_IS_GLOBAL(parent)) { 890 rw_exit(&parent->sdev_dotdot->sdev_contents); 891 return (ENOTSUP); 892 } 893 rw_exit(&parent->sdev_dotdot->sdev_contents); 894 895 /* find existing name */ 896 error = VOP_LOOKUP(dvp, lnm, &vp, NULL, 0, NULL, cred); 897 if (error == 0) { 898 ASSERT(vp); 899 VN_RELE(vp); 900 sdcmn_err2(("sdev_symlink: node %s already exists\n", lnm)); 901 return (EEXIST); 902 } 903 904 if (error != ENOENT) { 905 return (error); 906 } 907 908 /* put it into memory cache */ 909 rw_enter(&parent->sdev_contents, RW_WRITER); 910 error = sdev_mknode(parent, lnm, &self, tva, NULL, (void *)tnm, 911 cred, SDEV_READY); 912 if (error) { 913 rw_exit(&parent->sdev_contents); 914 sdcmn_err2(("sdev_symlink: node %s creation failed\n", lnm)); 915 if (self) 916 SDEV_RELE(self); 917 918 return (error); 919 } 920 ASSERT(self && (self->sdev_state == SDEV_READY)); 921 rw_exit(&parent->sdev_contents); 922 923 /* take care the timestamps for the node and its parent */ 924 sdev_update_timestamps(SDEVTOV(self), kcred, 925 AT_CTIME|AT_MTIME|AT_ATIME); 926 sdev_update_timestamps(dvp, kcred, AT_MTIME|AT_ATIME); 927 if (SDEV_IS_GLOBAL(parent)) 928 atomic_inc_ulong(&parent->sdev_gdir_gen); 929 930 /* wake up other threads blocked on looking up this node */ 931 mutex_enter(&self->sdev_lookup_lock); 932 SDEV_UNBLOCK_OTHERS(self, SDEV_LOOKUP); 933 mutex_exit(&self->sdev_lookup_lock); 934 SDEV_RELE(self); /* don't return with vnode held */ 935 return (0); 936 } 937 938 static int 939 sdev_mkdir(struct vnode *dvp, char *nm, struct vattr *va, struct vnode **vpp, 940 struct cred *cred) 941 { 942 int error; 943 struct sdev_node *parent = (struct sdev_node *)VTOSDEV(dvp); 944 struct sdev_node *self = NULL; 945 struct vnode *vp = NULL; 946 947 ASSERT(parent && parent->sdev_dotdot); 948 rw_enter(&parent->sdev_dotdot->sdev_contents, RW_READER); 949 if (parent->sdev_state == SDEV_ZOMBIE) { 950 rw_exit(&parent->sdev_dotdot->sdev_contents); 951 return (ENOENT); 952 } 953 954 /* non-global do not allow pure directory creation */ 955 if (!SDEV_IS_GLOBAL(parent)) { 956 rw_exit(&parent->sdev_dotdot->sdev_contents); 957 return (prof_lookup(dvp, nm, vpp, cred)); 958 } 959 rw_exit(&parent->sdev_dotdot->sdev_contents); 960 961 /* find existing name */ 962 error = VOP_LOOKUP(dvp, nm, &vp, NULL, 0, NULL, cred); 963 if (error == 0) { 964 VN_RELE(vp); 965 return (EEXIST); 966 } 967 968 if (error != ENOENT) 969 return (error); 970 971 /* put it into memory */ 972 rw_enter(&parent->sdev_contents, RW_WRITER); 973 error = sdev_mknode(parent, nm, &self, 974 va, NULL, NULL, cred, SDEV_READY); 975 if (error) { 976 rw_exit(&parent->sdev_contents); 977 if (self) 978 SDEV_RELE(self); 979 return (error); 980 } 981 ASSERT(self && (self->sdev_state == SDEV_READY)); 982 rw_exit(&parent->sdev_contents); 983 984 /* take care the timestamps for the node and its parent */ 985 sdev_update_timestamps(SDEVTOV(self), kcred, 986 AT_CTIME|AT_MTIME|AT_ATIME); 987 sdev_update_timestamps(dvp, kcred, AT_MTIME|AT_ATIME); 988 if (SDEV_IS_GLOBAL(parent)) 989 atomic_inc_ulong(&parent->sdev_gdir_gen); 990 991 /* wake up other threads blocked on looking up this node */ 992 mutex_enter(&self->sdev_lookup_lock); 993 SDEV_UNBLOCK_OTHERS(self, SDEV_LOOKUP); 994 mutex_exit(&self->sdev_lookup_lock); 995 *vpp = SDEVTOV(self); 996 return (0); 997 } 998 999 /* 1000 * allowing removing an empty directory under /dev 1001 */ 1002 /*ARGSUSED*/ 1003 static int 1004 sdev_rmdir(struct vnode *dvp, char *nm, struct vnode *cdir, struct cred *cred) 1005 { 1006 int error = 0; 1007 struct sdev_node *parent = (struct sdev_node *)VTOSDEV(dvp); 1008 struct sdev_node *self = NULL; 1009 struct vnode *vp = NULL; 1010 1011 /* bail out early */ 1012 if (strcmp(nm, ".") == 0) 1013 return (EINVAL); 1014 if (strcmp(nm, "..") == 0) 1015 return (EEXIST); /* should be ENOTEMPTY */ 1016 1017 /* no destruction of non-global node */ 1018 ASSERT(parent && parent->sdev_dotdot); 1019 rw_enter(&parent->sdev_dotdot->sdev_contents, RW_READER); 1020 if (!SDEV_IS_GLOBAL(parent)) { 1021 rw_exit(&parent->sdev_dotdot->sdev_contents); 1022 return (ENOTSUP); 1023 } 1024 rw_exit(&parent->sdev_dotdot->sdev_contents); 1025 1026 /* check existing name */ 1027 rw_enter(&parent->sdev_contents, RW_WRITER); 1028 self = sdev_cache_lookup(parent, nm); 1029 if (self == NULL) { 1030 rw_exit(&parent->sdev_contents); 1031 return (ENOENT); 1032 } 1033 1034 vp = SDEVTOV(self); 1035 if ((self->sdev_state == SDEV_INIT) || 1036 (self->sdev_state == SDEV_ZOMBIE)) { 1037 rw_exit(&parent->sdev_contents); 1038 VN_RELE(vp); 1039 return (ENOENT); 1040 } 1041 1042 /* some sanity checks */ 1043 if (vp == dvp || vp == cdir) { 1044 rw_exit(&parent->sdev_contents); 1045 VN_RELE(vp); 1046 return (EINVAL); 1047 } 1048 1049 if (vp->v_type != VDIR) { 1050 rw_exit(&parent->sdev_contents); 1051 VN_RELE(vp); 1052 return (ENOTDIR); 1053 } 1054 1055 if (vn_vfswlock(vp)) { 1056 rw_exit(&parent->sdev_contents); 1057 VN_RELE(vp); 1058 return (EBUSY); 1059 } 1060 1061 if (vn_mountedvfs(vp) != NULL) { 1062 rw_exit(&parent->sdev_contents); 1063 vn_vfsunlock(vp); 1064 VN_RELE(vp); 1065 return (EBUSY); 1066 } 1067 1068 self = VTOSDEV(vp); 1069 /* bail out on a non-empty directory */ 1070 rw_enter(&self->sdev_contents, RW_READER); 1071 if (self->sdev_nlink > 2) { 1072 rw_exit(&self->sdev_contents); 1073 rw_exit(&parent->sdev_contents); 1074 vn_vfsunlock(vp); 1075 VN_RELE(vp); 1076 return (ENOTEMPTY); 1077 } 1078 rw_exit(&self->sdev_contents); 1079 1080 /* unlink it from the directory cache */ 1081 error = sdev_cache_update(parent, &self, nm, SDEV_CACHE_DELETE); 1082 rw_exit(&parent->sdev_contents); 1083 vn_vfsunlock(vp); 1084 1085 if (error && (error != EBUSY)) { 1086 VN_RELE(vp); 1087 } else { 1088 sdcmn_err2(("sdev_rmdir: cleaning node %s from directory " 1089 " cache with error %d\n", nm, error)); 1090 1091 /* best effort to clean up the backing store */ 1092 if (SDEV_IS_PERSIST(parent)) { 1093 ASSERT(parent->sdev_attrvp); 1094 error = VOP_RMDIR(parent->sdev_attrvp, nm, 1095 parent->sdev_attrvp, kcred); 1096 sdcmn_err2(("sdev_rmdir: cleaning device %s is on" 1097 " disk error %d\n", parent->sdev_path, error)); 1098 } 1099 1100 if (error == EBUSY) 1101 error = 0; 1102 } 1103 1104 return (error); 1105 } 1106 1107 /* 1108 * read the contents of a symbolic link 1109 */ 1110 static int 1111 sdev_readlink(struct vnode *vp, struct uio *uiop, struct cred *cred) 1112 { 1113 struct sdev_node *dv; 1114 int error = 0; 1115 1116 ASSERT(vp->v_type == VLNK); 1117 1118 dv = VTOSDEV(vp); 1119 1120 if (dv->sdev_attrvp) { 1121 /* non-NULL attrvp implys a persisted node at READY state */ 1122 return (VOP_READLINK(dv->sdev_attrvp, uiop, cred)); 1123 } else if (dv->sdev_symlink != NULL) { 1124 /* memory nodes, e.g. local nodes */ 1125 rw_enter(&dv->sdev_contents, RW_READER); 1126 sdcmn_err2(("sdev_readlink link is %s\n", dv->sdev_symlink)); 1127 error = uiomove(dv->sdev_symlink, strlen(dv->sdev_symlink), 1128 UIO_READ, uiop); 1129 rw_exit(&dv->sdev_contents); 1130 return (error); 1131 } 1132 1133 return (ENOENT); 1134 } 1135 1136 static int 1137 sdev_readdir(struct vnode *dvp, struct uio *uiop, struct cred *cred, int *eofp) 1138 { 1139 struct sdev_node *parent = VTOSDEV(dvp); 1140 1141 ASSERT(parent); 1142 if (!SDEV_IS_GLOBAL(parent)) 1143 prof_filldir(parent); 1144 return (devname_readdir_func(dvp, uiop, cred, eofp, SDEV_BROWSE)); 1145 } 1146 1147 /*ARGSUSED1*/ 1148 static void 1149 sdev_inactive(struct vnode *vp, struct cred *cred) 1150 { 1151 int clean; 1152 struct sdev_node *dv = VTOSDEV(vp); 1153 struct sdev_node *ddv = dv->sdev_dotdot; 1154 struct sdev_node *idv; 1155 struct sdev_node *prev = NULL; 1156 int state; 1157 struct devname_nsmap *map = NULL; 1158 struct devname_ops *dirops = NULL; 1159 void (*fn)(devname_handle_t *, struct cred *) = NULL; 1160 1161 rw_enter(&ddv->sdev_contents, RW_WRITER); 1162 state = dv->sdev_state; 1163 1164 mutex_enter(&vp->v_lock); 1165 ASSERT(vp->v_count >= 1); 1166 1167 clean = (vp->v_count == 1) && (state == SDEV_ZOMBIE); 1168 1169 /* 1170 * last ref count on the ZOMBIE node is released. 1171 * clean up the sdev_node, and 1172 * release the hold on the backing store node so that 1173 * the ZOMBIE backing stores also cleaned out. 1174 */ 1175 if (clean) { 1176 ASSERT(ddv); 1177 if (SDEV_IS_GLOBAL(dv)) { 1178 map = ddv->sdev_mapinfo; 1179 dirops = map ? map->dir_ops : NULL; 1180 if (dirops && (fn = dirops->devnops_inactive)) 1181 (*fn)(&(dv->sdev_handle), cred); 1182 } 1183 1184 ddv->sdev_nlink--; 1185 if (vp->v_type == VDIR) { 1186 dv->sdev_nlink--; 1187 } 1188 for (idv = ddv->sdev_dot; idv && idv != dv; 1189 prev = idv, idv = idv->sdev_next); 1190 ASSERT(idv == dv); 1191 if (prev == NULL) 1192 ddv->sdev_dot = dv->sdev_next; 1193 else 1194 prev->sdev_next = dv->sdev_next; 1195 dv->sdev_next = NULL; 1196 dv->sdev_nlink--; 1197 --vp->v_count; 1198 mutex_exit(&vp->v_lock); 1199 sdev_nodedestroy(dv, 0); 1200 } else { 1201 --vp->v_count; 1202 mutex_exit(&vp->v_lock); 1203 } 1204 rw_exit(&ddv->sdev_contents); 1205 } 1206 1207 static int 1208 sdev_fid(struct vnode *vp, struct fid *fidp) 1209 { 1210 struct sdev_node *dv = VTOSDEV(vp); 1211 struct sdev_fid *sdev_fid; 1212 1213 if (fidp->fid_len < (sizeof (struct sdev_fid) - sizeof (ushort_t))) { 1214 fidp->fid_len = sizeof (struct sdev_fid) - sizeof (ushort_t); 1215 return (ENOSPC); 1216 } 1217 1218 sdev_fid = (struct sdev_fid *)fidp; 1219 bzero(sdev_fid, sizeof (struct sdev_fid)); 1220 sdev_fid->sdevfid_len = 1221 (int)sizeof (struct sdev_fid) - sizeof (ushort_t); 1222 sdev_fid->sdevfid_ino = dv->sdev_ino; 1223 1224 return (0); 1225 } 1226 1227 /* 1228 * This pair of routines bracket all VOP_READ, VOP_WRITE 1229 * and VOP_READDIR requests. The contents lock stops things 1230 * moving around while we're looking at them. 1231 */ 1232 static void 1233 sdev_rwlock(struct vnode *vp, int write_flag) 1234 { 1235 rw_enter(&VTOSDEV(vp)->sdev_contents, write_flag ? RW_WRITER : 1236 RW_READER); 1237 } 1238 1239 /*ARGSUSED1*/ 1240 static void 1241 sdev_rwunlock(struct vnode *vp, int write_flag) 1242 { 1243 rw_exit(&VTOSDEV(vp)->sdev_contents); 1244 } 1245 1246 /*ARGSUSED1*/ 1247 static int 1248 sdev_seek(struct vnode *vp, offset_t ooff, offset_t *noffp) 1249 { 1250 struct vnode *attrvp = VTOSDEV(vp)->sdev_attrvp; 1251 1252 ASSERT(vp->v_type != VCHR && 1253 vp->v_type != VBLK && vp->v_type != VLNK); 1254 1255 if (vp->v_type == VDIR) 1256 return (fs_seek(vp, ooff, noffp)); 1257 1258 ASSERT(attrvp); 1259 return (VOP_SEEK(attrvp, ooff, noffp)); 1260 } 1261 1262 /*ARGSUSED1*/ 1263 static int 1264 sdev_frlock(struct vnode *vp, int cmd, struct flock64 *bfp, int flag, 1265 offset_t offset, struct flk_callback *flk_cbp, struct cred *cr) 1266 { 1267 int error; 1268 struct sdev_node *dv = VTOSDEV(vp); 1269 1270 ASSERT(dv); 1271 ASSERT(dv->sdev_attrvp); 1272 error = VOP_FRLOCK(dv->sdev_attrvp, cmd, bfp, flag, offset, 1273 flk_cbp, cr); 1274 1275 return (error); 1276 } 1277 1278 static int 1279 sdev_setfl(struct vnode *vp, int oflags, int nflags, cred_t *cr) 1280 { 1281 struct sdev_node *dv = VTOSDEV(vp); 1282 ASSERT(dv); 1283 ASSERT(dv->sdev_attrvp); 1284 1285 return (VOP_SETFL(dv->sdev_attrvp, oflags, nflags, cr)); 1286 } 1287 1288 static int 1289 sdev_pathconf(vnode_t *vp, int cmd, ulong_t *valp, cred_t *cr) 1290 { 1291 switch (cmd) { 1292 case _PC_ACL_ENABLED: 1293 *valp = SDEV_ACL_FLAVOR(vp); 1294 return (0); 1295 } 1296 1297 return (fs_pathconf(vp, cmd, valp, cr)); 1298 } 1299 1300 vnodeops_t *sdev_vnodeops; 1301 1302 const fs_operation_def_t sdev_vnodeops_tbl[] = { 1303 VOPNAME_OPEN, sdev_open, 1304 VOPNAME_CLOSE, sdev_close, 1305 VOPNAME_READ, sdev_read, 1306 VOPNAME_WRITE, sdev_write, 1307 VOPNAME_IOCTL, sdev_ioctl, 1308 VOPNAME_GETATTR, sdev_getattr, 1309 VOPNAME_SETATTR, sdev_setattr, 1310 VOPNAME_ACCESS, sdev_access, 1311 VOPNAME_LOOKUP, sdev_lookup, 1312 VOPNAME_CREATE, sdev_create, 1313 VOPNAME_RENAME, sdev_rename, 1314 VOPNAME_REMOVE, sdev_remove, 1315 VOPNAME_MKDIR, sdev_mkdir, 1316 VOPNAME_RMDIR, sdev_rmdir, 1317 VOPNAME_READDIR, sdev_readdir, 1318 VOPNAME_SYMLINK, sdev_symlink, 1319 VOPNAME_READLINK, sdev_readlink, /* readlink */ 1320 VOPNAME_FSYNC, (fs_generic_func_p) fs_sync, 1321 VOPNAME_INACTIVE, (fs_generic_func_p)sdev_inactive, 1322 VOPNAME_FID, sdev_fid, 1323 VOPNAME_RWLOCK, (fs_generic_func_p)sdev_rwlock, 1324 VOPNAME_RWUNLOCK, (fs_generic_func_p)sdev_rwunlock, 1325 VOPNAME_SEEK, sdev_seek, 1326 VOPNAME_FRLOCK, sdev_frlock, 1327 VOPNAME_PATHCONF, sdev_pathconf, 1328 VOPNAME_SETFL, sdev_setfl, 1329 VOPNAME_SETSECATTR, sdev_setsecattr, /* setsecattr */ 1330 VOPNAME_GETSECATTR, sdev_getsecattr, /* getsecattr */ 1331 NULL, NULL 1332 }; 1333 1334 int sdev_vnodeops_tbl_size = sizeof (sdev_vnodeops_tbl); 1335