Plan 9 from Bell Labs’s /usr/web/sources/contrib/miller/usb/audio/audiofs.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include <u.h>
#include <libc.h>
#include <thread.h>
#include <auth.h>
#include <fcall.h>
#include <libsec.h>
#include "usb.h"
#include "usbaudio.h"
#include "usbaudioctl.h"

int attachok;

#define STACKSIZE 16*1024

enum
{
	OPERM	= 0x3,		// mask of all permission types in open mode
};

typedef struct Fid Fid;
typedef struct Audioctldata Audioctldata;
typedef struct Worker Worker;

struct Audioctldata
{
	long		offoff;			// offset of the offset for audioctl
	long		values[2][Ncontrol][8];	// last values transmitted
	char	*	s;
	int		ns;
};

enum {
	Busy =	0x01,
	Open =	0x02,
	Eof =		0x04,
};

struct Fid
{
	QLock;
	int		fid;
	Dir	*	dir;
	ushort	flags;
	short	readers;
	void	*	fiddata;		// file specific per-fid data (used for audioctl)
	Fid	*	next;
};

struct Worker
{
	Fid		*fid;
	ushort	tag;
	Fcall		*rhdr;
	Dir		*dir;
	Channel	*eventc;
	Worker	*next;
};

enum {
	// Event channel messages for worker
	Work =	0x01,
	Check =	0x02,
	Flush =	0x03,
};

enum {
	Qdir,
	Qvolume,
	Qaudioctl,
	Qaudiostat,
	Qaudio,
	Qaudioin,
	Nqid,
};

Dir dirs[] = {
	/* Note: Qaudio{in} used as mount point for /dev/usb/%d/ep%ddata only */
[Qdir] =		{0,0,{Qdir, 0,QTDIR},0555|DMDIR,0,0,0,	".",		nil,nil,nil},
[Qvolume] =	{0,0,{Qvolume, 0,QTFILE},0666,0,0,0,	"volume",	nil,nil,nil},
[Qaudioctl] =	{0,0,{Qaudioctl, 0,QTFILE},0666,0,0,0,	"audioctl",nil,nil,nil},
[Qaudiostat] =	{0,0,{Qaudiostat, 0,QTFILE},0666,0,0,0,	"audiostat",nil,nil,nil},
[Qaudio] =	{0,0,{Qaudio, 0,QTFILE},0222,0,0,0,		"audio",	nil,nil,nil},
[Qaudioin] =	{0,0,{Qaudioin, 0,QTFILE},0444,0,0,0,	"audioin",	nil,nil,nil},
};

int	messagesize = 4*1024+IOHDRSZ;
uchar	mdata[8*1024+IOHDRSZ];
uchar	mbuf[8*1024+IOHDRSZ];

Fcall	thdr;
Fcall	rhdr;
Worker *workers;

char srvfile[64], mntdir[64], epdata[64], audiofile[64];
int mfd[2], p[2];
char user[32];
char *srvpost;

Channel *procchan;
Channel *replchan;

Fid *fids;

Fid*		newfid(int);
void		io(void *);
void		usage(void);

extern char *mntpt;

char	*rflush(Fid*), *rauth(Fid*),
	*rattach(Fid*), *rwalk(Fid*),
	*ropen(Fid*), *rcreate(Fid*),
	*rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
	*rremove(Fid*), *rstat(Fid*), *rwstat(Fid*),
	*rversion(Fid*);

char 	*(*fcalls[])(Fid*) = {
	[Tflush]		rflush,
	[Tversion]		rversion,
	[Tauth]		rauth,
	[Tattach]		rattach,
	[Twalk]		rwalk,
	[Topen]		ropen,
	[Tcreate]		rcreate,
	[Tread]		rread,
	[Twrite]		rwrite,
	[Tclunk]		rclunk,
	[Tremove]	rremove,
	[Tstat]		rstat,
	[Twstat]		rwstat,
};

char	Eperm[] =		"permission denied";
char	Enotdir[] =	"not a directory";
char	Enoauth[] =	"no authentication in ramfs";
char	Enotexist[] =	"file does not exist";
char	Einuse[] =		"file in use";
char	Eexist[] =		"file exists";
char	Enotowner[] =	"not owner";
char	Eisopen[] = 	"file already open for I/O";
char	Excl[] = 		"exclusive use file already open";
char	Ename[] = 	"illegal name";
char	Ebadctl[] =	"unknown control message";

int
notifyf(void *, char *s)
{
	if(strncmp(s, "interrupt", 9) == 0)
		return 1;
	return 0;
}

void
post(char *name, char *envname, int srvfd)
{
	int fd;
	char buf[32];

	fd = create(name, OWRITE, attachok?0666:0600);
	if(fd < 0)
		return;
	sprint(buf, "%d",srvfd);
	if(write(fd, buf, strlen(buf)) != strlen(buf))
		sysfatal("srv write");
	close(fd);
	putenv(envname, name);
}

void
serve(void *)
{
	int i;
	ulong t;

	if(pipe(p) < 0)
		sysfatal("pipe failed");
	mfd[0] = p[0];
	mfd[1] = p[0];

	atnotify(notifyf, 1);
	strcpy(user, getuser());
	t = time(nil);
	for (i = 0; i < Nqid; i++){
		dirs[i].uid = user;
		dirs[i].gid = user;
		dirs[i].muid = user;
		dirs[i].atime = t;
		dirs[i].mtime = t;
	}
	if(mntpt == nil){
		snprint(mntdir, sizeof(mntdir), "/dev");
		mntpt = mntdir;
	}

	if(debug)
		fmtinstall('F', fcallfmt);

	procrfork(io, nil, STACKSIZE, RFFDG|RFNAMEG);

	close(p[0]);	/* don't deadlock if child fails */
	if(srvpost){
		sprint(srvfile, "/srv/%s", srvpost);
		remove(srvfile);
		post(srvfile, "usbaudio", p[1]);
	}
	if(mount(p[1], -1, mntpt, MBEFORE, "") < 0)
		sysfatal("mount failed");
	if (endpt[Play] >= 0){
		sprint(epdata, "#U/usb%d/%d/ep%ddata", ad->ctlrno, ad->id, endpt[Play]);
		sprint(audiofile, "%s/audio", mntpt);
		if(bind(epdata, audiofile, MREPL) < 0)
			sysfatal("bind failed");
	}
	if (endpt[Record] >= 0){
		sprint(epdata, "#U/usb%d/%d/ep%ddata", ad->ctlrno, ad->id, endpt[Record]);
		sprint(audiofile, "%s/audioin", mntpt);
		if(bind(epdata, audiofile, MREPL) < 0)
			sysfatal("bind failed");
	}
	threadexits(nil);
}

char*
rversion(Fid*)
{
	Fid *f;

	if(thdr.msize < 256)
		return "max messagesize too small";
	if(thdr.msize < messagesize)
		messagesize = thdr.msize;
	rhdr.msize = messagesize;
	if(strncmp(thdr.version, "9P2000", 6) != 0)
		return "unknown 9P version";
	else
		rhdr.version = "9P2000";
	for(f = fids; f; f = f->next)
		if(f->flags & Busy)
			rclunk(f);
	return nil;
}

char*
rauth(Fid*)
{
	return "usbaudio: no authentication required";
}

char*
rflush(Fid *)
{
	Worker *w;
	int waitflush;

	do {
		waitflush = 0;
		for (w = workers; w; w = w->next)
			if (w->tag == thdr.oldtag){
				waitflush++;
				nbsendul(w->eventc, thdr.oldtag << 16 | Flush);
			}
		if (waitflush)
			sleep(50);
	} while(waitflush);
	if (debug & Dbgproc)
		fprint(2, "flush done on tag %d\n", thdr.oldtag);
	return 0;
}

char*
rattach(Fid *f)
{
	f->flags |= Busy;
	f->dir = &dirs[Qdir];
	rhdr.qid = f->dir->qid;
	if(attachok == 0 && strcmp(thdr.uname, user) != 0)
		return Eperm;
	return 0;
}

static Fid*
doclone(Fid *f, int nfid)
{
	Fid *nf;

	nf = newfid(nfid);
	if(nf->flags & Busy)
		return nil;
	nf->flags |= Busy;
	nf->flags &= ~Open;
	nf->dir = f->dir;
	return nf;
}

char*
dowalk(Fid *f, char *name)
{
	int t;

	if (strcmp(name, ".") == 0)
		return nil;
	if (strcmp(name, "..") == 0){
		f->dir = &dirs[Qdir];
		return nil;
	}
	if(f->dir != &dirs[Qdir])
		return Enotexist;
	for (t = 1; t < Nqid; t++){
		if (t == Qaudio && endpt[Play] < 0)
			continue;
		if (t == Qaudioin && endpt[Record] < 0)
			continue;
		if(strcmp(name, dirs[t].name) == 0){
			f->dir = &dirs[t];
			return nil;
		}
	}
	return Enotexist;
}

char*
rwalk(Fid *f)
{
	Fid *nf;
	char *rv;
	int i;
	Dir *savedir;

	if(f->flags & Open)
		return Eisopen;

	rhdr.nwqid = 0;
	nf = nil;
	savedir = f->dir;
	/* clone if requested */
	if(thdr.newfid != thdr.fid){
		nf = doclone(f, thdr.newfid);
		if(nf == nil)
			return "new fid in use";
		f = nf;
	}

	/* if it's just a clone, return */
	if(thdr.nwname == 0 && nf != nil)
		return nil;

	/* walk each element */
	rv = nil;
	for(i = 0; i < thdr.nwname; i++){
		rv = dowalk(f, thdr.wname[i]);
		if(rv != nil){
			if(nf != nil)	
				rclunk(nf);
			else
				f->dir = savedir;
			break;
		}
		rhdr.wqid[i] = f->dir->qid;
	}
	rhdr.nwqid = i;

	/* we only error out if no walk  */
	if(i > 0)
		rv = nil;

	return rv;
}

Audioctldata *
allocaudioctldata(void)
{
	int i, j, k;
	Audioctldata *a;

	a = emallocz(sizeof(Audioctldata), 1);
	for (i = 0; i < 2; i++)
		for(j=0; j < Ncontrol; j++)
			for(k=0; k < 8; k++)
			a->values[i][j][k] = Undef;
	return a;
}

char *
ropen(Fid *f)
{
	if(f->flags & Open)
		return Eisopen;

	if(f->dir == &dirs[Qaudio] || f->dir == &dirs[Qaudioin])
		return Eperm;
	if(thdr.mode != OREAD && (f->dir->mode & 0x2) == 0)
		return Eperm;
	qlock(f);
	if(f->dir == &dirs[Qaudioctl] && f->fiddata == nil)
		f->fiddata = allocaudioctldata();
	qunlock(f);
	rhdr.iounit = 0;
	rhdr.qid = f->dir->qid;
	f->flags |= Open;
	return nil;
}

char *
rcreate(Fid*)
{
	return Eperm;
}

int
readtopdir(Fid*, uchar *buf, long off, int cnt, int blen)
{
	int i, m, n;
	long pos;

	n = 0;
	pos = 0;
	for (i = 1; i < Nqid; i++){
		if (endpt[Play] < 0 && i == Qaudio)
			continue;
		if (endpt[Record] < 0 && i == Qaudioin)
			continue;
		m = convD2M(&dirs[i], &buf[n], blen-n);
		if(off <= pos){
			if(m <= BIT16SZ || m > cnt)
				break;
			n += m;
			cnt -= m;
		}
		pos += m;
	}
	return n;
}

int
makeaudioctldata(Fid *f)
{
	int rec, ctl, i, different;
	char *p, *e;
	Audiocontrol *c;
	Audioctldata *a;

	if ((a = f->fiddata) == nil)
		sysfatal("fiddata");
	if ((p = a->s) == nil){
		p = emalloc(1024);
		a->s = p;
	}
	e = p + 1024;
	for (rec = 0; rec < 2; rec++)
		for (ctl = 0; ctl < Ncontrol; ctl++) {
			c = &controls[rec][ctl];
			different = 0;
			if (c->chans){
				for (i = 1; i < 8; i++)
					if ((c->chans & 1<<i) && c->value[i] != a->values[rec][ctl][i])
						different = 1;
			}else
				if (c->value[0] != a->values[rec][ctl][0])
					different = 1;
			if (different){
				p = seprint(p, e, "%s %s %A", c->name, rec?"in":"out", c);
				memmove(a->values[rec][ctl], c->value, sizeof c->value);
				if (c->min != Undef){
					p = seprint(p, e, " %ld %ld", c->min, c->max);
					if (c->step != Undef)
						p = seprint(p, e, " %ld", c->step);
				}
				p = seprint(p, e, "\n");
			}
		}
	assert(strlen(a->s) < 1024);
	a->ns = p - a->s;
	return a->ns;
}

void
readproc(void *x)
{
	Fcall *rhdr;
	Audioctldata *a;
	Fid *f;
	uchar *mdata;
	int n, cnt;
	long off;
	Worker *w;
	ulong event;

	w = x;
	mdata = emalloc(8*1024+IOHDRSZ);
	while(event = recvul(w->eventc)){
		if (event != Work)
			continue;
		f = w->fid;
		rhdr = w->rhdr;
		a = f->fiddata;
		off = rhdr->offset;
		cnt = rhdr->count;
		assert(a->offoff == off);
		/* f is already locked */
		for(;;){
			qunlock(f);
			event = recvul(w->eventc);
			qlock(f);
			if (debug & Dbgproc)
				fprint(2, "readproc unblocked fid %d %lld\n", f->fid, f->dir->qid.path);
			switch (event & 0xffff) {
			case Work:
				sysfatal("readproc phase error");
			case Check:
				if (f->fiddata && makeaudioctldata(f) == 0)
					continue;
				break;
			case Flush:
				if ((event >> 16) == rhdr->tag) {
					if (debug & Dbgproc)
						fprint(2, "readproc flushing fid %d, tag %d\n", f->fid, rhdr->tag);
					goto flush;
				}
				continue;
			}
			if (f->fiddata){
				rhdr->data = a->s;
				rhdr->count = a->ns;
				break;
			}
			yield();
		}
		if (rhdr->count > cnt)
			rhdr->count = cnt;
		if (rhdr->count)
			f->flags &= ~Eof;
		if(debug & (Dbgproc|Dbgfs))
			fprint(2, "readproc:->%F\n", rhdr);
		n = convS2M(rhdr, mdata, messagesize);
		if(write(mfd[1], mdata, n) != n)
			sysfatal("mount write");
flush:
		w->tag = NOTAG;
		f->readers--;
		assert(f->readers == 0);
		free(rhdr);
		w->rhdr = nil;
		qunlock(f);
		sendp(procchan, w);
	}
	threadexits(nil);
}

char*
rread(Fid *f)
{
	long off;
	int i, n, cnt, rec;
	char *p;
	static char buf[1024];
	Audiocontrol *c;
	Audioctldata *a;
	Worker *w;

	rhdr.count = 0;
	off = thdr.offset;
	cnt = thdr.count;

	if(cnt > messagesize - IOHDRSZ)
		cnt = messagesize - IOHDRSZ;

	rhdr.data = (char*)mbuf;

	if(f->dir == &dirs[Qdir]){
		n = readtopdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
		rhdr.count = n;
		return nil;
	}

	if(f->dir == &dirs[Qvolume]){
		p = buf;
		n = sizeof buf;
		for (rec = 0; rec < 2; rec++){
			c = &controls[rec][Volume_control];
			if (c->readable){
				i = snprint(p, n, "audio %s %ld\n", rec?"in":"out", (c->min != Undef) ?
					100*(c->value[0]-c->min)/(c->max-c->min) : c->value[0]);
				p+=i; n-=i;
			}
			c = &controls[rec][Treble_control];
			if (c->readable){
				i = snprint(p, n, "treb %s %ld\n", rec?"in":"out", (c->min != Undef) ?
					100*(c->value[0]-c->min)/(c->max-c->min) : c->value[0]);
				p+=i; n-=i;
			}
			c = &controls[rec][Bass_control];
			if (c->readable){
				i = snprint(p, n, "bass %s %ld\n", rec?"in":"out", (c->min != Undef) ?
					100*(c->value[0]-c->min)/(c->max-c->min) : c->value[0]);
				p+=i; n-=i;
			}
			c = &controls[rec][Speed_control];
			if (c->readable){
				i = snprint(p, n, "speed %s %ld\n", rec?"in":"out", c->value[0]);
				p+=i; n-=i;
			}
		}
		n = sizeof buf - n;
		if (off > n)
			rhdr.count = 0;
		else{
			rhdr.data = buf + off;
			rhdr.count = n - off;
			if (rhdr.count > cnt)
				rhdr.count = cnt;
		}
		return nil;
	}

	if(f->dir == &dirs[Qaudioctl]){
		Fcall *hdr;

		qlock(f);
		a = f->fiddata;
		if (off - a->offoff < 0){
			/* there was a seek */
			a->offoff = off;
			a->ns = 0;
		}
		do {
			if (off - a->offoff < a->ns){
				rhdr.data = a->s + (off - a->offoff);
				rhdr.count = a->ns - (off - a->offoff);
				if (rhdr.count > cnt)
					rhdr.count = cnt;
				qunlock(f);
				return nil;
			}
			if (a->offoff != off){
				a->ns = 0;
				a->offoff = off;
				rhdr.count = 0;
				qunlock(f);
				return nil;
			}
		} while (makeaudioctldata(f) != 0);

		assert(a->offoff == off);
		/* Wait for data off line */
		f->readers++;
		w = nbrecvp(procchan);
		if (w == nil){
			w = emallocz(sizeof(Worker), 1);
			w->eventc = chancreate(sizeof(ulong), 1);
			w->next = workers;
			workers = w;
			proccreate(readproc, w, 4096);
		}
		hdr = emalloc(sizeof(Fcall));
		w->fid = f;
		w->tag = thdr.tag;
		assert(w->rhdr == nil);
		w->rhdr = hdr;
		hdr->count = cnt;
		hdr->offset = off;
		hdr->type = thdr.type+1;
		hdr->fid = thdr.fid;
		hdr->tag = thdr.tag;
		sendul(w->eventc, Work);
		return (char*)~0;
	}

	return Eperm;
}

char*
rwrite(Fid *f)
{
	long cnt, value;
	char *lines[2*Ncontrol], *fields[4], *subfields[9], *err, *p;
	int nlines, i, nf, nnf, rec, ctl;
	Audiocontrol *c;
	Worker *w;
	static char buf[256];

	rhdr.count = 0;
	cnt = thdr.count;

	if(cnt > messagesize - IOHDRSZ)
		cnt = messagesize - IOHDRSZ;

	err = nil;
	if(f->dir == &dirs[Qvolume] || f->dir == &dirs[Qaudioctl]){
		thdr.data[cnt] = '\0';
		nlines = getfields(thdr.data, lines, 2*Ncontrol, 1, "\n");
		for(i = 0; i < nlines; i++){
			if (debug) fprint(2, "line: %s\n", lines[i]);
			nf = tokenize(lines[i], fields, 4);
			if (nf == 0)
				continue;
			if (nf == 3){
				if (strcmp(fields[1], "in") == 0 || strcmp(fields[1], "record") == 0)
					rec = 1;
				else if (strcmp(fields[1], "out") == 0 || strcmp(fields[1], "playback") == 0)
					rec = 0;
				else {
					if (debug) fprint(2, "bad1\n");
					return Ebadctl;
				}
			}else if (nf == 2)
				rec = 0;
			else {
				if (debug) fprint(2, "bad2 %d\n", nf);
				return Ebadctl;
			}
			c = nil;
			if (strcmp(fields[0], "audio") == 0)	/* special case */
				fields[0] = "volume";
			for (ctl = 0; ctl < Ncontrol; ctl++){
				c = &controls[rec][ctl];
				if (strcmp(fields[0], c->name) == 0)
					break;
			}
			if (ctl == Ncontrol){
				if (debug) fprint(2, "bad3\n");
				return Ebadctl;
			}
			if (f->dir == &dirs[Qvolume] && ctl != Speed_control && c->min != Undef && c->max != Undef){
				nnf = tokenize(fields[nf-1], subfields, nelem(subfields));
				if (nnf <= 0 || nnf > 8){
					if (debug) fprint(2, "bad4\n");
					return Ebadctl;
				}
				p = buf;
				for (i = 0; i < nnf; i++){
					value = strtol(subfields[i], nil, 0);
					value = ((100 - value) * c->min + value * c->max) / 100;
					if (debug) {
						if (p == buf)
							fprint(2, "rwrite: %s %s '%ld",
								c->name, rec?"record":"playback", value);
						else
							fprint(2, " %ld", value);
					}
					if (p == buf)
						p = seprint(p, buf+sizeof buf, "0x%p %s %s '%ld",
							replchan, c->name, rec?"record":"playback", value);
					else
						p = seprint(p, buf+sizeof buf, " %ld", value);
				}
				if (debug) fprint(2, "'\n");
				seprint(p, buf+sizeof buf, "'");
				chanprint(controlchan, buf);
			} else {
				if (debug) fprint(2, "rwrite: %s %s %q", c->name, rec?"record":"playback", fields[nf-1]);
				chanprint(controlchan, "0x%p %s %s %q", replchan, c->name, rec?"record":"playback", fields[nf-1]);
			}
			p = recvp(replchan);
			if (p){
				if (strcmp(p, "ok") == 0){
					free(p);
					p = nil;
				}
				if (err == nil)
					err = p;
			}
		}
		for (w = workers; w; w = w->next)
			nbsendul(w->eventc, Qaudioctl << 16 | Check);
		rhdr.count = thdr.count;
		return err;
	}
	return Eperm;
}

char *
rclunk(Fid *f)
{
	Audioctldata *a;

	qlock(f);
	f->flags &= ~(Open|Busy);
	assert (f->readers ==0);
	if (f->fiddata){
		a = f->fiddata;
		if (a->s)
			free(a->s);
		free(a);
		f->fiddata = nil;
	}
	qunlock(f);
	return 0;
}

char *
rremove(Fid *)
{
	return Eperm;
}

char *
rstat(Fid *f)
{
	Audioctldata *a;

	if (f->dir == &dirs[Qaudio] && endpt[Play] < 0)
			return Enotexist;
	if (f->dir == &dirs[Qaudioin] && endpt[Record] < 0)
			return Enotexist;
	if (f->dir == &dirs[Qaudioctl]){
		qlock(f);
		if (f->fiddata == nil)
			f->fiddata = allocaudioctldata();
		a = f->fiddata;
		if (a->ns == 0)
			makeaudioctldata(f);
		f->dir->length = a->offoff + a->ns;
		qunlock(f);
	}
	rhdr.nstat = convD2M(f->dir, mbuf, messagesize - IOHDRSZ);
	rhdr.stat = mbuf;
	return 0;
}

char *
rwstat(Fid*)
{
	return Eperm;
}

Fid *
newfid(int fid)
{
	Fid *f, *ff;

	ff = nil;
	for(f = fids; f; f = f->next)
		if(f->fid == fid){
			return f;
		}else if(ff == nil && (f->flags & Busy) == 0)
			ff = f;
	if(ff == nil){
		ff = emallocz(sizeof *ff, 1);
		ff->next = fids;
		fids = ff;
	}
	ff->fid = fid;
	ff->flags &= ~(Busy|Open);
	ff->dir = nil;
	return ff;
}

void
io(void *)
{
	char *err, e[32];
	int n;

	close(p[1]);

	procchan = chancreate(sizeof(Channel*), 8);
	replchan = chancreate(sizeof(char*), 0);
	for(;;){
		/*
		 * reading from a pipe or a network device
		 * will give an error after a few eof reads
		 * however, we cannot tell the difference
		 * between a zero-length read and an interrupt
		 * on the processes writing to us,
		 * so we wait for the error
		 */
		n = read9pmsg(mfd[0], mdata, messagesize);
		if(n == 0)
			continue;
		if(n < 0){
			rerrstr(e, sizeof e);
			if (strcmp(e, "interrupted") == 0){
				if (debug) fprint(2, "read9pmsg interrupted\n");
				continue;
			}
			return;
		}
		if(convM2S(mdata, n, &thdr) == 0)
			continue;

		if(debug & Dbgfs)
			fprint(2, "io:<-%F\n", &thdr);

		rhdr.data = (char*)mdata + messagesize;
		if(!fcalls[thdr.type])
			err = "bad fcall type";
		else
			err = (*fcalls[thdr.type])(newfid(thdr.fid));
		if (err == (char*)~0)
			continue;	/* handled off line */
		if(err){
			rhdr.type = Rerror;
			rhdr.ename = err;
		}else{
			rhdr.type = thdr.type + 1;
			rhdr.fid = thdr.fid;
		}
		rhdr.tag = thdr.tag;
		if(debug & Dbgfs)
			fprint(2, "io:->%F\n", &rhdr);
		n = convS2M(&rhdr, mdata, messagesize);
		if(write(mfd[1], mdata, n) != n)
			sysfatal("mount write");
	}
}

int
newid(void)
{
	int rv;
	static int id;
	static Lock idlock;

	lock(&idlock);
	rv = ++id;
	unlock(&idlock);

	return rv;
}

void
ctlevent(void)
{
	Worker *w;

	for (w = workers; w; w = w->next)
		nbsendul(w->eventc, Qaudioctl << 16 | Check);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.