Esempio n. 1
0
static int32_t
consread(Chan *c, void *buf, int32_t n, int64_t off)
{
	Proc *up = externup();
	uint64_t l;
	Mach *mp;
	char *b, *bp, *s, *e;
	char tmp[512];		/* Qswap is 381 bytes at clu */
	int i, k, id;
	int32_t offset;


	if(n <= 0)
		return n;

	offset = off;
	switch((uint32_t)c->qid.path){
	case Qdir:
		return devdirread(c, buf, n, consdir, nelem(consdir), devgen);

	case Qcons:
		error(Egreg);

	case Qcputime:
		k = offset;
		if(k >= 6*NUMSIZE)
			return 0;
		if(k+n > 6*NUMSIZE)
			n = 6*NUMSIZE - k;
		/* easiest to format in a separate buffer and copy out */
		for(i=0; i<6 && NUMSIZE*i<k+n; i++){
			l = up->time[i];
			if(i == TReal)
				l = sys->ticks - l;
			l = TK2MS(l);
			readnum(0, tmp+NUMSIZE*i, NUMSIZE, l, NUMSIZE);
		}
		memmove(buf, tmp+k, n);
		return n;

	case Qkmesg:
		/*
		 * This is unlocked to avoid tying up a process
		 * that's writing to the buffer.  kmesg.n never
		 * gets smaller, so worst case the reader will
		 * see a slurred buffer.
		 */
		if(off >= kmesg.n)
			n = 0;
		else{
			if(off+n > kmesg.n)
				n = kmesg.n - off;
			memmove(buf, kmesg.buf+off, n);
		}
		return n;

	case Qkprint:
		error(Egreg);

	case Qpgrpid:
		return readnum(offset, buf, n, up->pgrp->pgrpid, NUMSIZE);

	case Qpid:
		return readnum(offset, buf, n, up->pid, NUMSIZE);

	case Qppid:
		return readnum(offset, buf, n, up->parentpid, NUMSIZE);

	case Qtime:
		return readtime(offset, buf, n);

	case Qbintime:
		return readbintime(buf, n);

	case Qhostowner:
		return readstr(offset, buf, n, eve);

	case Qhostdomain:
		return readstr(offset, buf, n, hostdomain);

	case Quser:
		return readstr(offset, buf, n, up->user);

	case Qnull:
		return 0;

	case Qsysstat:
		n = MACHMAX*(NUMSIZE*11+2+1);
		b = smalloc(n + 1);	/* +1 for NUL */
		bp = b;
		e = bp + n;
		for(id = 0; id < MACHMAX; id++)
			if((mp = sys->machptr[id]) != nil && mp->online){
				readnum(0, bp, NUMSIZE, mp->machno, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->cs, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->intr, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->syscall, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->pfault, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->tlbfault, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->tlbpurge, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, sys->load, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE,
					(mp->perf.avg_inidle*100)/mp->perf.period,
					NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE,
					(mp->perf.avg_inintr*100)/mp->perf.period,
					NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, 0, NUMSIZE); /* sched # */
				bp += NUMSIZE;
				bp = strecpy(bp, e, rolename[mp->NIX.nixtype]);
				*bp++ = '\n';
			}
		if(waserror()){
			free(b);
			nexterror();
		}
		n = readstr(offset, buf, n, b);
		free(b);
		poperror();
		return n;

	case Qswap:
		tmp[0] = 0;
		s = seprintpagestats(tmp, tmp + sizeof tmp);
		s = seprintphysstats(s, tmp + sizeof tmp);
		b = buf;
		l = s - tmp;
		i = readstr(offset, b, l, tmp);
		b += i;
		n -= i;
		if(offset > l)
			offset -= l;
		else
			offset = 0;

		return i + mallocreadsummary(c, b, n, offset);

	case Qsysname:
		if(sysname == nil)
			return 0;
		return readstr(offset, buf, n, sysname);

	case Qrandom:
		return randomread(buf, n);

	case Qurandom:
		return urandomread(buf, n);

	case Qdrivers:
		return devtabread(c, buf, n, off);

	case Qzero:
		memset(buf, 0, n);
		return n;

	case Qosversion:
		snprint(tmp, sizeof tmp, "2000");
		n = readstr(offset, buf, n, tmp);
		return n;

	case Qdebug:
		s = seprint(tmp, tmp + sizeof tmp, "locks %lu\n", lockstats.locks);
		s = seprint(s, tmp + sizeof tmp, "glare %lu\n", lockstats.glare);
		s = seprint(s, tmp + sizeof tmp, "inglare %lu\n", lockstats.inglare);
		s = seprint(s, tmp + sizeof tmp, "qlock %lu\n", qlockstats.qlock);
		seprint(s, tmp + sizeof tmp, "qlockq %lu\n", qlockstats.qlockq);
		return readstr(offset, buf, n, tmp);
		break;

	case Qsyscall:
		snprint(tmp, sizeof tmp, "%s", printallsyscalls ? "on" : "off");
		return readstr(offset, buf, n, tmp);
		break;
	default:
		print("consread %#llx\n", c->qid.path);
		error(Egreg);
	}
	return -1;		/* never reached */
}
Esempio n. 2
0
static long consread(struct chan *c, void *buf, long n, int64_t off)
{
    ERRSTACK(1);
    uint32_t l;
#if 0
    Mach *mp;
#endif
    char *b, *bp, ch;
    char tmp[256];				/* must be >= 18*NUMSIZE (Qswap) */
    int i, k, id, send;
    int64_t offset = off;
#if 0
    extern char configfile[];
#endif

    if (n <= 0)
        return n;

    switch ((uint32_t) c->qid.path) {
    case Qdir:
        return devdirread(c, buf, n, consdir, ARRAY_SIZE(consdir), devgen);

    case Qcons:
        qlock(&(&kbd)->qlock);
        if (waserror()) {
            qunlock(&(&kbd)->qlock);
            nexterror();
        }
        while (!qcanread(lineq)) {
            if (qread(kbdq, &ch, 1) == 0)
                continue;
            send = 0;
            if (ch == 0) {
                /* flush output on rawoff -> rawon */
                if (kbd.x > 0)
                    send = !qcanread(kbdq);
            } else if (kbd.raw) {
                kbd.line[kbd.x++] = ch;
                send = !qcanread(kbdq);
            } else {
                switch (ch) {
                case '\b':
                    if (kbd.x > 0)
                        kbd.x--;
                    break;
                case 0x15:	/* ^U */
                    kbd.x = 0;
                    break;
                case '\n':
                case 0x04:	/* ^D */
                    send = 1;
                default:
                    if (ch != 0x04)
                        kbd.line[kbd.x++] = ch;
                    break;
                }
            }
            if (send || kbd.x == sizeof kbd.line) {
                qwrite(lineq, kbd.line, kbd.x);
                kbd.x = 0;
            }
        }
        n = qread(lineq, buf, n);
        qunlock(&(&kbd)->qlock);
        poperror();
        return n;

#if 0
    case Qcputime:
        k = offset;
        if (k >= 6 * NUMSIZE)
            return 0;
        if (k + n > 6 * NUMSIZE)
            n = 6 * NUMSIZE - k;
        /* easiest to format in a separate buffer and copy out */
        for (i = 0; i < 6 && NUMSIZE * i < k + n; i++) {
            l = current->time[i];
            if (i == TReal)
                l = MACHP(0)->ticks - l;
            l = TK2MS(l);
            consreadnum(0, tmp + NUMSIZE * i, NUMSIZE, l, NUMSIZE);
        }
        memmove(buf, tmp + k, n);
        return n;
#endif

    case Qkmesg:
        /*
         * This is unlocked to avoid tying up a process
         * that's writing to the buffer.  kmesg.n never
         * gets smaller, so worst case the reader will
         * see a slurred buffer.
         */
        if (off >= kmesg.n)
            n = 0;
        else {
            if (off + n > kmesg.n)
                n = kmesg.n - off;
            memmove(buf, kmesg.buf + off, n);
        }
        return n;

    case Qkprint:
        return qread(kprintoq, buf, n);

    case Qpgrpid:
        return consreadnum((uint32_t) offset, buf, n, current->pgrp->pgrpid,
                           NUMSIZE);

    case Qpid:
        return consreadnum((uint32_t) offset, buf, n, current->pid,
                           NUMSIZE);

    case Qppid:
        return consreadnum((uint32_t) offset, buf, n, current->ppid,
                           NUMSIZE);

    case Qtime:
        return readtime((uint32_t) offset, buf, n);

    case Qbintime:
        return readbintime(buf, n);

    case Qhostowner:
        return consreadstr((uint32_t) offset, buf, n, eve);

    case Qhostdomain:
        return consreadstr((uint32_t) offset, buf, n, hostdomain);

    case Quser:
        return consreadstr((uint32_t) offset, buf, n, current->user);

    case Qnull:
        return 0;

#if 0
    case Qconfig:
        return consreadstr((uint32_t) offset, buf, n, configfile);

    case Qsysstat:
        b = kzmalloc(conf.nmach * (NUMSIZE * 11 + 1) + 1, 0);	/* +1 for NUL */
        bp = b;
        for (id = 0; id < 32; id++) {
            if (active.machs & (1 << id)) {
                mp = MACHP(id);
                consreadnum(0, bp, NUMSIZE, id, NUMSIZE);
                bp += NUMSIZE;
                consreadnum(0, bp, NUMSIZE, mp->cs, NUMSIZE);
                bp += NUMSIZE;
                consreadnum(0, bp, NUMSIZE, mp->intr, NUMSIZE);
                bp += NUMSIZE;
                consreadnum(0, bp, NUMSIZE, mp->syscall, NUMSIZE);
                bp += NUMSIZE;
                consreadnum(0, bp, NUMSIZE, mp->pfault, NUMSIZE);
                bp += NUMSIZE;
                consreadnum(0, bp, NUMSIZE, mp->tlbfault, NUMSIZE);
                bp += NUMSIZE;
                consreadnum(0, bp, NUMSIZE, mp->tlbpurge, NUMSIZE);
                bp += NUMSIZE;
                consreadnum(0, bp, NUMSIZE, mp->load, NUMSIZE);
                bp += NUMSIZE;
                consreadnum(0, bp, NUMSIZE,
                            (mp->perf.avg_inidle * 100) / mp->perf.period,
                            NUMSIZE);
                bp += NUMSIZE;
                consreadnum(0, bp, NUMSIZE,
                            (mp->perf.avg_inintr * 100) / mp->perf.period,
                            NUMSIZE);
                bp += NUMSIZE;
                *bp++ = '\n';
            }
        }
        if (waserror()) {
            kfree(b);
            nexterror();
        }
        n = consreadstr((uint32_t) offset, buf, n, b);
        kfree(b);
        poperror();
        return n;

    case Qswap:
        snprintf(tmp, sizeof tmp,
                 "%lud memory\n"
                 "%d pagesize\n"
                 "%lud kernel\n"
                 "%lud/%lud user\n"
                 "%lud/%lud swap\n"
                 "%lud/%lud kernel malloc\n"
                 "%lud/%lud kernel draw\n",
                 conf.npage * BY2PG,
                 BY2PG,
                 conf.npage - conf.upages,
                 palloc.user - palloc.freecount, palloc.user,
                 conf.nswap - swapalloc.free, conf.nswap,
                 mainmem->cursize, mainmem->maxsize,
                 imagmem->cursize, imagmem->maxsize);

        return consreadstr((uint32_t) offset, buf, n, tmp);
#endif

    case Qsysname:
        if (sysname == NULL)
            return 0;
        return consreadstr((uint32_t) offset, buf, n, sysname);

    case Qdrivers:
        b = kzmalloc(READSTR, 0);
        if (b == NULL)
            error(ENOMEM, "allocation for /dev/drivers read failed");
        k = 0;
        for (int i = 0; &devtab[i] < __devtabend; i++)
            k += snprintf(b + k, READSTR - k, "#%s\n", devtab[i].name);
        if (waserror()) {
            kfree(b);
            nexterror();
        }
        n = consreadstr((uint32_t) offset, buf, n, b);
        kfree(b);
        poperror();
        return n;

    case Qklog:
        //return qread(klogq, buf, n);
        /* the queue gives us some elasticity for log reading. */
        if (!logqueue)
            logqueue = qopen(1 << 20, 0, 0, 0);
        if (logqueue) {
            int ret;
            /* atomic sets/gets are not that important in this case. */
            reading_kmesg = 1;
            qwrite(logqueue, logbuffer, index);
            index = 0;
            ret = qread(logqueue, buf, n);
            reading_kmesg = 0;
            return ret;
        }
        break;

    case Qzero:
        memset(buf, 0, n);
        return n;

    case Qosversion:
        snprintf(tmp, sizeof tmp, "2000");
        n = consreadstr((uint32_t) offset, buf, n, tmp);
        return n;

    default:
        printd("consread %#llux\n", c->qid.path);
        error(EINVAL, "bad QID in consread");
    }
    return -1;	/* never reached */
}
Esempio n. 3
0
static long
consread(Chan *c, void *buf, long n, vlong off)
{
	ulong l;
	Mach *mp;
	char *b, *bp;
	char tmp[256];		/* must be >= 18*NUMSIZE (Qswap) */
	int i, k, id;
	vlong offset = off;
	extern char configfile[];

	if(n <= 0)
		return n;

	switch((ulong)c->qid.path){
	case Qdir:
		return devdirread(c, buf, n, consdir, nelem(consdir), devgen);

	case Qcons:
		error(Egreg);

	case Qcputime:
		k = offset;
		if(k >= 6*NUMSIZE)
			return 0;
		if(k+n > 6*NUMSIZE)
			n = 6*NUMSIZE - k;
		/* easiest to format in a separate buffer and copy out */
		for(i=0; i<6 && NUMSIZE*i<k+n; i++){
			l = up->time[i];
			if(i == TReal)
				l = MACHP(0)->ticks - l;
			l = TK2MS(l);
			readnum(0, tmp+NUMSIZE*i, NUMSIZE, l, NUMSIZE);
		}
		memmove(buf, tmp+k, n);
		return n;

	case Qkmesg:
		/*
		 * This is unlocked to avoid tying up a process
		 * that's writing to the buffer.  kmesg.n never 
		 * gets smaller, so worst case the reader will
		 * see a slurred buffer.
		 */
		if(off >= kmesg.n)
			n = 0;
		else{
			if(off+n > kmesg.n)
				n = kmesg.n - off;
			memmove(buf, kmesg.buf+off, n);
		}
		return n;
		
	case Qkprint:
		return qread(kprintoq, buf, n);

	case Qpgrpid:
		return readnum((ulong)offset, buf, n, up->pgrp->pgrpid, NUMSIZE);

	case Qpid:
		return readnum((ulong)offset, buf, n, up->pid, NUMSIZE);

	case Qppid:
		return readnum((ulong)offset, buf, n, up->parentpid, NUMSIZE);

	case Qtime:
		return readtime((ulong)offset, buf, n);

	case Qbintime:
		return readbintime(buf, n);

	case Qhostowner:
		return readstr((ulong)offset, buf, n, eve);

	case Qhostdomain:
		return readstr((ulong)offset, buf, n, hostdomain);

	case Quser:
		return readstr((ulong)offset, buf, n, up->user);

	case Qnull:
		return 0;

	case Qconfig:
		return readstr((ulong)offset, buf, n, configfile);

	case Qsysstat:
		b = smalloc(conf.nmach*(NUMSIZE*11+1) + 1);	/* +1 for NUL */
		bp = b;
		for(id = 0; id < 32; id++) {
			if(active.machs & (1<<id)) {
				mp = MACHP(id);
				readnum(0, bp, NUMSIZE, id, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->cs, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->intr, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->syscall, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->pfault, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->tlbfault, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->tlbpurge, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->load, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE,
					(mp->perf.avg_inidle*100)/mp->perf.period,
					NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE,
					(mp->perf.avg_inintr*100)/mp->perf.period,
					NUMSIZE);
				bp += NUMSIZE;
				*bp++ = '\n';
			}
		}
		if(waserror()){
			free(b);
			nexterror();
		}
		n = readstr((ulong)offset, buf, n, b);
		free(b);
		poperror();
		return n;

	case Qswap:
		snprint(tmp, sizeof tmp,
			"%lud memory\n"
			"%d pagesize\n"
			"%lud kernel\n"
			"%lud/%lud user\n"
			"%lud/%lud swap\n"
			"%lud/%lud kernel malloc\n"
			"%lud/%lud kernel draw\n",
			conf.npage*BY2PG,
			BY2PG,
			conf.npage-conf.upages,
			palloc.user-palloc.freecount, palloc.user,
			conf.nswap-swapalloc.free, conf.nswap,
			mainmem->cursize, mainmem->maxsize,
			imagmem->cursize, imagmem->maxsize);

		return readstr((ulong)offset, buf, n, tmp);

	case Qsysname:
		if(sysname == nil)
			return 0;
		return readstr((ulong)offset, buf, n, sysname);

	case Qrandom:
		return randomread(buf, n);

	case Qdrivers:
		b = smalloc(READSTR);
		k = 0;
		for(i = 0; devtab[i] != nil; i++)
			k += snprint(b+k, READSTR-k, "#%C %s\n",
				devtab[i]->dc, devtab[i]->name);
		if(waserror()){
			free(b);
			nexterror();
		}
		n = readstr((ulong)offset, buf, n, b);
		poperror();
		free(b);
		return n;

	case Qzero:
		memset(buf, 0, n);
		return n;
	
	case Qmordor:
		error("one does not simply read from mordor");
		return 0;

	case Qosversion:
		snprint(tmp, sizeof tmp, "2000");
		n = readstr((ulong)offset, buf, n, tmp);
		return n;

	default:
		print("consread %#llux\n", c->qid.path);
		error(Egreg);
	}
	return -1;		/* never reached */
}
Esempio n. 4
0
static long
consread(Chan *c, void *buf, long n, vlong off)
{
	ulong l;
	Mach *mp;
	char *b, *bp, ch;
	char tmp[256];		/* must be >= 18*NUMSIZE (Qswap) */
	int i, k, id, send;
	vlong offset = off;

	if(n <= 0)
		return n;

	switch((ulong)c->qid.path){
	case Qdir:
		return devdirread(c, buf, n, consdir, nelem(consdir), devgen);

	case Qcons:
		qlock(&kbd.lk);
		if(waserror()) {
			qunlock(&kbd.lk);
			nexterror();
		}
		while(!qcanread(lineq)){
			if(qread(kbdq, &ch, 1) == 0)
				continue;
			send = 0;
			if(ch == 0){
				/* flush output on rawoff -> rawon */
				if(kbd.x > 0)
					send = !qcanread(kbdq);
			}else if(kbd.raw){
				kbd.line[kbd.x++] = ch;
				send = !qcanread(kbdq);
			}else{
				switch(ch){
				case '\b':
					if(kbd.x > 0)
						kbd.x--;
					break;
				case 0x15:	/* ^U */
					kbd.x = 0;
					break;
				case '\n':
				case 0x04:	/* ^D */
					send = 1;
				default:
					if(ch != 0x04)
						kbd.line[kbd.x++] = ch;
					break;
				}
			}
			if(send || kbd.x == sizeof kbd.line){
				qwrite(lineq, kbd.line, kbd.x);
				kbd.x = 0;
			}
		}
		n = qread(lineq, buf, n);
		qunlock(&kbd.lk);
		poperror();
		return n;

	case Qcputime:
		k = offset;
		if(k >= 6*NUMSIZE)
			return 0;
		if(k+n > 6*NUMSIZE)
			n = 6*NUMSIZE - k;
		/* easiest to format in a separate buffer and copy out */
		for(i=0; i<6 && NUMSIZE*i<k+n; i++){
			l = up->time[i];
			if(i == TReal)
				l = msec() - l;
			l = TK2MS(l);
			readnum(0, tmp+NUMSIZE*i, NUMSIZE, l, NUMSIZE);
		}
		memmove(buf, tmp+k, n);
		return n;

	case Qkmesg:
		/*
		 * This is unlocked to avoid tying up a process
		 * that's writing to the buffer.  kmesg.n never 
		 * gets smaller, so worst case the reader will
		 * see a slurred buffer.
		 */
		if(off >= kmesg.n)
			n = 0;
		else{
			if(off+n > kmesg.n)
				n = kmesg.n - off;
			memmove(buf, kmesg.buf+off, n);
		}
		return n;
		
	case Qkprint:
		return qread(kprintoq, buf, n);

	case Qpgrpid:
		return readnum((ulong)offset, buf, n, up->pgrp->pgrpid, NUMSIZE);

	case Qpid:
		return readnum((ulong)offset, buf, n, up->pid, NUMSIZE);

	case Qppid:
		return readnum((ulong)offset, buf, n, up->parentpid, NUMSIZE);

	case Qtime:
		return readtime((ulong)offset, buf, n);

	case Qbintime:
		return readbintime(buf, n);

	case Qhostowner:
		return readstr((ulong)offset, buf, n, eve);

	case Qhostdomain:
		return readstr((ulong)offset, buf, n, hostdomain);

	case Quser:
		return readstr((ulong)offset, buf, n, up->user);

	case Qnull:
		return 0;

	case Qsysstat:
		b = smalloc(conf.nmach*(NUMSIZE*11+1) + 1);	/* +1 for NUL */
		bp = b;
		for(id = 0; id < 32; id++) {
			if(active.machs & (1<<id)) {
				mp = MACHP(id);
				readnum(0, bp, NUMSIZE, id, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->cs, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->intr, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->syscall, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->pfault, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->tlbfault, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->tlbpurge, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE, mp->load, NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE,
					(mp->perf.avg_inidle*100)/mp->perf.period,
					NUMSIZE);
				bp += NUMSIZE;
				readnum(0, bp, NUMSIZE,
					(mp->perf.avg_inintr*100)/mp->perf.period,
					NUMSIZE);
				bp += NUMSIZE;
				*bp++ = '\n';
			}
		}
		if(waserror()){
			free(b);
			nexterror();
		}
		n = readstr((ulong)offset, buf, n, b);
		free(b);
		poperror();
		return n;

	case Qswap:
		tmp[0] = 0;

		return readstr((ulong)offset, buf, n, tmp);

	case Qsysname:
		if(sysname == nil)
			return 0;
		return readstr((ulong)offset, buf, n, sysname);

	case Qrandom:
		return randomread(buf, n);

	case Qdrivers:
		b = malloc(READSTR);
		if(b == nil)
			error(Enomem);
		n = 0;
		for(i = 0; devtab[i] != nil; i++)
			n += snprint(b+n, READSTR-n, "#%C %s\n", devtab[i]->dc,  devtab[i]->name);
		if(waserror()){
			free(b);
			nexterror();
		}
		n = readstr((ulong)offset, buf, n, b);
		free(b);
		poperror();
		return n;

	case Qzero:
		memset(buf, 0, n);
		return n;

	case Qosversion:
		snprint(tmp, sizeof tmp, "2000");
		n = readstr((ulong)offset, buf, n, tmp);
		return n;

	default:
		print("consread 0x%llux\n", c->qid.path);
		error(Egreg);
	}
	return -1;		/* never reached */
}