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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. 24 * 25 * Copyright (c) 1983,1984,1985,1986,1987,1988,1989 AT&T. 26 * All rights reserved. 27 */ 28 29 #include <sys/param.h> 30 #include <sys/systm.h> 31 #include <sys/thread.h> 32 #include <sys/t_lock.h> 33 #include <sys/time.h> 34 #include <sys/vnode.h> 35 #include <sys/vfs.h> 36 #include <sys/errno.h> 37 #include <sys/buf.h> 38 #include <sys/stat.h> 39 #include <sys/cred.h> 40 #include <sys/kmem.h> 41 #include <sys/debug.h> 42 #include <sys/vmsystm.h> 43 #include <sys/flock.h> 44 #include <sys/share.h> 45 #include <sys/cmn_err.h> 46 #include <sys/tiuser.h> 47 #include <sys/sysmacros.h> 48 #include <sys/callb.h> 49 #include <sys/acl.h> 50 #include <sys/kstat.h> 51 #include <sys/signal.h> 52 #include <sys/list.h> 53 #include <sys/zone.h> 54 55 #include <netsmb/smb.h> 56 #include <netsmb/smb_conn.h> 57 #include <netsmb/smb_subr.h> 58 59 #include <smbfs/smbfs.h> 60 #include <smbfs/smbfs_node.h> 61 #include <smbfs/smbfs_subr.h> 62 63 #include <vm/hat.h> 64 #include <vm/as.h> 65 #include <vm/page.h> 66 #include <vm/pvn.h> 67 #include <vm/seg.h> 68 #include <vm/seg_map.h> 69 #include <vm/seg_vn.h> 70 71 static int smbfs_getattr_cache(vnode_t *, smbfattr_t *); 72 static void smbfattr_to_vattr(vnode_t *, smbfattr_t *, vattr_t *); 73 static void smbfattr_to_xvattr(smbfattr_t *, vattr_t *); 74 75 /* 76 * The following code provide zone support in order to perform an action 77 * for each smbfs mount in a zone. This is also where we would add 78 * per-zone globals and kernel threads for the smbfs module (since 79 * they must be terminated by the shutdown callback). 80 */ 81 82 struct smi_globals { 83 kmutex_t smg_lock; /* lock protecting smg_list */ 84 list_t smg_list; /* list of SMBFS mounts in zone */ 85 boolean_t smg_destructor_called; 86 }; 87 typedef struct smi_globals smi_globals_t; 88 89 static zone_key_t smi_list_key; 90 91 /* 92 * Attributes caching: 93 * 94 * Attributes are cached in the smbnode in struct vattr form. 95 * There is a time associated with the cached attributes (r_attrtime) 96 * which tells whether the attributes are valid. The time is initialized 97 * to the difference between current time and the modify time of the vnode 98 * when new attributes are cached. This allows the attributes for 99 * files that have changed recently to be timed out sooner than for files 100 * that have not changed for a long time. There are minimum and maximum 101 * timeout values that can be set per mount point. 102 */ 103 104 /* 105 * Validate caches by checking cached attributes. If they have timed out 106 * get the attributes from the server and compare mtimes. If mtimes are 107 * different purge all caches for this vnode. 108 */ 109 int 110 smbfs_validate_caches( 111 struct vnode *vp, 112 cred_t *cr) 113 { 114 struct vattr va; 115 116 va.va_mask = AT_SIZE; 117 return (smbfsgetattr(vp, &va, cr)); 118 } 119 120 /* 121 * Purge all of the various data caches. 122 */ 123 /*ARGSUSED*/ 124 void 125 smbfs_purge_caches(struct vnode *vp) 126 { 127 #if 0 /* not yet: mmap support */ 128 /* 129 * NFS: Purge the DNLC for this vp, 130 * Clear any readdir state bits, 131 * the readlink response cache, ... 132 */ 133 smbnode_t *np = VTOSMB(vp); 134 135 /* 136 * Flush the page cache. 137 */ 138 if (vn_has_cached_data(vp)) { 139 (void) VOP_PUTPAGE(vp, (u_offset_t)0, 0, B_INVAL, cr, NULL); 140 } 141 #endif /* not yet */ 142 } 143 144 /* 145 * Check the attribute cache to see if the new attributes match 146 * those cached. If they do, the various `data' caches are 147 * considered to be good. Otherwise, purge the cached data. 148 */ 149 void 150 smbfs_cache_check( 151 struct vnode *vp, 152 struct smbfattr *fap) 153 { 154 smbnode_t *np; 155 int purge_data = 0; 156 int purge_acl = 0; 157 158 np = VTOSMB(vp); 159 mutex_enter(&np->r_statelock); 160 161 /* 162 * Compare with NFS macro: CACHE_VALID 163 * If the mtime or size has changed, 164 * purge cached data. 165 */ 166 if (np->r_attr.fa_mtime.tv_sec != fap->fa_mtime.tv_sec || 167 np->r_attr.fa_mtime.tv_nsec != fap->fa_mtime.tv_nsec) 168 purge_data = 1; 169 if (np->r_attr.fa_size != fap->fa_size) 170 purge_data = 1; 171 172 if (np->r_attr.fa_ctime.tv_sec != fap->fa_ctime.tv_sec || 173 np->r_attr.fa_ctime.tv_nsec != fap->fa_ctime.tv_nsec) 174 purge_acl = 1; 175 176 if (purge_acl) { 177 /* just invalidate r_secattr (XXX: OK?) */ 178 np->r_sectime = gethrtime(); 179 } 180 181 mutex_exit(&np->r_statelock); 182 183 if (purge_data) 184 smbfs_purge_caches(vp); 185 } 186 187 /* 188 * Set attributes cache for given vnode using vnode attributes. 189 * From NFS: nfs_attrcache_va 190 */ 191 #if 0 /* not yet (not sure if we need this) */ 192 void 193 smbfs_attrcache_va(vnode_t *vp, struct vattr *vap) 194 { 195 smbfattr_t fa; 196 smbnode_t *np; 197 198 vattr_to_fattr(vp, vap, &fa); 199 smbfs_attrcache_fa(vp, &fa); 200 } 201 #endif /* not yet */ 202 203 /* 204 * Set attributes cache for given vnode using SMB fattr 205 * and update the attribute cache timeout. 206 * 207 * From NFS: nfs_attrcache, nfs_attrcache_va 208 */ 209 void 210 smbfs_attrcache_fa(vnode_t *vp, struct smbfattr *fap) 211 { 212 smbnode_t *np; 213 smbmntinfo_t *smi; 214 hrtime_t delta, now; 215 u_offset_t newsize; 216 vtype_t vtype, oldvt; 217 mode_t mode; 218 219 np = VTOSMB(vp); 220 smi = VTOSMI(vp); 221 222 /* 223 * We allow v_type to change, so set that here 224 * (and the mode, which depends on the type). 225 */ 226 if (fap->fa_attr & SMB_FA_DIR) { 227 vtype = VDIR; 228 mode = smi->smi_dmode; 229 } else { 230 vtype = VREG; 231 mode = smi->smi_fmode; 232 } 233 234 mutex_enter(&np->r_statelock); 235 now = gethrtime(); 236 237 /* 238 * Delta is the number of nanoseconds that we will 239 * cache the attributes of the file. It is based on 240 * the number of nanoseconds since the last time that 241 * we detected a change. The assumption is that files 242 * that changed recently are likely to change again. 243 * There is a minimum and a maximum for regular files 244 * and for directories which is enforced though. 245 * 246 * Using the time since last change was detected 247 * eliminates direct comparison or calculation 248 * using mixed client and server times. SMBFS 249 * does not make any assumptions regarding the 250 * client and server clocks being synchronized. 251 */ 252 if (fap->fa_mtime.tv_sec != np->r_attr.fa_mtime.tv_sec || 253 fap->fa_mtime.tv_nsec != np->r_attr.fa_mtime.tv_nsec || 254 fap->fa_size != np->r_attr.fa_size) 255 np->r_mtime = now; 256 257 if ((smi->smi_flags & SMI_NOAC) || (vp->v_flag & VNOCACHE)) 258 delta = 0; 259 else { 260 delta = now - np->r_mtime; 261 if (vtype == VDIR) { 262 if (delta < smi->smi_acdirmin) 263 delta = smi->smi_acdirmin; 264 else if (delta > smi->smi_acdirmax) 265 delta = smi->smi_acdirmax; 266 } else { 267 if (delta < smi->smi_acregmin) 268 delta = smi->smi_acregmin; 269 else if (delta > smi->smi_acregmax) 270 delta = smi->smi_acregmax; 271 } 272 } 273 274 np->r_attrtime = now + delta; 275 np->r_attr = *fap; 276 np->n_mode = mode; 277 oldvt = vp->v_type; 278 vp->v_type = vtype; 279 280 /* 281 * Shall we update r_size? (local notion of size) 282 * 283 * The real criteria for updating r_size should be: 284 * if the file has grown on the server, or if 285 * the client has not modified the file. 286 * 287 * Also deal with the fact that SMB presents 288 * directories as having size=0. Doing that 289 * here and leaving fa_size as returned OtW 290 * avoids fixing the size lots of places. 291 */ 292 newsize = fap->fa_size; 293 if (vtype == VDIR && newsize < DEV_BSIZE) 294 newsize = DEV_BSIZE; 295 296 if (np->r_size != newsize) { 297 #if 0 /* not yet: mmap support */ 298 if (!vn_has_cached_data(vp) || ...) 299 /* XXX: See NFS page cache code. */ 300 #endif /* not yet */ 301 /* OK to set the size. */ 302 np->r_size = newsize; 303 } 304 305 /* NFS: np->r_flags &= ~RWRITEATTR; */ 306 np->n_flag &= ~NATTRCHANGED; 307 308 mutex_exit(&np->r_statelock); 309 310 if (oldvt != vtype) { 311 SMBVDEBUG("vtype change %d to %d\n", oldvt, vtype); 312 } 313 } 314 315 /* 316 * Fill in attribute from the cache. 317 * 318 * If valid, copy to *fap and return zero, 319 * otherwise return an error. 320 * 321 * From NFS: nfs_getattr_cache() 322 */ 323 int 324 smbfs_getattr_cache(vnode_t *vp, struct smbfattr *fap) 325 { 326 smbnode_t *np; 327 int error; 328 329 np = VTOSMB(vp); 330 331 mutex_enter(&np->r_statelock); 332 if (gethrtime() >= np->r_attrtime) { 333 /* cache expired */ 334 error = ENOENT; 335 } else { 336 /* cache is valid */ 337 *fap = np->r_attr; 338 error = 0; 339 } 340 mutex_exit(&np->r_statelock); 341 342 return (error); 343 } 344 345 /* 346 * Get attributes over-the-wire and update attributes cache 347 * if no error occurred in the over-the-wire operation. 348 * Return 0 if successful, otherwise error. 349 * From NFS: nfs_getattr_otw 350 */ 351 int 352 smbfs_getattr_otw(vnode_t *vp, struct smbfattr *fap, cred_t *cr) 353 { 354 struct smbnode *np; 355 struct smb_cred scred; 356 int error; 357 358 np = VTOSMB(vp); 359 360 /* 361 * NFS uses the ACL rpc here (if smi_flags & SMI_ACL) 362 * With SMB, getting the ACL is a significantly more 363 * expensive operation, so we do that only when asked 364 * for the uid/gid. See smbfsgetattr(). 365 */ 366 367 /* Shared lock for (possible) n_fid use. */ 368 if (smbfs_rw_enter_sig(&np->r_lkserlock, RW_READER, SMBINTR(vp))) 369 return (EINTR); 370 smb_credinit(&scred, cr); 371 372 bzero(fap, sizeof (*fap)); 373 error = smbfs_smb_getfattr(np, fap, &scred); 374 375 smb_credrele(&scred); 376 smbfs_rw_exit(&np->r_lkserlock); 377 378 if (error) { 379 /* NFS had: PURGE_STALE_FH(error, vp, cr) */ 380 smbfs_attrcache_remove(np); 381 if (error == ENOENT || error == ENOTDIR) { 382 /* 383 * Getattr failed because the object was 384 * removed or renamed by another client. 385 * Remove any cached attributes under it. 386 */ 387 smbfs_attrcache_prune(np); 388 } 389 return (error); 390 } 391 392 /* 393 * NFS: smbfs_cache_fattr(vap, fa, vap, t, cr); 394 * which did: fattr_to_vattr, nfs_attr_cache. 395 * We cache the fattr form, so just do the 396 * cache check and store the attributes. 397 */ 398 smbfs_cache_check(vp, fap); 399 smbfs_attrcache_fa(vp, fap); 400 401 return (0); 402 } 403 404 /* 405 * Return either cached or remote attributes. If get remote attr 406 * use them to check and invalidate caches, then cache the new attributes. 407 * 408 * From NFS: nfsgetattr() 409 */ 410 int 411 smbfsgetattr(vnode_t *vp, struct vattr *vap, cred_t *cr) 412 { 413 struct smbfattr fa; 414 smbmntinfo_t *smi; 415 uint_t mask; 416 int error; 417 418 smi = VTOSMI(vp); 419 420 ASSERT(curproc->p_zone == smi->smi_zone_ref.zref_zone); 421 422 /* 423 * If asked for UID or GID, update n_uid, n_gid. 424 */ 425 mask = AT_ALL; 426 if (vap->va_mask & (AT_UID | AT_GID)) { 427 if (smi->smi_flags & SMI_ACL) 428 (void) smbfs_acl_getids(vp, cr); 429 /* else leave as set in make_smbnode */ 430 } else { 431 mask &= ~(AT_UID | AT_GID); 432 } 433 434 /* 435 * If we've got cached attributes, just use them; 436 * otherwise go to the server to get attributes, 437 * which will update the cache in the process. 438 */ 439 error = smbfs_getattr_cache(vp, &fa); 440 if (error) 441 error = smbfs_getattr_otw(vp, &fa, cr); 442 if (error) 443 return (error); 444 vap->va_mask |= mask; 445 446 /* 447 * Re. client's view of the file size, see: 448 * smbfs_attrcache_fa, smbfs_getattr_otw 449 */ 450 smbfattr_to_vattr(vp, &fa, vap); 451 if (vap->va_mask & AT_XVATTR) 452 smbfattr_to_xvattr(&fa, vap); 453 454 return (0); 455 } 456 457 458 /* 459 * Convert SMB over the wire attributes to vnode form. 460 * Returns 0 for success, error if failed (overflow, etc). 461 * From NFS: nattr_to_vattr() 462 */ 463 void 464 smbfattr_to_vattr(vnode_t *vp, struct smbfattr *fa, struct vattr *vap) 465 { 466 struct smbnode *np = VTOSMB(vp); 467 468 /* 469 * Take type, mode, uid, gid from the smbfs node, 470 * which has have been updated by _getattr_otw. 471 */ 472 vap->va_type = vp->v_type; 473 vap->va_mode = np->n_mode; 474 475 vap->va_uid = np->n_uid; 476 vap->va_gid = np->n_gid; 477 478 vap->va_fsid = vp->v_vfsp->vfs_dev; 479 vap->va_nodeid = np->n_ino; 480 vap->va_nlink = 1; 481 482 /* 483 * Difference from NFS here: We cache attributes as 484 * reported by the server, so r_attr.fa_size is the 485 * server's idea of the file size. This is called 486 * for getattr, so we want to return the client's 487 * idea of the file size. NFS deals with that in 488 * nfsgetattr(), the equivalent of our caller. 489 */ 490 vap->va_size = np->r_size; 491 492 /* 493 * Times. Note, already converted from NT to 494 * Unix form (in the unmarshalling code). 495 */ 496 vap->va_atime = fa->fa_atime; 497 vap->va_mtime = fa->fa_mtime; 498 vap->va_ctime = fa->fa_ctime; 499 500 /* 501 * rdev, blksize, seq are made up. 502 * va_nblocks is 512 byte blocks. 503 */ 504 vap->va_rdev = vp->v_rdev; 505 vap->va_blksize = MAXBSIZE; 506 vap->va_nblocks = (fsblkcnt64_t)btod(np->r_attr.fa_allocsz); 507 vap->va_seq = 0; 508 } 509 510 /* 511 * smbfattr_to_xvattr: like smbfattr_to_vattr but for 512 * Extensible system attributes (PSARC 2007/315) 513 */ 514 static void 515 smbfattr_to_xvattr(struct smbfattr *fa, struct vattr *vap) 516 { 517 xvattr_t *xvap = (xvattr_t *)vap; /* *vap may be xvattr_t */ 518 xoptattr_t *xoap = NULL; 519 520 if ((xoap = xva_getxoptattr(xvap)) == NULL) 521 return; 522 523 if (XVA_ISSET_REQ(xvap, XAT_CREATETIME)) { 524 xoap->xoa_createtime = fa->fa_createtime; 525 XVA_SET_RTN(xvap, XAT_CREATETIME); 526 } 527 528 if (XVA_ISSET_REQ(xvap, XAT_ARCHIVE)) { 529 xoap->xoa_archive = 530 ((fa->fa_attr & SMB_FA_ARCHIVE) != 0); 531 XVA_SET_RTN(xvap, XAT_ARCHIVE); 532 } 533 534 if (XVA_ISSET_REQ(xvap, XAT_SYSTEM)) { 535 xoap->xoa_system = 536 ((fa->fa_attr & SMB_FA_SYSTEM) != 0); 537 XVA_SET_RTN(xvap, XAT_SYSTEM); 538 } 539 540 if (XVA_ISSET_REQ(xvap, XAT_READONLY)) { 541 xoap->xoa_readonly = 542 ((fa->fa_attr & SMB_FA_RDONLY) != 0); 543 XVA_SET_RTN(xvap, XAT_READONLY); 544 } 545 546 if (XVA_ISSET_REQ(xvap, XAT_HIDDEN)) { 547 xoap->xoa_hidden = 548 ((fa->fa_attr & SMB_FA_HIDDEN) != 0); 549 XVA_SET_RTN(xvap, XAT_HIDDEN); 550 } 551 } 552 553 /* 554 * SMB Client initialization and cleanup. 555 * Much of it is per-zone now. 556 */ 557 558 559 /* ARGSUSED */ 560 static void * 561 smbfs_zone_init(zoneid_t zoneid) 562 { 563 smi_globals_t *smg; 564 565 smg = kmem_alloc(sizeof (*smg), KM_SLEEP); 566 mutex_init(&smg->smg_lock, NULL, MUTEX_DEFAULT, NULL); 567 list_create(&smg->smg_list, sizeof (smbmntinfo_t), 568 offsetof(smbmntinfo_t, smi_zone_node)); 569 smg->smg_destructor_called = B_FALSE; 570 return (smg); 571 } 572 573 /* 574 * Callback routine to tell all SMBFS mounts in the zone to stop creating new 575 * threads. Existing threads should exit. 576 */ 577 /* ARGSUSED */ 578 static void 579 smbfs_zone_shutdown(zoneid_t zoneid, void *data) 580 { 581 smi_globals_t *smg = data; 582 smbmntinfo_t *smi; 583 584 ASSERT(smg != NULL); 585 again: 586 mutex_enter(&smg->smg_lock); 587 for (smi = list_head(&smg->smg_list); smi != NULL; 588 smi = list_next(&smg->smg_list, smi)) { 589 590 /* 591 * If we've done the shutdown work for this FS, skip. 592 * Once we go off the end of the list, we're done. 593 */ 594 if (smi->smi_flags & SMI_DEAD) 595 continue; 596 597 /* 598 * We will do work, so not done. Get a hold on the FS. 599 */ 600 VFS_HOLD(smi->smi_vfsp); 601 602 mutex_enter(&smi->smi_lock); 603 smi->smi_flags |= SMI_DEAD; 604 mutex_exit(&smi->smi_lock); 605 606 /* 607 * Drop lock and release FS, which may change list, then repeat. 608 * We're done when every mi has been done or the list is empty. 609 */ 610 mutex_exit(&smg->smg_lock); 611 VFS_RELE(smi->smi_vfsp); 612 goto again; 613 } 614 mutex_exit(&smg->smg_lock); 615 } 616 617 static void 618 smbfs_zone_free_globals(smi_globals_t *smg) 619 { 620 list_destroy(&smg->smg_list); /* makes sure the list is empty */ 621 mutex_destroy(&smg->smg_lock); 622 kmem_free(smg, sizeof (*smg)); 623 624 } 625 626 /* ARGSUSED */ 627 static void 628 smbfs_zone_destroy(zoneid_t zoneid, void *data) 629 { 630 smi_globals_t *smg = data; 631 632 ASSERT(smg != NULL); 633 mutex_enter(&smg->smg_lock); 634 if (list_head(&smg->smg_list) != NULL) { 635 /* Still waiting for VFS_FREEVFS() */ 636 smg->smg_destructor_called = B_TRUE; 637 mutex_exit(&smg->smg_lock); 638 return; 639 } 640 smbfs_zone_free_globals(smg); 641 } 642 643 /* 644 * Add an SMBFS mount to the per-zone list of SMBFS mounts. 645 */ 646 void 647 smbfs_zonelist_add(smbmntinfo_t *smi) 648 { 649 smi_globals_t *smg; 650 651 smg = zone_getspecific(smi_list_key, smi->smi_zone_ref.zref_zone); 652 mutex_enter(&smg->smg_lock); 653 list_insert_head(&smg->smg_list, smi); 654 mutex_exit(&smg->smg_lock); 655 } 656 657 /* 658 * Remove an SMBFS mount from the per-zone list of SMBFS mounts. 659 */ 660 void 661 smbfs_zonelist_remove(smbmntinfo_t *smi) 662 { 663 smi_globals_t *smg; 664 665 smg = zone_getspecific(smi_list_key, smi->smi_zone_ref.zref_zone); 666 mutex_enter(&smg->smg_lock); 667 list_remove(&smg->smg_list, smi); 668 /* 669 * We can be called asynchronously by VFS_FREEVFS() after the zone 670 * shutdown/destroy callbacks have executed; if so, clean up the zone's 671 * smi_globals. 672 */ 673 if (list_head(&smg->smg_list) == NULL && 674 smg->smg_destructor_called == B_TRUE) { 675 smbfs_zone_free_globals(smg); 676 return; 677 } 678 mutex_exit(&smg->smg_lock); 679 } 680 681 #ifdef lint 682 #define NEED_SMBFS_CALLBACKS 1 683 #endif 684 685 #ifdef NEED_SMBFS_CALLBACKS 686 /* 687 * Call-back hooks for netsmb, in case we want them. 688 * Apple's VFS wants them. We may not need them. 689 */ 690 /*ARGSUSED*/ 691 static void smbfs_dead(smb_share_t *ssp) 692 { 693 /* 694 * Walk the mount list, finding all mounts 695 * using this share... 696 */ 697 } 698 699 /*ARGSUSED*/ 700 static void smbfs_cb_nop(smb_share_t *ss) 701 { 702 /* no-op */ 703 } 704 705 smb_fscb_t smbfs_cb = { 706 .fscb_disconn = smbfs_dead, 707 .fscb_connect = smbfs_cb_nop, 708 .fscb_down = smbfs_cb_nop, 709 .fscb_up = smbfs_cb_nop }; 710 711 #endif /* NEED_SMBFS_CALLBACKS */ 712 713 /* 714 * SMBFS Client initialization routine. This routine should only be called 715 * once. It performs the following tasks: 716 * - Initalize all global locks 717 * - Call sub-initialization routines (localize access to variables) 718 */ 719 int 720 smbfs_clntinit(void) 721 { 722 723 zone_key_create(&smi_list_key, smbfs_zone_init, smbfs_zone_shutdown, 724 smbfs_zone_destroy); 725 #ifdef NEED_SMBFS_CALLBACKS 726 (void) smb_fscb_set(&smbfs_cb); 727 #endif /* NEED_SMBFS_CALLBACKS */ 728 return (0); 729 } 730 731 /* 732 * This routine is called when the modunload is called. This will cleanup 733 * the previously allocated/initialized nodes. 734 */ 735 void 736 smbfs_clntfini(void) 737 { 738 #ifdef NEED_SMBFS_CALLBACKS 739 (void) smb_fscb_set(NULL); 740 #endif /* NEED_SMBFS_CALLBACKS */ 741 (void) zone_key_delete(smi_list_key); 742 }