plan9fox/sys/src/9/port/devproc.c
cinap_lenrek 55c3138c64 kernel: ensure that all accesses to Mhead.mount is done with Mhead.lock acquired
The Mhead structures have two sources of references to them:

- from Pgrp.mnthash hash-table
- from a channels Chan.umh pointer as returned by namec() for a union directory

Unless one holds the Mhead.lock RWLock, the Mhead.mount chain
can be mutated by eigther cmount(), cunmount() or closepgrp().

Readers, skipping acquiering the lock where:

mountfix(): responsible for rewriting directory entries for
union directory reads; was walking the Mhead.mount chain to
detect if the passed channel itself appears in the mount list.

cmount(): had a check and copy when "new" chan was a union itself
and if the MCREATE flag is set and would copy the mount table.
All this needs to be done with Mhead read-locked while copying
the mount entries.

devproc(): in the handler for reading /proc/n/ns file.

namec(): while checking if the Chan->umh should be initialized.

In addition to this, cmount() is changed to do the mountfree()
of the original mount chain when MREPL is done after releasing
the locks.

Also, some cosmetic changes...
2021-10-03 15:56:51 +00:00

1675 lines
30 KiB
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 SLOT(q) (((((ulong)(q).path)&0x07FFFFFE0)>>QSHIFT)-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);
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)
{
if(conf.nproc >= (1<<(16-QSHIFT))-1)
print("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; i < conf.nproc; i++){
pp = proctab(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
postnotepg(ulong noteid, char *n, int flag)
{
Proc *p;
int i;
for(i = 0; i < conf.nproc; i++){
p = proctab(i);
if(p == up)
continue;
if(p->noteid != noteid || p->kp)
continue;
qlock(&p->debug);
if(p->noteid == noteid)
postnote(p, 0, n, flag);
qunlock(&p->debug);
}
}
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->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 {
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';
if(--p->nnote == 0)
p->notepending = 0;
memmove(p->note, p->note+1, p->nnote*sizeof(Note));
}
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)
{
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;
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 == 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:
postnote(p, 0, nil, NUser);
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;
}