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