|
|
1.1 ! root 1: /* ! 2: * tcp_device.c ! 3: */ ! 4: ! 5: #include "sys/param.h" ! 6: #include "sys/stream.h" ! 7: #include "sys/inio.h" ! 8: #include "sys/ttyio.h" ! 9: #include "sys/conf.h" ! 10: #include "sys/user.h" ! 11: #include "sys/inet/in.h" ! 12: #include "sys/inet/ip_var.h" ! 13: #include "sys/inet/tcp.h" ! 14: #include "sys/inet/tcp_timer.h" ! 15: #include "sys/inet/tcp_seq.h" ! 16: #include "sys/inet/tcp_var.h" ! 17: #include "sys/inet/tcpip.h" ! 18: #include "sys/inet/tcp_user.h" ! 19: #include "sys/inet/tcp_fsm.h" ! 20: ! 21: extern int tcp_busy; /* set to discourage timers & ensuing panics */ ! 22: extern int tcp_maxseg; ! 23: ! 24: long tcpdopen(); ! 25: int tcpdclose(), tcpdput(); ! 26: int tcpdosrv(), tcpdisrv(); ! 27: static struct qinit tcpdrinit = { noput, tcpdisrv, tcpdopen, tcpdclose, 2048, 64 }; ! 28: struct qinit tcpdwinit = { tcpdput, tcpdosrv, tcpdopen, tcpdclose, 2048, 64}; ! 29: struct streamtab tcpdinfo = { &tcpdrinit, &tcpdwinit }; ! 30: ! 31: struct cdevsw tcpcdev = cstrinit(&tcpdinfo); ! 32: ! 33: extern int tcpcnt; ! 34: extern struct tcpcb tcpcb[]; ! 35: ! 36: long ! 37: tcpdopen(q, dev) ! 38: register struct queue *q; ! 39: dev_t dev; ! 40: { ! 41: struct tcpcb *tp; ! 42: ! 43: dev = minor(dev); ! 44: if(dev >= tcpcnt) ! 45: return(0); ! 46: tp = &tcpcb[dev]; ! 47: if(dev&01){ ! 48: /* outgoing channel */ ! 49: /* ! 50: * disallow if active ! 51: */ ! 52: if(tp->t_state != TCPS_CLOSED) ! 53: return(0); ! 54: /* ! 55: * only one opener at a time for outgoing channels ! 56: */ ! 57: if(q->ptr) ! 58: return(0); ! 59: } else { ! 60: /* incoming channel */ ! 61: /* ! 62: * disallow if inactive ! 63: */ ! 64: if(tp->t_state == TCPS_CLOSED) { ! 65: printf("tcp%d closed\n", dev); ! 66: return(0); ! 67: } ! 68: /* ! 69: * disallow if it's not open or waiting to be opened ! 70: */ ! 71: if((tp->so_state&(SS_OPEN|SS_PLEASEOPEN))==0) { ! 72: printf("tcp%d state %x\n", dev, tp->so_state); ! 73: return(0); ! 74: } ! 75: /* ! 76: * if already open, just accept ! 77: */ ! 78: if(q->ptr) ! 79: return(1); ! 80: } ! 81: tcp_busy++; ! 82: if((tp->so_state & SS_PLEASEOPEN) == 0){ ! 83: tp->so_rcount = tp->so_wcount = tp->so_options = 0; ! 84: tp->so_laddr = tp->so_faddr = (in_addr)0; ! 85: tp->so_lport = tp->so_fport = (tcp_port)0; ! 86: tp->so_oobmark = tp->so_delimcnt = 0; ! 87: tp->so_head = (struct tcpcb *)0; ! 88: tp->so_state = SS_WAITING; ! 89: tp->t_maxseg = TCP_DEFMAXSEG; /* until changed by other end */ ! 90: } ! 91: tp->so_state |= SS_OPEN; ! 92: tp->so_dev = dev; ! 93: tp->so_rq = q; ! 94: tp->so_wq = WR(q); ! 95: q->ptr = (caddr_t)tp; ! 96: WR(q)->flag |= QBIGB; ! 97: WR(q)->ptr = (caddr_t)tp; ! 98: --tcp_busy; ! 99: if(tp->so_state & SS_PLEASEOPEN){ ! 100: tp->so_state &= ~SS_PLEASEOPEN; ! 101: qenable(WR(q)); /* to force out rcv wnd update */ ! 102: } ! 103: return(1); ! 104: } ! 105: ! 106: tcpdclose(q) ! 107: register struct queue *q; ! 108: { ! 109: struct tcpcb *tp; ! 110: ! 111: tp = (struct tcpcb *)q->ptr; ! 112: tcp_busy++; ! 113: if(tp == 0){ ! 114: --tcp_busy; ! 115: return; ! 116: } ! 117: tp->so_state &= ~(SS_OPEN|SS_WAITING); ! 118: tp->so_rq = tp->so_wq = (struct queue *) 0; ! 119: tp->so_wcount = tp->so_rcount = 0; ! 120: if(tp->t_state > TCPS_LISTEN) ! 121: tcp_disconnect(tp); ! 122: else ! 123: tcp_close(tp); ! 124: --tcp_busy; ! 125: } ! 126: ! 127: tcpdput(q, bp) ! 128: register struct queue *q; ! 129: register struct block *bp; ! 130: { ! 131: int s, x; ! 132: struct tcpcb *tp; ! 133: struct block *nbp; ! 134: struct foo { ! 135: int com; ! 136: struct tcpuser rep; ! 137: }; ! 138: struct foo *fp; ! 139: ! 140: tp = (struct tcpcb *)q->ptr; ! 141: switch(bp->type){ ! 142: case M_IOCTL: ! 143: bp->type = M_IOCACK; ! 144: switch(stiocom(bp)){ ! 145: case TIOCSETP: ! 146: case TIOCSETN: ! 147: x = ((struct ttydevb *)stiodata(bp))->ispeed; ! 148: bp->wptr = bp->rptr; ! 149: bp->type = M_IOCACK; ! 150: qreply(q, bp); ! 151: if(x == 0) ! 152: qpctl(OTHERQ(q), M_HANGUP); ! 153: return; ! 154: case TIOCGETP: ! 155: ((struct ttydevb *)stiodata(bp))->ispeed = ! 156: ((struct ttydevb *)stiodata(bp))->ospeed = B9600; ! 157: break; ! 158: case TCPGETADDR: ! 159: nbp = allocb(sizeof(struct foo)); ! 160: if (nbp == NULL) { ! 161: bp->type = M_IOCNAK; ! 162: break; ! 163: } ! 164: nbp->type = M_IOCACK; ! 165: fp = (struct foo *)nbp->rptr; ! 166: fp->com = stiocom(bp); ! 167: fp->rep.lport = tp->so_lport; ! 168: fp->rep.fport = tp->so_fport; ! 169: fp->rep.laddr = tp->so_laddr; ! 170: fp->rep.faddr = tp->so_faddr; ! 171: nbp->wptr = nbp->rptr + sizeof(struct foo); ! 172: freeb(bp); ! 173: bp = nbp; ! 174: break; ! 175: case TCPIOHUP: ! 176: tp->so_state |= SS_HANGUP; ! 177: bp->wptr = bp->rptr; ! 178: bp->type = M_IOCACK; ! 179: qreply(q, bp); ! 180: return; ! 181: default: ! 182: bp->type = M_IOCNAK; ! 183: } ! 184: qreply(q, bp); ! 185: return; ! 186: case M_DATA: ! 187: if(socantsendmore(tp)){ ! 188: freeb(bp); ! 189: return; ! 190: } ! 191: s = spl6(); ! 192: if (bp->wptr!=bp->rptr) ! 193: tp->so_delimcnt = 0; ! 194: if (bp->class&S_DELIM) ! 195: tp->so_delimcnt++; /* 2 delims are logical EOF */ ! 196: if (bp->wptr==bp->rptr){ ! 197: freeb(bp); ! 198: } else { ! 199: tp->so_wcount += BLEN(bp); /* BEFORE the putq */ ! 200: putq(q, bp); ! 201: } ! 202: splx(s); ! 203: if(tp->so_delimcnt ! 204: || (tp->so_options&(SO_ACCEPTCONN|SS_WAITING)) == 0) ! 205: qenable(q); ! 206: break; ! 207: default: ! 208: freeb(bp); ! 209: break; ! 210: } ! 211: } ! 212: ! 213: tcpdosrv(q) ! 214: struct queue *q; ! 215: { ! 216: register struct tcpcb *tp; ! 217: ! 218: if((tp = (struct tcpcb *)q->ptr) == 0) ! 219: return; ! 220: if (tp->so_state&SS_WCLOSED) ! 221: return; ! 222: tcp_busy++; ! 223: if(tp->so_delimcnt > 1){ ! 224: tp->so_state |= SS_WCLOSED; ! 225: tp = tcp_usrclosed(tp); ! 226: if(tp) ! 227: tcp_output(tp); ! 228: } else if ((tp->so_options & SO_ACCEPTCONN) == 0 ! 229: && (tp->so_state & SS_WAITING) == 0) ! 230: tcp_output(tp); ! 231: else ! 232: tcpduser(tp); ! 233: --tcp_busy; ! 234: } ! 235: ! 236: tcpdrint(bp, tp) ! 237: register struct block *bp; ! 238: register struct tcpcb *tp; ! 239: { ! 240: register struct block *bp1; ! 241: ! 242: if (tp->so_rq == NULL) { ! 243: printf("tcpdrint but no tp->so_rq\n"); ! 244: bp_free(bp); ! 245: return -1; ! 246: } ! 247: while(bp){ ! 248: bp1 = bp->next; ! 249: tp->so_rcount += bp->wptr - bp->rptr; ! 250: if(bp->wptr <= bp->rptr) ! 251: freeb(bp); ! 252: else ! 253: putq(tp->so_rq, bp); ! 254: bp = bp1; ! 255: } ! 256: return 0; ! 257: } ! 258: ! 259: tcpdisrv(q) ! 260: register struct queue *q; ! 261: { ! 262: register struct tcpcb *tp = (struct tcpcb *)(q->ptr); ! 263: register struct block *bp; ! 264: ! 265: while ((q->next->flag&QFULL) == 0 && (bp = getq(q)) != NULL) { ! 266: if(bp->type == M_DATA) ! 267: tp->so_rcount -= bp->wptr - bp->rptr; ! 268: if(tp->so_rcount < 0){ ! 269: printf("so_rcount %d\n", tp->so_rcount); ! 270: if(tp->so_state & SS_OPEN) ! 271: panic("so_rcount"); ! 272: } ! 273: (*q->next->qinfo->putp)(q->next, bp); ! 274: } ! 275: if(q->count <= q->qinfo->lolimit) ! 276: qenable(OTHERQ(q)); /* update remote send window */ ! 277: } ! 278: ! 279: /* ! 280: * here if channel isn't connected yet: ! 281: * output `data' is a user command ! 282: * grab all the data written so far, ! 283: * even though we'll discard the part we don't use ! 284: */ ! 285: tcpduser(tp) ! 286: register struct tcpcb *tp; ! 287: { ! 288: extern struct ipif *ip_ifwithaddr(); ! 289: struct tcpuser *tu; ! 290: struct block *bp, *tail, *head; ! 291: ! 292: tail = head = NULL; ! 293: while(bp = getq(tp->so_wq)){ ! 294: if(bp->type != M_DATA){ ! 295: freeb(bp); ! 296: continue; ! 297: } ! 298: bp->next = NULL; ! 299: if (head == NULL) ! 300: head = bp; ! 301: else ! 302: tail->next = bp; ! 303: tail = bp; ! 304: } ! 305: if(head == NULL) ! 306: return; ! 307: tp->so_wcount = 0; ! 308: bp = head; ! 309: if(BLEN(bp) < sizeof(struct tcpuser)){ ! 310: bp_free(bp); ! 311: return; ! 312: } ! 313: ! 314: /* ! 315: * tu->code is a request to bind this channel ! 316: */ ! 317: tu = (struct tcpuser *)bp->rptr; ! 318: switch(tu->code){ ! 319: case TCPC_CONNECT: ! 320: if(tp->t_state != TCPS_CLOSED){ ! 321: tu->code = TCPC_BADDEV; ! 322: goto bad; ! 323: } ! 324: if (tu->laddr != INADDR_ANY) { ! 325: /* has the user specified a legal local address? */ ! 326: if (ip_ifwithaddr(tu->laddr) == 0) { ! 327: tu->code = TCPC_BADLOCAL; ! 328: goto bad; ! 329: } ! 330: } else { ! 331: /* pick a local address related to the destination */ ! 332: tu->laddr = ip_hoston(tu->faddr); ! 333: if(tu->laddr == INADDR_ANY) { ! 334: tu->code = TCPC_NOROUTE; ! 335: goto bad; ! 336: } ! 337: } ! 338: tp->so_options = tu->param & ~SO_ACCEPTCONN; ! 339: if(tcpcb_bind(tp, tu->laddr, tu->lport)) { ! 340: tu->code = TCPC_BOUND; ! 341: goto bad; ! 342: } ! 343: tp->so_fport = tu->fport; ! 344: tp->so_faddr = tu->faddr; ! 345: tcp_template(tp); ! 346: if(tp->t_template == (struct block *)0) { ! 347: printf("no template - "); ! 348: tu->code = TCPC_BADDEV; ! 349: goto bad; ! 350: } ! 351: if (tp->so_options & SO_KEEPALIVE) ! 352: tcp_timers(tp, TCPT_KEEP); ! 353: tp->t_state = TCPS_SYN_SENT; ! 354: tp->iss = tcp_iss; tcp_iss += TCP_ISSINCR/2; ! 355: tcp_sendseqinit(tp); ! 356: tp->so_state &= ~SS_WAITING; ! 357: tcp_output(tp); ! 358: break; ! 359: case TCPC_LISTEN: ! 360: if(tp->t_state != TCPS_CLOSED){ ! 361: tu->code = TCPC_BADDEV; ! 362: goto bad; ! 363: } ! 364: if (tu->laddr != INADDR_ANY) { ! 365: /* has the user specified a legal local address? */ ! 366: if (ip_ifwithaddr(tu->laddr) == 0) { ! 367: tu->code = TCPC_BADLOCAL; ! 368: goto bad; ! 369: } ! 370: } ! 371: tp->so_options = tu->param & ~SO_ACCEPTCONN; ! 372: if(tcpcb_bind(tp, tu->laddr, tu->lport)) { ! 373: tu->code = TCPC_BOUND; ! 374: goto bad; ! 375: } ! 376: tp->t_state = TCPS_LISTEN; ! 377: tp->so_options |= SO_ACCEPTCONN; ! 378: tp->so_fport = tu->fport==0 ? TCPPORT_ANY : tu->fport; ! 379: tp->so_faddr = tu->faddr; ! 380: tcp_template(tp); ! 381: if(tp->t_template == 0) { ! 382: tu->code = TCPC_BADDEV; ! 383: printf("no template - "); ! 384: goto bad; ! 385: } ! 386: tp->so_state &= ~SS_WAITING; ! 387: if (tcp_isconnected(tp)<0) { ! 388: tu->code = TCPC_BADDEV; ! 389: goto bad; ! 390: } ! 391: break; ! 392: default: ! 393: tu->code = TCPC_SORRY; ! 394: goto bad; ! 395: } ! 396: bp_free(bp); ! 397: return; ! 398: bad: ! 399: /* ! 400: * send reason for failure back to user. ! 401: */ ! 402: if(bp->next) { ! 403: bp_free(bp->next); ! 404: bp->next = 0; ! 405: } ! 406: (*tp->so_rq->next->qinfo->putp)(tp->so_rq->next, bp); ! 407: tcp_hungup(tp); ! 408: } ! 409: ! 410: struct tcpcb * ! 411: tcp_disconnect(tp) ! 412: register struct tcpcb *tp; ! 413: { ! 414: ! 415: if(tp->t_state < TCPS_ESTABLISHED) ! 416: tp = tcp_close(tp); ! 417: else { ! 418: tp->so_state &= ~SS_PLEASEOPEN; ! 419: tcp_hungup(tp); /* sends M_HANGUP */ ! 420: tp = tcp_usrclosed(tp); ! 421: if(tp) ! 422: tcp_output(tp); ! 423: } ! 424: return(tp); ! 425: } ! 426: ! 427: struct tcpcb * ! 428: tcp_usrclosed(tp) ! 429: register struct tcpcb *tp; ! 430: { ! 431: ! 432: switch(tp->t_state){ ! 433: ! 434: case TCPS_CLOSED: ! 435: case TCPS_LISTEN: ! 436: case TCPS_SYN_SENT: ! 437: tp->t_state = TCPS_CLOSED; ! 438: tp = tcp_close(tp); ! 439: break; ! 440: ! 441: case TCPS_SYN_RECEIVED: ! 442: case TCPS_ESTABLISHED: ! 443: tp->t_state = TCPS_FIN_WAIT_1; ! 444: tp->so_options |= SO_KEEPALIVE; ! 445: tcp_timers(tp, TCPT_KEEP); ! 446: break; ! 447: ! 448: case TCPS_CLOSE_WAIT: ! 449: tp->t_state = TCPS_LAST_ACK; ! 450: tp->so_options |= SO_KEEPALIVE; ! 451: tcp_timers(tp, TCPT_KEEP); ! 452: break; ! 453: } ! 454: if(tp && tp->t_state >= TCPS_FIN_WAIT_2) { ! 455: tp->so_state &= ~(SS_PLEASEOPEN|SS_RCVATMARK); ! 456: if(tp->so_state&SS_OPEN && !(tp->so_state&SS_HUNGUP)) { ! 457: tp->so_state |= SS_HUNGUP; ! 458: tcp_hungup(tp); ! 459: } ! 460: } ! 461: return(tp); ! 462: } ! 463: ! 464: tcp_isconnected(tp) ! 465: struct tcpcb *tp; ! 466: { ! 467: struct block *bp; ! 468: struct tcpuser *tu; ! 469: struct tcpcb *rtp; ! 470: ! 471: if(tp->so_head) ! 472: rtp = tp->so_head; ! 473: else ! 474: rtp = tp; ! 475: if((rtp->so_state & SS_OPEN) == 0){ ! 476: printf("isconnected, no fd ref\n"); ! 477: return -1; ! 478: } ! 479: bp = allocb(sizeof(struct tcpuser)); ! 480: if(bp == 0) ! 481: return -1; ! 482: bp->wptr += sizeof(struct tcpuser); ! 483: tu = (struct tcpuser *)bp->rptr; ! 484: tu->code = TCPC_OK; ! 485: tu->fport = tp->so_fport; ! 486: tu->faddr = tp->so_faddr; ! 487: tu->lport = tp->so_lport; ! 488: tu->laddr = tp->so_laddr; ! 489: tu->param = tp->so_dev; ! 490: bp->class |= S_DELIM; ! 491: return tcpdrint(bp, rtp); ! 492: } ! 493: ! 494: tcp_hungup(tp) ! 495: register struct tcpcb *tp; ! 496: { ! 497: register struct queue *q; ! 498: ! 499: q = tp->so_rq; ! 500: if(q == 0) ! 501: return; ! 502: qpctl(q, M_HANGUP); ! 503: } ! 504: ! 505: /* ! 506: * find a spare even-numbered tcp device for a new passive-end ! 507: * connection. ! 508: */ ! 509: struct tcpcb * ! 510: tcp_newconn(tp) ! 511: register struct tcpcb *tp; ! 512: { ! 513: register struct tcpcb *ntp; ! 514: register struct tcpcb *tend; ! 515: ! 516: if(tp->so_rq && (tp->so_rq->flag&QFULL)){ ! 517: printf("listen %d q full\n", tp->so_lport); ! 518: return(0); ! 519: } ! 520: tend = &tcpcb[tcpcnt]; ! 521: for(ntp = &tcpcb[0]; ntp < tend; ntp += 2){ ! 522: if(ntp->t_state == TCPS_CLOSED){ ! 523: bzero(ntp, sizeof(struct tcpcb)); ! 524: ntp->t_maxseg = TCP_DEFMAXSEG; ! 525: ntp->so_options = tp->so_options & SO_KEEPALIVE; ! 526: ntp->so_head = tp; ! 527: ntp->so_dev = ntp - tcpcb; ! 528: return(ntp); ! 529: } ! 530: } ! 531: return(0); ! 532: } ! 533: ! 534: struct tcpcb * ! 535: tcpcb_lookup(faddr, fport, laddr, lport) ! 536: register in_addr faddr, laddr; ! 537: register tcp_port fport, lport; ! 538: { ! 539: register struct tcpcb *tp; ! 540: int highscore = 0, score; ! 541: struct tcpcb *match = 0; ! 542: struct tcpcb *tend; ! 543: ! 544: tend = &tcpcb[tcpcnt]; ! 545: for(tp = &tcpcb[0]; tp < tend; tp++){ ! 546: if(tp->t_state == TCPS_CLOSED) ! 547: continue; ! 548: score = 22; ! 549: if(tp->so_faddr != faddr) { ! 550: if(tp->so_faddr != INADDR_ANY) ! 551: continue; ! 552: else ! 553: score -=8; ! 554: } ! 555: if(tp->so_fport != fport) { ! 556: if(tp->so_fport != TCPPORT_ANY) ! 557: continue; ! 558: else ! 559: score -=8; ! 560: } ! 561: if(tp->so_laddr != laddr) { ! 562: if(tp->so_laddr == in_netof(laddr)) ! 563: score -=1; ! 564: else if(tp->so_laddr != INADDR_ANY) ! 565: continue; ! 566: else ! 567: score -=2; ! 568: } ! 569: if(tp->so_lport != lport) { ! 570: if(tp->so_lport != TCPPORT_ANY) ! 571: continue; ! 572: else ! 573: score -=4; ! 574: } ! 575: if (score==22) ! 576: return tp; ! 577: if (score<highscore) ! 578: continue; ! 579: match = tp; ! 580: highscore = score; ! 581: } ! 582: return(match); ! 583: } ! 584: ! 585: /* n chars were acked; drop them now */ ! 586: sbsnddrop(tp, n) ! 587: register struct tcpcb *tp; ! 588: register int n; ! 589: { ! 590: register struct queue *q; ! 591: register int i; ! 592: register struct block *bp; ! 593: ! 594: q = tp->so_wq; ! 595: if(q == 0) ! 596: return; ! 597: bp = 0; ! 598: while(n > 0 && (bp = getq(q))){ ! 599: i = MIN(BLEN(bp), n); ! 600: bp->rptr += i; ! 601: n -= i; ! 602: tp->so_wcount -= i; ! 603: if(bp->rptr >= bp->wptr){ ! 604: freeb(bp); ! 605: bp = 0; ! 606: } else if(n > 0){ ! 607: panic("sbsnddrop"); ! 608: } ! 609: } ! 610: if(bp) ! 611: putbq(q, bp); ! 612: } ! 613: ! 614: static tcp_port portnext[] = { 600, 1024 }; ! 615: static tcp_port portlow[] = { 600, 1024 }; ! 616: static tcp_port porthigh[] = { 1024, 2048 }; ! 617: ! 618: tcpcb_bind(tp, addr, port) ! 619: register struct tcpcb *tp; ! 620: register in_addr addr; ! 621: register tcp_port port; ! 622: { ! 623: register int i; ! 624: ! 625: tp->so_lport = 0; ! 626: if(port){ ! 627: /* ! 628: * Don't let just anyone get a trusted port. ! 629: */ ! 630: if(port<porthigh[0] && u.u_uid!=0) ! 631: return(1); ! 632: tp->so_lport = port; ! 633: tp->so_laddr = addr; ! 634: return(0); ! 635: } ! 636: /* ! 637: * No specific port, pick one. Root only gets a trusted ! 638: * port if explicitly requested, so privileged programs ! 639: * can make non-privileged calls. ! 640: */ ! 641: i = (u.u_uid==0 && (tp->so_options&SO_TRUSTED)) ? 0 : 1; ! 642: if(portnext[i] >= porthigh[i]) ! 643: portnext[i] = portlow[i]; ! 644: port = portnext[i]; ! 645: while(1){ ! 646: if(tcpcb_bind(tp, addr, portnext[i]) == 0){ ! 647: portnext[i]++; ! 648: return(0); ! 649: } ! 650: portnext[i]++; ! 651: if(portnext[i] >= porthigh[i]) ! 652: portnext[i] = portlow[i]; ! 653: if(portnext[i] == port) /* tried them all */ ! 654: break; ! 655: } ! 656: return(1); ! 657: } ! 658: ! 659: tcp_cantrcvmore(tp) ! 660: register struct tcpcb *tp; ! 661: { ! 662: register struct queue *q; ! 663: ! 664: q = tp->so_rq; ! 665: if(q == NULL) ! 666: return; ! 667: if(tp->so_state & SS_HANGUP) ! 668: qpctl(q, M_HANGUP); ! 669: else { ! 670: /* two delims ensure a zero length read at the process */ ! 671: qpctld(q, M_DATA); ! 672: qpctld(q, M_DATA); ! 673: } ! 674: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.