1 /* 2 * Copyright (c) 1989 The Regents of the University of California. 3 * All rights reserved. 4 * 5 * This code is derived from software contributed to Berkeley by 6 * Rick Macklem at The University of Guelph. 7 * 8 * Redistribution and use in source and binary forms are permitted 9 * provided that the above copyright notice and this paragraph are 10 * duplicated in all such forms and that any documentation, 11 * advertising materials, and other materials related to such 12 * distribution and use acknowledge that the software was developed 13 * by the University of California, Berkeley. The name of the 14 * University may not be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 19 * 20 * @(#)nfs_socket.c 7.2 (Berkeley) 07/06/89 21 */ 22 23 /* 24 * Socket operations for use by nfs (similar to uipc_socket.c, but never 25 * with copies to/from a uio vector) 26 * NB: For now, they only work for UDP datagram sockets. 27 * (Use on stream sockets would require some record boundary mark in the 28 * stream such as Sun's RM (Section 3.2 of the Sun RPC Message Protocol 29 * manual, in Networking on the Sun Workstation, Part #800-1324-03 30 * and different versions of send, receive and reply that do not assume 31 * an atomic protocol 32 */ 33 34 #include "types.h" 35 #include "param.h" 36 #include "uio.h" 37 #include "user.h" 38 #include "mount.h" 39 #include "kernel.h" 40 #include "malloc.h" 41 #include "mbuf.h" 42 #include "vnode.h" 43 #include "domain.h" 44 #include "protosw.h" 45 #include "socket.h" 46 #include "socketvar.h" 47 #include "netinet/in.h" 48 #include "rpcv2.h" 49 #include "nfsv2.h" 50 #include "nfs.h" 51 #include "xdr_subs.h" 52 #include "nfsm_subs.h" 53 #include "nfsmount.h" 54 55 #define TRUE 1 56 57 /* set lock on sockbuf sb, sleep at neg prio */ 58 #define nfs_sblock(sb) { \ 59 while ((sb)->sb_flags & SB_LOCK) { \ 60 (sb)->sb_flags |= SB_WANT; \ 61 sleep((caddr_t)&(sb)->sb_flags, PZERO-1); \ 62 } \ 63 (sb)->sb_flags |= SB_LOCK; \ 64 } 65 66 /* 67 * External data, mostly RPC constants in XDR form 68 */ 69 extern u_long rpc_reply, rpc_msgdenied, rpc_mismatch, rpc_vers, rpc_auth_unix, 70 rpc_msgaccepted, rpc_call; 71 extern u_long nfs_prog, nfs_vers; 72 int nfsrv_null(), 73 nfsrv_getattr(), 74 nfsrv_setattr(), 75 nfsrv_lookup(), 76 nfsrv_readlink(), 77 nfsrv_read(), 78 nfsrv_write(), 79 nfsrv_create(), 80 nfsrv_remove(), 81 nfsrv_rename(), 82 nfsrv_link(), 83 nfsrv_symlink(), 84 nfsrv_mkdir(), 85 nfsrv_rmdir(), 86 nfsrv_readdir(), 87 nfsrv_statfs(), 88 nfsrv_noop(); 89 90 int (*nfsrv_procs[NFS_NPROCS])() = { 91 nfsrv_null, 92 nfsrv_getattr, 93 nfsrv_setattr, 94 nfsrv_noop, 95 nfsrv_lookup, 96 nfsrv_readlink, 97 nfsrv_read, 98 nfsrv_noop, 99 nfsrv_write, 100 nfsrv_create, 101 nfsrv_remove, 102 nfsrv_rename, 103 nfsrv_link, 104 nfsrv_symlink, 105 nfsrv_mkdir, 106 nfsrv_rmdir, 107 nfsrv_readdir, 108 nfsrv_statfs, 109 }; 110 111 112 /* 113 * This is a stripped down version of sosend() specific to 114 * udp/ip and uses the mbuf list provdied 115 */ 116 nfs_udpsend(so, nam, top, flags, siz) 117 register struct socket *so; 118 struct mbuf *nam; 119 struct mbuf *top; 120 int flags; 121 int siz; 122 { 123 register int space; 124 int error = 0, s, dontroute, first = 1; 125 126 dontroute = 127 (flags & MSG_DONTROUTE) && (so->so_options & SO_DONTROUTE) == 0 && 128 (so->so_proto->pr_flags & PR_ATOMIC); 129 #define snderr(errno) { error = errno; splx(s); goto release; } 130 131 #ifdef MGETHDR 132 top->m_pkthdr.len = siz; 133 #endif 134 restart: 135 nfs_sblock(&so->so_snd); 136 s = splnet(); 137 if (so->so_state & SS_CANTSENDMORE) 138 snderr(EPIPE); 139 if (so->so_error) 140 snderr(so->so_error); 141 space = sbspace(&so->so_snd); 142 if (space < siz) { 143 sbunlock(&so->so_snd); 144 nfs_sbwait(&so->so_snd); 145 splx(s); 146 goto restart; 147 } 148 splx(s); 149 if (dontroute) 150 so->so_options |= SO_DONTROUTE; 151 s = splnet(); /* XXX */ 152 error = (*so->so_proto->pr_usrreq)(so, 153 PRU_SEND, 154 top, (caddr_t)nam, (struct mbuf *)0, (struct mbuf *)0); 155 splx(s); 156 if (dontroute) 157 so->so_options &= ~SO_DONTROUTE; 158 top = (struct mbuf *)0; 159 160 release: 161 sbunlock(&so->so_snd); 162 if (top) 163 m_freem(top); 164 return (error); 165 } 166 167 /* 168 * This is a stripped down udp specific version of soreceive() 169 */ 170 nfs_udpreceive(so, aname, mp) 171 register struct socket *so; 172 struct mbuf **aname; 173 struct mbuf **mp; 174 { 175 register struct mbuf *m; 176 int s, error = 0; 177 struct protosw *pr = so->so_proto; 178 struct mbuf *nextrecord; 179 180 if (aname) 181 *aname = 0; 182 183 restart: 184 sblock(&so->so_rcv); 185 s = splnet(); 186 187 if (so->so_rcv.sb_cc == 0) { 188 if (so->so_error) { 189 error = so->so_error; 190 so->so_error = 0; 191 goto release; 192 } 193 if (so->so_state & SS_CANTRCVMORE) 194 goto release; 195 sbunlock(&so->so_rcv); 196 sbwait(&so->so_rcv); 197 splx(s); 198 goto restart; 199 } 200 m = so->so_rcv.sb_mb; 201 if (m == 0) 202 panic("nfs_receive 1"); 203 nextrecord = m->m_nextpkt; 204 if (m->m_type != MT_SONAME) 205 panic("nfs_receive 1a"); 206 sbfree(&so->so_rcv, m); 207 if (aname) { 208 *aname = m; 209 so->so_rcv.sb_mb = m->m_next; 210 m->m_next = 0; 211 m = so->so_rcv.sb_mb; 212 } else { 213 MFREE(m, so->so_rcv.sb_mb); 214 m = so->so_rcv.sb_mb; 215 } 216 if (m && m->m_type == MT_RIGHTS) 217 panic("nfs_receive 2"); 218 if (m && m->m_type == MT_CONTROL) { 219 sbfree(&so->so_rcv, m); 220 MFREE(m, so->so_rcv.sb_mb); 221 m = so->so_rcv.sb_mb; 222 } 223 *mp = m; 224 while (m) { 225 if (m->m_type != MT_DATA && m->m_type != MT_HEADER) 226 panic("nfs_receive 3"); 227 sbfree(&so->so_rcv, m); 228 m = so->so_rcv.sb_mb = m->m_next; 229 } 230 so->so_rcv.sb_mb = nextrecord; 231 so->so_state &= ~SS_RCVATMARK; /* Necessary ?? */ 232 release: 233 sbunlock(&so->so_rcv); 234 splx(s); 235 return (error); 236 } 237 238 struct nfsreq nfsreqh = { 239 (struct nfsreq *)0, 240 (struct nfsreq *)0, 241 (struct mbuf *)0, 242 (struct mbuf *)0, 243 (struct nfsmount *)0, 244 0, 0, 0, 0, 0, 245 }; 246 247 struct rpc_replyhead { 248 u_long r_xid; 249 u_long r_rep; 250 }; 251 252 /* 253 * Implement receipt of reply on a socket. 254 * We depend on the way that records are added to the sockbuf 255 * by sbappend*. In particular, each record (mbufs linked through m_next) 256 * must begin with an address, followed by optional MT_CONTROL mbuf 257 * and then zero or more mbufs of data. 258 * Although the sockbuf is locked, new data may still be appended, 259 * and thus we must maintain consistency of the sockbuf during that time. 260 * We must search through the list of received datagrams matching them 261 * with outstanding requests using the xid, until ours is found. 262 */ 263 nfs_udpreply(so, mntp, repl, myrep) 264 register struct socket *so; 265 struct nfsmount *mntp; 266 struct nfsreq *repl, *myrep; 267 { 268 register struct mbuf *m; 269 register struct nfsreq *rep; 270 register int error = 0, s; 271 struct protosw *pr = so->so_proto; 272 struct mbuf *nextrecord; 273 struct sockaddr_in *sad, *sad2; 274 struct rpc_replyhead replyh; 275 struct mbuf *mp; 276 char *cp; 277 int cnt, xfer; 278 int found; 279 280 restart: 281 /* Already received, bye bye */ 282 if (myrep->r_mrep != NULL) 283 return (0); 284 /* If a soft mount and we have run out of retries */ 285 if (myrep->r_retry == 0 && myrep->r_timer == 0) 286 return (ETIMEDOUT); 287 nfs_sblock(&so->so_rcv); 288 s = splnet(); 289 290 if (so->so_rcv.sb_cc == 0) { 291 if (so->so_error) { 292 error = so->so_error; 293 so->so_error = 0; 294 goto release; 295 } 296 if (so->so_state & SS_CANTRCVMORE) 297 goto release; 298 sbunlock(&so->so_rcv); 299 nfs_sbwait(&so->so_rcv); 300 splx(s); 301 goto restart; 302 } 303 m = so->so_rcv.sb_mb; 304 if (m == 0) 305 panic("nfs_soreply 1"); 306 nextrecord = m->m_nextpkt; 307 308 /* 309 * Take off the address, check for rights and ditch any control 310 * mbufs. 311 */ 312 if (m->m_type != MT_SONAME) 313 panic("nfs reply SONAME"); 314 sad = mtod(m, struct sockaddr_in *); 315 sad2 = mtod(mntp->nm_sockaddr, struct sockaddr_in *); 316 found = 0; 317 if (sad->sin_addr.s_addr != sad2->sin_addr.s_addr) 318 goto dropit; 319 sbfree(&so->so_rcv, m); 320 MFREE(m, so->so_rcv.sb_mb); 321 m = so->so_rcv.sb_mb; 322 if (m && m->m_type == MT_RIGHTS) 323 panic("nfs reply RIGHTS"); 324 if (m && m->m_type == MT_CONTROL) { 325 sbfree(&so->so_rcv, m); 326 MFREE(m, so->so_rcv.sb_mb); 327 m = so->so_rcv.sb_mb; 328 } 329 if (m) 330 m->m_nextpkt = nextrecord; 331 else { 332 sbunlock(&so->so_rcv); 333 splx(s); 334 goto restart; 335 } 336 337 /* 338 * Get the xid and check that it is an rpc reply 339 */ 340 mp = m; 341 if (m->m_len >= 2*NFSX_UNSIGNED) 342 bcopy(mtod(m, caddr_t), (caddr_t)&replyh, 2*NFSX_UNSIGNED); 343 else { 344 cnt = 2*NFSX_UNSIGNED; 345 cp = (caddr_t)&replyh; 346 while (mp && cnt > 0) { 347 if (mp->m_len > 0) { 348 xfer = (mp->m_len >= cnt) ? cnt : mp->m_len; 349 bcopy(mtod(mp, caddr_t), cp, xfer); 350 cnt -= xfer; 351 cp += xfer; 352 } 353 if (cnt > 0) 354 mp = mp->m_next; 355 } 356 } 357 if (replyh.r_rep != rpc_reply || mp == NULL) 358 goto dropit; 359 /* 360 * Loop through the request list to match up the reply 361 * Iff no match, just drop the datagram 362 */ 363 rep = repl; 364 while (!found && rep) { 365 if (rep->r_mrep == NULL && replyh.r_xid == rep->r_xid) { 366 /* Found it.. */ 367 rep->r_mrep = m; 368 while (m) { 369 if (m->m_type != MT_DATA && m->m_type != MT_HEADER) 370 panic("nfs_soreply 3"); 371 sbfree(&so->so_rcv, m); 372 m = so->so_rcv.sb_mb = m->m_next; 373 } 374 so->so_rcv.sb_mb = nextrecord; 375 if (rep == myrep) 376 goto release; 377 found++; 378 } 379 rep = rep->r_next; 380 } 381 /* Iff not matched to request, drop it */ 382 dropit: 383 if (!found) 384 sbdroprecord(&so->so_rcv); 385 sbunlock(&so->so_rcv); 386 splx(s); 387 goto restart; 388 release: 389 sbunlock(&so->so_rcv); 390 splx(s); 391 return (error); 392 } 393 394 /* 395 * nfs_request - goes something like this 396 * - fill in request struct 397 * - links it into list 398 * - calls nfs_sosend() for first transmit 399 * - calls nfs_soreceive() to get reply 400 * - break down rpc header and return with nfs reply pointed to 401 * by mrep or error 402 * nb: always frees up mreq mbuf list 403 */ 404 nfs_request(vp, mreq, xid, mp, mrp, mdp, dposp) 405 struct vnode *vp; 406 struct mbuf *mreq; 407 u_long xid; 408 struct mount *mp; 409 struct mbuf **mrp; 410 struct mbuf **mdp; 411 caddr_t *dposp; 412 { 413 register struct mbuf *m, *mrep; 414 register struct nfsreq *rep; 415 register u_long *p; 416 register int len; 417 struct nfsmount *mntp; 418 struct mbuf *md; 419 caddr_t dpos; 420 char *cp2; 421 int t1; 422 int s; 423 int error; 424 425 mntp = vfs_to_nfs(mp); 426 m = mreq; 427 MALLOC(rep, struct nfsreq *, sizeof(struct nfsreq), M_NFSREQ, M_WAITOK); 428 rep->r_xid = xid; 429 rep->r_mntp = mntp; 430 rep->r_vp = vp; 431 if (mntp->nm_flag & NFSMNT_SOFT) 432 rep->r_retry = mntp->nm_retrans; 433 else 434 rep->r_retry = VNOVAL; 435 rep->r_mrep = NULL; 436 rep->r_mreq = m; 437 rep->r_timer = rep->r_timeout = mntp->nm_timeo; 438 len = 0; 439 while (m) { 440 len += m->m_len; 441 m = m->m_next; 442 } 443 rep->r_msiz = len; 444 m = NFSMCOPY(mreq, 0, M_COPYALL, M_WAIT); 445 446 /* Chain it into list of outstanding requests */ 447 s = splnet(); 448 rep->r_next = nfsreqh.r_next; 449 if (rep->r_next != NULL) 450 rep->r_next->r_prev = rep; 451 nfsreqh.r_next = rep; 452 rep->r_prev = &nfsreqh; 453 splx(s); 454 455 /* 456 * Iff the NFSMCOPY above succeeded, send it off... 457 * otherwise the timer will retransmit later 458 */ 459 if (m != NULL) 460 error = nfs_udpsend(mntp->nm_so, (struct mbuf *)0, m, 0, len); 461 error = nfs_udpreply(mntp->nm_so, mntp, nfsreqh.r_next, rep); 462 463 s = splnet(); 464 rep->r_prev->r_next = rep->r_next; 465 if (rep->r_next != NULL) 466 rep->r_next->r_prev = rep->r_prev; 467 splx(s); 468 m_freem(rep->r_mreq); 469 mrep = md = rep->r_mrep; 470 FREE((caddr_t)rep, M_NFSREQ); 471 if (error) 472 return (error); 473 474 /* 475 * break down the rpc header and check if ok 476 */ 477 dpos = mtod(md, caddr_t); 478 nfsm_disect(p, u_long *, 5*NFSX_UNSIGNED); 479 p += 2; 480 if (*p++ == rpc_msgdenied) { 481 if (*p == rpc_mismatch) 482 error = EOPNOTSUPP; 483 else 484 error = EACCES; 485 m_freem(mrep); 486 return (error); 487 } 488 /* 489 * skip over the auth_verf, someday we may want to cache auth_short's 490 * for nfs_reqhead(), but for now just dump it 491 */ 492 if (*++p != 0) { 493 len = nfsm_rndup(fxdr_unsigned(long, *p)); 494 nfsm_adv(len); 495 } 496 nfsm_disect(p, u_long *, NFSX_UNSIGNED); 497 /* 0 == ok */ 498 if (*p == 0) { 499 nfsm_disect(p, u_long *, NFSX_UNSIGNED); 500 if (*p != 0) { 501 error = fxdr_unsigned(int, *p); 502 m_freem(mrep); 503 return (error); 504 } 505 *mrp = mrep; 506 *mdp = md; 507 *dposp = dpos; 508 return (0); 509 } 510 m_freem(mrep); 511 return (EPROTONOSUPPORT); 512 nfsmout: 513 return (error); 514 } 515 516 /* 517 * Get a request for the server main loop 518 * - receive a request via. nfs_soreceive() 519 * - verify it 520 * - fill in the cred struct. 521 */ 522 nfs_getreq(so, prog, vers, maxproc, nam, mrp, mdp, dposp, retxid, proc, cr) 523 struct socket *so; 524 u_long prog; 525 u_long vers; 526 int maxproc; 527 struct mbuf **nam; 528 struct mbuf **mrp; 529 struct mbuf **mdp; 530 caddr_t *dposp; 531 u_long *retxid; 532 u_long *proc; 533 register struct ucred *cr; 534 { 535 register int i; 536 register struct mbuf *m; 537 nfsm_vars; 538 int len, len2; 539 540 if (error = nfs_udpreceive(so, nam, &mrep)) 541 return (error); 542 md = mrep; 543 dpos = mtod(mrep, caddr_t); 544 nfsm_disect(p, u_long *, 10*NFSX_UNSIGNED); 545 *retxid = *p++; 546 if (*p++ != rpc_call) { 547 m_freem(mrep); 548 return (ERPCMISMATCH); 549 } 550 if (*p++ != rpc_vers) { 551 m_freem(mrep); 552 return (ERPCMISMATCH); 553 } 554 if (*p++ != prog) { 555 m_freem(mrep); 556 return (EPROGUNAVAIL); 557 } 558 if (*p++ != vers) { 559 m_freem(mrep); 560 return (EPROGMISMATCH); 561 } 562 *proc = fxdr_unsigned(u_long, *p++); 563 if (*proc == NFSPROC_NULL) { 564 *mrp = mrep; 565 return (0); 566 } 567 if (*proc > maxproc || *p++ != rpc_auth_unix) { 568 m_freem(mrep); 569 return (EPROCUNAVAIL); 570 } 571 len = fxdr_unsigned(int, *p++); 572 len2 = fxdr_unsigned(int, *++p); 573 nfsm_adv(nfsm_rndup(len2)); 574 nfsm_disect(p, u_long *, 3*NFSX_UNSIGNED); 575 cr->cr_uid = fxdr_unsigned(uid_t, *p++); 576 cr->cr_gid = fxdr_unsigned(gid_t, *p++); 577 len2 = fxdr_unsigned(int, *p); 578 if (len2 > 10) { 579 m_freem(mrep); 580 return (EBADRPC); 581 } 582 nfsm_disect(p, u_long *, (len2+2)*NFSX_UNSIGNED); 583 for (i = 1; i <= len2; i++) 584 cr->cr_groups[i] = fxdr_unsigned(gid_t, *p++); 585 cr->cr_ngroups = len2+1; 586 /* 587 * Do we have any use for the verifier. 588 * According to the "Remote Procedure Call Protocol Spec." it 589 * should be AUTH_NULL, but some clients make it AUTH_UNIX? 590 * For now, just skip over it 591 */ 592 len2 = fxdr_unsigned(int, *++p); 593 if (len2 > 0) 594 nfsm_adv(nfsm_rndup(len2)); 595 *mrp = mrep; 596 *mdp = md; 597 *dposp = dpos; 598 return (0); 599 nfsmout: 600 return (error); 601 } 602 603 /* 604 * Generate the rpc reply header 605 * siz arg. is used to decide if adding a cluster is worthwhile 606 */ 607 nfs_rephead(siz, retxid, err, mrq, mbp, bposp) 608 int siz; 609 u_long retxid; 610 int err; 611 struct mbuf **mrq; 612 struct mbuf **mbp; 613 caddr_t *bposp; 614 { 615 nfsm_vars; 616 617 NFSMGETHDR(mreq); 618 mb = mreq; 619 if ((siz+RPC_REPLYSIZ) > MHLEN) 620 NFSMCLGET(mreq, M_WAIT); 621 p = mtod(mreq, u_long *); 622 mreq->m_len = 6*NFSX_UNSIGNED; 623 bpos = ((caddr_t)p)+mreq->m_len; 624 *p++ = retxid; 625 *p++ = rpc_reply; 626 if (err == ERPCMISMATCH) { 627 *p++ = rpc_msgdenied; 628 *p++ = rpc_mismatch; 629 *p++ = txdr_unsigned(2); 630 *p = txdr_unsigned(2); 631 } else { 632 *p++ = rpc_msgaccepted; 633 *p++ = 0; 634 *p++ = 0; 635 switch (err) { 636 case EPROGUNAVAIL: 637 *p = txdr_unsigned(RPC_PROGUNAVAIL); 638 break; 639 case EPROGMISMATCH: 640 *p = txdr_unsigned(RPC_PROGMISMATCH); 641 nfsm_build(p, u_long *, 2*NFSX_UNSIGNED); 642 *p++ = txdr_unsigned(2); 643 *p = txdr_unsigned(2); /* someday 3 */ 644 break; 645 case EPROCUNAVAIL: 646 *p = txdr_unsigned(RPC_PROCUNAVAIL); 647 break; 648 default: 649 *p = 0; 650 if (err != VNOVAL) { 651 nfsm_build(p, u_long *, NFSX_UNSIGNED); 652 *p = txdr_unsigned(err); 653 } 654 break; 655 }; 656 } 657 *mrq = mreq; 658 *mbp = mb; 659 *bposp = bpos; 660 if (err != 0 && err != VNOVAL) 661 nfsstats.srvrpc_errs++; 662 return (0); 663 } 664 665 /* 666 * Nfs timer routine 667 * Scan the nfsreq list and retranmit any requests that have timed out 668 * To avoid retransmission attempts on STREAM sockets (in the future) make 669 * sure to set the r_retry field to 0. 670 */ 671 nfs_timer() 672 { 673 register struct nfsreq *rep; 674 register struct mbuf *m; 675 register struct socket *so; 676 int s, len; 677 678 s = splnet(); 679 rep = nfsreqh.r_next; 680 while (rep != NULL) { 681 if (rep->r_timer > 0) 682 rep->r_timer--; 683 else if (rep->r_mrep == NULL && rep->r_retry > 0) { 684 so = rep->r_mntp->nm_so; 685 if ((so->so_state & SS_CANTSENDMORE) == 0 && 686 !so->so_error && 687 sbspace(&so->so_snd) >= rep->r_msiz) { 688 m = NFSMCOPY(rep->r_mreq, 0, M_COPYALL, M_DONTWAIT); 689 if (m != NULL) { 690 nfsstats.rpcretries++; 691 rep->r_timeout <<= 2; /* x4 backoff */ 692 if (rep->r_timeout > NFS_MAXTIMEO) 693 rep->r_timeout = NFS_MAXTIMEO; 694 rep->r_timer = rep->r_timeout; 695 if (rep->r_retry != VNOVAL) 696 rep->r_retry--; 697 #ifdef MGETHDR 698 m->m_pkthdr.len = rep->r_msiz; 699 #endif 700 (*so->so_proto->pr_usrreq)(so, PRU_SEND, 701 m, (caddr_t)0, (struct mbuf *)0, 702 (struct mbuf *)0); 703 } 704 } 705 } 706 rep = rep->r_next; 707 } 708 splx(s); 709 timeout(nfs_timer, (caddr_t)0, hz/10); 710 } 711 712 /* 713 * nfs_sbwait() is simply sbwait() but at a negative priority so that it 714 * can not be interrupted by a signal. 715 */ 716 nfs_sbwait(sb) 717 struct sockbuf *sb; 718 { 719 sb->sb_flags |= SB_WAIT; 720 sleep((caddr_t)&sb->sb_cc, PZERO-1); 721 } 722