#include #include #include #include #include #include #include #include "libframe/frame.h" #include #include #include #include "dat.h" #include "fns.h" enum { Ctlsize = 5 * 12 }; char Edel[] = "deleted window"; char Ebadctl[] = "ill-formed control message"; char Ebadaddr[] = "bad address syntax"; char Eaddr[] = "address out of range"; char Einuse[] = "already in use"; char Ebadevent[] = "bad event syntax"; extern char Eperm[]; static void clampaddr(Window* w) { if (w->addr.q0 < 0) w->addr.q0 = 0; if (w->addr.q1 < 0) w->addr.q1 = 0; if (w->addr.q0 > w->body.file->b.nc) w->addr.q0 = w->body.file->b.nc; if (w->addr.q1 > w->body.file->b.nc) w->addr.q1 = w->body.file->b.nc; } void xfidctl(void* arg) { Xfid* x; void (*f)(Xfid*); threadsetname("xfidctlthread"); x = arg; for (;;) { f = (void (*)(Xfid*))recvp(x->c); (*f)(x); flushimage(display, 1); sendp(cxfidfree, x); } } void xfidflush(Xfid* x) { Fcall fc; int i, j; Window* w; Column* c; Xfid* wx; xfidlogflush(x); /* search windows for matching tag */ qlock(&row.lk); for (j = 0; j < row.ncol; j++) { c = row.col[j]; for (i = 0; i < c->nw; i++) { w = c->w[i]; winlock(w, 'E'); wx = w->eventx; if (wx != nil && wx->fcall.tag == x->fcall.oldtag) { w->eventx = nil; wx->flushed = TRUE; sendp(wx->c, nil); winunlock(w); goto out; } winunlock(w); } } out: qunlock(&row.lk); respond(x, &fc, nil); } void xfidopen(Xfid* x) { Fcall fc; Window* w; Text* t; char* s; Rune* r; int m, n, q, q0, q1; w = x->f->w; t = &w->body; q = FILE(x->f->qid); if (w) { winlock(w, 'E'); switch (q) { case QWaddr: if (w->nopen[q]++ == 0) { w->addr = range(0, 0); w->limit = range(-1, -1); } break; case QWdata: case QWxdata: w->nopen[q]++; break; case QWevent: if (w->nopen[q]++ == 0) { if (!w->isdir && w->col != nil) { w->filemenu = FALSE; winsettag(w); } } break; case QWrdsel: /* * Use a temporary file. * A pipe would be the obvious, but we can't afford the * broken pipe notification. Using the code to read QWbody * is n², which should probably also be fixed. Even then, * though, we'd need to squirrel away the data in case it's * modified during the operation, e.g. by |sort */ if (w->rdselfd > 0) { winunlock(w); respond(x, &fc, Einuse); return; } w->rdselfd = tempfile(); if (w->rdselfd < 0) { winunlock(w); respond(x, &fc, "can't create temp file"); return; } w->nopen[q]++; q0 = t->q0; q1 = t->q1; r = fbufalloc(); s = fbufalloc(); while (q0 < q1) { n = q1 - q0; if (n > BUFSIZE / UTFmax) n = BUFSIZE / UTFmax; bufread(&t->file->b, q0, r, n); m = snprint(s, BUFSIZE + 1, "%.*S", n, r); if (write(w->rdselfd, s, m) != m) { warning(nil, "can't write temp file for pipe command %r\n"); break; } q0 += n; } fbuffree(s); fbuffree(r); break; case QWwrsel: w->nopen[q]++; seq++; filemark(t->file); cut(t, t, nil, FALSE, TRUE, nil, 0); w->wrselrange = range(t->q1, t->q1); w->nomark = TRUE; break; case QWeditout: if (editing == FALSE) { winunlock(w); respond(x, &fc, Eperm); return; } if (!canqlock(&w->editoutlk)) { winunlock(w); respond(x, &fc, Einuse); return; } w->wrselrange = range(t->q1, t->q1); break; } winunlock(w); } else { switch (q) { case Qlog: xfidlogopen(x); break; case Qeditout: if (!canqlock(&editoutlk)) { respond(x, &fc, Einuse); return; } break; } } fc.qid = x->f->qid; fc.iounit = messagesize - IOHDRSZ; x->f->open = TRUE; respond(x, &fc, nil); } void xfidclose(Xfid* x) { Fcall fc; Window* w; int q; Text* t; w = x->f->w; x->f->busy = FALSE; x->f->w = nil; if (x->f->open == FALSE) { if (w != nil) winclose(w); respond(x, &fc, nil); return; } q = FILE(x->f->qid); x->f->open = FALSE; if (w) { winlock(w, 'E'); switch (q) { case QWctl: if (w->ctlfid != ~0 && w->ctlfid == x->f->fid) { w->ctlfid = ~0; qunlock(&w->ctllock); } break; case QWdata: case QWxdata: w->nomark = FALSE; /* fall through */ case QWaddr: case QWevent: /* BUG: do we need to shut down Xfid? */ if (--w->nopen[q] == 0) { if (q == QWdata || q == QWxdata) w->nomark = FALSE; if (q == QWevent && !w->isdir && w->col != nil) { w->filemenu = TRUE; winsettag(w); } if (q == QWevent) { free(w->dumpstr); free(w->dumpdir); w->dumpstr = nil; w->dumpdir = nil; } } break; case QWrdsel: close(w->rdselfd); w->rdselfd = 0; break; case QWwrsel: w->nomark = FALSE; t = &w->body; /* before: only did this if !w->noscroll, but that didn't seem right in * practice */ textshow( t, min(w->wrselrange.q0, t->file->b.nc), min(w->wrselrange.q1, t->file->b.nc), 1); textscrdraw(t); break; case QWeditout: qunlock(&w->editoutlk); break; } winunlock(w); winclose(w); } else { switch (q) { case Qeditout: qunlock(&editoutlk); break; } } respond(x, &fc, nil); } void xfidread(Xfid* x) { Fcall fc; int n, q; uint off; char* b; char buf[256]; Window* w; q = FILE(x->f->qid); w = x->f->w; if (w == nil) { fc.count = 0; switch (q) { case Qcons: case Qlabel: break; case Qindex: xfidindexread(x); return; case Qlog: xfidlogread(x); return; default: warning(nil, "unknown qid %d\n", q); break; } respond(x, &fc, nil); return; } winlock(w, 'F'); if (w->col == nil) { winunlock(w); respond(x, &fc, Edel); return; } off = x->fcall.offset; switch (q) { case QWaddr: textcommit(&w->body, TRUE); clampaddr(w); sprint(buf, "%11d %11d ", w->addr.q0, w->addr.q1); goto Readbuf; case QWbody: xfidutfread(x, &w->body, w->body.file->b.nc, QWbody); break; case QWctl: b = winctlprint(w, buf, 1); goto Readb; Readbuf: b = buf; Readb: n = strlen(b); if (off > n) off = n; if (off + x->fcall.count > n) x->fcall.count = n - off; fc.count = x->fcall.count; fc.data = b + off; respond(x, &fc, nil); if (b != buf) free(b); break; case QWevent: xfideventread(x, w); break; case QWdata: /* BUG: what should happen if q1 > q0? */ if (w->addr.q0 > w->body.file->b.nc) { respond(x, &fc, Eaddr); break; } w->addr.q0 += xfidruneread(x, &w->body, w->addr.q0, w->body.file->b.nc); w->addr.q1 = w->addr.q0; break; case QWxdata: /* BUG: what should happen if q1 > q0? */ if (w->addr.q0 > w->body.file->b.nc) { respond(x, &fc, Eaddr); break; } w->addr.q0 += xfidruneread(x, &w->body, w->addr.q0, w->addr.q1); break; case QWtag: xfidutfread(x, &w->tag, w->tag.file->b.nc, QWtag); break; case QWrdsel: seek(w->rdselfd, off, 0); n = x->fcall.count; if (n > BUFSIZE) n = BUFSIZE; b = fbufalloc(); n = read(w->rdselfd, b, n); if (n < 0) { respond(x, &fc, "I/O error in temp file"); break; } fc.count = n; fc.data = b; respond(x, &fc, nil); fbuffree(b); break; default: sprint(buf, "unknown qid %d in read", q); respond(x, &fc, nil); } winunlock(w); } static int shouldscroll(Text* t, uint q0, int qid) { if (qid == Qcons) return TRUE; return t->org <= q0 && q0 <= t->org + t->fr.nchars; } static Rune* fullrunewrite(Xfid* x, int* inr) { int q, cnt, c, nb, nr; Rune* r; q = x->f->nrpart; cnt = x->fcall.count; if (q > 0) { memmove( x->fcall.data + q, x->fcall.data, cnt); /* there's room; see fsysproc */ memmove(x->fcall.data, x->f->rpart, q); cnt += q; x->f->nrpart = 0; } r = runemalloc(cnt); cvttorunes(x->fcall.data, cnt - UTFmax, r, &nb, &nr, nil); /* approach end of buffer */ while (fullrune(x->fcall.data + nb, cnt - nb)) { c = nb; nb += chartorune(&r[nr], x->fcall.data + c); if (r[nr]) nr++; } if (nb < cnt) { memmove(x->f->rpart, x->fcall.data + nb, cnt - nb); x->f->nrpart = cnt - nb; } *inr = nr; return r; } void xfidwrite(Xfid* x) { Fcall fc; int c, qid, nb, nr, eval; char buf[64], *err; Window* w; Rune* r; Range a; Text* t; uint q0, tq0, tq1; qid = FILE(x->f->qid); w = x->f->w; if (w) { c = 'F'; if (qid == QWtag || qid == QWbody) c = 'E'; winlock(w, c); if (w->col == nil) { winunlock(w); respond(x, &fc, Edel); return; } } x->fcall.data[x->fcall.count] = 0; switch (qid) { case Qcons: w = errorwin(x->f->mntdir, 'X'); t = &w->body; goto BodyTag; case Qlabel: fc.count = x->fcall.count; respond(x, &fc, nil); break; case QWaddr: x->fcall.data[x->fcall.count] = 0; r = bytetorune(x->fcall.data, &nr); t = &w->body; wincommit(w, t); eval = TRUE; a = address( FALSE, t, w->limit, w->addr, r, 0, nr, rgetc, &eval, (uint*)&nb); free(r); if (nb < nr) { respond(x, &fc, Ebadaddr); break; } if (!eval) { respond(x, &fc, Eaddr); break; } w->addr = a; fc.count = x->fcall.count; respond(x, &fc, nil); break; case Qeditout: case QWeditout: r = fullrunewrite(x, &nr); if (w) err = edittext(w, w->wrselrange.q1, r, nr); else err = edittext(nil, 0, r, nr); free(r); if (err != nil) { respond(x, &fc, err); break; } fc.count = x->fcall.count; respond(x, &fc, nil); break; case QWerrors: w = errorwinforwin(w); t = &w->body; goto BodyTag; case QWbody: case QWwrsel: t = &w->body; goto BodyTag; case QWctl: xfidctlwrite(x, w); break; case QWdata: a = w->addr; t = &w->body; wincommit(w, t); if (a.q0 > t->file->b.nc || a.q1 > t->file->b.nc) { respond(x, &fc, Eaddr); break; } r = runemalloc(x->fcall.count); cvttorunes(x->fcall.data, x->fcall.count, r, &nb, &nr, nil); if (w->nomark == FALSE) { seq++; filemark(t->file); } q0 = a.q0; if (a.q1 > q0) { textdelete(t, q0, a.q1, TRUE); w->addr.q1 = q0; } tq0 = t->q0; tq1 = t->q1; textinsert(t, q0, r, nr, TRUE); if (tq0 >= q0) tq0 += nr; if (tq1 >= q0) tq1 += nr; textsetselect(t, tq0, tq1); if (shouldscroll(t, q0, qid)) textshow(t, q0 + nr, q0 + nr, 0); textscrdraw(t); winsettag(w); free(r); w->addr.q0 += nr; w->addr.q1 = w->addr.q0; fc.count = x->fcall.count; respond(x, &fc, nil); break; case QWevent: xfideventwrite(x, w); break; case QWtag: t = &w->tag; goto BodyTag; BodyTag: r = fullrunewrite(x, &nr); if (nr > 0) { wincommit(w, t); if (qid == QWwrsel) { q0 = w->wrselrange.q1; if (q0 > t->file->b.nc) q0 = t->file->b.nc; } else q0 = t->file->b.nc; if (qid == QWtag) textinsert(t, q0, r, nr, TRUE); else { if (w->nomark == FALSE) { seq++; filemark(t->file); } q0 = textbsinsert(t, q0, r, nr, TRUE, &nr); textsetselect( t, t->q0, t->q1); /* insert could leave it somewhere else */ if (qid != QWwrsel && shouldscroll(t, q0, qid)) textshow(t, q0 + nr, q0 + nr, 1); textscrdraw(t); } winsettag(w); if (qid == QWwrsel) w->wrselrange.q1 += nr; free(r); } fc.count = x->fcall.count; respond(x, &fc, nil); break; default: sprint(buf, "unknown qid %d in write", qid); respond(x, &fc, buf); break; } if (w) winunlock(w); } void xfidctlwrite(Xfid* x, Window* w) { Fcall fc; int i, m, n, nb, nr, nulls; Rune* r; char *err, *p, *pp, *q, *e; int isfbuf, scrdraw, settag; Text* t; err = nil; e = x->fcall.data + x->fcall.count; scrdraw = FALSE; settag = FALSE; isfbuf = TRUE; if (x->fcall.count < RBUFSIZE) r = fbufalloc(); else { isfbuf = FALSE; r = emalloc(x->fcall.count * UTFmax + 1); } x->fcall.data[x->fcall.count] = 0; textcommit(&w->tag, TRUE); for (n = 0; n < x->fcall.count; n += m) { p = x->fcall.data + n; if (strncmp(p, "lock", 4) == 0) { /* make window exclusive use */ qlock(&w->ctllock); w->ctlfid = x->f->fid; m = 4; } else if (strncmp(p, "unlock", 6) == 0) { /* release exclusive use */ w->ctlfid = ~0; qunlock(&w->ctllock); m = 6; } else if (strncmp(p, "clean", 5) == 0) { /* mark window 'clean', seq=0 */ t = &w->body; t->eq0 = ~0; filereset(t->file); t->file->mod = FALSE; w->dirty = FALSE; settag = TRUE; m = 5; } else if (strncmp(p, "dirty", 5) == 0) { /* mark window 'dirty' */ t = &w->body; /* doesn't change sequence number, so "Put" won't appear. it shouldn't. */ t->file->mod = TRUE; w->dirty = TRUE; settag = TRUE; m = 5; } else if (strncmp(p, "show", 4) == 0) { /* show dot */ t = &w->body; textshow(t, t->q0, t->q1, 1); m = 4; } else if (strncmp(p, "name ", 5) == 0) { /* set file name */ pp = p + 5; m = 5; q = memchr(pp, '\n', e - pp); if (q == nil || q == pp) { err = Ebadctl; break; } *q = 0; nulls = FALSE; cvttorunes(pp, q - pp, r, &nb, &nr, &nulls); if (nulls) { err = "nulls in file name"; break; } for (i = 0; i < nr; i++) if (r[i] <= ' ') { err = "bad character in file name"; goto out; } out: seq++; filemark(w->body.file); winsetname(w, r, nr); m += (q + 1) - pp; } else if (strncmp(p, "font ", 5) == 0) { /* execute font command */ pp = p + 5; m = 5; q = memchr(pp, '\n', e - pp); if (q == nil || q == pp) { err = Ebadctl; break; } *q = 0; nulls = FALSE; cvttorunes(pp, q - pp, r, &nb, &nr, &nulls); if (nulls) { err = "nulls in font string"; break; } fontx(&w->body, nil, nil, FALSE, XXX, r, nr); m += (q + 1) - pp; } else if (strncmp(p, "dump ", 5) == 0) { /* set dump string */ pp = p + 5; m = 5; q = memchr(pp, '\n', e - pp); if (q == nil || q == pp) { err = Ebadctl; break; } *q = 0; nulls = FALSE; cvttorunes(pp, q - pp, r, &nb, &nr, &nulls); if (nulls) { err = "nulls in dump string"; break; } w->dumpstr = runetobyte(r, nr); m += (q + 1) - pp; } else if (strncmp(p, "dumpdir ", 8) == 0) { /* set dump directory */ pp = p + 8; m = 8; q = memchr(pp, '\n', e - pp); if (q == nil || q == pp) { err = Ebadctl; break; } *q = 0; nulls = FALSE; cvttorunes(pp, q - pp, r, &nb, &nr, &nulls); if (nulls) { err = "nulls in dump directory string"; break; } w->dumpdir = runetobyte(r, nr); m += (q + 1) - pp; } else if (strncmp(p, "delete", 6) == 0) { /* delete for sure */ colclose(w->col, w, TRUE); m = 6; } else if (strncmp(p, "del", 3) == 0) { /* delete, but check dirty */ if (!winclean(w, TRUE)) { err = "file dirty"; break; } colclose(w->col, w, TRUE); m = 3; } else if (strncmp(p, "get", 3) == 0) { /* get file */ get(&w->body, nil, nil, FALSE, XXX, nil, 0); m = 3; } else if (strncmp(p, "put", 3) == 0) { /* put file */ put(&w->body, nil, nil, XXX, XXX, nil, 0); m = 3; } else if (strncmp(p, "dot=addr", 8) == 0) { /* set dot */ textcommit(&w->body, TRUE); clampaddr(w); w->body.q0 = w->addr.q0; w->body.q1 = w->addr.q1; textsetselect(&w->body, w->body.q0, w->body.q1); settag = TRUE; m = 8; } else if (strncmp(p, "addr=dot", 8) == 0) { /* set addr */ w->addr.q0 = w->body.q0; w->addr.q1 = w->body.q1; m = 8; } else if (strncmp(p, "limit=addr", 10) == 0) { /* set limit */ textcommit(&w->body, TRUE); clampaddr(w); w->limit.q0 = w->addr.q0; w->limit.q1 = w->addr.q1; m = 10; } else if (strncmp(p, "nomark", 6) == 0) { /* turn off automatic marking */ w->nomark = TRUE; m = 6; } else if (strncmp(p, "mark", 4) == 0) { /* mark file */ seq++; filemark(w->body.file); settag = TRUE; m = 4; } else if (strncmp(p, "nomenu", 6) == 0) { /* turn off automatic menu */ w->filemenu = FALSE; settag = TRUE; m = 6; } else if (strncmp(p, "menu", 4) == 0) { /* enable automatic menu */ w->filemenu = TRUE; settag = TRUE; m = 4; } else if (strncmp(p, "cleartag", 8) == 0) { /* wipe tag right of bar */ wincleartag(w); settag = TRUE; m = 8; } else { err = Ebadctl; break; } while (p[m] == '\n') m++; } if (isfbuf) fbuffree(r); else free(r); if (err) n = 0; fc.count = n; respond(x, &fc, err); if (settag) winsettag(w); if (scrdraw) textscrdraw(&w->body); } void xfideventwrite(Xfid* x, Window* w) { Fcall fc; int m, n; Rune* r; char *err, *p, *q; int isfbuf; Text* t; int c; uint q0, q1; err = nil; isfbuf = TRUE; if (x->fcall.count < RBUFSIZE) r = fbufalloc(); else { isfbuf = FALSE; r = emalloc(x->fcall.count * UTFmax + 1); } for (n = 0; n < x->fcall.count; n += m) { p = x->fcall.data + n; w->owner = *p++; /* disgusting */ c = *p++; while (*p == ' ') p++; q0 = strtoul(p, &q, 10); if (q == p) goto Rescue; p = q; while (*p == ' ') p++; q1 = strtoul(p, &q, 10); if (q == p) goto Rescue; p = q; while (*p == ' ') p++; if (*p++ != '\n') goto Rescue; m = p - (x->fcall.data + n); if ('a' <= c && c <= 'z') t = &w->tag; else if ('A' <= c && c <= 'Z') t = &w->body; else goto Rescue; if (q0 > t->file->b.nc || q1 > t->file->b.nc || q0 > q1) goto Rescue; qlock(&row.lk); /* just like mousethread */ switch (c) { case 'x': case 'X': execute(t, q0, q1, TRUE, nil); break; case 'l': case 'L': look3(t, q0, q1, TRUE); break; default: qunlock(&row.lk); goto Rescue; } qunlock(&row.lk); } Out: if (isfbuf) fbuffree(r); else free(r); if (err) n = 0; fc.count = n; respond(x, &fc, err); return; Rescue: err = Ebadevent; goto Out; } void xfidutfread(Xfid* x, Text* t, uint q1, int qid) { Fcall fc; Window* w; Rune* r; char *b, *b1; uint q, off, boff; int m, n, nr, nb; w = t->w; wincommit(w, t); off = x->fcall.offset; r = fbufalloc(); b = fbufalloc(); b1 = fbufalloc(); n = 0; if (qid == w->utflastqid && off >= w->utflastboff && w->utflastq <= q1) { boff = w->utflastboff; q = w->utflastq; } else { /* BUG: stupid code: scan from beginning */ boff = 0; q = 0; } w->utflastqid = qid; while (q < q1 && n < x->fcall.count) { /* * Updating here avoids partial rune problem: we're always on a * char boundary. The cost is we will usually do one more read * than we really need, but that's better than being n^2. */ w->utflastboff = boff; w->utflastq = q; nr = q1 - q; if (nr > BUFSIZE / UTFmax) nr = BUFSIZE / UTFmax; bufread(&t->file->b, q, r, nr); nb = snprint(b, BUFSIZE + 1, "%.*S", nr, r); if (boff >= off) { m = nb; if (boff + m > off + x->fcall.count) m = off + x->fcall.count - boff; memmove(b1 + n, b, m); n += m; } else if (boff + nb > off) { if (n != 0) error("bad count in utfrune"); m = nb - (off - boff); if (m > x->fcall.count) m = x->fcall.count; memmove(b1, b + (off - boff), m); n += m; } boff += nb; q += nr; } fbuffree(r); fbuffree(b); fc.count = n; fc.data = b1; respond(x, &fc, nil); fbuffree(b1); } int xfidruneread(Xfid* x, Text* t, uint q0, uint q1) { Fcall fc; Window* w; Rune *r, junk; char *b, *b1; uint q, boff; int i, rw, m, n, nr, nb; w = t->w; wincommit(w, t); r = fbufalloc(); b = fbufalloc(); b1 = fbufalloc(); n = 0; q = q0; boff = 0; while (q < q1 && n < x->fcall.count) { nr = q1 - q; if (nr > BUFSIZE / UTFmax) nr = BUFSIZE / UTFmax; bufread(&t->file->b, q, r, nr); nb = snprint(b, BUFSIZE + 1, "%.*S", nr, r); m = nb; if (boff + m > x->fcall.count) { i = x->fcall.count - boff; /* copy whole runes only */ m = 0; nr = 0; while (m < i) { rw = chartorune(&junk, b + m); if (m + rw > i) break; m += rw; nr++; } if (m == 0) break; } memmove(b1 + n, b, m); n += m; boff += nb; q += nr; } fbuffree(r); fbuffree(b); fc.count = n; fc.data = b1; respond(x, &fc, nil); fbuffree(b1); return q - q0; } void xfideventread(Xfid* x, Window* w) { Fcall fc; int i, n; i = 0; x->flushed = FALSE; while (w->nevents == 0) { if (i) { if (!x->flushed) respond(x, &fc, "window shut down"); return; } w->eventx = x; winunlock(w); recvp(x->c); winlock(w, 'F'); i++; } n = w->nevents; if (n > x->fcall.count) n = x->fcall.count; fc.count = n; fc.data = w->events; respond(x, &fc, nil); w->nevents -= n; if (w->nevents) { memmove(w->events, w->events + n, w->nevents); w->events = erealloc(w->events, w->nevents); } else { free(w->events); w->events = nil; } } void xfidindexread(Xfid* x) { Fcall fc; int i, j, m, n, nmax, isbuf, cnt, off; Window* w; char* b; Rune* r; Column* c; qlock(&row.lk); nmax = 0; for (j = 0; j < row.ncol; j++) { c = row.col[j]; for (i = 0; i < c->nw; i++) { w = c->w[i]; nmax += Ctlsize + w->tag.file->b.nc * UTFmax + 1; } } nmax++; isbuf = (nmax <= RBUFSIZE); if (isbuf) b = (char*)x->buf; else b = emalloc(nmax); r = fbufalloc(); n = 0; for (j = 0; j < row.ncol; j++) { c = row.col[j]; for (i = 0; i < c->nw; i++) { w = c->w[i]; /* only show the currently active window of a set */ if (w->body.file->curtext != &w->body) continue; winctlprint(w, b + n, 0); n += Ctlsize; m = min(RBUFSIZE, w->tag.file->b.nc); bufread(&w->tag.file->b, 0, r, m); m = n + snprint(b + n, nmax - n - 1, "%.*S", m, r); while (n < m && b[n] != '\n') n++; b[n++] = '\n'; } } qunlock(&row.lk); off = x->fcall.offset; cnt = x->fcall.count; if (off > n) off = n; if (off + cnt > n) cnt = n - off; fc.count = cnt; memmove(r, b + off, cnt); fc.data = (char*)r; if (!isbuf) free(b); respond(x, &fc, nil); fbuffree(r); }