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 2008 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #include "lint.h" 28 #include <mtlib.h> 29 #include <sys/types.h> 30 #include <errno.h> 31 #include <pwd.h> 32 #include <nss_dbdefs.h> 33 #include <stdio.h> 34 #include <string.h> 35 #include <synch.h> 36 #include <sys/param.h> 37 #include <fcntl.h> 38 #include <unistd.h> 39 #include <stdlib.h> 40 #include <getxby_door.h> 41 #include <sys/door.h> 42 #include <procfs.h> 43 #include <door.h> 44 #include <sys/mman.h> 45 #include "libc.h" 46 #include "tsd.h" 47 #include "base_conversion.h" 48 49 /* nss<->door hints */ 50 static mutex_t hints_lock = DEFAULTMUTEX; 51 static size_t door_bsize = 0; 52 static size_t door_nbsize = 0; 53 static int proc_is_cache = -1; 54 55 /* library<->nscd door interaction apis */ 56 57 /* 58 * 59 * Routine that actually performs the door call. 60 * Note that we cache a file descriptor. We do 61 * the following to prevent disasters: 62 * 63 * 1) Never use 0,1 or 2; if we get this from the open 64 * we dup it upwards. 65 * 66 * 2) Set the close on exec flags so descriptor remains available 67 * to child processes. 68 * 69 * 3) Verify that the door is still the same one we had before 70 * by using door_info on the client side. 71 * 72 * Note that we never close the file descriptor if it isn't one 73 * we allocated; we check this with door info. The rather tricky 74 * logic is designed to be fast in the normal case (fd is already 75 * allocated and is ok) while handling the case where the application 76 * closed it underneath us or where the nscd dies or re-execs itself 77 * and we're a multi-threaded application. Note that we cannot protect 78 * the application if it closes the fd and it is multi-threaded. 79 * 80 * int _nsc_trydoorcall(void *dptr, size_t *bufsize, size_t *actualsize); 81 * 82 * *dptr IN: points to arg buffer OUT: points to results buffer 83 * *bufsize IN: overall size of buffer OUT: overall size of buffer 84 * *actualsize IN: size of call data OUT: size of return data 85 * 86 * Note that *dptr may change if provided space as defined by *bufsize is 87 * inadequate. In this case the door call mmaps more space and places 88 * the answer there and sets dptr to contain a pointer to the space, which 89 * should be freed with munmap. 90 * 91 * Returns 0 if the door call reached the server, -1 if contact was not made. 92 * 93 */ 94 95 /* 96 * Max size for list of db names supported by the private nscd 97 * No implied max here, any size will do, fixed size chosen to 98 * reduce yet another malloc 99 */ 100 101 #define BD_BUFSIZE 1024 102 #define BD_SEP ',' 103 104 typedef struct _nsc_door_t { 105 int doorfd; 106 mutex_t door_lock; 107 door_info_t doori; 108 } nsc_door_t; 109 110 static nsc_door_t nsc_door[2] = { 111 { -1, DEFAULTMUTEX, { 0 } }, /* front (fattached) door */ 112 { -1, DEFAULTMUTEX, { 0 } }, /* back (private) door */ 113 }; 114 115 /* assumed to be locked by using nsc_door[1] mutex */ 116 static char *nsc_db_buf = NULL; 117 static char **nsc_db_list = NULL; 118 119 /* 120 * Check for a valid and matching db in the list. 121 * assume list is in the locked state. 122 */ 123 124 static int 125 _nsc_use_backdoor(char *db) 126 { 127 char **ndb; 128 129 if (db && nsc_db_buf != NULL && nsc_db_list != NULL) { 130 for (ndb = nsc_db_list; *ndb; ndb++) { 131 if (strcmp(db, *ndb) == 0) 132 return (1); 133 } 134 } 135 return (0); 136 } 137 138 /* 139 * flush private db lists 140 */ 141 static void 142 _nsc_flush_private_db() 143 { 144 if (nsc_db_buf != NULL) { 145 libc_free((void *)nsc_db_buf); 146 nsc_db_buf = NULL; 147 } 148 if (nsc_db_list != NULL) { 149 libc_free((void *)nsc_db_list); 150 nsc_db_list = NULL; 151 } 152 } 153 154 /* 155 * init/update nsc_db_buf given buff containing list of 156 * db's to be processed by a private nscd. 157 * This function assumes it has a well formed string from nscd. 158 */ 159 160 static int 161 _nsc_init_private_db(char *dblist) 162 { 163 char *cp, **lp; 164 int buflen = 0; 165 int arrlen = 0; 166 167 if (dblist == NULL) 168 return (0); 169 170 /* reset db list */ 171 _nsc_flush_private_db(); 172 173 /* rebuild fresh list */ 174 buflen = strlen(dblist) + 1; 175 for (cp = dblist; *cp; cp++) 176 if (*cp == BD_SEP) 177 arrlen++; 178 if (cp == dblist) 179 return (0); 180 arrlen += 2; 181 nsc_db_buf = (char *)libc_malloc(buflen); 182 if (nsc_db_buf == (char *)NULL) 183 return (0); 184 nsc_db_list = (char **)libc_malloc(arrlen * sizeof (char *)); 185 if (nsc_db_list == (char **)NULL) { 186 libc_free((void *)nsc_db_buf); 187 nsc_db_buf = NULL; 188 return (0); 189 } 190 (void) memcpy(nsc_db_buf, dblist, buflen); 191 lp = nsc_db_list; 192 *lp++ = nsc_db_buf; 193 for (cp = nsc_db_buf; *cp; ) { 194 if (*cp == BD_SEP) { 195 *cp++ = '\0'; 196 *lp++ = cp; 197 } else 198 cp++; 199 } 200 *lp = NULL; 201 return (1); 202 } 203 204 /* 205 * _nsc_initdoor_fp attempts to validate the given door and 206 * confirm that it is still available for use. The options are: 207 * Front door: 208 * If it's not open, attempt to open or error 209 * If it's open attempt to validate. 210 * If it's not validatable, reset fd and try again. 211 * Other wise it open and validated, return success 212 * Per user (back) door: 213 * This door is passed to the client through th front door 214 * attempt to validate it. If it can't be validated, it 215 * must be reset. Then send a NSS_ALTRESET error, so nscd can 216 * forward another fd if desired. 217 */ 218 219 static nss_status_t 220 _nsc_initdoor_fp(nsc_door_t *dp) 221 { 222 223 door_info_t my_door; 224 225 if (dp == NULL) { 226 errno = ENOTCONN; 227 return (NSS_ERROR); 228 } 229 230 /* 231 * the first time in we try and open and validate the front door. 232 * A front door request may return an alternate private back door 233 * that the client should use instead. 234 * 235 * To validate a door the door must have been created with 236 * the name service door cookie. The front door is file 237 * attached, owned by root and readonly by user, group and 238 * other. If any of these validations fail we refuse to use 239 * the door. A back door is delivered from the front door 240 * via a door_desc_t, and have the same cooke notification. 241 */ 242 243 lmutex_lock(&dp->door_lock); 244 245 try_again: 246 247 if (dp->doorfd == -1 && dp == &nsc_door[0]) { /* open front door */ 248 int tbc[3]; 249 int i; 250 251 dp->doorfd = open64(NAME_SERVICE_DOOR, O_RDONLY, 0); 252 if (dp->doorfd == -1) { 253 lmutex_unlock(&dp->door_lock); 254 return (NSS_ERROR); 255 } 256 257 /* 258 * dup up the file descriptor if we have 0 - 2 259 * to avoid problems with shells stdin/out/err 260 */ 261 i = 0; 262 263 while (dp->doorfd < 3) { /* we have a reserved fd */ 264 tbc[i++] = dp->doorfd; 265 if ((dp->doorfd = dup(dp->doorfd)) < 0) { 266 while (i--) 267 (void) close(tbc[i]); 268 dp->doorfd = -1; 269 lmutex_unlock(&dp->door_lock); 270 return (NSS_ERROR); 271 } 272 } 273 274 while (i--) 275 (void) close(tbc[i]); 276 277 /* 278 * mark this door descriptor as close on exec 279 */ 280 (void) fcntl(dp->doorfd, F_SETFD, FD_CLOEXEC); 281 if (__door_info(dp->doorfd, &dp->doori) < 0 || 282 (dp->doori.di_attributes & DOOR_REVOKED) || 283 dp->doori.di_data != (uintptr_t)NAME_SERVICE_DOOR_COOKIE) { 284 /* 285 * we should close doorfd because we just opened it 286 */ 287 (void) close(dp->doorfd); 288 dp->doorfd = -1; 289 (void) memset((void *)&dp->doori, 290 '\0', sizeof (door_info_t)); 291 lmutex_unlock(&dp->door_lock); 292 errno = ECONNREFUSED; 293 return (NSS_ERROR); 294 } 295 } else { 296 if (__door_info(dp->doorfd, &my_door) < 0 || 297 my_door.di_data != (uintptr_t)NAME_SERVICE_DOOR_COOKIE || 298 my_door.di_uniquifier != dp->doori.di_uniquifier) { 299 /* 300 * don't close it - 301 * someone else has clobbered fd 302 */ 303 dp->doorfd = -1; 304 (void) memset((void *)&dp->doori, 305 '\0', sizeof (door_info_t)); 306 if (dp == &nsc_door[1]) { /* reset back door */ 307 /* flush invalid db list */ 308 _nsc_flush_private_db(); 309 lmutex_unlock(&dp->door_lock); 310 return (NSS_ALTRESET); 311 } 312 goto try_again; 313 } 314 315 if (my_door.di_attributes & DOOR_REVOKED) { 316 (void) close(dp->doorfd); /* nscd exited .... */ 317 dp->doorfd = -1; /* try and restart connection */ 318 (void) memset((void *)&dp->doori, 319 '\0', sizeof (door_info_t)); 320 if (dp == &nsc_door[1]) { /* back door reset */ 321 /* flush invalid db list */ 322 _nsc_flush_private_db(); 323 lmutex_unlock(&dp->door_lock); 324 return (NSS_ALTRESET); 325 } 326 goto try_again; 327 } 328 } 329 330 lmutex_unlock(&dp->door_lock); 331 return (NSS_SUCCESS); 332 } 333 334 /* 335 * Try the door request once only, to the specified connection. 336 * return the results or error. 337 */ 338 339 static nss_status_t 340 _nsc_try1door(nsc_door_t *dp, void **dptr, size_t *ndata, 341 size_t *adata, int *pdesc) 342 { 343 door_arg_t param; 344 int ret; 345 nss_pheader_t *rp; 346 347 ret = _nsc_initdoor_fp(dp); 348 if (ret != NSS_SUCCESS) 349 return (ret); 350 351 param.rbuf = (char *)*dptr; 352 param.rsize = *ndata; 353 param.data_ptr = (char *)*dptr; 354 param.data_size = *adata; 355 param.desc_ptr = NULL; 356 param.desc_num = 0; 357 ret = __door_call(dp->doorfd, ¶m); 358 if (ret < 0) { 359 return (NSS_ERROR); 360 } 361 *adata = param.data_size; 362 *ndata = param.rsize; 363 *dptr = (void *)param.data_ptr; 364 rp = (nss_pheader_t *)((void *)param.rbuf); 365 if (pdesc != NULL && rp && rp->p_status == NSS_ALTRETRY && 366 param.desc_ptr != NULL && param.desc_num > 0) { 367 if ((param.desc_ptr->d_attributes & DOOR_DESCRIPTOR) && 368 param.desc_ptr->d_data.d_desc.d_descriptor >= 0 && 369 param.desc_ptr->d_data.d_desc.d_id != 0) { 370 /* have an alt descriptor */ 371 *pdesc = param.desc_ptr->d_data.d_desc.d_descriptor; 372 /* got a NSS_ALTRETRY command */ 373 return (NSS_ALTRETRY); 374 } 375 errno = EINVAL; 376 return (NSS_ERROR); /* other error? */ 377 } 378 if (*adata == 0 || *dptr == NULL) { 379 errno = ENOTCONN; 380 return (NSS_ERROR); 381 } 382 383 if (rp->p_status == NSS_ALTRESET || 384 rp->p_status == NSS_ALTRETRY || 385 rp->p_status == NSS_TRYLOCAL) 386 return (rp->p_status); 387 388 return (NSS_SUCCESS); 389 } 390 391 /* 392 * Backwards compatible API 393 */ 394 395 nss_status_t 396 _nsc_trydoorcall(void **dptr, size_t *ndata, size_t *adata) 397 { 398 return (_nsc_try1door(&nsc_door[0], dptr, ndata, adata, NULL)); 399 } 400 401 /* 402 * Send the request to the designated door, based on the supplied db 403 * Retry on the alternate door fd if possible. 404 */ 405 406 nss_status_t 407 _nsc_trydoorcall_ext(void **dptr, size_t *ndata, size_t *adata) 408 { 409 int ret = NSS_ALTRETRY; 410 nsc_door_t *frontd = &nsc_door[0]; 411 nsc_door_t *backd = &nsc_door[1]; 412 int fd; 413 414 nss_pheader_t *ph, ph_save; 415 char *dbl; 416 char *db = NULL; 417 nss_dbd_t *dbd; 418 int fb2frontd = 0; 419 int reset_frontd = 0; 420 size_t ndata_save = *ndata, adata_save = *adata; 421 void *dptr_save = *dptr; 422 423 ph = (nss_pheader_t *)*dptr; 424 dbd = (nss_dbd_t *)((void *)((char *)ph + ph->dbd_off)); 425 if (dbd->o_name != 0) 426 db = (char *)dbd + dbd->o_name; 427 428 /* 429 * save away a copy of the header, in case the request needs 430 * to be sent to nscd more than once. In that case, this 431 * original header can be copied back to the door buffer 432 * to replace the possibly changed header 433 */ 434 ph_save = *ph; 435 436 while (ret == NSS_ALTRETRY || ret == NSS_ALTRESET) { 437 /* try private (back) door first if it exists and applies */ 438 if (db != NULL && backd->doorfd > 0 && fb2frontd == 0 && 439 _nsc_use_backdoor(db)) { 440 ret = _nsc_try1door(backd, dptr, ndata, adata, NULL); 441 if (ret == NSS_ALTRESET) { 442 /* 443 * received NSS_ALTRESET command, 444 * retry on front door 445 */ 446 lmutex_lock(&backd->door_lock); 447 backd->doorfd = -1; 448 (void) memset((void *)&backd->doori, 449 '\0', sizeof (door_info_t)); 450 /* flush now invalid db list */ 451 _nsc_flush_private_db(); 452 lmutex_unlock(&backd->door_lock); 453 continue; 454 } else if (ret == NSS_ALTRETRY) { 455 /* 456 * received NSS_ALTRETRY command, 457 * fall back and retry on front door 458 */ 459 fb2frontd = 1; 460 if (*dptr != dptr_save) 461 (void) munmap((void *)*dptr, *ndata); 462 463 /* 464 * restore the buffer size and header 465 * data so that the front door will 466 * see the original request 467 */ 468 *ndata = ndata_save; 469 *adata = adata_save; 470 *dptr = dptr_save; 471 ph = (nss_pheader_t *)*dptr; 472 *ph = ph_save; 473 /* 474 * tell the front door server, this is 475 * a fallback call 476 */ 477 ph->p_status = NSS_ALTRETRY; 478 continue; 479 } 480 481 /* return the result or error */ 482 break; 483 } 484 485 /* try the front door */ 486 fd = -1; 487 ret = _nsc_try1door(frontd, dptr, ndata, adata, &fd); 488 489 if (ret != NSS_ALTRETRY) { 490 /* 491 * got a success or failure result. 492 * but front door should never send NSS_ALTRESET 493 */ 494 if (ret == NSS_ALTRESET) 495 /* reset the front door */ 496 reset_frontd = 1; 497 else 498 /* 499 * not NSS_ALTRETRY and not NSS_ALTRESET 500 * return the result or error 501 */ 502 break; 503 } else if (fb2frontd == 1) { 504 /* 505 * front door should never send NSS_ALTRETRY 506 * in a fallback call. Reset the front door. 507 */ 508 reset_frontd = 1; 509 } 510 511 if (reset_frontd == 1) { 512 lmutex_lock(&frontd->door_lock); 513 frontd->doorfd = -1; 514 (void) memset((void *)&frontd->doori, 515 '\0', sizeof (door_info_t)); 516 lmutex_unlock(&frontd->door_lock); 517 /* error out */ 518 ret = NSS_ERROR; 519 break; 520 } 521 522 /* process NSS_ALTRETRY request from front door */ 523 if (fd < 0) 524 continue; /* no new door given, try again */ 525 526 /* update and try alternate door */ 527 lmutex_lock(&backd->door_lock); 528 if (backd->doorfd >= 0) { 529 /* unexpected open alt door - clean up, continue */ 530 _nsc_flush_private_db(); 531 (void) close(backd->doorfd); 532 } 533 534 /* set up back door fd */ 535 backd->doorfd = fd; 536 537 /* set up back door db list */ 538 ph = (nss_pheader_t *)*dptr; 539 dbl = ((char *)ph) + ph->data_off; 540 541 if (_nsc_init_private_db(dbl) == 0) { 542 /* could not init db list, try again */ 543 (void) close(backd->doorfd); 544 backd->doorfd = -1; 545 lmutex_unlock(&backd->door_lock); 546 continue; 547 } 548 if (door_info(backd->doorfd, &backd->doori) < 0 || 549 (backd->doori.di_attributes & DOOR_REVOKED) || 550 backd->doori.di_data != 551 (uintptr_t)NAME_SERVICE_DOOR_COOKIE) { 552 /* doorfd bad, or must not really be open */ 553 (void) close(backd->doorfd); 554 backd->doorfd = -1; 555 (void) memset((void *)&backd->doori, 556 '\0', sizeof (door_info_t)); 557 } 558 (void) fcntl(backd->doorfd, F_SETFD, FD_CLOEXEC); 559 lmutex_unlock(&backd->door_lock); 560 /* NSS_ALTRETRY new back door */ 561 if (*dptr != dptr_save) 562 (void) munmap((void *)*dptr, *ndata); 563 564 /* 565 * restore the buffer size and header 566 * data so that the back door will 567 * see the original request 568 */ 569 *ndata = ndata_save; 570 *adata = adata_save; 571 *dptr = dptr_save; 572 ph = (nss_pheader_t *)*dptr; 573 *ph = ph_save; 574 } 575 return (ret); 576 } 577 578 /* 579 * Get the current (but growable) buffer size for a NSS2 packet. 580 * Heuristic algorithm used: 581 * 1) Make sure it's at least NSS_BUFLEN_DOOR in length (16k default) 582 * 2) if an incoming user buffer is > larger than the current size 583 * Make the buffer at least NSS_BUFLEN_DOOR/2+user buffer size 584 * This should account for any reasonable nss_pheader, keys 585 * extended area etc. 586 * 3) keep the prototype/debugging (private)NSS_BUFLEN option 587 * to change any preconfigured value if needed(?) 588 */ 589 590 static size_t 591 _nsc_getdoorbsize(size_t min_size) 592 { 593 if (!door_bsize) { 594 lmutex_lock(&hints_lock); 595 if (!door_bsize) { 596 /* future work - get nscd hint & use hint size */ 597 door_bsize = ROUND_UP(door_bsize, NSS_BUFSIZ); 598 if (door_bsize < NSS_BUFLEN_DOOR) { 599 door_bsize = NSS_BUFLEN_DOOR; 600 } 601 } 602 lmutex_unlock(&hints_lock); 603 } 604 if (min_size && door_bsize < (min_size + NSS_BUFLEN_DOOR/2)) { 605 lmutex_lock(&hints_lock); 606 if (door_bsize < (min_size + NSS_BUFLEN_DOOR/2)) { 607 min_size += NSS_BUFLEN_DOOR; 608 door_bsize = ROUND_UP(min_size, NSS_BUFSIZ); 609 } 610 lmutex_unlock(&hints_lock); 611 } 612 return (door_bsize); 613 } 614 615 static void 616 _nsc_freedbuf(void *arg) 617 { 618 nss_XbyY_buf_t *tsdbuf = arg; 619 620 if (tsdbuf != NULL && tsdbuf->buffer != NULL) { 621 lfree(tsdbuf->buffer, (size_t)tsdbuf->buflen); 622 tsdbuf->result = NULL; 623 tsdbuf->buffer = NULL; 624 tsdbuf->buflen = 0; 625 } 626 } 627 628 /* 629 * _nsc_getdoorbuf - return the client side per thread door buffer 630 * Elsewhere, it is assumed that the header is 0'd upon return from here. 631 */ 632 633 int 634 _nsc_getdoorbuf(void **doorptr, size_t *bufsize) 635 { 636 nss_XbyY_buf_t *tsdbuf; 637 char *bp; 638 size_t dsize; 639 640 if (doorptr == NULL || bufsize == NULL) 641 return (-1); 642 643 /* Get thread specific pointer to door buffer */ 644 tsdbuf = tsdalloc(_T_DOORBUF, sizeof (nss_XbyY_buf_t), _nsc_freedbuf); 645 if (tsdbuf == NULL) 646 return (-1); 647 648 /* if door buffer does not exist create it */ 649 if (tsdbuf->buffer == NULL) { 650 dsize = _nsc_getdoorbsize(*bufsize); 651 652 /* setup a door buffer with a total length of dsize */ 653 bp = lmalloc(dsize); 654 if (bp == NULL) 655 return (-1); 656 tsdbuf->buffer = bp; 657 tsdbuf->buflen = dsize; 658 } else { 659 /* check old buffer size and resize if needed */ 660 if (*bufsize) { 661 dsize = _nsc_getdoorbsize(*bufsize); 662 if (tsdbuf->buflen < dsize) { 663 lfree(tsdbuf->buffer, (size_t)tsdbuf->buflen); 664 bp = lmalloc(dsize); 665 if (bp == NULL) 666 return (-1); 667 tsdbuf->buffer = bp; 668 tsdbuf->buflen = dsize; 669 } 670 } 671 /* freshly malloc'd door bufs are 0'd */ 672 /* 0 header for now. Zero entire buf(?) TDB */ 673 (void) memset((void *)tsdbuf->buffer, 0, 674 (size_t)sizeof (nss_pheader_t)); 675 676 } 677 *doorptr = (void *)tsdbuf->buffer; 678 *bufsize = tsdbuf->buflen; 679 return (0); 680 } 681 682 void 683 _nsc_resizedoorbuf(size_t bsize) 684 { 685 /* signal to update if new door size is desired */ 686 lmutex_lock(&hints_lock); 687 if (bsize > door_bsize && door_nbsize < bsize) 688 door_nbsize = bsize; 689 lmutex_unlock(&hints_lock); 690 } 691 692 /* 693 * Check uid and /proc/PID/psinfo to see if this process is nscd 694 * If it is set the appropriate flags and allow policy reconfiguration. 695 */ 696 int 697 _nsc_proc_is_cache() 698 { 699 psinfo_t pinfo; 700 char fname[128]; 701 int ret; 702 int fd; 703 704 if (proc_is_cache >= 0) 705 return (proc_is_cache); 706 lmutex_lock(&hints_lock); 707 if (proc_is_cache >= 0) { 708 lmutex_unlock(&hints_lock); 709 return (proc_is_cache); 710 } 711 proc_is_cache = 0; 712 /* It can't be nscd if it's not running as root... */ 713 if (getuid() != 0) { 714 lmutex_unlock(&hints_lock); 715 return (0); 716 } 717 ret = snprintf(fname, 128, "/proc/%d/psinfo", getpid()); 718 if (ret > 0 && ret < 128) { 719 if ((fd = open(fname, O_RDONLY)) >= 0) { 720 ret = read(fd, &pinfo, sizeof (psinfo_t)); 721 (void) close(fd); 722 if (ret == sizeof (psinfo_t) && 723 (strcmp(pinfo.pr_fname, "nscd") == 0)) { 724 /* process runs as root and is named nscd */ 725 /* that's good enough for now */ 726 proc_is_cache = 1; 727 } 728 } 729 } 730 lmutex_unlock(&hints_lock); 731 return (proc_is_cache); 732 } 733