V8/usr/sys/chaosld/chaos_device.c
/*
* C H A O S _ D E V I C E
*
* Chaosnet pseudo-device. This is the multiplexer/demultiplexer
* used in conjunction with the Chaosnet line discipline.
*
*
* (c) Copyright 1985 Nirvonics, Inc.
*
* Written by Kurt Gollhardt
* Last update Thu Mar 14 16:03:34 1985
*
* This software is the property of Nirvonics, Inc.
* All rights reserved.
*
*/
#include "ch.h"
#if NCH > 0
#include "../h/param.h"
#include "../h/systm.h"
#include "../h/stream.h"
#include "../h/ioctl.h"
#include "../h/ttyld.h"
#include "../h/buf.h"
#include "../h/conf.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../chaosld/types.h"
#include "../chaosld/constants.h"
#include "../chaosld/globals.h"
#define CHIOPRIO (PZERO+1) /* Just interruptible */
int NCh = NCH; /* For peeking */
int NChopen;
int nodev(), chdopen(), chdclose(), chdput(), chdosrv();
static struct qinit chdrinit = { nodev, NULL, chdopen, chdclose, 0, 0 },
chdwinit = { chdput, chdosrv, chdopen, chdclose, 1024, 129 };
struct streamtab chdinfo = { &chdrinit, &chdwinit };
chdopen(q, dev)
register struct queue *q;
dev_t dev;
{
dev = minor(dev);
if (ChaosQ == (struct queue *)0) /* Make sure line-discipline is on */
return 0;
if (dev > NCH || Chconn[dev] != NOCONN)
return 0;
if ((Chconn[dev] = new_conn(dev)) == NOCONN)
return 0;
NChopen++;
Chconn[dev]->cn_rdq = q;
Chconn[dev]->cn_state |= CSSTARTING;
q->ptr = WR(q)->ptr = (caddr_t)Chconn[dev];
q->flag |= QDELIM;
WR(q)->flag |= QNOENB|QBIGB;
if (!Chtimer)
ch_timer();
return 1;
}
chdclose(q)
struct queue *q;
{
register struct connection *conn;
register struct packet *pkt;
int ps;
if ((conn = (struct connection *)q->ptr) == NOCONN)
return;
conn->cn_time = Chclock;
switch (conn->cn_mode) {
case CHSTREAM:
ps = spl6();
if (setjmp(u.u_qsav)) {
if ((pkt = new_packet()) != NOPKT) {
pkt->pk_op = CLSOP;
append_packet(pkt, "User interrupted", 16);
}
chld_close(conn, pkt);
goto shut;
}
if (conn->cn_flags & CHWRITER) {
/*
* We set this flag telling the interrupt time
* receiver to abort the connection if any new packets
* arrive.
*/
conn->cn_sflags |= CHCLOSING;
/*
* Closing a stream transmitter involves flushing
* the last packet, sending an EOF and waiting for
* it to be acknowledged. If the connection was
* bidirectional, the reader should have already
* read until EOF if everything is to be closed
* cleanly.
*/
if (conn->cn_state == CSOPEN ||
conn->cn_state == CSRFCRCVD) {
if (conn->cn_toutput) {
chld_write(conn, conn->cn_toutput);
conn->cn_toutput = NULL;
}
if (conn->cn_state == CSOPEN)
chld_eof(conn);
}
while (!chtempty(conn)) {
conn->cn_sflags |= CHOWAIT;
sleep((caddr_t)&conn->cn_thead, CHIOPRIO);
}
if (conn->cn_flags & CHREADER) {
if (conn->cn_flags & CHSERVER)
chld_eof(conn);
else
goto close_wait;
}
} else if (conn->cn_state == CSOPEN) {
/*
* If we are only reading then we should read the EOF
* before closing and wait for the other end to close.
*/
close_wait:
if (conn->cn_flags & CHEOFSEEN)
while (conn->cn_state == CSOPEN)
sleep((caddr_t)conn, CHIOPRIO);
}
splx(ps);
/* Fall into... */
case CHRECORD: /* Record oriented close is just sending a CLOSE */
if (conn->cn_state == CSOPEN) {
if ((pkt = new_packet()) != NOPKT)
pkt->pk_op = CLSOP;
chld_close(conn, pkt);
}
}
shut:
chld_close(conn, NOPKT);
free_conn(conn);
q->ptr = (caddr_t)0;
--NChopen;
}
chdstart(q, conn)
register struct queue *q;
register struct connection *conn;
{
register struct packet *pkt;
register struct block *bp;
register struct chopen *c;
int len, rwsize, err, ps;
err = 0;
conn->cn_sflags |= CHOPNWAIT;
if (block_pullup(q, sizeof(struct chopen)) == 0) {
err = EINVAL;
goto flush;
}
c = (struct chopen *)q->first->rptr;
len = c->co_clength + c->co_length + (c->co_length? 1 : 0);
if (len > CHMAXPKT || c->co_clength <= 0) {
err = E2BIG;
flush:
flush_packet(q);
goto out;
}
if (block_pullup(q, len + sizeof(struct chopen)) == 0) {
err = EINVAL;
goto flush;
}
bp = getq(q);
flush_packet(q);
c = (struct chopen *)bp->rptr;
if ((pkt = new_packet()) == NOPKT) {
err = ENOMEM;
goto out;
}
append_packet(pkt, bp->rptr += sizeof(struct chopen), c->co_clength);
if (c->co_length) {
append_packet(pkt, " ", 1);
append_packet(pkt, bp->rptr += c->co_clength, c->co_length);
}
conn->cn_mode = c->co_mode;
if (c->co_mode == CHSTREAM)
RD(q)->flag &= ~QDELIM;
rwsize = (c->co_rwsize ? c->co_rwsize : CHDRWSIZE);
if (c->co_host)
chld_open(conn, c->co_host, rwsize, pkt);
else
chld_listen(conn, rwsize, pkt);
out:
ps = spl6();
if (conn->cn_sflags & CHOPNWAIT) {
if (c->co_async || err)
chdoreply(conn, err, NOPKT, c->co_async);
}
splx(ps);
}
chdoreply(conn, err, pkt, async)
struct connection *conn;
struct packet *pkt;
{
struct choreply c;
struct queue *q;
conn->cn_sflags &= ~CHOPNWAIT;
q = conn->cn_rdq;
if (err == 0 && !async) {
if (conn->cn_flags & CHSERVER) {
if (conn->cn_state != CSRFCRCVD)
err = EIO;
} else {
if (conn->cn_state != CSOPEN &&
(conn->cn_state != CSCLOSED ||
pkt == NOPKT || pkt->pk_op != ANSOP))
err = EIO;
}
}
c.errno = err;
putcpy(q->next, &c, sizeof(c));
putctl(q->next, M_DELIM);
}
chdput(q, bp)
register struct queue *q;
register struct block *bp;
{
switch(bp->type){
case M_IOCTL:
chdioctl(q, bp);
break;
case M_DATA:
putq(q, bp);
break;
case M_DELIM:
putq(q, bp);
if (!(((struct connection *)q->ptr)->cn_sflags & CHOQWAIT))
qenable(q);
break;
default:
freeb(bp);
break;
}
}
chdioctl(q, bp)
register struct queue *q;
register struct block *bp;
{
register struct connection *conn;
register struct packet *pkt;
union stmsg *sp;
int ps, len;
sp = (union stmsg *)(bp->rptr);
if ((conn = (struct connection *)q->ptr) == NOCONN)
goto bad;
bp->type = M_IOCACK;
switch(sp->ioc0.com){
case CHIOCRPKT:
/* Read a packet that would normally be discarded.
* This is typically used to read the error message
* given with a CLS packet, or to look at the contact
* string for a listener.
* The ioctl returns 2 bytes for the opcode, 2 bytes for
* the (data) length, and the bytes of data.
*/
ps = spl6();
if ((pkt = conn->cn_expkt) == NOPKT || flatten(pkt))
break;
conn->cn_expkt = NULL;
splx(ps);
len = pkt->pk_len + 4 + sizeof(sp->ioc0.com);
if (bp->lim - bp->base < len) {
freeb(bp);
if ((bp = allocb(len)) == NOBLOCK
|| bp->lim - bp->base < len)
break;
bp->type = M_IOCACK;
}
sp = (union stmsg *)(bp->rptr = bp->base);
bp->wptr = bp->rptr + len;
sp->ioc0.com = CHIOCRPKT;
*(short *)(&sp->iocx.xxx[0]) = pkt->pk_op;
*(short *)(&sp->iocx.xxx[2]) = pkt->pk_len;
bcopy(pkt->data->rptr, &sp->iocx.xxx[4], pkt->pk_len);
free_packet(pkt);
goto reply;
case CHIOCFLUSH:
/* Flush the current output packet if there is one.
* Applicable in stream mode only.
*/
if (conn->cn_toutput != NOPKT) {
chld_write(conn, conn->cn_toutput);
conn->cn_toutput = NULL;
}
goto noreply;
case CHIOCOWAIT:
/* Wait for all output to be acknowledged.
* If in stream mode, any pending output is flushed first.
* If *addr is non-zero, an EOF is also sent before waiting.
*/
if (conn->cn_toutput != NOPKT) {
chld_write(conn, conn->cn_toutput);
conn->cn_toutput = NULL;
}
if (sp->iocx.xxx[0])
chld_eof(conn);
ps = spl6();
if (chtempty(conn)) {
/* already empty */
splx(ps);
goto noreply;
}
conn->cn_sflags |= CHEMPWAIT;
conn->cn_wait = bp;
splx(ps);
return;
case CHIOCGSTAT:
/* Return the status of the connection */
ps = spl6();
{
register struct chstatus *chst;
chst = (struct chstatus *)sp->iocx.xxx;
chst->st_fhost = conn->cn_faddr.ch_addr;
chst->st_cnum = conn->cn_lidx.ci_tidx;
chst->st_rwsize = conn->cn_rwsize;
chst->st_twsize = conn->cn_twsize;
chst->st_state = conn->cn_state;
chst->st_cmode = conn->cn_mode;
chst->st_oroom = conn->cn_twsize -
(conn->cn_tlast - conn->cn_tacked);
chst->st_ptype = chst->st_plength = 0;
bp->wptr = (u_char *)(chst + 1);
}
splx(ps);
goto reply;
case CHIOCSWAIT:
/* Wait for the state of the connection to be different
* from the given state.
*/
ps = spl6();
if (conn->cn_state != *(int *)sp->iocx.xxx) {
/* already out of the given state */
splx(ps);
goto noreply;
}
conn->cn_sflags |= CHSWAIT;
conn->cn_wait = bp;
splx(ps);
return;
case CHIOCANSWER:
/* Answer an RFC. This sets a bit saying the next write
* call should send an ANS packet and close the connection.
*/
if (conn->cn_state == CSRFCRCVD) {
conn->cn_flags |= CHANSWER;
goto noreply;
}
break;
case CHIOCREJECT:
/* Reject an RFC. This closes the connection with a CLS
* packet containing an error message string. The user
* passes in a structure containing a pointer to the error
* message (reason) and its length.
*/
{
register struct chreject *cr;
int flag, c;
cr = (struct chreject *)sp->iocx.xxx;
pkt = NOPKT;
flag = 0;
if (cr->cr_length > 0 && cr->cr_length <= CHMAXPKT) {
if ((pkt = new_packet()) == NOPKT)
++flag;
else {
pkt->pk_op = CLSOP;
while (cr->cr_length-- > 0) {
if ((c = fubyte(cr->cr_reason++)) < 0)
break;
append_packet(pkt, &c, 1);
}
}
}
chld_close(conn, pkt);
if (flag == 0)
goto noreply;
}
break;
case CHIOCACCEPT:
/* Accept an RFC causing the OPN packet to be sent */
ps = spl6();
if (conn->cn_state == CSRFCRCVD) {
chld_accept(conn);
splx(ps);
goto noreply;
}
splx(ps);
break;
}
bad:
bp->type = M_IOCNAK;
noreply:
bp->wptr = bp->rptr;
reply:
qreply(q, bp);
}
chdosrv(q)
struct queue *q;
{
register struct block *bp;
if (chdwrite(q))
return;
/* See if there's another packet on the queue */
for (bp = q->first; bp != NOBLOCK; bp = bp->next)
if (bp->type == M_DELIM) {
qenable(q); /* re-enable queue for the next packet */
break;
}
}
chdwrite(q)
register struct queue *q;
{
register struct packet *pkt;
register struct block *bp, *tail;
register struct connection *conn;
int n, ps;
if ((conn = (struct connection *)q->ptr) == NOCONN) {
flush_packet(q);
return 1;
}
if (conn->cn_state == CSSTARTING) {
chdstart(q, conn);
return 0;
}
conn->cn_flags |= CHWRITER;
ps = spl6();
if (chtfull(conn)) {
conn->cn_sflags |= CHOQWAIT;
splx(ps);
return 1;
}
splx(ps);
switch (conn->cn_mode) {
case CHSTREAM:
while ((bp = getq(q)) != NOBLOCK) {
if (bp->rptr >= bp->wptr) {
freeb(bp);
continue;
}
if (conn->cn_state != CSOPEN &&
(conn->cn_state != CSRFCRCVD ||
(conn->cn_flags & CHANSWER) == 0)) {
freeb(bp);
flush_packet(q);
return 1;
}
if ((pkt = conn->cn_toutput) == NOPKT) {
if ((pkt = new_packet()) == NOPKT) {
printf("chodsrv: can't allocate packet\n");
return 0;
}
setconn(conn, pkt);
pkt->pk_op = (conn->cn_flags & CHANSWER ?
ANSOP : DATOP);
conn->cn_toutput = pkt;
}
n = CHMAXDATA - pkt->pk_len;
if (bp->wptr - bp->rptr >= n) {
append_packet(pkt, bp->rptr, n);
bp->rptr += n;
conn->cn_toutput = NOPKT;
if (bp->rptr < bp->wptr)
putbq(q, bp);
chld_write(conn, pkt);
} else {
bp->next = NOBLOCK;
if (pkt->data == NOBLOCK)
pkt->data = bp;
else {
for (tail = pkt->data; tail->next != NOBLOCK;)
tail = tail->next;
tail->next = bp;
}
pkt->pk_lenword += bp->wptr - bp->rptr;
}
}
break;
case CHRECORD:
if ((pkt = get_packet(q, 1)) == NOPKT) {
printf("chdosrv: can't allocate packet\n");
return 0;
}
setconn(conn, pkt);
chld_write(conn, pkt);
break;
}
return 0;
}
chdrint(conn, pkt)
register struct connection *conn;
register struct packet *pkt;
{
register struct queue *q;
if (conn->cn_sflags & CHOPNWAIT)
chdoreply(conn, 0, pkt, 0);
if (ISDATA(pkt) || pkt->pk_op == EOFOP)
conn->cn_flags |= CHREADER;
q = conn->cn_rdq;
if (q) {
if (conn->cn_mode == CHSTREAM) {
if (pkt->pk_op == EOFOP)
conn->cn_flags |= CHEOFSEEN;
else
conn->cn_flags &= ~CHEOFSEEN;
if (!READABLE(pkt)) {
conn->cn_expkt = pkt;
return;
}
}
if (q->next->flag & QFULL) {
free_packet(pkt);
debug(DABNOR,printf("chdrint: QFULL\n"));
return;
}
ch_busy++;
if (conn->cn_mode == CHRECORD) /* Check for RECORD mode */
putcpy(q->next, &pkt->pk_op, 1);
put_pkdata(q, pkt);
--ch_busy;
} else
free_packet(pkt);
}
chd_newstate(conn)
register struct connection *conn;
{
register struct queue *qnext = conn->cn_rdq->next;
int ps = spl6();
if (conn->cn_sflags & CHOPNWAIT)
chdoreply(conn, 0, NOPKT);
if (conn->cn_sflags & CHSWAIT) {
register union stmsg *sp = (union stmsg *)conn->cn_wait->rptr;
if (conn->cn_state != *(int *)sp->iocx.xxx) {
conn->cn_wait->wptr = (u_char *)sp;
(*qnext->qinfo->putp) (qnext, conn->cn_wait);
conn->cn_sflags &= ~CHSWAIT;
conn->cn_wait = NOBLOCK;
}
}
if (chdead(conn)) { /* send an end-of-file */
putctl(qnext, M_DELIM);
putctl(qnext, M_DELIM);
}
splx(ps);
}
chd_newout(conn)
register struct connection *conn;
{
int ps = spl6();
if (conn->cn_sflags & CHOWAIT)
wakeup((caddr_t)&conn->cn_thead);
if (conn->cn_sflags & CHEMPWAIT) {
register union stmsg *sp = (union stmsg *)conn->cn_wait->rptr;
if (chtempty(conn)) {
conn->cn_wait->wptr = (u_char *)sp;
qreply(conn->cn_rdq, conn->cn_wait);
conn->cn_sflags &= ~CHEMPWAIT;
conn->cn_wait = NOBLOCK;
}
}
if (conn->cn_sflags & CHOQWAIT) {
conn->cn_sflags &= ~CHOQWAIT;
qenable(WR(conn->cn_rdq));
}
splx(ps);
}
#endif