Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#include        "u.h"
#include        <trace.h>
#include        "tos.h"
#include        "../port/lib.h"
#include        "mem.h"
#include        "dat.h"
#include        "fns.h"
#include        "../port/error.h"
#include        "ureg.h"
#include        "../port/edf.h"

enum
{
        Qdir,
        Qtrace,
        Qargs,
        Qctl,
        Qfd,
        Qfpregs,
        Qkregs,
        Qmem,
        Qnote,
        Qnoteid,
        Qnotepg,
        Qns,
        Qproc,
        Qregs,
        Qsegment,
        Qstatus,
        Qtext,
        Qwait,
        Qprofile,
        Qsyscall,
};

enum
{
        CMclose,
        CMclosefiles,
        CMfixedpri,
        CMhang,
        CMkill,
        CMnohang,
        CMnoswap,
        CMpri,
        CMprivate,
        CMprofile,
        CMstart,
        CMstartstop,
        CMstartsyscall,
        CMstop,
        CMwaitstop,
        CMwired,
        CMtrace,
        /* real time */
        CMperiod,
        CMdeadline,
        CMcost,
        CMsporadic,
        CMdeadlinenotes,
        CMadmit,
        CMextra,
        CMexpel,
        CMevent,
};

enum{
        Nevents = 0x4000,
        Emask = Nevents - 1,
};

#define STATSIZE        (2*KNAMELEN+12+9*12)
/*
 * Status, fd, and ns are left fully readable (0444) because of their use in debugging,
 * particularly on shared servers.
 * Arguably, ns and fd shouldn't be readable; if you'd prefer, change them to 0000
 */
Dirtab procdir[] =
{
        "args",         {Qargs},        0,                      0660,
        "ctl",          {Qctl},         0,                      0000,
        "fd",           {Qfd},          0,                      0444,
        "fpregs",       {Qfpregs},      sizeof(FPsave),         0000,
        "kregs",        {Qkregs},       sizeof(Ureg),           0400,
        "mem",          {Qmem},         0,                      0000,
        "note",         {Qnote},        0,                      0000,
        "noteid",       {Qnoteid},      0,                      0664,
        "notepg",       {Qnotepg},      0,                      0000,
        "ns",           {Qns},          0,                      0444,
        "proc",         {Qproc},        0,                      0400,
        "regs",         {Qregs},        sizeof(Ureg),           0000,
        "segment",      {Qsegment},     0,                      0444,
        "status",       {Qstatus},      STATSIZE,               0444,
        "text",         {Qtext},        0,                      0000,
        "wait",         {Qwait},        0,                      0400,
        "profile",      {Qprofile},     0,                      0400,
        "syscall",      {Qsyscall},     0,                      0400,   
};

static
Cmdtab proccmd[] = {
        CMclose,                "close",                2,
        CMclosefiles,           "closefiles",           1,
        CMfixedpri,             "fixedpri",             2,
        CMhang,                 "hang",                 1,
        CMnohang,               "nohang",               1,
        CMnoswap,               "noswap",               1,
        CMkill,                 "kill",                 1,
        CMpri,                  "pri",                  2,
        CMprivate,              "private",              1,
        CMprofile,              "profile",              1,
        CMstart,                "start",                1,
        CMstartstop,            "startstop",            1,
        CMstartsyscall,         "startsyscall",         1,
        CMstop,                 "stop",                 1,
        CMwaitstop,             "waitstop",             1,
        CMwired,                "wired",                2,
        CMtrace,                "trace",                0,
        CMperiod,               "period",               2,
        CMdeadline,             "deadline",             2,
        CMcost,                 "cost",                 2,
        CMsporadic,             "sporadic",             1,
        CMdeadlinenotes,        "deadlinenotes",        1,
        CMadmit,                "admit",                1,
        CMextra,                "extra",                1,
        CMexpel,                "expel",                1,
        CMevent,                "event",                1,
};

/* Segment type from portdat.h */
static char *sname[]={ "Text", "Data", "Bss", "Stack", "Shared", "Phys", };

/*
 * Qids are, in path:
 *       5 bits of file type (qids above)
 *      26 bits of process slot number + 1
 *           in vers,
 *      32 bits of pid, for consistency checking
 * If notepg, c->pgrpid.path is pgrp slot, .vers is noteid.
 */
#define QSHIFT  5       /* location in qid of proc slot # */

#define QID(q)          ((((ulong)(q).path) & ((1<<QSHIFT)-1)) >> 0)
#define SLOT(q)         (((((ulong)(q).path) & ~(1UL<<31)) >> QSHIFT) - 1)
#define PID(q)          ((q).vers)
#define NOTEID(q)       ((q).vers)

void    procctlreq(Proc*, char*, int);
int     procctlmemio(Proc*, ulong, int, void*, int);
Chan*   proctext(Chan*, Proc*);
Segment* txt2data(Proc*, Segment*);
int     procstopped(void*);
void    mntscan(Mntwalk*, Proc*);

static Traceevent *tevents;
static Lock tlock;
static int topens;
static int tproduced, tconsumed;
void (*proctrace)(Proc*, int, vlong);

extern int unfair;

static void
profclock(Ureg *ur, Timer *)
{
        Tos *tos;

        if(up == 0 || up->state != Running)
                return;

        /* user profiling clock */
        if(userureg(ur)){
                tos = (Tos*)(USTKTOP-sizeof(Tos));
                tos->clock += TK2MS(1);
                segclock(ur->pc);
        }
}

static int
procgen(Chan *c, char *name, Dirtab *tab, int, int s, Dir *dp)
{
        Qid qid;
        Proc *p;
        char *ename;
        Segment *q;
        ulong pid, path, perm, len;

        if(s == DEVDOTDOT){
                mkqid(&qid, Qdir, 0, QTDIR);
                devdir(c, qid, "#p", 0, eve, 0555, dp);
                return 1;
        }

        if(c->qid.path == Qdir){
                if(s == 0){
                        strcpy(up->genbuf, "trace");
                        mkqid(&qid, Qtrace, -1, QTFILE);
                        devdir(c, qid, up->genbuf, 0, eve, 0444, dp);
                        return 1;
                }

                if(name != nil){
                        /* ignore s and use name to find pid */
                        pid = strtol(name, &ename, 10);
                        if(pid==0 || ename[0]!='\0')
                                return -1;
                        s = procindex(pid);
                        if(s < 0)
                                return -1;
                }
                else if(--s >= conf.nproc)
                        return -1;

                p = proctab(s);
                pid = p->pid;
                if(pid == 0)
                        return 0;
                snprint(up->genbuf, sizeof up->genbuf, "%lud", pid);
                /*
                 * String comparison is done in devwalk so name must match its formatted pid
                */
                if(name != nil && strcmp(name, up->genbuf) != 0)
                        return -1;
                mkqid(&qid, (s+1)<<QSHIFT, pid, QTDIR);
                devdir(c, qid, up->genbuf, 0, p->user, DMDIR|0555, dp);
                return 1;
        }
        if(c->qid.path == Qtrace){
                strcpy(up->genbuf, "trace");
                mkqid(&qid, Qtrace, -1, QTFILE);
                devdir(c, qid, up->genbuf, 0, eve, 0444, dp);
                return 1;
        }
        if(s >= nelem(procdir))
                return -1;
        if(tab)
                panic("procgen");

        tab = &procdir[s];
        path = c->qid.path&~(((1<<QSHIFT)-1));  /* slot component */

        /* p->procmode determines default mode for files in /proc */
        p = proctab(SLOT(c->qid));
        perm = tab->perm;
        if(perm == 0)
                perm = p->procmode;
        else    /* just copy read bits */
                perm |= p->procmode & 0444;

        len = tab->length;
        switch(QID(c->qid)) {
        case Qwait:
                len = p->nwait; /* incorrect size, but >0 means there's something to read */
                break;
        case Qprofile:
                q = p->seg[TSEG];
                if(q && q->profile) {
                        len = (q->top-q->base)>>LRESPROF;
                        len *= sizeof(*q->profile);
                }
                break;
        }

        mkqid(&qid, path|tab->qid.path, c->qid.vers, QTFILE);
        devdir(c, qid, tab->name, len, p->user, perm, dp);
        return 1;
}

static void
_proctrace(Proc* p, Tevent etype, vlong ts)
{
        Traceevent *te;

        if (p->trace == 0 || topens == 0 ||
                tproduced - tconsumed >= Nevents)
                return;

        te = &tevents[tproduced&Emask];
        te->pid = p->pid;
        te->etype = etype;
        if (ts == 0)
                te->time = todget(nil);
        else
                te->time = ts;
        tproduced++;
}

static void
procinit(void)
{
        if(conf.nproc >= (1<<(31-QSHIFT))-1)
                print("warning: too many procs for devproc\n");
        addclock0link((void (*)(void))profclock, 113);  /* Relative prime to HZ */
}

static Chan*
procattach(char *spec)
{
        return devattach('p', spec);
}

static Walkqid*
procwalk(Chan *c, Chan *nc, char **name, int nname)
{
        return devwalk(c, nc, name, nname, 0, 0, procgen);
}

static int
procstat(Chan *c, uchar *db, int n)
{
        return devstat(c, db, n, 0, 0, procgen);
}

/*
 *  none can't read or write state on other
 *  processes.  This is to contain access of
 *  servers running as none should they be
 *  subverted by, for example, a stack attack.
 */
static void
nonone(Proc *p)
{
        if(p == up)
                return;
        if(strcmp(up->user, "none") != 0)
                return;
        if(iseve())
                return;
        error(Eperm);
}

static Chan*
procopen(Chan *c, int omode)
{
        Proc *p;
        Pgrp *pg;
        Chan *tc;
        int pid;

        if(c->qid.type & QTDIR)
                return devopen(c, omode, 0, 0, procgen);

        if(QID(c->qid) == Qtrace){
                if (omode != OREAD) 
                        error(Eperm);
                lock(&tlock);
                if (waserror()){
                        unlock(&tlock);
                        nexterror();
                }
                if (topens > 0)
                        error("already open");
                topens++;
                if (tevents == nil){
                        tevents = (Traceevent*)malloc(sizeof(Traceevent) * Nevents);
                        if(tevents == nil)
                                error(Enomem);
                        tproduced = tconsumed = 0;
                }
                proctrace = _proctrace;
                unlock(&tlock);
                poperror();

                c->mode = openmode(omode);
                c->flag |= COPEN;
                c->offset = 0;
                return c;
        }
                
        p = proctab(SLOT(c->qid));
        qlock(&p->debug);
        if(waserror()){
                qunlock(&p->debug);
                nexterror();
        }
        pid = PID(c->qid);
        if(p->pid != pid)
                error(Eprocdied);

        omode = openmode(omode);

        switch(QID(c->qid)){
        case Qtext:
                if(omode != OREAD)
                        error(Eperm);
                tc = proctext(c, p);
                tc->offset = 0;
                qunlock(&p->debug);
                poperror();
                cclose(c);
                return tc;

        case Qproc:
        case Qkregs:
        case Qsegment:
        case Qprofile:
        case Qfd:
                if(omode != OREAD)
                        error(Eperm);
                break;

        case Qnote:
                if(p->privatemem)
                        error(Eperm);
                break;

        case Qmem:
        case Qctl:
                if(p->privatemem)
                        error(Eperm);
                nonone(p);
                break;

        case Qargs:
        case Qnoteid:
        case Qstatus:
        case Qwait:
        case Qregs:
        case Qfpregs:
        case Qsyscall:  
                nonone(p);
                break;

        case Qns:
                if(omode != OREAD)
                        error(Eperm);
                c->aux = malloc(sizeof(Mntwalk));
                break;

        case Qnotepg:
                nonone(p);
                pg = p->pgrp;
                if(pg == nil)
                        error(Eprocdied);
                if(omode!=OWRITE || pg->pgrpid == 1)
                        error(Eperm);
                c->pgrpid.path = pg->pgrpid+1;
                c->pgrpid.vers = p->noteid;
                break;

        default:
                pprint("procopen %#lux\n", QID(c->qid));
                error(Egreg);
        }

        /* Affix pid to qid */
        if(p->state != Dead)
                c->qid.vers = p->pid;

        /* make sure the process slot didn't get reallocated while we were playing */
        coherence();
        if(p->pid != pid)
                error(Eprocdied);

        tc = devopen(c, omode, 0, 0, procgen);
        qunlock(&p->debug);
        poperror();

        return tc;
}

static int
procwstat(Chan *c, uchar *db, int n)
{
        Proc *p;
        Dir *d;

        if(c->qid.type&QTDIR)
                error(Eperm);

        if(QID(c->qid) == Qtrace)
                return devwstat(c, db, n);
                
        p = proctab(SLOT(c->qid));
        nonone(p);
        d = nil;
        if(waserror()){
                free(d);
                qunlock(&p->debug);
                nexterror();
        }
        qlock(&p->debug);

        if(p->pid != PID(c->qid))
                error(Eprocdied);

        if(strcmp(up->user, p->user) != 0 && strcmp(up->user, eve) != 0)
                error(Eperm);

        d = smalloc(sizeof(Dir)+n);
        n = convM2D(db, n, &d[0], (char*)&d[1]);
        if(n == 0)
                error(Eshortstat);
        if(!emptystr(d->uid) && strcmp(d->uid, p->user) != 0){
                if(strcmp(up->user, eve) != 0)
                        error(Eperm);
                else
                        kstrdup(&p->user, d->uid);
        }
        /* p->procmode determines default mode for files in /proc */
        if(d->mode != ~0UL)
                p->procmode = d->mode&0777;

        poperror();
        free(d);
        qunlock(&p->debug);
        return n;
}


static long
procoffset(long offset, char *va, int *np)
{
        if(offset > 0) {
                offset -= *np;
                if(offset < 0) {
                        memmove(va, va+*np+offset, -offset);
                        *np = -offset;
                }
                else
                        *np = 0;
        }
        return offset;
}

static int
procqidwidth(Chan *c)
{
        char buf[32];

        return snprint(buf, sizeof buf, "%lud", c->qid.vers);
}

int
procfdprint(Chan *c, int fd, int w, char *s, int ns)
{
        int n;

        if(w == 0)
                w = procqidwidth(c);
        n = snprint(s, ns, "%3d %.2s %C %4ld (%.16llux %*lud %.2ux) %5ld %8lld %s\n",
                fd,
                &"r w rw"[(c->mode&3)<<1],
                devtab[c->type]->dc, c->dev,
                c->qid.path, w, c->qid.vers, c->qid.type,
                c->iounit, c->offset, c->path->s);
        return n;
}

static int
procfds(Proc *p, char *va, int count, long offset)
{
        Fgrp *f;
        Chan *c;
        char buf[256];
        int n, i, w, ww;
        char *a;

        /* print to buf to avoid holding fgrp lock while writing to user space */
        if(count > sizeof buf)
                count = sizeof buf;
        a = buf;

        qlock(&p->debug);
        f = p->fgrp;
        if(f == nil){
                qunlock(&p->debug);
                return 0;
        }
        lock(f);
        if(waserror()){
                unlock(f);
                qunlock(&p->debug);
                nexterror();
        }

        n = readstr(0, a, count, p->dot->path->s);
        n += snprint(a+n, count-n, "\n");
        offset = procoffset(offset, a, &n);
        /* compute width of qid.path */
        w = 0;
        for(i = 0; i <= f->maxfd; i++) {
                c = f->fd[i];
                if(c == nil)
                        continue;
                ww = procqidwidth(c);
                if(ww > w)
                        w = ww;
        }
        for(i = 0; i <= f->maxfd; i++) {
                c = f->fd[i];
                if(c == nil)
                        continue;
                n += procfdprint(c, i, w, a+n, count-n);
                offset = procoffset(offset, a, &n);
        }
        unlock(f);
        qunlock(&p->debug);
        poperror();

        /* copy result to user space, now that locks are released */
        memmove(va, buf, n);

        return n;
}

static void
procclose(Chan * c)
{
        if(QID(c->qid) == Qtrace){
                lock(&tlock);
                if(topens > 0)
                        topens--;
                if(topens == 0)
                        proctrace = nil;
                unlock(&tlock);
        }
        if(QID(c->qid) == Qns && c->aux != 0)
                free(c->aux);
}

static void
int2flag(int flag, char *s)
{
        if(flag == 0){
                *s = '\0';
                return;
        }
        *s++ = '-';
        if(flag & MAFTER)
                *s++ = 'a';
        if(flag & MBEFORE)
                *s++ = 'b';
        if(flag & MCREATE)
                *s++ = 'c';
        if(flag & MCACHE)
                *s++ = 'C';
        *s = '\0';
}

static int
procargs(Proc *p, char *buf, int nbuf)
{
        int j, k, m;
        char *a;
        int n;

        a = p->args;
        if(p->setargs){
                snprint(buf, nbuf, "%s [%s]", p->text, p->args);
                return strlen(buf);
        }
        n = p->nargs;
        for(j = 0; j < nbuf - 1; j += m){
                if(n <= 0)
                        break;
                if(j != 0)
                        buf[j++] = ' ';
                m = snprint(buf+j, nbuf-j, "%q",  a);
                k = strlen(a) + 1;
                a += k;
                n -= k;
        }
        return j;
}

static int
eventsavailable(void *)
{
        return tproduced > tconsumed;
}

static long
procread(Chan *c, void *va, long n, vlong off)
{
        /* NSEG*32 was too small for worst cases */
        char *a, flag[10], *sps, *srv, statbuf[NSEG*64];
        int i, j, m, navail, ne, pid, rsize;
        long l;
        uchar *rptr;
        ulong offset;
        Confmem *cm;
        Mntwalk *mw;
        Proc *p;
        Segment *sg, *s;
        Ureg kur;
        Waitq *wq;
        
        a = va;
        offset = off;

        if(c->qid.type & QTDIR)
                return devdirread(c, a, n, 0, 0, procgen);

        if(QID(c->qid) == Qtrace){
                if(!eventsavailable(nil))
                        return 0;

                rptr = (uchar*)va;
                navail = tproduced - tconsumed;
                if(navail > n / sizeof(Traceevent))
                        navail = n / sizeof(Traceevent);
                while(navail > 0) {
                        ne = ((tconsumed & Emask) + navail > Nevents)? 
                                        Nevents - (tconsumed & Emask): navail;
                        memmove(rptr, &tevents[tconsumed & Emask], 
                                        ne * sizeof(Traceevent));

                        tconsumed += ne;
                        rptr += ne * sizeof(Traceevent);
                        navail -= ne;
                }
                return rptr - (uchar*)va;
        }

        p = proctab(SLOT(c->qid));
        if(p->pid != PID(c->qid))
                error(Eprocdied);

        switch(QID(c->qid)){
        case Qargs:
                qlock(&p->debug);
                j = procargs(p, up->genbuf, sizeof up->genbuf);
                qunlock(&p->debug);
                if(offset >= j)
                        return 0;
                if(offset+n > j)
                        n = j-offset;
                memmove(a, &up->genbuf[offset], n);
                return n;
        case Qsyscall:
                if(!p->syscalltrace)
                        return 0;
                n = readstr(offset, a, n, p->syscalltrace);
                return n;

        case Qmem:
                if(offset < KZERO)
                        return procctlmemio(p, offset, n, va, 1);

                if(!iseve())
                        error(Eperm);

                /* validate kernel addresses */
                if(offset < (ulong)end) {
                        if(offset+n > (ulong)end)
                                n = (ulong)end - offset;
                        memmove(a, (char*)offset, n);
                        return n;
                }
                for(i=0; i<nelem(conf.mem); i++){
                        cm = &conf.mem[i];
                        /* klimit-1 because klimit might be zero! */
                        if(cm->kbase <= offset && offset <= cm->klimit-1){
                                if(offset+n >= cm->klimit-1)
                                        n = cm->klimit - offset;
                                memmove(a, (char*)offset, n);
                                return n;
                        }
                }
                error(Ebadarg);

        case Qprofile:
                s = p->seg[TSEG];
                if(s == 0 || s->profile == 0)
                        error("profile is off");
                i = (s->top-s->base)>>LRESPROF;
                i *= sizeof(*s->profile);
                if(offset >= i)
                        return 0;
                if(offset+n > i)
                        n = i - offset;
                memmove(a, ((char*)s->profile)+offset, n);
                return n;

        case Qnote:
                qlock(&p->debug);
                if(waserror()){
                        qunlock(&p->debug);
                        nexterror();
                }
                if(p->pid != PID(c->qid))
                        error(Eprocdied);
                if(n < 1)       /* must accept at least the '\0' */
                        error(Etoosmall);
                if(p->nnote == 0)
                        n = 0;
                else {
                        m = strlen(p->note[0].msg) + 1;
                        if(m > n)
                                m = n;
                        memmove(va, p->note[0].msg, m);
                        ((char*)va)[m-1] = '\0';
                        p->nnote--;
                        memmove(p->note, p->note+1, p->nnote*sizeof(Note));
                        n = m;
                }
                if(p->nnote == 0)
                        p->notepending = 0;
                poperror();
                qunlock(&p->debug);
                return n;

        case Qproc:
                if(offset >= sizeof(Proc))
                        return 0;
                if(offset+n > sizeof(Proc))
                        n = sizeof(Proc) - offset;
                memmove(a, ((char*)p)+offset, n);
                return n;

        case Qregs:
                rptr = (uchar*)p->dbgreg;
                rsize = sizeof(Ureg);
                goto regread;

        case Qkregs:
                memset(&kur, 0, sizeof(Ureg));
                setkernur(&kur, p);
                rptr = (uchar*)&kur;
                rsize = sizeof(Ureg);
                goto regread;

        case Qfpregs:
                rptr = (uchar*)&p->fpsave;
                rsize = sizeof(FPsave);
        regread:
                if(rptr == 0)
                        error(Enoreg);
                if(offset >= rsize)
                        return 0;
                if(offset+n > rsize)
                        n = rsize - offset;
                memmove(a, rptr+offset, n);
                return n;

        case Qstatus:
                if(offset >= STATSIZE)
                        return 0;
                if(offset+n > STATSIZE)
                        n = STATSIZE - offset;

                sps = p->psstate;
                if(sps == 0)
                        sps = statename[p->state];
                memset(statbuf, ' ', sizeof statbuf);
                readstr(0, statbuf+0*KNAMELEN, KNAMELEN-1, p->text);
                readstr(0, statbuf+1*KNAMELEN, KNAMELEN-1, p->user);
                readstr(0, statbuf+2*KNAMELEN, 11, sps);
                j = 2*KNAMELEN + 12;

                for(i = 0; i < 6; i++) {
                        l = p->time[i];
                        if(i == TReal)
                                l = MACHP(0)->ticks - l;
                        l = TK2MS(l);
                        readnum(0, statbuf+j+NUMSIZE*i, NUMSIZE, l, NUMSIZE);
                }
                /* ignore stack, which is mostly non-existent */
                l = 0;
                for(i=1; i<NSEG; i++){
                        s = p->seg[i];
                        if(s)
                                l += s->top - s->base;
                }
                readnum(0, statbuf+j+NUMSIZE*6, NUMSIZE, l>>10, NUMSIZE);
                readnum(0, statbuf+j+NUMSIZE*7, NUMSIZE, p->basepri, NUMSIZE);
                readnum(0, statbuf+j+NUMSIZE*8, NUMSIZE, p->priority, NUMSIZE);
                memmove(a, statbuf+offset, n);
                return n;

        case Qsegment:
                j = 0;
                for(i = 0; i < NSEG; i++) {
                        sg = p->seg[i];
                        if(sg == 0)
                                continue;
                        j += snprint(statbuf+j, sizeof statbuf - j,
                                "%-6s %c%c %.8lux %.8lux %4ld\n",
                                sname[sg->type&SG_TYPE],
                                sg->type&SG_RONLY ? 'R' : ' ',
                                sg->profile ? 'P' : ' ',
                                sg->base, sg->top, sg->ref);
                }
                if(offset >= j)
                        return 0;
                if(offset+n > j)
                        n = j-offset;
                if(n == 0 && offset == 0)
                        exhausted("segments");
                memmove(a, &statbuf[offset], n);
                return n;

        case Qwait:
                if(!canqlock(&p->qwaitr))
                        error(Einuse);

                if(waserror()) {
                        qunlock(&p->qwaitr);
                        nexterror();
                }

                lock(&p->exl);
                if(up == p && p->nchild == 0 && p->waitq == 0) {
                        unlock(&p->exl);
                        error(Enochild);
                }
                pid = p->pid;
                while(p->waitq == 0) {
                        unlock(&p->exl);
                        sleep(&p->waitr, haswaitq, p);
                        if(p->pid != pid)
                                error(Eprocdied);
                        lock(&p->exl);
                }
                wq = p->waitq;
                p->waitq = wq->next;
                p->nwait--;
                unlock(&p->exl);

                qunlock(&p->qwaitr);
                poperror();
                n = snprint(a, n, "%d %lud %lud %lud %q",
                        wq->w.pid,
                        wq->w.time[TUser], wq->w.time[TSys], wq->w.time[TReal],
                        wq->w.msg);
                free(wq);
                return n;

        case Qns:
                qlock(&p->debug);
                if(waserror()){
                        qunlock(&p->debug);
                        nexterror();
                }
                if(p->pgrp == nil || p->pid != PID(c->qid))
                        error(Eprocdied);
                mw = c->aux;
                if(mw == nil)
                        error(Enomem);
                if(mw->cddone){
                        qunlock(&p->debug);
                        poperror();
                        return 0;
                }
                mntscan(mw, p);
                if(mw->mh == 0){
                        mw->cddone = 1;
                        i = snprint(a, n, "cd %s\n", p->dot->path->s);
                        qunlock(&p->debug);
                        poperror();
                        return i;
                }
                int2flag(mw->cm->mflag, flag);
                if(strcmp(mw->cm->to->path->s, "#M") == 0){
                        srv = srvname(mw->cm->to->mchan);
                        i = snprint(a, n, "mount %s %s %s %s\n", flag,
                                srv==nil? mw->cm->to->mchan->path->s : srv,
                                mw->mh->from->path->s, mw->cm->spec? mw->cm->spec : "");
                        free(srv);
                }else
                        i = snprint(a, n, "bind %s %s %s\n", flag,
                                mw->cm->to->path->s, mw->mh->from->path->s);
                qunlock(&p->debug);
                poperror();
                return i;

        case Qnoteid:
                return readnum(offset, va, n, p->noteid, NUMSIZE);
        case Qfd:
                return procfds(p, va, n, offset);
        }
        error(Egreg);
        return 0;               /* not reached */
}

void
mntscan(Mntwalk *mw, Proc *p)
{
        Pgrp *pg;
        Mount *t;
        Mhead *f;
        int nxt, i;
        ulong last, bestmid;

        pg = p->pgrp;
        rlock(&pg->ns);

        nxt = 0;
        bestmid = ~0;

        last = 0;
        if(mw->mh)
                last = mw->cm->mountid;

        for(i = 0; i < MNTHASH; i++) {
                for(f = pg->mnthash[i]; f; f = f->hash) {
                        for(t = f->mount; t; t = t->next) {
                                if(mw->mh == 0 ||
                                  (t->mountid > last && t->mountid < bestmid)) {
                                        mw->cm = t;
                                        mw->mh = f;
                                        bestmid = mw->cm->mountid;
                                        nxt = 1;
                                }
                        }
                }
        }
        if(nxt == 0)
                mw->mh = 0;

        runlock(&pg->ns);
}

static long
procwrite(Chan *c, void *va, long n, vlong off)
{
        int id, m;
        Proc *p, *t, *et;
        char *a, *arg, buf[ERRMAX];
        ulong offset = off;

        a = va;
        if(c->qid.type & QTDIR)
                error(Eisdir);

        p = proctab(SLOT(c->qid));

        /* Use the remembered noteid in the channel rather
         * than the process pgrpid
         */
        if(QID(c->qid) == Qnotepg) {
                pgrpnote(NOTEID(c->pgrpid), va, n, NUser);
                return n;
        }

        qlock(&p->debug);
        if(waserror()){
                qunlock(&p->debug);
                nexterror();
        }
        if(p->pid != PID(c->qid))
                error(Eprocdied);

        switch(QID(c->qid)){
        case Qargs:
                if(n == 0)
                        error(Eshort);
                if(n >= ERRMAX)
                        error(Etoobig);
                arg = malloc(n+1);
                if(arg == nil)
                        error(Enomem);
                memmove(arg, va, n);
                m = n;
                if(arg[m-1] != 0)
                        arg[m++] = 0;
                free(p->args);
                p->nargs = m;
                p->args = arg;
                p->setargs = 1;
                break;

        case Qmem:
                if(p->state != Stopped)
                        error(Ebadctl);

                n = procctlmemio(p, offset, n, va, 0);
                break;

        case Qregs:
                if(offset >= sizeof(Ureg))
                        n = 0;
                else if(offset+n > sizeof(Ureg))
                        n = sizeof(Ureg) - offset;
                if(p->dbgreg == 0)
                        error(Enoreg);
                setregisters(p->dbgreg, (char*)(p->dbgreg)+offset, va, n);
                break;

        case Qfpregs:
                if(offset >= sizeof(FPsave))
                        n = 0;
                else if(offset+n > sizeof(FPsave))
                        n = sizeof(FPsave) - offset;
                memmove((uchar*)&p->fpsave+offset, va, n);
                break;

        case Qctl:
                procctlreq(p, va, n);
                break;

        case Qnote:
                if(p->kp)
                        error(Eperm);
                if(n >= ERRMAX-1)
                        error(Etoobig);
                memmove(buf, va, n);
                buf[n] = 0;
                if(!postnote(p, 0, buf, NUser))
                        error("note not posted");
                break;
        case Qnoteid:
                id = atoi(a);
                if(id == p->pid) {
                        p->noteid = id;
                        break;
                }
                t = proctab(0);
                for(et = t+conf.nproc; t < et; t++) {
                        if(t->state == Dead)
                                continue;
                        if(id == t->noteid) {
                                if(strcmp(p->user, t->user) != 0)
                                        error(Eperm);
                                p->noteid = id;
                                break;
                        }
                }
                if(p->noteid != id)
                        error(Ebadarg);
                break;
        default:
                pprint("unknown qid in procwrite\n");
                error(Egreg);
        }
        poperror();
        qunlock(&p->debug);
        return n;
}

Dev procdevtab = {
        'p',
        "proc",

        devreset,
        procinit,
        devshutdown,
        procattach,
        procwalk,
        procstat,
        procopen,
        devcreate,
        procclose,
        procread,
        devbread,
        procwrite,
        devbwrite,
        devremove,
        procwstat,
};

Chan*
proctext(Chan *c, Proc *p)
{
        Chan *tc;
        Image *i;
        Segment *s;

        s = p->seg[TSEG];
        if(s == 0)
                error(Enonexist);
        if(p->state==Dead)
                error(Eprocdied);

        lock(s);
        i = s->image;
        if(i == 0) {
                unlock(s);
                error(Eprocdied);
        }
        unlock(s);

        lock(i);
        if(waserror()) {
                unlock(i);
                nexterror();
        }

        tc = i->c;
        if(tc == 0)
                error(Eprocdied);

        if(incref(tc) == 1 || (tc->flag&COPEN) == 0 || tc->mode!=OREAD) {
                cclose(tc);
                error(Eprocdied);
        }

        if(p->pid != PID(c->qid)){
                cclose(tc);
                error(Eprocdied);
        }

        unlock(i);
        poperror();

        return tc;
}

void
procstopwait(Proc *p, int ctl)
{
        int pid;

        if(p->pdbg)
                error(Einuse);
        if(procstopped(p) || p->state == Broken)
                return;

        if(ctl != 0)
                p->procctl = ctl;
        p->pdbg = up;
        pid = p->pid;
        qunlock(&p->debug);
        up->psstate = "Stopwait";
        if(waserror()) {
                p->pdbg = 0;
                qlock(&p->debug);
                nexterror();
        }
        sleep(&up->sleep, procstopped, p);
        poperror();
        qlock(&p->debug);
        if(p->pid != pid)
                error(Eprocdied);
}

static void
procctlcloseone(Proc *p, Fgrp *f, int fd)
{
        Chan *c;

        c = f->fd[fd];
        if(c == nil)
                return;
        f->fd[fd] = nil;
        unlock(f);
        qunlock(&p->debug);
        cclose(c);
        qlock(&p->debug);
        lock(f);
}

void
procctlclosefiles(Proc *p, int all, int fd)
{
        int i;
        Fgrp *f;

        f = p->fgrp;
        if(f == nil)
                error(Eprocdied);

        lock(f);
        f->ref++;
        if(all)
                for(i = 0; i < f->maxfd; i++)
                        procctlcloseone(p, f, i);
        else
                procctlcloseone(p, f, fd);
        unlock(f);
        closefgrp(f);
}

static char *
parsetime(vlong *rt, char *s)
{
        uvlong ticks;
        ulong l;
        char *e, *p;
        static int p10[] = {100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1};

        if (s == nil)
                return("missing value");
        ticks=strtoul(s, &e, 10);
        if (*e == '.'){
                p = e+1;
                l = strtoul(p, &e, 10);
                if(e-p > nelem(p10))
                        return "too many digits after decimal point";
                if(e-p == 0)
                        return "ill-formed number";
                l *= p10[e-p-1];
        }else
                l = 0;
        if (*e == '\0' || strcmp(e, "s") == 0){
                ticks = 1000000000 * ticks + l;
        }else if (strcmp(e, "ms") == 0){
                ticks = 1000000 * ticks + l/1000;
        }else if (strcmp(e, "µs") == 0 || strcmp(e, "us") == 0){
                ticks = 1000 * ticks + l/1000000;
        }else if (strcmp(e, "ns") != 0)
                return "unrecognized unit";
        *rt = ticks;
        return nil;
}

void
procctlreq(Proc *p, char *va, int n)
{
        Segment *s;
        int npc, pri;
        Cmdbuf *cb;
        Cmdtab *ct;
        vlong time;
        char *e;
        void (*pt)(Proc*, int, vlong);

        if(p->kp)       /* no ctl requests to kprocs */
                error(Eperm);

        cb = parsecmd(va, n);
        if(waserror()){
                free(cb);
                nexterror();
        }

        ct = lookupcmd(cb, proccmd, nelem(proccmd));

        switch(ct->index){
        case CMclose:
                procctlclosefiles(p, 0, atoi(cb->f[1]));
                break;
        case CMclosefiles:
                procctlclosefiles(p, 1, 0);
                break;
        case CMhang:
                p->hang = 1;
                break;
        case CMkill:
                switch(p->state) {
                case Broken:
                        unbreak(p);
                        break;
                case Stopped:
                        p->procctl = Proc_exitme;
                        postnote(p, 0, "sys: killed", NExit);
                        ready(p);
                        break;
                default:
                        p->procctl = Proc_exitme;
                        postnote(p, 0, "sys: killed", NExit);
                }
                break;
        case CMnohang:
                p->hang = 0;
                break;
        case CMnoswap:
                p->noswap = 1;
                break;
        case CMpri:
                pri = atoi(cb->f[1]);
                if(pri > PriNormal && !iseve())
                        error(Eperm);
                procpriority(p, pri, 0);
                break;
        case CMfixedpri:
                pri = atoi(cb->f[1]);
                if(pri > PriNormal && !iseve())
                        error(Eperm);
                procpriority(p, pri, 1);
                break;
        case CMprivate:
                p->privatemem = 1;
                break;
        case CMprofile:
                s = p->seg[TSEG];
                if(s == 0 || (s->type&SG_TYPE) != SG_TEXT)
                        error(Ebadctl);
                if(s->profile != 0)
                        free(s->profile);
                npc = (s->top-s->base)>>LRESPROF;
                s->profile = malloc(npc*sizeof(*s->profile));
                if(s->profile == 0)
                        error(Enomem);
                break;
        case CMstart:
                if(p->state != Stopped)
                        error(Ebadctl);
                ready(p);
                break;
        case CMstartstop:
                if(p->state != Stopped)
                        error(Ebadctl);
                p->procctl = Proc_traceme;
                ready(p);
                procstopwait(p, Proc_traceme);
                break;
        case CMstartsyscall:
                if(p->state != Stopped)
                        error(Ebadctl);
                p->procctl = Proc_tracesyscall;
                ready(p);
                procstopwait(p, Proc_tracesyscall);
                break;
        case CMstop:
                procstopwait(p, Proc_stopme);
                break;
        case CMwaitstop:
                procstopwait(p, 0);
                break;
        case CMwired:
                procwired(p, atoi(cb->f[1]));
                break;
        case CMtrace:
                switch(cb->nf){
                case 1:
                        p->trace ^= 1;
                        break;
                case 2:
                        p->trace = (atoi(cb->f[1]) != 0);
                        break;
                default:
                        error("args");
                }
                break;
        /* real time */
        case CMperiod:
                if(p->edf == nil)
                        edfinit(p);
                if(e=parsetime(&time, cb->f[1]))        /* time in ns */
                        error(e);
                edfstop(p);
                p->edf->T = time/1000;  /* Edf times are in µs */
                break;
        case CMdeadline:
                if(p->edf == nil)
                        edfinit(p);
                if(e=parsetime(&time, cb->f[1]))
                        error(e);
                edfstop(p);
                p->edf->D = time/1000;
                break;
        case CMcost:
                if(p->edf == nil)
                        edfinit(p);
                if(e=parsetime(&time, cb->f[1]))
                        error(e);
                edfstop(p);
                p->edf->C = time/1000;
                break;
        case CMsporadic:
                if(p->edf == nil)
                        edfinit(p);
                p->edf->flags |= Sporadic;
                break;
        case CMdeadlinenotes:
                if(p->edf == nil)
                        edfinit(p);
                p->edf->flags |= Sendnotes;
                break;
        case CMadmit:
                if(p->edf == 0)
                        error("edf params");
                if(e = edfadmit(p))
                        error(e);
                break;
        case CMextra:
                if(p->edf == nil)
                        edfinit(p);
                p->edf->flags |= Extratime;
                break;
        case CMexpel:
                if(p->edf)
                        edfstop(p);
                break;
        case CMevent:
                pt = proctrace;
                if(up->trace && pt)
                        pt(up, SUser, 0);
                break;
        }

        poperror();
        free(cb);
}

int
procstopped(void *a)
{
        Proc *p = a;
        return p->state == Stopped;
}

int
procctlmemio(Proc *p, ulong offset, int n, void *va, int read)
{
        KMap *k;
        Pte *pte;
        Page *pg;
        Segment *s;
        ulong soff, l;
        char *a = va, *b;

        for(;;) {
                s = seg(p, offset, 1);
                if(s == 0)
                        error(Ebadarg);

                if(offset+n >= s->top)
                        n = s->top-offset;

                if(!read && (s->type&SG_TYPE) == SG_TEXT)
                        s = txt2data(p, s);

                s->steal++;
                soff = offset-s->base;
                if(waserror()) {
                        s->steal--;
                        nexterror();
                }
                if(fixfault(s, offset, read, 0) == 0)
                        break;
                poperror();
                s->steal--;
        }
        poperror();
        pte = s->map[soff/PTEMAPMEM];
        if(pte == 0)
                panic("procctlmemio");
        pg = pte->pages[(soff&(PTEMAPMEM-1))/BY2PG];
        if(pagedout(pg))
                panic("procctlmemio1");

        l = BY2PG - (offset&(BY2PG-1));
        if(n > l)
                n = l;

        k = kmap(pg);
        if(waserror()) {
                s->steal--;
                kunmap(k);
                nexterror();
        }
        b = (char*)VA(k);
        b += offset&(BY2PG-1);
        if(read == 1)
                memmove(a, b, n);       /* This can fault */
        else
                memmove(b, a, n);
        kunmap(k);
        poperror();

        /* Ensure the process sees text page changes */
        if(s->flushme)
                memset(pg->cachectl, PG_TXTFLUSH, sizeof(pg->cachectl));

        s->steal--;

        if(read == 0)
                p->newtlb = 1;

        return n;
}

Segment*
txt2data(Proc *p, Segment *s)
{
        int i;
        Segment *ps;

        ps = newseg(SG_DATA, s->base, s->size);
        ps->image = s->image;
        incref(ps->image);
        ps->fstart = s->fstart;
        ps->flen = s->flen;
        ps->flushme = 1;

        qlock(&p->seglock);
        for(i = 0; i < NSEG; i++)
                if(p->seg[i] == s)
                        break;
        if(i == NSEG)
                panic("segment gone");

        qunlock(&s->lk);
        putseg(s);
        qlock(&ps->lk);
        p->seg[i] = ps;
        qunlock(&p->seglock);

        return ps;
}

Segment*
data2txt(Segment *s)
{
        Segment *ps;

        ps = newseg(SG_TEXT, s->base, s->size);
        ps->image = s->image;
        incref(ps->image);
        ps->fstart = s->fstart;
        ps->flen = s->flen;
        ps->flushme = 1;

        return ps;
}