git: plan9front

Download patch

ref: 40724b99884a1df683f2257792941ae367acf866
parent: c1c4d40def86d7c0f6eb8176094fa1078fc83a53
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sun Oct 20 08:22:36 EDT 2024

devether: Fix memory leaks in ifstat reads

Instead of having the driver allocate the temporary
READSTR buffer (and messing up the error handling),
allocate it in devether (netif) and pass the driver
start and end pointers to it.

Also, systematically check that the ifstat()
function checks the zero-length read (meaning
it is supposed to just update statistics counters
for a stats file read).

--- a/sys/src/9/bcm/ether4330.c
+++ b/sys/src/9/bcm/ether4330.c
@@ -2060,12 +2060,12 @@
  * Plan 9 driver interface
  */
 
-static long
-etherbcmifstat(Ether* edev, void* a, long n, ulong offset)
+static char*
+etherbcmifstat(void *arg, char *p, char *e)
 {
-	Ctlr *ctl;
-	char *p;
-	int l;
+	Ether *edev = arg;
+	Ctlr *ctl = edev->ctlr;
+
 	static char *cryptoname[4] = {
 		[0]	"off",
 		[Wep]	"wep",
@@ -2079,18 +2079,15 @@
 		[Connected] = "associated",
 	};
 
-	ctl = edev->ctlr;
-	if(ctl == nil)
-		return 0;
-	p = malloc(READSTR);
-	l = 0;
+	if(ctl == nil || p >= e)
+		return p;
 
-	l += snprint(p+l, READSTR-l, "channel: %d\n", ctl->chanid);
-	l += snprint(p+l, READSTR-l, "essid: %s\n", ctl->essid);
-	l += snprint(p+l, READSTR-l, "crypt: %s\n", cryptoname[ctl->cryptotype]);
-	l += snprint(p+l, READSTR-l, "oq: %d\n", qlen(edev->oq));
-	l += snprint(p+l, READSTR-l, "txwin: %d\n", ctl->txwindow);
-	l += snprint(p+l, READSTR-l, "txseq: %d\n", ctl->txseq);
+	p = seprint(p, e, "channel: %d\n", ctl->chanid);
+	p = seprint(p, e, "essid: %s\n", ctl->essid);
+	p = seprint(p, e, "crypt: %s\n", cryptoname[ctl->cryptotype]);
+	p = seprint(p, e, "oq: %d\n", qlen(edev->oq));
+	p = seprint(p, e, "txwin: %d\n", ctl->txwindow);
+	p = seprint(p, e, "txseq: %d\n", ctl->txseq);
 	/*
 	 * hack: prevent aux/wpa from trying to connect while bypassed
 	 * as wljoin() generates spurious traffic which poisons the
@@ -2097,11 +2094,8 @@
 	 * switch port tables.
 	 */
 	if(edev->bypass == nil)
-		l += snprint(p+l, READSTR-l, "status: %s\n", connectstate[ctl->status]);
-	USED(l);
-	n = readstr(offset, a, n, p);
-	free(p);
-	return n;
+		p = seprint(p, e, "status: %s\n", connectstate[ctl->status]);
+	return p;
 }
 
 static void
@@ -2380,12 +2374,12 @@
 	edev->ctlr = ctlr;
 	edev->attach = etherbcmattach;
 	edev->transmit = etherbcmtransmit;
-	edev->ifstat = etherbcmifstat;
 	edev->ctl = etherbcmctl;
 	edev->scanbs = etherbcmscan;
 	edev->shutdown = etherbcmshutdown;
 	edev->promiscuous = etherbcmprom;
 	edev->multicast = etherbcmmulti;
+	edev->ifstat = etherbcmifstat;
 	edev->arg = edev;
 
 	return 0;
--- a/sys/src/9/cycv/ethercycv.c
+++ b/sys/src/9/cycv/ethercycv.c
@@ -402,8 +402,8 @@
 	}
 }
 
-static long
-ethifstat(Ether *edev, void *a, long n, ulong offset)
+static char*
+ethifstat(void *arg, char *p, char *e)
 {
 	static char *names[] = {
 		"txoctetcount_gb", "txframecount_gb", "txbroadcastframes_g", "txmulticastframes_g",
@@ -420,18 +420,15 @@
 		"rxlengtherror", "rxoutofrangetype", "rxpauseframes", "rxfifooverflow",
 		"rxvlanframes_gb", "rxwatchdogerror", "rxrcverror", "rxctrlframes_g",
 	};
+	Ether *edev = arg;
+	Ctlr *c = edev->ctlr;
 	int i;
-	char *buf, *p, *e;
-	Ctlr *c;
-	
-	p = buf = smalloc(READSTR);
-	e = p + READSTR;
-	c = edev->ctlr;
-	for(i = 0; i < nelem(names); i++)
-		p = seprint(p, e, "%s: %lud\n", names[i], c->r[0x114/4 + i]);
-	n = readstr(offset, a, n, buf);
-	free(buf);
-	return n;
+
+	if(p < e){
+		for(i = 0; i < nelem(names); i++)
+			p = seprint(p, e, "%s: %lud\n", names[i], c->r[0x114/4 + i]);
+	}
+	return p;
 }
 
 static int
--- a/sys/src/9/kw/ether1116.c
+++ b/sys/src/9/kw/ether1116.c
@@ -1641,15 +1641,15 @@
 	ctlr->latecoll	+= reg->latecoll;
 }
 
-long
-ifstat(Ether *ether, void *a, long n, ulong off)
+static char*
+ifstat(void *arg, char *p, char *e)
 {
+	Ether *ether = arg;
 	Ctlr *ctlr = ether->ctlr;
 	Gbereg *reg = ctlr->reg;
-	char *buf, *p, *e;
 
-	buf = p = malloc(READSTR);
-	e = p + READSTR;
+	if(p >= e)
+		return p;
 
 	ilock(ctlr);
 	getmibstats(ctlr);
@@ -1702,12 +1702,9 @@
 	p = seprint(p, e, "crc errors: %lud\n", ctlr->crcerr);
 	p = seprint(p, e, "collisions: %lud\n", ctlr->collisions);
 	p = seprint(p, e, "late collisions: %lud\n", ctlr->latecoll);
-	USED(p);
 	iunlock(ctlr);
 
-	n = readstr(off, a, n, buf);
-	free(buf);
-	return n;
+	return p;
 }
 
 
@@ -1764,11 +1761,11 @@
 
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->ifstat = ifstat;
 	ether->shutdown = shutdown;
 	ether->ctl = ctl;
 
 	ether->arg = ether;
+	ether->ifstat = ifstat;
 	ether->promiscuous = promiscuous;
 	ether->multicast = multicast;
 
--- a/sys/src/9/mt7688/ether7688.c
+++ b/sys/src/9/mt7688/ether7688.c
@@ -738,48 +738,42 @@
 }
 
 
-static long
-ifstat(Ether* edev, void* a, long n, ulong offset)
+static char*
+ifstat(void *arg, char *p, char *e)
 {
-	char* p;
-	Ctlr* ctlr;
-	int l;
+	Ether *edev = arg;
+	Ctlr* ctlr = edev->ctlr;
 
-	ctlr = edev->ctlr;
+	if(p >= e)
+		return p;
 
-	p = smalloc(READSTR);
-	l = 0;
 	qlock(ctlr);
-	l += snprint(p+l, READSTR-l, "tx: %d\n", ctlr->txstat);
-	l += snprint(p+l, READSTR-l, "rx: %d\n", ctlr->rxstat);
-	l += snprint(p+l, READSTR-l, "txintr: %d\n", ctlr->txintr);
-	l += snprint(p+l, READSTR-l, "rxintr: %d\n", ctlr->rxintr);
-	l += snprint(p+l, READSTR-l, "nointr: %d\n", ctlr->nointr);
-	l += snprint(p+l, READSTR-l, "bad rx: %d\n", ctlr->badrx);
-	l += snprint(p+l, READSTR-l, "\n");
-	l += snprint(p+l, READSTR-l, "dma errs: tx: %d rx: %d\n", ctlr->txdmaerr, ctlr->rxdmaerr);
-	l += snprint(p+l, READSTR-l, "\n");
-	l += snprint(p+l, READSTR-l, "txptr: %08uX\n", ethrd(PDMA_TX0_PTR));
-	l += snprint(p+l, READSTR-l, "txcnt: %uX\n", ethrd(PDMA_TX0_COUNT));
-	l += snprint(p+l, READSTR-l, "txidx: %uX\n", ethrd(PDMA_TX0_CPU_IDX));
-	l += snprint(p+l, READSTR-l, "txdtx: %uX\n", ethrd(PDMA_TX0_DMA_IDX));
-	l += snprint(p+l, READSTR-l, "\n");
-	l += snprint(p+l, READSTR-l, "rxptr: %08uX\n", ethrd(PDMA_RX0_PTR));
-	l += snprint(p+l, READSTR-l, "rxcnt: %uX\n", ethrd(PDMA_RX0_COUNT));
-	l += snprint(p+l, READSTR-l, "rxidx: %uX\n", ethrd(PDMA_RX0_CPU_IDX));
-	l += snprint(p+l, READSTR-l, "rxdtx: %uX\n", ethrd(PDMA_RX0_DMA_IDX));
-	l += snprint(p+l, READSTR-l, "\n");
-	l += snprint(p+l, READSTR-l, "GLOBAL CFG: %08uX\n", ethrd(PDMA_GLOBAL_CFG));
-	l += snprint(p+l, READSTR-l, "INT STATUS: %08uX\n", ethrd(INT_STATUS));
-	l += snprint(p+l, READSTR-l, "INT   MASK: %08uX\n", ethrd(INT_MASK));
-	snprint(p+l, READSTR-l, "\n");
-
-	n = readstr(offset, a, n, p);
-	free(p);
-
+	p = seprint(p, e, "tx: %d\n", ctlr->txstat);
+	p = seprint(p, e, "rx: %d\n", ctlr->rxstat);
+	p = seprint(p, e, "txintr: %d\n", ctlr->txintr);
+	p = seprint(p, e, "rxintr: %d\n", ctlr->rxintr);
+	p = seprint(p, e, "nointr: %d\n", ctlr->nointr);
+	p = seprint(p, e, "bad rx: %d\n", ctlr->badrx);
+	p = seprint(p, e, "\n");
+	p = seprint(p, e, "dma errs: tx: %d rx: %d\n", ctlr->txdmaerr, ctlr->rxdmaerr);
+	p = seprint(p, e, "\n");
+	p = seprint(p, e, "txptr: %08uX\n", ethrd(PDMA_TX0_PTR));
+	p = seprint(p, e, "txcnt: %uX\n", ethrd(PDMA_TX0_COUNT));
+	p = seprint(p, e, "txidx: %uX\n", ethrd(PDMA_TX0_CPU_IDX));
+	p = seprint(p, e, "txdtx: %uX\n", ethrd(PDMA_TX0_DMA_IDX));
+	p = seprint(p, e, "\n");
+	p = seprint(p, e, "rxptr: %08uX\n", ethrd(PDMA_RX0_PTR));
+	p = seprint(p, e, "rxcnt: %uX\n", ethrd(PDMA_RX0_COUNT));
+	p = seprint(p, e, "rxidx: %uX\n", ethrd(PDMA_RX0_CPU_IDX));
+	p = seprint(p, e, "rxdtx: %uX\n", ethrd(PDMA_RX0_DMA_IDX));
+	p = seprint(p, e, "\n");
+	p = seprint(p, e, "GLOBAL CFG: %08uX\n", ethrd(PDMA_GLOBAL_CFG));
+	p = seprint(p, e, "INT STATUS: %08uX\n", ethrd(INT_STATUS));
+	p = seprint(p, e, "INT   MASK: %08uX\n", ethrd(INT_MASK));
+	p = seprint(p, e, "\n");
 	qunlock(ctlr);
 
-	return n;
+	return p;
 }
 
 /* set Ether and Ctlr */
@@ -810,8 +804,8 @@
 
 	edev->attach = attach;
 	edev->shutdown = shutdown;
-	edev->ifstat = ifstat;
 //	edev->ctl = ctl;
+	edev->ifstat = ifstat;
 	edev->promiscuous = prom;
 	edev->multicast = multi;
 
--- a/sys/src/9/mtx/ether2114x.c
+++ b/sys/src/9/mtx/ether2114x.c
@@ -296,69 +296,54 @@
 	iunlock(&ctlr->lock);
 }
 
-static long
-ifstat(Ether* ether, void* a, long n, ulong offset)
+static char*
+ifstat(void *arg, char *p, char *e)
 {
-	Ctlr *ctlr;
-	char *buf, *p;
-	int i, l, len;
+	Ether *ether = arg;
+	Ctlr *ctlr = ether->ctlr;
+	int i;
 
-	ctlr = ether->ctlr;
-
 	ether->crcs = ctlr->ce;
 	ether->frames = ctlr->rf+ctlr->cs;
 	ether->buffs = ctlr->de+ctlr->tl;
 	ether->overflows = ctlr->of;
 
-	if(n == 0)
-		return 0;
+	if(p >= e)
+		return p;
 
-	p = malloc(READSTR);
-	l = snprint(p, READSTR, "Overflow: %lud\n", ctlr->of);
-	l += snprint(p+l, READSTR-l, "Ru: %lud\n", ctlr->ru);
-	l += snprint(p+l, READSTR-l, "Rps: %lud\n", ctlr->rps);
-	l += snprint(p+l, READSTR-l, "Rwt: %lud\n", ctlr->rwt);
-	l += snprint(p+l, READSTR-l, "Tps: %lud\n", ctlr->tps);
-	l += snprint(p+l, READSTR-l, "Tu: %lud\n", ctlr->tu);
-	l += snprint(p+l, READSTR-l, "Tjt: %lud\n", ctlr->tjt);
-	l += snprint(p+l, READSTR-l, "Unf: %lud\n", ctlr->unf);
-	l += snprint(p+l, READSTR-l, "CRC Error: %lud\n", ctlr->ce);
-	l += snprint(p+l, READSTR-l, "Collision Seen: %lud\n", ctlr->cs);
-	l += snprint(p+l, READSTR-l, "Frame Too Long: %lud\n", ctlr->tl);
-	l += snprint(p+l, READSTR-l, "Runt Frame: %lud\n", ctlr->rf);
-	l += snprint(p+l, READSTR-l, "Descriptor Error: %lud\n", ctlr->de);
-	l += snprint(p+l, READSTR-l, "Underflow Error: %lud\n", ctlr->uf);
-	l += snprint(p+l, READSTR-l, "Excessive Collisions: %lud\n", ctlr->ec);
-	l += snprint(p+l, READSTR-l, "Late Collision: %lud\n", ctlr->lc);
-	l += snprint(p+l, READSTR-l, "No Carrier: %lud\n", ctlr->nc);
-	l += snprint(p+l, READSTR-l, "Loss of Carrier: %lud\n", ctlr->lo);
-	l += snprint(p+l, READSTR-l, "Transmit Jabber Timeout: %lud\n",
-		ctlr->to);
-	l += snprint(p+l, READSTR-l, "csr6: %luX %uX\n", csr32r(ctlr, 6),
-		ctlr->csr6);
-	snprint(p+l, READSTR-l, "ntqmax: %d\n", ctlr->ntqmax);
+	p = seprint(p, e, "Overflow: %lud\n", ctlr->of);
+	p = seprint(p, e, "Ru: %lud\n", ctlr->ru);
+	p = seprint(p, e, "Rps: %lud\n", ctlr->rps);
+	p = seprint(p, e, "Rwt: %lud\n", ctlr->rwt);
+	p = seprint(p, e, "Tps: %lud\n", ctlr->tps);
+	p = seprint(p, e, "Tu: %lud\n", ctlr->tu);
+	p = seprint(p, e, "Tjt: %lud\n", ctlr->tjt);
+	p = seprint(p, e, "Unf: %lud\n", ctlr->unf);
+	p = seprint(p, e, "CRC Error: %lud\n", ctlr->ce);
+	p = seprint(p, e, "Collision Seen: %lud\n", ctlr->cs);
+	p = seprint(p, e, "Frame Too Long: %lud\n", ctlr->tl);
+	p = seprint(p, e, "Runt Frame: %lud\n", ctlr->rf);
+	p = seprint(p, e, "Descriptor Error: %lud\n", ctlr->de);
+	p = seprint(p, e, "Underflow Error: %lud\n", ctlr->uf);
+	p = seprint(p, e, "Excessive Collisions: %lud\n", ctlr->ec);
+	p = seprint(p, e, "Late Collision: %lud\n", ctlr->lc);
+	p = seprint(p, e, "No Carrier: %lud\n", ctlr->nc);
+	p = seprint(p, e, "Loss of Carrier: %lud\n", ctlr->lo);
+	p = seprint(p, e, "Transmit Jabber Timeout: %lud\n", ctlr->to);
+	p = seprint(p, e, "csr6: %luX %uX\n", csr32r(ctlr, 6), ctlr->csr6);
+	p = seprint(p, e, "ntqmax: %d\n", ctlr->ntqmax);
 	ctlr->ntqmax = 0;
-	buf = a;
-	len = readstr(offset, buf, n, p);
-	if(offset > l)
-		offset -= l;
-	else
-		offset = 0;
-	buf += len;
-	n -= len;
 
-	l = snprint(p, READSTR, "srom:");
+	p = seprint(p, e, "srom:");
 	for(i = 0; i < (1<<(ctlr->sromsz)*sizeof(ushort)); i++){
 		if(i && ((i & 0x0F) == 0))
-			l += snprint(p+l, READSTR-l, "\n     ");
-		l += snprint(p+l, READSTR-l, " %2.2uX", ctlr->srom[i]);
+			p = seprint(p, e, "\n     ");
+		p = seprint(p, e, " %2.2uX", ctlr->srom[i]);
 	}
 
-	snprint(p+l, READSTR-l, "\n");
-	len += readstr(offset, buf, n, p);
-	free(p);
+	p = seprint(p, e, "\n");
 
-	return len;
+	return p;
 }
 
 static void
@@ -1654,9 +1639,9 @@
 	 */
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->ifstat = ifstat;
 
 	ether->arg = ether;
+	ether->ifstat = ifstat;
 	ether->promiscuous = promiscuous;
 
 	intrenable(ether->irq, interrupt, ether, ether->tbdf, ether->name);
--- a/sys/src/9/omap/ether9221.c
+++ b/sys/src/9/omap/ether9221.c
@@ -336,17 +336,18 @@
 }
 
 
-static long
-smcifstat(Ether* edev, void* a, long n, ulong offset)
+static char*
+smcifstat(void *arg, char *p, char *e)
 {
-	Ctlr *ctlr;
-	char *p, *s;
-	int i, l, r;
+	Ether *edev = arg;
+	Ctlr *ctlr = edev->ctlr;
+	char *s;
+	int i, r;
 
-	ctlr = edev->ctlr;
+	if(p >= e)
+		return p;
+
 	qlock(&ctlr->slock);
-	p = malloc(READSTR);
-	l = 0;
 	for(i = 0; i < Nstatistics; i++){
 		// read regs->rxdrop TODO
 		r = 0;
@@ -357,33 +358,26 @@
 			ctlr->statistics[i] += r;
 			if(ctlr->statistics[i] == 0)
 				continue;
-			l += snprint(p+l, READSTR-l, "%s: %ud %ud\n",
+			p = seprint(p, e, "%s: %ud %ud\n",
 				s, ctlr->statistics[i], r);
 			break;
 		}
 	}
 
-	l += snprint(p+l, READSTR-l, "lintr: %ud %ud\n",
-		ctlr->lintr, ctlr->lsleep);
-	l += snprint(p+l, READSTR-l, "rintr: %ud %ud\n",
-		ctlr->rintr, ctlr->rsleep);
-	l += snprint(p+l, READSTR-l, "tintr: %ud %ud\n",
-		ctlr->tintr, ctlr->tsleep);
+	p = seprint(p, e, "lintr: %ud %ud\n", ctlr->lintr, ctlr->lsleep);
+	p = seprint(p, e, "rintr: %ud %ud\n", ctlr->rintr, ctlr->rsleep);
+	p = seprint(p, e, "tintr: %ud %ud\n", ctlr->tintr, ctlr->tsleep);
 
-	l += snprint(p+l, READSTR-l, "eeprom:");
+	p = seprint(p, e, "eeprom:");
 	for(i = 0; i < 0x40; i++){
 		if(i && ((i & 0x07) == 0))
-			l += snprint(p+l, READSTR-l, "\n       ");
-		l += snprint(p+l, READSTR-l, " %4.4uX", ctlr->eeprom[i]);
+			p = seprint(p, e, "\n       ");
+		p = seprint(p, e, " %4.4uX", ctlr->eeprom[i]);
 	}
-	l += snprint(p+l, READSTR-l, "\n");
-	USED(l);
-
-	n = readstr(offset, a, n, p);
-	free(p);
+	p = seprint(p, e, "\n");
 	qunlock(&ctlr->slock);
 
-	return n;
+	return p;
 }
 
 static void
@@ -942,10 +936,10 @@
 	 */
 	edev->attach = smcattach;
 	edev->transmit = smctransmitcall;
-	edev->ifstat = smcifstat;
 /*	edev->ctl = smcctl;			/* no ctl msgs supported */
 
 	edev->arg = edev;
+	edev->ifstat = smcifstat;
 	edev->promiscuous = smcpromiscuous;
 	edev->multicast = smcmulticast;
 	edev->shutdown = smcshutdown;
--- a/sys/src/9/pc/ether2114x.c
+++ b/sys/src/9/pc/ether2114x.c
@@ -308,69 +308,53 @@
 	iunlock(&ctlr->lock);
 }
 
-static long
-ifstat(Ether* ether, void* a, long n, ulong offset)
+static char*
+ifstat(void *arg, char* p, char* e)
 {
-	Ctlr *ctlr;
-	char *buf, *p;
-	int i, l, len;
+	Ether *ether = arg;
+	Ctlr *ctlr = ether->ctlr;
+	int i;
 
-	ctlr = ether->ctlr;
-
 	ether->crcs = ctlr->ce;
 	ether->frames = ctlr->rf+ctlr->cs;
 	ether->buffs = ctlr->de+ctlr->tl;
 	ether->overflows = ctlr->of;
 
-	if(n == 0)
-		return 0;
+	if(p >= e)
+		return p;
 
-	p = smalloc(READSTR);
-	l = snprint(p, READSTR, "Overflow: %lud\n", ctlr->of);
-	l += snprint(p+l, READSTR-l, "Ru: %lud\n", ctlr->ru);
-	l += snprint(p+l, READSTR-l, "Rps: %lud\n", ctlr->rps);
-	l += snprint(p+l, READSTR-l, "Rwt: %lud\n", ctlr->rwt);
-	l += snprint(p+l, READSTR-l, "Tps: %lud\n", ctlr->tps);
-	l += snprint(p+l, READSTR-l, "Tu: %lud\n", ctlr->tu);
-	l += snprint(p+l, READSTR-l, "Tjt: %lud\n", ctlr->tjt);
-	l += snprint(p+l, READSTR-l, "Unf: %lud\n", ctlr->unf);
-	l += snprint(p+l, READSTR-l, "CRC Error: %lud\n", ctlr->ce);
-	l += snprint(p+l, READSTR-l, "Collision Seen: %lud\n", ctlr->cs);
-	l += snprint(p+l, READSTR-l, "Frame Too Long: %lud\n", ctlr->tl);
-	l += snprint(p+l, READSTR-l, "Runt Frame: %lud\n", ctlr->rf);
-	l += snprint(p+l, READSTR-l, "Descriptor Error: %lud\n", ctlr->de);
-	l += snprint(p+l, READSTR-l, "Underflow Error: %lud\n", ctlr->uf);
-	l += snprint(p+l, READSTR-l, "Excessive Collisions: %lud\n", ctlr->ec);
-	l += snprint(p+l, READSTR-l, "Late Collision: %lud\n", ctlr->lc);
-	l += snprint(p+l, READSTR-l, "No Carrier: %lud\n", ctlr->nc);
-	l += snprint(p+l, READSTR-l, "Loss of Carrier: %lud\n", ctlr->lo);
-	l += snprint(p+l, READSTR-l, "Transmit Jabber Timeout: %lud\n",
-		ctlr->to);
-	l += snprint(p+l, READSTR-l, "csr6: %luX %uX\n", csr32r(ctlr, 6),
-		ctlr->csr6);
-	snprint(p+l, READSTR-l, "ntqmax: %d\n", ctlr->ntqmax);
+	p = seprint(p, e, "Overflow: %lud\n", ctlr->of);
+	p = seprint(p, e, "Ru: %lud\n", ctlr->ru);
+	p = seprint(p, e, "Rps: %lud\n", ctlr->rps);
+	p = seprint(p, e, "Rwt: %lud\n", ctlr->rwt);
+	p = seprint(p, e, "Tps: %lud\n", ctlr->tps);
+	p = seprint(p, e, "Tu: %lud\n", ctlr->tu);
+	p = seprint(p, e, "Tjt: %lud\n", ctlr->tjt);
+	p = seprint(p, e, "Unf: %lud\n", ctlr->unf);
+	p = seprint(p, e, "CRC Error: %lud\n", ctlr->ce);
+	p = seprint(p, e, "Collision Seen: %lud\n", ctlr->cs);
+	p = seprint(p, e, "Frame Too Long: %lud\n", ctlr->tl);
+	p = seprint(p, e, "Runt Frame: %lud\n", ctlr->rf);
+	p = seprint(p, e, "Descriptor Error: %lud\n", ctlr->de);
+	p = seprint(p, e, "Underflow Error: %lud\n", ctlr->uf);
+	p = seprint(p, e, "Excessive Collisions: %lud\n", ctlr->ec);
+	p = seprint(p, e, "Late Collision: %lud\n", ctlr->lc);
+	p = seprint(p, e, "No Carrier: %lud\n", ctlr->nc);
+	p = seprint(p, e, "Loss of Carrier: %lud\n", ctlr->lo);
+	p = seprint(p, e, "Transmit Jabber Timeout: %lud\n", ctlr->to);
+	p = seprint(p, e, "csr6: %luX %uX\n", csr32r(ctlr, 6), ctlr->csr6);
+	p = seprint(p, e, "ntqmax: %d\n", ctlr->ntqmax);
 	ctlr->ntqmax = 0;
-	buf = a;
-	len = readstr(offset, buf, n, p);
-	if(offset > l)
-		offset -= l;
-	else
-		offset = 0;
-	buf += len;
-	n -= len;
 
-	l = snprint(p, READSTR, "srom:");
+	p = seprint(p, e, "srom:");
 	for(i = 0; i < (1<<(ctlr->sromsz)*sizeof(ushort)); i++){
 		if(i && ((i & 0x0F) == 0))
-			l += snprint(p+l, READSTR-l, "\n     ");
-		l += snprint(p+l, READSTR-l, " %2.2uX", ctlr->srom[i]);
+			p = seprint(p, e, "\n     ");
+		p = seprint(p, e, " %2.2uX", ctlr->srom[i]);
 	}
+	p = seprint(p, e, "\n");
 
-	snprint(p+l, READSTR-l, "\n");
-	len += readstr(offset, buf, n, p);
-	free(p);
-
-	return len;
+	return p;
 }
 
 static void
@@ -1833,12 +1817,12 @@
 	 */
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->ifstat = ifstat;
 
 	ether->arg = ether;
 	ether->shutdown = shutdown;
 	ether->multicast = multicast;
 	ether->promiscuous = promiscuous;
+	ether->ifstat = ifstat;
 
 	intrenable(ether->irq, interrupt, ether, ether->tbdf, ether->name);
 
--- a/sys/src/9/pc/ether79c970.c
+++ b/sys/src/9/pc/ether79c970.c
@@ -190,41 +190,34 @@
 {
 }
 
-static long
-ifstat(Ether* ether, void* a, long n, ulong offset)
+static char*
+ifstat(void *arg, char *p, char *e)
 {
-	char *p;
-	int len;
-	Ctlr *ctlr;
+	Ether *ether = arg;
+	Ctlr *ctlr = ether->ctlr;
 
-	ctlr = ether->ctlr;
-
 	ether->crcs = ctlr->crc;
 	ether->frames = ctlr->fram;
 	ether->buffs = ctlr->rxbuff+ctlr->txbuff;
 	ether->overflows = ctlr->oflo;
 
-	if(n == 0)
-		return 0;
+	if(p >= e)
+		return p;
 
-	p = smalloc(READSTR);
-	len = snprint(p, READSTR, "Rxbuff: %ld\n", ctlr->rxbuff);
-	len += snprint(p+len, READSTR-len, "Crc: %ld\n", ctlr->crc);
-	len += snprint(p+len, READSTR-len, "Oflo: %ld\n", ctlr->oflo);
-	len += snprint(p+len, READSTR-len, "Fram: %ld\n", ctlr->fram);
-	len += snprint(p+len, READSTR-len, "Rtry: %ld\n", ctlr->rtry);
-	len += snprint(p+len, READSTR-len, "Lcar: %ld\n", ctlr->lcar);
-	len += snprint(p+len, READSTR-len, "Lcol: %ld\n", ctlr->lcol);
-	len += snprint(p+len, READSTR-len, "Uflo: %ld\n", ctlr->uflo);
-	len += snprint(p+len, READSTR-len, "Txbuff: %ld\n", ctlr->txbuff);
-	len += snprint(p+len, READSTR-len, "Merr: %ld\n", ctlr->merr);
-	len += snprint(p+len, READSTR-len, "Miss: %ld\n", ctlr->miss);
-	snprint(p+len, READSTR-len, "Babl: %ld\n", ctlr->babl);
+	p = seprint(p, e, "Rxbuff: %ld\n", ctlr->rxbuff);
+	p = seprint(p, e, "Crc: %ld\n", ctlr->crc);
+	p = seprint(p, e, "Oflo: %ld\n", ctlr->oflo);
+	p = seprint(p, e, "Fram: %ld\n", ctlr->fram);
+	p = seprint(p, e, "Rtry: %ld\n", ctlr->rtry);
+	p = seprint(p, e, "Lcar: %ld\n", ctlr->lcar);
+	p = seprint(p, e, "Lcol: %ld\n", ctlr->lcol);
+	p = seprint(p, e, "Uflo: %ld\n", ctlr->uflo);
+	p = seprint(p, e, "Txbuff: %ld\n", ctlr->txbuff);
+	p = seprint(p, e, "Merr: %ld\n", ctlr->merr);
+	p = seprint(p, e, "Miss: %ld\n", ctlr->miss);
+	p = seprint(p, e, "Babl: %ld\n", ctlr->babl);
 
-	n = readstr(offset, a, n, p);
-	free(p);
-
-	return n;
+	return p;
 }
 
 static void
@@ -673,12 +666,12 @@
 	 */
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->ifstat = ifstat;
 
 	ether->arg = ether;
 	ether->promiscuous = promiscuous;
 	ether->multicast = multicast;
 //	ether->shutdown = shutdown;
+	ether->ifstat = ifstat;
 
 	intrenable(ether->irq, interrupt, ether, ether->tbdf, ether->name);
 
--- a/sys/src/9/pc/ether8139.c
+++ b/sys/src/9/pc/ether8139.c
@@ -302,46 +302,45 @@
 	iunlock(&ctlr->ilock);
 }
 
-static long
-rtl8139ifstat(Ether* edev, void* a, long n, ulong offset)
+static char*
+rtl8139stat(void *ether, char *p, char *e)
 {
-	int l;
-	char *p;
+	Ether *edev = ether;
 	Ctlr *ctlr;
 
+	if(p >= e)
+		return p;
+
 	ctlr = edev->ctlr;
-	p = smalloc(READSTR);
-	l = snprint(p, READSTR, "rcr %#8.8ux\n", ctlr->rcr);
-	l += snprint(p+l, READSTR-l, "multicast %ud\n", ctlr->mcast);
-	l += snprint(p+l, READSTR-l, "ierrs %d\n", ctlr->ierrs);
-	l += snprint(p+l, READSTR-l, "etxth %d\n", ctlr->etxth);
-	l += snprint(p+l, READSTR-l, "taligned %d\n", ctlr->taligned);
-	l += snprint(p+l, READSTR-l, "tunaligned %d\n", ctlr->tunaligned);
+	p = seprint(p, e, "rcr %#8.8ux\n", ctlr->rcr);
+	p = seprint(p, e, "multicast %ud\n", ctlr->mcast);
+	p = seprint(p, e, "ierrs %d\n", ctlr->ierrs);
+	p = seprint(p, e, "etxth %d\n", ctlr->etxth);
+	p = seprint(p, e, "taligned %d\n", ctlr->taligned);
+	p = seprint(p, e, "tunaligned %d\n", ctlr->tunaligned);
 	ctlr->dis += csr16r(ctlr, Dis);
-	l += snprint(p+l, READSTR-l, "dis %d\n", ctlr->dis);
+	p = seprint(p, e, "dis %d\n", ctlr->dis);
 	ctlr->fcsc += csr16r(ctlr, Fcsc);
-	l += snprint(p+l, READSTR-l, "fcscnt %d\n", ctlr->fcsc);
+	p = seprint(p, e, "fcscnt %d\n", ctlr->fcsc);
 	ctlr->rec += csr16r(ctlr, Rec);
-	l += snprint(p+l, READSTR-l, "rec %d\n", ctlr->rec);
+	p = seprint(p, e, "rec %d\n", ctlr->rec);
 
-	l += snprint(p+l, READSTR-l, "Tcr %#8.8lux\n", csr32r(ctlr, Tcr));
-	l += snprint(p+l, READSTR-l, "Config0 %#2.2ux\n", csr8r(ctlr, Config0));
-	l += snprint(p+l, READSTR-l, "Config1 %#2.2ux\n", csr8r(ctlr, Config1));
-	l += snprint(p+l, READSTR-l, "Msr %#2.2ux\n", csr8r(ctlr, Msr));
-	l += snprint(p+l, READSTR-l, "Config3 %#2.2ux\n", csr8r(ctlr, Config3));
-	l += snprint(p+l, READSTR-l, "Config4 %#2.2ux\n", csr8r(ctlr, Config4));
+	p = seprint(p, e, "Tcr %#8.8lux\n", csr32r(ctlr, Tcr));
+	p = seprint(p, e, "Config0 %#2.2ux\n", csr8r(ctlr, Config0));
+	p = seprint(p, e, "Config1 %#2.2ux\n", csr8r(ctlr, Config1));
+	p = seprint(p, e, "Msr %#2.2ux\n", csr8r(ctlr, Msr));
+	p = seprint(p, e, "Config3 %#2.2ux\n", csr8r(ctlr, Config3));
+	p = seprint(p, e, "Config4 %#2.2ux\n", csr8r(ctlr, Config4));
 
-	l += snprint(p+l, READSTR-l, "Bmcr %#4.4ux\n", csr16r(ctlr, Bmcr));
-	l += snprint(p+l, READSTR-l, "Bmsr %#4.4ux\n", csr16r(ctlr, Bmsr));
-	l += snprint(p+l, READSTR-l, "Anar %#4.4ux\n", csr16r(ctlr, Anar));
-	l += snprint(p+l, READSTR-l, "Anlpar %#4.4ux\n", csr16r(ctlr, Anlpar));
-	l += snprint(p+l, READSTR-l, "Aner %#4.4ux\n", csr16r(ctlr, Aner));
-	l += snprint(p+l, READSTR-l, "Nwaytr %#4.4ux\n", csr16r(ctlr, Nwaytr));
-	snprint(p+l, READSTR-l, "Cscr %#4.4ux\n", csr16r(ctlr, Cscr));
-	n = readstr(offset, a, n, p);
-	free(p);
+	p = seprint(p, e, "Bmcr %#4.4ux\n", csr16r(ctlr, Bmcr));
+	p = seprint(p, e, "Bmsr %#4.4ux\n", csr16r(ctlr, Bmsr));
+	p = seprint(p, e, "Anar %#4.4ux\n", csr16r(ctlr, Anar));
+	p = seprint(p, e, "Anlpar %#4.4ux\n", csr16r(ctlr, Anlpar));
+	p = seprint(p, e, "Aner %#4.4ux\n", csr16r(ctlr, Aner));
+	p = seprint(p, e, "Nwaytr %#4.4ux\n", csr16r(ctlr, Nwaytr));
+	p = seprint(p, e, "Cscr %#4.4ux\n", csr16r(ctlr, Cscr));
 
-	return n;
+	return p;
 }
 
 static int
@@ -807,12 +806,12 @@
 
 	edev->attach = rtl8139attach;
 	edev->transmit = rtl8139transmit;
-	edev->ifstat = rtl8139ifstat;
 
 	edev->arg = edev;
 	edev->promiscuous = rtl8139promiscuous;
 	edev->multicast = rtl8139multicast;
 //	edev->shutdown = rtl8139shutdown;
+	edev->ifstat = rtl8139stat;
 
 	intrenable(edev->irq, rtl8139interrupt, edev, edev->tbdf, edev->name);
 
--- a/sys/src/9/pc/ether8169.c
+++ b/sys/src/9/pc/ether8169.c
@@ -518,22 +518,18 @@
 	iunlock(ctlr);
 }
 
-static long
-rtl8169ifstat(Ether* edev, void* a, long n, ulong offset)
+static char*
+rtl8169ifstat(void *arg, char *p, char *e)
 {
-	char *p;
+	Ether *edev = arg;
 	Ctlr *ctlr;
 	Dtcc *dtcc;
-	int i, l, r, timeo;
+	int i, r, timeo;
 
-	p = smalloc(READSTR);
-
 	ctlr = edev->ctlr;
 	qlock(&ctlr->slock);
-
 	if(waserror()){
 		qunlock(&ctlr->slock);
-		free(p);
 		nexterror();
 	}
 
@@ -554,61 +550,57 @@
 	edev->buffs = dtcc->misspkt;
 	edev->overflows = ctlr->txdu+ctlr->rdu;
 
-	if(n == 0){
+	if(p >= e){
 		qunlock(&ctlr->slock);
 		poperror();
-		free(p);
-		return 0;
+		return p;
 	}
 
-	l = snprint(p, READSTR, "TxOk: %llud\n", dtcc->txok);
-	l += snprint(p+l, READSTR-l, "RxOk: %llud\n", dtcc->rxok);
-	l += snprint(p+l, READSTR-l, "TxEr: %llud\n", dtcc->txer);
-	l += snprint(p+l, READSTR-l, "RxEr: %ud\n", dtcc->rxer);
-	l += snprint(p+l, READSTR-l, "MissPkt: %ud\n", dtcc->misspkt);
-	l += snprint(p+l, READSTR-l, "FAE: %ud\n", dtcc->fae);
-	l += snprint(p+l, READSTR-l, "Tx1Col: %ud\n", dtcc->tx1col);
-	l += snprint(p+l, READSTR-l, "TxMCol: %ud\n", dtcc->txmcol);
-	l += snprint(p+l, READSTR-l, "RxOkPh: %llud\n", dtcc->rxokph);
-	l += snprint(p+l, READSTR-l, "RxOkBrd: %llud\n", dtcc->rxokbrd);
-	l += snprint(p+l, READSTR-l, "RxOkMu: %ud\n", dtcc->rxokmu);
-	l += snprint(p+l, READSTR-l, "TxAbt: %ud\n", dtcc->txabt);
-	l += snprint(p+l, READSTR-l, "TxUndrn: %ud\n", dtcc->txundrn);
+	p = seprint(p, e, "TxOk: %llud\n", dtcc->txok);
+	p = seprint(p, e, "RxOk: %llud\n", dtcc->rxok);
+	p = seprint(p, e, "TxEr: %llud\n", dtcc->txer);
+	p = seprint(p, e, "RxEr: %ud\n", dtcc->rxer);
+	p = seprint(p, e, "MissPkt: %ud\n", dtcc->misspkt);
+	p = seprint(p, e, "FAE: %ud\n", dtcc->fae);
+	p = seprint(p, e, "Tx1Col: %ud\n", dtcc->tx1col);
+	p = seprint(p, e, "TxMCol: %ud\n", dtcc->txmcol);
+	p = seprint(p, e, "RxOkPh: %llud\n", dtcc->rxokph);
+	p = seprint(p, e, "RxOkBrd: %llud\n", dtcc->rxokbrd);
+	p = seprint(p, e, "RxOkMu: %ud\n", dtcc->rxokmu);
+	p = seprint(p, e, "TxAbt: %ud\n", dtcc->txabt);
+	p = seprint(p, e, "TxUndrn: %ud\n", dtcc->txundrn);
 
-	l += snprint(p+l, READSTR-l, "serr: %ud\n", ctlr->serr);
-	l += snprint(p+l, READSTR-l, "fovw: %ud\n", ctlr->fovw);
+	p = seprint(p, e, "serr: %ud\n", ctlr->serr);
+	p = seprint(p, e, "fovw: %ud\n", ctlr->fovw);
 
-	l += snprint(p+l, READSTR-l, "txdu: %ud\n", ctlr->txdu);
-	l += snprint(p+l, READSTR-l, "tcpf: %ud\n", ctlr->tcpf);
-	l += snprint(p+l, READSTR-l, "udpf: %ud\n", ctlr->udpf);
-	l += snprint(p+l, READSTR-l, "ipf: %ud\n", ctlr->ipf);
-	l += snprint(p+l, READSTR-l, "fovf: %ud\n", ctlr->fovf);
-	l += snprint(p+l, READSTR-l, "rer: %ud\n", ctlr->rer);
-	l += snprint(p+l, READSTR-l, "rdu: %ud\n", ctlr->rdu);
-	l += snprint(p+l, READSTR-l, "punlc: %ud\n", ctlr->punlc);
+	p = seprint(p, e, "txdu: %ud\n", ctlr->txdu);
+	p = seprint(p, e, "tcpf: %ud\n", ctlr->tcpf);
+	p = seprint(p, e, "udpf: %ud\n", ctlr->udpf);
+	p = seprint(p, e, "ipf: %ud\n", ctlr->ipf);
+	p = seprint(p, e, "fovf: %ud\n", ctlr->fovf);
+	p = seprint(p, e, "rer: %ud\n", ctlr->rer);
+	p = seprint(p, e, "rdu: %ud\n", ctlr->rdu);
+	p = seprint(p, e, "punlc: %ud\n", ctlr->punlc);
 
-	l += snprint(p+l, READSTR-l, "tcr: %#8.8ux\n", ctlr->tcr);
-	l += snprint(p+l, READSTR-l, "rcr: %#8.8ux\n", ctlr->rcr);
-	l += snprint(p+l, READSTR-l, "multicast: %ud\n", ctlr->mcast);
+	p = seprint(p, e, "tcr: %#8.8ux\n", ctlr->tcr);
+	p = seprint(p, e, "rcr: %#8.8ux\n", ctlr->rcr);
+	p = seprint(p, e, "multicast: %ud\n", ctlr->mcast);
 
 	if(ctlr->mii != nil && ctlr->mii->curphy != nil){
-		l += snprint(p+l, READSTR-l, "phy:   ");
+		p = seprint(p, e, "phy:   ");
 		for(i = 0; i < NMiiPhyr; i++){
 			if(i && ((i & 0x07) == 0))
-				l += snprint(p+l, READSTR-l, "\n       ");
+				p = seprint(p, e, "\n       ");
 			r = miimir(ctlr->mii, i);
-			l += snprint(p+l, READSTR-l, " %4.4ux", r);
+			p = seprint(p, e, " %4.4ux", r);
 		}
-		snprint(p+l, READSTR-l, "\n");
+		p = seprint(p, e, "\n");
 	}
 
-	n = readstr(offset, a, n, p);
-
 	qunlock(&ctlr->slock);
 	poperror();
-	free(p);
 
-	return n;
+	return p;
 }
 
 static void
--- a/sys/src/9/pc/ether82543gc.c
+++ b/sys/src/9/pc/ether82543gc.c
@@ -500,19 +500,21 @@
 	"TCP Segmentation Context Fail",
 };
 
-static long
-gc82543ifstat(Ether* edev, void* a, long n, ulong offset)
+static char*
+gc82543ifstat(void *arg, char *p, char *e)
 {
+	Ether *edev;
 	Ctlr *ctlr;
-	char *p, *s;
-	int i, l, r;
+	char *s;
+	int i, r;
 	uvlong tuvl, ruvl;
 
-	p = smalloc(READSTR);
+	if(p >= e)
+		return p;
 
+	edev = arg;
 	ctlr = edev->ctlr;
 	lock(&ctlr->slock);
-	l = 0;
 	for(i = 0; i < Nstatistics; i++){
 		r = csr32r(ctlr, Statistics+i*4);
 		if((s = statistics[i]) == nil)
@@ -531,7 +533,7 @@
 				continue;
 			ctlr->statistics[i] = tuvl;
 			ctlr->statistics[i+1] = tuvl>>32;
-			l += snprint(p+l, READSTR-l, "%s: %llud %llud\n",
+			p = seprint(p, e, "%s: %llud %llud\n",
 				s, tuvl, ruvl);
 			i++;
 			break;
@@ -540,25 +542,23 @@
 			ctlr->statistics[i] += r;
 			if(ctlr->statistics[i] == 0)
 				continue;
-			l += snprint(p+l, READSTR-l, "%s: %ud %ud\n",
+			p = seprint(p, e, "%s: %ud %ud\n",
 				s, ctlr->statistics[i], r);
 			break;
 		}
 	}
 
-	l += snprint(p+l, READSTR-l, "eeprom:");
+	p = seprint(p, e, "eeprom:");
 	for(i = 0; i < 0x40; i++){
 		if(i && ((i & 0x07) == 0))
-			l += snprint(p+l, READSTR-l, "\n       ");
-		l += snprint(p+l, READSTR-l, " %4.4uX", ctlr->eeprom[i]);
+			p = seprint(p, e, "\n       ");
+		p = seprint(p, e, " %4.4uX", ctlr->eeprom[i]);
 	}
 
-	snprint(p+l, READSTR-l, "\ntxstalled %d\n", ctlr->txstalled);
-	n = readstr(offset, a, n, p);
-	free(p);
+	p = seprint(p, e, "\ntxstalled %d\n", ctlr->txstalled);
 	unlock(&ctlr->slock);
 
-	return n;
+	return p;
 }
 
 static void
@@ -1360,10 +1360,10 @@
 	edev->attach = gc82543attach;
 	edev->transmit = gc82543transmit;
 	edev->interrupt = gc82543interrupt;
-	edev->ifstat = gc82543ifstat;
 	edev->shutdown = gc82543shutdown;
 	edev->ctl = gc82543ctl;
 	edev->arg = edev;
+	edev->ifstat = gc82543ifstat;
 	edev->promiscuous = gc82543promiscuous;
 	edev->multicast = gc82543multicast;
 
--- a/sys/src/9/pc/ether82557.c
+++ b/sys/src/9/pc/ether82557.c
@@ -403,14 +403,15 @@
 	}
 }
 
-static long
-ifstat(Ether* ether, void* a, long n, ulong offset)
+static char*
+ifstat(void *a, char *p, char *e)
 {
-	char *p;
-	int i, len, phyaddr;
+	Ether *ether;
 	Ctlr *ctlr;
+	int i, phyaddr;
 	ulong dump[17];
 
+	ether = a;
 	ctlr = ether->ctlr;
 	lock(&ctlr->dlock);
 	if(waserror()){
@@ -441,56 +442,51 @@
 	poperror();
 	unlock(&ctlr->dlock);
 
-	if(n == 0)
-		return 0;
+	if(p >= e)
+		return p;
 
-	p = smalloc(READSTR);
-	len = snprint(p, READSTR, "transmit good frames: %lud\n", dump[0]);
-	len += snprint(p+len, READSTR-len, "transmit maximum collisions errors: %lud\n", dump[1]);
-	len += snprint(p+len, READSTR-len, "transmit late collisions errors: %lud\n", dump[2]);
-	len += snprint(p+len, READSTR-len, "transmit underrun errors: %lud\n", dump[3]);
-	len += snprint(p+len, READSTR-len, "transmit lost carrier sense: %lud\n", dump[4]);
-	len += snprint(p+len, READSTR-len, "transmit deferred: %lud\n", dump[5]);
-	len += snprint(p+len, READSTR-len, "transmit single collisions: %lud\n", dump[6]);
-	len += snprint(p+len, READSTR-len, "transmit multiple collisions: %lud\n", dump[7]);
-	len += snprint(p+len, READSTR-len, "transmit total collisions: %lud\n", dump[8]);
-	len += snprint(p+len, READSTR-len, "receive good frames: %lud\n", dump[9]);
-	len += snprint(p+len, READSTR-len, "receive CRC errors: %lud\n", dump[10]);
-	len += snprint(p+len, READSTR-len, "receive alignment errors: %lud\n", dump[11]);
-	len += snprint(p+len, READSTR-len, "receive resource errors: %lud\n", dump[12]);
-	len += snprint(p+len, READSTR-len, "receive overrun errors: %lud\n", dump[13]);
-	len += snprint(p+len, READSTR-len, "receive collision detect errors: %lud\n", dump[14]);
-	len += snprint(p+len, READSTR-len, "receive short frame errors: %lud\n", dump[15]);
-	len += snprint(p+len, READSTR-len, "nop: %d\n", ctlr->nop);
+	p = seprint(p, e, "transmit good frames: %lud\n", dump[0]);
+	p = seprint(p, e, "transmit maximum collisions errors: %lud\n", dump[1]);
+	p = seprint(p, e, "transmit late collisions errors: %lud\n", dump[2]);
+	p = seprint(p, e, "transmit underrun errors: %lud\n", dump[3]);
+	p = seprint(p, e, "transmit lost carrier sense: %lud\n", dump[4]);
+	p = seprint(p, e, "transmit deferred: %lud\n", dump[5]);
+	p = seprint(p, e, "transmit single collisions: %lud\n", dump[6]);
+	p = seprint(p, e, "transmit multiple collisions: %lud\n", dump[7]);
+	p = seprint(p, e, "transmit total collisions: %lud\n", dump[8]);
+	p = seprint(p, e, "receive good frames: %lud\n", dump[9]);
+	p = seprint(p, e, "receive CRC errors: %lud\n", dump[10]);
+	p = seprint(p, e, "receive alignment errors: %lud\n", dump[11]);
+	p = seprint(p, e, "receive resource errors: %lud\n", dump[12]);
+	p = seprint(p, e, "receive overrun errors: %lud\n", dump[13]);
+	p = seprint(p, e, "receive collision detect errors: %lud\n", dump[14]);
+	p = seprint(p, e, "receive short frame errors: %lud\n", dump[15]);
+	p = seprint(p, e, "nop: %d\n", ctlr->nop);
 	if(ctlr->cbqmax > ctlr->cbqmaxhw)
 		ctlr->cbqmaxhw = ctlr->cbqmax;
-	len += snprint(p+len, READSTR-len, "cbqmax: %d\n", ctlr->cbqmax);
+	p = seprint(p, e, "cbqmax: %d\n", ctlr->cbqmax);
 	ctlr->cbqmax = 0;
-	len += snprint(p+len, READSTR-len, "threshold: %d\n", ctlr->threshold);
+	p = seprint(p, e, "threshold: %d\n", ctlr->threshold);
 
-	len += snprint(p+len, READSTR-len, "eeprom:");
+	p = seprint(p, e, "eeprom:");
 	for(i = 0; i < (1<<ctlr->eepromsz); i++){
 		if(i && ((i & 0x07) == 0))
-			len += snprint(p+len, READSTR-len, "\n       ");
-		len += snprint(p+len, READSTR-len, " %4.4ux", ctlr->eeprom[i]);
+			p = seprint(p, e, "\n       ");
+		p = seprint(p, e, " %4.4ux", ctlr->eeprom[i]);
 	}
 
 	if((ctlr->eeprom[6] & 0x1F00) && !(ctlr->eeprom[6] & 0x8000)){
 		phyaddr = ctlr->eeprom[6] & 0x00FF;
-		len += snprint(p+len, READSTR-len, "\nphy %2d:", phyaddr);
+		p = seprint(p, e, "\nphy %2d:", phyaddr);
 		for(i = 0; i < 6; i++){
 			static int miir(Ctlr*, int, int);
-
-			len += snprint(p+len, READSTR-len, " %4.4ux",
-				miir(ctlr, phyaddr, i));
+			p = seprint(p, e, " %4.4ux", miir(ctlr, phyaddr, i));
 		}
 	}
 
-	snprint(p+len, READSTR-len, "\n");
-	n = readstr(offset, a, n, p);
-	free(p);
+	p = seprint(p, e, "\n");
 
-	return n;
+	return p;
 }
 
 static void
@@ -1324,11 +1320,11 @@
 	 */
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->ifstat = ifstat;
 	ether->shutdown = shutdown;
 
 	ether->promiscuous = promiscuous;
 	ether->multicast = multicast;
+	ether->ifstat = ifstat;
 	ether->arg = ether;
 
 	intrenable(ether->irq, interrupt, ether, ether->tbdf, ether->name);
--- a/sys/src/9/pc/ether82563.c
+++ b/sys/src/9/pc/ether82563.c
@@ -654,17 +654,19 @@
 	return cttab[c->type].name;
 }
 
-static long
-i82563ifstat(Ether *edev, void *a, long n, ulong offset)
+static char*
+i82563ifstat(void *arg, char *p, char *e)
 {
-	char *s, *p, *e, *stat;
+	Ether *edev;
+	Ctlr *ctlr;
+	char *stat;
 	int i, r;
 	uvlong tuvl, ruvl;
-	Ctlr *ctlr;
 
-	p = s = smalloc(READSTR);
-	e = p + READSTR;
+	if(p >= e)
+		return p;
 
+	edev = arg;
 	ctlr = edev->ctlr;
 	qlock(&ctlr->slock);
 
@@ -724,13 +726,9 @@
 		p = seprint(p, e, " %4.4ux", ctlr->eeprom[i]);
 	}
 	p = seprint(p, e, "\n");
-
-	USED(p);
-	n = readstr(offset, a, n, s);
-	free(s);
 	qunlock(&ctlr->slock);
 
-	return n;
+	return p;
 }
 
 static void
--- a/sys/src/9/pc/ether82598.c
+++ b/sys/src/9/pc/ether82598.c
@@ -332,29 +332,28 @@
 	10000,
 };
 
-static long
-ifstat(Ether *e, void *a, long n, ulong offset)
+static char*
+ifstat(void *a, char *p, char *q)
 {
 	uint i, *t;
-	char *s, *p, *q;
+	Ether *e;
 	Ctlr *c;
 
-	p = s = smalloc(READSTR);
-	q = p + READSTR;
+	if(p >= q)
+		return p;
 
+	e = a;
 	c = e->ctlr;
 	readstats(c);
 	for(i = 0; i < nelem(stattab); i++)
 		if(c->stats[i] > 0)
-			p = seprint(p, q, "%.10s  %uld\n", stattab[i].name,					c->stats[i]);
+			p = seprint(p, q, "%.10s  %uld\n", stattab[i].name, c->stats[i]);
 	t = c->speeds;
 	p = seprint(p, q, "speeds: 0:%d 1000:%d 10000:%d\n", t[0], t[1], t[2]);
 	seprint(p, q, "rdfree %d rdh %d rdt %d\n", c->rdfree, c->reg[Rdt],
 		c->reg[Rdh]);
-	n = readstr(offset, a, n, s);
-	free(s);
 
-	return n;
+	return p;
 }
 
 static void
--- a/sys/src/9/pc/ether83815.c
+++ b/sys/src/9/pc/ether83815.c
@@ -345,13 +345,14 @@
 	iunlock(&ctlr->lock);
 }
 
-static long
-ifstat(Ether* ether, void* a, long n, ulong offset)
+static char*
+ifstat(void *a, char *p, char *e)
 {
+	Ether *ether;
 	Ctlr *ctlr;
-	char *buf, *p;
-	int i, l, len;
+	int i;
 
+	ether = a;
 	ctlr = ether->ctlr;
 
 	ether->crcs = ctlr->crce;
@@ -359,54 +360,43 @@
 	ether->buffs = ctlr->rxorn+ctlr->tfu;
 	ether->overflows = ctlr->rxsovr;
 
-	if(n == 0)
-		return 0;
+	if(p >= e)
+		return p;
 
-	p = smalloc(READSTR);
-	l = snprint(p, READSTR, "Rxa: %lud\n", ctlr->rxa);
-	l += snprint(p+l, READSTR-l, "Rxo: %lud\n", ctlr->rxo);
-	l += snprint(p+l, READSTR-l, "Rlong: %lud\n", ctlr->rlong);
-	l += snprint(p+l, READSTR-l, "Runt: %lud\n", ctlr->runt);
-	l += snprint(p+l, READSTR-l, "Ise: %lud\n", ctlr->ise);
-	l += snprint(p+l, READSTR-l, "Fae: %lud\n", ctlr->fae);
-	l += snprint(p+l, READSTR-l, "Lbp: %lud\n", ctlr->lbp);
-	l += snprint(p+l, READSTR-l, "Tfu: %lud\n", ctlr->tfu);
-	l += snprint(p+l, READSTR-l, "Txa: %lud\n", ctlr->txa);
-	l += snprint(p+l, READSTR-l, "CRC Error: %lud\n", ctlr->crce);
-	l += snprint(p+l, READSTR-l, "Collision Seen: %lud\n", ctlr->col);
-	l += snprint(p+l, READSTR-l, "Frame Too Long: %lud\n", ctlr->rlong);
-	l += snprint(p+l, READSTR-l, "Runt Frame: %lud\n", ctlr->runt);
-	l += snprint(p+l, READSTR-l, "Rx Underflow Error: %lud\n", ctlr->rxorn);
-	l += snprint(p+l, READSTR-l, "Tx Underrun: %lud\n", ctlr->txurn);
-	l += snprint(p+l, READSTR-l, "Excessive Collisions: %lud\n", ctlr->ec);
-	l += snprint(p+l, READSTR-l, "Late Collision: %lud\n", ctlr->owc);
-	l += snprint(p+l, READSTR-l, "Loss of Carrier: %lud\n", ctlr->crs);
-	l += snprint(p+l, READSTR-l, "Parity: %lud\n", ctlr->dperr);
-	l += snprint(p+l, READSTR-l, "Aborts: %lud\n", ctlr->rmabt+ctlr->rtabt);
-	l += snprint(p+l, READSTR-l, "RX Status overrun: %lud\n", ctlr->rxsover);
-	snprint(p+l, READSTR-l, "ntqmax: %d\n", ctlr->ntqmax);
+	p = seprint(p, e, "Rxa: %lud\n", ctlr->rxa);
+	p = seprint(p, e, "Rxo: %lud\n", ctlr->rxo);
+	p = seprint(p, e, "Rlong: %lud\n", ctlr->rlong);
+	p = seprint(p, e, "Runt: %lud\n", ctlr->runt);
+	p = seprint(p, e, "Ise: %lud\n", ctlr->ise);
+	p = seprint(p, e, "Fae: %lud\n", ctlr->fae);
+	p = seprint(p, e, "Lbp: %lud\n", ctlr->lbp);
+	p = seprint(p, e, "Tfu: %lud\n", ctlr->tfu);
+	p = seprint(p, e, "Txa: %lud\n", ctlr->txa);
+	p = seprint(p, e, "CRC Error: %lud\n", ctlr->crce);
+	p = seprint(p, e, "Collision Seen: %lud\n", ctlr->col);
+	p = seprint(p, e, "Frame Too Long: %lud\n", ctlr->rlong);
+	p = seprint(p, e, "Runt Frame: %lud\n", ctlr->runt);
+	p = seprint(p, e, "Rx Underflow Error: %lud\n", ctlr->rxorn);
+	p = seprint(p, e, "Tx Underrun: %lud\n", ctlr->txurn);
+	p = seprint(p, e, "Excessive Collisions: %lud\n", ctlr->ec);
+	p = seprint(p, e, "Late Collision: %lud\n", ctlr->owc);
+	p = seprint(p, e, "Loss of Carrier: %lud\n", ctlr->crs);
+	p = seprint(p, e, "Parity: %lud\n", ctlr->dperr);
+	p = seprint(p, e, "Aborts: %lud\n", ctlr->rmabt+ctlr->rtabt);
+	p = seprint(p, e, "RX Status overrun: %lud\n", ctlr->rxsover);
+	p = seprint(p, e, "ntqmax: %d\n", ctlr->ntqmax);
 	ctlr->ntqmax = 0;
-	buf = a;
-	len = readstr(offset, buf, n, p);
-	if(offset > l)
-		offset -= l;
-	else
-		offset = 0;
-	buf += len;
-	n -= len;
 
-	l = snprint(p, READSTR, "srom:");
+	p = seprint(p, e, "srom:");
 	for(i = 0; i < nelem(ctlr->srom); i++){
 		if(i && ((i & 0x0F) == 0))
-			l += snprint(p+l, READSTR-l, "\n     ");
-		l += snprint(p+l, READSTR-l, " %4.4uX", ctlr->srom[i]);
+			p = seprint(p, e, "\n     ");
+		p = seprint(p, e, " %4.4uX", ctlr->srom[i]);
 	}
 
-	snprint(p+l, READSTR-l, "\n");
-	len += readstr(offset, buf, n, p);
-	free(p);
+	p = seprint(p, e, "\n");
 
-	return len;
+	return p;
 }
 
 static void
@@ -1216,9 +1206,9 @@
 	 */
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->ifstat = ifstat;
 
 	ether->arg = ether;
+	ether->ifstat = ifstat;
 	ether->promiscuous = promiscuous;
 	ether->multicast = multicast;
 	ether->shutdown = shutdown;
--- a/sys/src/9/pc/ether8390.c
+++ b/sys/src/9/pc/ether8390.c
@@ -791,8 +791,6 @@
 	 */
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->ifstat = 0;
-
 	ether->promiscuous = promiscuous;
 	ether->multicast = multicast;
 	ether->arg = ether;
--- a/sys/src/9/pc/etherdp83820.c
+++ b/sys/src/9/pc/etherdp83820.c
@@ -902,13 +902,14 @@
 	}
 }
 
-static long
-dp83820ifstat(Ether* edev, void* a, long n, ulong offset)
+static char*
+dp83820ifstat(void *a, char *p, char *e)
 {
-	char *p;
+	Ether *edev;
 	Ctlr *ctlr;
-	int i, l, r;
+	int i, r;
 
+	edev = a;
 	ctlr = edev->ctlr;
 
 	edev->crcs = ctlr->mibd[Mibd+(1*sizeof(int))];
@@ -916,49 +917,44 @@
 	edev->buffs = ctlr->mibd[Mibd+(5*sizeof(int))];
 	edev->overflows = ctlr->mibd[Mibd+(2*sizeof(int))];
 
-	if(n == 0)
-		return 0;
+	if(p >= e)
+		return p;
 
-	p = smalloc(READSTR);
-	l = 0;
 	for(i = 0; i < Nmibd; i++){
 		r = csr32r(ctlr, Mibd+(i*sizeof(int)));
 		ctlr->mibd[i] += r & 0xFFFF;
 		if(ctlr->mibd[i] != 0 && dp83820mibs[i] != nil)
-			l += snprint(p+l, READSTR-l, "%s: %ud %ud\n",
+			p = seprint(p, e, "%s: %ud %ud\n",
 				dp83820mibs[i], ctlr->mibd[i], r);
 	}
-	l += snprint(p+l, READSTR-l, "rxidle %d\n", ctlr->rxidle);
-	l += snprint(p+l, READSTR-l, "ec %d\n", ctlr->ec);
-	l += snprint(p+l, READSTR-l, "owc %d\n", ctlr->owc);
-	l += snprint(p+l, READSTR-l, "ed %d\n", ctlr->ed);
-	l += snprint(p+l, READSTR-l, "crs %d\n", ctlr->crs);
-	l += snprint(p+l, READSTR-l, "tfu %d\n", ctlr->tfu);
-	l += snprint(p+l, READSTR-l, "txa %d\n", ctlr->txa);
+	p = seprint(p, e, "rxidle %d\n", ctlr->rxidle);
+	p = seprint(p, e, "ec %d\n", ctlr->ec);
+	p = seprint(p, e, "owc %d\n", ctlr->owc);
+	p = seprint(p, e, "ed %d\n", ctlr->ed);
+	p = seprint(p, e, "crs %d\n", ctlr->crs);
+	p = seprint(p, e, "tfu %d\n", ctlr->tfu);
+	p = seprint(p, e, "txa %d\n", ctlr->txa);
 
-	l += snprint(p+l, READSTR-l, "rom:");
+	p = seprint(p, e, "rom:");
 	for(i = 0; i < 0x10; i++){
 		if(i && ((i & 0x07) == 0))
-			l += snprint(p+l, READSTR-l, "\n    ");
-		l += snprint(p+l, READSTR-l, " %4.4uX", ctlr->eeprom[i]);
+			p = seprint(p, e, "\n    ");
+		p = seprint(p, e, " %4.4uX", ctlr->eeprom[i]);
 	}
-	l += snprint(p+l, READSTR-l, "\n");
+	p = seprint(p, e, "\n");
 
 	if(ctlr->mii != nil && ctlr->mii->curphy != nil){
-		l += snprint(p+l, READSTR-l, "phy:");
+		p = seprint(p, e, "phy:");
 		for(i = 0; i < NMiiPhyr; i++){
 			if(i && ((i & 0x07) == 0))
-				l += snprint(p+l, READSTR-l, "\n    ");
+				p = seprint(p, e, "\n    ");
 			r = miimir(ctlr->mii, i);
-			l += snprint(p+l, READSTR-l, " %4.4uX", r);
+			p = seprint(p, e, " %4.4uX", r);
 		}
-		snprint(p+l, READSTR-l, "\n");
+		p = seprint(p, e, "\n");
 	}
 
-	n = readstr(offset, a, n, p);
-	free(p);
-
-	return n;
+	return p;
 }
 
 static void
@@ -1235,9 +1231,9 @@
 
 	edev->attach = dp83820attach;
 	edev->transmit = dp83820transmit;
-	edev->ifstat = dp83820ifstat;
 
 	edev->arg = edev;
+	edev->ifstat = dp83820ifstat;
 	edev->promiscuous = dp83820promiscuous;
 	edev->multicast = dp83820multicast;
 	edev->shutdown = dp83820shutdown;
--- a/sys/src/9/pc/etherelnk3.c
+++ b/sys/src/9/pc/etherelnk3.c
@@ -1162,16 +1162,16 @@
 	iunlock(&ctlr->wlock);
 }
 
-static long
-ifstat(Ether* ether, void* a, long n, ulong offset)
+static char*
+ifstat(void *a, char *p, char *e)
 {
-	char *p;
-	int len;
+	Ether *ether;
 	Ctlr *ctlr;
 
-	if(n == 0)
-		return 0;
+	if(p >= e)
+		return p;
 
+	ether = a;
 	ctlr = ether->ctlr;
 
 	ilock(&ctlr->wlock);
@@ -1178,38 +1178,25 @@
 	statistics(ether);
 	iunlock(&ctlr->wlock);
 
-	p = smalloc(READSTR);
-	len = snprint(p, READSTR, "interrupts: %lud\n", ctlr->interrupts);
-	len += snprint(p+len, READSTR-len, "bogusinterrupts: %lud\n", ctlr->bogusinterrupts);
-	len += snprint(p+len, READSTR-len, "timer: %lud %lud\n",
-		ctlr->timer[0], ctlr->timer[1]);
-	len += snprint(p+len, READSTR-len, "carrierlost: %lud\n",
-		ctlr->stats[CarrierLost]);
-	len += snprint(p+len, READSTR-len, "sqeerrors: %lud\n",
-		ctlr->stats[SqeErrors]);
-	len += snprint(p+len, READSTR-len, "multiplecolls: %lud\n",
-		ctlr->stats[MultipleColls]);
-	len += snprint(p+len, READSTR-len, "singlecollframes: %lud\n",
-		ctlr->stats[SingleCollFrames]);
-	len += snprint(p+len, READSTR-len, "latecollisions: %lud\n",
-		ctlr->stats[LateCollisions]);
-	len += snprint(p+len, READSTR-len, "rxoverruns: %lud\n",
-		ctlr->stats[RxOverruns]);
-	len += snprint(p+len, READSTR-len, "framesxmittedok: %lud\n",
-		ctlr->stats[FramesXmittedOk]);
-	len += snprint(p+len, READSTR-len, "framesrcvdok: %lud\n",
-		ctlr->stats[FramesRcvdOk]);
-	len += snprint(p+len, READSTR-len, "framesdeferred: %lud\n",
-		ctlr->stats[FramesDeferred]);
-	len += snprint(p+len, READSTR-len, "bytesrcvdok: %lud\n",
-		ctlr->stats[BytesRcvdOk]);
-	len += snprint(p+len, READSTR-len, "bytesxmittedok: %lud\n",
-		ctlr->stats[BytesRcvdOk+1]);
+	p = seprint(p, e, "interrupts: %lud\n", ctlr->interrupts);
+	p = seprint(p, e, "bogusinterrupts: %lud\n", ctlr->bogusinterrupts);
+	p = seprint(p, e, "timer: %lud %lud\n",	ctlr->timer[0], ctlr->timer[1]);
+	p = seprint(p, e, "carrierlost: %lud\n", ctlr->stats[CarrierLost]);
+	p = seprint(p, e, "sqeerrors: %lud\n", ctlr->stats[SqeErrors]);
+	p = seprint(p, e, "multiplecolls: %lud\n", ctlr->stats[MultipleColls]);
+	p = seprint(p, e, "singlecollframes: %lud\n", ctlr->stats[SingleCollFrames]);
+	p = seprint(p, e, "latecollisions: %lud\n", ctlr->stats[LateCollisions]);
+	p = seprint(p, e, "rxoverruns: %lud\n", ctlr->stats[RxOverruns]);
+	p = seprint(p, e, "framesxmittedok: %lud\n", ctlr->stats[FramesXmittedOk]);
+	p = seprint(p, e, "framesrcvdok: %lud\n", ctlr->stats[FramesRcvdOk]);
+	p = seprint(p, e, "framesdeferred: %lud\n", ctlr->stats[FramesDeferred]);
+	p = seprint(p, e, "bytesrcvdok: %lud\n", ctlr->stats[BytesRcvdOk]);
+	p = seprint(p, e, "bytesxmittedok: %lud\n", ctlr->stats[BytesRcvdOk+1]);
 
 	if(ctlr->upenabled){
 		if(ctlr->upqmax > ctlr->upqmaxhw)
 			ctlr->upqmaxhw = ctlr->upqmax;
-		len += snprint(p+len, READSTR-len, "up: q %lud i %lud m %d h %d s %lud\n",
+		p = seprint(p, e, "up: q %lud i %lud m %d h %d s %lud\n",
 			ctlr->upqueued, ctlr->upinterrupts,
 			ctlr->upqmax, ctlr->upqmaxhw, ctlr->upstalls);
 		ctlr->upqmax = 0;
@@ -1217,17 +1204,14 @@
 	if(ctlr->dnenabled){
 		if(ctlr->dnqmax > ctlr->dnqmaxhw)
 			ctlr->dnqmaxhw = ctlr->dnqmax;
-		len += snprint(p+len, READSTR-len, "dn: q %lud i %lud m %d h %d\n",
+		p = seprint(p, e, "dn: q %lud i %lud m %d h %d\n",
 			ctlr->dnqueued, ctlr->dninterrupts, ctlr->dnqmax, ctlr->dnqmaxhw);
 		ctlr->dnqmax = 0;
 	}
 
-	snprint(p+len, READSTR-len, "badssd: %lud\n", ctlr->stats[BytesRcvdOk+2]);
+	p = seprint(p, e, "badssd: %lud\n", ctlr->stats[BytesRcvdOk+2]);
 
-	n = readstr(offset, a, n, p);
-	free(p);
-
-	return n;
+	return p;
 }
 
 static void
@@ -2125,11 +2109,11 @@
 	 */
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->ifstat = ifstat;
 
 	ether->promiscuous = promiscuous;
 	ether->multicast = multicast;
 	ether->shutdown = shutdown;
+	ether->ifstat = ifstat;
 	ether->arg = ether;
 
 	intrenable(ether->irq, interrupt, ether, ether->tbdf, ether->name);
--- a/sys/src/9/pc/etherga620.c
+++ b/sys/src/9/pc/etherga620.c
@@ -332,39 +332,36 @@
 	USED(ctlr);
 }
 
-static long
-ga620ifstat(Ether* edev, void* a, long n, ulong offset)
+static char*
+ga620ifstat(void *arg, char *p, char *e)
 {
-	char *p;
+	Ether *edev;
 	Ctlr *ctlr;
-	int i, l, r;
+	int i, r;
 
+	if(p >= e)
+		return p;
+
+	edev = arg;
 	ctlr = edev->ctlr;
 
-	if(n == 0)
-		return 0;
-	p = smalloc(READSTR);
-	l = 0;
 	for(i = 0; i < 256; i++){
 		if((r = ctlr->gib->statistics[i]) == 0)
 			continue;
-		l += snprint(p+l, READSTR-l, "%d: %ud\n", i, r);
+		p = seprint(p, e, "%d: %ud\n", i, r);
 	}
 
-	l += snprint(p+l, READSTR-l, "interrupts: %ud\n", ctlr->interrupts);
-	l += snprint(p+l, READSTR-l, "mi: %ud\n", ctlr->mi);
-	l += snprint(p+l, READSTR-l, "ticks: %llud\n", ctlr->ticks);
-	l += snprint(p+l, READSTR-l, "coalupdateonly: %d\n", ctlr->coalupdateonly);
-	l += snprint(p+l, READSTR-l, "hardwarecksum: %d\n", ctlr->hardwarecksum);
-	l += snprint(p+l, READSTR-l, "rct: %d\n", ctlr->rct);
-	l += snprint(p+l, READSTR-l, "sct: %d\n", ctlr->sct);
-	l += snprint(p+l, READSTR-l, "smcbd: %d\n", ctlr->smcbd);
-	snprint(p+l, READSTR-l, "rmcbd: %d\n", ctlr->rmcbd);
+	p = seprint(p, e, "interrupts: %ud\n", ctlr->interrupts);
+	p = seprint(p, e, "mi: %ud\n", ctlr->mi);
+	p = seprint(p, e, "ticks: %llud\n", ctlr->ticks);
+	p = seprint(p, e, "coalupdateonly: %d\n", ctlr->coalupdateonly);
+	p = seprint(p, e, "hardwarecksum: %d\n", ctlr->hardwarecksum);
+	p = seprint(p, e, "rct: %d\n", ctlr->rct);
+	p = seprint(p, e, "sct: %d\n", ctlr->sct);
+	p = seprint(p, e, "smcbd: %d\n", ctlr->smcbd);
+	p = seprint(p, e, "rmcbd: %d\n", ctlr->rmcbd);
 
-	n = readstr(offset, a, n, p);
-	free(p);
-
-	return n;
+	return p;
 }
 
 static long
@@ -1266,10 +1263,10 @@
 	 */
 	edev->attach = ga620attach;
 	edev->transmit = ga620transmit;
-	edev->ifstat = ga620ifstat;
 	edev->ctl = ga620ctl;
 
 	edev->arg = edev;
+	edev->ifstat = ga620ifstat;
 	edev->promiscuous = ga620promiscuous;
 	edev->multicast = ga620multicast;
 	edev->shutdown = ga620shutdown;
--- a/sys/src/9/pc/etherigbe.c
+++ b/sys/src/9/pc/etherigbe.c
@@ -587,16 +587,19 @@
 	"TCP Segmentation Context Fail",
 };
 
-static long
-igbeifstat(Ether* edev, void* a, long n, ulong offset)
+static char*
+igbeifstat(void *arg, char *p, char *e)
 {
+	Ether *edev;
 	Ctlr *ctlr;
-	char *p, *s;
-	int i, l, r;
+	char *s;
+	int i, r;
 	uvlong tuvl, ruvl;
 
-	p = smalloc(READSTR);
-	l = 0;
+	if(p >= e)
+		return p;
+
+	edev = arg;
 	ctlr = edev->ctlr;
 	qlock(&ctlr->slock);
 	for(i = 0; i < Nstatistics; i++){
@@ -617,8 +620,7 @@
 				continue;
 			ctlr->statistics[i] = tuvl;
 			ctlr->statistics[i+1] = tuvl>>32;
-			l += snprint(p+l, READSTR-l, "%s: %llud %llud\n",
-				s, tuvl, ruvl);
+			p = seprint(p, e, "%s: %llud %llud\n", s, tuvl, ruvl);
 			i++;
 			break;
 
@@ -626,46 +628,39 @@
 			ctlr->statistics[i] += r;
 			if(ctlr->statistics[i] == 0)
 				continue;
-			l += snprint(p+l, READSTR-l, "%s: %ud %ud\n",
-				s, ctlr->statistics[i], r);
+			p = seprint(p, e, "%s: %ud %ud\n", s, ctlr->statistics[i], r);
 			break;
 		}
 	}
 
-	l += snprint(p+l, READSTR-l, "lintr: %ud %ud\n",
-		ctlr->lintr, ctlr->lsleep);
-	l += snprint(p+l, READSTR-l, "rintr: %ud %ud\n",
-		ctlr->rintr, ctlr->rsleep);
-	l += snprint(p+l, READSTR-l, "tintr: %ud %ud\n",
-		ctlr->tintr, ctlr->txdw);
-	l += snprint(p+l, READSTR-l, "ixcs: %ud %ud %ud\n",
-		ctlr->ixsm, ctlr->ipcs, ctlr->tcpcs);
-	l += snprint(p+l, READSTR-l, "rdtr: %ud\n", ctlr->rdtr);
-	l += snprint(p+l, READSTR-l, "Ctrlext: %08x\n", csr32r(ctlr, Ctrlext));
+	p = seprint(p, e, "lintr: %ud %ud\n", ctlr->lintr, ctlr->lsleep);
+	p = seprint(p, e,  "rintr: %ud %ud\n", ctlr->rintr, ctlr->rsleep);
+	p = seprint(p, e, "tintr: %ud %ud\n", ctlr->tintr, ctlr->txdw);
+	p = seprint(p, e, "ixcs: %ud %ud %ud\n", ctlr->ixsm, ctlr->ipcs, ctlr->tcpcs);
+	p = seprint(p, e, "rdtr: %ud\n", ctlr->rdtr);
+	p = seprint(p, e, "Ctrlext: %08x\n", csr32r(ctlr, Ctrlext));
 
-	l += snprint(p+l, READSTR-l, "eeprom:");
+	p = seprint(p, e, "eeprom:");
 	for(i = 0; i < 0x40; i++){
 		if(i && ((i & 0x07) == 0))
-			l += snprint(p+l, READSTR-l, "\n       ");
-		l += snprint(p+l, READSTR-l, " %4.4uX", ctlr->eeprom[i]);
+			p = seprint(p, e, "\n       ");
+		p = seprint(p, e, " %4.4uX", ctlr->eeprom[i]);
 	}
-	l += snprint(p+l, READSTR-l, "\n");
+	p = seprint(p, e, "\n");
 
 	if(ctlr->mii != nil && ctlr->mii->curphy != nil){
-		l += snprint(p+l, READSTR-l, "phy:   ");
+		p = seprint(p, e, "phy:   ");
 		for(i = 0; i < NMiiPhyr; i++){
 			if(i && ((i & 0x07) == 0))
-				l += snprint(p+l, READSTR-l, "\n       ");
+				p = seprint(p, e, "\n       ");
 			r = miimir(ctlr->mii, i);
-			l += snprint(p+l, READSTR-l, " %4.4uX", r);
+			p = seprint(p, e, " %4.4uX", r);
 		}
-		snprint(p+l, READSTR-l, "\n");
+		p = seprint(p, e, "\n");
 	}
-	n = readstr(offset, a, n, p);
-	free(p);
 	qunlock(&ctlr->slock);
 
-	return n;
+	return p;
 }
 
 enum {
@@ -2021,7 +2016,6 @@
 	 */
 	edev->attach = igbeattach;
 	edev->transmit = igbetransmit;
-	edev->ifstat = igbeifstat;
 	edev->ctl = igbectl;
 
 	edev->arg = edev;
@@ -2028,6 +2022,7 @@
 	edev->promiscuous = igbepromiscuous;
 	edev->shutdown = igbeshutdown;
 	edev->multicast = igbemulticast;
+	edev->ifstat = igbeifstat;
 
 	intrenable(edev->irq, igbeinterrupt, edev, edev->tbdf, edev->name);
 
--- a/sys/src/9/pc/etherm10g.c
+++ b/sys/src/9/pc/etherm10g.c
@@ -1348,18 +1348,19 @@
 	return i;
 }
 
-static long
-m10gifstat(Ether *e, void *v, long n, ulong off)
+static char*
+m10gifstat(void *arg, char *p, char *e)
 {
-	char *p;
-	Ctlr *c;
+	Ether *edev = arg;
+	Ctlr *c = edev->ctlr;
 	Stats s;
 
-	c = e->ctlr;
-	p = smalloc(READSTR);
+	if(p >= e)
+		return p;
+
 	/* no point in locking this because this is done via dma. */
 	memmove(&s, c->stats, sizeof s);
-	snprint(p, READSTR,
+	return seprint(p, e,
 		"txcnt = %lud\n"  "linkstat = %lud\n" 	"dlink = %lud\n"
 		"derror = %lud\n" "drunt = %lud\n" 	"doverrun = %lud\n"
 		"dnosm = %lud\n"  "dnobg = %lud\n"	"nrdma = %lud\n"
@@ -1381,10 +1382,6 @@
 		c->sm.cnt, c->sm.i, c->sm.pool->n, lstcount(c->sm.pool->head),
 		c->bg.cnt, c->bg.i, c->bg.pool->n, lstcount(c->bg.pool->head),
 		c->tx.segsz, gbit32((uchar*)c->coal));
-
-	n = readstr(off, v, n, p);
-	free(p);
-	return n;
 }
 
 //static void
@@ -1604,12 +1601,12 @@
 	e->attach = m10gattach;
 	e->transmit = m10gtransmit;
 	e->interrupt = m10ginterrupt;
-	e->ifstat = m10gifstat;
 	e->ctl = m10gctl;
 //	e->power = m10gpower;
 	e->shutdown = m10gshutdown;
 
 	e->arg = e;
+	e->ifstat = m10gifstat;
 	e->promiscuous = m10gpromiscuous;
 	e->multicast = m10gmulticast;
 
--- a/sys/src/9/pc/etherrt2860.c
+++ b/sys/src/9/pc/etherrt2860.c
@@ -1287,15 +1287,12 @@
 	return 0;
 }
 
-static long
-rt2860ifstat(Ether *edev, void *buf, long n, ulong off)
+static char*
+rt2860ifstat(void *a, char *s, char *e)
 {
-	Ctlr *ctlr;
-
-	ctlr = edev->ctlr;
-	if(ctlr->wifi)
-		return wifistat(ctlr->wifi, buf, n, off);
-	return 0;
+	Ether *edev = a;
+	Ctlr *ctlr = edev->ctlr;
+	return wifistat(ctlr->wifi, s, e);
 }
 
 static void
@@ -3543,8 +3540,8 @@
 	edev->tbdf = ctlr->pdev->tbdf;
 	edev->arg = edev;
 	edev->attach = rt2860attach;
-	edev->ifstat = rt2860ifstat;
 	edev->ctl = rt2860ctl;
+	edev->ifstat = rt2860ifstat;
 	edev->promiscuous = rt2860promiscuous;
 	edev->multicast = rt2860multicast;
 	edev->mbps = 10;
--- a/sys/src/9/pc/ethersmc.c
+++ b/sys/src/9/pc/ethersmc.c
@@ -624,8 +624,8 @@
 	iunlock(ctlr);
 }
 
-static long
-ifstat(Ether* ether, void* a, long n, ulong offset)
+static char*
+ifstat(void *a, char *p, char *e)
 {
 	static char *chiprev[] = {
 		[3] 	"92",
@@ -635,14 +635,15 @@
 		[9]	"110",
 	};
 
+	Ether* ether;
 	Smc91xx* ctlr;
-	char* p;
-	int r, len;
 	char* s;
-	
-	if (n == 0)
-		return 0;
+	int r;
 
+	if (p >= e)
+		return p;
+
+	ether = a;
 	ctlr = ether->ctlr;
 
 	s = 0;
@@ -659,21 +660,16 @@
 		}
 	}
 
-	p = smalloc(READSTR);
-	len = snprint(p, READSTR, "rev: 91c%s\n", (s) ? s : "???");
-	len += snprint(p + len, READSTR - len, "rxovrn: %uld\n", ctlr->rovrn);
-	len += snprint(p + len, READSTR - len, "lcar: %uld\n", ctlr->lcar);
-	len += snprint(p + len, READSTR - len, "col: %uld\n", ctlr->col);
-	len += snprint(p + len, READSTR - len, "16col: %uld\n", ctlr->scol);
-	len += snprint(p + len, READSTR - len, "mcol: %uld\n", ctlr->mcol);
-	len += snprint(p + len, READSTR - len, "lcol: %uld\n", ctlr->lcol);
-	len += snprint(p + len, READSTR - len, "dfr: %uld\n", ctlr->dfr);
-	USED(len);
+	p = seprint(p, e, "rev: 91c%s\n", (s) ? s : "???");
+	p = seprint(p, e, "rxovrn: %uld\n", ctlr->rovrn);
+	p = seprint(p, e, "lcar: %uld\n", ctlr->lcar);
+	p = seprint(p, e, "col: %uld\n", ctlr->col);
+	p = seprint(p, e, "16col: %uld\n", ctlr->scol);
+	p = seprint(p, e, "mcol: %uld\n", ctlr->mcol);
+	p = seprint(p, e, "lcol: %uld\n", ctlr->lcol);
+	p = seprint(p, e, "dfr: %uld\n", ctlr->dfr);
 
-	n = readstr(offset, a, n, p);
-	free(p);
-	
-	return n;
+	return p;
 }
 
 static int
@@ -766,9 +762,9 @@
 
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->ifstat = ifstat;
 	ether->promiscuous = promiscuous;
 	ether->multicast = multicast;
+	ether->ifstat = ifstat;
 	ether->arg = ether;
 
 	iunlock(ctlr);
--- a/sys/src/9/pc/ethervgbe.c
+++ b/sys/src/9/pc/ethervgbe.c
@@ -454,28 +454,25 @@
 	return r;
 }
 
-static long
-vgbeifstat(Ether* edev, void* a, long n, ulong offset)
+static char*
+vgbeifstat(void *a, char *p, char *e)
 {
-	char* p;
+	Ether* edev;
 	Ctlr* ctlr;
-	int l;
 
+	if(p >= e)
+		return p;
+
+	edev = a;
 	ctlr = edev->ctlr;
 
-	p = smalloc(READSTR);
-	l = 0;
-	l += snprint(p+l, READSTR-l, "tx: %uld\n", ctlr->stats.tx);
-	l += snprint(p+l, READSTR-l, "tx [errs]: %uld\n", ctlr->stats.txe);
-	l += snprint(p+l, READSTR-l, "tx [no entry]: %uld\n", ctlr->stats.txentry);
-	l += snprint(p+l, READSTR-l, "rx: %uld\n", ctlr->stats.rx);
-	l += snprint(p+l, READSTR-l, "intr: %uld\n", ctlr->stats.intr);
-	snprint(p+l, READSTR-l, "\n");
+	p = seprint(p, e, "tx: %uld\n", ctlr->stats.tx);
+	p = seprint(p, e, "tx [errs]: %uld\n", ctlr->stats.txe);
+	p = seprint(p, e, "tx [no entry]: %uld\n", ctlr->stats.txentry);
+	p = seprint(p, e, "rx: %uld\n", ctlr->stats.rx);
+	p = seprint(p, e, "intr: %uld\n", ctlr->stats.intr);
 
-	n = readstr(offset, a, n, p);
-	free(p);
-
-	return n;
+	return p;
 }
 
 static char* vgbeisr_info[] = {
@@ -1267,10 +1264,10 @@
 	memmove(edev->ea, ctlr->ea, Eaddrlen);
 	edev->attach = vgbeattach;
 	edev->transmit = vgbetransmit;
-	edev->ifstat = vgbeifstat;
 	edev->promiscuous = vgbepromiscuous;
 	edev->multicast = vgbemulticast;
 //	edev->shutdown = vgbeshutdown;
+	edev->ifstat = vgbeifstat;
 	edev->ctl = vgbectl;
 	edev->arg = edev;
 
--- a/sys/src/9/pc/ethervirtio.c
+++ b/sys/src/9/pc/ethervirtio.c
@@ -436,35 +436,34 @@
 	kproc(name, txproc, edev);
 }
 
-static long
-ifstat(Ether *edev, void *a, long n, ulong offset)
+static char*
+ifstat(void *a, char *p, char *e)
 {
-	int i, l;
-	char *p;
+	int i;
+	Ether *edev;
 	Ctlr *ctlr;
 	Vqueue *q;
 
+	if(p >= e)
+		return p;
+
+	edev = a;
 	ctlr = edev->ctlr;
 
-	p = smalloc(READSTR);
-
-	l = snprint(p, READSTR, "devfeat %32.32lub\n", ctlr->feat);
-	l += snprint(p+l, READSTR-l, "drvfeat %32.32lub\n", inl(ctlr->port+Qdrvfeat));
-	l += snprint(p+l, READSTR-l, "devstatus %8.8ub\n", inb(ctlr->port+Qstatus));
+	p = seprint(p, e, "devfeat %32.32lub\n", ctlr->feat);
+	p = seprint(p, e, "drvfeat %32.32lub\n", inl(ctlr->port+Qdrvfeat));
+	p = seprint(p, e, "devstatus %8.8ub\n", inb(ctlr->port+Qstatus));
 	if(ctlr->feat & Fstatus)
-		l += snprint(p+l, READSTR-l, "netstatus %8.8ub\n",  inb(ctlr->port+Qnetstatus));
+		p = seprint(p, e, "netstatus %8.8ub\n",  inb(ctlr->port+Qnetstatus));
 
 	for(i = 0; i < ctlr->nqueue; i++){
 		q = &ctlr->queue[i];
-		l += snprint(p+l, READSTR-l,
+		p = seprint(p, e,
 			"vq%d %#p size %d avail->idx %d used->idx %d lastused %hud nintr %ud nnote %ud\n",
 			i, q, q->qsize, q->avail->idx, q->used->idx, q->lastused, q->nintr, q->nnote);
 	}
 
-	n = readstr(offset, a, n, p);
-	free(p);
-
-	return n;
+	return p;
 }
 
 static void
--- a/sys/src/9/pc/ethervt6102.c
+++ b/sys/src/9/pc/ethervt6102.c
@@ -337,55 +337,54 @@
 	"Excessive Collisions",
 };
 
-static long
-vt6102ifstat(Ether* edev, void* a, long n, ulong offset)
+static char*
+vt6102ifstat(void *arg, char *p, char *e)
 {
-	char *p;
+	Ether *edev;
 	Ctlr *ctlr;
-	int i, l, r;
+	int i, r;
 
+	if(p >= e)
+		return p;
+
+	edev = arg;
 	ctlr = edev->ctlr;
 
-	p = smalloc(READSTR);
-	l = 0;
 	for(i = 0; i < Nrxstats; i++){
-		l += snprint(p+l, READSTR-l, "%s: %ud\n",
+		p = seprint(p, e, "%s: %ud\n",
 			rxstats[i], ctlr->rxstats[i]);
 	}
 	for(i = 0; i < Ntxstats; i++){
 		if(txstats[i] == nil)
 			continue;
-		l += snprint(p+l, READSTR-l, "%s: %ud\n",
+		p = seprint(p, e, "%s: %ud\n",
 			txstats[i], ctlr->txstats[i]);
 	}
-	l += snprint(p+l, READSTR-l, "cls: %ud\n", ctlr->cls);
-	l += snprint(p+l, READSTR-l, "intr: %ud\n", ctlr->intr);
-	l += snprint(p+l, READSTR-l, "lintr: %ud\n", ctlr->lintr);
-	l += snprint(p+l, READSTR-l, "lsleep: %ud\n", ctlr->lsleep);
-	l += snprint(p+l, READSTR-l, "rintr: %ud\n", ctlr->rintr);
-	l += snprint(p+l, READSTR-l, "tintr: %ud\n", ctlr->tintr);
-	l += snprint(p+l, READSTR-l, "taligned: %ud\n", ctlr->taligned);
-	l += snprint(p+l, READSTR-l, "tsplit: %ud\n", ctlr->tsplit);
-	l += snprint(p+l, READSTR-l, "tcopied: %ud\n", ctlr->tcopied);
-	l += snprint(p+l, READSTR-l, "txdw: %ud\n", ctlr->txdw);
-	l += snprint(p+l, READSTR-l, "tft: %ud\n", ctlr->tft);
+	p = seprint(p, e, "cls: %ud\n", ctlr->cls);
+	p = seprint(p, e, "intr: %ud\n", ctlr->intr);
+	p = seprint(p, e, "lintr: %ud\n", ctlr->lintr);
+	p = seprint(p, e, "lsleep: %ud\n", ctlr->lsleep);
+	p = seprint(p, e, "rintr: %ud\n", ctlr->rintr);
+	p = seprint(p, e, "tintr: %ud\n", ctlr->tintr);
+	p = seprint(p, e, "taligned: %ud\n", ctlr->taligned);
+	p = seprint(p, e, "tsplit: %ud\n", ctlr->tsplit);
+	p = seprint(p, e, "tcopied: %ud\n", ctlr->tcopied);
+	p = seprint(p, e, "txdw: %ud\n", ctlr->txdw);
+	p = seprint(p, e, "tft: %ud\n", ctlr->tft);
 
 	if(ctlr->mii != nil && ctlr->mii->curphy != nil){
-		l += snprint(p+l, READSTR-l, "phy:   ");
+		p = seprint(p, e, "phy:   ");
 		for(i = 0; i < NMiiPhyr; i++){
 			if(i && ((i & 0x07) == 0))
-				l += snprint(p+l, READSTR-l, "\n       ");
+				p = seprint(p, e, "\n       ");
 			r = miimir(ctlr->mii, i);
-			l += snprint(p+l, READSTR-l, " %4.4uX", r);
+			p = seprint(p, e, " %4.4uX", r);
 		}
-		snprint(p+l, READSTR-l, "\n");
+		p = seprint(p, e, "\n");
 	}
-	snprint(p+l, READSTR-l, "\n");
+	p = seprint(p, e, "\n");
 
-	n = readstr(offset, a, n, p);
-	free(p);
-
-	return n;
+	return p;
 }
 
 static void
@@ -1031,10 +1030,10 @@
 	 */
 	edev->attach = vt6102attach;
 	edev->transmit = vt6102transmit;
-	edev->ifstat = vt6102ifstat;
 	edev->ctl = nil;
 
 	edev->arg = edev;
+	edev->ifstat = vt6102ifstat;
 	edev->promiscuous = vt6102promiscuous;
 	edev->multicast = vt6102multicast;
 
--- a/sys/src/9/pc/ethervt6105m.c
+++ b/sys/src/9/pc/ethervt6105m.c
@@ -431,17 +431,19 @@
 	"Excessive Collisions",
 };
 
-static long
-vt6105Mifstat(Ether* edev, void* a, long n, ulong offset)
+static char*
+vt6105Mifstat(void *arg, char *p, char *e)
 {
-	int i, r;
+	Ether *edev;
 	Ctlr *ctlr;
-	char *alloc, *e, *p;
+	int i, r;
 
+	if(p >= e)
+		return p;
+
+	edev = arg;
 	ctlr = edev->ctlr;
 	
-	p = alloc = smalloc(READSTR);
-	e = p + READSTR;
 	for(i = 0; i < Nrxstats; i++){
 		p = seprint(p, e, "%s: %ud\n", rxstats[i], ctlr->rxstats[i]);
 	}
@@ -486,13 +488,10 @@
 			r = miimir(ctlr->mii, i);
 			p = seprint(p, e, " %4.4uX", r);
 		}
-		seprint(p, e, "\n");
+		p = seprint(p, e, "\n");
 	}
 
-	n = readstr(offset, a, n, alloc);
-	free(alloc);
-
-	return n;
+	return p;
 }
 
 static void
@@ -1199,10 +1198,10 @@
 	 */
 	edev->attach = vt6105Mattach;
 	edev->transmit = vt6105Mtransmit;
-	edev->ifstat = vt6105Mifstat;
 	edev->ctl = nil;
 
 	edev->arg = edev;
+	edev->ifstat = vt6105Mifstat;
 	edev->promiscuous = vt6105Mpromiscuous;
 	edev->multicast = vt6105Mmulticast;
 
--- a/sys/src/9/pc/etherwpi.c
+++ b/sys/src/9/pc/etherwpi.c
@@ -1488,15 +1488,12 @@
 	return 0;
 }
 
-static long
-wpiifstat(Ether *edev, void *buf, long n, ulong off)
+static char*
+wpiifstat(void *a, char *s, char *e)
 {
-	Ctlr *ctlr;
-
-	ctlr = edev->ctlr;
-	if(ctlr->wifi)
-		return wifistat(ctlr->wifi, buf, n, off);
-	return 0;
+	Ether *edev = a;
+ 	Ctlr *ctlr = edev->ctlr;
+	return wifistat(ctlr->wifi, s, e);
 }
 
 static void
@@ -1848,11 +1845,11 @@
 	edev->tbdf = ctlr->pdev->tbdf;
 	edev->arg = edev;
 	edev->attach = wpiattach;
-	edev->ifstat = wpiifstat;
 	edev->ctl = wpictl;
 	edev->shutdown = wpishutdown;
 	edev->promiscuous = wpipromiscuous;
 	edev->multicast = wpimulticast;
+	edev->ifstat = wpiifstat;
 	edev->mbps = 54;
 
 	pcienable(ctlr->pdev);
--- a/sys/src/9/pc/etherx550.c
+++ b/sys/src/9/pc/etherx550.c
@@ -294,15 +294,15 @@
 	10000,
 };
 
-static long
-ifstat(Ether *e, void *a, long n, ulong offset)
+static char*
+ifstat(void *arg, char *p, char *q)
 {
+	Ether *e = arg;
 	uint i, *t;
-	char *s, *p, *q;
 	Ctlr *c;
 
-	p = s = smalloc(READSTR);
-	q = p + READSTR;
+	if(p >= q)
+		return p;
 
 	c = e->ctlr;
 	readstats(c);
@@ -314,10 +314,8 @@
 	p = seprint(p, q, "speeds: 0:%d 1000:%d 10000:%d\n", t[0], t[1], t[2]);
 	seprint(p, q, "rdfree %d rdh %d rdt %d\n", c->rdfree, c->reg[Rdt],
 		c->reg[Rdh]);
-	n = readstr(offset, a, n, s);
-	free(s);
 
-	return n;
+	return p;
 }
 
 static void
--- a/sys/src/9/pc/etheryuk.c
+++ b/sys/src/9/pc/etheryuk.c
@@ -1742,17 +1742,20 @@
  	c->reg[Ism] |= Ibmu | Iport<<Iphy2base*c->portno;
 }
 
-static long
-ifstat(Ether *e0, void *a, long n, ulong offset)
+static char*
+ifstat(void *a, char *p, char *e)
 {
-	char *s, *e, *p;
 	int i;
 	uint u;
 	Ctlr *c;
+	Ether *e0;
 
+	if(p >= e)
+		return p;
+
+	e0 = a;
 	c = e0->ctlr;
-	p = s = malloc(READSTR);
-	e = p + READSTR;
+
 	for(i = 0; i < nelem(stattab); i++){
 		u = gmacread32(c, Stats + stattab[i].offset/4);
 		if(u > 0)
@@ -1768,9 +1771,7 @@
 	}
 	seprint(p, e, "%s rev %d phy %s\n", idtab[c->type].name,
 		c->rev, c->feat&Ffiber? "fiber": "copper");
-	n = readstr(offset, a, n, s);
-	free(s);
-	return n;
+	return p;
 }
 
 static Cmdtab ctltab[] = {
--- a/sys/src/9/pc/mkfile
+++ b/sys/src/9/pc/mkfile
@@ -121,7 +121,7 @@
 sdiahci.$O:			ahci.h ../port/led.h
 devaoe.$O sdaoe.$O:		../port/aoe.h
 main.$O:			rebootcode.i
-wavelan.$O:			wavelan.c ../pc/wavelan.c ../pc/wavelan.h
+wavelan.$O:			wavelan.c ../pc/wavelan.c ../pc/wavelan.h ../port/etherif.h ../port/netif.h
 etherwavelan.$O:		etherwavelan.c ../pc/wavelan.h
 devusb.$O usbuhci.$O usbohci.$O usbehci.$O usbehcipc.$O usbxhci.$O: ../port/usb.h
 usbehci.$O usbehcipc.$O:	usbehci.h
--- a/sys/src/9/pc/wavelan.c
+++ b/sys/src/9/pc/wavelan.c
@@ -826,15 +826,16 @@
 	iunlock(ctlr);
 }
 
-#define PRINTSTAT(fmt,val)	l += snprint(p+l, READSTR-l, (fmt), (val))
-#define PRINTSTR(fmt)		l += snprint(p+l, READSTR-l, (fmt))
+#define PRINTSTAT(fmt,val)	p = seprint(p, e, (fmt), (val))
+#define PRINTSTR(fmt)		p = seprint(p, e, (fmt))
 
-long
-w_ifstat(Ether* ether, void* a, long n, ulong offset)
+char*
+w_ifstat(void *arg, char *p, char *e)
 {
+	Ether* ether = (Ether*) arg;
 	Ctlr *ctlr = (Ctlr*) ether->ctlr;
-	char *k, *p;
-	int i, l, txid;
+	char *k;
+	int i, txid;
 
 	ether->oerrs = ctlr->ntxerr;
 	ether->crcs = ctlr->nrxfcserr;
@@ -842,16 +843,9 @@
 	ether->buffs = ctlr->nrxdropnobuf;
 	ether->overflows = 0;
 
-	//
-	// Offset must be zero or there's a possibility the
-	// new data won't match the previous read.
-	//
-	if(n == 0 || offset != 0)
-		return 0;
+	if(p >= e)
+		return p;
 
-	p = smalloc(READSTR);
-	l = 0;
-
 	PRINTSTAT("Signal: %d\n", ctlr->signal-149);
 	PRINTSTAT("Noise: %d\n", ctlr->noise-149);
 	PRINTSTAT("SNR: %ud\n", ctlr->signal-ctlr->noise);
@@ -910,7 +904,7 @@
 		ltv.len = 4;
 		if(w_inltv(ctlr, &ltv))
 			print("#l%d: unable to read base station mac addr\n", ether->ctlrno);
-		l += snprint(p+l, READSTR-l, "Base station: %2.2x%2.2x%2.2x%2.2x%2.2x%2.2x\n",
+		p = seprint(p, e, "Base station: %2.2x%2.2x%2.2x%2.2x%2.2x%2.2x\n",
 			ltv.addr[0], ltv.addr[1], ltv.addr[2], ltv.addr[3], ltv.addr[4], ltv.addr[5]);
 	}
 	PRINTSTAT("Net name: %s\n", ltv_inname(ctlr, WTyp_WantName));
@@ -951,10 +945,8 @@
 	PRINTSTAT("nrxcantdecrypt: %lud\n", ctlr->nrxcantdecrypt);
 	PRINTSTAT("nrxmsgfrag: %lud\n", ctlr->nrxmsgfrag);
 	PRINTSTAT("nrxmsgbadfrag: %lud\n", ctlr->nrxmsgbadfrag);
-	USED(l);
-	n = readstr(offset, a, n, p);
-	free(p);
-	return n;
+
+	return p;
 }
 #undef PRINTSTR
 #undef PRINTSTAT
@@ -1235,12 +1227,12 @@
 	ether->mbps = 10;
 	ether->attach = w_attach;
 	ether->transmit = w_transmit;
-	ether->ifstat = w_ifstat;
 	ether->ctl = w_ctl;
 	ether->power = w_power;
 	ether->promiscuous = w_promiscuous;
 	ether->multicast = w_multicast;
 	ether->scanbs = w_scanbs;
+	ether->ifstat = w_ifstat;
 	ether->arg = ether;
 
 	intrenable(ether->irq, w_interrupt, ether, ether->tbdf, ether->name);
--- a/sys/src/9/port/devether.c
+++ b/sys/src/9/port/devether.c
@@ -98,22 +98,7 @@
 static long
 etherread(Chan* chan, void* buf, long n, vlong off)
 {
-	Ether *ether;
-	ulong offset = off;
-
-	ether = etherxx[chan->dev];
-	if((chan->qid.type & QTDIR) == 0 && ether->ifstat){
-		/*
-		 * With some controllers it is necessary to reach
-		 * into the chip to extract statistics.
-		 */
-		if(NETTYPE(chan->qid.path) == Nifstatqid)
-			return ether->ifstat(ether, buf, n, offset);
-		else if(NETTYPE(chan->qid.path) == Nstatqid)
-			ether->ifstat(ether, buf, 0, offset);
-	}
-
-	return netifread(ether, chan, buf, n, offset);
+	return netifread(etherxx[chan->dev], chan, buf, n, (ulong)off);
 }
 
 static Block*
--- a/sys/src/9/port/etherif.h
+++ b/sys/src/9/port/etherif.h
@@ -36,7 +36,6 @@
 
 	void	(*attach)(Ether*);	/* filled in by reset routine */
 	void	(*transmit)(Ether*);
-	long	(*ifstat)(Ether*, void*, long, ulong);
 	long 	(*ctl)(Ether*, void*, long); /* custom ctl messages */
 	void	(*power)(Ether*, int);	/* power on/off */
 	void	(*shutdown)(Ether*);	/* shutdown hardware before reboot */
--- a/sys/src/9/port/etheriwl.c
+++ b/sys/src/9/port/etheriwl.c
@@ -3966,15 +3966,12 @@
 	return 0;
 }
 
-static long
-iwlifstat(Ether *edev, void *buf, long n, ulong off)
+static char*
+iwlifstat(void *a, char *s, char *e)
 {
-	Ctlr *ctlr;
-
-	ctlr = edev->ctlr;
-	if(ctlr->wifi)
-		return wifistat(ctlr->wifi, buf, n, off);
-	return 0;
+	Ether *edev = a;
+ 	Ctlr *ctlr = edev->ctlr;
+	return wifistat(ctlr->wifi, s, e);
 }
 
 static void
@@ -4545,11 +4542,11 @@
 	edev->tbdf = ctlr->pdev->tbdf;
 	edev->arg = edev;
 	edev->attach = iwlattach;
-	edev->ifstat = iwlifstat;
 	edev->ctl = iwlctl;
 	edev->shutdown = iwlshutdown;
 	edev->promiscuous = iwlpromiscuous;
 	edev->multicast = iwlmulticast;
+	edev->ifstat = iwlifstat;
 	edev->mbps = 54;
 
 	pcienable(ctlr->pdev);
--- a/sys/src/9/port/ethervirtio10.c
+++ b/sys/src/9/port/ethervirtio10.c
@@ -484,32 +484,27 @@
 	kproc(name, txproc, edev);
 }
 
-static long
-ifstat(Ether *edev, void *a, long n, ulong offset)
+static char*
+ifstat(void *a, char *s, char *e)
 {
-	int i, l;
-	char *p;
+	Ether *edev;
 	Ctlr *ctlr;
 	Vqueue *q;
+	int i;
 
+	if(s >= e)
+		return s;
+	edev = a;
 	ctlr = edev->ctlr;
-
-	p = smalloc(READSTR);
-
-	l = snprint(p, READSTR, "devfeat %32.32lub %32.32lub\n", ctlr->feat[1], ctlr->feat[0]);
-	l += snprint(p+l, READSTR-l, "devstatus %8.8ub\n", ctlr->cfg->status);
-
+	s = seprint(s, e, "devfeat %32.32lub %32.32lub\n", ctlr->feat[1], ctlr->feat[0]);
+	s = seprint(s, e, "devstatus %8.8ub\n", ctlr->cfg->status);
 	for(i = 0; i < ctlr->nqueue; i++){
 		q = &ctlr->queue[i];
-		l += snprint(p+l, READSTR-l,
+		s = seprint(s, e,
 			"vq%d %#p size %d avail->idx %d used->idx %d lastused %hud nintr %ud nnote %ud\n",
 			i, q, q->qsize, q->avail->idx, q->used->idx, q->lastused, q->nintr, q->nnote);
 	}
-
-	n = readstr(offset, a, n, p);
-	free(p);
-
-	return n;
+	return s;
 }
 
 static void
--- a/sys/src/9/port/netif.c
+++ b/sys/src/9/port/netif.c
@@ -213,7 +213,18 @@
 	case Nctlqid:
 		return readnum(offset, a, n, NETID(c->qid.path), NUMSIZE);
 	case Nstatqid:
+		if(offset >= READSTR)
+			return 0;
 		p = smalloc(READSTR);
+		if(waserror()){
+			free(p);
+			nexterror();
+		}
+		p[0] = '\0';
+		if(nif->ifstat != nil && !waserror()){
+			(*nif->ifstat)(nif->arg, p, p);
+			poperror();
+		}
 		j = snprint(p, READSTR, "in: %llud\n", nif->inpackets);
 		j += snprint(p+j, READSTR-j, "link: %d\n", nif->link);
 		j += snprint(p+j, READSTR-j, "out: %llud\n", nif->outpackets);
@@ -231,20 +242,30 @@
 		snprint(p+j, READSTR-j, "\n");
 		n = readstr(offset, a, n, p);
 		free(p);
+		poperror();
 		return n;
 	case Naddrqid:
-		p = smalloc(READSTR);
 		j = 0;
 		for(i = 0; i < nif->alen; i++)
-			j += snprint(p+j, READSTR-j, "%2.2ux", nif->addr[i]);
-		n = readstr(offset, a, n, p);
-		free(p);
-		return n;
+			j += snprint(up->genbuf+j, sizeof(up->genbuf)-j, "%2.2ux", nif->addr[i]);
+		return readstr(offset, a, n, up->genbuf);
 	case Ntypeqid:
 		f = nif->f[NETID(c->qid.path)];
 		return readnum(offset, a, n, f->type, NUMSIZE);
 	case Nifstatqid:
-		return 0;
+		if(nif->ifstat == nil || offset >= READSTR)
+			return 0;
+		p = smalloc(READSTR);
+		if(waserror()){
+			free(p);
+			nexterror();
+		}
+		p[0] = '\0';
+		(*nif->ifstat)(nif->arg, p, p + READSTR);
+		n = readstr(offset, a, n, p);
+		free(p);
+		poperror();
+		return n;
 	}
 	error(Ebadarg);
 }
--- a/sys/src/9/port/netif.h
+++ b/sys/src/9/port/netif.h
@@ -105,6 +105,7 @@
 	void	(*promiscuous)(void*, int);
 	void	(*multicast)(void*, uchar*, int);
 	void	(*scanbs)(void*, uint);	/* scan for base stations */
+	char*	(*ifstat)(void*, char*, char*);
 };
 
 void	netifinit(Netif*, char*, int, ulong);
--- a/sys/src/9/port/wifi.c
+++ b/sys/src/9/port/wifi.c
@@ -1084,19 +1084,18 @@
 	return n;
 }
 
-long
-wifistat(Wifi *wifi, void *buf, long n, ulong off)
+char*
+wifistat(Wifi *wifi, char *p, char *e)
 {
 	static uchar zeros[Eaddrlen];
 	char essid[Essidlen+1];
-	char *s, *p, *e;
 	Wnode *wn;
 	Wkey *k;
 	long now;
 	int i;
 
-	p = s = smalloc(4096);
-	e = s + 4096;
+	if(wifi == nil || p >= e)
+		return p;
 
 	wn = wifi->bss;
 	if(wn != nil){
@@ -1141,9 +1140,7 @@
 		p = seprint(p, e, "node: %E %.4x %-11ld %.2d %s\n",
 			wn->bssid, wn->cap, TK2MS(now - wn->lastseen), wn->channel, essid);
 	}
-	n = readstr(off, buf, n, s);
-	free(s);
-	return n;
+	return p;
 }
 
 static void tkipencrypt(Wkey *k, Wifipkt *w, Block *b, uvlong tsc);
--- a/sys/src/9/port/wifi.h
+++ b/sys/src/9/port/wifi.h
@@ -105,6 +105,6 @@
 int wifihdrlen(Wifipkt*);
 void wifitxfail(Wifi*, Block*);
 
-long wifistat(Wifi*, void*, long, ulong);
+char *wifistat(Wifi*, char*, char*);
 long wifictl(Wifi*, void*, long);
 void wificfg(Wifi*, char*);
--- a/sys/src/9/ppc/etherfcc.c
+++ b/sys/src/9/ppc/etherfcc.c
@@ -453,52 +453,49 @@
 #endif
 }
 
-static long
-ifstat(Ether* ether, void* a, long n, ulong offset)
+static char*
+ifstat(void *arg, char *p, char *e)
 {
-	char *p;
-	int len, i, r;
+	Ether *ether;
 	Ctlr *ctlr;
 	MiiPhy *phy;
+	int i, r;
 
-	if(n == 0)
-		return 0;
+	if(p >= e)
+		return p;
 
+	ether = arg;
 	ctlr = ether->ctlr;
 
-	p = malloc(READSTR);
-	len = snprint(p, READSTR, "interrupts: %lud\n", ctlr->interrupts);
-	len += snprint(p+len, READSTR-len, "carrierlost: %lud\n", ctlr->carrierlost);
-	len += snprint(p+len, READSTR-len, "heartbeat: %lud\n", ctlr->heartbeat);
-	len += snprint(p+len, READSTR-len, "retrylimit: %lud\n", ctlr->retrylim);
-	len += snprint(p+len, READSTR-len, "retrycount: %lud\n", ctlr->retrycount);
-	len += snprint(p+len, READSTR-len, "latecollisions: %lud\n", ctlr->latecoll);
-	len += snprint(p+len, READSTR-len, "rxoverruns: %lud\n", ctlr->overrun);
-	len += snprint(p+len, READSTR-len, "txunderruns: %lud\n", ctlr->underrun);
-	len += snprint(p+len, READSTR-len, "framesdeferred: %lud\n", ctlr->deferred);
+	p = seprint(p, e, "interrupts: %lud\n", ctlr->interrupts);
+	p = seprint(p, e, "carrierlost: %lud\n", ctlr->carrierlost);
+	p = seprint(p, e, "heartbeat: %lud\n", ctlr->heartbeat);
+	p = seprint(p, e, "retrylimit: %lud\n", ctlr->retrylim);
+	p = seprint(p, e, "retrycount: %lud\n", ctlr->retrycount);
+	p = seprint(p, e, "latecollisions: %lud\n", ctlr->latecoll);
+	p = seprint(p, e, "rxoverruns: %lud\n", ctlr->overrun);
+	p = seprint(p, e, "txunderruns: %lud\n", ctlr->underrun);
+	p = seprint(p, e, "framesdeferred: %lud\n", ctlr->deferred);
 	miistatus(ctlr->mii);
 	phy = ctlr->mii->curphy;
-	len += snprint(p+len, READSTR-len, "phy: link=%d, tfc=%d, rfc=%d, speed=%d, fd=%d\n",
+	p = seprint(p, e, "phy: link=%d, tfc=%d, rfc=%d, speed=%d, fd=%d\n",
 		phy->link, phy->tfc, phy->rfc, phy->speed, phy->fd);
 
 #ifdef DBG
 	if(ctlr->mii != nil && ctlr->mii->curphy != nil){
-		len += snprint(p+len, READSTR, "phy:   ");
+		p = seprint(p, e, "phy:   ");
 		for(i = 0; i < NMiiPhyr; i++){
 			if(i && ((i & 0x07) == 0))
-				len += snprint(p+len, READSTR-len, "\n       ");
+				p = seprint(p, e, "\n       ");
 			r = miimir(ctlr->mii, i);
-			len += snprint(p+len, READSTR-len, " %4.4uX", r);
+			p = seprint(p, e, " %4.4uX", r);
 		}
-		snprint(p+len, READSTR-len, "\n");
+		p = seprint(p, e, "\n");
 	}
 #endif
-	snprint(p+len, READSTR-len, "\n");
+	p = seprint(p, e, "\n");
 
-	n = readstr(offset, a, n, p);
-	free(p);
-
-	return n;
+	return p;
 }
 
 /*
@@ -719,9 +716,9 @@
 	ether->mbps = 100;	/* TO DO: could be 10mbps */
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->ifstat = ifstat;
 
 	ether->arg = ether;
+	ether->ifstat = ifstat;
 	ether->promiscuous = promiscuous;
 	ether->multicast = multicast;
 
--- a/sys/src/9/teg2/ether8169.c
+++ b/sys/src/9/teg2/ether8169.c
@@ -564,21 +564,17 @@
 	iunlock(&ctlr->ilock);
 }
 
-static long
-rtl8169ifstat(Ether* edev, void* a, long n, ulong offset)
+static char*
+rtl8169ifstat(void *arg, char *p, char *e)
 {
-	char *p;
-	Ctlr *ctlr;
+	Ether *edev = arg;
+	Ctlr *ctlr = edev->ctlr;
 	Dtcc *dtcc;
-	int i, l, r, timeo;
+	int i, r, timeo;
 
-	ctlr = edev->ctlr;
 	qlock(&ctlr->slock);
-
-	p = nil;
 	if(waserror()){
 		qunlock(&ctlr->slock);
-		free(p);
 		nexterror();
 	}
 
@@ -603,62 +599,55 @@
 	edev->buffs = dtcc->misspkt;
 	edev->overflows = ctlr->txdu + ctlr->rdu;
 
-	if(n == 0){
+	if(p >= e){
 		qunlock(&ctlr->slock);
 		poperror();
-		return 0;
+		return p;
 	}
 
-	if((p = malloc(READSTR)) == nil)
-		error(Enomem);
+	p = seprint(p, e, "TxOk: %llud\n", dtcc->txok);
+	p = seprint(p, e, "RxOk: %llud\n", dtcc->rxok);
+	p = seprint(p, e, "TxEr: %llud\n", dtcc->txer);
+	p = seprint(p, e, "RxEr: %ud\n", dtcc->rxer);
+	p = seprint(p, e, "MissPkt: %ud\n", dtcc->misspkt);
+	p = seprint(p, e, "FAE: %ud\n", dtcc->fae);
+	p = seprint(p, e, "Tx1Col: %ud\n", dtcc->tx1col);
+	p = seprint(p, e, "TxMCol: %ud\n", dtcc->txmcol);
+	p = seprint(p, e, "RxOkPh: %llud\n", dtcc->rxokph);
+	p = seprint(p, e, "RxOkBrd: %llud\n", dtcc->rxokbrd);
+	p = seprint(p, e, "RxOkMu: %ud\n", dtcc->rxokmu);
+	p = seprint(p, e, "TxAbt: %ud\n", dtcc->txabt);
+	p = seprint(p, e, "TxUndrn: %ud\n", dtcc->txundrn);
 
-	l = snprint(p, READSTR, "TxOk: %llud\n", dtcc->txok);
-	l += snprint(p+l, READSTR-l, "RxOk: %llud\n", dtcc->rxok);
-	l += snprint(p+l, READSTR-l, "TxEr: %llud\n", dtcc->txer);
-	l += snprint(p+l, READSTR-l, "RxEr: %ud\n", dtcc->rxer);
-	l += snprint(p+l, READSTR-l, "MissPkt: %ud\n", dtcc->misspkt);
-	l += snprint(p+l, READSTR-l, "FAE: %ud\n", dtcc->fae);
-	l += snprint(p+l, READSTR-l, "Tx1Col: %ud\n", dtcc->tx1col);
-	l += snprint(p+l, READSTR-l, "TxMCol: %ud\n", dtcc->txmcol);
-	l += snprint(p+l, READSTR-l, "RxOkPh: %llud\n", dtcc->rxokph);
-	l += snprint(p+l, READSTR-l, "RxOkBrd: %llud\n", dtcc->rxokbrd);
-	l += snprint(p+l, READSTR-l, "RxOkMu: %ud\n", dtcc->rxokmu);
-	l += snprint(p+l, READSTR-l, "TxAbt: %ud\n", dtcc->txabt);
-	l += snprint(p+l, READSTR-l, "TxUndrn: %ud\n", dtcc->txundrn);
+	p = seprint(p, e, "txdu: %ud\n", ctlr->txdu);
+	p = seprint(p, e, "tcpf: %ud\n", ctlr->tcpf);
+	p = seprint(p, e, "udpf: %ud\n", ctlr->udpf);
+	p = seprint(p, e, "ipf: %ud\n", ctlr->ipf);
+	p = seprint(p, e, "fovf: %ud\n", ctlr->fovf);
+	p = seprint(p, e, "ierrs: %ud\n", ctlr->ierrs);
+	p = seprint(p, e, "rer: %ud\n", ctlr->rer);
+	p = seprint(p, e, "rdu: %ud\n", ctlr->rdu);
+	p = seprint(p, e, "punlc: %ud\n", ctlr->punlc);
+	p = seprint(p, e, "fovw: %ud\n", ctlr->fovw);
 
-	l += snprint(p+l, READSTR-l, "txdu: %ud\n", ctlr->txdu);
-	l += snprint(p+l, READSTR-l, "tcpf: %ud\n", ctlr->tcpf);
-	l += snprint(p+l, READSTR-l, "udpf: %ud\n", ctlr->udpf);
-	l += snprint(p+l, READSTR-l, "ipf: %ud\n", ctlr->ipf);
-	l += snprint(p+l, READSTR-l, "fovf: %ud\n", ctlr->fovf);
-	l += snprint(p+l, READSTR-l, "ierrs: %ud\n", ctlr->ierrs);
-	l += snprint(p+l, READSTR-l, "rer: %ud\n", ctlr->rer);
-	l += snprint(p+l, READSTR-l, "rdu: %ud\n", ctlr->rdu);
-	l += snprint(p+l, READSTR-l, "punlc: %ud\n", ctlr->punlc);
-	l += snprint(p+l, READSTR-l, "fovw: %ud\n", ctlr->fovw);
+	p = seprint(p, e, "tcr: %#8.8ux\n", ctlr->tcr);
+	p = seprint(p, e, "rcr: %#8.8ux\n", ctlr->rcr);
+	p = seprint(p, e, "multicast: %ud\n", ctlr->mcast);
 
-	l += snprint(p+l, READSTR-l, "tcr: %#8.8ux\n", ctlr->tcr);
-	l += snprint(p+l, READSTR-l, "rcr: %#8.8ux\n", ctlr->rcr);
-	l += snprint(p+l, READSTR-l, "multicast: %ud\n", ctlr->mcast);
-
 	if(ctlr->mii != nil && ctlr->mii->curphy != nil){
-		l += snprint(p+l, READSTR, "phy:   ");
+		p = seprint(p, e, "phy:   ");
 		for(i = 0; i < NMiiPhyr; i++){
 			if(i && ((i & 0x07) == 0))
-				l += snprint(p+l, READSTR-l, "\n       ");
+				p = seprint(p, e, "\n       ");
 			r = miimir(ctlr->mii, i);
-			l += snprint(p+l, READSTR-l, " %4.4ux", r);
+			p = seprint(p, e, " %4.4ux", r);
 		}
-		snprint(p+l, READSTR-l, "\n");
+		p = seprint(p, e, "\n");
 	}
-
-	n = readstr(offset, a, n, p);
-
 	qunlock(&ctlr->slock);
 	poperror();
-	free(p);
 
-	return n;
+	return p;
 }
 
 static void
@@ -1646,9 +1635,9 @@
 
 	edev->attach = rtl8169attach;
 	edev->transmit = rtl8169transmit;
-	edev->ifstat = rtl8169ifstat;
 
 	edev->arg = edev;
+	edev->ifstat = rtl8169ifstat;
 	edev->promiscuous = rtl8169promiscuous;
 	edev->multicast = rtl8169multicast;
 	edev->shutdown = rtl8169shutdown;
--- a/sys/src/9/xen/etherxen.c
+++ b/sys/src/9/xen/etherxen.c
@@ -413,30 +413,23 @@
 	USED(arg, addr, on);
 }
 
-static long
-ifstat(Ether* ether, void* a, long n, ulong offset)
+static char*
+ifstat(void *arg, char *p, char *e)
 {
-	Ctlr *ctlr;
-	char *buf, *p;
-	int l, len;
+	Ether *ether = arg;
+	Ctlr *ctlr = ether->ctlr;
 
-	ctlr = ether->ctlr;
-	if(n == 0)
-		return 0;
-	if((p = malloc(READSTR)) == nil)
-		error(Enomem);
-	l = snprint(p, READSTR, "intr: %lud\n", ctlr->interrupts);
-	l += snprint(p+l, READSTR-l, "transmits: %lud\n", ctlr->transmits);
-	l += snprint(p+l, READSTR-l, "receives: %lud\n", ctlr->receives);
-	l += snprint(p+l, READSTR-l, "txerrors: %lud\n", ctlr->txerrors);
-	l += snprint(p+l, READSTR-l, "rxerrors: %lud\n", ctlr->rxerrors);
-	snprint(p+l, READSTR-l, "rxoverflows: %lud\n", ctlr->rxoverflows);
+	if(p >= e)
+		return p;
 
-	buf = a;
-	len = readstr(offset, buf, n, p);
-	free(p);
+	p = seprint(p, e, "intr: %lud\n", ctlr->interrupts);
+	p = seprint(p, e, "transmits: %lud\n", ctlr->transmits);
+	p = seprint(p, e, "receives: %lud\n", ctlr->receives);
+	p = seprint(p, e, "txerrors: %lud\n", ctlr->txerrors);
+	p = seprint(p, e, "rxerrors: %lud\n", ctlr->rxerrors);
+	p = seprint(p, e, "rxoverflows: %lud\n", ctlr->rxoverflows);
 
-	return len;
+	return p;
 }
 
 static int
@@ -476,10 +469,10 @@
 	ether->transmit = etherxentransmit;
 	ether->irq = -1;
 	ether->tbdf = BUSUNKNOWN;
-	ether->ifstat = ifstat;
 	ether->ctl = etherxenctl;
 	ether->promiscuous = nil;
 	ether->multicast = etherxenmulticast;
+	ether->ifstat = ifstat;
 	ether->arg = ether;
 
 	intrenable(ether->irq, etherxenintr, ether, ether->tbdf, ether->name);
--