4662 lines
105 KiB
C
4662 lines
105 KiB
C
/* @(#)str_io.c 1.1 92/07/30 SMI; from S5R3.1 os/streamio.c 10.17.3.4 */
|
|
|
|
/* Copyright (c) 1984 AT&T */
|
|
/* All Rights Reserved */
|
|
|
|
/* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T */
|
|
/* The copyright notice above does not evidence any */
|
|
/* actual or intended publication of such source code. */
|
|
|
|
/*
|
|
* Copyright (c) 1989, 1990 by Sun Microsystems, Inc.
|
|
*/
|
|
|
|
/*
|
|
* Streams I/O interface.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/user.h>
|
|
#include <sys/termios.h>
|
|
#include <sys/termio.h>
|
|
#include <sys/ttold.h>
|
|
#include <sys/filio.h>
|
|
#include <sys/signal.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/vfs.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/file.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/buf.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/stream.h>
|
|
#include <sys/stropts.h>
|
|
#include <sys/strstat.h>
|
|
#include <sys/poll.h>
|
|
#include <sys/debug.h>
|
|
#include <sys/syslog.h>
|
|
#include <sys/session.h>
|
|
#include <specfs/snode.h>
|
|
|
|
#define HZ hz
|
|
|
|
/*
|
|
* id value used to distinguish between different ioctl messages
|
|
*/
|
|
static long ioc_id;
|
|
|
|
|
|
/*
|
|
* Qinit structure and Module_info structures
|
|
* for stream head read and write queues
|
|
*/
|
|
static int strrput(/*queue_t *q, mblk_t *bp*/);
|
|
static int strwsrv(/*queue_t *q*/);
|
|
|
|
static struct module_info strm_info = {
|
|
0,
|
|
"strrhead",
|
|
0,
|
|
INFPSZ,
|
|
STRHIGH,
|
|
STRLOW
|
|
};
|
|
|
|
static struct module_info stwm_info = {
|
|
0,
|
|
"strwhead",
|
|
0,
|
|
0,
|
|
0,
|
|
0
|
|
};
|
|
|
|
struct qinit strdata = {
|
|
strrput,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&strm_info,
|
|
NULL
|
|
};
|
|
|
|
struct qinit stwdata = {
|
|
NULL,
|
|
strwsrv,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&stwm_info,
|
|
NULL
|
|
};
|
|
|
|
static void assign_ctty(/*struct vnode *vp,
|
|
struct stdata *stp, int force*/);
|
|
static void strdismantle(/*struct stdata *stp, int flag*/);
|
|
void strclean(/*struct vnode *vp*/);
|
|
static void strsendsig(/*struct strevent *siglist, int event*/);
|
|
static void strwakepoll(/*struct stdata *stp, int events*/);
|
|
static void dopush(/*struct stdata *stp, char *mname, struct vnode *vp,
|
|
int waitflag, int oflag*/);
|
|
static void initialize_ldtab(/*void*/);
|
|
static queue_t *getcompatqueue(/*struct stdata *stp*/);
|
|
static char *getmname(/*queue_t *q*/);
|
|
static void strdoioctl(/*struct stdata *stp, struct strioctl *strioc,
|
|
int copyflg, int flag*/);
|
|
static int qattach(/*struct streamtab *qinfo, queue_t *qp, dev_t dev,
|
|
int oflag, int sflag*/);
|
|
static int qdetach(/*queue_t *qp, int clmode, int flag*/);
|
|
static int strtime(/*struct stdata *stp*/);
|
|
static int str2time(/*struct stdata *stp*/);
|
|
static int str3time(/*struct stdata *stp*/);
|
|
static int putiocd(/*mblk_t *bp, caddr_t arg, int copymode*/);
|
|
static int getiocd(/*mblk_t *bp, caddr_t arg, int copymode*/);
|
|
static struct linkblk *alloclink(/*queue_t *qup, queue_t *qdown, int index*/);
|
|
static int linkcycle(/*queue_t *qup, queue_t *qdown*/);
|
|
static int munlinkone(/*struct stdata *stp, struct file *fpdown,
|
|
struct linkblk *linkblkp, int cflag*/);
|
|
static void munlinkall(/*struct stdata *stp, int cflag*/);
|
|
void setq(/*queue_t *rq, struct qinit *rinit,
|
|
struct qinit *winit*/);
|
|
mblk_t *strmakemsg(/*struct strbuf *mctl, int count,
|
|
struct uio *uio, int wroff, long flag, int nowait*/);
|
|
int strwaitbuf(/*int size, int pri, int ncalls*/);
|
|
void strunbcall(/*int size, int pri, struct proc *p*/);
|
|
static int strwaitq(/*struct stdata *stp, int flag, int fmode*/);
|
|
static int strvtime(/*struct stdata *stp*/);
|
|
|
|
/*
|
|
* Open a stream device. Sflag may be 0 or CLONEOPEN.
|
|
* Return zero on success, or errno value on failure.
|
|
* The first argument is a pointer to a vnode pointer
|
|
* because clone opens require a new vnode to replace
|
|
* the original one.
|
|
*/
|
|
int
|
|
stropen(vpp, oflag, sflag)
|
|
struct vnode **vpp;
|
|
int oflag, sflag;
|
|
{
|
|
register struct vnode *vp = *vpp;
|
|
register struct stdata *stp;
|
|
register queue_t *qp;
|
|
register int unit;
|
|
int newctty;
|
|
char **modlp;
|
|
char *name;
|
|
int sverrno;
|
|
register int s;
|
|
|
|
ASSERT(sflag == 0 || sflag == CLONEOPEN);
|
|
|
|
/*
|
|
* Clone opens will result either in failure or in a fresh
|
|
* device instance with no superstructure of pushed modules.
|
|
* Since the vnode we have in hand doesn't match the one that
|
|
* will result from cloning, it's inappropriate to go through
|
|
* the code below, which assumes that the original vnode will
|
|
* remain valid. The primary consequence is that this code
|
|
* can't handle an extension to stream semantics that allows
|
|
* modules to remain pushed after the last close.
|
|
*/
|
|
if (sflag == CLONEOPEN)
|
|
goto forcedclone;
|
|
|
|
if (!(vp->v_stream)) {
|
|
/*
|
|
* This vnode isn't streaming, but another vnode
|
|
* may refer to same device, so look for it in snode
|
|
* table to avoid building 2 streams to 1 device.
|
|
*/
|
|
register struct vnode *othervp;
|
|
|
|
if ((othervp = other_specvp(vp)) != NULL)
|
|
vp->v_stream = othervp->v_stream;
|
|
}
|
|
|
|
/*
|
|
* If the stream already exists, wait for any open in progress
|
|
* to complete then call the open function of each module and
|
|
* driver in the stream. Otherwise create the stream.
|
|
*/
|
|
retry:
|
|
if (stp = vp->v_stream) {
|
|
int error;
|
|
int strhup_set = 0;
|
|
|
|
/*
|
|
* Waiting for stream to be created to device
|
|
* due to another open, or for it to be dismantled
|
|
* due to a close in progress.
|
|
*/
|
|
if (stp->sd_flag & (STWOPEN|STRCLOSE)) {
|
|
if (sleep((caddr_t)stp, STOPRI|PCATCH))
|
|
return (EINTR);
|
|
/*
|
|
* We can't assume anything about the stream;
|
|
* the open could have failed or cloned to a
|
|
* new instance.
|
|
*/
|
|
goto retry;
|
|
}
|
|
if (stp->sd_flag & STRERR)
|
|
return (EIO);
|
|
|
|
/*
|
|
* XXX - Allow opens of a stream marked as STRHUP
|
|
* to go through if and only if it is no longer a
|
|
* controlling TTY for some process. (e.g., this
|
|
* will occur when a user spawns off background
|
|
* processes and hangs up a dial-in line)
|
|
*/
|
|
if (stp->sd_flag & STRHUP) {
|
|
if (stp->sd_pgrp == 0)
|
|
strhup_set = 1;
|
|
else
|
|
return (EIO);
|
|
}
|
|
s = splstr();
|
|
stp->sd_flag |= STWOPEN;
|
|
(void) splx(s);
|
|
|
|
/*
|
|
* Open all modules and devices down stream to notify
|
|
* that another user is streaming.
|
|
* For modules, set the last argument to MODOPEN and
|
|
* do not pass any open flags.
|
|
* Note that this code makes no provision for cloned
|
|
* opens. (This means that we can't currently handle
|
|
* modules that remain pushed after the last close.)
|
|
*/
|
|
qp = stp->sd_wrq;
|
|
error = 0;
|
|
while (qp = qp->q_next) {
|
|
if (qp->q_flag & QREADR)
|
|
break;
|
|
unit = (*RD(qp)->q_qinfo->qi_qopen)(RD(qp), vp->v_rdev,
|
|
(qp->q_next ? 0 : oflag),
|
|
(qp->q_next ? MODOPEN : sflag));
|
|
if (unit == OPENFAIL) {
|
|
error = u.u_error ? u.u_error : ENXIO;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If the driver asked to have this made the
|
|
* controlling TTY, do so if we're really supposed to.
|
|
*/
|
|
if (!(oflag & FNOCTTY) && (unit & NEWCTTY))
|
|
assign_ctty(vp, stp, 0);
|
|
}
|
|
/*
|
|
* XXX - If all modules and devices on a stream with
|
|
* STRHUP set were successfully opened, clear it, as
|
|
* the stream is no longer in that state.
|
|
*/
|
|
if (strhup_set) {
|
|
if (error == 0) {
|
|
s = splstr();
|
|
stp->sd_flag &= ~STRHUP;
|
|
(void) splx(s);
|
|
} else {
|
|
/*
|
|
* If an error occurred and this is the "last"
|
|
* open reference, dismantle the stream
|
|
* after clearing the STWOPEN flag.
|
|
*/
|
|
if (!stillopen(VTOS(vp)->s_dev, vp->v_type)) {
|
|
s = splstr();
|
|
stp->sd_flag &= ~STWOPEN;
|
|
(void) splx(s);
|
|
strclose(vp, oflag);
|
|
return (error);
|
|
}
|
|
}
|
|
}
|
|
s = splstr();
|
|
stp->sd_flag &= ~STWOPEN;
|
|
(void) splx(s);
|
|
wakeup((caddr_t)stp);
|
|
return (error);
|
|
}
|
|
|
|
forcedclone:
|
|
/*
|
|
* Not already streaming so create a stream to driver
|
|
*/
|
|
|
|
if (!(qp = allocq()))
|
|
return (ENOSR);
|
|
|
|
if (!(stp = allocstr())) {
|
|
freeq(qp);
|
|
return (ENOSR);
|
|
}
|
|
|
|
/*
|
|
* Initialize stream head. The initialization can't be
|
|
* complete because we still don't have the right vnode
|
|
* to cross-link to.
|
|
*/
|
|
s = splstr();
|
|
stp->sd_wrq = WR(qp);
|
|
stp->sd_flag = STWOPEN;
|
|
stp->sd_siglist = NULL;
|
|
stp->sd_pollist = NULL;
|
|
stp->sd_sigflags = 0;
|
|
stp->sd_pollflags = 0;
|
|
(void) splx(s);
|
|
stp->sd_pgrp = 0;
|
|
stp->sd_sigio = 0;
|
|
stp->sd_vmin = -1; /* vmin processing initially disabled */
|
|
stp->sd_vtime = 0;
|
|
stp->sd_error = 0;
|
|
stp->sd_wroff = 0;
|
|
stp->sd_iocwait = 0;
|
|
stp->sd_iocblk = NULL;
|
|
stp->sd_pushcnt = 0;
|
|
stp->sd_selr = NULL;
|
|
stp->sd_selw = NULL;
|
|
stp->sd_sele = NULL;
|
|
setq(qp, &strdata, &stwdata);
|
|
qp->q_ptr = WR(qp)->q_ptr = (caddr_t)stp;
|
|
stp->sd_strtab = cdevsw[major(vp->v_rdev)].d_str;
|
|
|
|
/*
|
|
* XXX - The `v_stream' field may be valid for CLONEOPENS and must
|
|
* not be unconditionally reset before the device open actually
|
|
* completes. This, however, may result in a race leading to the
|
|
* creation of more than one stream to the device if it's open
|
|
* routine sleeps. The "other_specvp()" routine cannot prevent
|
|
* this since it will either find a vnode with a NULL `v_stream'
|
|
* field if the normal open has not completed, or fail if CLONEOPEN
|
|
* has not completed (since the new vnode hasn't been created).
|
|
*
|
|
* The correct way of solving this is to block all "stropen()" calls
|
|
* to the device which hasn't completed the CLONEOPEN. This requires
|
|
* processes to sleep on a data structure that uniquely represents
|
|
* the device. Since no such structure exists, the fix is to set
|
|
* `v_stream' only if it isn't valid.
|
|
*/
|
|
if (!(vp->v_stream))
|
|
vp->v_stream = stp;
|
|
|
|
/*
|
|
* Open driver and create stream to it (via qattach). Device opens
|
|
* may sleep, but must set PCATCH if they do so that signals will
|
|
* not cause a longjump. Failure to do this may result in the queues
|
|
* and stream head not being freed.
|
|
*
|
|
* Note that the device's open routine has no access to the
|
|
* associated vnode, since it doesn't necessarily yet exist.
|
|
* (If we're cloning, the original vnode is inappropriate.)
|
|
*/
|
|
unit = qattach(stp->sd_strtab, qp, vp->v_rdev, oflag, sflag);
|
|
|
|
if (unit == OPENFAIL)
|
|
goto openfail;
|
|
|
|
/*
|
|
* If the module requested that this be made a controlling
|
|
* TTY, note that fact and clear out the special flag.
|
|
*/
|
|
if (unit & NEWCTTY) {
|
|
newctty = 1;
|
|
unit &= ~NEWCTTY;
|
|
} else
|
|
newctty = 0;
|
|
|
|
/*
|
|
* Get a vnode for the device instance we've just opened
|
|
* and cross-link it with the stream head. A cloned instance
|
|
* requires a fresh vnode.
|
|
*/
|
|
if (sflag == CLONEOPEN) {
|
|
register dev_t newdev = makedev(major(vp->v_rdev), unit);
|
|
register struct vnode *nvp = makespecvp(newdev, vp->v_type);
|
|
|
|
VN_RELE(vp);
|
|
*vpp = vp = nvp;
|
|
}
|
|
stp->sd_vnode = vp;
|
|
vp->v_stream = stp;
|
|
|
|
/*
|
|
* If the driver asked to have this made the controlling TTY,
|
|
* do so if we're really supposed to.
|
|
*/
|
|
if (newctty && !(oflag & FNOCTTY))
|
|
assign_ctty(vp, stp, 0);
|
|
|
|
/*
|
|
* If this driver's streamtab specifies a list of modules to
|
|
* automatically push, do so.
|
|
*/
|
|
modlp = cdevsw[major(vp->v_rdev)].d_str->st_modlist;
|
|
if (modlp != NULL) {
|
|
while ((name = *modlp++) != NULL) {
|
|
dopush(stp, name, vp, 0, oflag);
|
|
if ((sverrno = u.u_error) != 0) {
|
|
s = splstr();
|
|
stp->sd_flag |= (STRHUP | STRCLOSE);
|
|
stp->sd_flag &= ~STWOPEN;
|
|
(void) splx(s);
|
|
/*
|
|
* Processes waiting for this stream
|
|
* will be woken up only after it is
|
|
* dismantled.
|
|
*/
|
|
strdismantle(stp, oflag);
|
|
u.u_error = sverrno;
|
|
return (u.u_error);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Wake up others that are waiting for stream to be created.
|
|
*/
|
|
s = splstr();
|
|
stp->sd_flag &= ~STWOPEN;
|
|
(void) splx(s);
|
|
wakeup((caddr_t)stp);
|
|
return (0);
|
|
|
|
openfail:
|
|
s = splstr();
|
|
stp->sd_flag |= STRHUP;
|
|
stp->sd_flag &= ~STWOPEN;
|
|
(void) splx(s);
|
|
vp->v_stream = NULL;
|
|
wakeup((caddr_t)stp);
|
|
stp->sd_vnode = NULL;
|
|
freestr(stp);
|
|
freeq(qp);
|
|
return (u.u_error ? u.u_error : ENXIO);
|
|
}
|
|
|
|
/*
|
|
* Assign the stream referred to by "stp", whose controlling vnode is
|
|
* "vp", as the controlling TTY for this process, if the device or module
|
|
* so requested (by returning a value with NEWCTTY set).
|
|
*/
|
|
static void
|
|
assign_ctty(vp, stp, force)
|
|
register struct vnode *vp;
|
|
register struct stdata *stp;
|
|
{
|
|
register struct proc *p;
|
|
struct sess *isactty();
|
|
register struct sess *sp;
|
|
|
|
/*
|
|
* All processes must satisfy the following criteria before
|
|
* getting a ctty:
|
|
* 1) The process must be a session leader.
|
|
* 2) The session must not have a ctty.
|
|
* 3) The tty can't be a ctty (i.e., in a session) already.
|
|
*
|
|
* We make sure that they will by handing out sessions if they
|
|
* do a setpgrp(0, 0) or an ioctl TIOCNOTTY and by making every
|
|
* child of init a session leader.
|
|
*/
|
|
if ((p = u.u_procp)->p_pid == 0)
|
|
return;
|
|
/*
|
|
* We have a problem as follows: A pre 4.4BSD program may assume
|
|
* (more or less legitimately) that its pgrp is 0 and that an open
|
|
* of a tty will give it a ctty. The hack here is that we have
|
|
* an unsetsid() system call that puts you back in session 0.
|
|
* If you are in sess 0 (pgrp == 0) then I'll call setsid()
|
|
* for you and give you a ctty.
|
|
*/
|
|
if (p->p_pgrp == 0 && !isactty(vp)) {
|
|
(void) setsid(SESS_NEW);
|
|
}
|
|
if (p->p_pid != p->p_sessp->s_sid || p->p_sessp->s_vp)
|
|
return;
|
|
/*
|
|
* XXX - this kludge is to allow the tty to be ripped away from someone
|
|
* else. force comes from TIOCSCTTY which getty calls to get the
|
|
* ctty.
|
|
*/
|
|
if (sp = isactty(vp)) {
|
|
if (!force)
|
|
return;
|
|
SESS_VN_RELE(sp);
|
|
}
|
|
VN_HOLD(vp);
|
|
stp->sd_pgrp = p->p_pgrp;
|
|
p->p_sessp->s_ttyp = &stp->sd_pgrp;
|
|
p->p_sessp->s_vp = vp;
|
|
p->p_sessp->s_ttyd = vp->v_rdev;
|
|
}
|
|
|
|
|
|
/*
|
|
* Close a stream.
|
|
* This is called from closef() on the last close of an open stream.
|
|
* Strclean() will already have removed the siglist and pollist
|
|
* information, so all that remains is to remove all multiplexor links
|
|
* for the stream, pop all the modules (and the driver), and free the
|
|
* stream structure.
|
|
*/
|
|
strclose(vp, flag)
|
|
struct vnode *vp;
|
|
{
|
|
register struct stdata *stp;
|
|
register struct vnode *othervp;
|
|
|
|
ASSERT(vp->v_stream);
|
|
|
|
stp = vp->v_stream;
|
|
/*
|
|
* XXX - Do not dismantle the stream if STWOPEN is set
|
|
* since this implies that some process is waiting in
|
|
* a driver open routine (e.g., for carrier). The stream
|
|
* will eventually be dismantled by that process.
|
|
*/
|
|
if (stp->sd_flag & STWOPEN)
|
|
return;
|
|
|
|
if ((othervp = other_specvp(vp)) != NULL) {
|
|
/*
|
|
* Another vnode refers to this device. If "vp" currently
|
|
* "owns" this stream, make that other vnode "own" it, since
|
|
* "vp" presumably wants nothing more to do with it.
|
|
*/
|
|
if (stp->sd_vnode == vp)
|
|
stp->sd_vnode = othervp;
|
|
vp->v_stream = NULL;
|
|
wakeup((caddr_t)stp);
|
|
} else {
|
|
/*
|
|
* This is the last vnode that refers to this stream.
|
|
* Shut it down by ripping out all transient links under it and
|
|
* dismantling it.
|
|
* And don't forget to take the ctty away from the session.
|
|
*/
|
|
register int s;
|
|
|
|
s = splstr();
|
|
stp->sd_flag |= STRCLOSE;
|
|
(void) splx(s);
|
|
munlinkall(stp, 1);
|
|
strdismantle(stp, flag);
|
|
if (vp == u.u_procp->p_sessp->s_vp)
|
|
SESS_VN_RELE(u.u_procp->p_sessp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Dismantle a stream.
|
|
* This is called by "stropen" if an auto-push fails, or by "strclose".
|
|
* Pop all the modules (and the driver), and free the stream structure.
|
|
*/
|
|
static void
|
|
strdismantle(stp, flag)
|
|
register struct stdata *stp;
|
|
{
|
|
register s;
|
|
register queue_t *qp;
|
|
register mblk_t *mp, *tmp;
|
|
int strtime();
|
|
register struct vnode *vp;
|
|
|
|
ASSERT(stp->sd_flag & STRCLOSE);
|
|
|
|
qp = stp->sd_wrq;
|
|
while (qp->q_next) {
|
|
if (!(flag & (FNDELAY | FNBIO | FNONBIO))) {
|
|
s = splstr();
|
|
stp->sd_flag |= STRTIME | WSLEEP;
|
|
timeout(strtime, (caddr_t)stp, STRTIMOUT*HZ);
|
|
/*
|
|
* Sleep until awakened by strwsrv() or strtime();
|
|
* i.e., until something is removed from the
|
|
* queue below the stream head or the timeout
|
|
* expires.
|
|
*/
|
|
while ((stp->sd_flag & STRTIME) &&
|
|
qp->q_next->q_count) {
|
|
stp->sd_flag |= WSLEEP;
|
|
/* ensure strwsrv gets enabled */
|
|
qp->q_next->q_flag |= QWANTW;
|
|
if (sleep((caddr_t)qp, STIPRI | PCATCH))
|
|
break;
|
|
}
|
|
untimeout(strtime, (caddr_t)stp);
|
|
stp->sd_flag &= ~(STRTIME | WSLEEP);
|
|
(void) splx(s);
|
|
}
|
|
qdetach(RD(qp->q_next), 1, flag);
|
|
}
|
|
flushq(qp, FLUSHALL);
|
|
qp = RD(qp);
|
|
s = splstr();
|
|
mp = qp->q_first;
|
|
qp->q_first = qp->q_last = NULL;
|
|
qp->q_count = 0;
|
|
|
|
if (stp->sd_iocblk) {
|
|
freemsg(stp->sd_iocblk);
|
|
stp->sd_iocblk = NULL;
|
|
}
|
|
vp = stp->sd_vnode;
|
|
ASSERT(vp->v_stream == stp);
|
|
stp->sd_vnode = NULL;
|
|
|
|
/* free stream head queue pair */
|
|
freeq(qp);
|
|
(void) splx(s);
|
|
|
|
while (mp) {
|
|
tmp = mp->b_next;
|
|
if (mp->b_datap->db_type == M_PASSFP)
|
|
closef(((struct strrecvfd *)mp->b_rptr)->f.fp);
|
|
freemsg(mp);
|
|
mp = tmp;
|
|
}
|
|
s = splstr();
|
|
vp->v_stream = NULL;
|
|
stp->sd_flag &= ~STRCLOSE;
|
|
(void) splx(s);
|
|
wakeup((caddr_t)stp);
|
|
freestr(stp);
|
|
}
|
|
|
|
/*
|
|
* Clean up after a process when it closes a stream. This is called
|
|
* from closef for all closes, whereas strclose is called only for the
|
|
* last close on a stream. The pollist and siglist are scanned for entries
|
|
* for the current process, and these are removed.
|
|
*/
|
|
void
|
|
strclean(vp)
|
|
struct vnode *vp;
|
|
{
|
|
register struct strevent *sep, *psep, *tsep;
|
|
struct stdata *stp;
|
|
|
|
stp = vp->v_stream;
|
|
psep = NULL;
|
|
sep = stp->sd_siglist;
|
|
while (sep) {
|
|
if (sep->se_procp == u.u_procp) {
|
|
tsep = sep->se_next;
|
|
if (psep)
|
|
psep->se_next = tsep;
|
|
else
|
|
stp->sd_siglist = tsep;
|
|
sefree(sep);
|
|
sep = tsep;
|
|
} else {
|
|
psep = sep;
|
|
sep = sep->se_next;
|
|
}
|
|
}
|
|
|
|
psep = NULL;
|
|
sep = stp->sd_pollist;
|
|
while (sep) {
|
|
if (sep->se_procp == u.u_procp) {
|
|
tsep = sep->se_next;
|
|
if (psep)
|
|
psep->se_next = tsep;
|
|
else
|
|
stp->sd_pollist = tsep;
|
|
sefree(sep);
|
|
sep = tsep;
|
|
} else {
|
|
psep = sep;
|
|
sep = sep->se_next;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read a stream according to the mode flags in sd_flag:
|
|
*
|
|
* (default mode) - Byte stream, msg boundaries are ignored
|
|
* RMSGDIS (msg discard) - Read on msg boundaries and throw away
|
|
* any data remaining in msg
|
|
* RMSGNODIS (msg non-discard) - Read on msg boundaries and put back
|
|
* any remaining data on head of read queue
|
|
*
|
|
* Consume readable messages on the front of the queue until uio->uio_resid
|
|
* is satisfied, the readable messages are exhausted and:
|
|
* VMIN is non-zero and VMIN characters are consumed or, if VTIME is
|
|
* non-zero, VTIME tenths of a second have elapsed since the last
|
|
* character arrived;
|
|
* VMIN is zero and, if VTIME is non-zero, VTIME tenths of a second have
|
|
* expired since the read was done;
|
|
* or a boundary is reached in a message mode. If no data was read, the
|
|
* VMIN and VTIME criterion has not been satisfied, and the stream was not
|
|
* opened with the NDELAY flag, block until data arrives.
|
|
* Otherwise return the data read and update the count.
|
|
*
|
|
* In default mode a 0 length message signifies end-of-file and terminates
|
|
* a read in progress. The 0 length message is removed from the queue
|
|
* only if it is the only message read (no data is read).
|
|
*
|
|
* Attempts to read an M_PROTO or M_PCPROTO message result in an
|
|
* EBADMSG error return.
|
|
*/
|
|
strread(vp, uio)
|
|
struct vnode *vp;
|
|
struct uio *uio;
|
|
{
|
|
register s;
|
|
register struct stdata *stp;
|
|
register mblk_t *bp, *nbp;
|
|
int n;
|
|
u_int nreturned;
|
|
int is_controlling_tty;
|
|
int check_background;
|
|
|
|
ASSERT(vp->v_stream);
|
|
stp = vp->v_stream;
|
|
|
|
if (stp->sd_flag & (STRERR|STPLEX)) {
|
|
u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
|
|
return;
|
|
}
|
|
|
|
nreturned = 0;
|
|
stp->sd_flag &= ~TIMEDOUT;
|
|
is_controlling_tty = (vp == u.u_procp->p_sessp->s_vp);
|
|
check_background = is_controlling_tty;
|
|
|
|
/* loop terminates when uio->uio_resid == 0 */
|
|
for (;;) {
|
|
/*
|
|
* Loop until we're allowed to read and we have something to
|
|
* read.
|
|
*/
|
|
for (;;) {
|
|
register int waitflags;
|
|
|
|
if (check_background) {
|
|
if (u.u_error = tty_rd_wait(stp))
|
|
return;
|
|
check_background = 0;
|
|
}
|
|
s = splstr();
|
|
if (bp = getq(RD(stp->sd_wrq))) {
|
|
(void) splx(s);
|
|
break;
|
|
}
|
|
if (stp->sd_flag & TIMEDOUT) {
|
|
/*
|
|
* Timer has expired. Don't wait for any
|
|
* more data, just go with what we've got.
|
|
*/
|
|
(void) splx(s);
|
|
goto out;
|
|
}
|
|
if (stp->sd_vmin >= 0) {
|
|
/*
|
|
* Vmin/vtime processing is in effect.
|
|
*/
|
|
if (stp->sd_vmin > 0) {
|
|
/*
|
|
* Waiting for some minimum number of
|
|
* characters.
|
|
*/
|
|
stp->sd_deficit =
|
|
stp->sd_vmin - nreturned;
|
|
if (stp->sd_deficit <= 0) {
|
|
/*
|
|
* VMIN or more bytes returned;
|
|
* read is satisfied.
|
|
*/
|
|
(void) splx(s);
|
|
goto out;
|
|
}
|
|
/*
|
|
* If both vmin and vtime are active,
|
|
* make sure that characters present
|
|
* when the read was issued start the
|
|
* timer.
|
|
*/
|
|
else if (nreturned != 0 &&
|
|
stp->sd_vtime != 0 &&
|
|
!(stp->sd_flag & CLKRUNNING)) {
|
|
stp->sd_flag |= CLKRUNNING;
|
|
timeout(strvtime, (caddr_t)stp,
|
|
(int)stp->sd_vtime*(HZ/10));
|
|
}
|
|
} else {
|
|
if (stp->sd_vtime == 0 ||
|
|
nreturned != 0) {
|
|
/*
|
|
* Timeout of zero expires
|
|
* immediately; return, no
|
|
* matter how much data was
|
|
* returned. If any data has
|
|
* been read, return.
|
|
*/
|
|
(void) splx(s);
|
|
goto out;
|
|
}
|
|
if (!(stp->sd_flag & CLKRUNNING)) {
|
|
stp->sd_flag |= CLKRUNNING;
|
|
timeout(strvtime, (caddr_t)stp,
|
|
(int)stp->sd_vtime*(HZ/10));
|
|
}
|
|
stp->sd_deficit = 0;
|
|
}
|
|
} else {
|
|
/*
|
|
* Vmin/vtime processing inactive.
|
|
* Firewall: make sure sd_deficit has a
|
|
* reasonable value, although its value in
|
|
* this case shouldn't matter.
|
|
*/
|
|
stp->sd_deficit = 0;
|
|
}
|
|
if (stp->sd_flag & STRHUP) {
|
|
/*
|
|
* Carrier (or other connection) went away;
|
|
* don't wait for more data, there's none
|
|
* coming.
|
|
*/
|
|
(void) splx(s);
|
|
goto out;
|
|
}
|
|
/*
|
|
* Wait for something to show up in the read queue.
|
|
* If interrupted and we haven't yet transferred any
|
|
* data, allow the call to restart, otherwise arrange
|
|
* to return successfully with what we have.
|
|
*/
|
|
waitflags = (nreturned != 0) ?
|
|
(READWAIT|NOINTR) : (READWAIT|INTRRESTART);
|
|
if (strwaitq(stp, waitflags, uio->uio_fmode)) {
|
|
/*
|
|
* Old-style S5 no-delay mode caused 0 to be
|
|
* returned if no data, not -1 with "errno" set
|
|
* to EAGAIN, on terminals. Support for POSIX
|
|
* added as well.
|
|
*/
|
|
if ((uio->uio_fmode & FNONBIO) == 0 &&
|
|
(uio->uio_fmode & FNBIO) &&
|
|
(stp->sd_flag & OLDNDELAY) &&
|
|
(u.u_error == EAGAIN))
|
|
u.u_error = 0;
|
|
(void) splx(s);
|
|
goto out;
|
|
}
|
|
(void) splx(s);
|
|
check_background = is_controlling_tty;
|
|
}
|
|
|
|
if (qready())
|
|
runqueues();
|
|
|
|
switch (bp->b_datap->db_type) {
|
|
|
|
case M_DATA:
|
|
/*
|
|
* Strip off leading zero-length message
|
|
* blocks, making sure that at least one
|
|
* block remains.
|
|
*/
|
|
while (bp->b_cont && (bp->b_rptr >= bp->b_wptr)) {
|
|
nbp = bp;
|
|
bp = bp->b_cont;
|
|
freeb(nbp);
|
|
}
|
|
|
|
if ((bp->b_wptr - bp->b_rptr) == 0) {
|
|
/*
|
|
* If vmin is in effect, we cannot return
|
|
* until the entire vmin amount has been
|
|
* satisfied, so an empty message is
|
|
* equivalent to no message at all.
|
|
*/
|
|
if (stp->sd_vmin > 0) {
|
|
freemsg(bp);
|
|
continue;
|
|
}
|
|
/*
|
|
* if already read data put zero
|
|
* length message back on queue else
|
|
* free msg and return 0.
|
|
*/
|
|
if (nreturned != 0)
|
|
putbq(RD(stp->sd_wrq), bp);
|
|
else
|
|
freemsg(bp);
|
|
goto out;
|
|
}
|
|
|
|
while (bp && uio->uio_resid) {
|
|
if (n = MIN(uio->uio_resid,
|
|
bp->b_wptr - bp->b_rptr)) {
|
|
u.u_error = uiomove((caddr_t)
|
|
bp->b_rptr, n, UIO_READ, uio);
|
|
nreturned += n;
|
|
}
|
|
|
|
if (u.u_error) {
|
|
freemsg(bp);
|
|
goto out;
|
|
}
|
|
|
|
bp->b_rptr += n;
|
|
while (bp && (bp->b_rptr >= bp->b_wptr)) {
|
|
nbp = bp;
|
|
bp = bp->b_cont;
|
|
freeb(nbp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The data may have been the leftover of a PCPROTO, so
|
|
* if none are left reset the STRPRI flag just in case.
|
|
*/
|
|
if (bp) {
|
|
/*
|
|
* Have remaining data in message.
|
|
* Free msg if in discard mode.
|
|
*/
|
|
if (stp->sd_flag & RMSGDIS) {
|
|
freemsg(bp);
|
|
s = splstr();
|
|
stp->sd_flag &= ~STRPRI;
|
|
(void) splx(s);
|
|
} else
|
|
putbq(RD(stp->sd_wrq), bp);
|
|
|
|
} else {
|
|
s = splstr();
|
|
stp->sd_flag &= ~STRPRI;
|
|
(void) splx(s);
|
|
}
|
|
|
|
/*
|
|
* Check for signal messages at the front of the read
|
|
* queue and generate the signal(s) if appropriate.
|
|
* The only signal that can be on queue is M_SIG at
|
|
* this point.
|
|
*/
|
|
while (((bp = RD(stp->sd_wrq)->q_first)) &&
|
|
(bp->b_datap->db_type == M_SIG)) {
|
|
bp = getq(RD(stp->sd_wrq));
|
|
switch (*bp->b_rptr) {
|
|
case SIGPOLL:
|
|
if (stp->sd_sigflags & S_MSG)
|
|
strsendsig(stp->sd_siglist,
|
|
S_MSG);
|
|
break;
|
|
|
|
default:
|
|
if (stp->sd_pgrp)
|
|
gsignal(stp->sd_pgrp,
|
|
(int)*bp->b_rptr);
|
|
break;
|
|
}
|
|
freemsg(bp);
|
|
if (qready())
|
|
runqueues();
|
|
}
|
|
|
|
if ((uio->uio_resid == 0) ||
|
|
(stp->sd_flag & (RMSGDIS|RMSGNODIS)))
|
|
goto out;
|
|
continue;
|
|
|
|
case M_PROTO:
|
|
case M_PCPROTO:
|
|
case M_PASSFP:
|
|
/*
|
|
* Only data messages are readable.
|
|
* Any others generate an error.
|
|
*/
|
|
u.u_error= EBADMSG;
|
|
putbq(RD(stp->sd_wrq), bp);
|
|
goto out;
|
|
|
|
default:
|
|
/*
|
|
* Garbage on stream head read queue
|
|
*/
|
|
ASSERT(0);
|
|
freemsg(bp);
|
|
break;
|
|
}
|
|
}
|
|
|
|
out:
|
|
/*
|
|
* The read is complete; shut off any timers that were running.
|
|
*/
|
|
if (stp->sd_flag & CLKRUNNING) {
|
|
untimeout(strvtime, (caddr_t)stp);
|
|
stp->sd_flag &= ~CLKRUNNING;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#if defined(sun)
|
|
/*
|
|
* Kludge to do reads on streams from within the kernel; used by the
|
|
* window system. Never blocks; reads are always treated as no-delay
|
|
* reads. Never lowers interrupt priority; this routine is run from
|
|
* a "timeout" routine.
|
|
*/
|
|
int
|
|
strkread(stp, bufptr, len_ptr)
|
|
register struct stdata *stp;
|
|
caddr_t bufptr;
|
|
int *len_ptr;
|
|
{
|
|
register mblk_t *bp, *nbp;
|
|
register int n;
|
|
char rflg;
|
|
register int len;
|
|
|
|
if (stp->sd_flag&(STRERR|STPLEX))
|
|
return ((stp->sd_flag&STPLEX) ? EINVAL : stp->sd_error);
|
|
|
|
/* loop terminates when len == 0 or when no more data */
|
|
len = *len_ptr;
|
|
rflg = 0;
|
|
for (;;) {
|
|
if (!(bp = getq(RD(stp->sd_wrq))))
|
|
goto done;
|
|
|
|
switch (bp->b_datap->db_type) {
|
|
|
|
case M_DATA:
|
|
/*
|
|
* Strip off leading zero-length message
|
|
* blocks, making sure that at least one
|
|
* block remains.
|
|
*/
|
|
while (bp->b_cont && (bp->b_rptr >= bp->b_wptr)) {
|
|
nbp = bp;
|
|
bp = bp->b_cont;
|
|
freeb(nbp);
|
|
}
|
|
|
|
if ((bp->b_wptr - bp->b_rptr) == 0) {
|
|
/*
|
|
* if already read data put zero
|
|
* length message back on queue else
|
|
* free msg and return 0.
|
|
*/
|
|
if (rflg)
|
|
putbq(RD(stp->sd_wrq), bp);
|
|
else
|
|
freemsg(bp);
|
|
goto done;
|
|
}
|
|
|
|
rflg = 1;
|
|
while (bp && len) {
|
|
if (n = MIN(len, bp->b_wptr - bp->b_rptr))
|
|
bcopy((caddr_t)bp->b_rptr, bufptr,
|
|
(u_int)n);
|
|
|
|
bp->b_rptr += n;
|
|
bufptr += n;
|
|
len -= n;
|
|
while (bp && (bp->b_rptr >= bp->b_wptr)) {
|
|
nbp = bp;
|
|
bp = bp->b_cont;
|
|
freeb(nbp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The data may have been the leftover of a PCPROTO, so
|
|
* if none are left reset the STRPRI flag just in case.
|
|
*/
|
|
if (bp) {
|
|
/*
|
|
* Have remaining data in message.
|
|
*/
|
|
putbq(RD(stp->sd_wrq), bp);
|
|
} else
|
|
stp->sd_flag &= ~STRPRI;
|
|
|
|
if (len == 0)
|
|
goto done;
|
|
continue;
|
|
|
|
case M_PROTO:
|
|
case M_PCPROTO:
|
|
case M_PASSFP:
|
|
/*
|
|
* Only data messages are readable.
|
|
* Any others generate an error.
|
|
*/
|
|
putbq(RD(stp->sd_wrq), bp);
|
|
return (EBADMSG);
|
|
|
|
default:
|
|
/*
|
|
* Garbage on stream head read queue
|
|
*/
|
|
ASSERT(0);
|
|
freemsg(bp);
|
|
break;
|
|
}
|
|
}
|
|
|
|
done:
|
|
if ((*len_ptr -= len) == 0)
|
|
return (EWOULDBLOCK); /* nothing read */
|
|
return (0);
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
* Stream read put procedure. Called from downstream driver/module
|
|
* with messages for the stream head. Data, protocol, and in-stream
|
|
* signal messages are placed on the queue, others are handled directly.
|
|
*/
|
|
static int
|
|
strrput(q, bp)
|
|
register queue_t *q;
|
|
register mblk_t *bp;
|
|
{
|
|
register struct stdata *stp;
|
|
register struct iocblk *iocbp;
|
|
struct stroptions *sop;
|
|
register int datalen;
|
|
register int s;
|
|
|
|
stp = (struct stdata *)q->q_ptr;
|
|
|
|
ASSERT(!(stp->sd_flag & STPLEX));
|
|
|
|
switch (bp->b_datap->db_type) {
|
|
|
|
case M_DATA:
|
|
case M_PROTO:
|
|
case M_PCPROTO:
|
|
case M_PASSFP:
|
|
s = splstr();
|
|
if (bp->b_datap->db_type == M_PCPROTO) {
|
|
/*
|
|
* Only one priority protocol message is allowed at the
|
|
* stream head at a time.
|
|
*/
|
|
if (stp->sd_flag & STRPRI) {
|
|
freemsg(bp);
|
|
(void) splx(s);
|
|
return;
|
|
}
|
|
stp->sd_flag |= STRPRI;
|
|
if (stp->sd_sele) {
|
|
selwakeup(stp->sd_sele,
|
|
(stp->sd_flag & ECOLL) != 0);
|
|
stp->sd_sele = 0;
|
|
stp->sd_flag &= ~ECOLL;
|
|
}
|
|
if (stp->sd_sigflags & S_HIPRI)
|
|
strsendsig(stp->sd_siglist, S_HIPRI);
|
|
if (stp->sd_pollflags & POLLPRI)
|
|
strwakepoll(stp, POLLPRI);
|
|
} else if (!q->q_first) {
|
|
if (stp->sd_selr) {
|
|
selwakeup(stp->sd_selr,
|
|
(stp->sd_flag & RCOLL) != 0);
|
|
stp->sd_selr = 0;
|
|
stp->sd_flag &= ~RCOLL;
|
|
}
|
|
if (stp->sd_sigflags & S_INPUT)
|
|
strsendsig(stp->sd_siglist, S_INPUT);
|
|
if (stp->sd_pollflags & POLLIN)
|
|
strwakepoll(stp, POLLIN);
|
|
}
|
|
|
|
if (stp->sd_flag & RSLEEP) {
|
|
/*
|
|
* Cancel any pending VTIME timeouts, and start a
|
|
* new one, if VMIN is non-zero.
|
|
*/
|
|
if (stp->sd_vmin > 0 && stp->sd_vtime != 0) {
|
|
|
|
if (stp->sd_flag & CLKRUNNING)
|
|
untimeout(strvtime, (caddr_t)stp);
|
|
stp->sd_flag |= CLKRUNNING;
|
|
stp->sd_flag &= ~TIMEDOUT;
|
|
timeout(strvtime, (caddr_t)stp,
|
|
(int) stp->sd_vtime*(HZ/10));
|
|
}
|
|
/*
|
|
* Avoid extra work by awakening processes sleeping in
|
|
* read/getmsg only if the arrival of this message
|
|
* makes it possible that they can proceed. Criteria
|
|
* are any of the following:
|
|
* - Not a data message.
|
|
* - Zero-length (semantics are tricky enough that we
|
|
* don't want to pre-empt them here).
|
|
* - Vmin/vtime processing not active.
|
|
* - The message contains enough data to satisfy the
|
|
* outstanding vmin deficit.
|
|
*/
|
|
if (bp->b_datap->db_type != M_DATA ||
|
|
(datalen = msgdsize(bp)) == 0 ||
|
|
stp->sd_vmin < 0 ||
|
|
(stp->sd_deficit -= datalen) <= 0) {
|
|
char oldpri;
|
|
|
|
stp->sd_flag &= ~RSLEEP;
|
|
oldpri = curpri;
|
|
curpri = STIPRI; /* no preemption */
|
|
wakeup((caddr_t)q);
|
|
curpri = oldpri;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Notify with SIGIO if 4.2-style asynchronous I/O requested.
|
|
*/
|
|
if (stp->sd_flag & ASYNC)
|
|
str_sigio(stp);
|
|
|
|
putq(q, bp);
|
|
(void) splx(s);
|
|
return;
|
|
|
|
|
|
case M_ERROR:
|
|
/*
|
|
* An error has occurred downstream; the errno is in the first
|
|
* byte of the message.
|
|
*/
|
|
if (*bp->b_rptr != 0) {
|
|
s = splstr();
|
|
stp->sd_flag |= STRERR;
|
|
stp->sd_error = *bp->b_rptr;
|
|
(void) splx(s);
|
|
wakeup((caddr_t)q); /* the readers */
|
|
wakeup((caddr_t)WR(q)); /* the writers */
|
|
wakeup((caddr_t)stp); /* the ioctllers */
|
|
|
|
strwakepoll(stp, POLLERR);
|
|
|
|
bp->b_datap->db_type = M_FLUSH;
|
|
*bp->b_rptr = FLUSHRW;
|
|
qreply(q, bp);
|
|
return;
|
|
}
|
|
freemsg(bp);
|
|
return;
|
|
|
|
case M_HANGUP:
|
|
freemsg(bp);
|
|
s = splstr();
|
|
stp->sd_error = ENXIO;
|
|
stp->sd_flag |= STRHUP;
|
|
(void) splx(s);
|
|
|
|
/*
|
|
* send signal if controlling tty
|
|
*/
|
|
if (stp->sd_pgrp)
|
|
gsignal(stp->sd_pgrp, SIGHUP);
|
|
|
|
wakeup((caddr_t)q); /* the readers */
|
|
wakeup((caddr_t)WR(q)); /* the writers */
|
|
wakeup((caddr_t)stp); /* the ioctllers */
|
|
|
|
/*
|
|
* wake up read, write, and exception pollers and
|
|
* reset wakeup mechanism.
|
|
*/
|
|
strwakepoll(stp, POLLHUP);
|
|
return;
|
|
|
|
case M_UNHANGUP:
|
|
freemsg(bp);
|
|
s = splstr();
|
|
stp->sd_error = 0;
|
|
stp->sd_flag &= ~STRHUP;
|
|
(void) splx(s);
|
|
return;
|
|
|
|
case M_SIG:
|
|
/*
|
|
* Someone downstream wants to post a signal. The
|
|
* signal to post is contained in the first byte of the
|
|
* message. If the message would go on the front of
|
|
* the queue, send a signal to the process group
|
|
* (if not SIGPOLL) or to the siglist processes
|
|
* (SIGPOLL). If something is already on the queue,
|
|
* just enqueue the message.
|
|
*/
|
|
if (q->q_first) {
|
|
putq(q, bp);
|
|
return;
|
|
}
|
|
/* flow through */
|
|
|
|
case M_PCSIG:
|
|
/*
|
|
* Don't enqueue, just post the signal.
|
|
*/
|
|
switch (*bp->b_rptr) {
|
|
case SIGPOLL:
|
|
if (stp->sd_sigflags & S_MSG)
|
|
strsendsig(stp->sd_siglist, S_MSG);
|
|
break;
|
|
|
|
default:
|
|
if (stp->sd_pgrp)
|
|
gsignal(stp->sd_pgrp, (int)*bp->b_rptr);
|
|
break;
|
|
}
|
|
freemsg(bp);
|
|
return;
|
|
|
|
case M_FLUSH:
|
|
/*
|
|
* Flush queues. The indication of which queues to flush
|
|
* is in the first byte of the message. If the read queue
|
|
* is specified, then flush it.
|
|
*/
|
|
if (*bp->b_rptr & FLUSHR) {
|
|
mblk_t *mp, *tmp;
|
|
|
|
s = splstr();
|
|
mp = q->q_first;
|
|
q->q_first = q->q_last = NULL;
|
|
q->q_count = 0;
|
|
q->q_flag &= ~(QFULL | QWANTW);
|
|
(void) splx(s);
|
|
while (mp) {
|
|
tmp = mp->b_next;
|
|
if (mp->b_datap->db_type == M_PASSFP)
|
|
closef(((struct strrecvfd *)
|
|
mp->b_rptr)->f.fp);
|
|
freemsg(mp);
|
|
mp = tmp;
|
|
}
|
|
}
|
|
if (*bp->b_rptr & FLUSHW) {
|
|
*bp->b_rptr &= ~FLUSHR;
|
|
qreply(q, bp);
|
|
return;
|
|
}
|
|
freemsg(bp);
|
|
return;
|
|
|
|
case M_IOCACK:
|
|
case M_IOCNAK:
|
|
iocbp = (struct iocblk *)bp->b_rptr;
|
|
/*
|
|
* if not waiting for ACK or NAK then just free msg
|
|
* if already have ACK or NAK for user then just free msg
|
|
* if incorrect id sequence number then just free msg
|
|
*/
|
|
s = splstr();
|
|
if (!(stp->sd_flag & IOCWAIT) || stp->sd_iocblk ||
|
|
(stp->sd_iocid != iocbp->ioc_id)) {
|
|
freemsg(bp);
|
|
(void) splx(s);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* assign ACK or NAK to user and wake up
|
|
*/
|
|
stp->sd_iocblk = bp;
|
|
(void) splx(s);
|
|
wakeup((caddr_t)stp);
|
|
return;
|
|
|
|
case M_SETOPTS:
|
|
/*
|
|
* Set stream head options (read option, write offset,
|
|
* min/max packet size, high/low water marks for the read
|
|
* side only, and/or min/time values).
|
|
*
|
|
* N.B.: The stream head and ldterm both cooperate in treating
|
|
* so_vmin as a signed value, even though it's declared as a
|
|
* ushort. Tty semantics force normal values to fit in a
|
|
* u_char, so the cast below should cause no significance
|
|
* problems there. The value (short) -1 is used as an out of
|
|
* band value to disable vmin processing altogether.
|
|
*/
|
|
|
|
ASSERT((bp->b_wptr - bp->b_rptr) == sizeof (struct stroptions));
|
|
sop = (struct stroptions *)bp->b_rptr;
|
|
s = splstr();
|
|
if (sop->so_flags & SO_READOPT) {
|
|
switch (sop->so_readopt) {
|
|
case RNORM:
|
|
stp->sd_flag &= ~(RMSGDIS | RMSGNODIS);
|
|
break;
|
|
case RMSGD:
|
|
stp->sd_flag =
|
|
(stp->sd_flag & ~RMSGNODIS) | RMSGDIS;
|
|
break;
|
|
case RMSGN:
|
|
stp->sd_flag =
|
|
(stp->sd_flag & ~RMSGDIS) | RMSGNODIS;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (sop->so_flags & SO_WROFF)
|
|
stp->sd_wroff = sop->so_wroff;
|
|
if (sop->so_flags & SO_MINPSZ)
|
|
q->q_minpsz = sop->so_minpsz;
|
|
if (sop->so_flags & SO_MAXPSZ)
|
|
q->q_maxpsz = sop->so_maxpsz;
|
|
if (sop->so_flags & SO_HIWAT)
|
|
q->q_hiwat = sop->so_hiwat;
|
|
if (sop->so_flags & SO_LOWAT)
|
|
q->q_lowat = sop->so_lowat;
|
|
if (sop->so_flags & SO_VMIN)
|
|
stp->sd_vmin = (short) sop->so_vmin;
|
|
if (sop->so_flags & SO_VTIME)
|
|
stp->sd_vtime = sop->so_vtime;
|
|
if (sop->so_flags & SO_NDELON)
|
|
stp->sd_flag |= OLDNDELAY;
|
|
if (sop->so_flags & SO_NDELOFF)
|
|
stp->sd_flag &= ~OLDNDELAY;
|
|
if (sop->so_flags & SO_TOSTOP) {
|
|
if (sop->so_tostop)
|
|
stp->sd_flag |= STRTOSTOP;
|
|
else
|
|
stp->sd_flag &= ~STRTOSTOP;
|
|
}
|
|
|
|
freemsg(bp);
|
|
|
|
if ((q->q_count <= q->q_lowat) && (q->q_flag & QWANTW)) {
|
|
q->q_flag &= ~QWANTW;
|
|
for (q=backq(q); q && !q->q_qinfo->qi_srvp; q=backq(q))
|
|
;
|
|
if (q)
|
|
qenable(q);
|
|
}
|
|
|
|
(void) splx(s);
|
|
return;
|
|
|
|
/*
|
|
* The following set of cases deal with situations where two stream
|
|
* heads are connected to each other (twisted streams). These messages
|
|
* should never originate at a driver or module.
|
|
*/
|
|
case M_BREAK:
|
|
case M_CTL:
|
|
case M_DELAY:
|
|
case M_START:
|
|
case M_STOP:
|
|
freemsg(bp);
|
|
return;
|
|
|
|
case M_IOCTL:
|
|
/*
|
|
* Always NAK this condition
|
|
* (makes no sense)
|
|
*/
|
|
bp->b_datap->db_type = M_IOCNAK;
|
|
qreply(q, bp);
|
|
return;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
freemsg(bp);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Send SIGPOLL signal to all processes registered on the given signal
|
|
* list that want a signal for the specified event.
|
|
*/
|
|
static void
|
|
strsendsig(siglist, event)
|
|
register struct strevent *siglist;
|
|
register event;
|
|
{
|
|
struct strevent *sep;
|
|
|
|
for (sep = siglist; sep; sep = sep->se_next) {
|
|
if (sep->se_events & event)
|
|
psignal(sep->se_procp, SIGPOLL);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Send a SIGIO to the pid/pgrp that wants it.
|
|
*
|
|
* N.B. This does not follow the old semantics in that a process
|
|
* that has asked for sigio on its ctty and then changes from fg to bg,
|
|
* it will still be signaled. Previously, the process that became
|
|
* the fg process would get signaled, like it or not.
|
|
*
|
|
* XXX - This should be merged in with the sys5 way of doing ASYNC IO.
|
|
*/
|
|
str_sigio(stp)
|
|
struct stdata *stp;
|
|
{
|
|
register sigio = stp->sd_sigio;
|
|
|
|
/*
|
|
* If they were assuming that the tty pgrp gets sigio, match that.
|
|
* If no one wants the signal, drop it.
|
|
*/
|
|
if (!sigio && !(sigio = -stp->sd_pgrp))
|
|
return;
|
|
if (sigio < 0)
|
|
gsignal(-sigio, SIGIO);
|
|
else {
|
|
struct proc *p;
|
|
|
|
if (p = pfind(sigio))
|
|
psignal(p, SIGIO);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Wake up all processes sleeping on a poll for the given events
|
|
* on this stream. POLLERR and POLLHUP cause a wakeup of all processes
|
|
* regardless of the events they were looking for. Remove all of
|
|
* the event cells matching the given events from the pollist.
|
|
*/
|
|
static void
|
|
strwakepoll(stp, events)
|
|
struct stdata *stp;
|
|
{
|
|
register struct strevent *sep, *psep;
|
|
register s;
|
|
|
|
stp->sd_pollflags &= ~events;
|
|
sep = stp->sd_pollist;
|
|
psep = NULL;
|
|
while (sep) {
|
|
if ((sep->se_events & events) ||
|
|
(events & (POLLHUP|POLLERR|POLLNVAL))) {
|
|
s = splstr();
|
|
if (sep->se_procp->p_wchan == (caddr_t)&pollwait)
|
|
unselect(sep->se_procp);
|
|
else
|
|
sep->se_procp->p_flag &= ~SPOLL;
|
|
(void) splx(s);
|
|
if (psep){
|
|
psep->se_next = sep->se_next;
|
|
sefree(sep);
|
|
sep = psep->se_next;
|
|
} else {
|
|
stp->sd_pollist = sep->se_next;
|
|
sefree(sep);
|
|
sep = stp->sd_pollist;
|
|
}
|
|
} else {
|
|
psep = sep;
|
|
sep = sep->se_next;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Write attempts to break the read request into messages conforming
|
|
* with the minimum and maximum packet sizes set downstream.
|
|
*
|
|
* Write will always attempt to get the largest buffer it can to satisfy the
|
|
* message size. If it can not, then it will try up to 2 classes down to try
|
|
* to satisfy the write. Write will not block if downstream queue is full and
|
|
* O_NDELAY is set, otherwise it will block waiting for the queue to get room.
|
|
*
|
|
* A write of zero bytes gets packaged into a zero length message and sent
|
|
* downstream like any other message.
|
|
*
|
|
* If buffers of the requested sizes are not available, the write will
|
|
* sleep until the buffers become available.
|
|
*
|
|
* Write (if specified) will supply a write offset in a message if it
|
|
* makes sense. This can be specified by downstream modules as part of
|
|
* a M_SETOPTS message. Write will not supply the write offset if it
|
|
* cannot supply any data in a buffer. In other words, write will never
|
|
* send down an empty packet due to a write offset.
|
|
*/
|
|
strwrite(vp, uio)
|
|
struct vnode *vp;
|
|
struct uio *uio;
|
|
{
|
|
int tempmode;
|
|
register s;
|
|
register struct stdata *stp;
|
|
mblk_t *mp;
|
|
short rmin, rmax;
|
|
char waitflag;
|
|
int is_controlling_tty;
|
|
int check_background;
|
|
int written;
|
|
|
|
ASSERT(vp->v_stream);
|
|
stp = vp->v_stream;
|
|
|
|
if (stp->sd_flag & (STRERR|STRHUP|STPLEX)) {
|
|
if (stp->sd_flag & STPLEX)
|
|
u.u_error = EINVAL;
|
|
else if (stp->sd_flag & STRHUP)
|
|
u.u_error = EIO; /* posix */
|
|
else
|
|
u.u_error = stp->sd_error;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Check the min/max packet size constraints. If min packet size
|
|
* is non-zero, the write cannot be split into multiple messages
|
|
* and still guarantee the size constraints.
|
|
*/
|
|
rmin = stp->sd_wrq->q_next->q_minpsz;
|
|
rmax = stp->sd_wrq->q_next->q_maxpsz;
|
|
ASSERT(rmax);
|
|
if (rmax == 0)
|
|
return;
|
|
if (rmax == INFPSZ)
|
|
rmax = strmsgsz;
|
|
else
|
|
rmax = MIN(strmsgsz, rmax);
|
|
|
|
if (rmin > 0 && (uio->uio_resid < rmin || uio->uio_resid > rmax)) {
|
|
u.u_error = ERANGE;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Do until count satisfied or error.
|
|
*/
|
|
waitflag = WRITEWAIT|INTRRESTART;
|
|
|
|
/*
|
|
* Old-style S5 no-delay mode didn't apply on writes to terminals.
|
|
*/
|
|
if (stp->sd_flag & OLDNDELAY)
|
|
tempmode = uio->uio_fmode & ~FNBIO;
|
|
else
|
|
tempmode = uio->uio_fmode;
|
|
|
|
is_controlling_tty = (vp == u.u_procp->p_sessp->s_vp);
|
|
check_background = is_controlling_tty;
|
|
written = 0;
|
|
|
|
do {
|
|
for (;;) {
|
|
if (check_background) {
|
|
if (u.u_error = tty_wr_wait(stp, 1))
|
|
return;
|
|
check_background = 0;
|
|
}
|
|
s = splstr();
|
|
if (canput(stp->sd_wrq->q_next)) {
|
|
(void) splx(s);
|
|
break;
|
|
}
|
|
if (strwaitq(stp, waitflag, tempmode)) {
|
|
/*
|
|
* Don't error on POSIX- or BSD-style
|
|
* non-blocking I/O if anything has been
|
|
* written.
|
|
*/
|
|
if (written &&
|
|
(uio->uio_fmode & (FNONBIO|FNDELAY)) &&
|
|
(u.u_error == EAGAIN ||
|
|
u.u_error == EWOULDBLOCK))
|
|
u.u_error = 0;
|
|
(void) splx(s);
|
|
return;
|
|
}
|
|
(void) splx(s);
|
|
check_background = is_controlling_tty;
|
|
}
|
|
|
|
/*
|
|
* Determine the size of the next message to be
|
|
* packaged. May have to break write into several
|
|
* messages based on max packet size.
|
|
*/
|
|
if (!(mp = strmakemsg((struct strbuf *)NULL,
|
|
MIN(uio->uio_resid, rmax), uio,
|
|
(int)stp->sd_wroff, (long)0, 0)))
|
|
return;
|
|
|
|
/*
|
|
* Put block downstream
|
|
*/
|
|
(*stp->sd_wrq->q_next->q_qinfo->qi_putp)
|
|
(stp->sd_wrq->q_next, mp);
|
|
written = 1;
|
|
waitflag |= NOINTR;
|
|
if (qready())
|
|
runqueues();
|
|
|
|
} while (uio->uio_resid);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Check the uppermost write queue on the stream associated with vp for
|
|
* space for a kernel message (from uprintf/tprintf). Allow some space
|
|
* over the normal hiwater mark so we don't lose messages due to normal
|
|
* flow control, but don't let the device (tty) run amok.
|
|
* The sleep is interruptable.
|
|
*/
|
|
int
|
|
strcheckoutq(vp, wait)
|
|
struct vnode *vp;
|
|
int wait;
|
|
{
|
|
register struct stdata *stp;
|
|
int s;
|
|
int serror;
|
|
|
|
ASSERT(vp->v_stream);
|
|
stp = vp->v_stream;
|
|
|
|
if ((stp->sd_flag & (STRERR|STRHUP|STPLEX|STWOPEN|STRCLOSE)) ||
|
|
(stp->sd_wrq == (queue_t *)0))
|
|
return (0);
|
|
|
|
s = splstr();
|
|
if (!canputextra(stp->sd_wrq->q_next)) {
|
|
if (!wait) {
|
|
(void) splx(s);
|
|
return (0);
|
|
}
|
|
serror = u.u_error;
|
|
while (!canput(stp->sd_wrq->q_next)) {
|
|
if (strwaitq(stp, WRITEWAIT|NOINTR, FWRITE)) {
|
|
(void) splx(s);
|
|
u.u_error = serror;
|
|
return (0);
|
|
}
|
|
}
|
|
}
|
|
(void) splx(s);
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Variant of "strwrite" for printing kernel messages to a tty.
|
|
* Arguments are a pointer to the vnode to write to, the address
|
|
* of the characters to print, and the number of characters to print.
|
|
* Flow control is ignored; it is assumed that if any special flow-control
|
|
* checking is to be done, it's already been done by "strcheckoutq".
|
|
*/
|
|
int
|
|
stroutput(vp, base, count)
|
|
struct vnode *vp;
|
|
char *base;
|
|
int count;
|
|
{
|
|
register struct stdata *stp;
|
|
register queue_t *qnext;
|
|
mblk_t *mp;
|
|
struct uio uio;
|
|
struct iovec iov;
|
|
short rmin, rmax;
|
|
int serror;
|
|
|
|
ASSERT(vp->v_stream);
|
|
stp = vp->v_stream;
|
|
|
|
if (stp->sd_flag & (STRERR|STRHUP|STPLEX|STWOPEN|STRCLOSE))
|
|
return (0);
|
|
|
|
/*
|
|
* Check the min/max packet size constraints. If min packet size
|
|
* is non-zero, the write cannot be split into multiple messages
|
|
* and still guarantee the size constraints.
|
|
*/
|
|
qnext = stp->sd_wrq->q_next;
|
|
rmin = qnext->q_minpsz;
|
|
rmax = qnext->q_maxpsz;
|
|
ASSERT(rmax);
|
|
if (rmax == 0)
|
|
return (0);
|
|
if (rmax == INFPSZ)
|
|
rmax = strmsgsz;
|
|
else
|
|
rmax = MIN(strmsgsz, rmax);
|
|
|
|
if ((rmin > 0) && ((count < rmin) || (count > rmax)))
|
|
return (0);
|
|
|
|
/*
|
|
* do until count satisfied or error
|
|
*/
|
|
iov.iov_base = base;
|
|
iov.iov_len = count;
|
|
uio.uio_iov = &iov;
|
|
uio.uio_iovcnt = 1;
|
|
uio.uio_offset = 0;
|
|
uio.uio_segflg = UIO_SYSSPACE;
|
|
uio.uio_fmode = 0;
|
|
uio.uio_resid = iov.iov_len;
|
|
serror = u.u_error;
|
|
do {
|
|
/*
|
|
* Determine the size of the next message to be
|
|
* packaged. May have to break write into several
|
|
* messages based on max packet size.
|
|
*/
|
|
if (!(mp = strmakemsg((struct strbuf *)NULL,
|
|
MIN(uio.uio_resid, rmax), &uio,
|
|
(int)stp->sd_wroff, (long)0, 1))) {
|
|
u.u_error = serror;
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Put block downstream
|
|
*/
|
|
(*qnext->q_qinfo->qi_putp)(qnext, mp);
|
|
} while (uio.uio_resid);
|
|
u.u_error = serror;
|
|
return (1);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Stream head write service routine.
|
|
* Its job is to wake up any sleeping writers when a queue
|
|
* downstream needs data (part of the flow control in putq and getq).
|
|
* It also must wake anyone sleeping on a poll().
|
|
* For stream head right below mux module, it must also invoke put procedure
|
|
* of next downstream module
|
|
*/
|
|
static int
|
|
strwsrv(q)
|
|
register queue_t *q;
|
|
{
|
|
register struct stdata *stp;
|
|
register int s;
|
|
|
|
stp = (struct stdata *)q->q_ptr;
|
|
|
|
s = splstr();
|
|
ASSERT(!(stp->sd_flag & STPLEX));
|
|
|
|
if (stp->sd_selw) {
|
|
selwakeup(stp->sd_selw, (stp->sd_flag & WCOLL) != 0);
|
|
stp->sd_selw = 0;
|
|
stp->sd_flag &= ~WCOLL;
|
|
}
|
|
|
|
if (stp->sd_flag & WSLEEP) {
|
|
char oldpri;
|
|
|
|
stp->sd_flag &= ~WSLEEP;
|
|
oldpri = curpri;
|
|
curpri = STOPRI;
|
|
wakeup((caddr_t)q);
|
|
curpri = oldpri;
|
|
}
|
|
|
|
if (stp->sd_flag & ASYNC)
|
|
str_sigio(stp);
|
|
if (stp->sd_sigflags & S_OUTPUT)
|
|
strsendsig(stp->sd_siglist, S_OUTPUT);
|
|
if (stp->sd_pollflags & POLLOUT)
|
|
strwakepoll(stp, POLLOUT);
|
|
(void) splx(s);
|
|
}
|
|
|
|
|
|
/*
|
|
* Table to map module names to line discipline numbers.
|
|
*/
|
|
typedef struct {
|
|
char *ld_name; /* name of line discipline module */
|
|
struct streamtab *ld_tab; /* streamtab entry for that module */
|
|
} ldinfo;
|
|
|
|
static ldinfo ldtab[] = {
|
|
{ "ldterm" }, /* old line discipline */
|
|
{ "bk" }, /* Berknet line discipline */
|
|
{ "ldterm" }, /* new line discipline */
|
|
{ "tab" }, /* Hitachi tablet discipline */
|
|
{ "ntab" }, /* gtco tablet discipline */
|
|
{ "ms" }, /* mouse discipline */
|
|
{ "kb" }, /* keyboard discipline */
|
|
{ "slip" }, /* Serial Line IP discipline */
|
|
};
|
|
|
|
#define NLDISC (sizeof ldtab / sizeof ldtab[0])
|
|
|
|
/*
|
|
* Module name of compatibility module.
|
|
* We assume that this guy sits on top of all line discipline modules,
|
|
* since it doesn't make much sense to have TIOCSETD and TIOCGETD
|
|
* without the rest of the V7/4.2BSD "ioctl"s.
|
|
*/
|
|
|
|
static char ttcompatname[] = "ttcompat";
|
|
static struct qinit *ttcompat_qinit;
|
|
|
|
static int ldtab_initialized = 0;
|
|
|
|
/*
|
|
* ioctl for streams
|
|
*/
|
|
/*ARGSUSED*/
|
|
strioctl(vp, cmd, data, flag)
|
|
struct vnode *vp;
|
|
caddr_t data;
|
|
int flag;
|
|
{
|
|
register struct stdata *stp;
|
|
struct strioctl strioc;
|
|
int arg = *(int *)data;
|
|
int size;
|
|
int strict_posix = 0;
|
|
register int s;
|
|
|
|
ASSERT(vp->v_stream);
|
|
stp = vp->v_stream;
|
|
|
|
if (stp->sd_flag & (STRERR|STPLEX)) {
|
|
u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
|
|
return;
|
|
}
|
|
switch (cmd) {
|
|
|
|
/*
|
|
* Backwards compatibility hacks.
|
|
*/
|
|
case TIOCGETD: {
|
|
register queue_t *q;
|
|
register int i;
|
|
|
|
if (!ldtab_initialized)
|
|
initialize_ldtab();
|
|
if ((q = getcompatqueue(stp)) == NULL ||
|
|
(q = q->q_next) == NULL)
|
|
goto regular; /* do it as a regular "ioctl" */
|
|
/*
|
|
* We found a module below the compatibility module.
|
|
* Now see if it corresponds to a line discipline.
|
|
* We start the search at the first entry, so that
|
|
* we always seem to be in the new line discipline
|
|
* if we're using the regular TTY module.
|
|
*/
|
|
for (i = 1; i < NLDISC; i++) {
|
|
if (ldtab[i].ld_tab != NULL &&
|
|
q->q_qinfo == ldtab[i].ld_tab->st_wrinit)
|
|
goto found_getd;
|
|
}
|
|
goto regular; /* do it as a regular "ioctl" */
|
|
|
|
found_getd:
|
|
*(int *)data = i;
|
|
break;
|
|
}
|
|
|
|
case TIOCSETD:
|
|
if (ldreplace(vp, stp, *(int *)data))
|
|
break;
|
|
/*
|
|
* Try doing it as a regular "ioctl".
|
|
*/
|
|
|
|
default:
|
|
regular:
|
|
/*
|
|
* Set up strioc buffer and call strdoioctl() to do
|
|
* the work.
|
|
*/
|
|
strioc.ic_cmd = cmd;
|
|
|
|
/*
|
|
* Turn the timeout OFF; TCSETSW waits for output
|
|
* to drain, and if the user hits ^S and goes out
|
|
* to lunch, this could take a while to finish...
|
|
*/
|
|
strioc.ic_timout = INFTIM;
|
|
|
|
/*
|
|
* Interpret high order word to find
|
|
* amount of data to be copied to/from the
|
|
* user's address space.
|
|
*/
|
|
size = (cmd &~ (_IOC_INOUT|_IOC_VOID)) >> 16;
|
|
if (cmd&_IOC_IN) {
|
|
/*
|
|
* Argument is a pointer to a variable that is read
|
|
* by the system; it may also be modified.
|
|
* "data" is a pointer to a copy of that variable;
|
|
* if the call modifies that variable, whatever we
|
|
* stuff back into what "data" points to will be
|
|
* copied back there.
|
|
*/
|
|
strioc.ic_len = size;
|
|
} else if (cmd&_IOC_OUT) {
|
|
/*
|
|
* Argument is a pointer to a variable that is
|
|
* modified, but not read, by the system.
|
|
* "data" is a pointer to a buffer for that variable;
|
|
* whatever we stuff back into what "data" points to
|
|
* will be copied back there.
|
|
*/
|
|
strioc.ic_len = 0;
|
|
} else if (cmd&_IOC_VOID) {
|
|
/*
|
|
* Argument is a cookie interpreted by the module;
|
|
* it had better not be a pointer, as the module does
|
|
* not run in process context!
|
|
* The cookie is assumed to be an "int"; "data"
|
|
* points to a copy of it.
|
|
*/
|
|
strioc.ic_len = sizeof (int);
|
|
} else {
|
|
/*
|
|
* Argument is an old-style "ioctl" command, and "data"
|
|
* is not valid.
|
|
*/
|
|
strioc.ic_len = 0;
|
|
}
|
|
|
|
strioc.ic_dp = (char *)data;
|
|
strdoioctl(stp, &strioc, K_TO_K_BOTH, flag);
|
|
return;
|
|
|
|
/*
|
|
* Calls that apply to all sorts of stream objects.
|
|
*/
|
|
case FIONBIO:
|
|
if (*(int *)data)
|
|
stp->sd_flag |= NBIO;
|
|
else
|
|
stp->sd_flag &= ~NBIO;
|
|
return;
|
|
|
|
case FIOASYNC:
|
|
if (*(int *)data)
|
|
stp->sd_flag |= ASYNC;
|
|
else
|
|
stp->sd_flag &= ~ASYNC;
|
|
return;
|
|
|
|
case FIONREAD: {
|
|
/*
|
|
* return total number of bytes of data in all messages
|
|
* in read queue
|
|
*/
|
|
register mblk_t *mp;
|
|
int count = 0;
|
|
|
|
for (mp = RD(stp->sd_wrq)->q_first; mp; mp = mp->b_next)
|
|
count += msgdsize(mp);
|
|
*(int *)data = count;
|
|
return;
|
|
}
|
|
|
|
case FIOSETOWN:
|
|
stp->sd_sigio = *(int*)data;
|
|
break;
|
|
|
|
case TIOCSETPGRP:
|
|
strict_posix = 1;
|
|
/* fall through */
|
|
|
|
case TIOCSPGRP: {
|
|
/*
|
|
* POSIX checks:
|
|
* range
|
|
* calling proc must have an active ctty,
|
|
* and this stream must be it,
|
|
* and this tty must be still accessible by the session
|
|
* and arg must be a pgrp in the process' session.
|
|
* SunOS checks:
|
|
* and tty must be open for reading.
|
|
*/
|
|
struct proc *p;
|
|
struct sess *sp = u.u_procp->p_sessp;
|
|
|
|
if (arg < 0 || arg >= MAXPID) {
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
if (vp == sp->s_vp) {
|
|
if (u.u_error = tty_wr_wait(stp, 0)) {
|
|
return;
|
|
}
|
|
} else {
|
|
u.u_error = ENOTTY;
|
|
return;
|
|
}
|
|
p = pgfind(arg);
|
|
if (strict_posix) {
|
|
if (!p || p->p_sessp != sp) {
|
|
u.u_error = EPERM;
|
|
return;
|
|
}
|
|
} else {
|
|
/*
|
|
* Should insist that p exists and is in this session.
|
|
* Instead we insist that if arg is an existing pgrp
|
|
* it is in our session. This goes away when the
|
|
* shells work properly. The shell problem is that
|
|
* the parent or the child may try to give the tty to
|
|
* a pgrp that doesn't exist yet but will exist soon.
|
|
*/
|
|
if (p && p->p_sessp != sp) {
|
|
u.u_error = EPERM;
|
|
return;
|
|
}
|
|
}
|
|
if (u.u_uid && (flag & FREAD) == 0) {
|
|
u.u_error = EPERM;
|
|
return;
|
|
}
|
|
stp->sd_pgrp = arg;
|
|
break;
|
|
}
|
|
|
|
case TIOCSCTTY:
|
|
assign_ctty(vp, stp, suser() && arg == 1);
|
|
break;
|
|
|
|
case FIOGETOWN:
|
|
*(int *)data = stp->sd_sigio ? stp->sd_sigio : -stp->sd_pgrp;
|
|
break;
|
|
|
|
case TIOCGETPGRP: {
|
|
register struct sess *sp;
|
|
|
|
sp = u.u_procp->p_sessp;
|
|
if (sp == NULL || sp->s_vp != vp) {
|
|
u.u_error = ENOTTY;
|
|
return;
|
|
}
|
|
/* fall through to TIOCGPRP */
|
|
}
|
|
|
|
case TIOCGPGRP:
|
|
*(int *)data = stp->sd_pgrp;
|
|
break;
|
|
|
|
case I_STR: {
|
|
/*
|
|
* Stream ioctl. Read in an strioctl buffer from the user
|
|
* along with any data specified and send it downstream.
|
|
* Strdoioctl will wait allow only one ioctl message at
|
|
* a time, and waits for the acknowledgement.
|
|
*/
|
|
register struct strioctl *striocp;
|
|
|
|
striocp = (struct strioctl *)data;
|
|
if (striocp->ic_timout < -1) {
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
|
|
strdoioctl(stp, striocp, U_TO_K, flag);
|
|
return;
|
|
}
|
|
|
|
case I_NREAD: {
|
|
/*
|
|
* return number of bytes of data in first message
|
|
* in queue in "arg" and return the number of messages
|
|
* in queue in return value
|
|
*/
|
|
mblk_t *bp;
|
|
|
|
if (bp = RD(stp->sd_wrq)->q_first)
|
|
*(int *)data = msgdsize(bp);
|
|
u.u_rval1 = qsize(RD(stp->sd_wrq));
|
|
return;
|
|
}
|
|
|
|
case I_FIND: {
|
|
/*
|
|
* get module name
|
|
*/
|
|
char *mname;
|
|
queue_t *q;
|
|
int i;
|
|
|
|
mname = (char *)data;
|
|
/*
|
|
* find module in fmodsw
|
|
*/
|
|
if ((i = findmod(mname)) < 0) {
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
u.u_rval1 = 0;
|
|
|
|
/* look downstream to see if module is there */
|
|
for (q = stp->sd_wrq->q_next;
|
|
q && (fmodsw[i].f_str->st_wrinit != q->q_qinfo);
|
|
q = q->q_next)
|
|
;
|
|
|
|
u.u_rval1 = (q ? 1 : 0);
|
|
return;
|
|
}
|
|
|
|
case I_PUSH:
|
|
/*
|
|
* Push a module below the stream head.
|
|
*/
|
|
dopush(stp, (char *)data, vp, 1, 0);
|
|
return;
|
|
|
|
case I_POP:
|
|
/*
|
|
* Pop the module that's below the stream head (if there
|
|
* is one).
|
|
*/
|
|
if (stp->sd_flag & STRHUP) {
|
|
u.u_error = ENXIO;
|
|
return;
|
|
}
|
|
if (stp->sd_wrq->q_next->q_next &&
|
|
!(stp->sd_wrq->q_next->q_next->q_flag & QREADR)) {
|
|
qdetach(RD(stp->sd_wrq->q_next), 1, 0);
|
|
stp->sd_pushcnt--;
|
|
return;
|
|
}
|
|
u.u_error = EINVAL;
|
|
return;
|
|
|
|
case I_LOOK: {
|
|
/*
|
|
* Get name of first module downstream.
|
|
* If no module, return error.
|
|
*/
|
|
register char *mname;
|
|
|
|
if ((mname = getmname(stp->sd_wrq->q_next)) != NULL) {
|
|
bcopy((caddr_t) mname, data, FMNAMESZ + 1);
|
|
return;
|
|
}
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
|
|
|
|
case I_LINK:
|
|
case I_PLINK: {
|
|
/*
|
|
* link a multiplexer
|
|
*/
|
|
struct file *fpdown;
|
|
struct linkblk *linkblkp, *alloclink();
|
|
struct stdata *stpdown;
|
|
queue_t *qup;
|
|
queue_t *rq;
|
|
|
|
/*
|
|
* Test for invalid upper stream
|
|
*/
|
|
if (stp->sd_flag & STRHUP) {
|
|
u.u_error = ENXIO;
|
|
return;
|
|
}
|
|
if (!stp->sd_strtab->st_muxwinit) {
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
|
|
fpdown = getf(arg);
|
|
if (u.u_error)
|
|
return;
|
|
|
|
/*
|
|
* Test for invalid lower stream.
|
|
* If the potential lower stream isn't a stream,
|
|
* or if it's the same as the upper stream,
|
|
* or if it's already linked below something,
|
|
* has had a hangup or an error, or is waiting
|
|
* for an "ioctl" response,
|
|
* disallow the link.
|
|
*/
|
|
if (!(stpdown = ((struct vnode *)(fpdown->f_data))->v_stream) ||
|
|
(stpdown == stp) ||
|
|
(stpdown->sd_flag & (STPLEX|STRHUP|STRERR|IOCWAIT))) {
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If this is not a permanent link and you're
|
|
* going to introduce a cycle, disallow the link.
|
|
* (If it's a permanent link, the notion of a cycle
|
|
* doesn't apply, since a permanent link only
|
|
* involves one stream.)
|
|
*/
|
|
if (cmd == I_LINK) {
|
|
qup = getendq(stp->sd_wrq);
|
|
if (linkcycle(qup, stpdown->sd_wrq)) {
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
} else
|
|
qup = NULL;
|
|
|
|
if (!(linkblkp = alloclink(qup,
|
|
stpdown->sd_wrq, fpdown - &file[0]))) {
|
|
u.u_error = EAGAIN;
|
|
return;
|
|
}
|
|
strioc.ic_cmd = cmd;
|
|
strioc.ic_timout = 0;
|
|
strioc.ic_len = sizeof (struct linkblk);
|
|
strioc.ic_dp = (char *) linkblkp;
|
|
|
|
/* Set up queues for link */
|
|
rq = RD(stpdown->sd_wrq);
|
|
setq(rq, stp->sd_strtab->st_muxrinit,
|
|
stp->sd_strtab->st_muxwinit);
|
|
rq->q_ptr = WR(rq)->q_ptr = NULL;
|
|
rq->q_flag |= QWANTR;
|
|
WR(rq)->q_flag |= QWANTR;
|
|
|
|
strdoioctl(stp, &strioc, K_TO_K, flag);
|
|
|
|
if (u.u_error) {
|
|
linkblkp->l_qbot = NULL;
|
|
setq(rq, &strdata, &stwdata);
|
|
rq->q_ptr = WR(rq)->q_ptr = (caddr_t)stpdown;
|
|
return;
|
|
}
|
|
s = splstr();
|
|
stpdown->sd_flag |= STPLEX;
|
|
(void) splx(s);
|
|
fpdown->f_count++;
|
|
/*
|
|
* Wake up any other processes that may have been
|
|
* waiting on the lower stream. These will all
|
|
* error out.
|
|
*/
|
|
wakeup((caddr_t)rq);
|
|
wakeup((caddr_t)WR(rq));
|
|
wakeup((caddr_t)stpdown);
|
|
u.u_rval1 = fpdown - &file[0];
|
|
return;
|
|
}
|
|
|
|
|
|
case I_UNLINK: {
|
|
/*
|
|
* Unlink a multiplexer.
|
|
* If arg is -1, unlink all links for which this is the
|
|
* controlling stream. Otherwise, arg is an index number
|
|
* for a link to be removed.
|
|
*/
|
|
struct file *fpdown;
|
|
struct linkblk *linkblkp;
|
|
struct stdata *stpdown;
|
|
|
|
if (stp->sd_flag & STRHUP) {
|
|
u.u_error = ENXIO;
|
|
return;
|
|
}
|
|
if (arg == -1) {
|
|
munlinkall(stp, 0);
|
|
} else {
|
|
fpdown = &file[arg];
|
|
if ((fpdown < file) ||
|
|
(fpdown >= fileNFILE) ||
|
|
fpdown->f_type != DTYPE_VNODE ||
|
|
!fpdown->f_data ||
|
|
!(stpdown = ((struct vnode *)
|
|
(fpdown->f_data))->v_stream) ||
|
|
!(stpdown->sd_flag & STPLEX) ||
|
|
!(linkblkp = findlinks(getendq(stp->sd_wrq),
|
|
stpdown->sd_wrq, arg))) {
|
|
/* invalid user supplied index number */
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
(void) munlinkone(stp, fpdown, linkblkp, 0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
case I_PUNLINK: {
|
|
/*
|
|
* Unlink a multiplexer; the link being broken is a
|
|
* persistent link. The argument is a file descriptor
|
|
* referring to the stream being unlinked. This is not,
|
|
* however, the file descriptor that held the lower
|
|
* stream open, so it's not the one we close; that one
|
|
* is the one referred to by "linkblkp->l_index".
|
|
*/
|
|
struct file *fpdown;
|
|
struct linkblk *linkblkp;
|
|
struct stdata *stpdown;
|
|
|
|
if (stp->sd_flag & STRHUP) {
|
|
u.u_error = ENXIO;
|
|
return;
|
|
}
|
|
fpdown = getf(arg);
|
|
if (u.u_error)
|
|
return;
|
|
if (fpdown->f_type != DTYPE_VNODE ||
|
|
!fpdown->f_data ||
|
|
!(stpdown = ((struct vnode *)(fpdown->f_data))->v_stream) ||
|
|
!(stpdown->sd_flag & STPLEX) ||
|
|
!(linkblkp = findlinks((queue_t *)NULL,
|
|
stpdown->sd_wrq, 0))) {
|
|
/* invalid user supplied file descriptor */
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
(void) munlinkone(stp, &file[linkblkp->l_index],
|
|
linkblkp, 0);
|
|
return;
|
|
}
|
|
|
|
case I_FLUSH:
|
|
/*
|
|
* send a flush message downstream
|
|
* flush message can indicate
|
|
* FLUSHR - flush read queue
|
|
* FLUSHW - flush write queue
|
|
* FLUSHRW - flush read/write queue
|
|
*/
|
|
if (stp->sd_flag & STRHUP) {
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
if (arg & ~FLUSHRW) {
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
while (!putctl1(stp->sd_wrq->q_next, M_FLUSH, arg)) {
|
|
if (strwaitbuf(1, BPRI_HI, 1))
|
|
return;
|
|
}
|
|
|
|
if (qready())
|
|
runqueues();
|
|
return;
|
|
|
|
case I_SRDOPT:
|
|
/*
|
|
* Set read options
|
|
*
|
|
* RNORM - default stream mode
|
|
* RMSGN - message no discard
|
|
* RMSGD - message discard
|
|
*/
|
|
s = splstr();
|
|
switch (arg) {
|
|
case RNORM:
|
|
stp->sd_flag &= ~(RMSGDIS | RMSGNODIS);
|
|
break;
|
|
case RMSGD:
|
|
stp->sd_flag = (stp->sd_flag & ~RMSGNODIS) | RMSGDIS;
|
|
break;
|
|
case RMSGN:
|
|
stp->sd_flag = (stp->sd_flag & ~RMSGDIS) | RMSGNODIS;
|
|
break;
|
|
default:
|
|
u.u_error = EINVAL;
|
|
break;
|
|
}
|
|
(void) splx(s);
|
|
return;
|
|
|
|
case I_GRDOPT: {
|
|
/*
|
|
* Get read option and return the value
|
|
* to spot pointed to by arg
|
|
*/
|
|
|
|
*(int *)data = ((stp->sd_flag & RMSGDIS ? RMSGD :
|
|
(stp->sd_flag & RMSGNODIS ? RMSGN : RNORM)));
|
|
return;
|
|
}
|
|
|
|
case I_SETSIG: {
|
|
/*
|
|
* Register the calling proc to receive the SIGPOLL
|
|
* signal based on the events given in arg. If
|
|
* arg is zero, remove the proc from register list.
|
|
*/
|
|
struct strevent *sep, *psep;
|
|
|
|
psep = NULL;
|
|
|
|
s = splstr();
|
|
for (sep = stp->sd_siglist;
|
|
sep && (sep->se_procp != u.u_procp);
|
|
psep = sep, sep = sep->se_next)
|
|
;
|
|
|
|
if (arg) {
|
|
if (arg & ~(S_INPUT|S_HIPRI|S_OUTPUT|S_MSG)) {
|
|
(void) splx(s);
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If proc not already registered, add it to list
|
|
*/
|
|
if (!sep) {
|
|
if (!(sep = sealloc(SE_SLEEP))) {
|
|
(void) splx(s);
|
|
u.u_error = EAGAIN;
|
|
return;
|
|
}
|
|
if (psep)
|
|
psep->se_next = sep;
|
|
else
|
|
stp->sd_siglist = sep;
|
|
sep->se_procp = u.u_procp;
|
|
}
|
|
/*
|
|
* set events
|
|
*/
|
|
sep->se_events = arg;
|
|
stp->sd_sigflags |= arg;
|
|
(void) splx(s);
|
|
return;
|
|
}
|
|
/*
|
|
* Remove proc from register list
|
|
*/
|
|
if (sep) {
|
|
if (psep)
|
|
psep->se_next = sep->se_next;
|
|
else
|
|
stp->sd_siglist = sep->se_next;
|
|
sefree(sep);
|
|
/*
|
|
* recalculate OR of sig events
|
|
*/
|
|
stp->sd_sigflags = 0;
|
|
for (sep = stp->sd_siglist; sep; sep = sep->se_next)
|
|
stp->sd_sigflags |= sep->se_events;
|
|
(void) splx(s);
|
|
return;
|
|
}
|
|
(void) splx(s);
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
|
|
case I_GETSIG: {
|
|
/*
|
|
* Return (in arg) the current registration of events
|
|
* for which the calling proc is to be signaled.
|
|
*/
|
|
struct strevent *sep;
|
|
|
|
s = splstr();
|
|
for (sep = stp->sd_siglist; sep; sep = sep->se_next)
|
|
if (sep->se_procp == u.u_procp) {
|
|
*(int *)data = sep->se_events;
|
|
(void) splx(s);
|
|
return;
|
|
};
|
|
(void) splx(s);
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
|
|
case I_PEEK: {
|
|
mblk_t *bp;
|
|
register struct strpeek *strpeekp = (struct strpeek *)data;
|
|
struct uio uio;
|
|
struct iovec iov;
|
|
int n;
|
|
|
|
if (!(bp = RD(stp->sd_wrq)->q_first) ||
|
|
((strpeekp->flags & RS_HIPRI) && (queclass(bp) == QNORM))){
|
|
u.u_rval1 = 0;
|
|
return;
|
|
}
|
|
|
|
if (bp->b_datap->db_type == M_PASSFP) {
|
|
u.u_error = EBADMSG;
|
|
return;
|
|
}
|
|
|
|
if (bp->b_datap->db_type == M_PCPROTO)
|
|
strpeekp->flags = RS_HIPRI;
|
|
else
|
|
strpeekp->flags = 0;
|
|
|
|
|
|
/*
|
|
* First process PROTO blocks, if any
|
|
*/
|
|
iov.iov_base = strpeekp->ctlbuf.buf;
|
|
iov.iov_len = strpeekp->ctlbuf.maxlen;
|
|
uio.uio_iov = &iov;
|
|
uio.uio_iovcnt = 1;
|
|
uio.uio_offset = 0;
|
|
uio.uio_segflg = UIO_USERSPACE;
|
|
uio.uio_fmode = 0;
|
|
uio.uio_resid = iov.iov_len;
|
|
while (bp && bp->b_datap->db_type != M_DATA &&
|
|
uio.uio_resid >= 0) {
|
|
if (n = MIN(uio.uio_resid, bp->b_wptr - bp->b_rptr))
|
|
u.u_error = uiomove((caddr_t)bp->b_rptr,
|
|
n, UIO_READ, &uio);
|
|
if (u.u_error)
|
|
return;
|
|
bp = bp->b_cont;
|
|
}
|
|
strpeekp->ctlbuf.len = strpeekp->ctlbuf.maxlen - uio.uio_resid;
|
|
|
|
/*
|
|
* Now process DATA blocks, if any
|
|
*/
|
|
iov.iov_base = strpeekp->databuf.buf;
|
|
iov.iov_len = strpeekp->databuf.maxlen;
|
|
uio.uio_iovcnt = 1;
|
|
uio.uio_resid = iov.iov_len;
|
|
while (bp && uio.uio_resid) {
|
|
if (n = MIN(uio.uio_resid, bp->b_wptr - bp->b_rptr))
|
|
u.u_error = uiomove((caddr_t)bp->b_rptr,
|
|
n, UIO_READ, &uio);
|
|
if (u.u_error)
|
|
return;
|
|
bp = bp->b_cont;
|
|
}
|
|
|
|
|
|
strpeekp->databuf.len =
|
|
strpeekp->databuf.maxlen - uio.uio_resid;
|
|
u.u_rval1 = 1;
|
|
return;
|
|
}
|
|
|
|
case I_FDINSERT: {
|
|
register struct strfdinsert *strfdinsertp;
|
|
struct file *resftp;
|
|
struct stdata *resstp;
|
|
queue_t *q;
|
|
mblk_t *mp;
|
|
register msgsize;
|
|
short rmin, rmax;
|
|
struct uio uio;
|
|
struct iovec iov;
|
|
|
|
if (stp->sd_flag & (STRERR|STRHUP|STPLEX)) {
|
|
u.u_error = ((stp->sd_flag&STPLEX) ?
|
|
EINVAL : stp->sd_error);
|
|
return;
|
|
}
|
|
strfdinsertp = (struct strfdinsert *)data;
|
|
if (strfdinsertp->offset < 0 ||
|
|
(strfdinsertp->offset % sizeof (queue_t *)) != 0) {
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
if (!(resftp = getf(strfdinsertp->fildes)) ||
|
|
!(resstp = ((struct vnode *)(resftp->f_data))->v_stream)) {
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
|
|
if (resstp->sd_flag & (STRERR|STRHUP|STPLEX)) {
|
|
u.u_error = ((resstp->sd_flag&STPLEX) ?
|
|
EINVAL : resstp->sd_error);
|
|
return;
|
|
}
|
|
|
|
/* get read queue of stream terminus */
|
|
for (q = resstp->sd_wrq->q_next; q->q_next; q = q->q_next)
|
|
;
|
|
q = RD(q);
|
|
|
|
if (strfdinsertp->ctlbuf.len <
|
|
(strfdinsertp->offset + sizeof (queue_t *))) {
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Check for legal flag value
|
|
*/
|
|
if (strfdinsertp->flags & ~RS_HIPRI) {
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* make sure ctl and data sizes together fall within
|
|
* the limits of the max and min receive packet sizes
|
|
* and do not exceed system limit. A negative data length
|
|
* means that no data part is to be sent.
|
|
*/
|
|
rmin = stp->sd_wrq->q_next->q_minpsz;
|
|
rmax = stp->sd_wrq->q_next->q_maxpsz;
|
|
if (rmax == INFPSZ)
|
|
rmax = strmsgsz;
|
|
else
|
|
rmax = MIN(rmax, strmsgsz);
|
|
msgsize = strfdinsertp->databuf.len;
|
|
if (msgsize < 0)
|
|
msgsize = 0;
|
|
if ((msgsize < rmin) || (msgsize > rmax) ||
|
|
(strfdinsertp->ctlbuf.len > strctlsz)) {
|
|
u.u_error = ERANGE;
|
|
return;
|
|
}
|
|
|
|
s = splstr();
|
|
while (!(strfdinsertp->flags & RS_HIPRI) &&
|
|
!canput(stp->sd_wrq->q_next))
|
|
if (strwaitq(stp, WRITEWAIT, flag)) {
|
|
(void) splx(s);
|
|
return;
|
|
}
|
|
(void) splx(s);
|
|
|
|
iov.iov_base = strfdinsertp->databuf.buf;
|
|
iov.iov_len = strfdinsertp->databuf.len;
|
|
uio.uio_iov = &iov;
|
|
uio.uio_iovcnt = 1;
|
|
uio.uio_offset = 0;
|
|
uio.uio_segflg = UIO_USERSPACE;
|
|
uio.uio_fmode = 0;
|
|
uio.uio_resid = iov.iov_len;
|
|
if (!(mp = strmakemsg(&strfdinsertp->ctlbuf,
|
|
strfdinsertp->databuf.len, &uio,
|
|
(int)stp->sd_wroff, strfdinsertp->flags, 0)))
|
|
return;
|
|
|
|
/*
|
|
* place pointer to queue 'offset' bytes from the
|
|
* start of the control portion of the message
|
|
*/
|
|
|
|
*((queue_t **)(mp->b_rptr + strfdinsertp->offset)) = q;
|
|
|
|
/*
|
|
* Put message downstream
|
|
*/
|
|
(*stp->sd_wrq->q_next->q_qinfo->qi_putp)
|
|
(stp->sd_wrq->q_next, mp);
|
|
if (qready())
|
|
runqueues();
|
|
return;
|
|
}
|
|
|
|
case I_SENDFD: {
|
|
register queue_t *qp;
|
|
register mblk_t *mp;
|
|
register struct strrecvfd *srf;
|
|
struct file *fp;
|
|
|
|
if (stp->sd_flag & STRHUP) {
|
|
u.u_error = ENXIO;
|
|
return;
|
|
}
|
|
for (qp = stp->sd_wrq; qp->q_next; qp = qp->q_next)
|
|
;
|
|
if (qp->q_qinfo != &strdata) {
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
if (!(fp = getf(arg)))
|
|
return;
|
|
if ((qp->q_flag & QFULL) ||
|
|
!(mp = allocb((int)sizeof (struct strrecvfd), BPRI_MED))) {
|
|
u.u_error = EAGAIN;
|
|
return;
|
|
}
|
|
srf = (struct strrecvfd *)mp->b_rptr;
|
|
mp->b_wptr += sizeof (struct strrecvfd);
|
|
mp->b_datap->db_type = M_PASSFP;
|
|
srf->f.fp = fp;
|
|
srf->uid = u.u_uid;
|
|
srf->gid = u.u_gid;
|
|
fp->f_count++;
|
|
strrput(qp, mp);
|
|
return;
|
|
}
|
|
|
|
case I_RECVFD: {
|
|
register mblk_t *mp;
|
|
register struct strrecvfd *srf;
|
|
register i;
|
|
|
|
if (stp->sd_flag & (STRERR|STPLEX)) {
|
|
u.u_error = ((stp->sd_flag & STPLEX) ?
|
|
EINVAL :stp->sd_error);
|
|
return;
|
|
}
|
|
s = splstr();
|
|
while (!(mp = getq(RD(stp->sd_wrq)))) {
|
|
if (stp->sd_flag&STRHUP) {
|
|
(void) splx(s);
|
|
u.u_error = ENXIO;
|
|
return;
|
|
}
|
|
if (strwaitq(stp, READWAIT, flag)) {
|
|
(void) splx(s);
|
|
return;
|
|
}
|
|
}
|
|
if (mp->b_datap->db_type != M_PASSFP) {
|
|
putbq(RD(stp->sd_wrq), mp);
|
|
(void) splx(s);
|
|
u.u_error = EBADMSG;
|
|
return;
|
|
}
|
|
(void) splx(s);
|
|
srf = (struct strrecvfd *)mp->b_rptr;
|
|
if ((i = ufalloc(0)) < 0) {
|
|
putbq(RD(stp->sd_wrq), mp);
|
|
return;
|
|
}
|
|
u.u_ofile[i] = srf->f.fp;
|
|
srf->f.fd = i;
|
|
bcopy((caddr_t)srf, data, sizeof (struct strrecvfd));
|
|
#ifdef notdef
|
|
/* XXX - can ioctl fail to copyout? */
|
|
if (copyout((caddr_t)srf, (caddr_t)arg,
|
|
sizeof (struct strrecvfd))) {
|
|
u.u_error = EFAULT;
|
|
srf->f.fp = u.u_ofile[i];
|
|
putbq(RD(stp->sd_wrq), mp);
|
|
u.u_ofile[i] = NULL;
|
|
return;
|
|
}
|
|
#endif
|
|
freemsg(mp);
|
|
u.u_rval1 = 0; /* reset value set by ufalloc() */
|
|
return;
|
|
} /* case I_RECVFD */
|
|
} /* switch */
|
|
}
|
|
|
|
/*
|
|
* Replace one streams module that implements an old-style line discipline with
|
|
* another; line disciplines must appear below a compatibility module.
|
|
* Return 0 if not currently a line discipline, 1 otherwise; if "otherwise",
|
|
* "u.u_error" is set on failure. (If return is 0, TIOCSETD should be sent
|
|
* downstream in case somebody there wants to respond to it.)
|
|
*/
|
|
int
|
|
ldreplace(vp, stp, newldisc)
|
|
register struct vnode *vp;
|
|
register struct stdata *stp;
|
|
int newldisc;
|
|
{
|
|
register queue_t *q;
|
|
register int i;
|
|
struct streamtab *current_streamtab, *new_streamtab;
|
|
register int status;
|
|
register int s;
|
|
|
|
if (!ldtab_initialized)
|
|
initialize_ldtab();
|
|
|
|
for (;;) {
|
|
if (vp == u.u_procp->p_sessp->s_vp &&
|
|
(u.u_error = tty_wr_wait(stp, 0)))
|
|
return (1);
|
|
|
|
/*
|
|
* If nobody else is doing opens, pushes, etc., grab exclusive
|
|
* use of the stream to prevent anyone else from doing opens,
|
|
* pushes, etc. Otherwise, wait until they're done, and check
|
|
* foreground/background access again.
|
|
*/
|
|
if (!(stp->sd_flag & STWOPEN))
|
|
break; /* everybody else is done */
|
|
if (sleep((caddr_t)stp, STOPRI|PCATCH)) {
|
|
u.u_error = EINTR;
|
|
return (1);
|
|
}
|
|
if (stp->sd_flag & (STRERR|STRHUP|STPLEX)) {
|
|
u.u_error = ((stp->sd_flag&STPLEX) ?
|
|
EINVAL : stp->sd_error);
|
|
return (1);
|
|
}
|
|
}
|
|
s = splstr();
|
|
stp->sd_flag |= STWOPEN;
|
|
(void) splx(s);
|
|
|
|
if ((q = getcompatqueue(stp)) == NULL ||
|
|
q->q_next == NULL) {
|
|
status = 0; /* stream not set up properly */
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* We found a module below the compatibility module.
|
|
* Now see if it corresponds to a line discipline.
|
|
* We start the search at the first entry, so that
|
|
* we always seem to be in the new line discipline
|
|
* if we're using the regular TTY module.
|
|
*/
|
|
for (i = 1; i < NLDISC; i++) {
|
|
if (ldtab[i].ld_tab != NULL &&
|
|
q->q_next->q_qinfo == ldtab[i].ld_tab->st_wrinit)
|
|
goto found_setd;
|
|
}
|
|
|
|
status = 0; /* module isn't a line discipline */
|
|
goto done;
|
|
|
|
found_setd:
|
|
current_streamtab = ldtab[i].ld_tab;
|
|
if ((i = newldisc) < 0 || i > NLDISC) {
|
|
u.u_error = EINVAL; /* not a valid new line discipline */
|
|
status = 1;
|
|
goto done;
|
|
}
|
|
if ((new_streamtab = ldtab[i].ld_tab) == NULL) {
|
|
u.u_error = EINVAL; /* not a valid new line discipline */
|
|
status = 1;
|
|
goto done;
|
|
}
|
|
if (new_streamtab == current_streamtab) {
|
|
status = 1; /* nothing to do */
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* Pop off old module.
|
|
*/
|
|
if (q->q_next->q_next == NULL ||
|
|
(q->q_next->q_next->q_flag & QREADR)) {
|
|
u.u_error = EINVAL;
|
|
goto done;
|
|
}
|
|
qdetach(RD(q->q_next), 1, 0);
|
|
u.u_error = 0; /* ignore errors */
|
|
|
|
/*
|
|
* Push new module and call its open routine via qattach.
|
|
*/
|
|
i = qattach(new_streamtab, RD(q), vp->v_rdev, 0, MODOPEN);
|
|
if (i == OPENFAIL) {
|
|
if (!u.u_error)
|
|
u.u_error = ENXIO;
|
|
(void) qattach(current_streamtab, RD(q), vp->v_rdev,
|
|
0, MODOPEN);
|
|
}
|
|
status = 1;
|
|
|
|
done:
|
|
s = splstr();
|
|
stp->sd_flag &= ~STWOPEN;
|
|
(void) splx(s);
|
|
wakeup((caddr_t)stp);
|
|
return (status);
|
|
}
|
|
|
|
static void
|
|
dopush(stp, mname, vp, waitflag, oflag)
|
|
register struct stdata *stp;
|
|
char *mname;
|
|
register struct vnode *vp;
|
|
int waitflag;
|
|
int oflag; /* passed in from stropen for autopushes */
|
|
{
|
|
register int unit;
|
|
register int s;
|
|
int i;
|
|
queue_t *q;
|
|
|
|
if (stp->sd_flag & STRHUP) {
|
|
u.u_error = ENXIO;
|
|
return;
|
|
}
|
|
if (stp->sd_pushcnt >= nstrpush) {
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Look up module in fmodsw.
|
|
*/
|
|
if ((i = findmod(mname)) < 0) {
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If "waitflag" is set, wait until we have exclusive use of
|
|
* the stream; i.e., wait until everybody else doing opens, pushes,
|
|
* etc. is done, and then either quit if there is an error or
|
|
* set STWOPEN to lock everybody else out.
|
|
* If "waitflag" is not set, it is presumed that we already have
|
|
* exclusive use of the stream.
|
|
*/
|
|
if (waitflag) {
|
|
while (stp->sd_flag & (STWOPEN|STRCLOSE)) {
|
|
if (sleep((caddr_t)stp, STOPRI|PCATCH)) {
|
|
u.u_error = EINTR;
|
|
return;
|
|
}
|
|
if (stp->sd_flag & (STRERR|STRHUP|STPLEX)) {
|
|
u.u_error = ((stp->sd_flag&STPLEX) ?
|
|
EINVAL : stp->sd_error);
|
|
return;
|
|
}
|
|
}
|
|
s = splstr();
|
|
stp->sd_flag |= STWOPEN;
|
|
(void) splx(s);
|
|
}
|
|
|
|
/*
|
|
* Push new module and call its open routine via qattach.
|
|
*/
|
|
unit = qattach(fmodsw[i].f_str, RD(stp->sd_wrq), vp->v_rdev, 0,
|
|
MODOPEN);
|
|
if (unit == OPENFAIL) {
|
|
if (!u.u_error)
|
|
u.u_error = ENXIO;
|
|
} else {
|
|
/*
|
|
* If controlling tty established, mark the
|
|
* process group and tty vnode pointer.
|
|
*/
|
|
if ((unit & NEWCTTY) && !(oflag & FNOCTTY))
|
|
assign_ctty(vp, stp, 0);
|
|
stp->sd_pushcnt++;
|
|
}
|
|
|
|
/*
|
|
* If flow control is on, don't break it; enable the
|
|
* first back queue with a service procedure.
|
|
*/
|
|
if (RD(stp->sd_wrq)->q_flag & QWANTW) {
|
|
for (q = backq(RD(stp->sd_wrq->q_next));
|
|
q && !q->q_qinfo->qi_srvp; q = backq(q))
|
|
;
|
|
if (q)
|
|
qenable(q);
|
|
}
|
|
|
|
s = splstr();
|
|
stp->sd_flag &= ~STWOPEN;
|
|
(void) splx(s);
|
|
wakeup((caddr_t)stp);
|
|
}
|
|
|
|
static void
|
|
initialize_ldtab()
|
|
{
|
|
register int i;
|
|
register ldinfo *ldtabp;
|
|
|
|
for (ldtabp = &ldtab[0]; ldtabp < &ldtab[NLDISC]; ldtabp++) {
|
|
for (i = 0; i < fmodcnt; i++) {
|
|
if (strncmp(ldtabp->ld_name, fmodsw[i].f_name,
|
|
FMNAMESZ + 1) == 0) {
|
|
ldtabp->ld_tab = fmodsw[i].f_str;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < fmodcnt; i++) {
|
|
if (strcmp(ttcompatname, fmodsw[i].f_name) == 0) {
|
|
ttcompat_qinit = fmodsw[i].f_str->st_wrinit;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ldtab_initialized++;
|
|
}
|
|
|
|
/*
|
|
* Search for a queue belonging to the compatibility module, if there is
|
|
* one.
|
|
*/
|
|
static queue_t *
|
|
getcompatqueue(stp)
|
|
register struct stdata *stp;
|
|
{
|
|
register queue_t *q;
|
|
|
|
q = stp->sd_wrq->q_next;
|
|
do {
|
|
if (ttcompat_qinit == q->q_qinfo)
|
|
break;
|
|
} while ((q = q->q_next) != NULL);
|
|
|
|
return (q);
|
|
}
|
|
|
|
/*
|
|
* Get the name of the module to which a particular queue belongs.
|
|
*/
|
|
static char *
|
|
getmname(q)
|
|
register queue_t *q;
|
|
{
|
|
register int i;
|
|
|
|
for (i = 0; i < fmodcnt; i++) {
|
|
if (fmodsw[i].f_str &&
|
|
fmodsw[i].f_str->st_wrinit == q->q_qinfo)
|
|
return (fmodsw[i].f_name);
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Send an ioctl message downstream and wait for acknowledgement
|
|
*/
|
|
static void
|
|
strdoioctl(stp, strioc, copyflg, flag)
|
|
register struct stdata *stp;
|
|
register struct strioctl *strioc;
|
|
int copyflg;
|
|
int flag;
|
|
{
|
|
register mblk_t *bp;
|
|
register s;
|
|
register struct iocblk *iocbp;
|
|
extern str2time(), str3time();
|
|
|
|
if ((strioc->ic_len < 0) || (strioc->ic_len > strmsgsz)) {
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
|
|
retry:
|
|
/*
|
|
* If the ioctl is a tty ioctl that involves modification,
|
|
* hang if in the background.
|
|
* If it's a TIOCSTI, do extra permission checks up here.
|
|
* We can't do this check on TIOCSSIZE/TIOCSWINSZ; "shelltool"
|
|
* sets the size on the slave side, not the controller side (which
|
|
* is what it should do), and it's not in the same process as
|
|
* the terminal so it would hang if we did the check. In the
|
|
* old system, the check was in effect suppressed for pseudo-
|
|
* ttys, but then the old TIOCSSIZE only worked on pseudo-ttys.
|
|
*/
|
|
switch (strioc->ic_cmd) {
|
|
|
|
case TIOCSTI:
|
|
if (u.u_uid) {
|
|
if ((flag & FREAD) == 0) {
|
|
u.u_error = EPERM;
|
|
return;
|
|
}
|
|
if (u.u_procp->p_sessp->s_vp != stp->sd_vnode) {
|
|
u.u_error = EACCES;
|
|
return;
|
|
}
|
|
}
|
|
|
|
case TIOCSETP:
|
|
case TIOCSETN:
|
|
case TIOCFLUSH:
|
|
case TIOCSETC:
|
|
case TIOCSLTC:
|
|
case TIOCLBIS:
|
|
case TIOCLBIC:
|
|
case TIOCLSET:
|
|
case TIOCSETX:
|
|
case TCSETA:
|
|
case TCSETAF:
|
|
case TCSETAW:
|
|
case TCSETS:
|
|
case TCSETSW:
|
|
case TCSETSF:
|
|
case TCFLSH:
|
|
case TCSBRK:
|
|
case TCXONC:
|
|
if (stp->sd_vnode == u.u_procp->p_sessp->s_vp &&
|
|
(u.u_error = tty_wr_wait(stp, 0))) {
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Allocate the M_IOCTL message after passing the background and
|
|
* permission tests; that way a longjmp out of the sleep in
|
|
* tty_wr_wait (to issue a SIGTTOU) won't cause a storage leak.
|
|
*/
|
|
if (!(bp = allocb((int)sizeof (struct iocblk), BPRI_HI))) {
|
|
if (strwaitbuf((int)sizeof (struct iocblk), BPRI_HI, 1))
|
|
return;
|
|
/*
|
|
* Might have gone into the background; revalidate.
|
|
*/
|
|
goto retry;
|
|
}
|
|
|
|
if (stp->sd_flag & STRHUP) {
|
|
u.u_error = ENXIO;
|
|
freemsg(bp);
|
|
return;
|
|
}
|
|
|
|
iocbp = (struct iocblk *)bp->b_wptr;
|
|
iocbp->ioc_count = strioc->ic_len;
|
|
iocbp->ioc_cmd = strioc->ic_cmd;
|
|
iocbp->ioc_uid = u.u_uid;
|
|
iocbp->ioc_gid = u.u_gid;
|
|
iocbp->ioc_error = 0;
|
|
iocbp->ioc_rval = 0;
|
|
bp->b_datap->db_type = M_IOCTL;
|
|
bp->b_wptr += sizeof (struct iocblk);
|
|
|
|
/*
|
|
* If there is data to copy into ioctl block, do so.
|
|
*/
|
|
if (iocbp->ioc_count && !putiocd(bp, strioc->ic_dp, copyflg)) {
|
|
freemsg(bp);
|
|
return;
|
|
}
|
|
s = splstr();
|
|
|
|
/*
|
|
* Block for up to STRTIMOUT sec if there is a outstanding
|
|
* ioctl for this stream already pending. All processes
|
|
* sleeping here will be awakened as a result of an ACK
|
|
* or NAK being received for the outstanding ioctl, or
|
|
* as a result of the timer expiring on the outstanding
|
|
* ioctl (a failure), or as a result of any waiting
|
|
* process's timer expiring (also a failure).
|
|
*/
|
|
stp->sd_flag |= STR2TIME;
|
|
timeout(str2time, (caddr_t)stp, STRTIMOUT*HZ);
|
|
|
|
while (stp->sd_flag & IOCWAIT) {
|
|
stp->sd_iocwait++;
|
|
if (sleep((caddr_t)&stp->sd_iocwait, STIPRI|PCATCH) ||
|
|
!(stp->sd_flag & STR2TIME)) {
|
|
stp->sd_iocwait--;
|
|
u.u_error = (stp->sd_flag&STR2TIME ? EINTR : ETIME);
|
|
if (!stp->sd_iocwait)
|
|
stp->sd_flag &= ~STR2TIME;
|
|
(void) splx(s);
|
|
untimeout(str2time, (caddr_t)stp);
|
|
freemsg(bp);
|
|
return;
|
|
}
|
|
stp->sd_iocwait--;
|
|
if (stp->sd_flag & (STRHUP|STRERR|STPLEX)) {
|
|
u.u_error = ((stp->sd_flag & STPLEX) ?
|
|
EINVAL : stp->sd_error);
|
|
if (!stp->sd_iocwait)
|
|
stp->sd_flag &= ~STR2TIME;
|
|
(void) splx(s);
|
|
untimeout(str2time, (caddr_t)stp);
|
|
freemsg(bp);
|
|
return;
|
|
}
|
|
}
|
|
untimeout(str2time, (caddr_t)stp);
|
|
if (!stp->sd_iocwait)
|
|
stp->sd_flag &= ~STR2TIME;
|
|
|
|
/*
|
|
* Have control of ioctl mechanism.
|
|
* Send down ioctl packet and wait for
|
|
* response.
|
|
*/
|
|
if (stp->sd_iocblk) {
|
|
freemsg(stp->sd_iocblk);
|
|
stp->sd_iocblk = NULL;
|
|
}
|
|
stp->sd_flag |= IOCWAIT;
|
|
|
|
/*
|
|
* Assign sequence number.
|
|
*/
|
|
stp->sd_iocid = getiocseqno();
|
|
iocbp->ioc_id = stp->sd_iocid;
|
|
|
|
(void) splx(s);
|
|
(*stp->sd_wrq->q_next->q_qinfo->qi_putp)(stp->sd_wrq->q_next, bp);
|
|
if (qready())
|
|
runqueues();
|
|
|
|
|
|
/*
|
|
* Timed wait for acknowledgment. The wait time is limited by the
|
|
* timeout value, which must be a positive integer (number of seconds
|
|
* to wait, or 0 (use default value of STRTIMOUT seconds), or -1
|
|
* (wait forever). This will be awakened either by an ACK/NAK
|
|
* message arriving, the timer expiring, or the timer expiring
|
|
* on another ioctl waiting for control of the mechanism.
|
|
*/
|
|
s = splstr();
|
|
if (strioc->ic_timout >= 0)
|
|
timeout(str3time, (caddr_t)stp,
|
|
(strioc->ic_timout ? strioc->ic_timout: STRTIMOUT) * HZ);
|
|
|
|
stp->sd_flag |= STR3TIME;
|
|
/*
|
|
* If the reply has already arrived, don't sleep. If awakened from
|
|
* the sleep, fail only if the reply has not arrived by then.
|
|
* Otherwise, process the reply.
|
|
*/
|
|
while (!stp->sd_iocblk) {
|
|
if (stp->sd_flag & (STRERR|STPLEX)) {
|
|
u.u_error = ((stp->sd_flag & STPLEX) ?
|
|
EINVAL : stp->sd_error);
|
|
stp->sd_flag &= ~(STR3TIME|IOCWAIT);
|
|
if (strioc->ic_timout >= 0)
|
|
untimeout(str3time, (caddr_t)stp);
|
|
(void) splx(s);
|
|
wakeup((caddr_t)&(stp->sd_iocwait));
|
|
return;
|
|
}
|
|
|
|
if (sleep((caddr_t)stp, STIPRI|PCATCH) ||
|
|
!(stp->sd_flag & STR3TIME)) {
|
|
u.u_error = ((stp->sd_flag&STR3TIME) ? EINTR : ETIME);
|
|
stp->sd_flag &= ~(STR3TIME|IOCWAIT);
|
|
if (stp->sd_iocblk) {
|
|
freemsg(stp->sd_iocblk);
|
|
stp->sd_iocblk = NULL;
|
|
}
|
|
if (strioc->ic_timout >= 0)
|
|
untimeout(str3time, (caddr_t)stp);
|
|
(void) splx(s);
|
|
wakeup((caddr_t)&(stp->sd_iocwait));
|
|
return;
|
|
}
|
|
}
|
|
ASSERT(stp->sd_iocblk);
|
|
bp = stp->sd_iocblk;
|
|
stp->sd_iocblk = NULL;
|
|
stp->sd_flag &= ~(STR3TIME|IOCWAIT);
|
|
if (strioc->ic_timout >= 0)
|
|
untimeout(str3time, (caddr_t)stp);
|
|
(void) splx(s);
|
|
wakeup((caddr_t)&(stp->sd_iocwait));
|
|
|
|
|
|
/*
|
|
* Have received acknowlegment
|
|
*/
|
|
|
|
iocbp = (struct iocblk *)bp->b_rptr;
|
|
switch (bp->b_datap->db_type) {
|
|
case M_IOCACK:
|
|
/*
|
|
* Positive ack
|
|
*/
|
|
|
|
/*
|
|
* set error if indicated
|
|
*/
|
|
if (iocbp->ioc_error) {
|
|
u.u_error = iocbp->ioc_error;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* set return value
|
|
*/
|
|
u.u_rval1 = iocbp->ioc_rval;
|
|
|
|
/*
|
|
* Data may have been returned in ACK message (ioc_count > 0).
|
|
* If so, copy it out to the user's buffer.
|
|
*/
|
|
if (iocbp->ioc_count)
|
|
if (!getiocd(bp, strioc->ic_dp, copyflg))
|
|
break;
|
|
strioc->ic_len = iocbp->ioc_count;
|
|
break;
|
|
|
|
case M_IOCNAK:
|
|
/*
|
|
* Negative ack
|
|
*
|
|
* The only thing to do is set error as specified
|
|
* in neg ack packet
|
|
*/
|
|
u.u_error = (iocbp->ioc_error ? iocbp->ioc_error : EINVAL);
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
freemsg(bp);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Return the next available "ioctl" sequence number.
|
|
* Exported, so that streams modules can send "ioctl" messages downstream
|
|
* from their open routine.
|
|
*/
|
|
int
|
|
getiocseqno()
|
|
{
|
|
return (++ioc_id);
|
|
}
|
|
|
|
/*
|
|
* Get the next message from the read queue. If the message is
|
|
* priority, STRPRI will have been set by strrput(). This flag
|
|
* should be reset only when the entire message at the front of the
|
|
* queue as been consumed.
|
|
*/
|
|
int
|
|
strgetmsg(vp, mctl, mdata, flagp, fmode)
|
|
register struct vnode *vp;
|
|
register struct strbuf *mctl;
|
|
register struct strbuf *mdata;
|
|
register caddr_t flagp;
|
|
int fmode;
|
|
{
|
|
register s;
|
|
register struct stdata *stp;
|
|
register mblk_t *bp, *nbp;
|
|
mblk_t *savemp = NULL;
|
|
mblk_t *savemptail = NULL;
|
|
int rflag;
|
|
int flg = 0;
|
|
int more = 0;
|
|
int n;
|
|
int bcnt;
|
|
char *ubuf;
|
|
|
|
|
|
ASSERT(vp->v_stream);
|
|
stp = vp->v_stream;
|
|
|
|
if (stp->sd_flag & (STRERR|STPLEX)) {
|
|
u.u_error = ((stp->sd_flag&STPLEX) ? EINVAL : stp->sd_error);
|
|
return (0);
|
|
}
|
|
if (copyin(flagp, (caddr_t)&rflag, sizeof (int))) {
|
|
u.u_error = EFAULT;
|
|
return (0);
|
|
}
|
|
if (rflag & (~RS_HIPRI)) {
|
|
u.u_error = EINVAL;
|
|
return (0);
|
|
}
|
|
|
|
s = splstr();
|
|
stp->sd_deficit = 0; /* force wakeup on any input */
|
|
while (((rflag & RS_HIPRI) && !(stp->sd_flag & STRPRI)) ||
|
|
!(bp = getq(RD(stp->sd_wrq)))) {
|
|
/*
|
|
* If STRHUP, return 0 length control and data
|
|
*/
|
|
if (stp->sd_flag & STRHUP) {
|
|
mctl->len = mdata->len = 0;
|
|
if (copyout((caddr_t)&flg, flagp, sizeof (int)))
|
|
u.u_error = EFAULT;
|
|
(void) splx(s);
|
|
return (0);
|
|
}
|
|
if (strwaitq(stp, READWAIT, fmode)) {
|
|
(void) splx(s);
|
|
return (0);
|
|
}
|
|
}
|
|
(void) splx(s);
|
|
|
|
if (bp->b_datap->db_type == M_PASSFP) {
|
|
putbq(RD(stp->sd_wrq), bp);
|
|
u.u_error = EBADMSG;
|
|
return (0);
|
|
}
|
|
|
|
if (qready())
|
|
runqueues();
|
|
|
|
/*
|
|
* Set HIPRI flag if message is priority.
|
|
*/
|
|
if (stp->sd_flag & STRPRI)
|
|
flg |= RS_HIPRI;
|
|
|
|
/*
|
|
* First process PROTO or PCPROTO blocks, if any.
|
|
*/
|
|
if (mctl->maxlen >= 0 && bp && bp->b_datap->db_type != M_DATA) {
|
|
bcnt = mctl->maxlen;
|
|
ubuf = mctl->buf;
|
|
while (bp && bp->b_datap->db_type != M_DATA && bcnt >= 0) {
|
|
if ((n = MIN(bcnt, bp->b_wptr - bp->b_rptr)) &&
|
|
copyout((caddr_t)bp->b_rptr, ubuf, (u_int)n)) {
|
|
u.u_error = EFAULT;
|
|
s = splstr();
|
|
stp->sd_flag &= ~STRPRI;
|
|
(void) splx(s);
|
|
more = 0;
|
|
freemsg(bp);
|
|
goto getmout;
|
|
}
|
|
ubuf += n;
|
|
bp->b_rptr += n;
|
|
if (bp->b_rptr >= bp->b_wptr) {
|
|
nbp = bp;
|
|
bp = bp->b_cont;
|
|
freeb(nbp);
|
|
}
|
|
if ((bcnt -= n) <= 0)
|
|
break;
|
|
}
|
|
mctl->len = mctl->maxlen - bcnt;
|
|
} else
|
|
mctl->len = -1;
|
|
|
|
|
|
if (bp && bp->b_datap->db_type != M_DATA) {
|
|
/*
|
|
* more PROTO blocks in msg
|
|
*/
|
|
more |= MORECTL;
|
|
savemp = bp;
|
|
while (bp && bp->b_datap->db_type!=M_DATA) {
|
|
savemptail = bp;
|
|
bp = bp->b_cont;
|
|
}
|
|
savemptail->b_cont = NULL;
|
|
}
|
|
|
|
/*
|
|
* Now process DATA blocks, if any
|
|
*/
|
|
if (mdata->maxlen >= 0 && bp) {
|
|
bcnt = mdata->maxlen;
|
|
ubuf = mdata->buf;
|
|
while (bp && bcnt >= 0) {
|
|
if ((n = MIN(bcnt, bp->b_wptr - bp->b_rptr)) &&
|
|
copyout((caddr_t)bp->b_rptr, ubuf, (u_int)n)) {
|
|
u.u_error = EFAULT;
|
|
s = splstr();
|
|
stp->sd_flag &= ~STRPRI;
|
|
(void) splx(s);
|
|
more = 0;
|
|
freemsg(bp);
|
|
goto getmout;
|
|
}
|
|
ubuf += n;
|
|
bp->b_rptr += n;
|
|
if (bp->b_rptr >= bp->b_wptr) {
|
|
nbp = bp;
|
|
bp = bp->b_cont;
|
|
freeb(nbp);
|
|
}
|
|
if ((bcnt -= n) <= 0)
|
|
break;
|
|
}
|
|
mdata->len = mdata->maxlen - bcnt;
|
|
} else
|
|
mdata->len = -1;
|
|
|
|
if (bp) { /* more data blocks in msg */
|
|
more |= MOREDATA;
|
|
if (savemp)
|
|
savemptail->b_cont = bp;
|
|
else
|
|
savemp = bp;
|
|
}
|
|
|
|
s = splstr();
|
|
if (savemp)
|
|
putbq(RD(stp->sd_wrq), savemp);
|
|
else
|
|
stp->sd_flag &= ~STRPRI;
|
|
(void) splx(s);
|
|
|
|
if (copyout((caddr_t)&flg, flagp, sizeof (int)))
|
|
u.u_error = EFAULT;
|
|
|
|
/*
|
|
* Getmsg cleanup processing - if the state of the queue has changed
|
|
* some signals may need to be sent and/or poll awakened.
|
|
*/
|
|
getmout:
|
|
while ((bp=RD(stp->sd_wrq)->q_first) && (bp->b_datap->db_type==M_SIG)) {
|
|
bp = getq(RD(stp->sd_wrq));
|
|
switch (*bp->b_rptr) {
|
|
case SIGPOLL:
|
|
if (stp->sd_sigflags & S_MSG)
|
|
strsendsig(stp->sd_siglist, S_MSG);
|
|
break;
|
|
|
|
default:
|
|
if (stp->sd_pgrp)
|
|
gsignal(stp->sd_pgrp, (int)*bp->b_rptr);
|
|
break;
|
|
}
|
|
freemsg(bp);
|
|
if (qready())
|
|
runqueues();
|
|
}
|
|
/*
|
|
* If we have just received a high priority message and a
|
|
* regular message is now at the front of the queue, send
|
|
* signals in S_INPUT processes and wake up processes polling
|
|
* on POLLIN or selecting on input.
|
|
*/
|
|
if (RD(stp->sd_wrq)->q_first &&
|
|
!(stp->sd_flag & STRPRI) && (flg & RS_HIPRI)) {
|
|
s = splstr();
|
|
if (stp->sd_selr) {
|
|
selwakeup(stp->sd_selr,
|
|
(stp->sd_flag & RCOLL) != 0);
|
|
stp->sd_selr = 0;
|
|
stp->sd_flag &= ~RCOLL;
|
|
}
|
|
if (stp->sd_sigflags & S_INPUT)
|
|
strsendsig(stp->sd_siglist, S_INPUT);
|
|
if (stp->sd_pollflags & POLLIN)
|
|
strwakepoll(stp, POLLIN);
|
|
(void) splx(s);
|
|
}
|
|
return (more);
|
|
}
|
|
|
|
|
|
/*
|
|
* Put a message downstream
|
|
*/
|
|
strputmsg(vp, mctl, mdata, flag, fmode)
|
|
register struct vnode *vp;
|
|
register struct strbuf *mctl;
|
|
register struct strbuf *mdata;
|
|
register flag;
|
|
int fmode;
|
|
{
|
|
register struct stdata *stp;
|
|
mblk_t *mp;
|
|
register s;
|
|
register msgsize;
|
|
short rmin, rmax;
|
|
struct uio uio;
|
|
struct iovec iov;
|
|
|
|
ASSERT(vp->v_stream);
|
|
stp = vp->v_stream;
|
|
|
|
if (stp->sd_flag & (STRHUP|STRERR|STPLEX)) {
|
|
u.u_error = ((stp->sd_flag&STPLEX) ? EINVAL : stp->sd_error);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Check for legal flag value
|
|
*/
|
|
if ((flag & ~RS_HIPRI) || ((flag & RS_HIPRI) && (mctl->len < 0))) {
|
|
u.u_error = EINVAL;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* make sure ctl and data sizes together fall within the limits of the
|
|
* max and min receive packet sizes and do not exceed system limit
|
|
*/
|
|
rmin = stp->sd_wrq->q_next->q_minpsz;
|
|
rmax = stp->sd_wrq->q_next->q_maxpsz;
|
|
if (rmax == INFPSZ)
|
|
rmax = strmsgsz;
|
|
else
|
|
rmax = MIN(rmax, strmsgsz);
|
|
msgsize = mdata->len;
|
|
if (msgsize < 0) {
|
|
msgsize = 0;
|
|
rmin = 0; /* no range check for NULL data part */
|
|
}
|
|
if (msgsize < rmin || msgsize > rmax || mctl->len > strctlsz) {
|
|
u.u_error = ERANGE;
|
|
return;
|
|
}
|
|
|
|
iov.iov_base = mdata->buf;
|
|
iov.iov_len = mdata->len;
|
|
uio.uio_iov = &iov;
|
|
uio.uio_iovcnt = 1;
|
|
uio.uio_offset = 0;
|
|
uio.uio_segflg = UIO_USERSPACE;
|
|
uio.uio_fmode = 0;
|
|
uio.uio_resid = iov.iov_len;
|
|
|
|
s = splstr();
|
|
while (!(flag & RS_HIPRI) && !canput(stp->sd_wrq->q_next)) {
|
|
if (strwaitq(stp, WRITEWAIT, fmode)) {
|
|
(void) splx(s);
|
|
return;
|
|
}
|
|
}
|
|
(void) splx(s);
|
|
|
|
if (!(mp = strmakemsg(mctl, mdata->len, &uio, (int)stp->sd_wroff,
|
|
(long)flag, 0)))
|
|
return;
|
|
|
|
/*
|
|
* Put message downstream
|
|
*/
|
|
(*stp->sd_wrq->q_next->q_qinfo->qi_putp)(stp->sd_wrq->q_next, mp);
|
|
if (qready())
|
|
runqueues();
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Determines whether the necessary conditions are set on a stream
|
|
* for it to be readable, writeable, or have exceptions; used by the "poll"
|
|
* system call.
|
|
*/
|
|
int
|
|
strpoll(stp, events, anyyet)
|
|
register struct stdata *stp;
|
|
short events;
|
|
{
|
|
register retevents = 0;
|
|
register s;
|
|
register struct strevent *sep;
|
|
|
|
if (stp->sd_flag & STPLEX)
|
|
return (POLLNVAL);
|
|
|
|
s = splstr();
|
|
if (stp->sd_flag & STRERR) {
|
|
(void) splx(s);
|
|
return (POLLERR);
|
|
}
|
|
|
|
if (stp->sd_flag & STRHUP)
|
|
retevents |= POLLHUP;
|
|
|
|
if ((events & POLLOUT) &&
|
|
canput(stp->sd_wrq->q_next) &&
|
|
!(stp->sd_flag & STRHUP))
|
|
retevents |= POLLOUT;
|
|
|
|
if ((events & POLLIN) &&
|
|
!(stp->sd_flag & STRPRI) &&
|
|
RD(stp->sd_wrq)->q_first)
|
|
retevents |= POLLIN;
|
|
|
|
if ((events & POLLPRI) &&
|
|
(stp->sd_flag & STRPRI))
|
|
retevents |= POLLPRI;
|
|
|
|
ASSERT((retevents & (POLLPRI|POLLIN)) != (POLLPRI|POLLIN));
|
|
|
|
if (retevents) {
|
|
(void) splx(s);
|
|
pollreset(stp);
|
|
return (retevents);
|
|
}
|
|
|
|
|
|
/*
|
|
* if poll() has not found any events yet, set up event cell
|
|
* to wake up the poll if a requested event occurs on this
|
|
* stream. This will occasionally result in adding an event
|
|
* cell on top of one that's already there, but both will get
|
|
* cleaned up in the pollout: section of poll() in str_syscalls.c.
|
|
* Hence, it is not worth checking for here.
|
|
*/
|
|
if (!anyyet) {
|
|
if (sep = sealloc(SE_SLEEP)) {
|
|
sep->se_procp = u.u_procp;
|
|
sep->se_events = events;
|
|
sep->se_next = stp->sd_pollist;
|
|
stp->sd_pollist = sep;
|
|
stp->sd_pollflags |= events;
|
|
(void) splx(s);
|
|
return (0);
|
|
}
|
|
(void) splx(s);
|
|
pollreset(stp);
|
|
u.u_error = EAGAIN;
|
|
return (0);
|
|
}
|
|
/*
|
|
* If we get here the poll has already found an event but
|
|
* there are no return events for this stream. Remove
|
|
* any event cell for this process from the pollist, and
|
|
* recalculate the pollflags.
|
|
* A cell will exist if this stream was previously scanned
|
|
* in this poll().
|
|
*/
|
|
(void) splx(s);
|
|
pollreset(stp);
|
|
return (retevents);
|
|
}
|
|
|
|
/*
|
|
* Determines whether the necessary conditions are set on a stream
|
|
* for it to be readable, writeable, or have exceptions; used by the "select"
|
|
* system call.
|
|
*/
|
|
int
|
|
strselect(vp, which)
|
|
register struct vnode *vp;
|
|
int which;
|
|
{
|
|
register struct stdata *stp;
|
|
register struct proc *p;
|
|
register int s;
|
|
|
|
ASSERT(vp->v_stream);
|
|
stp = vp->v_stream;
|
|
|
|
if (stp->sd_flag & STPLEX) {
|
|
u.u_error = EINVAL;
|
|
return (0);
|
|
}
|
|
|
|
s = splstr();
|
|
if (stp->sd_flag & STRERR) {
|
|
(void) splx(s);
|
|
u.u_error = stp->sd_error;
|
|
return (0);
|
|
}
|
|
|
|
switch (which) {
|
|
|
|
case FREAD:
|
|
if (!(stp->sd_flag & STRPRI) && RD(stp->sd_wrq)->q_first) {
|
|
/*
|
|
* Regular data is available.
|
|
*/
|
|
(void) splx(s);
|
|
return (1);
|
|
}
|
|
if ((p = stp->sd_selr) && p->p_wchan == (caddr_t)&selwait)
|
|
stp->sd_flag |= RCOLL;
|
|
else
|
|
stp->sd_selr = u.u_procp;
|
|
break;
|
|
|
|
case FWRITE:
|
|
if (canput(stp->sd_wrq->q_next) && !(stp->sd_flag & STRHUP)) {
|
|
(void) splx(s);
|
|
return (1);
|
|
}
|
|
if ((p = stp->sd_selw) && p->p_wchan == (caddr_t)&selwait)
|
|
stp->sd_flag |= WCOLL;
|
|
else
|
|
stp->sd_selw = u.u_procp;
|
|
break;
|
|
case 0:
|
|
if (stp->sd_flag & STRPRI) {
|
|
(void) splx(s);
|
|
return (1);
|
|
}
|
|
if ((p = stp->sd_sele) && p->p_wchan == (caddr_t)&selwait)
|
|
stp->sd_flag |= ECOLL;
|
|
else
|
|
stp->sd_sele = u.u_procp;
|
|
break;
|
|
}
|
|
(void) splx(s);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Attach a stream device or module.
|
|
* qp is a read queue; the new queue goes in so its next
|
|
* read ptr is the argument, and the write queue corresponding
|
|
* to the argument points to this queue. Return the return
|
|
* value given us by the module or driver's open routine, or
|
|
* OPENFAIL if something else altogether goes wrong.
|
|
*/
|
|
static int
|
|
qattach(qinfo, qp, dev, oflag, sflag)
|
|
register struct streamtab *qinfo;
|
|
register queue_t *qp;
|
|
dev_t dev;
|
|
int oflag, sflag;
|
|
{
|
|
register queue_t *rq;
|
|
register int s;
|
|
register int unit;
|
|
|
|
if (!(rq = allocq()))
|
|
return (OPENFAIL);
|
|
|
|
s = splstr();
|
|
rq->q_next = qp;
|
|
WR(rq)->q_next = WR(qp)->q_next;
|
|
if (WR(qp)->q_next) {
|
|
ASSERT(sflag == MODOPEN);
|
|
OTHERQ(WR(qp)->q_next)->q_next = rq;
|
|
}
|
|
WR(qp)->q_next = WR(rq);
|
|
setq(rq, qinfo->st_rdinit, qinfo->st_wrinit);
|
|
rq->q_flag |= QWANTR;
|
|
WR(rq)->q_flag |= QWANTR;
|
|
|
|
/*
|
|
* Open the attached module or driver.
|
|
* The open may sleep, but it must always return here. Therefore,
|
|
* all sleeps must set PCATCH or ignore all signals to avoid a
|
|
* longjump if a signal arrives.
|
|
*/
|
|
unit = (*rq->q_qinfo->qi_qopen)(rq, dev, oflag, sflag);
|
|
if (unit == OPENFAIL) {
|
|
qdetach(rq, 0, 0);
|
|
(void) splx(s);
|
|
return (OPENFAIL);
|
|
}
|
|
(void) splx(s);
|
|
return (unit);
|
|
}
|
|
|
|
/*
|
|
* Detach a stream module or device.
|
|
* If clmode == 1 then the module or driver was opened and its
|
|
* close routine must be called. If clmode == 0, the module
|
|
* or driver was never opened or the open failed, and so its close
|
|
* should not be called.
|
|
*/
|
|
static int
|
|
qdetach(qp, clmode, flag)
|
|
register queue_t *qp;
|
|
{
|
|
register s;
|
|
register queue_t *q, *prev = NULL;
|
|
|
|
if (clmode) {
|
|
if (qready())
|
|
runqueues();
|
|
s = splstr();
|
|
(*qp->q_qinfo->qi_qclose)(qp, (qp->q_next ? 0 : flag));
|
|
/*
|
|
* Check if queues are still enabled, and remove from
|
|
* runlist if necessary.
|
|
*/
|
|
if (qp->q_flag & QENAB || (WR(qp)->q_flag) & QENAB) {
|
|
for (q = qhead; q; q = q->q_link) {
|
|
if (q == qp || q == WR(qp)) {
|
|
if (prev)
|
|
prev->q_link = q->q_link;
|
|
else
|
|
qhead = q->q_link;
|
|
if (q == qtail)
|
|
qtail = prev;
|
|
}
|
|
prev = q;
|
|
}
|
|
}
|
|
flushq(qp, FLUSHALL);
|
|
flushq(WR(qp), FLUSHALL);
|
|
} else
|
|
s = splstr();
|
|
|
|
if (WR(qp)->q_next)
|
|
backq(qp)->q_next = qp->q_next;
|
|
if (qp->q_next)
|
|
backq(WR(qp))->q_next = WR(qp)->q_next;
|
|
freeq(qp);
|
|
(void) splx(s);
|
|
}
|
|
|
|
|
|
/*
|
|
* Are we ignoring or blocking the sig or is the process in vfork?
|
|
*/
|
|
#define SHOULDSTOP(sig) (!(p->p_sigignore & sigmask((sig))) && \
|
|
!(p->p_sigmask & sigmask((sig))) && \
|
|
!(p->p_flag & SVFORK))
|
|
|
|
/*
|
|
* return 0 when the current process can read the tty "stp".
|
|
* if it can't read the tty, return errno why not.
|
|
*/
|
|
tty_rd_wait(stp)
|
|
struct stdata *stp;
|
|
{
|
|
register struct proc *p = u.u_procp;
|
|
|
|
/*
|
|
* while in background
|
|
* if orphaned or don't want to stop
|
|
* EIO
|
|
* signal TTIN and sleep
|
|
*/
|
|
while (p->p_pgrp != stp->sd_pgrp) {
|
|
if ((p->p_flag & SORPHAN) || !SHOULDSTOP(SIGTTIN))
|
|
return (EIO);
|
|
gsignal(p->p_pgrp, SIGTTIN);
|
|
(void) sleep((caddr_t)&lbolt, STIPRI);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* return 0 when the current process can write the tty "stp".
|
|
* if it can't write the tty, return errno why not.
|
|
* if flg is set, check for TOSTOP as well as normal checks.
|
|
*/
|
|
tty_wr_wait(stp, flg)
|
|
struct stdata *stp;
|
|
{
|
|
register struct proc *p = u.u_procp;
|
|
|
|
/*
|
|
* while in background
|
|
* if checking tostop flag and tostop clear OR
|
|
* don't want to stop
|
|
* do the write
|
|
* if orphaned
|
|
* EIO
|
|
* signal TTOU and sleep
|
|
*/
|
|
while (p->p_pgrp != stp->sd_pgrp) {
|
|
if ((flg && !(stp->sd_flag & STRTOSTOP)) ||
|
|
!SHOULDSTOP(SIGTTOU))
|
|
return (0);
|
|
if (p->p_flag & SORPHAN)
|
|
return (EIO);
|
|
gsignal(p->p_pgrp, SIGTTOU);
|
|
(void) sleep((caddr_t)&lbolt, STOPRI);
|
|
}
|
|
return (0);
|
|
}
|
|
#undef SHOULDSTOP(stp)
|
|
|
|
|
|
/*
|
|
* This function is placed in the callout table to wake up a process
|
|
* waiting to close a stream that has not completely drained.
|
|
*/
|
|
static int
|
|
strtime(stp)
|
|
struct stdata *stp;
|
|
{
|
|
|
|
if (stp->sd_flag & STRTIME) {
|
|
wakeup((caddr_t)stp->sd_wrq);
|
|
stp->sd_flag &= ~STRTIME;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function is placed in the callout table to wake up all
|
|
* processes waiting to send an ioctl down a particular stream,
|
|
* as well as the process whose ioctl is still outstanding. The
|
|
* process placing this function in the callout table will remove
|
|
* it if he gets control of the ioctl mechanism for the stream -
|
|
* this should only run if there is a failure. This wakes up
|
|
* the same processes as str3time below.
|
|
*/
|
|
static int
|
|
str2time(stp)
|
|
struct stdata *stp;
|
|
{
|
|
|
|
if (stp->sd_flag & STR2TIME) {
|
|
wakeup((caddr_t)&stp->sd_iocwait);
|
|
stp->sd_flag &= ~STR2TIME;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function is placed on the callout table to wake up the
|
|
* the process that has an outstanding ioctl waiting acknowledgement
|
|
* on a stream, as well as any processes waiting to send their
|
|
* own ioctl messages. It should be removed from the callout table
|
|
* when the acknowledgement arrives. If this function runs, it
|
|
* is the result of a failure. This wakes up the same processes
|
|
* as str2time above.
|
|
*/
|
|
static int
|
|
str3time(stp)
|
|
struct stdata *stp;
|
|
{
|
|
|
|
if (stp->sd_flag & STR3TIME) {
|
|
wakeup((caddr_t)stp);
|
|
stp->sd_flag &= ~STR3TIME;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Put ioctl data from kernel or user buffer to message.
|
|
* Return 0 for failure, 1 for success.
|
|
*/
|
|
static int
|
|
putiocd(bp, arg, copymode)
|
|
register mblk_t *bp;
|
|
caddr_t arg;
|
|
int copymode;
|
|
{
|
|
register mblk_t *tmp;
|
|
register int count, n;
|
|
|
|
count = ((struct iocblk *)bp->b_rptr)->ioc_count;
|
|
|
|
/*
|
|
* strdoioctl validates ioc_count, so if this assert fails it
|
|
* cannot be due to user error.
|
|
*/
|
|
ASSERT(count >= 0);
|
|
|
|
while (count) {
|
|
n = MIN(MAXIOCBSZ, count);
|
|
if (!(tmp = allocb(n, BPRI_HI))) {
|
|
u.u_error = EAGAIN;
|
|
return (0);
|
|
}
|
|
switch (copymode) {
|
|
|
|
case K_TO_K:
|
|
case K_TO_K_BOTH:
|
|
bcopy((caddr_t)arg, (caddr_t)tmp->b_wptr, (u_int)n);
|
|
break;
|
|
|
|
case U_TO_K:
|
|
if (copyin((caddr_t)arg, (caddr_t)tmp->b_wptr,
|
|
(u_int)n)) {
|
|
freeb(tmp);
|
|
u.u_error = EFAULT;
|
|
return (0);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
freeb(tmp);
|
|
return (0);
|
|
}
|
|
arg += n;
|
|
tmp->b_datap->db_type = M_DATA;
|
|
tmp->b_wptr += n;
|
|
count -= n;
|
|
bp = (bp->b_cont = tmp);
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Get ioctl data from message into kernel or user buffer.
|
|
* Return 0 for failure, 1 for success.
|
|
*/
|
|
static int
|
|
getiocd(bp, arg, copymode)
|
|
register mblk_t *bp;
|
|
caddr_t arg;
|
|
int copymode;
|
|
{
|
|
register int count, n;
|
|
|
|
count = ((struct iocblk *)bp->b_rptr)->ioc_count;
|
|
ASSERT(count >= 0);
|
|
|
|
for (bp = bp->b_cont;
|
|
bp && count; count -= n, bp = bp->b_cont, arg += n) {
|
|
n = MIN(count, bp->b_wptr - bp->b_rptr);
|
|
switch (copymode) {
|
|
|
|
case K_TO_K_BOTH:
|
|
bcopy((caddr_t)bp->b_rptr, (caddr_t)arg, (u_int)n);
|
|
break;
|
|
|
|
case K_TO_K:
|
|
case U_TO_K:
|
|
if (copyout((caddr_t)bp->b_rptr, (caddr_t)arg,
|
|
(u_int)n)) {
|
|
u.u_error = EFAULT;
|
|
return (0);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
return (0);
|
|
}
|
|
}
|
|
ASSERT(count==0);
|
|
return (1);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* allocate a linkblk table entry for the triple:
|
|
* (write queue of bottom module of top stream, write queue of stream head of
|
|
* bottom stream, file table index number)
|
|
*
|
|
* linkblk table entries are freed by nulling the l_qbot field.
|
|
*/
|
|
static struct linkblk *
|
|
alloclink(qup, qdown, index)
|
|
queue_t *qup, *qdown;
|
|
int index;
|
|
{
|
|
register struct linkblk *linkblkp;
|
|
|
|
for (linkblkp = &linkblk[0]; linkblkp < &linkblk[nmuxlink]; linkblkp++)
|
|
if (!linkblkp->l_qbot) {
|
|
linkblkp->l_qtop = qup;
|
|
linkblkp->l_qbot = qdown;
|
|
linkblkp->l_index = index;
|
|
return (linkblkp);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
* Check for a potential linking cycle.
|
|
* Qup is the upper queue in a multiplexor that is going to be linked.
|
|
* Qdown is initially the queue to be linked below the multiplexor.
|
|
* Linkcycle() is called to recursively scan the tree of links
|
|
* rooted at qdown to determine if qup is contained in that tree.
|
|
*/
|
|
static int
|
|
linkcycle(qup, qdown)
|
|
register queue_t *qup, *qdown;
|
|
{
|
|
register struct linkblk *linkblkp;
|
|
|
|
for (linkblkp = &linkblk[0]; linkblkp < &linkblk[nmuxlink]; linkblkp++){
|
|
/*
|
|
* The first clause of the test verifies that the linkblk
|
|
* we're checking is actually in use.
|
|
*/
|
|
if (linkblkp->l_qbot != NULL && linkblkp->l_qtop == qdown)
|
|
if ((linkblkp->l_qbot == qup) ||
|
|
linkcycle(qup, linkblkp->l_qbot))
|
|
return (1);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
|
|
/*
|
|
* find linkblk table entry corresponding to triple.
|
|
* A NULL parameter means any value matches that member of the triple.
|
|
* Return pointer to linkblk entry.
|
|
*/
|
|
struct linkblk *
|
|
findlinks(qup, qdown, index)
|
|
register queue_t *qup, *qdown;
|
|
int index;
|
|
{
|
|
register struct linkblk *linkblkp;
|
|
|
|
ASSERT(qup || qdown || index);
|
|
|
|
for (linkblkp = &linkblk[0]; linkblkp < &linkblk[nmuxlink]; linkblkp++){
|
|
if (linkblkp->l_qbot &&
|
|
(!qup || (qup == linkblkp->l_qtop)) &&
|
|
(!qdown || (qdown == linkblkp->l_qbot)) &&
|
|
(!index || (index == linkblkp->l_index)))
|
|
return (linkblkp);
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
* given a queue ptr, follow the chain of q_next pointers until you reach the
|
|
* last queue on the chain and return it
|
|
*/
|
|
queue_t *
|
|
getendq(q)
|
|
register queue_t *q;
|
|
{
|
|
while (q->q_next)
|
|
q = q->q_next;
|
|
return (q);
|
|
}
|
|
|
|
|
|
/*
|
|
* unlink a multiplexer link. Stp is the controlling stream for the
|
|
* link, fpdown is the file pointer for the lower stream, and
|
|
* linkblkp points to the link's entry in the linkblk table.
|
|
*/
|
|
static int
|
|
munlinkone(stp, fpdown, linkblkp, cflag)
|
|
struct stdata *stp;
|
|
struct file *fpdown;
|
|
struct linkblk *linkblkp;
|
|
int cflag;
|
|
{
|
|
register int s;
|
|
struct strioctl strioc;
|
|
struct stdata *stpdown;
|
|
queue_t *rq;
|
|
|
|
strioc.ic_cmd = I_UNLINK;
|
|
strioc.ic_timout = 0;
|
|
strioc.ic_len = sizeof (struct linkblk);
|
|
strioc.ic_dp = (char *) linkblkp;
|
|
|
|
strdoioctl(stp, &strioc, K_TO_K, 0);
|
|
|
|
/*
|
|
* If there was an error and this is not called via strclose,
|
|
* return to the user. Otherwise, pretend there was no error
|
|
* and close the link.
|
|
*/
|
|
if (u.u_error) {
|
|
if (cflag) {
|
|
log(LOG_WARNING,
|
|
"munlink: could not perform unlink ioctl, closing anyway\n");
|
|
u.u_error = 0;
|
|
/* allows strdoioctl() to work */
|
|
s = splstr();
|
|
stp->sd_flag &= ~STRERR;
|
|
(void) splx(s);
|
|
} else
|
|
return (-1);
|
|
}
|
|
|
|
stpdown = ((struct vnode *)(fpdown->f_data))->v_stream;
|
|
s = splstr();
|
|
stpdown->sd_flag &= ~STPLEX;
|
|
(void) splx(s);
|
|
rq = RD(stpdown->sd_wrq);
|
|
setq(rq, &strdata, &stwdata);
|
|
rq->q_ptr = WR(rq)->q_ptr = (caddr_t)stpdown;
|
|
linkblkp->l_qbot = NULL;
|
|
closef(fpdown);
|
|
return (0);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* unlink all multiplexer links for which stp is the controlling stream.
|
|
*/
|
|
static void
|
|
munlinkall(stp, cflag)
|
|
register struct stdata *stp;
|
|
int cflag;
|
|
{
|
|
|
|
register struct file *fpdown;
|
|
register struct linkblk *linkblkp;
|
|
register queue_t *qup;
|
|
|
|
qup = getendq(stp->sd_wrq);
|
|
while (linkblkp = findlinks(qup, (queue_t *)NULL, 0)) {
|
|
fpdown = &file[linkblkp->l_index];
|
|
ASSERT((fpdown >= file) && (fpdown < fileNFILE));
|
|
if (munlinkone(stp, fpdown, linkblkp, cflag) == -1) {
|
|
ASSERT(0);
|
|
return;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Set the interface values for a pair of queues (qinit structure,
|
|
* packet sizes, water marks).
|
|
*/
|
|
void
|
|
setq(rq, rinit, winit)
|
|
queue_t *rq;
|
|
struct qinit *rinit, *winit;
|
|
{
|
|
register queue_t *wq;
|
|
register int s;
|
|
|
|
wq = WR(rq);
|
|
s = splstr();
|
|
rq->q_qinfo = rinit;
|
|
rq->q_hiwat = rinit->qi_minfo->mi_hiwat;
|
|
rq->q_lowat = rinit->qi_minfo->mi_lowat;
|
|
rq->q_minpsz = rinit->qi_minfo->mi_minpsz;
|
|
rq->q_maxpsz = rinit->qi_minfo->mi_maxpsz;
|
|
wq->q_qinfo = winit;
|
|
wq->q_hiwat = winit->qi_minfo->mi_hiwat;
|
|
wq->q_lowat = winit->qi_minfo->mi_lowat;
|
|
wq->q_minpsz = winit->qi_minfo->mi_minpsz;
|
|
wq->q_maxpsz = winit->qi_minfo->mi_maxpsz;
|
|
(void) splx(s);
|
|
}
|
|
|
|
/*
|
|
* Make a protocol message given control and data buffers
|
|
*/
|
|
mblk_t *
|
|
strmakemsg(mctl, count, uio, wroff, flag, nowait)
|
|
register struct strbuf *mctl;
|
|
int count;
|
|
struct uio *uio;
|
|
int wroff;
|
|
long flag;
|
|
int nowait;
|
|
{
|
|
register mblk_t *mp = NULL;
|
|
register mblk_t *bp;
|
|
caddr_t base;
|
|
int pri;
|
|
int msgtype;
|
|
int offlg = 0;
|
|
|
|
if (flag & RS_HIPRI)
|
|
pri = BPRI_MED;
|
|
else
|
|
pri = BPRI_LO;
|
|
|
|
/*
|
|
* Create control part of message, if any
|
|
*/
|
|
if (mctl != NULL && mctl->len >= 0) {
|
|
register int ctlcount;
|
|
|
|
if (flag & RS_HIPRI)
|
|
msgtype = M_PCPROTO;
|
|
else
|
|
msgtype = M_PROTO;
|
|
|
|
ctlcount = mctl->len;
|
|
base = mctl->buf;
|
|
|
|
/*
|
|
* range checking has already been done, simply try
|
|
* to allocate a message block for the ctl part.
|
|
*/
|
|
while (!(bp = allocb(ctlcount, pri))) {
|
|
if (nowait) {
|
|
u.u_error = EWOULDBLOCK;
|
|
return (NULL);
|
|
}
|
|
if (strwaitbuf(ctlcount, pri, 1))
|
|
return (NULL);
|
|
}
|
|
|
|
bp->b_datap->db_type = msgtype;
|
|
if (copyin(base, (caddr_t)bp->b_wptr, (u_int)ctlcount)) {
|
|
u.u_error = EFAULT;
|
|
freeb(bp);
|
|
return (NULL);
|
|
}
|
|
bp->b_wptr += ctlcount;
|
|
mp = bp;
|
|
}
|
|
|
|
/*
|
|
* Create data part of message, if any.
|
|
*
|
|
* This code relies on strmsgsz being positive, so that it constrains
|
|
* the maximum message size. If we change the system to allow
|
|
* unbounded sizes, then the code below should be changed to fragment
|
|
* the message through the b_cont field, probably with each chunk no
|
|
* larger than the system page size.
|
|
*/
|
|
if (count >= 0) {
|
|
register int size;
|
|
|
|
size = count + (offlg ? 0 : wroff);
|
|
while ((bp = allocb(size, pri)) == NULL) {
|
|
if (nowait) {
|
|
freemsg(mp);
|
|
u.u_error = EWOULDBLOCK;
|
|
return (NULL);
|
|
}
|
|
if (strwaitbuf(size, pri, 1)) {
|
|
freemsg(mp);
|
|
return (NULL);
|
|
}
|
|
}
|
|
|
|
if (wroff && !offlg++ &&
|
|
(wroff < bp->b_datap->db_lim - bp->b_wptr)) {
|
|
bp->b_rptr += wroff;
|
|
bp->b_wptr += wroff;
|
|
}
|
|
size = MIN(count, bp->b_datap->db_lim - bp->b_wptr);
|
|
if (size) {
|
|
u.u_error = uiomove((caddr_t)bp->b_wptr, size,
|
|
UIO_WRITE, uio);
|
|
if (u.u_error) {
|
|
freeb(bp);
|
|
freemsg(mp);
|
|
return (NULL);
|
|
}
|
|
}
|
|
bp->b_wptr += size;
|
|
count -= size;
|
|
if (!mp)
|
|
mp = bp;
|
|
else
|
|
linkb(mp, bp);
|
|
}
|
|
return (mp);
|
|
}
|
|
|
|
/*
|
|
* Wait for a buffer to become available. Return 1 if not able to wait,
|
|
* 0 if buffer is probably there. 'ncalls' is # of bufcall() attempts.
|
|
*
|
|
* XXX: The ncalls argument should now be obsolete, given that the class-based
|
|
* buffer allocation method is gone, but rather than chase down all
|
|
* callers, we leave the argument in place for now.
|
|
*/
|
|
/* ARGSUSED */
|
|
int
|
|
strwaitbuf(size, pri, ncalls)
|
|
int size, pri, ncalls;
|
|
{
|
|
register int s;
|
|
|
|
/* spl: else may set process running before it goes to sleep */
|
|
s = splstr();
|
|
if (!bufcall((u_int)size, pri, wakeup, (long)&(u.u_procp->p_flag))) {
|
|
(void) splx(s);
|
|
u.u_error = ENOSR;
|
|
return (1);
|
|
}
|
|
|
|
if (sleep((caddr_t)&(u.u_procp->p_flag), STOPRI|PCATCH)) {
|
|
(void) splx(s);
|
|
strunbcall(size, u.u_procp);
|
|
u.u_error = EINTR;
|
|
return (1);
|
|
}
|
|
(void) splx(s);
|
|
strunbcall(size, u.u_procp);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Remove a setrun for the given process from the bufcall list for
|
|
* the given buffer size. 'size' can be -1 for externally-supplied buffers.
|
|
*/
|
|
/* ARGSUSED */
|
|
void
|
|
strunbcall(size, p)
|
|
int size;
|
|
struct proc *p;
|
|
{
|
|
/*
|
|
* This routine is now a no-op. We no longer use setrun as the
|
|
* bufcall function for strwaitq et al, but instead use wakeup. Since
|
|
* doing redundant wakeups is harmless (albeit potentially
|
|
* inefficient), it's not worthwhile trying to purge these bufcalls.
|
|
*/
|
|
}
|
|
|
|
/*
|
|
* This function waits for a read or write event to happen on a stream.
|
|
*
|
|
* flag dictates how to wait. It is composed as follows. Exactly one of
|
|
* READWAIT or WRITEWAIT must be set; the value determines sleep address,
|
|
* priority, etc. Setting NOINTR forces u.u_error to remain unchanged when
|
|
* blocking is disallowed or an interrupt has broken the sleep, so that the
|
|
* caller can return successfully. Setting INTRRESTART arranges to restart
|
|
* the system call if an interrupt breaks the sleep.
|
|
*
|
|
* Return value is 0 for a successfully completed wait and 1 for failure (with
|
|
* no possibility of successful retry).
|
|
*/
|
|
static int
|
|
strwaitq(stp, flag, fmode)
|
|
register struct stdata *stp;
|
|
int flag;
|
|
int fmode;
|
|
{
|
|
register int s;
|
|
int slpflg, slppri, errs;
|
|
caddr_t slpadr;
|
|
|
|
/*
|
|
* Check for nonblocking i/o. If any of the variations are in effect,
|
|
* the call returns; respecting the NOINTR setting.
|
|
*/
|
|
if (fmode & FNONBIO) {
|
|
if (!(flag & NOINTR))
|
|
u.u_error = EAGAIN;
|
|
return (1);
|
|
}
|
|
/*
|
|
* 4.2BSD-style non-blocking I/O (no need to test for FNDELAY,
|
|
* if it's set then NBIO is also set in sd_flag).
|
|
*/
|
|
if (stp->sd_flag & NBIO) {
|
|
if (!(flag & NOINTR))
|
|
u.u_error = EWOULDBLOCK;
|
|
return (1);
|
|
}
|
|
if (fmode & FNBIO) {
|
|
if (!(flag & NOINTR))
|
|
u.u_error = EAGAIN;
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Set sleep address, priority, etc.
|
|
*/
|
|
if (flag & READWAIT) {
|
|
slpflg = RSLEEP;
|
|
slpadr = (caddr_t)RD(stp->sd_wrq);
|
|
slppri = STIPRI;
|
|
errs = STRERR|STPLEX;
|
|
} else {
|
|
slpflg = WSLEEP;
|
|
slpadr = (caddr_t)stp->sd_wrq;
|
|
slppri = STOPRI;
|
|
errs = STRERR|STRHUP|STPLEX;
|
|
}
|
|
|
|
s = splstr();
|
|
stp->sd_flag |= slpflg;
|
|
if (sleep(slpadr, slppri|PCATCH)) {
|
|
stp->sd_flag &= ~slpflg;
|
|
(void) splx(s);
|
|
wakeup(slpadr);
|
|
if (!(flag & NOINTR)) {
|
|
/*
|
|
* NOINTR not set; therefore, either set error or
|
|
* restart.
|
|
*/
|
|
if (!(flag & INTRRESTART) ||
|
|
(u.u_sigintr & sigmask(u.u_procp->p_cursig)))
|
|
u.u_error = EINTR;
|
|
else
|
|
u.u_eosys = RESTARTSYS;
|
|
}
|
|
return (1);
|
|
}
|
|
(void) splx(s);
|
|
if (stp->sd_flag & errs) {
|
|
u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
|
|
return (1);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
strvtime(stp)
|
|
register struct stdata *stp;
|
|
{
|
|
stp->sd_flag &= ~CLKRUNNING;
|
|
if (stp->sd_vtime == 0)
|
|
return; /* VTIME not in effect */
|
|
stp->sd_flag |= TIMEDOUT;
|
|
stp->sd_flag &= ~RSLEEP;
|
|
wakeup((caddr_t)RD(stp->sd_wrq));
|
|
}
|