1 /* $NetBSD: svc_dg.c,v 1.12 2008/04/25 17:44:44 christos Exp $ */ 2 3 /* 4 * Sun RPC is a product of Sun Microsystems, Inc. and is provided for 5 * unrestricted use provided that this legend is included on all tape 6 * media and as a part of the software program in whole or part. Users 7 * may copy or modify Sun RPC without charge, but are not authorized 8 * to license or distribute it to anyone else except as part of a product or 9 * program developed by the user. 10 * 11 * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE 12 * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR 13 * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. 14 * 15 * Sun RPC is provided with no support and without any obligation on the 16 * part of Sun Microsystems, Inc. to assist in its use, correction, 17 * modification or enhancement. 18 * 19 * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE 20 * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC 21 * OR ANY PART THEREOF. 22 * 23 * In no event will Sun Microsystems, Inc. be liable for any lost revenue 24 * or profits or other special, indirect and consequential damages, even if 25 * Sun has been advised of the possibility of such damages. 26 * 27 * Sun Microsystems, Inc. 28 * 2550 Garcia Avenue 29 * Mountain View, California 94043 30 */ 31 32 /* 33 * Copyright (c) 1986-1991 by Sun Microsystems Inc. 34 */ 35 36 /* #ident "@(#)svc_dg.c 1.17 94/04/24 SMI" */ 37 38 39 /* 40 * svc_dg.c, Server side for connectionless RPC. 41 * 42 * Does some caching in the hopes of achieving execute-at-most-once semantics. 43 */ 44 45 #include <sys/cdefs.h> 46 #if defined(LIBC_SCCS) && !defined(lint) 47 __RCSID("$NetBSD: svc_dg.c,v 1.12 2008/04/25 17:44:44 christos Exp $"); 48 #endif 49 50 #include "namespace.h" 51 #include "reentrant.h" 52 #include <sys/types.h> 53 #include <sys/socket.h> 54 #include <rpc/rpc.h> 55 #include <assert.h> 56 #include <errno.h> 57 #include <unistd.h> 58 #include <stdio.h> 59 #include <stdlib.h> 60 #include <string.h> 61 #ifdef RPC_CACHE_DEBUG 62 #include <netconfig.h> 63 #include <netdir.h> 64 #endif 65 #include <err.h> 66 67 #include "rpc_internal.h" 68 #include "svc_dg.h" 69 70 #define su_data(xprt) ((struct svc_dg_data *)(xprt->xp_p2)) 71 #define rpc_buffer(xprt) ((xprt)->xp_p1) 72 73 #ifdef __weak_alias 74 __weak_alias(svc_dg_create,_svc_dg_create) 75 #endif 76 77 #ifndef MAX 78 #define MAX(a, b) (((a) > (b)) ? (a) : (b)) 79 #endif 80 81 static void svc_dg_ops __P((SVCXPRT *)); 82 static enum xprt_stat svc_dg_stat __P((SVCXPRT *)); 83 static bool_t svc_dg_recv __P((SVCXPRT *, struct rpc_msg *)); 84 static bool_t svc_dg_reply __P((SVCXPRT *, struct rpc_msg *)); 85 static bool_t svc_dg_getargs __P((SVCXPRT *, xdrproc_t, caddr_t)); 86 static bool_t svc_dg_freeargs __P((SVCXPRT *, xdrproc_t, caddr_t)); 87 static void svc_dg_destroy __P((SVCXPRT *)); 88 static bool_t svc_dg_control __P((SVCXPRT *, const u_int, void *)); 89 static int cache_get __P((SVCXPRT *, struct rpc_msg *, char **, size_t *)); 90 static void cache_set __P((SVCXPRT *, size_t)); 91 92 /* 93 * Usage: 94 * xprt = svc_dg_create(sock, sendsize, recvsize); 95 * Does other connectionless specific initializations. 96 * Once *xprt is initialized, it is registered. 97 * see (svc.h, xprt_register). If recvsize or sendsize are 0 suitable 98 * system defaults are chosen. 99 * The routines returns NULL if a problem occurred. 100 */ 101 static const char svc_dg_str[] = "svc_dg_create: %s"; 102 static const char svc_dg_err1[] = "could not get transport information"; 103 static const char svc_dg_err2[] = " transport does not support data transfer"; 104 static const char __no_mem_str[] = "out of memory"; 105 106 SVCXPRT * 107 svc_dg_create(fd, sendsize, recvsize) 108 int fd; 109 u_int sendsize; 110 u_int recvsize; 111 { 112 SVCXPRT *xprt; 113 struct svc_dg_data *su = NULL; 114 struct __rpc_sockinfo si; 115 struct sockaddr_storage ss; 116 socklen_t slen; 117 118 if (!__rpc_fd2sockinfo(fd, &si)) { 119 warnx(svc_dg_str, svc_dg_err1); 120 return (NULL); 121 } 122 /* 123 * Find the receive and the send size 124 */ 125 sendsize = __rpc_get_t_size(si.si_af, si.si_proto, (int)sendsize); 126 recvsize = __rpc_get_t_size(si.si_af, si.si_proto, (int)recvsize); 127 if ((sendsize == 0) || (recvsize == 0)) { 128 warnx(svc_dg_str, svc_dg_err2); 129 return (NULL); 130 } 131 132 xprt = mem_alloc(sizeof (SVCXPRT)); 133 if (xprt == NULL) 134 goto freedata; 135 memset(xprt, 0, sizeof (SVCXPRT)); 136 137 su = mem_alloc(sizeof (*su)); 138 if (su == NULL) 139 goto freedata; 140 su->su_iosz = ((MAX(sendsize, recvsize) + 3) / 4) * 4; 141 if ((rpc_buffer(xprt) = malloc(su->su_iosz)) == NULL) 142 goto freedata; 143 xdrmem_create(&(su->su_xdrs), rpc_buffer(xprt), su->su_iosz, 144 XDR_DECODE); 145 su->su_cache = NULL; 146 xprt->xp_fd = fd; 147 xprt->xp_p2 = (caddr_t)(void *)su; 148 xprt->xp_verf.oa_base = su->su_verfbody; 149 svc_dg_ops(xprt); 150 xprt->xp_rtaddr.maxlen = sizeof (struct sockaddr_storage); 151 152 slen = sizeof ss; 153 if (getsockname(fd, (struct sockaddr *)(void *)&ss, &slen) < 0) 154 goto freedata; 155 xprt->xp_ltaddr.buf = mem_alloc(sizeof (struct sockaddr_storage)); 156 xprt->xp_ltaddr.maxlen = sizeof (struct sockaddr_storage); 157 xprt->xp_ltaddr.len = slen; 158 memcpy(xprt->xp_ltaddr.buf, &ss, slen); 159 160 xprt_register(xprt); 161 return (xprt); 162 freedata: 163 (void) warnx(svc_dg_str, __no_mem_str); 164 if (xprt) { 165 if (su) 166 (void) mem_free(su, sizeof (*su)); 167 (void) mem_free(xprt, sizeof (SVCXPRT)); 168 } 169 return (NULL); 170 } 171 172 /*ARGSUSED*/ 173 static enum xprt_stat 174 svc_dg_stat(xprt) 175 SVCXPRT *xprt; 176 { 177 return (XPRT_IDLE); 178 } 179 180 static bool_t 181 svc_dg_recv(xprt, msg) 182 SVCXPRT *xprt; 183 struct rpc_msg *msg; 184 { 185 struct svc_dg_data *su; 186 XDR *xdrs; 187 char *reply; 188 struct sockaddr_storage ss; 189 socklen_t alen; 190 size_t replylen; 191 ssize_t rlen; 192 193 _DIAGASSERT(xprt != NULL); 194 _DIAGASSERT(msg != NULL); 195 196 su = su_data(xprt); 197 xdrs = &(su->su_xdrs); 198 199 again: 200 alen = sizeof (struct sockaddr_storage); 201 rlen = recvfrom(xprt->xp_fd, rpc_buffer(xprt), su->su_iosz, 0, 202 (struct sockaddr *)(void *)&ss, &alen); 203 if (rlen == -1 && errno == EINTR) 204 goto again; 205 if (rlen == -1 || (rlen < (ssize_t)(4 * sizeof (u_int32_t)))) 206 return (FALSE); 207 if (xprt->xp_rtaddr.len < alen) { 208 if (xprt->xp_rtaddr.len != 0) 209 mem_free(xprt->xp_rtaddr.buf, xprt->xp_rtaddr.len); 210 xprt->xp_rtaddr.buf = mem_alloc(alen); 211 xprt->xp_rtaddr.len = alen; 212 } 213 memcpy(xprt->xp_rtaddr.buf, &ss, alen); 214 #ifdef PORTMAP 215 if (ss.ss_family == AF_INET) { 216 xprt->xp_raddr = *(struct sockaddr_in *)xprt->xp_rtaddr.buf; 217 xprt->xp_addrlen = sizeof (struct sockaddr_in); 218 } 219 #endif 220 xdrs->x_op = XDR_DECODE; 221 XDR_SETPOS(xdrs, 0); 222 if (! xdr_callmsg(xdrs, msg)) { 223 return (FALSE); 224 } 225 su->su_xid = msg->rm_xid; 226 if (su->su_cache != NULL) { 227 if (cache_get(xprt, msg, &reply, &replylen)) { 228 (void)sendto(xprt->xp_fd, reply, replylen, 0, 229 (struct sockaddr *)(void *)&ss, alen); 230 return (FALSE); 231 } 232 } 233 return (TRUE); 234 } 235 236 static bool_t 237 svc_dg_reply(xprt, msg) 238 SVCXPRT *xprt; 239 struct rpc_msg *msg; 240 { 241 struct svc_dg_data *su; 242 XDR *xdrs; 243 bool_t stat = FALSE; 244 size_t slen; 245 246 _DIAGASSERT(xprt != NULL); 247 _DIAGASSERT(msg != NULL); 248 249 su = su_data(xprt); 250 xdrs = &(su->su_xdrs); 251 252 xdrs->x_op = XDR_ENCODE; 253 XDR_SETPOS(xdrs, 0); 254 msg->rm_xid = su->su_xid; 255 if (xdr_replymsg(xdrs, msg)) { 256 slen = XDR_GETPOS(xdrs); 257 if (sendto(xprt->xp_fd, rpc_buffer(xprt), slen, 0, 258 (struct sockaddr *)xprt->xp_rtaddr.buf, 259 (socklen_t)xprt->xp_rtaddr.len) == (ssize_t) slen) { 260 stat = TRUE; 261 if (su->su_cache) 262 cache_set(xprt, slen); 263 } 264 } 265 return (stat); 266 } 267 268 static bool_t 269 svc_dg_getargs(xprt, xdr_args, args_ptr) 270 SVCXPRT *xprt; 271 xdrproc_t xdr_args; 272 caddr_t args_ptr; 273 { 274 return (*xdr_args)(&(su_data(xprt)->su_xdrs), args_ptr); 275 } 276 277 static bool_t 278 svc_dg_freeargs(xprt, xdr_args, args_ptr) 279 SVCXPRT *xprt; 280 xdrproc_t xdr_args; 281 caddr_t args_ptr; 282 { 283 XDR *xdrs; 284 285 _DIAGASSERT(xprt != NULL); 286 287 xdrs = &(su_data(xprt)->su_xdrs); 288 xdrs->x_op = XDR_FREE; 289 return (*xdr_args)(xdrs, args_ptr); 290 } 291 292 static void 293 svc_dg_destroy(xprt) 294 SVCXPRT *xprt; 295 { 296 struct svc_dg_data *su; 297 298 _DIAGASSERT(xprt != NULL); 299 300 su = su_data(xprt); 301 302 xprt_unregister(xprt); 303 if (xprt->xp_fd != -1) 304 (void)close(xprt->xp_fd); 305 XDR_DESTROY(&(su->su_xdrs)); 306 (void) mem_free(rpc_buffer(xprt), su->su_iosz); 307 (void) mem_free(su, sizeof (*su)); 308 if (xprt->xp_rtaddr.buf) 309 (void) mem_free(xprt->xp_rtaddr.buf, xprt->xp_rtaddr.maxlen); 310 if (xprt->xp_ltaddr.buf) 311 (void) mem_free(xprt->xp_ltaddr.buf, xprt->xp_ltaddr.maxlen); 312 if (xprt->xp_tp) 313 (void) free(xprt->xp_tp); 314 (void) mem_free(xprt, sizeof (SVCXPRT)); 315 } 316 317 static bool_t 318 /*ARGSUSED*/ 319 svc_dg_control(xprt, rq, in) 320 SVCXPRT *xprt; 321 const u_int rq; 322 void *in; 323 { 324 return (FALSE); 325 } 326 327 static void 328 svc_dg_ops(xprt) 329 SVCXPRT *xprt; 330 { 331 static struct xp_ops ops; 332 static struct xp_ops2 ops2; 333 #ifdef _REENTRANT 334 extern mutex_t ops_lock; 335 #endif 336 337 _DIAGASSERT(xprt != NULL); 338 339 /* VARIABLES PROTECTED BY ops_lock: ops */ 340 341 mutex_lock(&ops_lock); 342 if (ops.xp_recv == NULL) { 343 ops.xp_recv = svc_dg_recv; 344 ops.xp_stat = svc_dg_stat; 345 ops.xp_getargs = svc_dg_getargs; 346 ops.xp_reply = svc_dg_reply; 347 ops.xp_freeargs = svc_dg_freeargs; 348 ops.xp_destroy = svc_dg_destroy; 349 ops2.xp_control = svc_dg_control; 350 } 351 xprt->xp_ops = &ops; 352 xprt->xp_ops2 = &ops2; 353 mutex_unlock(&ops_lock); 354 } 355 356 /* The CACHING COMPONENT */ 357 358 /* 359 * Could have been a separate file, but some part of it depends upon the 360 * private structure of the client handle. 361 * 362 * Fifo cache for cl server 363 * Copies pointers to reply buffers into fifo cache 364 * Buffers are sent again if retransmissions are detected. 365 */ 366 367 #define SPARSENESS 4 /* 75% sparse */ 368 369 #define ALLOC(type, size) \ 370 mem_alloc((sizeof (type) * (size))) 371 372 #define MEMZERO(addr, type, size) \ 373 (void) memset((void *) (addr), 0, sizeof (type) * (int) (size)) 374 375 #define FREE(addr, type, size) \ 376 mem_free((addr), (sizeof (type) * (size))) 377 378 /* 379 * An entry in the cache 380 */ 381 typedef struct cache_node *cache_ptr; 382 struct cache_node { 383 /* 384 * Index into cache is xid, proc, vers, prog and address 385 */ 386 u_int32_t cache_xid; 387 rpcproc_t cache_proc; 388 rpcvers_t cache_vers; 389 rpcprog_t cache_prog; 390 struct netbuf cache_addr; 391 /* 392 * The cached reply and length 393 */ 394 char *cache_reply; 395 size_t cache_replylen; 396 /* 397 * Next node on the list, if there is a collision 398 */ 399 cache_ptr cache_next; 400 }; 401 402 /* 403 * The entire cache 404 */ 405 struct cl_cache { 406 u_int uc_size; /* size of cache */ 407 cache_ptr *uc_entries; /* hash table of entries in cache */ 408 cache_ptr *uc_fifo; /* fifo list of entries in cache */ 409 u_int uc_nextvictim; /* points to next victim in fifo list */ 410 rpcprog_t uc_prog; /* saved program number */ 411 rpcvers_t uc_vers; /* saved version number */ 412 rpcproc_t uc_proc; /* saved procedure number */ 413 }; 414 415 416 /* 417 * the hashing function 418 */ 419 #define CACHE_LOC(transp, xid) \ 420 (xid % (SPARSENESS * ((struct cl_cache *) \ 421 su_data(transp)->su_cache)->uc_size)) 422 423 #ifdef _REENTRANT 424 extern mutex_t dupreq_lock; 425 #endif 426 427 /* 428 * Enable use of the cache. Returns 1 on success, 0 on failure. 429 * Note: there is no disable. 430 */ 431 static const char cache_enable_str[] = "svc_enablecache: %s %s"; 432 static const char alloc_err[] = "could not allocate cache "; 433 static const char enable_err[] = "cache already enabled"; 434 435 int 436 svc_dg_enablecache(transp, size) 437 SVCXPRT *transp; 438 u_int size; 439 { 440 struct svc_dg_data *su; 441 struct cl_cache *uc; 442 443 _DIAGASSERT(transp != NULL); 444 445 su = su_data(transp); 446 447 mutex_lock(&dupreq_lock); 448 if (su->su_cache != NULL) { 449 (void) warnx(cache_enable_str, enable_err, " "); 450 mutex_unlock(&dupreq_lock); 451 return (0); 452 } 453 uc = ALLOC(struct cl_cache, 1); 454 if (uc == NULL) { 455 warnx(cache_enable_str, alloc_err, " "); 456 mutex_unlock(&dupreq_lock); 457 return (0); 458 } 459 uc->uc_size = size; 460 uc->uc_nextvictim = 0; 461 uc->uc_entries = ALLOC(cache_ptr, size * SPARSENESS); 462 if (uc->uc_entries == NULL) { 463 warnx(cache_enable_str, alloc_err, "data"); 464 FREE(uc, struct cl_cache, 1); 465 mutex_unlock(&dupreq_lock); 466 return (0); 467 } 468 MEMZERO(uc->uc_entries, cache_ptr, size * SPARSENESS); 469 uc->uc_fifo = ALLOC(cache_ptr, size); 470 if (uc->uc_fifo == NULL) { 471 warnx(cache_enable_str, alloc_err, "fifo"); 472 FREE(uc->uc_entries, cache_ptr, size * SPARSENESS); 473 FREE(uc, struct cl_cache, 1); 474 mutex_unlock(&dupreq_lock); 475 return (0); 476 } 477 MEMZERO(uc->uc_fifo, cache_ptr, size); 478 su->su_cache = (char *)(void *)uc; 479 mutex_unlock(&dupreq_lock); 480 return (1); 481 } 482 483 /* 484 * Set an entry in the cache. It assumes that the uc entry is set from 485 * the earlier call to cache_get() for the same procedure. This will always 486 * happen because cache_get() is calle by svc_dg_recv and cache_set() is called 487 * by svc_dg_reply(). All this hoopla because the right RPC parameters are 488 * not available at svc_dg_reply time. 489 */ 490 491 static const char cache_set_str[] = "cache_set: %s"; 492 static const char cache_set_err1[] = "victim not found"; 493 static const char cache_set_err2[] = "victim alloc failed"; 494 static const char cache_set_err3[] = "could not allocate new rpc buffer"; 495 496 static void 497 cache_set(xprt, replylen) 498 SVCXPRT *xprt; 499 size_t replylen; 500 { 501 cache_ptr victim; 502 cache_ptr *vicp; 503 struct svc_dg_data *su; 504 struct cl_cache *uc; 505 u_int loc; 506 char *newbuf; 507 #ifdef RPC_CACHE_DEBUG 508 struct netconfig *nconf; 509 char *uaddr; 510 #endif 511 512 _DIAGASSERT(xprt != NULL); 513 514 su = su_data(xprt); 515 uc = (struct cl_cache *) su->su_cache; 516 517 mutex_lock(&dupreq_lock); 518 /* 519 * Find space for the new entry, either by 520 * reusing an old entry, or by mallocing a new one 521 */ 522 victim = uc->uc_fifo[uc->uc_nextvictim]; 523 if (victim != NULL) { 524 loc = CACHE_LOC(xprt, victim->cache_xid); 525 for (vicp = &uc->uc_entries[loc]; 526 *vicp != NULL && *vicp != victim; 527 vicp = &(*vicp)->cache_next) 528 ; 529 if (*vicp == NULL) { 530 warnx(cache_set_str, cache_set_err1); 531 mutex_unlock(&dupreq_lock); 532 return; 533 } 534 *vicp = victim->cache_next; /* remove from cache */ 535 newbuf = victim->cache_reply; 536 } else { 537 victim = ALLOC(struct cache_node, 1); 538 if (victim == NULL) { 539 warnx(cache_set_str, cache_set_err2); 540 mutex_unlock(&dupreq_lock); 541 return; 542 } 543 newbuf = mem_alloc(su->su_iosz); 544 if (newbuf == NULL) { 545 warnx(cache_set_str, cache_set_err3); 546 FREE(victim, struct cache_node, 1); 547 mutex_unlock(&dupreq_lock); 548 return; 549 } 550 } 551 552 /* 553 * Store it away 554 */ 555 #ifdef RPC_CACHE_DEBUG 556 if (nconf = getnetconfigent(xprt->xp_netid)) { 557 uaddr = taddr2uaddr(nconf, &xprt->xp_rtaddr); 558 freenetconfigent(nconf); 559 printf( 560 "cache set for xid= %x prog=%d vers=%d proc=%d for rmtaddr=%s\n", 561 su->su_xid, uc->uc_prog, uc->uc_vers, 562 uc->uc_proc, uaddr); 563 free(uaddr); 564 } 565 #endif 566 victim->cache_replylen = replylen; 567 victim->cache_reply = rpc_buffer(xprt); 568 rpc_buffer(xprt) = newbuf; 569 xdrmem_create(&(su->su_xdrs), rpc_buffer(xprt), 570 su->su_iosz, XDR_ENCODE); 571 victim->cache_xid = su->su_xid; 572 victim->cache_proc = uc->uc_proc; 573 victim->cache_vers = uc->uc_vers; 574 victim->cache_prog = uc->uc_prog; 575 victim->cache_addr = xprt->xp_rtaddr; 576 victim->cache_addr.buf = ALLOC(char, xprt->xp_rtaddr.len); 577 (void) memcpy(victim->cache_addr.buf, xprt->xp_rtaddr.buf, 578 (size_t)xprt->xp_rtaddr.len); 579 loc = CACHE_LOC(xprt, victim->cache_xid); 580 victim->cache_next = uc->uc_entries[loc]; 581 uc->uc_entries[loc] = victim; 582 uc->uc_fifo[uc->uc_nextvictim++] = victim; 583 uc->uc_nextvictim %= uc->uc_size; 584 mutex_unlock(&dupreq_lock); 585 } 586 587 /* 588 * Try to get an entry from the cache 589 * return 1 if found, 0 if not found and set the stage for cache_set() 590 */ 591 static int 592 cache_get(xprt, msg, replyp, replylenp) 593 SVCXPRT *xprt; 594 struct rpc_msg *msg; 595 char **replyp; 596 size_t *replylenp; 597 { 598 u_int loc; 599 cache_ptr ent; 600 struct svc_dg_data *su; 601 struct cl_cache *uc; 602 #ifdef RPC_CACHE_DEBUG 603 struct netconfig *nconf; 604 char *uaddr; 605 #endif 606 607 _DIAGASSERT(xprt != NULL); 608 _DIAGASSERT(msg != NULL); 609 _DIAGASSERT(replyp != NULL); 610 _DIAGASSERT(replylenp != NULL); 611 612 su = su_data(xprt); 613 uc = (struct cl_cache *) su->su_cache; 614 615 mutex_lock(&dupreq_lock); 616 loc = CACHE_LOC(xprt, su->su_xid); 617 for (ent = uc->uc_entries[loc]; ent != NULL; ent = ent->cache_next) { 618 if (ent->cache_xid == su->su_xid && 619 ent->cache_proc == msg->rm_call.cb_proc && 620 ent->cache_vers == msg->rm_call.cb_vers && 621 ent->cache_prog == msg->rm_call.cb_prog && 622 ent->cache_addr.len == xprt->xp_rtaddr.len && 623 (memcmp(ent->cache_addr.buf, xprt->xp_rtaddr.buf, 624 xprt->xp_rtaddr.len) == 0)) { 625 #ifdef RPC_CACHE_DEBUG 626 if (nconf = getnetconfigent(xprt->xp_netid)) { 627 uaddr = taddr2uaddr(nconf, &xprt->xp_rtaddr); 628 freenetconfigent(nconf); 629 printf( 630 "cache entry found for xid=%x prog=%d vers=%d proc=%d for rmtaddr=%s\n", 631 su->su_xid, msg->rm_call.cb_prog, 632 msg->rm_call.cb_vers, 633 msg->rm_call.cb_proc, uaddr); 634 free(uaddr); 635 } 636 #endif 637 *replyp = ent->cache_reply; 638 *replylenp = ent->cache_replylen; 639 mutex_unlock(&dupreq_lock); 640 return (1); 641 } 642 } 643 /* 644 * Failed to find entry 645 * Remember a few things so we can do a set later 646 */ 647 uc->uc_proc = msg->rm_call.cb_proc; 648 uc->uc_vers = msg->rm_call.cb_vers; 649 uc->uc_prog = msg->rm_call.cb_prog; 650 mutex_unlock(&dupreq_lock); 651 return (0); 652 } 653