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