ref: eb4f514a94fdfb05ea0c8ce98cd6bac153c8b3f6
dir: /sys/src/9/port/devproc.c/
#include "u.h"
#include <trace.h>
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#include "ureg.h"
#include "edf.h"
#include <pool.h>
enum
{
Qdir,
Qtrace,
Qargs,
Qctl,
Qfd,
Qfpregs,
Qkregs,
Qmem,
Qnote,
Qnoteid,
Qnotepg,
Qns,
Qppid,
Qproc,
Qregs,
Qsegment,
Qstatus,
Qtext,
Qwait,
Qprofile,
Qsyscall,
Qwatchpt,
};
enum
{
CMclose,
CMclosefiles,
CMfixedpri,
CMhang,
CMkill,
CMnohang,
CMnoswap,
CMpri,
CMprivate,
CMprofile,
CMstart,
CMstartstop,
CMstartsyscall,
CMstop,
CMwaitstop,
CMwired,
CMtrace,
CMinterrupt,
CMnointerrupt,
/* real time */
CMperiod,
CMdeadline,
CMcost,
CMsporadic,
CMdeadlinenotes,
CMadmit,
CMextra,
CMexpel,
CMevent,
};
enum{
Nevents = 0x4000,
Emask = Nevents - 1,
};
#define STATSIZE (2*28+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,
"ppid", {Qppid}, 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,
"watchpt", {Qwatchpt}, 0, 0600,
};
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,
CMinterrupt, "interrupt", 1,
CMnointerrupt, "nointerrupt", 1,
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", "Fixed", "Sticky" };
/*
* Qids are, in path:
* 4 bits of file type (qids above)
* 23 bits of process slot number + 1
* in vers,
* 32 bits of pid, for consistency checking
*/
#define QSHIFT 5 /* location in qid of proc slot # */
#define QID(q) ((((ulong)(q).path)&0x0000001F)>>0)
#define SLOTMAX 0x4000000
#define SLOT(q) (((((ulong)(q).path)>>QSHIFT)&(SLOTMAX-1))-1)
#define PID(q) ((q).vers)
#define NOTEID(q) ((q).vers)
static void procctlreq(Proc*, char*, int);
static long procctlmemio(Chan*, Proc*, uintptr, void*, long, int);
static Chan* proctext(Chan*, Proc*);
static int procstopped(void*);
static Traceevent *tevents;
static Lock tlock;
static int topens;
static int tproduced, tconsumed;
void (*proctrace)(Proc*, int, vlong);
static int lenwatchpt(Proc *);
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, 0400, 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);
if(p == nil)
return 0;
pid = p->pid;
if(pid == 0)
return 0;
/*
* String comparison is done in devwalk so name must match its formatted pid
*/
snprint(up->genbuf, sizeof(up->genbuf), "%lud", 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, 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, 0400, 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 != nil && q->profile != nil) {
len = (q->top-q->base)>>LRESPROF;
len *= sizeof(*q->profile);
}
break;
case Qwatchpt:
len = lenwatchpt(p);
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)
{
/* slot masks: lets see how big we can go */
if(conf.nproc > SLOTMAX)
panic("warning: too many procs for devproc\n");
}
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 void
changenoteid(Proc *p, ulong noteid)
{
Proc *pp;
int i;
if(noteid <= 0)
error(Ebadarg);
if(noteid == p->noteid)
return;
if(noteid == p->pid){
setnoteid(p, noteid);
return;
}
for(i = 0; (pp = proctab(i)) != nil; i++){
if(pp->noteid != noteid || pp->kp)
continue;
if(strcmp(pp->user, p->user) == 0){
nonone(pp);
setnoteid(p, noteid);
return;
}
}
error(Eperm);
}
static void clearwatchpt(Proc *p);
static Chan*
procopen(Chan *c, int omode0)
{
Proc *p;
Chan *tc;
int pid;
int omode;
if(c->qid.type & QTDIR)
return devopen(c, omode0, 0, 0, procgen);
if(QID(c->qid) == Qtrace){
if (omode0 != OREAD || !iseve())
error(Eperm);
lock(&tlock);
if (waserror()){
topens--;
unlock(&tlock);
nexterror();
}
if (topens++ > 0)
error("already open");
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(omode0);
c->flag |= COPEN;
c->offset = 0;
return c;
}
p = proctab(SLOT(c->qid));
eqlock(&p->debug);
if(waserror()){
qunlock(&p->debug);
nexterror();
}
pid = PID(c->qid);
if(p == nil || p->pid != pid)
error(Eprocdied);
omode = openmode(omode0);
switch(QID(c->qid)){
case Qtext:
if(omode != OREAD)
error(Eperm);
nonone(p);
qunlock(&p->debug);
poperror();
tc = proctext(c, p);
tc->offset = 0;
cclose(c);
return tc;
case Qstatus:
case Qppid:
case Qproc:
case Qkregs:
case Qsegment:
case Qns:
case Qfd:
if(omode != OREAD)
error(Eperm);
break;
case Qctl:
case Qargs:
case Qwait:
case Qnoteid:
if(omode == OREAD)
break;
case Qnote:
if(p->kp)
error(Eperm);
break;
case Qnotepg:
if(p->kp || omode != OWRITE)
error(Eperm);
pid = p->noteid;
break;
case Qmem:
case Qregs:
case Qfpregs:
case Qprofile:
case Qsyscall:
case Qwatchpt:
if(p->kp || p->privatemem)
error(Eperm);
break;
default:
print("procopen %#lux\n", QID(c->qid));
error(Egreg);
}
nonone(p);
/* Affix pid to qid */
if(pid == 0)
error(Eprocdied);
c->qid.vers = pid;
tc = devopen(c, omode, 0, 0, procgen);
if(waserror()){
cclose(tc);
nexterror();
}
switch(QID(c->qid)){
case Qwatchpt:
if((omode0 & OTRUNC) != 0)
clearwatchpt(p);
break;
}
poperror();
qunlock(&p->debug);
poperror();
return tc;
}
static int
procwstat(Chan *c, uchar *db, int n)
{
Dir *d;
Proc *p;
if(c->qid.type&QTDIR)
error(Eperm);
switch(QID(c->qid)){
case Qnotepg:
case Qtrace:
return devwstat(c, db, n);
}
d = smalloc(sizeof(Dir)+n);
if(waserror()){
free(d);
nexterror();
}
n = convM2D(db, n, &d[0], (char*)&d[1]);
if(n == 0)
error(Eshortstat);
p = proctab(SLOT(c->qid));
eqlock(&p->debug);
if(waserror()){
qunlock(&p->debug);
nexterror();
}
if(p->pid != PID(c->qid))
error(Eprocdied);
nonone(p);
if(strcmp(up->user, p->user) != 0 && !iseve())
error(Eperm);
if(!emptystr(d->uid) && strcmp(d->uid, p->user) != 0){
if(strcmp(d->uid, "none") != 0 && !iseve())
error(Eperm);
kstrdup(&p->user, d->uid);
}
/* p->procmode determines default mode for files in /proc */
if(d->mode != ~0UL)
p->procmode = d->mode&0777;
qunlock(&p->debug);
poperror();
poperror();
free(d);
return n;
}
static void
procclose(Chan *c)
{
Segio *sio;
if((c->flag & COPEN) == 0)
return;
switch(QID(c->qid)){
case Qtrace:
lock(&tlock);
if(topens > 0)
topens--;
if(topens == 0)
proctrace = nil;
unlock(&tlock);
return;
case Qmem:
sio = c->aux;
if(sio != nil){
c->aux = nil;
segio(sio, nil, nil, 0, 0, 0);
free(sio);
}
return;
}
}
static int
procargs(Proc *p, char *buf, int nbuf)
{
int j, k, m;
char *a;
int n;
a = p->args;
if(p->setargs)
return snprint(buf, nbuf, "%s [%s]", p->text, p->args);
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 int
prochaswaitq(void *x)
{
Chan *c;
Proc *p;
c = (Chan *)x;
p = proctab(SLOT(c->qid));
return p->pid != PID(c->qid) || p->waitq != nil;
}
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
readns1(Chan *c, Proc *p, char *buf, int nbuf)
{
Pgrp *pg;
Mount *t, *cm;
Mhead *f, *mh;
ulong minid, bestmid;
char flag[10], *srv;
int i;
pg = p->pgrp;
if(pg == nil || p->dot == nil || p->pid != PID(c->qid))
error(Eprocdied);
bestmid = ~0;
minid = c->nrock;
if(minid == bestmid)
return 0;
rlock(&pg->ns);
mh = nil;
cm = nil;
for(i = 0; i < MNTHASH; i++) {
for(f = pg->mnthash[i]; f != nil; f = f->hash) {
rlock(&f->lock);
for(t = f->mount; t != nil; t = t->next) {
if(t->mountid >= minid && t->mountid < bestmid) {
bestmid = t->mountid;
cm = t;
mh = f;
}
}
runlock(&f->lock);
}
}
if(bestmid == ~0) {
c->nrock = bestmid;
i = snprint(buf, nbuf, "cd %q\n", p->dot->path->s);
} else {
c->nrock = bestmid+1;
int2flag(cm->mflag, flag);
if(strcmp(cm->to->path->s, "#M") == 0){
srv = srvname(cm->to->mchan);
i = snprint(buf, nbuf, (cm->spec && *cm->spec)?
"mount %s %q %q %q\n": "mount %s %q %q\n", flag,
srv? srv: cm->to->mchan->path->s, mh->from->path->s, cm->spec);
free(srv);
}else{
i = snprint(buf, nbuf, "bind %s %q %q\n", flag,
cm->to->path->s, mh->from->path->s);
}
}
runlock(&pg->ns);
return i;
}
int
procfdprint(Chan *c, int fd, char *s, int ns)
{
return 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, c->qid.vers, c->qid.type,
c->iounit, c->offset, c->path->s);
}
static int
readfd1(Chan *c, Proc *p, char *buf, int nbuf)
{
Fgrp *fg;
int n, i;
fg = p->fgrp;
if(fg == nil || p->dot == nil || p->pid != PID(c->qid))
return 0;
if(c->nrock == 0){
c->nrock = 1;
return snprint(buf, nbuf, "%s\n", p->dot->path->s);
}
lock(fg);
n = 0;
for(;;){
i = c->nrock-1;
if(i < 0 || i > fg->maxfd)
break;
c->nrock++;
if(fg->fd[i] != nil){
n = procfdprint(fg->fd[i], i, buf, nbuf);
break;
}
}
unlock(fg);
return n;
}
/*
* setupwatchpts(Proc *p, Watchpt *wp, int nwp) is defined for all arches separately.
* It tests whether wp is a valid set of watchpoints and errors out otherwise.
* If and only if they are valid, it sets up all watchpoints (clearing any preexisting ones).
* This is to make sure that failed writes to watchpt don't touch the existing watchpoints.
*/
static void
clearwatchpt(Proc *p)
{
setupwatchpts(p, nil, 0);
free(p->watchpt);
p->watchpt = nil;
p->nwatchpt = 0;
}
static int
lenwatchpt(Proc *pr)
{
/* careful, not holding debug lock */
return pr->nwatchpt * (10 + 4 * sizeof(uintptr));
}
static int
readwatchpt(Proc *pr, char *buf, int nbuf)
{
char *p, *e;
Watchpt *w;
p = buf;
e = buf + nbuf;
/* careful, length has to match lenwatchpt() */
for(w = pr->watchpt; w < pr->watchpt + pr->nwatchpt; w++)
p = seprint(p, e, sizeof(uintptr) == 8 ? "%c%c%c %#.16p %#.16p\n" : "%c%c%c %#.8p %#.8p\n",
(w->type & WATCHRD) != 0 ? 'r' : '-',
(w->type & WATCHWR) != 0 ? 'w' : '-',
(w->type & WATCHEX) != 0 ? 'x' : '-',
(void *) w->addr, (void *) w->len);
return p - buf;
}
static int
writewatchpt(Proc *pr, char *buf, int nbuf, uvlong offset)
{
char *p, *q, *e;
char line[256], *f[4];
Watchpt *wp, *wq;
int rc, nwp, nwp0;
uvlong x;
p = buf;
e = buf + nbuf;
if(offset != 0)
nwp0 = pr->nwatchpt;
else
nwp0 = 0;
nwp = 0;
for(q = p; q < e; q++)
nwp += *q == '\n';
if(nwp > 65536) error(Egreg);
wp = malloc((nwp0+nwp) * sizeof(Watchpt));
if(wp == nil) error(Enomem);
if(waserror()){
free(wp);
nexterror();
}
if(nwp0 > 0)
memmove(wp, pr->watchpt, sizeof(Watchpt) * nwp0);
for(wq = wp + nwp0; wq < wp + nwp0+nwp;){
q = memchr(p, '\n', e - p);
if(q == nil)
break;
if(q - p > sizeof(line) - 1)
error("line too long");
memmove(line, p, q - p);
line[q - p] = 0;
p = q + 1;
rc = tokenize(line, f, nelem(f));
if(rc == 0) continue;
if(rc != 3)
error("wrong number of fields");
for(q = f[0]; *q != 0; q++)
switch(*q){
case 'r': if((wq->type & WATCHRD) != 0) goto tinval; wq->type |= WATCHRD; break;
case 'w': if((wq->type & WATCHWR) != 0) goto tinval; wq->type |= WATCHWR; break;
case 'x': if((wq->type & WATCHEX) != 0) goto tinval; wq->type |= WATCHEX; break;
case '-': break;
default: tinval: error("invalid type");
}
x = strtoull(f[1], &q, 0);
if(f[1] == q || *q != 0 || x != (uintptr) x) error("invalid address");
wq->addr = x;
x = strtoull(f[2], &q, 0);
if(f[2] == q || *q != 0 || x > (uintptr)-wq->addr) error("invalid length");
wq->len = x;
if(wq->addr + wq->len > USTKTOP) error("bad address");
wq++;
}
nwp = wq - (wp + nwp0);
if(nwp == 0 && nwp0 == pr->nwatchpt){
poperror();
free(wp);
return p - buf;
}
setupwatchpts(pr, wp, nwp0 + nwp);
poperror();
free(pr->watchpt);
pr->watchpt = wp;
pr->nwatchpt = nwp0 + nwp;
return p - buf;
}
/*
* userspace can't pass negative file offset for a
* 64 bit kernel address, so we use 63 bit and sign
* extend to 64 bit.
*/
static uintptr
off2addr(vlong off)
{
off <<= 1;
off >>= 1;
return off;
}
static long
procread(Chan *c, void *va, long n, vlong off)
{
char statbuf[1024], *sps;
ulong offset;
int i, j, rsize;
uchar *rptr;
uintptr addr;
Segment *s;
Ureg kur;
Waitq *wq;
Proc *p;
offset = off;
if(c->qid.type & QTDIR)
return devdirread(c, va, n, 0, 0, procgen);
if(QID(c->qid) == Qtrace){
int navail, ne;
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 Qmem:
addr = off2addr(off);
if(addr < KZERO)
return procctlmemio(c, p, addr, va, n, 1);
if(!iseve() || poolisoverlap(secrmem, (uchar*)addr, n))
error(Eperm);
/* validate kernel addresses */
if(addr < (uintptr)end) {
if(addr+n > (uintptr)end)
n = (uintptr)end - addr;
memmove(va, (char*)addr, n);
return n;
}
for(i=0; i<nelem(conf.mem); i++){
Confmem *cm = &conf.mem[i];
/* klimit-1 because klimit might be zero! */
if(cm->kbase <= addr && addr <= cm->klimit-1){
if(addr+n >= cm->klimit-1)
n = cm->klimit - addr;
memmove(va, (char*)addr, n);
return n;
}
}
error(Ebadarg);
case Qctl:
return readnum(offset, va, n, p->pid, NUMSIZE);
case Qnoteid:
return readnum(offset, va, n, p->noteid, NUMSIZE);
case Qppid:
return readnum(offset, va, n, p->parentpid, NUMSIZE);
case Qprofile:
s = p->seg[TSEG];
if(s == nil || s->profile == nil)
error("profile is off");
i = (s->top-s->base)>>LRESPROF;
i *= sizeof(s->profile[0]);
if(i < 0 || offset >= i)
return 0;
if(offset+n > i)
n = i - offset;
memmove(va, ((char*)s->profile)+offset, n);
return n;
case Qproc:
rptr = (uchar*)p;
rsize = sizeof(Proc);
goto regread;
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:
if(p->fpstate != FPinactive)
error(Enoreg);
rptr = (uchar*)p->fpsave;
rsize = sizeof(FPsave);
regread:
if(rptr == nil)
error(Enoreg);
if(offset >= rsize)
return 0;
if(offset+n > rsize)
n = rsize - offset;
memmove(va, rptr+offset, n);
return n;
case Qstatus:
sps = p->psstate;
if(sps == nil)
sps = statename[p->state];
j = snprint(statbuf, sizeof(statbuf),
"%-27s %-27s %-11s "
"%11lud %11lud %11lud "
"%11lud %11lud %11lud "
"%11lud %11lud %11lud\n",
p->text, p->user, sps,
tk2ms(p->time[TUser]),
tk2ms(p->time[TSys]),
tk2ms(MACHP(0)->ticks - p->time[TReal]),
tk2ms(p->time[TCUser]),
tk2ms(p->time[TCSys]),
tk2ms(p->time[TCReal]),
(ulong)(procpagecount(p)*BY2PG/1024),
p->basepri, p->priority);
statbufread:
if(offset >= j)
return 0;
if(offset+n > j)
n = j - offset;
memmove(va, statbuf+offset, n);
return n;
case Qsegment:
j = 0;
for(i = 0; i < NSEG; i++) {
s = p->seg[i];
if(s == nil)
continue;
j += snprint(statbuf+j, sizeof(statbuf)-j,
"%-6s %c%c %8p %8p %4ld\n",
sname[s->type&SG_TYPE],
s->type&SG_FAULT ? 'F' : (s->type&SG_RONLY ? 'R' : ' '),
s->profile ? 'P' : ' ',
s->base, s->top, s->ref);
}
goto statbufread;
case Qwait:
if(!canqlock(&p->qwaitr))
error(Einuse);
if(waserror()) {
qunlock(&p->qwaitr);
nexterror();
}
lock(&p->exl);
while(p->waitq == nil && p->pid == PID(c->qid)) {
if(up == p && p->nchild == 0) {
unlock(&p->exl);
error(Enochild);
}
unlock(&p->exl);
sleep(&p->waitr, prochaswaitq, c);
lock(&p->exl);
}
if(p->pid != PID(c->qid)){
unlock(&p->exl);
error(Eprocdied);
}
wq = p->waitq;
p->waitq = wq->next;
p->nwait--;
unlock(&p->exl);
qunlock(&p->qwaitr);
poperror();
j = snprint(statbuf, sizeof(statbuf), "%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);
offset = 0;
goto statbufread;
}
eqlock(&p->debug);
if(waserror()){
qunlock(&p->debug);
nexterror();
}
if(p->pid != PID(c->qid))
error(Eprocdied);
switch(QID(c->qid)){
case Qns:
case Qfd:
if(offset == 0 || offset != c->mrock)
c->nrock = c->mrock = 0;
do {
if(QID(c->qid) == Qns)
j = readns1(c, p, statbuf, sizeof(statbuf));
else
j = readfd1(c, p, statbuf, sizeof(statbuf));
if(j == 0)
break;
c->mrock += j;
} while(c->mrock <= offset);
i = c->mrock - offset;
qunlock(&p->debug);
poperror();
if(i <= 0 || i > j)
return 0;
if(i < n)
n = i;
offset = j - i;
goto statbufread;
case Qargs:
j = procargs(p, statbuf, sizeof(statbuf));
qunlock(&p->debug);
poperror();
goto statbufread;
case Qwatchpt:
j = readwatchpt(p, statbuf, sizeof(statbuf));
qunlock(&p->debug);
poperror();
goto statbufread;
case Qsyscall:
if(p->syscalltrace != nil)
n = readstr(offset, va, n, p->syscalltrace);
else
n = 0;
break;
case Qnote:
if(n < 1) /* must accept at least the '\0' */
error(Etoosmall);
if(p->nnote == 0)
n = 0;
else {
assert(p->note[0] != nil);
i = strlen(p->note[0]->msg) + 1;
if(i < n)
n = i;
memmove(va, p->note[0]->msg, n-1);
((char*)va)[n-1] = '\0';
free(p->note[0]);
if(--p->nnote == 0)
p->notepending = 0;
else
memmove(&p->note[0], &p->note[1], p->nnote*sizeof(Note*));
p->note[p->nnote] = nil;
}
break;
default:
print("unknown qid in procwread\n");
error(Egreg);
}
qunlock(&p->debug);
poperror();
return n;
}
static long
procwrite(Chan *c, void *va, long n, vlong off)
{
char buf[ERRMAX];
ulong offset;
Proc *p;
offset = off;
if(c->qid.type & QTDIR)
error(Eisdir);
/* use the remembered noteid in the channel qid */
if(QID(c->qid) == Qnotepg) {
if(n >= sizeof(buf))
error(Etoobig);
memmove(buf, va, n);
buf[n] = 0;
postnotepg(NOTEID(c->qid), buf, NUser);
return n;
}
p = proctab(SLOT(c->qid));
eqlock(&p->debug);
if(waserror()){
qunlock(&p->debug);
nexterror();
}
if(p->pid != PID(c->qid))
error(Eprocdied);
switch(QID(c->qid)){
case Qargs:
if(offset != 0 || n >= sizeof(buf))
error(Etoobig);
memmove(buf, va, n);
buf[n] = 0;
kstrdup(&p->args, buf);
p->nargs = 0;
p->setargs = 1;
break;
case Qmem:
if(p->state != Stopped)
error(Ebadctl);
n = procctlmemio(c, p, off2addr(off), va, n, 0);
break;
case Qregs:
if(offset >= sizeof(Ureg))
n = 0;
else if(offset+n > sizeof(Ureg))
n = sizeof(Ureg) - offset;
if(p->dbgreg == nil)
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;
if(p->fpstate != FPinactive || p->fpsave == nil)
error(Enoreg);
memmove((uchar*)p->fpsave+offset, va, n);
break;
case Qctl:
procctlreq(p, va, n);
break;
case Qnote:
if(n >= sizeof(buf))
error(Etoobig);
memmove(buf, va, n);
buf[n] = 0;
if(!postnote(p, 0, buf, NUser))
error("note not posted");
break;
case Qnoteid:
if(offset != 0 || n >= sizeof(buf))
error(Etoobig);
memmove(buf, va, n);
buf[n] = 0;
changenoteid(p, atoi(buf));
break;
case Qwatchpt:
writewatchpt(p, va, n, off);
break;
default:
print("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,
};
static Chan*
proctext(Chan *c, Proc *p)
{
Chan *tc;
Image *i;
Segment *s;
eqlock(&p->seglock);
if(waserror()){
qunlock(&p->seglock);
nexterror();
}
if(p->state == Dead || p->pid != PID(c->qid))
error(Eprocdied);
if((s = p->seg[TSEG]) == nil)
error(Enonexist);
if((i = s->image) == nil)
error(Enonexist);
lock(i);
tc = i->c;
if(i->notext || tc == nil || (tc->flag&COPEN) == 0 || tc->mode != OREAD){
unlock(i);
error(Enonexist);
}
incref(tc);
unlock(i);
qunlock(&p->seglock);
poperror();
return tc;
}
static void
procstopwait(Proc *p, int ctl)
{
char *state;
int pid;
if(p->pdbg != nil)
error(Einuse);
if(procstopped(p) || p->state == Broken)
return;
pid = p->pid;
if(pid == 0)
error(Eprocdied);
if(ctl != 0)
p->procctl = ctl;
if(p == up)
return;
p->pdbg = up;
qunlock(&p->debug);
state = up->psstate;
up->psstate = "Stopwait";
if(waserror()) {
up->psstate = state;
qlock(&p->debug);
if(p->pdbg == up)
p->pdbg = nil;
nexterror();
}
sleep(&up->sleep, procstopped, p);
poperror();
up->psstate = state;
qlock(&p->debug);
if(p->pid != pid)
error(Eprocdied);
}
static void
procctlclosefiles(Proc *p, int all, int fd)
{
Fgrp *f;
Chan *c;
if(fd < 0)
error(Ebadfd);
f = p->fgrp;
if(f == nil)
error(Eprocdied);
incref(f);
lock(f);
while(fd <= f->maxfd){
c = f->fd[fd];
if(c != nil){
f->fd[fd] = nil;
unlock(f);
qunlock(&p->debug);
cclose(c);
qlock(&p->debug);
lock(f);
}
if(!all)
break;
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;
}
static void
procctlreq(Proc *p, char *va, int n)
{
static Note killnote = {
"sys: killed",
NExit,
1,
};
Segment *s;
uintptr npc;
int pri;
Cmdbuf *cb;
Cmdtab *ct;
vlong time;
char *e;
void (*pt)(Proc*, int, vlong);
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;
incref(&killnote);
pushnote(p, &killnote);
ready(p);
break;
default:
p->procctl = Proc_exitme;
incref(&killnote);
pushnote(p, &killnote);
}
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 == nil || (s->type&SG_TYPE) != SG_TEXT) /* won't expand */
error(Egreg);
eqlock(s);
npc = (s->top-s->base)>>LRESPROF;
if(s->profile == nil){
s->profile = malloc(npc*sizeof(*s->profile));
if(s->profile == nil){
qunlock(s);
error(Enomem);
}
} else {
memset(s->profile, 0, npc*sizeof(*s->profile));
}
qunlock(s);
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;
case CMinterrupt:
procinterrupt(p);
break;
case CMnointerrupt:
if(p->nnote == 0)
p->notepending = 0;
else
error("notes pending");
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 == nil)
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 != nil)
edfstop(p);
break;
case CMevent:
pt = proctrace;
if(up->trace && pt != nil)
pt(up, SUser, 0);
break;
}
poperror();
free(cb);
}
static int
procstopped(void *a)
{
return ((Proc*)a)->state == Stopped;
}
static long
procctlmemio(Chan *c, Proc *p, uintptr offset, void *a, long n, int read)
{
Segio *sio;
Segment *s;
int i;
s = seg(p, offset, 0);
if(s == nil)
error(Ebadarg);
eqlock(&p->seglock);
if(waserror()) {
qunlock(&p->seglock);
nexterror();
}
if(p->state == Dead || p->pid != PID(c->qid))
error(Eprocdied);
for(i = 0; i < NSEG; i++) {
if(p->seg[i] == s)
break;
}
if(i == NSEG)
error(Egreg); /* segment gone */
eqlock(s);
if(waserror()){
qunlock(s);
nexterror();
}
if(!read && (s->type&SG_TYPE) == SG_TEXT) {
s = txt2data(s);
p->seg[i] = s;
}
offset -= s->base;
incref(s); /* for us while we copy */
qunlock(s);
poperror();
sio = c->aux;
if(sio == nil){
sio = smalloc(sizeof(Segio));
c->aux = sio;
}
qunlock(&p->seglock);
poperror();
if(waserror()) {
putseg(s);
nexterror();
}
n = segio(sio, s, a, n, offset, read);
putseg(s);
poperror();
if(!read)
p->newtlb = 1;
return n;
}